Compare commits
2 Commits
main
...
feature/sc
Author | SHA1 | Date |
---|---|---|
SeanOMik | 35815fa019 | |
SeanOMik | b51f1e16ef |
|
@ -83,6 +83,28 @@
|
||||||
},
|
},
|
||||||
"args": [],
|
"args": [],
|
||||||
"cwd": "${workspaceFolder}/lyra-ecs"
|
"cwd": "${workspaceFolder}/lyra-ecs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "lldb",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Debug unit tests in executable 'lyra-scene'",
|
||||||
|
"cargo": {
|
||||||
|
"args": [
|
||||||
|
"test",
|
||||||
|
"--no-run",
|
||||||
|
"--lib",
|
||||||
|
"--package=lyra-scene",
|
||||||
|
//"command::tests::deferred_commands",
|
||||||
|
//"--",
|
||||||
|
//"--exact --nocapture"
|
||||||
|
],
|
||||||
|
"filter": {
|
||||||
|
"name": "lyra-scene",
|
||||||
|
"kind": "lib"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder}/lyra-scene"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -1855,6 +1855,14 @@ dependencies = [
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lyra-scene"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"lyra-ecs",
|
||||||
|
"lyra-math",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lyra-scripting"
|
name = "lyra-scripting"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
|
@ -10,7 +10,7 @@ members = [
|
||||||
"lyra-ecs",
|
"lyra-ecs",
|
||||||
"lyra-reflect",
|
"lyra-reflect",
|
||||||
"lyra-scripting",
|
"lyra-scripting",
|
||||||
"lyra-game", "lyra-math"]
|
"lyra-game", "lyra-math", "lyra-scene"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
scripting = ["dep:lyra-scripting"]
|
scripting = ["dep:lyra-scripting"]
|
||||||
|
|
|
@ -55,7 +55,6 @@ impl ComponentColumn {
|
||||||
/// Set a component from pointer at an entity index.
|
/// Set a component from pointer at an entity index.
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
///
|
|
||||||
/// This column must have space to fit the component, if it does not have room it will panic.
|
/// This column must have space to fit the component, if it does not have room it will panic.
|
||||||
pub unsafe fn set_at(&mut self, entity_index: usize, comp_src: NonNull<u8>, tick: Tick) {
|
pub unsafe fn set_at(&mut self, entity_index: usize, comp_src: NonNull<u8>, tick: Tick) {
|
||||||
assert!(entity_index < self.capacity);
|
assert!(entity_index < self.capacity);
|
||||||
|
@ -76,6 +75,23 @@ impl ComponentColumn {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Inserts an entity and its component at a specific index.
|
||||||
|
pub unsafe fn insert_entity(&mut self, entity_index: usize, comp_src: NonNull<u8>, tick: Tick) {
|
||||||
|
assert!(entity_index < self.capacity);
|
||||||
|
|
||||||
|
let mut data = self.data.borrow_mut();
|
||||||
|
let data = data.deref_mut();
|
||||||
|
|
||||||
|
let size = self.info.layout().size();
|
||||||
|
let dest = NonNull::new_unchecked(data.as_ptr().add(entity_index * size));
|
||||||
|
ptr::copy_nonoverlapping(comp_src.as_ptr(), dest.as_ptr(), size);
|
||||||
|
|
||||||
|
// check if a component spot is being set twice and that the entity's tick is
|
||||||
|
// already stored
|
||||||
|
self.entity_ticks.push(tick);
|
||||||
|
self.len += 1;
|
||||||
|
}
|
||||||
|
|
||||||
/// Get a component at an entities index.
|
/// Get a component at an entities index.
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
|
@ -152,6 +168,8 @@ impl ComponentColumn {
|
||||||
/// Removes a component from the column, freeing it, and returning the old index of the entity that took its place in the column.
|
/// Removes a component from the column, freeing it, and returning the old index of the entity that took its place in the column.
|
||||||
pub unsafe fn remove_component(&mut self, entity_index: usize, tick: &Tick) -> Option<usize> {
|
pub unsafe fn remove_component(&mut self, entity_index: usize, tick: &Tick) -> Option<usize> {
|
||||||
let _ = tick; // may be used at some point
|
let _ = tick; // may be used at some point
|
||||||
|
|
||||||
|
debug_assert!(self.len > 0, "There are no entities in the Archetype to remove from!");
|
||||||
|
|
||||||
let mut data = self.data.borrow_mut();
|
let mut data = self.data.borrow_mut();
|
||||||
let data = data.deref_mut();
|
let data = data.deref_mut();
|
||||||
|
@ -170,8 +188,7 @@ impl ComponentColumn {
|
||||||
mem::swap(&mut old_comp_ptr, &mut new_comp_ptr); // new_comp_ptr is now the old ptr
|
mem::swap(&mut old_comp_ptr, &mut new_comp_ptr); // new_comp_ptr is now the old ptr
|
||||||
|
|
||||||
// make sure to keep entity indexes correct in the ticks list as well
|
// make sure to keep entity indexes correct in the ticks list as well
|
||||||
self.entity_ticks.swap(moved_index, entity_index);
|
self.entity_ticks.swap_remove(entity_index);
|
||||||
self.entity_ticks.pop();
|
|
||||||
|
|
||||||
Some(moved_index)
|
Some(moved_index)
|
||||||
} else { None };
|
} else { None };
|
||||||
|
@ -283,8 +300,7 @@ impl Archetype {
|
||||||
|
|
||||||
bundle.take(|data, type_id, _size| {
|
bundle.take(|data, type_id, _size| {
|
||||||
let col = self.get_column_mut(type_id).unwrap();
|
let col = self.get_column_mut(type_id).unwrap();
|
||||||
unsafe { col.set_at(entity_index.0 as usize, data, *tick); }
|
unsafe { col.insert_entity(entity_index.0 as usize, data, *tick); }
|
||||||
col.len += 1;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
entity_index
|
entity_index
|
||||||
|
@ -293,21 +309,21 @@ impl Archetype {
|
||||||
/// Removes an entity from the Archetype and frees its components. Returns the entity record
|
/// Removes an entity from the Archetype and frees its components. Returns the entity record
|
||||||
/// that took its place in the component column.
|
/// that took its place in the component column.
|
||||||
pub(crate) fn remove_entity(&mut self, entity: Entity, tick: &Tick) -> Option<(Entity, ArchetypeEntityId)> {
|
pub(crate) fn remove_entity(&mut self, entity: Entity, tick: &Tick) -> Option<(Entity, ArchetypeEntityId)> {
|
||||||
let entity_index = *self.entity_ids.get(&entity)
|
let entity_index = self.entity_ids.remove(&entity)
|
||||||
.expect("The entity is not in this Archetype!");
|
.expect("The entity is not in this Archetype!");
|
||||||
let mut removed_entity: Option<(Entity, ArchetypeEntityId)> = None;
|
let mut removed_entity: Option<(Entity, ArchetypeEntityId)> = None;
|
||||||
|
|
||||||
for c in self.columns.iter_mut() {
|
for c in self.columns.iter_mut() {
|
||||||
let moved_entity = unsafe { c.remove_component(entity_index.0 as usize, tick) };
|
let moved_entity = unsafe { c.remove_component(entity_index.0 as usize, tick) };
|
||||||
|
|
||||||
if let Some(res) = moved_entity {
|
if let Some(moved_idx) = moved_entity {
|
||||||
if let Some((_, aid)) = removed_entity {
|
if let Some((_, aid)) = removed_entity {
|
||||||
// Make sure that the moved entity is the same as what was moved in other columns.
|
// Make sure that the moved entity is the same as what was moved in other columns.
|
||||||
assert!(res as u64 == aid.0);
|
assert!(moved_idx as u64 == aid.0);
|
||||||
} else {
|
} else {
|
||||||
// This is the first move, so find the EntityId that points to the column index.
|
// This is the first move, so find the Entity that was moved into this index.
|
||||||
let just_removed = self.entities[res];
|
let just_removed = self.entities[moved_idx];
|
||||||
removed_entity = Some((just_removed, ArchetypeEntityId(res as u64)));
|
removed_entity = Some((just_removed, ArchetypeEntityId(moved_idx as u64)));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If there wasn't a moved entity, make sure no other columns moved something.
|
// If there wasn't a moved entity, make sure no other columns moved something.
|
||||||
|
@ -316,12 +332,15 @@ impl Archetype {
|
||||||
}
|
}
|
||||||
|
|
||||||
// safe from the .expect at the start of this method.
|
// safe from the .expect at the start of this method.
|
||||||
self.entity_ids.remove(&entity).unwrap();
|
//self.entity_ids.remove(&entity).unwrap();
|
||||||
if self.entities.len() > 1 {
|
|
||||||
let len = self.entities.len();
|
// update the archetype index of the moved entity
|
||||||
self.entities.swap(entity_index.0 as _, len - 1);
|
if let Some((moved, _old_idx)) = removed_entity {
|
||||||
|
self.entity_ids.insert(moved, entity_index);
|
||||||
}
|
}
|
||||||
self.entities.pop().unwrap();
|
|
||||||
|
let removed = self.entities.swap_remove(entity_index.0 as _);
|
||||||
|
assert_eq!(removed, entity);
|
||||||
|
|
||||||
// now change the ArchetypeEntityId to be the index that the moved entity was moved into.
|
// now change the ArchetypeEntityId to be the index that the moved entity was moved into.
|
||||||
removed_entity.map(|(e, _a)| (e, entity_index))
|
removed_entity.map(|(e, _a)| (e, entity_index))
|
||||||
|
@ -390,7 +409,8 @@ impl Archetype {
|
||||||
self.capacity = new_cap;
|
self.capacity = new_cap;
|
||||||
}
|
}
|
||||||
|
|
||||||
debug_assert_eq!(self.entity_ids.len(), self.entities.len(), "Somehow the Archetype's entity storage got unsynced");
|
debug_assert_eq!(self.entity_ids.len(), self.entities.len(),
|
||||||
|
"Somehow the Archetype's entity storage got unsynced");
|
||||||
let entity_index = ArchetypeEntityId(self.entity_ids.len() as u64);
|
let entity_index = ArchetypeEntityId(self.entity_ids.len() as u64);
|
||||||
self.entity_ids.insert(entity, entity_index);
|
self.entity_ids.insert(entity, entity_index);
|
||||||
self.entities.push(entity);
|
self.entities.push(entity);
|
||||||
|
@ -402,6 +422,12 @@ impl Archetype {
|
||||||
entity_index
|
entity_index
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ensure that the internal entity lists are synced in length
|
||||||
|
pub(crate) fn ensure_synced(&self) {
|
||||||
|
debug_assert_eq!(self.entity_ids.len(), self.entities.len(),
|
||||||
|
"Somehow the Archetype's entity storage got unsynced");
|
||||||
|
}
|
||||||
|
|
||||||
/// Moves the entity from this archetype into another one.
|
/// Moves the entity from this archetype into another one.
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
|
@ -453,6 +479,36 @@ impl Archetype {
|
||||||
pub fn has_entity(&self, e: Entity) -> bool {
|
pub fn has_entity(&self, e: Entity) -> bool {
|
||||||
self.entity_ids.contains_key(&e)
|
self.entity_ids.contains_key(&e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extend the Archetype by adding more columns.
|
||||||
|
///
|
||||||
|
/// In order to extend the Archetype, the archetype needs the components for the entities
|
||||||
|
/// it already has. These are provided through the `new_columns` parameter. **If the Vec
|
||||||
|
/// does not have the same amount of bundles in it as the amount of entities in the
|
||||||
|
/// Archetype, it will panic!**
|
||||||
|
pub fn extend<B: Bundle>(&mut self, tick: &Tick, new_columns: Vec<B>) {
|
||||||
|
debug_assert_eq!(new_columns.len(), self.len(), "The amount of provided column does not \
|
||||||
|
match the amount of entities");
|
||||||
|
|
||||||
|
let column_info = new_columns.iter()
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.info();
|
||||||
|
|
||||||
|
for coli in column_info.into_iter() {
|
||||||
|
let col = unsafe { ComponentColumn::new(coli, self.capacity) };
|
||||||
|
self.columns.push(col);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (eid, bundle) in new_columns.into_iter().enumerate() {
|
||||||
|
bundle.take(|ptr, tyid, _size| {
|
||||||
|
unsafe {
|
||||||
|
let col = self.get_column_mut(tyid).unwrap();
|
||||||
|
col.insert_entity(eid, ptr, tick.clone());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -666,4 +722,24 @@ mod tests {
|
||||||
assert_eq!(unsafe { *ptr.cast::<u32>().as_ref() }, comp);
|
assert_eq!(unsafe { *ptr.cast::<u32>().as_ref() }, comp);
|
||||||
assert_eq!(col.info, info);
|
assert_eq!(col.info, info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tests removing an entity from the Archetype when it is the only entity in it.
|
||||||
|
#[test]
|
||||||
|
fn remove_single_entity() {
|
||||||
|
let info = (Vec2::new(0.0, 0.0),).info();
|
||||||
|
let mut a = Archetype::from_bundle_info(super::ArchetypeId(0), info);
|
||||||
|
|
||||||
|
let ae = Entity {
|
||||||
|
id: EntityId(0),
|
||||||
|
generation: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
a.add_entity(
|
||||||
|
ae,
|
||||||
|
Vec2::new(10.0, 50.0),
|
||||||
|
&Tick::default()
|
||||||
|
);
|
||||||
|
|
||||||
|
a.remove_entity(ae, &Tick::default());
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -30,7 +30,7 @@ pub use component::*;
|
||||||
pub mod query;
|
pub mod query;
|
||||||
//pub use query::*;
|
//pub use query::*;
|
||||||
|
|
||||||
mod relation;
|
pub mod relation;
|
||||||
pub use relation::Relation;
|
pub use relation::Relation;
|
||||||
|
|
||||||
mod component_info;
|
mod component_info;
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
use super::Relation;
|
||||||
|
|
||||||
|
// TODO: Delete child entities when the parent is deleted
|
||||||
|
pub struct ChildOf;
|
||||||
|
|
||||||
|
impl Relation for ChildOf {
|
||||||
|
|
||||||
|
}
|
|
@ -10,19 +10,20 @@ use crate::lyra_engine;
|
||||||
use crate::World;
|
use crate::World;
|
||||||
|
|
||||||
mod relates_to;
|
mod relates_to;
|
||||||
#[doc(hidden)]
|
|
||||||
pub use relates_to::*;
|
pub use relates_to::*;
|
||||||
|
|
||||||
mod relate_pair;
|
mod relate_pair;
|
||||||
#[doc(hidden)]
|
|
||||||
pub use relate_pair::*;
|
pub use relate_pair::*;
|
||||||
|
|
||||||
|
mod child_of;
|
||||||
|
pub use child_of::*;
|
||||||
|
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
pub trait Relation: 'static {
|
pub trait Relation: 'static {
|
||||||
/// called when a relation of this type is set on a target
|
/// called when a relation of this type is set on a target
|
||||||
fn relation_add(&self, origin: Entity, target: Entity) { }
|
fn relation_add(&mut self, world: &mut World, origin: Entity, target: Entity) { }
|
||||||
/// called when a relation is removed
|
/// called when a relation is removed
|
||||||
fn relation_remove(&self, origin: Entity, target: Entity) { }
|
fn relation_remove(&mut self, world: &mut World, origin: Entity, target: Entity) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A component that stores the target of a relation.
|
/// A component that stores the target of a relation.
|
||||||
|
@ -31,10 +32,16 @@ pub trait Relation: 'static {
|
||||||
/// entities that the relation targets.
|
/// entities that the relation targets.
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct RelationOriginComponent<R: Relation> {
|
pub struct RelationOriginComponent<R: Relation> {
|
||||||
pub(crate) relation: R,
|
pub relation: R,
|
||||||
target: Entity,
|
target: Entity,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<R: Relation> RelationOriginComponent<R> {
|
||||||
|
pub fn target(&self) -> Entity {
|
||||||
|
self.target
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A component that stores the origin of a relation.
|
/// A component that stores the origin of a relation.
|
||||||
///
|
///
|
||||||
/// This component is on the target of the relation and can be used to find the
|
/// This component is on the target of the relation and can be used to find the
|
||||||
|
@ -79,12 +86,12 @@ impl World {
|
||||||
};
|
};
|
||||||
self.insert(target, comp);
|
self.insert(target, comp);
|
||||||
|
|
||||||
let comp = RelationOriginComponent {
|
let mut comp = RelationOriginComponent {
|
||||||
relation,
|
relation,
|
||||||
target,
|
target,
|
||||||
};
|
};
|
||||||
|
|
||||||
comp.relation.relation_add(origin, target);
|
comp.relation.relation_add(self, origin, target);
|
||||||
self.insert(origin, comp);
|
self.insert(origin, comp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,7 +69,7 @@ where
|
||||||
|
|
||||||
unsafe fn fetch<'a>(&self, _world: &'a World, archetype: &'a crate::archetype::Archetype, tick: crate::Tick) -> Self::Fetch<'a> {
|
unsafe fn fetch<'a>(&self, _world: &'a World, archetype: &'a crate::archetype::Archetype, tick: crate::Tick) -> Self::Fetch<'a> {
|
||||||
let _ = tick;
|
let _ = tick;
|
||||||
let col = archetype.get_column(self.type_id())
|
let col = archetype.get_column(TypeId::of::<RelationOriginComponent<R>>())
|
||||||
.expect("You ignored 'can_visit_archetype'!");
|
.expect("You ignored 'can_visit_archetype'!");
|
||||||
|
|
||||||
FetchRelatePair {
|
FetchRelatePair {
|
||||||
|
|
|
@ -133,8 +133,6 @@ impl World {
|
||||||
where
|
where
|
||||||
B: Bundle
|
B: Bundle
|
||||||
{
|
{
|
||||||
// TODO: If the archetype has a single entity, add a component column for the new
|
|
||||||
// component instead of moving the entity to a brand new archetype.
|
|
||||||
// TODO: If the entity already has the components in `bundle`, update the values of the
|
// TODO: If the entity already has the components in `bundle`, update the values of the
|
||||||
// components with the bundle.
|
// components with the bundle.
|
||||||
|
|
||||||
|
@ -142,7 +140,8 @@ impl World {
|
||||||
|
|
||||||
let record = self.entities.entity_record(entity).unwrap();
|
let record = self.entities.entity_record(entity).unwrap();
|
||||||
let current_arch = self.archetypes.get(&record.id).unwrap();
|
let current_arch = self.archetypes.get(&record.id).unwrap();
|
||||||
|
let current_arch_len = current_arch.len();
|
||||||
|
|
||||||
let mut col_types: Vec<DynTypeId> = current_arch.columns.iter().map(|c| c.info.type_id()).collect();
|
let mut col_types: Vec<DynTypeId> = current_arch.columns.iter().map(|c| c.info.type_id()).collect();
|
||||||
let orig_col = col_types.clone();
|
let orig_col = col_types.clone();
|
||||||
col_types.extend(bundle.type_ids());
|
col_types.extend(bundle.type_ids());
|
||||||
|
@ -150,7 +149,9 @@ impl World {
|
||||||
let mut col_infos: Vec<ComponentInfo> = current_arch.columns.iter().map(|c| c.info).collect();
|
let mut col_infos: Vec<ComponentInfo> = current_arch.columns.iter().map(|c| c.info).collect();
|
||||||
col_infos.extend(bundle.info());
|
col_infos.extend(bundle.info());
|
||||||
|
|
||||||
let col_ptrs: Vec<(NonNull<u8>, ComponentInfo)> = current_arch.columns.iter().map(|c| unsafe { (NonNull::new_unchecked(c.borrow_ptr().as_ptr()), c.info) }).collect();
|
let col_ptrs: Vec<(NonNull<u8>, ComponentInfo)> = current_arch.columns.iter()
|
||||||
|
.map(|c| unsafe { (NonNull::new_unchecked(c.borrow_ptr().as_ptr()), c.info) })
|
||||||
|
.collect();
|
||||||
|
|
||||||
if let Some(arch) = self.archetypes.values_mut().find(|a| a.is_archetype_for(&col_types)) {
|
if let Some(arch) = self.archetypes.values_mut().find(|a| a.is_archetype_for(&col_types)) {
|
||||||
let res_index = arch.reserve_one(entity);
|
let res_index = arch.reserve_one(entity);
|
||||||
|
@ -158,19 +159,21 @@ impl World {
|
||||||
for (col_type, (col_ptr, col_info)) in orig_col.into_iter().zip(col_ptrs.into_iter()) {
|
for (col_type, (col_ptr, col_info)) in orig_col.into_iter().zip(col_ptrs.into_iter()) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let ptr = NonNull::new_unchecked(col_ptr.as_ptr()
|
let ptr = NonNull::new_unchecked(col_ptr.as_ptr()
|
||||||
.add(res_index.0 as usize * col_info.layout().size()));
|
.add(record.index.0 as usize * col_info.layout().size()));
|
||||||
let col = arch.get_column_mut(col_type).unwrap();
|
let col = arch.get_column_mut(col_type).unwrap();
|
||||||
|
// set_at is used since the entity was reserved
|
||||||
col.set_at(res_index.0 as _, ptr, tick);
|
col.set_at(res_index.0 as _, ptr, tick);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bundle.take(|data, type_id, _size| {
|
bundle.take(|data, type_id, _size| {
|
||||||
let col = arch.get_column_mut(type_id).unwrap();
|
let col = arch.get_column_mut(type_id).unwrap();
|
||||||
|
// set_at is used since the entity was reserved
|
||||||
unsafe { col.set_at(res_index.0 as _, data, tick); }
|
unsafe { col.set_at(res_index.0 as _, data, tick); }
|
||||||
col.len += 1;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
arch.entity_ids.insert(entity, res_index);
|
arch.entity_ids.insert(entity, res_index);
|
||||||
|
arch.ensure_synced();
|
||||||
|
|
||||||
let new_record = Record {
|
let new_record = Record {
|
||||||
id: arch.id(),
|
id: arch.id(),
|
||||||
|
@ -178,9 +181,29 @@ impl World {
|
||||||
};
|
};
|
||||||
self.entities.insert_entity_record(entity, new_record);
|
self.entities.insert_entity_record(entity, new_record);
|
||||||
} else {
|
} else {
|
||||||
|
if current_arch_len == 1 {
|
||||||
|
// if this entity is the only entity for this archetype, add more columns to it
|
||||||
|
let current_arch = self.archetypes.get_mut(&record.id).unwrap();
|
||||||
|
current_arch.extend(&tick, vec![bundle]);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let new_arch_id = self.next_archetype_id.increment();
|
let new_arch_id = self.next_archetype_id.increment();
|
||||||
let mut archetype = Archetype::from_bundle_info(new_arch_id, col_infos);
|
let mut archetype = Archetype::from_bundle_info(new_arch_id, col_infos);
|
||||||
let entity_arch_id = archetype.add_entity(entity, bundle, &tick);
|
let entity_arch_id = archetype.add_entity(entity, bundle, &tick);
|
||||||
|
|
||||||
|
// move the old components into the new archetype
|
||||||
|
for (column_ptr, column_info) in col_ptrs.into_iter() {
|
||||||
|
unsafe {
|
||||||
|
// ptr of component for the entity
|
||||||
|
let comp_ptr = NonNull::new_unchecked(column_ptr.as_ptr()
|
||||||
|
.add(record.index.0 as usize * column_info.layout().size()));
|
||||||
|
|
||||||
|
let col = archetype.get_column_mut(column_info.type_id()).unwrap();
|
||||||
|
col.insert_entity(entity_arch_id.0 as _, comp_ptr, tick);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.archetypes.insert(new_arch_id, archetype);
|
self.archetypes.insert(new_arch_id, archetype);
|
||||||
|
|
||||||
|
@ -194,7 +217,13 @@ impl World {
|
||||||
}
|
}
|
||||||
|
|
||||||
let current_arch = self.archetypes.get_mut(&record.id).unwrap();
|
let current_arch = self.archetypes.get_mut(&record.id).unwrap();
|
||||||
current_arch.remove_entity(entity, &tick);
|
if current_arch.len() > 1 {
|
||||||
|
current_arch.remove_entity(entity, &tick);
|
||||||
|
} else if current_arch.len() == 1 {
|
||||||
|
// The old archetype will only be removed if there was another archetype that would
|
||||||
|
// work for the entity's components, and the old archetype only had a single entity.
|
||||||
|
self.archetypes.remove(&record.id).unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn entity_archetype(&self, entity: Entity) -> Option<&Archetype> {
|
pub fn entity_archetype(&self, entity: Entity) -> Option<&Archetype> {
|
||||||
|
@ -330,7 +359,7 @@ impl World {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{tests::{Vec2, Vec3}, query::TickOf};
|
use crate::{query::TickOf, tests::{Vec2, Vec3}, Entity};
|
||||||
|
|
||||||
use super::World;
|
use super::World;
|
||||||
|
|
||||||
|
@ -453,6 +482,46 @@ mod tests {
|
||||||
assert!(world.view_one::<&Vec3>(e).get().is_some())
|
assert!(world.view_one::<&Vec3>(e).get().is_some())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn insert_multiple_times() {
|
||||||
|
let v2s = &[Vec2::rand(), Vec2::rand(), Vec2::rand()];
|
||||||
|
let v3s = &[Vec3::rand(), Vec3::rand(), Vec3::rand()];
|
||||||
|
|
||||||
|
let mut world = World::new();
|
||||||
|
let e1 = world.spawn(v2s[0]);
|
||||||
|
let e2 = world.spawn(v2s[1]);
|
||||||
|
let e3 = world.spawn(v2s[2]);
|
||||||
|
println!("Spawned entities");
|
||||||
|
|
||||||
|
let ev2 = world.view_one::<&Vec2>(e2).get()
|
||||||
|
.expect("Failed to find Vec2 and Vec3 on inserted entity!");
|
||||||
|
assert_eq!(*ev2, v2s[1]);
|
||||||
|
drop(ev2);
|
||||||
|
|
||||||
|
let insert_and_assert = |world: &mut World, e: Entity, v2: Vec2, v3: Vec3| {
|
||||||
|
println!("inserting entity");
|
||||||
|
world.insert(e, (v3,));
|
||||||
|
println!("inserted entity");
|
||||||
|
|
||||||
|
let (ev2, ev3) = world.view_one::<(&Vec2, &Vec3)>(e).get()
|
||||||
|
.expect("Failed to find Vec2 and Vec3 on inserted entity!");
|
||||||
|
assert_eq!(*ev2, v2);
|
||||||
|
assert_eq!(*ev3, v3);
|
||||||
|
};
|
||||||
|
|
||||||
|
insert_and_assert(&mut world, e2, v2s[1], v3s[1]);
|
||||||
|
println!("Entity 2 is good");
|
||||||
|
insert_and_assert(&mut world, e3, v2s[2], v3s[2]);
|
||||||
|
println!("Entity 3 is good");
|
||||||
|
assert_eq!(world.archetypes.len(), 2);
|
||||||
|
println!("No extra archetypes were created");
|
||||||
|
|
||||||
|
insert_and_assert(&mut world, e1, v2s[0], v3s[0]);
|
||||||
|
println!("Entity 1 is good");
|
||||||
|
assert_eq!(world.archetypes.len(), 1);
|
||||||
|
println!("Empty archetype was removed");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn view_one() {
|
fn view_one() {
|
||||||
let v = Vec2::rand();
|
let v = Vec2::rand();
|
||||||
|
@ -468,11 +537,6 @@ mod tests {
|
||||||
fn view_change_tracking() {
|
fn view_change_tracking() {
|
||||||
let mut world = World::new();
|
let mut world = World::new();
|
||||||
|
|
||||||
/* let v = Vec2::rand();
|
|
||||||
world.spawn((v,));
|
|
||||||
let v = Vec2::rand();
|
|
||||||
world.spawn((v,)); */
|
|
||||||
|
|
||||||
println!("spawning");
|
println!("spawning");
|
||||||
world.spawn((Vec2::new(10.0, 10.0),));
|
world.spawn((Vec2::new(10.0, 10.0),));
|
||||||
world.spawn((Vec2::new(5.0, 5.0),));
|
world.spawn((Vec2::new(5.0, 5.0),));
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
[package]
|
||||||
|
name = "lyra-scene"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
lyra-ecs = { path = "../lyra-ecs", features = [ "math" ] }
|
||||||
|
lyra-math = { path = "../lyra-math" }
|
|
@ -0,0 +1,305 @@
|
||||||
|
use std::{collections::VecDeque, ops::{Deref, DerefMut}};
|
||||||
|
|
||||||
|
use lyra_ecs::{query::Entities, relation::ChildOf, Bundle, Component, Entity, World};
|
||||||
|
|
||||||
|
// So we can use lyra_ecs::Component derive macro
|
||||||
|
pub(crate) mod lyra_engine {
|
||||||
|
pub(crate) mod ecs {
|
||||||
|
pub use lyra_ecs::*;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod node;
|
||||||
|
use lyra_math::{Transform, Vec3};
|
||||||
|
pub use node::*;
|
||||||
|
|
||||||
|
/// A flag spawned on all scene node entities
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct SceneNodeFlag;
|
||||||
|
|
||||||
|
/// A flag spawned on only the scene root node
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct SceneNodeRoot;
|
||||||
|
|
||||||
|
enum MutCow<'a, T> {
|
||||||
|
Mut(&'a mut T),
|
||||||
|
Owned(T),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> Deref for MutCow<'a, T> {
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
match self {
|
||||||
|
MutCow::Mut(t) => t,
|
||||||
|
MutCow::Owned(t) => t,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> DerefMut for MutCow<'a, T> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
match self {
|
||||||
|
MutCow::Mut(t) => t,
|
||||||
|
MutCow::Owned(t) => t,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A SceneGraph is a Graph of nodes that represents the hierarchy of a scene.
|
||||||
|
///
|
||||||
|
/// This SceneGraph is special in the sense that it is literally just an ECS world with methods
|
||||||
|
/// implemented for it that make it easier to use for a SceneGraph.
|
||||||
|
//#[derive(Default)]
|
||||||
|
pub struct SceneGraph<'a> {
|
||||||
|
pub(crate) world: MutCow<'a, World>,
|
||||||
|
root_node: SceneNode,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> SceneGraph<'a> {
|
||||||
|
/// Create a new SceneGraph with its own ECS World.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let mut world = World::new();
|
||||||
|
|
||||||
|
let e = world.spawn((Transform::from_translation(Vec3::new(0.0, 0.0, 0.0)), SceneNodeRoot));
|
||||||
|
let root = SceneNode::new(None, e);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
world: MutCow::Owned(world),
|
||||||
|
root_node: root
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve a SceneGraph from an ECS World.
|
||||||
|
///
|
||||||
|
/// Returns `None` if the `root_entity` was not created from a `SceneGraph` that was later
|
||||||
|
/// inserted into another world with [`SceneGraph::into_world`].
|
||||||
|
pub fn from_world(world: &'a mut World, root_entity: Entity) -> Option<Self> {
|
||||||
|
if world.view_one::<(&SceneNodeRoot, &Transform)>(root_entity).get().is_none() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(Self {
|
||||||
|
world: MutCow::Mut(world),
|
||||||
|
root_node: SceneNode::new(None, root_entity),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new SceneGraph inside an existing ECS World.
|
||||||
|
pub fn new_from_world(world: &'a mut World) -> Self {
|
||||||
|
let root_en = world.spawn((Transform::from_translation(Vec3::new(0.0, 0.0, 0.0)), SceneNodeRoot));
|
||||||
|
let root = SceneNode::new(None, root_en);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
world: MutCow::Mut(world),
|
||||||
|
root_node: root,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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::<ChildOf>(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<B: Bundle>(&mut self, local_transform: Transform, bundle: B) -> SceneNode {
|
||||||
|
let node = self.root_node.clone();
|
||||||
|
self.add_node_under(&node, local_transform, 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<B: Bundle>(&mut self, parent: &SceneNode, local_transform: Transform, bundle: B) -> SceneNode {
|
||||||
|
world_add_child_node(&mut self.world, parent, local_transform, bundle)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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<F>(&self, mut callback: F)
|
||||||
|
where
|
||||||
|
F: FnMut(&SceneNode, Transform),
|
||||||
|
{
|
||||||
|
self.traverse_down_from(self.root_node.clone(), &mut callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Traverses down the SceneGraph from a starting node, calling `callback` with each
|
||||||
|
/// SceneNode and its world transform.
|
||||||
|
fn traverse_down_from<F>(&self, start: SceneNode, callback: &mut F)
|
||||||
|
where
|
||||||
|
F: FnMut(&SceneNode, Transform),
|
||||||
|
{
|
||||||
|
let v = self.world.view::<(Entities, &Transform)>()
|
||||||
|
.relates_to::<ChildOf>(start.entity());
|
||||||
|
|
||||||
|
for ((e, _), _rel) in v.iter() {
|
||||||
|
let node = SceneNode::new(Some(start.entity()), e);
|
||||||
|
let world_pos = node.world_transform(self);
|
||||||
|
callback(&node, world_pos);
|
||||||
|
|
||||||
|
self.traverse_down_from(node, callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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(crate) fn world_add_child_node<B: Bundle>(world: &mut World, parent: &SceneNode, local_transform: Transform, bundle: B) -> SceneNode {
|
||||||
|
let e = world.spawn(bundle);
|
||||||
|
world.insert(e, (SceneNodeFlag, local_transform));
|
||||||
|
world.add_relation(e, ChildOf, parent.entity());
|
||||||
|
|
||||||
|
SceneNode::new(Some(parent.entity()), e)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod tests {
|
||||||
|
use lyra_ecs::{query::Entities, relation::ChildOf, Component, World};
|
||||||
|
use lyra_math::{Transform, Vec3};
|
||||||
|
|
||||||
|
use crate::{lyra_engine, SceneGraph, SceneNodeRoot};
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct FakeMesh;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
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);
|
||||||
|
assert!(a.parent(&scene).unwrap() == scene.root_node);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
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);
|
||||||
|
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);
|
||||||
|
assert!(b.parent(&scene).unwrap() == a);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn traverse_down() {
|
||||||
|
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 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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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::<ChildOf>(root).iter().next().unwrap();
|
||||||
|
assert_eq!(*child_pos, Transform::from_translation(v2s[0]));
|
||||||
|
|
||||||
|
let ((_, childchild_pos), _) = other_world.view::<(Entities, &Transform)>()
|
||||||
|
.relates_to::<ChildOf>(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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
use lyra_ecs::{query::Entities, relation::{ChildOf, RelationOriginComponent}, Bundle, Entity};
|
||||||
|
use lyra_math::Transform;
|
||||||
|
|
||||||
|
use crate::SceneGraph;
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq)]
|
||||||
|
pub struct SceneNode {
|
||||||
|
//world: WorldLock,
|
||||||
|
parent: Option<Entity>,
|
||||||
|
entity: Entity
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SceneNode {
|
||||||
|
pub fn new(parent: Option<Entity>, entity: Entity) -> Self {
|
||||||
|
Self {
|
||||||
|
parent,
|
||||||
|
entity,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If this node has a parent, retrieves the parent node from the SceneGraph.
|
||||||
|
pub fn parent(&self, graph: &SceneGraph) -> Option<SceneNode> {
|
||||||
|
if let Some(parent) = self.parent {
|
||||||
|
let v = graph.world.view_one::<&RelationOriginComponent<ChildOf>>(parent);
|
||||||
|
|
||||||
|
let p_parent = if let Some(pp) = v.get() {
|
||||||
|
Some(pp.target())
|
||||||
|
} else { None };
|
||||||
|
|
||||||
|
Some(SceneNode::new(p_parent, parent))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parent_entity(&self) -> Option<Entity> {
|
||||||
|
self.parent
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn children(&self, graph: &SceneGraph) -> Vec<SceneNode> {
|
||||||
|
let v = graph.world.view::<Entities>()
|
||||||
|
.relates_to::<ChildOf>(self.entity);
|
||||||
|
|
||||||
|
v.into_iter().map(|(e, _)| SceneNode::new(Some(self.entity), e)).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn entity(&self) -> Entity {
|
||||||
|
self.entity
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a child node to this node.
|
||||||
|
///
|
||||||
|
/// 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<B: Bundle>(&self, graph: &mut SceneGraph, local_transform: Transform, bundle: B) -> SceneNode {
|
||||||
|
graph.add_node_under(self, local_transform, bundle)
|
||||||
|
}
|
||||||
|
|
||||||
|
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!");
|
||||||
|
t.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves the world transform of the Node.
|
||||||
|
///
|
||||||
|
/// This traverses up the SceneGraph from this node.
|
||||||
|
pub fn world_transform(&self, graph: &SceneGraph) -> Transform {
|
||||||
|
match self.parent(graph) {
|
||||||
|
Some(parent) => {
|
||||||
|
let pt = parent.world_transform(graph);
|
||||||
|
pt + self.local_transform(graph)
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
self.local_transform(graph)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use lyra_math::{Transform, Vec3};
|
||||||
|
|
||||||
|
use crate::{tests::FakeMesh, 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);
|
||||||
|
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);
|
||||||
|
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)));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue