Command Palette

Search for a command to run...

Hectal
Course outline
Topic 3.5Beginner ~19 min · step 5 of 7

The Scene Graph

In one line

Objects form a tree: a parent carries its children. Move, turn or scale the bike and the wheels follow, while each wheel still spins on its own. You learn how local and world positions relate and how to print the tree.

0/7 · 0%

Think of it like this

A person on a train. The person walks down the carriage (their own movement) while the carriage moves with the train (the parent's movement). To someone on the platform the two add together, and nobody has to calculate the sum.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

scene graph
The tree of objects in a scene. Each object's position, rotation and scale are measured from its parent.
local transform
An object's position, rotation and scale relative to its parent.
world transform
Its position, rotation and scale in the scene's own space, after all its ancestors' transforms are applied. Stored in matrixWorld.
pivot
The point an object turns around. In three.js it is the object's own origin, so you move a pivot by wrapping the object in a group.

Step by step

01Make a group for the whole bike

const bike = new THREE.Group(); bike.name = "bike"; scene.add(bike);. The group draws nothing; it is a handle for the whole bike.

02Make a group for each part that spins

createWheel returns a group placed at the axle: wheel.position.set(x, 0.35, 0). The torus and the spoke inside sit at the group's origin, so wheel.rotation.z spins them around the axle.

03Add the parts to the bike

bike.add(rearWheel, frontWheel). Their positions are now measured from the bike's origin, which is the middle of the stand.

04Move the parent and read the world

bike.position.x = 1, then scene.updateMatrixWorld() and wheel.getWorldPosition(new THREE.Vector3()). The world position changes, the local one does not.

Try it yourself

  1. 1

    Turn the parent

    In the first example replace bike.scale.setScalar(2); with bike.rotation.y = Math.PI / 2; (rename the label to "bike turned 90"). The last line prints world: 1, 0.35, -0.58: the front wheel swung around the bike's origin, then moved with the bike to x = 1.

  2. 2

    Turn the parent and watch the pivot

    In the first example add bike.rotation.y = time / 1500; to the loop. The whole bike, with both wheels, swings around the point (0, 0, 0), the bike group's origin, not around its own middle. A group turns about its own origin; its children go with it.

Code & diagrams

A bike that rolls while its wheels turn New tab

Two levels of movement: the bike's position (parent) and each wheel's rotation (child). The console shows the world position after each parent change.

idle

Console

Run the scene to see its console.

Expected console output

start              | front wheel local x: 0.58 | world: 0.58, 0.35, 0
bike moved +1      | front wheel local x: 0.58 | world: 1.58, 0.35, 0
bike scaled x2     | front wheel local x: 0.58 | world: 2.16, 0.7, 0

Try it: interactive

Move the parent, read the child

Move and turn the frame (parent) with the sliders and watch the wheel's local numbers stay the same while its world numbers change. Then scale the parent.

Print the tree: what the scene inspector shows New tab

A small recursive function prints names and types. The two tyres share one geometry and one material: the tree has two tyres, the GPU holds one shape.

idle

Console

Run the scene to see its console.

Expected console output

(no name)  [Scene]
  turntable  [Group]
    bike  [Group]
      rear-wheel  [Group]
        tyre  [TorusGeometry]
      front-wheel  [Group]
        tyre  [TorusGeometry]
  (no name)  [AmbientLight]
tyres: 2 | same material: true | same geometry: true

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Squash a parent that carries a turned child

In the first example delete the line bike.scale.setScalar(1); and change bike.scale.setScalar(2); to bike.scale.set(1, 3, 1);.

terminal
$ Run
── what you'll see ──
The last console line reads `world: 1.58, 1.05, 0` and the wheels are stretched into tall ovals (three times as tall as wide), cut off at the top of the preview.
No error and no warning: it is just the maths.

Myth vs fact

Myth

position is the object's place in the scene.

Fact

It is its place in its parent. It equals the scene position only when the parent is the scene, or when every ancestor is at the origin, unturned and unscaled.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    render() calls scene.updateMatrixWorld() every frame. If you read getWorldPosition between a change and the next render(), call updateMatrixWorld() yourself, as the examples do, or you get the previous frame's answer.

  • ▸

    For objects that never move, set object.matrixAutoUpdate = false (and call updateMatrix() once): the tree walk skips recomputing them. Phase 17 shows when this matters.

Remember this

  1. 1

    The problem this solves: a bike has about twenty parts that move together, and some also move alone (a wheel turns). Setting every part's world position each frame is twenty updates and a lot of maths. With a tree you move the bike and the parts follow.

  2. 2

    The scene graph is the tree of Object3Ds from the Scene down. Each position, rotation and scale is local, measured in the parent's space. The world position comes from multiplying the parents' matrices down from the scene (Topic 2.5). You only set local numbers.

  3. 3

    Move a parent, children follow. The wheels sit at x = −0.55 and 0.58 inside bike. bike.position.x = 1 changes no wheel number, and the front wheel's world x goes from 0.58 to 1.58. Wheels spin with wheel.rotation.z while the bike rolls: one movement on each level.

  4. 4

    Scale a parent and everything scales, positions included. With bike.scale.setScalar(2) the front wheel's world position is (2.16, 0.7, 0): its local (0.58, 0.35, 0) doubled, plus the bike's x of 1. A uniform scale is harmless; a non-uniform one on a parent stretches turned children (breakIt). Keep scale on leaf meshes.

  5. 5

    Turn a parent and children orbit its origin. A 90° turn about y moves the front wheel from (0.58, 0.35, 0) to about (0, 0.35, −0.58) in the bike's frame, and (1, 0.35, −0.58) with the bike at x = 1. A group turns about its own origin, so to spin a part about its own centre put it in a group at that centre (a wheel group at the axle). The Sun–Earth–Moon demo works the same way.

  6. 6

    Why keep the tree. Hiding a parent hides its subtree (Topic 3.4), disposing of a part walks its subtree, and picking (Phase 8) walks up the parent chain. Keep it shallow and named: a group per part, and one top group (bike) that tools and tests can find. The scene inspector under each canvas shows this tree.

Explain it without notes

01

A wheel is a child of the bike. The bike moves 1 m forward. What are the wheel's local and world positions before and after?

02

Why does a child turn around its parent's origin and not its own centre? How do you make a part spin about its own centre?

Practice

01

Build a crank group that holds two pedals 0.17 m on each side of its centre and turns once every 2 seconds.

02

Print how many objects are below bike in a scene where the bike has two wheels with two meshes each.

Done when you can

  • Explain local versus world position with the bike and its wheels

  • Move and scale a parent and predict a child's world position

  • Put a part's pivot at the right point with a group

  • Read the scene inspector's tree and match it to the code