nightshade-editor 0.14.2

Interactive map editor for the Nightshade game engine
use crate::ecs::EditorWorld;
use nightshade::ecs::lines::components::{Line, Lines};
use nightshade::ecs::world::{GLOBAL_TRANSFORM, LINES, SKIN, VISIBILITY};
use nightshade::prelude::*;
use std::collections::HashSet;

pub fn update(editor_world: &mut EditorWorld, world: &mut World) {
    if !editor_world.resources.skeleton_debug.enabled {
        if let Some(entity) = editor_world.resources.skeleton_debug.entity
            && let Some(data) = world.core.get_lines(entity)
            && !data.lines.is_empty()
        {
            world
                .core
                .set_lines(entity, Lines::new_always_on_top(Vec::new()));
        }
        return;
    }
    let entity = ensure_debug_entity(editor_world, world);
    let lines = build_skeleton_lines(world, &editor_world.resources.skeleton_debug);
    world
        .core
        .set_lines(entity, Lines::new_always_on_top(lines));
}

fn ensure_debug_entity(editor_world: &mut EditorWorld, world: &mut World) -> Entity {
    if let Some(entity) = editor_world.resources.skeleton_debug.entity
        && world.core.entity_has_components(entity, LINES)
    {
        return entity;
    }
    let entity = spawn_entities(world, LINES | VISIBILITY | GLOBAL_TRANSFORM, 1)[0];
    world
        .core
        .set_lines(entity, Lines::new_always_on_top(Vec::new()));
    world
        .core
        .set_visibility(entity, Visibility { visible: true });
    world
        .core
        .set_global_transform(entity, GlobalTransform::default());
    editor_world
        .resources
        .editor_scene
        .register_scaffolding(entity);
    editor_world.resources.skeleton_debug.entity = Some(entity);
    entity
}

fn build_skeleton_lines(world: &World, settings: &crate::ecs::SkeletonDebugState) -> Vec<Line> {
    let skin_owners: Vec<Entity> = world.core.query_entities(SKIN).collect();
    let mut lines = Vec::new();
    for owner in skin_owners {
        let Some(skin) = world.core.get_skin(owner) else {
            continue;
        };
        if skin.joints.is_empty() {
            continue;
        }
        let joint_set: HashSet<Entity> = skin.joints.iter().copied().collect();
        for &joint_entity in &skin.joints {
            let Some(joint_transform) = world.core.get_global_transform(joint_entity) else {
                continue;
            };
            let joint_position = joint_transform.translation();
            push_joint_cross(
                &mut lines,
                joint_position,
                settings.joint_size,
                settings.joint_color,
            );

            let Some(parent) = world
                .core
                .get_parent(joint_entity)
                .and_then(|parent| parent.0)
            else {
                continue;
            };
            if !joint_set.contains(&parent) {
                continue;
            }
            let Some(parent_transform) = world.core.get_global_transform(parent) else {
                continue;
            };
            lines.push(Line {
                start: parent_transform.translation(),
                end: joint_position,
                color: settings.bone_color,
            });
        }
    }
    lines
}

/// Three perpendicular line segments centred on a joint so the user
/// can see where the joint origin lies independently of any bone
/// segment touching it. Sized in world units to match how Blender
/// draws point representations of bones.
fn push_joint_cross(lines: &mut Vec<Line>, position: Vec3, size: f32, color: Vec4) {
    let half = size * 0.5;
    lines.push(Line {
        start: position - Vec3::new(half, 0.0, 0.0),
        end: position + Vec3::new(half, 0.0, 0.0),
        color,
    });
    lines.push(Line {
        start: position - Vec3::new(0.0, half, 0.0),
        end: position + Vec3::new(0.0, half, 0.0),
        color,
    });
    lines.push(Line {
        start: position - Vec3::new(0.0, 0.0, half),
        end: position + Vec3::new(0.0, 0.0, half),
        color,
    });
}