mittens_engine/engine/ecs/system/
transform_system.rs1use crate::engine::ecs::ComponentId;
2use crate::engine::ecs::World;
3use crate::engine::ecs::component::{
4 Camera2DComponent, Camera3DComponent, CollisionComponent, RenderableComponent,
5 TransformComponent, TransformParentComponent,
6};
7use crate::engine::ecs::system::CollisionSystem;
8use crate::engine::ecs::system::System;
9use crate::engine::ecs::system::TransformStreamSystem;
10use crate::engine::graphics::VisualWorld;
11use crate::engine::graphics::primitives::TransformMatrix;
12use crate::engine::user_input::InputState;
13
14#[derive(Debug, Default)]
23pub struct TransformSystem;
24
25impl TransformSystem {
26 pub fn new() -> Self {
27 Self
28 }
29
30 fn mat4_identity() -> TransformMatrix {
31 [
32 [1.0, 0.0, 0.0, 0.0],
33 [0.0, 1.0, 0.0, 0.0],
34 [0.0, 0.0, 1.0, 0.0],
35 [0.0, 0.0, 0.0, 1.0],
36 ]
37 }
38
39 fn mat4_mul(a: TransformMatrix, b: TransformMatrix) -> TransformMatrix {
40 let mut out = [[0.0f32; 4]; 4];
41 for c in 0..4 {
42 for r in 0..4 {
43 out[c][r] =
44 a[0][r] * b[c][0] + a[1][r] * b[c][1] + a[2][r] * b[c][2] + a[3][r] * b[c][3];
45 }
46 }
47 out
48 }
49
50 fn is_descendant_of(world: &World, mut node: ComponentId, ancestor: ComponentId) -> bool {
51 while let Some(parent) = world.parent_of(node) {
52 if parent == ancestor {
53 return true;
54 }
55 node = parent;
56 }
57 false
58 }
59
60 fn nearest_transform_self_or_ancestor(world: &World, cid: ComponentId) -> Option<ComponentId> {
61 if world
62 .get_component_by_id_as::<TransformComponent>(cid)
63 .is_some()
64 {
65 return Some(cid);
66 }
67 let mut cur = cid;
68 while let Some(parent) = world.parent_of(cur) {
69 if world
70 .get_component_by_id_as::<TransformComponent>(parent)
71 .is_some()
72 {
73 return Some(parent);
74 }
75 cur = parent;
76 }
77 None
78 }
79
80 fn propagate_subtree(
81 &mut self,
82 world: &mut World,
83 visuals: &mut VisualWorld,
84 root_node: ComponentId,
85 inherited_world: TransformMatrix,
86 transform_stream_system: &mut TransformStreamSystem,
87 camera_system: &mut crate::engine::ecs::system::CameraSystem,
88 collision_system: &mut CollisionSystem,
89 ) {
90 let mut stack: Vec<(ComponentId, TransformMatrix)> = vec![(root_node, inherited_world)];
91 while let Some((node, current_world)) = stack.pop() {
92 let stream_evaluated =
93 transform_stream_system.evaluate_stream_node(world, node, current_world);
94 let (current_world, stream_output_roots) = match stream_evaluated {
95 Some((processed_world, outputs)) => (processed_world, Some(outputs)),
96 None => (current_world, None),
97 };
98 if stream_output_roots.is_some() {
101 if let Some(t) = world.get_component_by_id_as_mut::<TransformComponent>(node) {
102 t.transform.matrix_world = current_world;
103 }
104 }
105
106 let children: Vec<ComponentId> = match stream_output_roots {
107 Some(outputs) if !outputs.is_empty() => outputs,
108 _ => world.children_of(node).to_vec(),
109 };
110 for child in children {
111 let inherited = if let Some(tp) =
112 world.get_component_by_id_as::<TransformParentComponent>(child)
113 {
114 let Some(target) = tp.resolve_target_component(world) else {
115 continue;
116 };
117 let Some(target_world) = Self::world_model(world, target) else {
118 continue;
119 };
120 target_world
121 } else {
122 current_world
123 };
124 let next_world = if let Some(t) =
125 world.get_component_by_id_as_mut::<TransformComponent>(child)
126 {
127 let w = Self::mat4_mul(inherited, t.transform.model);
128 t.transform.matrix_world = w;
129 w
130 } else {
131 inherited
132 };
133
134 if world
135 .get_component_by_id_as::<TransformComponent>(node)
136 .is_some()
137 {
138 if world
139 .get_component_by_id_as::<Camera2DComponent>(child)
140 .is_some()
141 {
142 camera_system
143 .update_camera_2d_from_parent_transform(world, visuals, child, node);
144 }
145
146 if world
147 .get_component_by_id_as::<Camera3DComponent>(child)
148 .is_some()
149 {
150 camera_system
151 .update_camera_3d_from_parent_transform(world, visuals, child, node);
152 }
153
154 if world
155 .get_component_by_id_as::<CollisionComponent>(child)
156 .is_some()
157 {
158 collision_system.update_from_transform(world, child, node);
159 }
160 }
161
162 if let Some(handle) = world
163 .get_component_by_id_as::<RenderableComponent>(child)
164 .and_then(|r| r.get_handle())
165 {
166 visuals.update_model(handle, next_world);
167 }
168
169 stack.push((child, next_world));
170 }
171 }
172 }
173
174 fn update_transform_parent_dependents(
175 &mut self,
176 world: &mut World,
177 visuals: &mut VisualWorld,
178 changed_component: ComponentId,
179 transform_stream_system: &mut TransformStreamSystem,
180 camera_system: &mut crate::engine::ecs::system::CameraSystem,
181 light_system: &mut crate::engine::ecs::system::LightSystem,
182 collision_system: &mut CollisionSystem,
183 ) {
184 let dependents: Vec<ComponentId> = world
185 .all_components()
186 .filter(|&cid| {
187 world
188 .get_component_by_id_as::<TransformParentComponent>(cid)
189 .is_some()
190 })
191 .filter(|&cid| !Self::is_descendant_of(world, cid, changed_component))
192 .filter(|&cid| {
193 let target_transform = world
194 .get_component_by_id_as::<TransformParentComponent>(cid)
195 .and_then(|tp| tp.resolve_target_component(world))
196 .and_then(|target| Self::nearest_transform_self_or_ancestor(world, target));
197 target_transform.is_some_and(|target_transform| {
198 target_transform == changed_component
199 || Self::is_descendant_of(world, target_transform, changed_component)
200 })
201 })
202 .collect();
203
204 for dependent in dependents {
205 let Some(inherited_world) = world
206 .get_component_by_id_as::<TransformParentComponent>(dependent)
207 .and_then(|tp| tp.resolve_target_component(world))
208 .and_then(|target| Self::world_model(world, target))
209 else {
210 continue;
211 };
212
213 self.propagate_subtree(
214 world,
215 visuals,
216 dependent,
217 inherited_world,
218 transform_stream_system,
219 camera_system,
220 collision_system,
221 );
222
223 let child_transform_roots: Vec<ComponentId> = world
224 .children_of(dependent)
225 .iter()
226 .copied()
227 .filter(|&cid| {
228 world
229 .get_component_by_id_as::<TransformComponent>(cid)
230 .is_some()
231 })
232 .collect();
233 for root in child_transform_roots {
234 light_system.transform_changed(world, visuals, root);
235 }
236 }
237 }
238
239 pub fn world_model(world: &World, cid: ComponentId) -> Option<TransformMatrix> {
244 if let Some(t) = world.get_component_by_id_as::<TransformComponent>(cid) {
246 return Some(t.transform.matrix_world);
247 }
248
249 let mut cur = cid;
251 while let Some(parent) = world.parent_of(cur) {
252 if let Some(t) = world.get_component_by_id_as::<TransformComponent>(parent) {
253 return Some(t.transform.matrix_world);
254 }
255 cur = parent;
256 }
257 None
258 }
259
260 pub fn world_position(world: &World, cid: ComponentId) -> Option<[f32; 3]> {
262 let model = Self::world_model(world, cid)?;
263 let p = model[3];
265 Some([p[0], p[1], p[2]])
266 }
267
268 pub fn transform_changed(
273 &mut self,
274 world: &mut World,
275 visuals: &mut VisualWorld,
276 component: ComponentId,
277 transform_stream_system: &mut TransformStreamSystem,
278 camera_system: &mut crate::engine::ecs::system::CameraSystem,
279 light_system: &mut crate::engine::ecs::system::LightSystem,
280 collision_system: &mut CollisionSystem,
281 ) {
282 if let Some(inherited_world) = world
283 .get_component_by_id_as::<TransformParentComponent>(component)
284 .and_then(|tp| tp.resolve_target_component(world))
285 .and_then(|target| Self::world_model(world, target))
286 {
287 self.propagate_subtree(
288 world,
289 visuals,
290 component,
291 inherited_world,
292 transform_stream_system,
293 camera_system,
294 collision_system,
295 );
296 light_system.transform_changed(world, visuals, component);
297 }
298 if world
299 .get_component_by_id_as::<TransformParentComponent>(component)
300 .is_some()
301 {
302 return;
303 }
304 let mut transform_chain: Vec<ComponentId> = Vec::new();
314 let mut stream_boundary = false; let mut transform_parent_basis = None;
316 let mut transform_parent_boundary = false;
317 let mut cur = component;
318 'chain: loop {
319 if world
320 .get_component_by_id_as::<TransformComponent>(cur)
321 .is_some()
322 {
323 transform_chain.push(cur);
324 let mut probe = cur;
328 while let Some(p) = world.parent_of(probe) {
329 if let Some(tp) = world.get_component_by_id_as::<TransformParentComponent>(p) {
330 transform_parent_boundary = true;
331 transform_parent_basis = tp
332 .resolve_target_component(world)
333 .and_then(|target| Self::world_model(world, target));
334 break 'chain;
335 }
336 if transform_stream_system.is_transform_stream_boundary(world, p) {
337 stream_boundary = true;
338 break 'chain;
339 }
340 if world
341 .get_component_by_id_as::<TransformComponent>(p)
342 .is_some()
343 {
344 break; }
346 probe = p;
347 }
348 }
349 let Some(parent) = world.parent_of(cur) else {
350 break;
351 };
352 cur = parent;
353 }
354 transform_chain.reverse();
355
356 if transform_parent_boundary && transform_parent_basis.is_none() {
359 return;
360 }
361
362 let (start_idx, mut chain_world) = if let Some(basis) = transform_parent_basis {
368 (0, basis)
369 } else if stream_boundary && !transform_chain.is_empty() {
370 let cached = world
371 .get_component_by_id_as::<TransformComponent>(transform_chain[0])
372 .map(|t| t.transform.matrix_world)
373 .unwrap_or_else(Self::mat4_identity);
374 (1, cached)
375 } else {
376 (0, Self::mat4_identity())
377 };
378 for tid in transform_chain[start_idx..].iter().copied() {
379 let local = match world
380 .get_component_by_id_as::<TransformComponent>(tid)
381 .map(|t| t.transform.model)
382 {
383 Some(m) => m,
384 None => continue,
385 };
386 chain_world = Self::mat4_mul(chain_world, local);
387 if let Some(t) = world.get_component_by_id_as_mut::<TransformComponent>(tid) {
388 t.transform.matrix_world = chain_world;
389 }
390 }
391
392 let root_world = match world
394 .get_component_by_id_as::<TransformComponent>(component)
395 .map(|t| t.transform.matrix_world)
396 {
397 Some(m) => m,
398 None => return,
399 };
400
401 self.propagate_subtree(
402 world,
403 visuals,
404 component,
405 root_world,
406 transform_stream_system,
407 camera_system,
408 collision_system,
409 );
410
411 light_system.transform_changed(world, visuals, component);
414 self.update_transform_parent_dependents(
415 world,
416 visuals,
417 component,
418 transform_stream_system,
419 camera_system,
420 light_system,
421 collision_system,
422 );
423 }
424}
425
426#[cfg(test)]
427mod tests {
428 use super::TransformSystem;
429 use crate::engine::ecs::World;
430 use crate::engine::ecs::component::{TransformComponent, TransformParentComponent};
431 use crate::engine::ecs::system::{
432 CameraSystem, CollisionSystem, LightSystem, TransformStreamSystem,
433 };
434 use crate::engine::graphics::VisualWorld;
435
436 #[test]
437 fn transform_parent_updates_cross_tree_child_when_target_changes() {
438 let mut world = World::default();
439 let mut visuals = VisualWorld::default();
440 let mut transform_system = TransformSystem::new();
441 let mut transform_stream_system = TransformStreamSystem::new();
442 let mut camera_system = CameraSystem::new();
443 let mut light_system = LightSystem::new();
444 let mut collision_system = CollisionSystem::new();
445
446 let source = world.add_component(TransformComponent::new().with_position(1.0, 0.0, 0.0));
447 let dependent_root = world.add_component(TransformComponent::new());
448 let transform_parent =
449 world.add_component(TransformParentComponent::new().with_target_source(
450 crate::engine::ecs::component::ComponentRef::Query("#source".to_string()),
451 ));
452 let child = world.add_component(TransformComponent::new().with_position(0.0, 2.0, 0.0));
453
454 world.get_component_record_mut(source).unwrap().name = "source".to_string();
455 world.add_child(dependent_root, transform_parent).unwrap();
456 world.add_child(transform_parent, child).unwrap();
457
458 transform_system.transform_changed(
459 &mut world,
460 &mut visuals,
461 source,
462 &mut transform_stream_system,
463 &mut camera_system,
464 &mut light_system,
465 &mut collision_system,
466 );
467
468 assert_eq!(
469 TransformSystem::world_position(&world, child),
470 Some([1.0, 2.0, 0.0])
471 );
472 }
473}
474
475impl System for TransformSystem {
476 fn tick(
477 &mut self,
478 _world: &mut World,
479 _visuals: &mut VisualWorld,
480 _input: &InputState,
481 _dt_sec: f32,
482 ) {
483 }
485}