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        .core
16        .get_name(entity)
17        .map(|name| name.0.clone())
18        .filter(|name| !name.is_empty())
19        .unwrap_or_else(|| format!("Entity {}", entity.id))
20}
21
22/// Renames the entity.
23pub fn set_name(world: &mut World, entity: Entity, name: &str) {
24    world.core.set_name(entity, Name(name.to_string()));
25}
26
27/// The entity's direct children, in id order.
28pub fn children(world: &World, entity: Entity) -> Vec<Entity> {
29    let mut found: Vec<Entity> = Vec::new();
30    world
31        .core
32        .query()
33        .with(LOCAL_TRANSFORM)
34        .iter(|candidate, _, _| {
35            if world.core.get_parent(candidate).and_then(|parent| parent.0) == Some(entity) {
36                found.push(candidate);
37            }
38        });
39    found.sort_by_key(|entity| entity.id);
40    found
41}
42
43/// The entity's whole subtree below it, excluding the entity itself.
44pub fn descendants(world: &World, entity: Entity) -> Vec<Entity> {
45    query_descendants(world, entity)
46}
47
48/// Every parentless transform entity, in id order.
49pub fn roots(world: &World) -> Vec<Entity> {
50    let mut found: Vec<Entity> = Vec::new();
51    world
52        .core
53        .query()
54        .with(LOCAL_TRANSFORM | GLOBAL_TRANSFORM)
55        .iter(|entity, _, _| {
56            if world
57                .core
58                .get_parent(entity)
59                .and_then(|parent| parent.0)
60                .is_none()
61            {
62                found.push(entity);
63            }
64        });
65    found.sort_by_key(|entity| entity.id);
66    found
67}
68
69/// One row of the flattened scene tree, in depth-first order.
70#[derive(Clone, PartialEq, Serialize, Deserialize)]
71pub struct SceneRow {
72    pub id: u32,
73    pub name: String,
74    pub depth: u32,
75    pub has_children: bool,
76    pub camera: bool,
77    pub light: bool,
78    pub mesh: bool,
79}
80
81/// The whole transform hierarchy flattened depth-first, the shape an outliner
82/// renders.
83pub fn scene_tree(world: &World) -> Vec<SceneRow> {
84    let mut all: HashSet<Entity> = HashSet::new();
85    world
86        .core
87        .query()
88        .with(LOCAL_TRANSFORM | GLOBAL_TRANSFORM)
89        .iter(|entity, _, _| {
90            all.insert(entity);
91        });
92
93    let mut children_map: HashMap<Entity, Vec<Entity>> = HashMap::new();
94    let mut root_entities: Vec<Entity> = Vec::new();
95    for &entity in &all {
96        let parent = world
97            .core
98            .get_parent(entity)
99            .and_then(|parent| parent.0)
100            .filter(|parent| all.contains(parent));
101        match parent {
102            Some(parent) => children_map.entry(parent).or_default().push(entity),
103            None => root_entities.push(entity),
104        }
105    }
106    root_entities.sort_by_key(|entity| entity.id);
107    for list in children_map.values_mut() {
108        list.sort_by_key(|entity| entity.id);
109    }
110
111    let mut rows = Vec::new();
112    let mut visited: HashSet<Entity> = HashSet::new();
113    let mut stack: Vec<(Entity, u32)> = root_entities
114        .iter()
115        .rev()
116        .map(|entity| (*entity, 0))
117        .collect();
118    while let Some((entity, depth)) = stack.pop() {
119        if !visited.insert(entity) {
120            continue;
121        }
122        let has_children = children_map
123            .get(&entity)
124            .is_some_and(|list| !list.is_empty());
125        rows.push(SceneRow {
126            id: entity.id,
127            name: name(world, entity),
128            depth,
129            has_children,
130            camera: world.core.entity_has_camera(entity),
131            light: world.core.entity_has_light(entity),
132            mesh: world.core.entity_has_render_mesh(entity),
133        });
134        if let Some(list) = children_map.get(&entity) {
135            for child in list.iter().rev() {
136                if !visited.contains(child) {
137                    stack.push((*child, depth + 1));
138                }
139            }
140        }
141    }
142    rows
143}