Scene Hierarchy
Nightshade supports parent-child relationships between entities, enabling hierarchical transforms where child entities move relative to their parents.
Parent-Child Relationships
Setting a Parent
#![allow(unused)] fn main() { world.core.set_parent(child_entity, Parent(Some(parent_entity))); world.core.set_local_transform_dirty(child_entity, LocalTransformDirty); }
When you set a parent, the child's LocalTransform becomes relative to the parent's position, rotation, and scale.
Getting Children
#![allow(unused)] fn main() { if let Some(children) = world.resources.children_cache.get(&parent_entity) { for child in children { } } }
Removing a Parent
#![allow(unused)] fn main() { world.core.set_parent(child_entity, Parent(None)); }
Transform Propagation
Each frame, transforms propagate through the hierarchy:
#![allow(unused)] fn main() { propagate_transforms(world); }
How It Works
- Find all entities marked with
LocalTransformDirty - Mark all their descendants as dirty
- Sort entities by depth (parents before children)
- For each entity:
- If has parent:
GlobalTransform = parent.GlobalTransform * LocalTransform - If no parent:
GlobalTransform = LocalTransform.to_matrix()
- If has parent:
- Remove the
LocalTransformDirtymarker
Local vs Global Transform
#![allow(unused)] fn main() { pub struct LocalTransform { pub translation: Vec3, pub rotation: Quat, pub scale: Vec3, } pub struct GlobalTransform(pub Mat4); }
- LocalTransform: Position, rotation, scale relative to parent (or world if no parent)
- GlobalTransform: Final world-space transformation matrix used for rendering
Scene Serialization
Scenes can be saved and loaded for level editing and persistence.
Scene Structure
#![allow(unused)] fn main() { pub struct Scene { pub name: String, pub entities: Vec<SerializedEntity>, pub hierarchy: Vec<HierarchyNode>, pub assets: SceneAssets, } pub struct SerializedEntity { pub id: u64, pub name: Option<String>, pub components: SerializedComponents, } pub struct SceneAssets { pub textures: Vec<TextureReference>, pub materials: Vec<MaterialReference>, pub meshes: Vec<MeshReference>, } }
Saving a Scene
#![allow(unused)] fn main() { let scene = world_to_scene(world); save_scene(&scene, "level1.scene")?; }
Loading a Scene
#![allow(unused)] fn main() { let scene = load_scene("level1.scene")?; spawn_scene(world, &scene); }
Binary Serialization
For faster loading and smaller file sizes:
#![allow(unused)] fn main() { let bytes = serialize_scene_binary(&scene)?; let scene = deserialize_scene_binary(&bytes)?; }
Recursive Operations
Despawning with Children
#![allow(unused)] fn main() { despawn_recursive_immediate(world, parent_entity); }
This removes the entity and all its descendants from the world.
Cloning Hierarchy
#![allow(unused)] fn main() { let clone = clone_entity_recursive(world, original_entity); }
Creates a deep copy of an entity and all its children.
Example: Building a Robot Arm
#![allow(unused)] fn main() { fn spawn_robot_arm(world: &mut World) -> Entity { let base = spawn_cube_at(world, Vec3::zeros()); world.core.set_name(base, Name("Base".to_string())); let lower_arm = spawn_cube_at(world, Vec3::new(0.0, 1.5, 0.0)); world.core.set_name(lower_arm, Name("Lower Arm".to_string())); world.core.set_parent(lower_arm, Parent(Some(base))); let upper_arm = spawn_cube_at(world, Vec3::new(0.0, 2.0, 0.0)); world.core.set_name(upper_arm, Name("Upper Arm".to_string())); world.core.set_parent(upper_arm, Parent(Some(lower_arm))); let hand = spawn_cube_at(world, Vec3::new(0.0, 1.5, 0.0)); world.core.set_name(hand, Name("Hand".to_string())); world.core.set_parent(hand, Parent(Some(upper_arm))); base } fn rotate_arm(world: &mut World, lower_arm: Entity, angle: f32) { if let Some(transform) = world.core.get_local_transform_mut(lower_arm) { transform.rotation = nalgebra_glm::quat_angle_axis( angle, &Vec3::z(), ); } world.core.set_local_transform_dirty(lower_arm, LocalTransformDirty); } }
Rotating the lower arm automatically rotates the upper arm and hand with it.