Skip to main content

dreamwell_engine/
scene.rs

1//! Scene transform storage and hierarchy propagation.
2//!
3//! `SceneTransforms` holds per-entity local and world transforms.
4//! `propagate_from_roots` walks the hierarchy BFS and multiplies
5//! parent world * child local to produce child world transforms.
6
7use crate::game_object::Transform;
8use crate::hierarchy::SceneHierarchy;
9use crate::transform::GlobalTransform;
10
11/// Per-entity transform storage (SoA layout).
12#[derive(Debug, Clone)]
13pub struct SceneTransforms {
14    /// Local-space transforms (TRS).
15    pub local: Vec<Transform>,
16    /// World-space transforms (4x4 matrices).
17    pub world: Vec<GlobalTransform>,
18    /// Dirty flags — set when local transform changes.
19    pub dirty: Vec<bool>,
20}
21
22impl SceneTransforms {
23    /// Create with pre-allocated capacity. All transforms start at identity.
24    pub fn with_capacity(capacity: usize) -> Self {
25        Self {
26            local: vec![Transform::default(); capacity],
27            world: vec![GlobalTransform::identity(); capacity],
28            dirty: vec![true; capacity],
29        }
30    }
31
32    /// Mark an entity's transform as dirty (needs world recalculation).
33    pub fn mark_dirty(&mut self, entity: u32) {
34        let idx = entity as usize;
35        if idx < self.dirty.len() {
36            self.dirty[idx] = true;
37        }
38    }
39
40    /// Propagate transforms from roots through hierarchy.
41    /// BFS: for each root, compute world = local.to_matrix().
42    /// For each child, compute world = parent_world * child_local.
43    pub fn propagate_from_roots(&mut self, hierarchy: &SceneHierarchy) {
44        let roots = hierarchy.roots();
45        let mut stack: Vec<u32> = roots;
46
47        while let Some(entity) = stack.pop() {
48            let idx = entity as usize;
49            if idx >= self.local.len() {
50                continue;
51            }
52
53            let local_matrix = GlobalTransform(self.local[idx].to_matrix());
54
55            let parent_world = hierarchy.parent[idx]
56                .map(|p| self.world[p as usize])
57                .unwrap_or_default();
58
59            self.world[idx] = parent_world.mul(&local_matrix);
60            self.dirty[idx] = false;
61
62            // Push children onto stack.
63            for &child in hierarchy.children_of(entity) {
64                stack.push(child);
65            }
66        }
67    }
68
69    /// Number of entity slots.
70    pub fn len(&self) -> usize {
71        self.local.len()
72    }
73
74    /// Whether the store is empty.
75    pub fn is_empty(&self) -> bool {
76        self.local.is_empty()
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn with_capacity_creates_identity() {
86        let st = SceneTransforms::with_capacity(5);
87        assert_eq!(st.len(), 5);
88        assert_eq!(st.world[0], GlobalTransform::identity());
89    }
90
91    #[test]
92    fn mark_dirty() {
93        let mut st = SceneTransforms::with_capacity(3);
94        st.dirty[0] = false;
95        st.mark_dirty(0);
96        assert!(st.dirty[0]);
97    }
98
99    #[test]
100    fn propagate_identity_roots() {
101        let mut st = SceneTransforms::with_capacity(3);
102        let h = SceneHierarchy::with_capacity(3);
103        st.propagate_from_roots(&h);
104        // All roots with default local -> world should be identity.
105        for i in 0..3 {
106            assert_eq!(st.world[i], GlobalTransform::identity());
107            assert!(!st.dirty[i]);
108        }
109    }
110
111    #[test]
112    fn propagate_parent_translation() {
113        let mut st = SceneTransforms::with_capacity(3);
114        let mut h = SceneHierarchy::with_capacity(3);
115        // Entity 0 is root, translated x+5
116        st.local[0].position = [5.0, 0.0, 0.0];
117        // Entity 1 is child of 0, translated x+3
118        st.local[1].position = [3.0, 0.0, 0.0];
119        h.set_parent(1, Some(0));
120
121        st.propagate_from_roots(&h);
122
123        // Entity 0 world: x=5
124        assert!((st.world[0].translation()[0] - 5.0).abs() < 1e-6);
125        // Entity 1 world: x=5+3=8
126        assert!((st.world[1].translation()[0] - 8.0).abs() < 1e-6);
127    }
128
129    #[test]
130    fn propagate_chain_of_three() {
131        let mut st = SceneTransforms::with_capacity(3);
132        let mut h = SceneHierarchy::with_capacity(3);
133        st.local[0].position = [1.0, 0.0, 0.0];
134        st.local[1].position = [2.0, 0.0, 0.0];
135        st.local[2].position = [3.0, 0.0, 0.0];
136        h.set_parent(1, Some(0));
137        h.set_parent(2, Some(1));
138
139        st.propagate_from_roots(&h);
140
141        assert!((st.world[0].translation()[0] - 1.0).abs() < 1e-6);
142        assert!((st.world[1].translation()[0] - 3.0).abs() < 1e-6);
143        assert!((st.world[2].translation()[0] - 6.0).abs() < 1e-6);
144    }
145
146    #[test]
147    fn propagate_clears_dirty() {
148        let mut st = SceneTransforms::with_capacity(2);
149        let h = SceneHierarchy::with_capacity(2);
150        assert!(st.dirty[0]);
151        st.propagate_from_roots(&h);
152        assert!(!st.dirty[0]);
153    }
154}