flutter3d
Showcase Changelog 38 packages API reference

The scene graph

since 0.1.0 Scene and geometry

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');
}