Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Scene hierarchy 🌳

When objects must move together - wheels on a car, segments of a robot arm, planets around a sun - you want a hierarchy: change the parent, and the children come along for the ride.

In renderling that is the NestedTransform. Each node has a local transform, relative to its parent. The node’s global transform is the composition of every local transform up the chain, and it is recalculated automatically whenever anything in the chain changes.

Building a hierarchy

Create nodes with Stage::new_nested_transform, position them with their local_* builder methods, and link them with NestedTransform::add_child. Then attach primitives with Primitive::with_transform, handing the node a reference:

    use renderling::{
        camera::Camera,
        context::Context,
        glam::{Mat4, Quat, Vec3, Vec4},
        stage::Stage,
    };

    let ctx = Context::headless(512, 512).await;
    let stage: Stage = ctx
        .new_stage()
        .with_background_color(Vec4::new(0.25, 0.25, 0.25, 1.0));

    let _camera: Camera = {
        let aspect = 1.0;
        let fovy = core::f32::consts::PI / 4.0;
        let projection = Mat4::perspective_rh(fovy, aspect, 0.1, 100.0);
        let eye = Vec3::new(0.0, 2.0, 6.0);
        let target = Vec3::ZERO;
        let up = Vec3::Y;
        let view = Mat4::look_at_rh(eye, target, up);

        stage
            .new_camera()
            .with_projection_and_view(projection, view)
    };

    // Build a three-level hierarchy: root -> child -> grandchild.
    //
    // Each node has a _local_ transform, relative to its parent.
    let root = stage.new_nested_transform();
    let child = stage
        .new_nested_transform()
        .with_local_translation(Vec3::X * 1.2);
    let grandchild = stage
        .new_nested_transform()
        .with_local_translation(Vec3::X * 1.2);
    root.add_child(&child);
    child.add_child(&grandchild);

    // Attach a colored cube to each node. Primitives take the node's
    // _global_ transform, which is composed from the whole hierarchy.
    let cube = |color: Vec4| {
        stage
            .new_primitive()
            .with_vertices(stage.new_vertices(unit_cube()))
            .with_material(
                stage
                    .new_material()
                    .with_albedo_factor(color)
                    .with_has_lighting(false),
            )
    };
    cube(Vec4::new(0.6, 0.6, 0.6, 1.0)).with_transform(&root);
    cube(Vec4::new(0.0, 0.85, 0.85, 1.0)).with_transform(&child);
    cube(Vec4::new(0.95, 0.85, 0.1, 1.0)).with_transform(&grandchild);

    let frame = ctx.get_next_frame().unwrap();
    stage.render(&frame.view());
    let img_before = frame.read_image().await.unwrap();
    img_before.save("scene/hierarchy.png").unwrap();
    frame.present();

a chain of three colored cubes: gray root, cyan child, yellow grandchild

Note the cubes are all attached to nodes, not positioned directly. The cyan cube sits at root * child, and the yellow one at root * child * grandchild.

Moving the parent moves the subtree

Because children are positioned relative to their parent, changing the parent moves everything below it:

    // Rotate the root - the entire hierarchy rotates with it, because
    // the child and grandchild are positioned relative to the root.
    root.set_local_rotation(Quat::from_axis_angle(Vec3::Y, core::f32::consts::FRAC_PI_4));

    let frame = ctx.get_next_frame().unwrap();
    stage.render(&frame.view());
    let img_parent = frame.read_image().await.unwrap();
    img_parent.save("scene/moved-parent.png").unwrap();
    frame.present();

the whole chain rotated by 45 degrees

One rotation of the root carried the entire chain.

Moving a child moves only its subtree

Change a node’s local transform, and its parent stays put:

    // Move the child - the root stays put, and the grandchild follows the
    // child.
    child.set_local_translation(Vec3::new(1.2, 1.2, 0.0));

    let frame = ctx.get_next_frame().unwrap();
    stage.render(&frame.view());
    let img_child = frame.read_image().await.unwrap();
    img_child.save("scene/moved-child.png").unwrap();
    frame.present();

the child cube and grandchild have moved up, the gray root stays put

Notes