nightshade-api 0.44.0

Procedural high level API for the nightshade game engine
Documentation
//! Reading the scene graph: an entity's name, its children and descendants, the
//! roots, and the whole tree flattened for an outliner. The write side
//! ([`set_parent`](crate::prelude::set_parent), spawn) already exists; this is
//! the read side a tool browses.

use nightshade::ecs::transform::queries::query_descendants;
use nightshade::ecs::world::{GLOBAL_TRANSFORM, LOCAL_TRANSFORM};
use nightshade::prelude::*;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};

/// The entity's name, or a stable `Entity {id}` fallback when it has none.
pub fn name(world: &World, entity: Entity) -> String {
    world
        .core
        .get_name(entity)
        .map(|name| name.0.clone())
        .filter(|name| !name.is_empty())
        .unwrap_or_else(|| format!("Entity {}", entity.id))
}

/// Renames the entity.
pub fn set_name(world: &mut World, entity: Entity, name: &str) {
    world.core.set_name(entity, Name(name.to_string()));
}

/// The entity's direct children, in id order.
pub fn children(world: &World, entity: Entity) -> Vec<Entity> {
    let mut found: Vec<Entity> = Vec::new();
    world
        .core
        .query()
        .with(LOCAL_TRANSFORM)
        .iter(|candidate, _, _| {
            if world.core.get_parent(candidate).and_then(|parent| parent.0) == Some(entity) {
                found.push(candidate);
            }
        });
    found.sort_by_key(|entity| entity.id);
    found
}

/// The entity's whole subtree below it, excluding the entity itself.
pub fn descendants(world: &World, entity: Entity) -> Vec<Entity> {
    query_descendants(world, entity)
}

/// Every parentless transform entity, in id order.
pub fn roots(world: &World) -> Vec<Entity> {
    let mut found: Vec<Entity> = Vec::new();
    world
        .core
        .query()
        .with(LOCAL_TRANSFORM | GLOBAL_TRANSFORM)
        .iter(|entity, _, _| {
            if world
                .core
                .get_parent(entity)
                .and_then(|parent| parent.0)
                .is_none()
            {
                found.push(entity);
            }
        });
    found.sort_by_key(|entity| entity.id);
    found
}

/// One row of the flattened scene tree, in depth-first order.
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub struct SceneRow {
    pub id: u32,
    pub name: String,
    pub depth: u32,
    pub has_children: bool,
    pub camera: bool,
    pub light: bool,
    pub mesh: bool,
}

/// The whole transform hierarchy flattened depth-first, the shape an outliner
/// renders.
pub fn scene_tree(world: &World) -> Vec<SceneRow> {
    let mut all: HashSet<Entity> = HashSet::new();
    world
        .core
        .query()
        .with(LOCAL_TRANSFORM | GLOBAL_TRANSFORM)
        .iter(|entity, _, _| {
            all.insert(entity);
        });

    let mut children_map: HashMap<Entity, Vec<Entity>> = HashMap::new();
    let mut root_entities: Vec<Entity> = Vec::new();
    for &entity in &all {
        let parent = world
            .core
            .get_parent(entity)
            .and_then(|parent| parent.0)
            .filter(|parent| all.contains(parent));
        match parent {
            Some(parent) => children_map.entry(parent).or_default().push(entity),
            None => root_entities.push(entity),
        }
    }
    root_entities.sort_by_key(|entity| entity.id);
    for list in children_map.values_mut() {
        list.sort_by_key(|entity| entity.id);
    }

    let mut rows = Vec::new();
    let mut visited: HashSet<Entity> = HashSet::new();
    let mut stack: Vec<(Entity, u32)> = root_entities
        .iter()
        .rev()
        .map(|entity| (*entity, 0))
        .collect();
    while let Some((entity, depth)) = stack.pop() {
        if !visited.insert(entity) {
            continue;
        }
        let has_children = children_map
            .get(&entity)
            .is_some_and(|list| !list.is_empty());
        rows.push(SceneRow {
            id: entity.id,
            name: name(world, entity),
            depth,
            has_children,
            camera: world.core.entity_has_camera(entity),
            light: world.core.entity_has_light(entity),
            mesh: world.core.entity_has_render_mesh(entity),
        });
        if let Some(list) = children_map.get(&entity) {
            for child in list.iter().rev() {
                if !visited.contains(child) {
                    stack.push((*child, depth + 1));
                }
            }
        }
    }
    rows
}