Skip to main content

nightshade_api/
hierarchy.rs

1//! Reading the scene graph: an entity's name, its children and descendants, the
2//! roots, and the whole tree flattened for an outliner. The write side
3//! ([`set_parent`](crate::prelude::set_parent), spawn) already exists; this is
4//! the read side a tool browses.
5
6use nightshade::ecs::transform::queries::query_descendants;
7use nightshade::ecs::world::{GLOBAL_TRANSFORM, LOCAL_TRANSFORM};
8use nightshade::prelude::*;
9use serde::{Deserialize, Serialize};
10use std::collections::{HashMap, HashSet};
11
12/// The entity's name, or a stable `Entity {id}` fallback when it has none.
13pub fn name(world: &World, entity: Entity) -> String {
14    world
15        .get::<nightshade::ecs::primitives::Name>(entity)
16        .map(|name| name.0.clone())
17        .filter(|name| !name.is_empty())
18        .unwrap_or_else(|| format!("Entity {}", entity.id))
19}
20
21/// Renames the entity.
22pub fn set_name(world: &mut World, entity: Entity, name: &str) {
23    world.set(entity, Name(name.to_string()));
24}
25
26/// The entity's direct children, in id order.
27pub fn children(world: &World, entity: Entity) -> Vec<Entity> {
28    let mut found: Vec<Entity> = Vec::new();
29    for candidate in world.ecs.worlds[CORE].query_entities(LOCAL_TRANSFORM) {
30        if world
31            .get::<freecs::dynamic::ChildOf>(candidate)
32            .map(|child_of| child_of.0)
33            == Some(entity)
34        {
35            found.push(candidate);
36        }
37    }
38    found.sort_by_key(|entity| entity.id);
39    found
40}
41
42/// The entity's whole subtree below it, excluding the entity itself.
43pub fn descendants(world: &World, entity: Entity) -> Vec<Entity> {
44    query_descendants(world, entity)
45}
46
47/// Every parentless transform entity, in id order.
48pub fn roots(world: &World) -> Vec<Entity> {
49    let mut found: Vec<Entity> = Vec::new();
50    for entity in world.ecs.worlds[CORE].query_entities(LOCAL_TRANSFORM | GLOBAL_TRANSFORM) {
51        if world
52            .get::<freecs::dynamic::ChildOf>(entity)
53            .map(|child_of| child_of.0)
54            .is_none()
55        {
56            found.push(entity);
57        }
58    }
59    found.sort_by_key(|entity| entity.id);
60    found
61}
62
63/// One row of the flattened scene tree, in depth-first order.
64#[derive(Clone, PartialEq, Serialize, Deserialize)]
65pub struct SceneRow {
66    pub id: u32,
67    pub name: String,
68    pub depth: u32,
69    pub has_children: bool,
70    pub camera: bool,
71    pub light: bool,
72    pub mesh: bool,
73}
74
75/// The whole transform hierarchy flattened depth-first, the shape an outliner
76/// renders.
77pub fn scene_tree(world: &World) -> Vec<SceneRow> {
78    let all: HashSet<Entity> = world.ecs.worlds[CORE]
79        .query_entities(LOCAL_TRANSFORM | GLOBAL_TRANSFORM)
80        .collect();
81
82    let mut children_map: HashMap<Entity, Vec<Entity>> = HashMap::new();
83    let mut root_entities: Vec<Entity> = Vec::new();
84    for &entity in &all {
85        let parent = world
86            .get::<freecs::dynamic::ChildOf>(entity)
87            .map(|child_of| child_of.0)
88            .filter(|parent| all.contains(parent));
89        match parent {
90            Some(parent) => children_map.entry(parent).or_default().push(entity),
91            None => root_entities.push(entity),
92        }
93    }
94    root_entities.sort_by_key(|entity| entity.id);
95    for list in children_map.values_mut() {
96        list.sort_by_key(|entity| entity.id);
97    }
98
99    let mut rows = Vec::new();
100    let mut visited: HashSet<Entity> = HashSet::new();
101    let mut stack: Vec<(Entity, u32)> = root_entities
102        .iter()
103        .rev()
104        .map(|entity| (*entity, 0))
105        .collect();
106    while let Some((entity, depth)) = stack.pop() {
107        if !visited.insert(entity) {
108            continue;
109        }
110        let has_children = children_map
111            .get(&entity)
112            .is_some_and(|list| !list.is_empty());
113        rows.push(SceneRow {
114            id: entity.id,
115            name: name(world, entity),
116            depth,
117            has_children,
118            camera: world.has::<nightshade::ecs::camera::components::Camera>(entity),
119            light: world.has::<nightshade::ecs::light::components::Light>(entity),
120            mesh: world.has::<nightshade::ecs::mesh::components::RenderMesh>(entity),
121        });
122        if let Some(list) = children_map.get(&entity) {
123            for child in list.iter().rev() {
124                if !visited.contains(child) {
125                    stack.push((*child, depth + 1));
126                }
127            }
128        }
129    }
130    rows
131}