The scene graph
Open the live demo · Read the source · View on GitHub
A scene graph stores local transforms in a hierarchy. Moving a parent changes where every child appears, so an orbit can be expressed with a pivot instead of recalculating each object's world position by hand.
Step 1: Build the hierarchy #
The system pivot owns the planet orbit. The planet orbit owns both the planet and a second pivot, which in turn owns the moon. Each child keeps a small local transform relative to its parent.
_systemPivot = SceneNode(name: 'system pivot');
_planetPivot = SceneNode(name: 'planet orbit')..setPosition(3.1, 0.0, 0.0);
_moonPivot = SceneNode(name: 'moon orbit')..setPosition(1.15, 0.0, 0.0);
final MeshNode planet = MeshNode(
sphere,
Material(
name: 'blue planet',
baseColor: Vector4(0.18, 0.48, 0.88, 1.0),
roughness: 0.62,
),
name: 'planet',
)..setUniformScale(0.72);
_moon = MeshNode(
sphere,
Material(
name: 'moon rock',
baseColor: Vector4(0.72, 0.7, 0.66, 1.0),
roughness: 0.92,
),
name: 'moon',
)..setUniformScale(0.25);
_systemPivot.add(_planetPivot);
_planetPivot
..add(planet)
..add(_moonPivot);
_moonPivot.add(_moon);
Step 2: Attach the branch to the scene #
Adding the top pivot registers every mesh below it with the scene. The root can also hold unrelated nodes, such as the star and its point light.
final MeshNode star = MeshNode(
sphere,
Material(
name: 'star',
baseColor: Vector4(1.0, 0.56, 0.12, 1.0),
emissive: Vector3(1.0, 0.28, 0.04),
emissiveStrength: 1.8,
roughness: 0.5,
),
name: 'star',
)..setUniformScale(1.15);
final Scene scene = Scene()
..ambientColor = Vector3(0.2, 0.26, 0.42)
..ambientIntensity = 0.12
..add(star)
..add(_systemPivot)
..add(
LightNode(
type: LightType.point,
intensity: 24.0,
range: 12.0,
name: 'starlight',
),
);
Step 3: Rotate the pivots #
The system pivot carries the whole branch around the star. Rotating the planet pivot at another rate moves the moon around the planet without changing the moon's own local position.
_angle += dt * speed;
_systemPivot.setRotationYawPitchRoll(_angle, 0.0, 0.0);
_planetPivot.setRotationYawPitchRoll(_angle * 2.7, 0.0, 0.0);
Step 4: Read a current world transform #
readWorldPosition resolves the ancestor chain when a version changed. The
page checks that the moon kept its parent chain, received a new world version,
and moved from the position recorded before the first update.
final Vector3 current = _moon.readWorldPosition();
final bool hierarchyIsIntact =
_moon.parent == _moonPivot &&
_moonPivot.parent == _planetPivot &&
_planetPivot.parent == _systemPivot;
if (!hierarchyIsIntact ||
(current - _initialMoonPosition).length < 0.001 ||
_moon.worldVersion == _initialMoonVersion ||
frame.drawCalls < 3) {
throw StateError('the child transforms did not follow their parents');
}