Skip to content

Adding a Scene

Alex Coulombe edited this page Apr 30, 2026 · 1 revision

Adding a Scene

This walks through adding a fourth scene from scratch. Use four-seasons.ts as your reference implementation.


Step 1 — Create the scene module

src/scenes/<slug>.ts

Minimal scaffold:

import { Engine, Scene, Vector3, Color4, HemisphericLight, MeshBuilder } from "@babylonjs/core";
import type { SceneApi, SceneDefinition } from "../types";
import { Controls } from "../controls";

const mySceneDef: SceneDefinition = {
  id: "my-scene",            // must match the slug you'll use in the URL: #scene=my-scene
  title: "My Scene",
  subtitle: "Short description shown in the UI header",
  placeholder: false,        // set true while geometry is placeholder; shows "Real venue model loads in the live demo"
  options: [
    {
      id: "time",
      label: "Time of day",
      choices: [
        { value: "day",   label: "Day" },
        { value: "night", label: "Night" },
      ],
      default: "day",
    },
  ],
  build(engine: Engine): SceneApi {
    const canvas = engine.getRenderingCanvas() as HTMLCanvasElement;
    const scene = new Scene(engine);
    scene.clearColor = new Color4(0.5, 0.7, 0.9, 1);
    scene.collisionsEnabled = true;

    new HemisphericLight("sky", new Vector3(0, 1, 0), scene).intensity = 0.8;

    // Ground — always add collision so the player doesn't fall through
    const ground = MeshBuilder.CreateGround("ground", { width: 40, height: 40 }, scene);
    ground.checkCollisions = true;

    // Load your venue model
    import("@babylonjs/loaders/glTF").then(() => {
      BABYLON.SceneLoader.ImportMeshAsync("", "/models/my-scene/", "venue.glb", scene)
        .catch(err => console.warn("Model load failed, falling back to placeholders", err));
    });

    const controls = new Controls(
      scene, canvas,
      new Vector3(0, 1.7, 8),   // spawn position
      new Vector3(0, 1.7, 0),   // look target
    );

    scene.onBeforeRenderObservable.add(() => {
      controls.tick(engine, engine.getDeltaTime() / 1000);
    });

    return {
      scene,
      camera: controls.camera,
      meshes: [],
      applyOption(id, value) {
        if (id === "time") {
          scene.clearColor = value === "night"
            ? new Color4(0.02, 0.02, 0.08, 1)
            : new Color4(0.5, 0.7, 0.9, 1);
        }
      },
      dispose() {
        controls.dispose();
        scene.dispose();
      },
    };
  },
};

export default mySceneDef;

Step 2 — Register in main.ts + types.ts

src/types.ts — add to the SceneId union:

// before:
export type SceneId = "four-seasons" | "carol" | "dnd";
// after:
export type SceneId = "four-seasons" | "carol" | "dnd" | "my-scene";

src/main.ts — import and add to SCENES:

import mySceneDef from "./scenes/my-scene";

const SCENES: Record<SceneId, SceneDefinition> = {
  "four-seasons": fourSeasonsDef,
  "carol":        carolDef,
  "dnd":          dndDef,
  "my-scene":     mySceneDef,   // add this line
};

Step 3 — Add models

Place your venue model (GLB preferred, Draco-compressed for size):

public/models/my-scene/venue.glb

Size budget: ≤8 MB total per scene for comfortable phone WebXR. Draco compression typically cuts geometry 6–10×. Use gltfpack to compress:

gltfpack -i venue-raw.glb -cc -o public/models/my-scene/venue.glb

Or gltf-pipeline:

npx gltf-pipeline -i venue-raw.glb -o public/models/my-scene/venue.glb -d

Step 4 — Add design options

Each option maps to an applyOption(id, value) call. Keep options to 2–3 maximum — the UI panel is narrow on phones.

Good option patterns:

  • Lighting preset — swap PointLight diffuse colors and HemisphericLight intensity
  • Layout swap — dispose and rebuild a set of meshes
  • Material toggle — swap PBRMaterial.albedoColor or texture
  • Fog densityscene.fogMode + scene.fogDensity

Step 5 — Screenshot and document

Once the scene renders correctly:

  1. Add a screenshot to public/screenshots/desktop-my-scene-entry.png (1920×1080)
  2. Update README.md — add a row to the Scenes table and a step to "Things to Try"
  3. Update Models and Licenses — add attribution for any third-party models

Example diff

A minimal diff adding a new scene looks like:

+ src/scenes/my-scene.ts        (new file)

  src/types.ts
+   "my-scene" added to SceneId union

  src/main.ts
+   import mySceneDef from "./scenes/my-scene";
+   "my-scene": mySceneDef  added to SCENES map

  README.md
+   Row added to Scenes table
+   Step added to Things to Try

+ public/models/my-scene/venue.glb
+ public/screenshots/desktop-my-scene-entry.png

  LICENSES.md
+   Attribution block for any new third-party models

Clone this wiki locally