Skip to main content

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}