# Scene Graph & Entity System The `oxide_engine::scene` module is the world model every later system plugs into. It pairs a lightweight ECS ([`hecs`](https://docs.rs/hecs)) with a parent/child [`Transform`](math.md) hierarchy, so entities can hold arbitrary components *and* live in a spatial tree. This document is the usage reference for the module as delivered in **Stage 3**. For the math types it builds on, see [math.md](math.md); for coordinate and units conventions, see [conventions.md](conventions.md). ## Importing ```rust use oxide_engine::scene::{Scene, Node, Entity, DespawnPolicy, SceneError}; // or, for the common types, via the prelude: use oxide_engine::prelude::*; // Scene, Node, Entity, DespawnPolicy, SceneError, Transform, … ``` The full ECS is re-exported as `oxide_engine::hecs` so you share one copy of `Entity` and the query API with the engine. ## Mental model - An **entity** is a `hecs::Entity` handle — a small `Copy` id. - Every entity created through the scene carries a [`Node`](#node) (name + enabled flag) and a **local** [`Transform`](math.md). - The **hierarchy** (which entity parents which) is owned by the `Scene`, not stored as components. This keeps child ordering deterministic and makes reparenting cheap. - A **local** transform is what you author. A **world** transform is the local composed with every ancestor: `world = parent_world * local`. The scene resolves these on demand; it does not cache them. | Type | Role | |------|------| | [`Scene`](#scene) | Owns entities + hierarchy; spawn, despawn, reparent, query, resolve transforms | | [`Node`](#node) | Per-entity metadata: `name`, `enabled` | | [`DespawnPolicy`](#despawning) | Whether despawn takes the subtree or detaches children | | [`SceneError`](#errors) | Reparent / (de)serialization failures | --- ## `Scene` ### Building a hierarchy ```rust use oxide_engine::prelude::*; let mut scene = Scene::new(); // A root entity (no parent). let sun = scene.spawn("sun", Transform::IDENTITY); // Children. `spawn_child` panics if the parent is not a live entity. let planet = scene.spawn_child( sun, "planet", Transform::from_translation(Vec3::new(10.0, 0.0, 0.0)), ); let moon = scene.spawn_child( planet, "moon", Transform::from_translation(Vec3::new(0.0, 0.0, 2.0)), ); ``` `spawn`/`spawn_child` take anything that converts into a `Node`, so a bare `&str` works as a name (`"planet"` ≡ `Node::new("planet")`); pass a `Node` directly when you need to set `enabled`. ### Resolving world transforms ```rust // One entity (walks up the parent chain): let moon_world = scene.world_transform(moon).unwrap(); // Every entity at once (single top-down pass — prefer this in bulk): let worlds = scene.world_transforms(); // HashMap ``` `world_transforms()` is the path the renderer will use; it resolves a 10,000-entity, 5-level scene in well under a millisecond (see the `world_transforms_10k_depth5` benchmark). ### Reparenting ```rust scene.set_parent(moon, Some(sun))?; // moon now orbits the sun directly scene.set_parent(moon, None)?; // moon becomes a root ``` Reparenting preserves the **local** transform (it does not compensate to keep the world transform fixed). Cycles are rejected: parenting an entity to itself or to one of its descendants returns [`SceneError::WouldCycle`], leaving the hierarchy untouched. ### Despawning ```rust use oxide_engine::scene::DespawnPolicy; // Remove the entity and its entire subtree: scene.despawn(planet, DespawnPolicy::Recursive); // Remove only the entity; its children move up to its parent (or become roots // if it was a root): scene.despawn(planet, DespawnPolicy::DetachChildren); ``` ### Editing nodes ```rust scene.set_name(planet, "earth"); scene.set_enabled(moon, false); // later systems skip disabled subtrees scene.set_local_transform(moon, Transform::IDENTITY); let name = scene.name(planet); // Option let on = scene.is_enabled(moon); // Option let local = scene.local_transform(moon); // Option ``` ### Querying ```rust for &root in scene.roots() { /* … */ } for &child in scene.children(planet) { /* … */ } let parent = scene.parent(moon); // Option let n = scene.len(); ``` ### Extra components (it's a real ECS) Entities are full `hecs` entities, so later stages attach their own components (meshes, rigid bodies, …) alongside the `Node`/`Transform`: ```rust scene.world_mut().insert_one(planet, /* e.g. */ 0u32).unwrap(); let value = scene.get::(planet); // Option> ``` Use `world()` for read-only queries and `world_mut()` for adding/removing *non-hierarchy* components. Drive lifecycle and parenting through the `Scene` methods so the hierarchy bookkeeping stays consistent — spawning or despawning directly on the world bypasses it. --- ## `Node` ```rust pub struct Node { pub name: String, // display name; not required to be unique pub enabled: bool, // honored by later systems, not by transform resolution } ``` `Node::new(name)` builds an enabled node. `enabled` is a declaration of intent: Stage 3 only stores and toggles it; rendering/physics/audio will skip disabled subtrees in later stages. It deliberately does **not** affect `world_transform`, which is purely geometric. --- ## Serialization A scene round-trips through RON. Because `hecs::Entity` handles are not stable across a save/load, the scene is flattened to an indexed node list in a deterministic pre-order walk, so serialize → deserialize → serialize is byte-for-byte stable. ```rust let ron: String = scene.to_ron()?; let restored = Scene::from_ron(&ron)?; assert_eq!(ron, restored.to_ron()?); // identical ``` Corrupt input (out-of-range child indices, a node listed as both root and child) is rejected with [`SceneError::Deserialize`]. --- ## Errors ```rust pub enum SceneError { NoSuchEntity, // operation referenced a dead entity WouldCycle, // reparent would make an entity its own ancestor Serialize(String), // encoding to RON failed Deserialize(String), // decoding failed or data was inconsistent } ``` --- ## Example A complete, runnable tour lives in [`examples/src/bin/scene_basic.rs`](../examples/src/bin/scene_basic.rs): ```sh cargo run -p oxide-examples --bin scene_basic ``` It builds a sun/planet/moon hierarchy, prints local vs. world transforms, reparents a node, round-trips through RON, and despawns with a detach policy. ## In the editor `oxide-editor` renders a **Scene Hierarchy** panel (left) and an **Inspector** (right) over the viewport, driving this same API: select a node, rename it, toggle its `enabled` flag, reparent it via the Inspector's parent dropdown, and add/delete nodes from the toolbar. The egui integration is editor-only — the engine exposes a generic post-clear draw hook ([`App::render`]) and keeps egui out of its own dependency tree. [`App::render`]: ../engine/src/window/app.rs ## Design notes - **Why hierarchy outside the ECS?** Storing `Parent`/`Children` as components is idiomatic but makes ordered iteration and reparenting awkward (archetype moves, borrow juggling) and gives no ordering guarantee. Keeping the tree in the `Scene` yields deterministic child order — which serialization and the editor both rely on — and O(1) link edits. The ECS still owns all entity *data*. - **World transforms are resolved, not stored.** There is no dirty-flag cache yet; `world_transforms()` recomputes in one pass. A cache can be added later behind the same API without changing callers. - **Despawn policies** map onto the two things callers actually want: delete a whole subtree, or remove one node and keep its children.