goud_engine/ecs/components/propagation/propagate3d.rs
1//! 3D transform propagation through entity hierarchies.
2
3use crate::ecs::components::hierarchy::{Children, Parent};
4use crate::ecs::entity::Entity;
5use crate::ecs::World;
6
7use super::super::global_transform::GlobalTransform;
8use super::super::transform::Transform;
9
10/// Propagates transforms through the entity hierarchy (3D).
11///
12/// This function updates `GlobalTransform` for all entities with `Transform`:
13/// - Root entities: `GlobalTransform` = `Transform` (direct copy)
14/// - Child entities: `GlobalTransform` = parent's `GlobalTransform` * local `Transform`
15///
16/// # Requirements
17///
18/// For correct propagation:
19/// - Parent entities must have both `Transform` and `GlobalTransform`
20/// - Parent entities must have a `Children` component listing their children
21/// - Child entities must have both `Transform`, `GlobalTransform`, and `Parent`
22///
23/// # Example
24///
25/// ```
26/// use goud_engine::ecs::World;
27/// use goud_engine::ecs::components::{Transform, GlobalTransform, Parent, Children};
28/// use goud_engine::ecs::components::propagation::propagate_transforms;
29/// use goud_engine::core::math::Vec3;
30///
31/// let mut world = World::new();
32///
33/// // Create parent at (10, 0, 0)
34/// let parent = world.spawn_empty();
35/// world.insert(parent, Transform::from_position(Vec3::new(10.0, 0.0, 0.0)));
36/// world.insert(parent, GlobalTransform::IDENTITY);
37///
38/// // Create child at local (5, 0, 0)
39/// let child = world.spawn_empty();
40/// world.insert(child, Transform::from_position(Vec3::new(5.0, 0.0, 0.0)));
41/// world.insert(child, GlobalTransform::IDENTITY);
42/// world.insert(child, Parent::new(parent));
43///
44/// // Set up parent's children list
45/// let mut children = Children::new();
46/// children.push(child);
47/// world.insert(parent, children);
48///
49/// // Propagate transforms
50/// propagate_transforms(&mut world);
51///
52/// // Child's global position should be (15, 0, 0)
53/// if let Some(global) = world.get::<GlobalTransform>(child) {
54/// let pos = global.translation();
55/// assert!((pos.x - 15.0).abs() < 0.001);
56/// }
57/// ```
58pub fn propagate_transforms(world: &mut World) {
59 // Step 1: Find all root entities (have Transform but no Parent)
60 let mut roots = Vec::new();
61 let mut entities_with_transform = Vec::new();
62
63 // Collect entities with Transform component
64 for archetype in world.archetypes().iter() {
65 for &entity in archetype.entities() {
66 if world.has::<Transform>(entity) {
67 entities_with_transform.push(entity);
68 }
69 }
70 }
71
72 // Identify roots vs children
73 for entity in &entities_with_transform {
74 if !world.has::<Parent>(*entity) {
75 roots.push(*entity);
76 }
77 }
78
79 // Step 2: Update root entities (GlobalTransform = Transform)
80 for &root in &roots {
81 if let Some(transform) = world.get::<Transform>(root) {
82 let global = GlobalTransform::from(*transform);
83 world.insert(root, global);
84 }
85 }
86
87 // Step 3: Process hierarchy depth-first using explicit stack
88 let mut stack: Vec<Entity> = Vec::new();
89
90 // Push children of all roots onto stack
91 for &root in &roots {
92 if let Some(children) = world.get::<Children>(root) {
93 // Push in reverse order so first child is processed first
94 for &child in children.as_slice().iter().rev() {
95 stack.push(child);
96 }
97 }
98 }
99
100 // Process stack
101 while let Some(entity) = stack.pop() {
102 // Get parent's global transform and local transform
103 let parent_global = if let Some(parent_comp) = world.get::<Parent>(entity) {
104 let parent_entity = parent_comp.get();
105 world.get::<GlobalTransform>(parent_entity).copied()
106 } else {
107 None
108 };
109
110 let local = world.get::<Transform>(entity).copied();
111
112 // Compute and update global transform
113 if let (Some(parent_global), Some(local)) = (parent_global, local) {
114 let global = parent_global.transform_by(&local);
115 world.insert(entity, global);
116 } else if let Some(local) = local {
117 // No parent, treat as root
118 world.insert(entity, GlobalTransform::from(local));
119 }
120
121 // Push children onto stack
122 if let Some(children) = world.get::<Children>(entity) {
123 for &child in children.as_slice().iter().rev() {
124 stack.push(child);
125 }
126 }
127 }
128}
129
130/// Updates GlobalTransform for a single entity and its descendants.
131///
132/// This is useful when you need to update a specific subtree rather than
133/// the entire world.
134///
135/// # Arguments
136///
137/// * `world` - The ECS world
138/// * `entity` - The root of the subtree to update
139/// * `parent_global` - The parent's global transform (or None for root)
140pub fn propagate_transform_subtree(
141 world: &mut World,
142 entity: Entity,
143 parent_global: Option<&GlobalTransform>,
144) {
145 // Get local transform
146 let local = match world.get::<Transform>(entity).copied() {
147 Some(t) => t,
148 None => return, // No transform, nothing to do
149 };
150
151 // Compute global transform
152 let global = match parent_global {
153 Some(pg) => pg.transform_by(&local),
154 None => GlobalTransform::from(local),
155 };
156
157 // Update entity's global transform
158 world.insert(entity, global);
159
160 // Process children
161 let children: Vec<Entity> = world
162 .get::<Children>(entity)
163 .map(|c| c.as_slice().to_vec())
164 .unwrap_or_default();
165
166 for child in children {
167 propagate_transform_subtree(world, child, Some(&global));
168 }
169}