1use glam::{Mat4, Quat, Vec3};
4use oxide_ecs::entity::Entity;
5use oxide_ecs::world::World;
6use oxide_ecs::{query, Component};
7use oxide_math::transform::Transform;
8
9#[derive(Component, Clone, Copy, Debug, PartialEq, Eq)]
10pub struct Parent(pub Entity);
11
12#[derive(Component, Clone, Debug, Default)]
13pub struct Children(pub Vec<Entity>);
14
15impl Children {
16 pub fn new() -> Self {
17 Self(Vec::new())
18 }
19
20 pub fn with(entities: Vec<Entity>) -> Self {
21 Self(entities)
22 }
23
24 pub fn push(&mut self, entity: Entity) {
25 self.0.push(entity);
26 }
27
28 pub fn remove(&mut self, entity: Entity) {
29 self.0.retain(|&e| e != entity);
30 }
31
32 pub fn len(&self) -> usize {
33 self.0.len()
34 }
35
36 pub fn is_empty(&self) -> bool {
37 self.0.is_empty()
38 }
39
40 pub fn iter(&self) -> impl Iterator<Item = Entity> + '_ {
41 self.0.iter().copied()
42 }
43}
44
45#[derive(Component, Clone, Debug)]
46pub struct TransformComponent {
47 pub transform: Transform,
48 pub is_dirty: bool,
49}
50
51impl Default for TransformComponent {
52 fn default() -> Self {
53 Self {
54 transform: Transform {
55 position: Vec3::ZERO,
56 rotation: Quat::IDENTITY,
57 scale: Vec3::ONE,
58 },
59 is_dirty: true,
60 }
61 }
62}
63
64impl TransformComponent {
65 pub fn new(transform: Transform) -> Self {
66 Self {
67 transform,
68 is_dirty: true,
69 }
70 }
71
72 pub fn from_position(position: Vec3) -> Self {
73 Self::new(Transform {
74 position,
75 rotation: Quat::IDENTITY,
76 scale: Vec3::ONE,
77 })
78 }
79
80 pub fn from_position_rotation(position: Vec3, rotation: Quat) -> Self {
81 Self::new(Transform {
82 position,
83 rotation,
84 scale: Vec3::ONE,
85 })
86 }
87
88 pub fn to_matrix(&self) -> Mat4 {
89 self.transform.to_matrix()
90 }
91
92 pub fn mark_dirty(&mut self) {
93 self.is_dirty = true;
94 }
95
96 pub fn clear_dirty(&mut self) {
97 self.is_dirty = false;
98 }
99
100 pub fn set_transform(&mut self, transform: Transform) {
101 self.transform = transform;
102 self.mark_dirty();
103 }
104
105 pub fn transform_mut(&mut self) -> &mut Transform {
106 self.mark_dirty();
107 &mut self.transform
108 }
109}
110
111impl From<Transform> for TransformComponent {
112 fn from(transform: Transform) -> Self {
113 Self::new(transform)
114 }
115}
116
117impl From<TransformComponent> for Transform {
118 fn from(component: TransformComponent) -> Self {
119 component.transform
120 }
121}
122
123#[derive(Component, Clone, Copy, Debug)]
124pub struct GlobalTransform {
125 pub matrix: Mat4,
126}
127
128impl Default for GlobalTransform {
129 fn default() -> Self {
130 Self {
131 matrix: Mat4::IDENTITY,
132 }
133 }
134}
135
136impl GlobalTransform {
137 pub fn from_matrix(matrix: Mat4) -> Self {
138 Self { matrix }
139 }
140
141 pub fn identity() -> Self {
142 Self {
143 matrix: Mat4::IDENTITY,
144 }
145 }
146
147 pub fn position(&self) -> Vec3 {
148 self.matrix.col(3).truncate()
149 }
150
151 pub fn mul(&self, other: &GlobalTransform) -> GlobalTransform {
152 GlobalTransform {
153 matrix: self.matrix * other.matrix,
154 }
155 }
156}
157
158pub fn attach_child(world: &mut World, parent: Entity, child: Entity) {
159 if let Some(existing_parent) = world.get::<Parent>(child).copied() {
160 if existing_parent.0 != parent {
161 if let Some(old_children) = world.get_mut::<Children>(existing_parent.0) {
162 old_children.remove(child);
163 }
164 }
165 }
166
167 world.entity_mut(child).insert(Parent(parent));
168
169 if let Some(children) = world.get_mut::<Children>(parent) {
170 if !children.0.contains(&child) {
171 children.push(child);
172 }
173 } else {
174 world.entity_mut(parent).insert(Children::with(vec![child]));
175 }
176
177 mark_subtree_dirty(world, child);
178}
179
180pub fn detach_child(world: &mut World, parent: Entity, child: Entity) {
181 if let Some(children) = world.get_mut::<Children>(parent) {
182 children.remove(child);
183 }
184 world.entity_mut(child).remove::<Parent>();
185 mark_subtree_dirty(world, child);
186}
187
188pub fn mark_subtree_dirty(world: &mut World, root: Entity) {
189 if let Some(local) = world.get_mut::<TransformComponent>(root) {
190 local.mark_dirty();
191 }
192
193 let children = world
194 .get::<Children>(root)
195 .map(|c| c.0.clone())
196 .unwrap_or_default();
197
198 for child in children {
199 mark_subtree_dirty(world, child);
200 }
201}
202
203pub fn transform_propagate_system(world: &mut World) {
204 let root_entities: Vec<Entity> = {
205 let mut roots = Vec::new();
206 let mut query = world
207 .query_filtered::<Entity, (query::With<TransformComponent>, query::Without<Parent>)>();
208 for entity in query.iter(world) {
209 roots.push(entity);
210 }
211 roots
212 };
213
214 for root in root_entities {
215 propagate_from_root(world, root, GlobalTransform::identity(), false);
216 }
217}
218
219fn propagate_from_root(
220 world: &mut World,
221 entity: Entity,
222 parent_global: GlobalTransform,
223 parent_changed: bool,
224) {
225 let (local_matrix, local_dirty) = match world.get::<TransformComponent>(entity) {
226 Some(transform) => (transform.to_matrix(), transform.is_dirty),
227 None => (Mat4::IDENTITY, false),
228 };
229
230 let computed_global = GlobalTransform::from_matrix(parent_global.matrix * local_matrix);
231 let existing_global = world.get::<GlobalTransform>(entity).copied();
232 let should_update = parent_changed || local_dirty || existing_global.is_none();
233
234 if should_update {
235 if let Some(global) = world.get_mut::<GlobalTransform>(entity) {
236 *global = computed_global;
237 } else {
238 world.entity_mut(entity).insert(computed_global);
239 }
240 }
241
242 if local_dirty {
243 if let Some(local) = world.get_mut::<TransformComponent>(entity) {
244 local.clear_dirty();
245 }
246 }
247
248 let current_global = if should_update {
249 computed_global
250 } else {
251 existing_global.unwrap_or(computed_global)
252 };
253
254 let children = world
255 .get::<Children>(entity)
256 .map(|children| children.0.clone())
257 .unwrap_or_default();
258
259 for child in children {
260 propagate_from_root(world, child, current_global, should_update);
261 }
262}
263
264#[cfg(test)]
265mod tests {
266 use super::*;
267 use glam::Vec3;
268
269 #[test]
270 fn transform_propagation_works_for_parent_child() {
271 let mut world = World::new();
272
273 let root = world
274 .spawn((
275 TransformComponent::from_position(Vec3::new(1.0, 0.0, 0.0)),
276 GlobalTransform::default(),
277 ))
278 .id();
279
280 let child = world
281 .spawn((
282 TransformComponent::from_position(Vec3::new(0.0, 2.0, 0.0)),
283 GlobalTransform::default(),
284 ))
285 .id();
286
287 attach_child(&mut world, root, child);
288 transform_propagate_system(&mut world);
289
290 let root_global = world
291 .get::<GlobalTransform>(root)
292 .expect("root global transform should exist");
293 assert_eq!(root_global.position(), Vec3::new(1.0, 0.0, 0.0));
294
295 let child_global = world
296 .get::<GlobalTransform>(child)
297 .expect("child global transform should exist");
298 assert_eq!(child_global.position(), Vec3::new(1.0, 2.0, 0.0));
299 }
300
301 #[test]
302 fn dirty_transform_marks_and_clears() {
303 let mut world = World::new();
304 let entity = world
305 .spawn((TransformComponent::default(), GlobalTransform::default()))
306 .id();
307
308 transform_propagate_system(&mut world);
309 assert!(
310 !world
311 .get::<TransformComponent>(entity)
312 .expect("transform component should exist")
313 .is_dirty
314 );
315
316 let transform = world
317 .get_mut::<TransformComponent>(entity)
318 .expect("transform component should exist");
319 transform.transform_mut().position = Vec3::new(3.0, 0.0, 0.0);
320
321 transform_propagate_system(&mut world);
322
323 let global = world
324 .get::<GlobalTransform>(entity)
325 .expect("global transform should exist");
326 assert_eq!(global.position(), Vec3::new(3.0, 0.0, 0.0));
327 assert!(
328 !world
329 .get::<TransformComponent>(entity)
330 .expect("transform component should exist")
331 .is_dirty
332 );
333 }
334}