Skip to content

Scene graph

A Scene renders a tree of Nodes. Each node has a transform, may carry a mesh, may hold child nodes, and may run behavior through attached components. Child transforms compose with their parent, so moving or rotating a node moves its whole subtree.

A solar system of nested pivots. Every ring, tilt, orbit, and body is a node in one tree, so parent transforms compose down it.

A Node holds a localTransform, a Matrix4 relative to its parent. Add a node to the scene with scene.add, which attaches it under the scene’s root node.

final node = Node(mesh: myMesh)
..localTransform = vm.Matrix4.translation(vm.Vector3(2, 0, 0));
scene.add(node);

A node’s world transform is its parent’s world transform times its own local transform. The render loop recomputes this each frame, so animating a transform is just assigning a new localTransform.

Add children to any node with node.add. The child’s transform is then relative to the parent.

final orbit = Node(); // a pivot at the origin
final planet = Node(mesh: planetMesh)
..localTransform = vm.Matrix4.translation(vm.Vector3(5, 0, 0));
orbit.add(planet);
scene.add(orbit);

Rotating orbit now sweeps planet around the origin, because the planet’s position is expressed in the pivot’s rotating space. Nest another pivot under the planet and its children orbit the planet in turn, which is exactly how the demo’s moon works.

A Component is behavior bound to a node. Override update, which the engine calls every frame with the elapsed seconds, and read or write node to affect the owner. The demo spins each pivot with a small component.

class Spin extends Component {
Spin(this.radiansPerSecond);
final double radiansPerSecond;
@override
void update(double deltaSeconds) {
node.localTransform =
node.localTransform * vm.Matrix4.rotationY(radiansPerSecond * deltaSeconds);
}
}
final pivot = Node()..addComponent(Spin(0.5));

Meshes are attached the same way. Node(mesh: ...) is a convenience that adds a mesh-drawing component for you, so a node can carry rendering, custom behavior, and other components together.

The scene’s root is scene.root, an ordinary Node you can traverse. Nodes carry an optional name, and getComponents<T>() returns the attached components of a type, which is how you reach a node’s mesh, light, or your own components after building the tree.

Scene, Node, and Component.