Skip to main content

wisp_storybook/stories/
s_transform_nesting.rs

1//! Story: nested containers demonstrating parent-child transform composition (M0.7 + M0.8).
2
3use glam::Vec2;
4use wisp::application::Application;
5use wisp::{Container, Sprite, Stage, Texture};
6
7use crate::story::Story;
8
9pub fn story() -> Story {
10    Story {
11        id: "transform-nesting",
12        category: "Scene Graph",
13        title: "Nested transforms",
14        milestone: "M0.7 / M0.8",
15        writeup: include_str!("writeups/transform_nesting.md"),
16        build,
17        tick: Some(tick),
18    }
19}
20
21fn build(app: &Application, stage: &mut Stage) {
22    // Three colored 4×4 textures so each ring is visually distinct.
23    let red = solid_texture(app, [255, 100, 100, 255]);
24    let green = solid_texture(app, [100, 255, 100, 255]);
25    let blue = solid_texture(app, [100, 180, 255, 255]);
26
27    // Ring 1: outer container with 3 sprites.
28    let outer = Container::default();
29    let outer_id = stage.add_child(stage.root(), outer).expect("outer");
30    add_orbiters(stage, outer_id, &red, 0.55, 6);
31
32    // Ring 2: nested container, smaller orbit.
33    let mid = Container::default();
34    let mid_id = stage.add_child(outer_id, mid).expect("mid");
35    add_orbiters(stage, mid_id, &green, 0.35, 4);
36
37    // Ring 3: deepest container, smallest orbit.
38    let inner = Container::default();
39    let inner_id = stage.add_child(mid_id, inner).expect("inner");
40    add_orbiters(stage, inner_id, &blue, 0.18, 3);
41}
42
43fn solid_texture(app: &Application, rgba: [u8; 4]) -> Texture {
44    let mut bytes = Vec::with_capacity(4 * 4 * 4);
45    for _ in 0..(4 * 4) {
46        bytes.extend_from_slice(&rgba);
47    }
48    Texture::from_rgba(app, 4, 4, &bytes)
49}
50
51fn add_orbiters(stage: &mut Stage, parent: wisp::NodeId, texture: &Texture, radius: f32, n: u8) {
52    for i in 0..n {
53        let theta = std::f32::consts::TAU * f32::from(i) / f32::from(n);
54        let mut sprite = Sprite::from_texture(texture.clone()).with_anchor(Vec2::splat(0.5));
55        sprite.container.transform.position = Vec2::new(theta.cos() * radius, theta.sin() * radius);
56        sprite.container.transform.scale = Vec2::splat(0.08);
57        let _ = stage.add_child(parent, sprite);
58    }
59}
60
61fn tick(stage: &mut Stage, t: f32) {
62    // Rotate each ring at a different rate; outer slowest, inner fastest.
63    let root_children: Vec<_> = stage
64        .get(stage.root())
65        .map(|n| n.container().children().collect())
66        .unwrap_or_default();
67    let Some(outer_id) = root_children.first().copied() else {
68        return;
69    };
70    set_rotation(stage, outer_id, t * 0.3);
71
72    let mid_children: Vec<_> = stage
73        .get(outer_id)
74        .map(|n| n.container().children().collect())
75        .unwrap_or_default();
76    // The first 6 children are sprites (the orbiters). The 7th is the next ring.
77    if let Some(mid_id) = mid_children.iter().next_back().copied() {
78        set_rotation(stage, mid_id, t * -0.7);
79        let inner_children: Vec<_> = stage
80            .get(mid_id)
81            .map(|n| n.container().children().collect())
82            .unwrap_or_default();
83        if let Some(inner_id) = inner_children.iter().next_back().copied() {
84            set_rotation(stage, inner_id, t * 1.3);
85        }
86    }
87}
88
89fn set_rotation(stage: &mut Stage, id: wisp::NodeId, angle: f32) {
90    if let Some(node) = stage.get_mut(id) {
91        node.container_mut().transform.rotation = angle;
92    }
93}