1use glam::{Mat4, Vec3};
8
9#[derive(Debug, Clone, Copy)]
13pub struct Transform {
14 pub position: Vec3,
15 pub rotation_z: f32, pub scale: Vec3,
17}
18
19impl Transform {
20 pub const IDENTITY: Self = Self {
21 position: Vec3::ZERO,
22 rotation_z: 0.0,
23 scale: Vec3::ONE,
24 };
25
26 pub fn from_position(position: Vec3) -> Self {
27 Self { position, ..Self::IDENTITY }
28 }
29
30 pub fn from_pos_scale(position: Vec3, scale: f32) -> Self {
31 Self { position, scale: Vec3::splat(scale), ..Self::IDENTITY }
32 }
33
34 pub fn to_matrix(&self) -> Mat4 {
36 let t = Mat4::from_translation(self.position);
37 let r = Mat4::from_rotation_z(self.rotation_z);
38 let s = Mat4::from_scale(self.scale);
39 t * r * s
40 }
41
42 pub fn lerp_toward(&self, other: &Transform, t: f32) -> Transform {
44 Transform {
45 position: self.position.lerp(other.position, t),
46 rotation_z: self.rotation_z + (other.rotation_z - self.rotation_z) * t,
47 scale: self.scale.lerp(other.scale, t),
48 }
49 }
50}
51
52impl Default for Transform {
53 fn default() -> Self { Self::IDENTITY }
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
60pub struct NodeId(pub u64);
61
62#[derive(Debug, Clone)]
64pub struct SceneNode {
65 pub id: NodeId,
66 pub name: Option<String>,
67 pub local: Transform,
68 pub visible: bool,
69 dirty: bool,
71 world_transform: Mat4,
73 pub parent: Option<NodeId>,
74 pub children: Vec<NodeId>,
75 pub tag: Option<String>,
77 pub sort_key: i32,
79 pub user_data: u64,
81}
82
83impl SceneNode {
84 pub fn new(id: NodeId, position: Vec3) -> Self {
85 Self {
86 id,
87 name: None,
88 local: Transform::from_position(position),
89 visible: true,
90 dirty: true,
91 world_transform: Mat4::IDENTITY,
92 parent: None,
93 children: Vec::new(),
94 tag: None,
95 sort_key: 0,
96 user_data: 0,
97 }
98 }
99
100 pub fn with_name(mut self, name: impl Into<String>) -> Self {
101 self.name = Some(name.into());
102 self
103 }
104
105 pub fn with_tag(mut self, tag: impl Into<String>) -> Self {
106 self.tag = Some(tag.into());
107 self
108 }
109
110 pub fn with_sort_key(mut self, key: i32) -> Self {
111 self.sort_key = key;
112 self
113 }
114
115 pub fn set_position(&mut self, pos: Vec3) {
117 self.local.position = pos;
118 self.dirty = true;
119 }
120
121 pub fn set_rotation_z(&mut self, angle: f32) {
123 self.local.rotation_z = angle;
124 self.dirty = true;
125 }
126
127 pub fn set_scale(&mut self, scale: f32) {
129 self.local.scale = Vec3::splat(scale);
130 self.dirty = true;
131 }
132
133 pub fn set_transform(&mut self, t: Transform) {
135 self.local = t;
136 self.dirty = true;
137 }
138
139 pub fn update_world_transform(&mut self, parent_world: &Mat4) -> bool {
142 if !self.dirty { return false; }
143 self.world_transform = *parent_world * self.local.to_matrix();
144 self.dirty = false;
145 true
146 }
147
148 pub fn world_matrix(&self) -> &Mat4 { &self.world_transform }
150
151 pub fn world_position(&self) -> Vec3 {
153 self.world_transform.w_axis.truncate()
154 }
155
156 pub fn world_scale(&self) -> Vec3 {
158 Vec3::new(
159 self.world_transform.x_axis.truncate().length(),
160 self.world_transform.y_axis.truncate().length(),
161 self.world_transform.z_axis.truncate().length(),
162 )
163 }
164
165 pub fn mark_dirty(&mut self) {
167 self.dirty = true;
168 }
169
170 pub fn is_dirty(&self) -> bool { self.dirty }
172
173 pub fn translate(&mut self, delta: Vec3) {
175 self.local.position += delta;
176 self.dirty = true;
177 }
178
179 pub fn rotate_z(&mut self, angle: f32) {
181 self.local.rotation_z += angle;
182 self.dirty = true;
183 }
184
185 pub fn is_visible(&self) -> bool { self.visible }
186 pub fn set_visible(&mut self, v: bool) { self.visible = v; }
187}
188
189pub struct SceneGraph {
193 nodes: Vec<SceneNode>,
194 next_id: u64,
195 roots: Vec<NodeId>,
196}
197
198impl SceneGraph {
199 pub fn new() -> Self {
200 Self { nodes: Vec::new(), next_id: 1, roots: Vec::new() }
201 }
202
203 pub fn create_root(&mut self, position: Vec3) -> NodeId {
205 let id = NodeId(self.next_id);
206 self.next_id += 1;
207 self.roots.push(id);
208 self.nodes.push(SceneNode::new(id, position));
209 id
210 }
211
212 pub fn create_child(&mut self, parent: NodeId, position: Vec3) -> Option<NodeId> {
214 let id = NodeId(self.next_id);
215 self.next_id += 1;
216 let mut node = SceneNode::new(id, position);
217 node.parent = Some(parent);
218
219 if let Some(p) = self.get_mut(parent) {
221 p.children.push(id);
222 } else {
223 return None;
224 }
225
226 self.nodes.push(node);
227 Some(id)
228 }
229
230 pub fn get(&self, id: NodeId) -> Option<&SceneNode> {
232 self.nodes.iter().find(|n| n.id == id)
233 }
234
235 pub fn get_mut(&mut self, id: NodeId) -> Option<&mut SceneNode> {
237 self.nodes.iter_mut().find(|n| n.id == id)
238 }
239
240 pub fn remove_subtree(&mut self, id: NodeId) -> usize {
242 let mut to_remove = vec![id];
243 let mut i = 0;
244 while i < to_remove.len() {
245 let nid = to_remove[i];
246 if let Some(node) = self.get(nid) {
247 to_remove.extend(node.children.iter().copied());
248 }
249 i += 1;
250 }
251 let removed = to_remove.len();
252 self.nodes.retain(|n| !to_remove.contains(&n.id));
253 self.roots.retain(|r| !to_remove.contains(r));
254 removed
255 }
256
257 pub fn flush_transforms(&mut self) {
259 let roots: Vec<NodeId> = self.roots.clone();
260 for root in roots {
261 self.flush_subtree(root, &Mat4::IDENTITY);
262 }
263 }
264
265 fn flush_subtree(&mut self, id: NodeId, parent_world: &Mat4) {
266 let children: Vec<NodeId>;
267 let new_world;
268 {
269 let node = match self.nodes.iter_mut().find(|n| n.id == id) {
270 Some(n) => n,
271 None => return,
272 };
273 node.update_world_transform(parent_world);
274 new_world = *node.world_matrix();
275 children = node.children.clone();
276 }
277 for child in children {
278 self.flush_subtree(child, &new_world);
279 }
280 }
281
282 pub fn find_by_tag(&self, tag: &str) -> Vec<NodeId> {
284 self.nodes.iter()
285 .filter(|n| n.tag.as_deref() == Some(tag))
286 .map(|n| n.id)
287 .collect()
288 }
289
290 pub fn visible_sorted(&self) -> Vec<NodeId> {
292 let mut ids: Vec<NodeId> = self.nodes.iter()
293 .filter(|n| n.visible)
294 .map(|n| n.id)
295 .collect();
296 ids.sort_by_key(|&id| {
297 self.get(id).map(|n| n.sort_key).unwrap_or(0)
298 });
299 ids
300 }
301
302 pub fn len(&self) -> usize { self.nodes.len() }
304 pub fn is_empty(&self) -> bool { self.nodes.is_empty() }
305
306 pub fn iter(&self) -> impl Iterator<Item = &SceneNode> { self.nodes.iter() }
308}
309
310impl Default for SceneGraph {
311 fn default() -> Self { Self::new() }
312}
313
314#[cfg(test)]
317mod tests {
318 use super::*;
319
320 #[test]
321 fn create_root_node() {
322 let mut graph = SceneGraph::new();
323 let id = graph.create_root(Vec3::new(1.0, 2.0, 0.0));
324 graph.flush_transforms();
325 let node = graph.get(id).unwrap();
326 let wp = node.world_position();
327 assert!((wp.x - 1.0).abs() < 0.001);
328 assert!((wp.y - 2.0).abs() < 0.001);
329 }
330
331 #[test]
332 fn child_inherits_parent_transform() {
333 let mut graph = SceneGraph::new();
334 let root = graph.create_root(Vec3::new(5.0, 0.0, 0.0));
335 let child = graph.create_child(root, Vec3::new(1.0, 0.0, 0.0)).unwrap();
336 graph.flush_transforms();
337 let wp = graph.get(child).unwrap().world_position();
338 assert!((wp.x - 6.0).abs() < 0.001);
340 }
341
342 #[test]
343 fn dirty_flag_cleared_after_flush() {
344 let mut graph = SceneGraph::new();
345 let id = graph.create_root(Vec3::ZERO);
346 graph.flush_transforms();
347 assert!(!graph.get(id).unwrap().is_dirty());
348 graph.get_mut(id).unwrap().translate(Vec3::X);
349 assert!(graph.get(id).unwrap().is_dirty());
350 }
351
352 #[test]
353 fn remove_subtree() {
354 let mut graph = SceneGraph::new();
355 let root = graph.create_root(Vec3::ZERO);
356 let child = graph.create_child(root, Vec3::X).unwrap();
357 let _gc = graph.create_child(child, Vec3::Y).unwrap();
358 assert_eq!(graph.len(), 3);
359 graph.remove_subtree(child);
360 assert_eq!(graph.len(), 1);
361 }
362
363 #[test]
364 fn find_by_tag() {
365 let mut graph = SceneGraph::new();
366 let id = graph.create_root(Vec3::ZERO);
367 graph.get_mut(id).unwrap().tag = Some("player".to_string());
368 let results = graph.find_by_tag("player");
369 assert_eq!(results, vec![id]);
370 }
371}