From 2daf617ba39c6c236a5bd6a98edbd06ba5f02163 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Wed, 10 Apr 2024 23:45:25 -0400 Subject: [PATCH] scene: implement WorldTransform struct to simplify getting the world transform of scene nodes --- lyra-scene/Cargo.toml | 1 + lyra-scene/src/lib.rs | 170 +++++++----------------------- lyra-scene/src/node.rs | 24 +++-- lyra-scene/src/world_transform.rs | 168 +++++++++++++++++++++++++++++ 4 files changed, 224 insertions(+), 139 deletions(-) create mode 100644 lyra-scene/src/world_transform.rs diff --git a/lyra-scene/Cargo.toml b/lyra-scene/Cargo.toml index 79a85fa..28d1c33 100644 --- a/lyra-scene/Cargo.toml +++ b/lyra-scene/Cargo.toml @@ -6,5 +6,6 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +anyhow = "1.0.81" lyra-ecs = { path = "../lyra-ecs", features = [ "math" ] } lyra-math = { path = "../lyra-math" } diff --git a/lyra-scene/src/lib.rs b/lyra-scene/src/lib.rs index 56d1976..b737e5c 100644 --- a/lyra-scene/src/lib.rs +++ b/lyra-scene/src/lib.rs @@ -1,6 +1,11 @@ -use std::collections::VecDeque; +mod node; +pub use node::*; -use lyra_ecs::{query::Entities, relation::ChildOf, Bundle, Component, Entity, World}; +mod world_transform; +pub use world_transform::*; + +use lyra_ecs::{query::{Entities, ViewOne}, relation::ChildOf, Bundle, Component, World}; +use lyra_math::Transform; // So we can use lyra_ecs::Component derive macro pub(crate) mod lyra_engine { @@ -9,10 +14,6 @@ pub(crate) mod lyra_engine { } } -mod node; -use lyra_math::{Transform, Vec3}; -pub use node::*; - /// A flag spawned on all scene node entities #[derive(Component)] pub struct SceneNodeFlag; @@ -41,7 +42,7 @@ impl SceneGraph { /// Create a new SceneGraph inside an existing ECS World. pub fn from_world(mut world: World) -> Self { - let root_en = world.spawn((Transform::from_translation(Vec3::new(0.0, 0.0, 0.0)), SceneNodeRoot)); + let root_en = world.spawn((WorldTransform::default(), Transform::default(), SceneNodeRoot)); let root = SceneNode::new(None, root_en); Self { @@ -50,70 +51,21 @@ impl SceneGraph { } } - /// Consume the SceneGraph, inserting its internal world into the provided world. - /// - /// The `into` World will contain all nodes of the SceneGraph. You could iterate through the - /// SceneGraph manually if you'd like. The root entity has a [`SceneNodeRoot`] flag component, - /// and a Transform component. The children nodes have a [`SceneNodeFlag`] component and a - /// Transform component. The transforms of the children will be local, you must find its world - /// transform by traversing up the hierarchy of the scene manually with the `ChildOf` relation. - /// - /// Returns: the Entity of the root node in the provided world. - pub fn into_world(mut self, into: &mut World) -> Entity { - // first insert the root entity into the World. - let v = self.world.view_one::<&Transform>(self.root_node.entity()); - let pos = *v.get().unwrap(); - let new_root = into.spawn((pos, SceneNodeRoot)); - - // now process the children of the root node. - Self::traverse_inserting_into(&mut self.world, self.root_node.entity(), new_root, into); - new_root - } - - /// Recursively traverse the `from` world, starting at `node_en`, inserting the - /// children entity into the world. - /// - /// Parameters: - /// * `scene_world` - The world that the SceneGraph exists in - /// * `scene_en` - The entity of the parent entity inside of the `scene_world`. - /// * `parent_en` - The entity of the parent entity inside of the `into` world. - /// * `into` - The world to insert the SceneGraph world into. - fn traverse_inserting_into(scene_world: &mut World, scene_en: Entity, parent_en: Entity, into: &mut World) { - let v = scene_world.view::<(Entities, &Transform)>() - .relates_to::(scene_en); - - // unfortunately, the borrow checker exists, and wasn't happy that `scene_world` was - // immutably borrowed with the view (v), and being mutably borrowed in the - // recursive call. - let mut child_entities = VecDeque::new(); - - for ((child_scene_en, pos), _rel) in v.iter() { - let into_en = into.spawn((*pos, SceneNodeFlag)); - into.add_relation(into_en, ChildOf, parent_en); - - child_entities.push_back((child_scene_en, into_en)); - } - - while let Some((child_scene_en, into_en)) = child_entities.pop_front() { - Self::traverse_inserting_into(scene_world, child_scene_en, into_en, into); - } - } - /// Adds a node to the root node of the SceneGraph. /// /// The spawned entity will have a `ChildOf` relation targeting the root node, the /// `SceneNodeFlag` component is also added to the entity. - pub fn add_node(&mut self, local_transform: Transform, bundle: B) -> SceneNode { + pub fn add_node(&mut self, bundle: B) -> SceneNode { let node = self.root_node.clone(); - self.add_node_under(&node, local_transform, bundle) + self.add_node_under(&node, bundle) } /// Add a node under a parent node. /// /// The spawned entity will have a `ChildOf` relation targeting the provided parent node, /// the `SceneNodeFlag` component is also added to the entity. - pub fn add_node_under(&mut self, parent: &SceneNode, local_transform: Transform, bundle: B) -> SceneNode { - world_add_child_node(&mut self.world, parent, local_transform, bundle) + pub fn add_node_under(&mut self, parent: &SceneNode, bundle: B) -> SceneNode { + world_add_child_node(&mut self.world, parent, bundle) } /// Insert a component bundle to a SceneNode. @@ -133,29 +85,32 @@ impl SceneGraph { /// Traverses down the SceneGraph, calling `callback` with each SceneNode and its world transform. /// /// The traversal does not include the root scene node. - pub fn traverse_down(&self, mut callback: F) + pub fn traverse_down(&self, mut callback: F) where - F: FnMut(&World, &SceneNode, Transform), + F: FnMut(&World, &SceneNode, ViewOne), + Q: lyra_ecs::query::AsQuery, { - self.traverse_down_from(self.root_node.clone(), &mut callback); + self.traverse_down_from::(self.root_node.clone(), &mut callback); } /// Recursively Traverses down the SceneGraph from a starting node, calling `callback` with each /// SceneNode and its world transform. - fn traverse_down_from(&self, start: SceneNode, callback: &mut F) + fn traverse_down_from(&self, start: SceneNode, callback: &mut F) where - F: FnMut(&World, &SceneNode, Transform), + F: FnMut(&World, &SceneNode, ViewOne), + Q: lyra_ecs::query::AsQuery, { let v = self.world - .view::<(Entities, &Transform)>() + .view::() .relates_to::(start.entity()); - for ((e, _), _rel) in v.iter() { + for (e, _rel) in v.iter() { let node = SceneNode::new(Some(start.entity()), e); - let world_pos = node.world_transform(self); - callback(&self.world, &node, world_pos); + //let world_pos = node.world_transform(self); + let v = self.world.view_one::(e); + callback(&self.world, &node, v); - self.traverse_down_from(node, callback); + self.traverse_down_from::(node, callback); } } @@ -173,9 +128,9 @@ impl SceneGraph { /// /// The spawned entity will have a `ChildOf` relation targeting the provided parent node, /// the `SceneNodeFlag` component is also added to the entity. -pub(crate) fn world_add_child_node(world: &mut World, parent: &SceneNode, local_transform: Transform, bundle: B) -> SceneNode { +pub(crate) fn world_add_child_node(world: &mut World, parent: &SceneNode, bundle: B) -> SceneNode { let e = world.spawn(bundle); - world.insert(e, (SceneNodeFlag, local_transform)); + world.insert(e, (SceneNodeFlag,)); world.add_relation(e, ChildOf, parent.entity()); SceneNode::new(Some(parent.entity()), e) @@ -183,10 +138,10 @@ pub(crate) fn world_add_child_node(world: &mut World, parent: &SceneN #[cfg(test)] pub mod tests { - use lyra_ecs::Component; + use lyra_ecs::{query::{filter::{Has, Not}, Entities}, relation::{ChildOf, RelationOriginComponent}, Component}; use lyra_math::{Transform, Vec3}; - use crate::{lyra_engine, SceneGraph}; + use crate::{lyra_engine, WorldTransform, SceneGraph}; #[derive(Component)] pub struct FakeMesh; @@ -195,7 +150,7 @@ pub mod tests { fn single_node_hierarchy() { let mut scene = SceneGraph::new(); - let a = scene.add_node(Transform::from_translation(Vec3::new(10.0, 10.0, 10.0)), FakeMesh); + let a = scene.add_node((Transform::from_translation(Vec3::new(10.0, 10.0, 10.0)), FakeMesh)); assert!(a.parent(&scene).unwrap() == scene.root_node); } @@ -203,10 +158,10 @@ pub mod tests { fn double_node_hierarchy() { let mut scene = SceneGraph::new(); - let a = scene.add_node(Transform::from_translation(Vec3::new(10.0, 10.0, 10.0)), FakeMesh); + let a = scene.add_node((Transform::from_translation(Vec3::new(10.0, 10.0, 10.0)), FakeMesh)); assert!(a.parent(&scene).unwrap() == scene.root_node); - let b = a.add_node(&mut scene, Transform::from_translation(Vec3::new(50.0, 50.0, 50.0)), FakeMesh); + let b = a.add_node(&mut scene, (Transform::from_translation(Vec3::new(50.0, 50.0, 50.0)), FakeMesh)); assert!(b.parent(&scene).unwrap() == a); } @@ -216,68 +171,25 @@ pub mod tests { let mut scene = SceneGraph::new(); - let a = scene.add_node(Transform::from_translation(v2s[0]), FakeMesh); + let a = scene.add_node((WorldTransform::default(), Transform::from_translation(v2s[0]), FakeMesh)); assert!(a.parent(&scene).unwrap() == scene.root_node); - let b = a.add_node(&mut scene, Transform::from_translation(v2s[1]), FakeMesh); + let b = a.add_node(&mut scene, (WorldTransform::default(), Transform::from_translation(v2s[1]), FakeMesh)); assert!(b.parent(&scene).unwrap() == a); + let view = scene.world.view::<(Entities, &mut WorldTransform, &Transform, Not>>)>(); + crate::system_update_world_transforms(&scene.world, view).unwrap(); + let mut idx = 0; - scene.traverse_down(|_, _, pos| { + scene.traverse_down::<_, &WorldTransform>(|_, _, v| { + let pos = v.get().unwrap(); if idx == 0 { - assert_eq!(pos, Transform::from_translation(v2s[idx])); + assert_eq!(**pos, Transform::from_translation(v2s[idx])); } else if idx == 1 { let t = v2s.iter().sum(); - assert_eq!(pos, Transform::from_translation(t)); + assert_eq!(**pos, Transform::from_translation(t)); } idx += 1; }); } - - /* #[test] - fn inserting_and_from_world() { - let v2s = vec![Vec3::new(10.0, 10.0, 10.0), Vec3::new(50.0, 50.0, 50.0)]; - - let mut scene = SceneGraph::new(); - - let a = scene.add_node(Transform::from_translation(v2s[0]), FakeMesh); - assert!(a.parent(&scene).unwrap() == scene.root_node); - let b = a.add_node(&mut scene, Transform::from_translation(v2s[1]), FakeMesh); - assert!(b.parent(&scene).unwrap() == a); - - let mut other_world = World::new(); - let root = scene.into_world(&mut other_world); - - // check all of the entities inside of the World - let (root_pos, _) = other_world.view_one::<(&Transform, &SceneNodeRoot)>(root).get().unwrap(); - assert_eq!(*root_pos, Transform::from_xyz(0.0, 0.0, 0.0)); - - let ((child_en, child_pos), _) = other_world.view::<(Entities, &Transform)>() - .relates_to::(root).iter().next().unwrap(); - assert_eq!(*child_pos, Transform::from_translation(v2s[0])); - - let ((_, childchild_pos), _) = other_world.view::<(Entities, &Transform)>() - .relates_to::(child_en).iter().next().unwrap(); - assert_eq!(*childchild_pos, Transform::from_translation(v2s[1])); - - drop(root_pos); - drop(child_pos); - drop(childchild_pos); - - // Now get the SceneGraph inside the World and use the nice utility tools to traverse it. - let scene = SceneGraph::from_world(&mut other_world, root) - .expect("Failed to get SceneGraph from World!"); - - let mut idx = 0; - scene.traverse_down(|_e, pos| { - if idx == 0 { - assert_eq!(pos, Transform::from_translation(v2s[idx])); - } else if idx == 1 { - let t = v2s.iter().sum(); - assert_eq!(pos, Transform::from_translation(t)); - } - - idx += 1; - }); - } */ } \ No newline at end of file diff --git a/lyra-scene/src/node.rs b/lyra-scene/src/node.rs index 557e56d..a09004f 100644 --- a/lyra-scene/src/node.rs +++ b/lyra-scene/src/node.rs @@ -1,5 +1,4 @@ use lyra_ecs::{query::Entities, relation::{ChildOf, RelationOriginComponent}, Bundle, Entity}; -use lyra_math::Transform; use crate::SceneGraph; @@ -52,11 +51,11 @@ impl SceneNode { /// /// The spawned entity backing the scene node will have a `ChildOf` relation targeting /// the provided parent node, the `SceneNodeFlag` component is also added to the entity. - pub fn add_node(&self, graph: &mut SceneGraph, local_transform: Transform, bundle: B) -> SceneNode { - graph.add_node_under(self, local_transform, bundle) + pub fn add_node(&self, graph: &mut SceneGraph, bundle: B) -> SceneNode { + graph.add_node_under(self, bundle) } - pub fn local_transform(&self, graph: &SceneGraph) -> Transform { + /* pub fn local_transform(&self, graph: &SceneGraph) -> Transform { let v = graph.world.view_one::<&Transform>(self.entity); let t = v.get().expect("Somehow the SceneNode is missing its Transform!"); @@ -76,26 +75,31 @@ impl SceneNode { self.local_transform(graph) } } - } + } */ } #[cfg(test)] mod tests { + use lyra_ecs::{query::{filter::{Has, Not}, Entities}, relation::{ChildOf, RelationOriginComponent}}; use lyra_math::{Transform, Vec3}; - use crate::{tests::FakeMesh, SceneGraph}; + use crate::{tests::FakeMesh, WorldTransform, SceneGraph}; #[test] fn node_world_transform() { let mut scene = SceneGraph::new(); - let a = scene.add_node(Transform::from_translation(Vec3::new(10.0, 10.0, 10.0)), FakeMesh); + let a = scene.add_node((WorldTransform::default(), Transform::from_translation(Vec3::new(10.0, 10.0, 10.0)), FakeMesh)); assert!(a.parent(&scene).unwrap() == scene.root_node); - let b = a.add_node(&mut scene, Transform::from_translation(Vec3::new(50.0, 50.0, 50.0)), FakeMesh); + let b = a.add_node(&mut scene, (WorldTransform::default(), Transform::from_translation(Vec3::new(50.0, 50.0, 50.0)), FakeMesh)); assert!(b.parent(&scene).unwrap() == a); - let wrld_tran = b.world_transform(&scene); - assert_eq!(wrld_tran, Transform::from_translation(Vec3::new(60.0, 60.0, 60.0))); + // update global transforms + let view = scene.world.view::<(Entities, &mut WorldTransform, &Transform, Not>>)>(); + crate::system_update_world_transforms(&scene.world, view).unwrap(); + + let tran = scene.world.view_one::<&WorldTransform>(b.entity).get().unwrap(); + assert_eq!(**tran, Transform::from_translation(Vec3::new(60.0, 60.0, 60.0))); } } \ No newline at end of file diff --git a/lyra-scene/src/world_transform.rs b/lyra-scene/src/world_transform.rs new file mode 100644 index 0000000..3eb54c7 --- /dev/null +++ b/lyra-scene/src/world_transform.rs @@ -0,0 +1,168 @@ +use std::ops::Deref; + +use lyra_ecs::{query::{filter::{Has, Not}, Entities, View}, relation::{ChildOf, RelationOriginComponent}, Component, Entity, World}; +use lyra_math::Transform; +use crate::lyra_engine; + +#[derive(Debug, Copy, Clone, PartialEq, Default, Component)] +pub struct WorldTransform(pub(crate) Transform); + +impl Deref for WorldTransform { + type Target = Transform; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/// A system that updates the [`WorldTransform`]'s for entities and their children. +/// +/// For entities without parents, this will update world transform to match local transform. +/// For any children entities, their [`GlobalTransform`]s will be updated to reflect the changes +/// of its parent entity. +pub fn system_update_world_transforms(world: &World, view: View<(Entities, &mut WorldTransform, &Transform, Not>>)>) -> anyhow::Result<()> { + + for (en, mut world_tran, tran, _) in view.into_iter() { + world_tran.0 = *tran; + println!("Found entity {:?} at {:?}", en, world_tran.translation); + + recurse_update_trans(world, &world_tran, en); + } + + Ok(()) +} + +fn recurse_update_trans(world: &World, parent_transform: &WorldTransform, entity: Entity) { + // store entities and their world transform to process outside of the view. + // it must be done after to avoid attempts of multiple mutable borrows to the archetype column + // with WorldTransform. + let mut next_entities = vec![]; + + for ((en, mut world_tran, tran), _) in + world.view::<(Entities, &mut WorldTransform, &Transform)>() + .relates_to::(entity) + .into_iter() + { + world_tran.0 = parent_transform.0 + *tran; + next_entities.push((en, world_tran.0.clone())); + } + + next_entities.reverse(); + while let Some((en, pos)) = next_entities.pop() { + recurse_update_trans(world, &WorldTransform(pos), en); + } +} + +#[cfg(test)] +mod tests { + use lyra_ecs::{query::{filter::{Has, Not}, Entities}, relation::{ChildOf, RelationOriginComponent}, World}; + use lyra_math::Transform; + + use crate::{system_update_world_transforms, WorldTransform}; + + #[test] + fn test_system() { + let mut world = World::new(); + + let parent = world.spawn((WorldTransform::default(), Transform::from_xyz(10.0, 10.0, 10.0))); + let child = world.spawn((WorldTransform::default(), Transform::from_xyz(15.0, 15.0, 15.0))); + world.add_relation(child, ChildOf, parent); + + let view = world.view::<(Entities, &mut WorldTransform, &Transform, Not>>)>(); + system_update_world_transforms(&world, view).unwrap(); + + let g = world.view_one::<&WorldTransform>(child).get().unwrap(); + assert_eq!(**g, Transform::from_xyz(25.0, 25.0, 25.0)); + } + + #[test] + fn test_system_many_entities() { + let mut world = World::new(); + + let parent = world.spawn((WorldTransform::default(), Transform::from_xyz(10.0, 10.0, 10.0))); + + let mut children = vec![]; + let mut base_offset = 15.0; + for _ in 0..10 { + let en = world.spawn((WorldTransform::default(), Transform::from_xyz(base_offset, base_offset, base_offset))); + world.add_relation(en, ChildOf, parent); + base_offset += 10.0; + children.push(en); + } + + let second_child = world.spawn((WorldTransform::default(), Transform::from_xyz(5.0, 3.0, 8.0))); + world.add_relation(second_child, ChildOf, parent); + + let view = world.view::<(Entities, &mut WorldTransform, &Transform, Not>>)>(); + system_update_world_transforms(&world, view).unwrap(); + + let mut base_offset = 25.0; + for child in children.into_iter() { + let g = world.view_one::<&WorldTransform>(child).get().unwrap(); + println!("Child {:?} at {:?}", child, g.translation); + assert_eq!(**g, Transform::from_xyz(base_offset, base_offset, base_offset)); + + base_offset += 10.0; + } + } + + #[test] + fn test_system_many_children() { + let mut world = World::new(); + + let parent = world.spawn((WorldTransform::default(), Transform::from_xyz(10.0, 10.0, 10.0))); + let first_child = world.spawn((WorldTransform::default(), Transform::from_xyz(15.0, 15.0, 15.0))); + world.add_relation(first_child, ChildOf, parent); + + let sec_chi = world.spawn((WorldTransform::default(), Transform::from_xyz(155.0, 23.0, 6.0))); + world.add_relation(sec_chi, ChildOf, first_child); + + let thir_chi = world.spawn((WorldTransform::default(), Transform::from_xyz(51.0, 85.0, 17.0))); + world.add_relation(thir_chi, ChildOf, sec_chi); + + let four_child = world.spawn((WorldTransform::default(), Transform::from_xyz(24.0, 61.0, 65.0))); + world.add_relation(four_child, ChildOf, thir_chi); + + let five_child = world.spawn((WorldTransform::default(), Transform::from_xyz(356.0, 54.0, 786.0))); + world.add_relation(five_child, ChildOf, four_child); + + let view = world.view::<(Entities, &mut WorldTransform, &Transform, Not>>)>(); + system_update_world_transforms(&world, view).unwrap(); + + let g = world.view_one::<&WorldTransform>(five_child).get().unwrap(); + assert_eq!(**g, Transform::from_xyz(611.0, 248.0, 899.0)); + } + + #[test] + fn test_system_branched_children() { + let mut world = World::new(); + + let parent = world.spawn((WorldTransform::default(), Transform::from_xyz(10.0, 10.0, 10.0))); + let first_child = world.spawn((WorldTransform::default(), Transform::from_xyz(15.0, 15.0, 15.0))); + world.add_relation(first_child, ChildOf, parent); + + let sec_chi = world.spawn((WorldTransform::default(), Transform::from_xyz(155.0, 23.0, 6.0))); + world.add_relation(sec_chi, ChildOf, first_child); + + let thir_chi = world.spawn((WorldTransform::default(), Transform::from_xyz(51.0, 85.0, 17.0))); + world.add_relation(thir_chi, ChildOf, first_child); + + let four_child = world.spawn((WorldTransform::default(), Transform::from_xyz(24.0, 61.0, 65.0))); + world.add_relation(four_child, ChildOf, thir_chi); + + let five_child = world.spawn((WorldTransform::default(), Transform::from_xyz(356.0, 54.0, 786.0))); + world.add_relation(five_child, ChildOf, sec_chi); + + let view = world.view::<(Entities, &mut WorldTransform, &Transform, Not>>)>(); + system_update_world_transforms(&world, view).unwrap(); + + let g = world.view_one::<&WorldTransform>(five_child).get().unwrap(); + assert_eq!(**g, Transform::from_xyz(536.0, 102.0, 817.0)); + + let g = world.view_one::<&WorldTransform>(thir_chi).get().unwrap(); + assert_eq!(**g, Transform::from_xyz(76.0, 110.0, 42.0)); + + let g = world.view_one::<&WorldTransform>(four_child).get().unwrap(); + assert_eq!(**g, Transform::from_xyz(100.0, 171.0, 107.0)); + } +} \ No newline at end of file