Three.js From Zero · Article s8-04

S8-04 Scene Authoring

Season 8 · Article 04

Scene Authoring Tools

Build an editor inside your app. Click to add objects. TransformControls to move/rotate/scale. Inspector panel for properties. Save/load JSON. Non-programmers can populate your world.

1. The minimum viable editor

  • Click canvas → raycast → select object.
  • TransformControls attached → drag to move/rotate/scale.
  • Delete key → remove.
  • Toolbar → add primitives (box, sphere).
  • Save → serialize scene to JSON + download.
  • Load → parse JSON + rebuild.

2. TransformControls

import { TransformControls } from 'three/addons/controls/TransformControls.js';

const tc = new TransformControls(camera, renderer.domElement);
tc.addEventListener('dragging-changed', e => {
  orbitControls.enabled = !e.value;  // disable orbit while dragging
});
tc.setMode('translate');  // or 'rotate', 'scale'
scene.add(tc);

function select(obj) {
  tc.attach(obj);
  updateInspector(obj);
}

3. Serialize to JSON

Three.js's toJSON:

const json = scene.toJSON();
downloadAsFile('scene.json', JSON.stringify(json, null, 2));

// Load
const loaded = await fetch('scene.json').then(r => r.json());
const loader = new THREE.ObjectLoader();
const scene2 = loader.parse(loaded);

Handles geometry, materials, lights, cameras, textures (embedded or referenced).

4. Inspector panel

function updateInspector(obj) {
  inspectorEl.innerHTML = `
    <div>Position: ${obj.position.x.toFixed(2)}, ${obj.position.y.toFixed(2)}, ...</div>
    <div>Rotation: ${obj.rotation.x}...</div>
    <input type="color" value="${obj.material?.color?.getHexString?.()}" onchange="obj.material.color.set(this.value)" />
  `;
}

For real apps: lil-gui or leva. Auto-generated GUI from an object schema.

5. lil-gui for quick inspectors

import GUI from 'lil-gui';
const gui = new GUI();

function buildInspector(obj) {
  gui.destroy(); gui = new GUI();
  gui.add(obj.position, 'x', -10, 10);
  gui.add(obj.position, 'y', -10, 10);
  gui.addColor(obj.material, 'color');
}

6. Undo/redo

const history = [];
let historyIdx = -1;

function commit(action) {
  history.splice(historyIdx + 1);  // drop redo branch
  history.push(action);
  historyIdx++;
}

function undo() { if (historyIdx >= 0) history[historyIdx--].undo(); }
function redo() { if (historyIdx + 1 < history.length) history[++historyIdx].do(); }

7. Live demo — mini editor

Click to select. T/R/S to switch mode. Delete to remove. Add buttons to spawn primitives. Save dumps JSON to console.

8. three.js editor as reference

Official editor is open-source — study its code for a production pattern. It's basically what we built above × 20 features.

9. Takeaways

  • TransformControls: drag handles for move/rotate/scale.
  • scene.toJSON() / ObjectLoader.parse() for persistence.
  • lil-gui for quick inspectors, leva for fancier.
  • Undo/redo: command pattern with do/undo closures.
  • Official three.js editor source is a masterclass.