nightshade-editor 0.19.0

Interactive map editor for the Nightshade game engine
//! Greyboxing is an editor activity layered on the general `build` authoring
//! tools: tag placed blocks as brushes, hide them behind final art, and flip
//! between the two. The engine has no notion of greyboxing.

use crate::ecs::EditorWorld;
use nightshade::prelude::*;

const TAG_BRUSH: &str = "greybox_brush";
const TAG_FINAL: &str = "greybox_final";

/// Enter greybox mode: enable snapping and switch the left panel to the
/// greybox tab.
pub fn set_mode(editor_world: &mut EditorWorld, world: &mut World, enabled: bool) {
    editor_world.resources.greybox.enabled = enabled;
    if enabled {
        editor_world.resources.snap.enabled = true;
        let tab_bar = editor_world.resources.ui_handles.tree.tab_bar;
        ui_tab_bar_set_value(world, tab_bar, 1);
    }
}

pub fn tag_brush(world: &mut World, entity: Entity) {
    let tags = world.resources.entities.tags.entry(entity).or_default();
    if !tags.iter().any(|tag| tag == TAG_BRUSH) {
        tags.push(TAG_BRUSH.to_string());
    }
}

#[cfg(not(target_arch = "wasm32"))]
fn tag_final(world: &mut World, entity: Entity) {
    let tags = world.resources.entities.tags.entry(entity).or_default();
    if !tags.iter().any(|tag| tag == TAG_FINAL) {
        tags.push(TAG_FINAL.to_string());
    }
}

pub fn mark_selection_as_brush(editor_world: &mut EditorWorld, world: &mut World) {
    let mut targets: Vec<Entity> = editor_world.resources.ui.selected_entities.clone();
    if targets.is_empty()
        && let Some(entity) = editor_world.resources.ui.selected_entity
    {
        targets.push(entity);
    }
    for entity in targets {
        tag_brush(world, entity);
    }
}

pub fn toggle_show_final(editor_world: &mut EditorWorld, world: &mut World) {
    let next = !editor_world.resources.greybox.show_final;
    editor_world.resources.greybox.show_final = next;
    apply_brush_visibility(world, next);
}

fn ensure_visibility(world: &mut World, entity: Entity, visible: bool) {
    world.core.add_components(entity, VISIBILITY);
    world.core.set_visibility(entity, Visibility { visible });
}

fn apply_brush_visibility(world: &mut World, show_final: bool) {
    let brushes: Vec<Entity> = world
        .resources
        .entities
        .tags
        .iter()
        .filter(|(_, tags)| tags.iter().any(|tag| tag == TAG_BRUSH))
        .map(|(entity, _)| *entity)
        .collect();
    for brush in brushes {
        let descendants = nightshade::ecs::transform::queries::query_descendants(world, brush);
        let final_child = descendants.into_iter().find(|descendant| {
            world
                .resources
                .entities
                .tags
                .get(descendant)
                .map(|tags| tags.iter().any(|tag| tag == TAG_FINAL))
                .unwrap_or(false)
        });
        if let Some(final_child) = final_child {
            ensure_visibility(world, brush, !show_final);
            ensure_visibility(world, final_child, show_final);
        }
    }
}

#[cfg(not(target_arch = "wasm32"))]
pub fn upgrade_selection_with_gltf(editor_world: &mut EditorWorld, world: &mut World) {
    let Some(brush) = editor_world.resources.ui.selected_entity else {
        return;
    };
    let Some(path) = rfd::FileDialog::new()
        .add_filter("glTF", &["glb", "gltf"])
        .set_title("Pick a glTF to upgrade this brush")
        .pick_file()
    else {
        return;
    };
    let Ok(bytes) = std::fs::read(&path) else {
        return;
    };
    let Ok(mut result) = nightshade::ecs::prefab::import_gltf_from_bytes(&bytes) else {
        return;
    };
    tag_brush(world, brush);
    let prefabs = std::mem::take(&mut result.prefabs);
    let animations = result.animations.clone();
    let skins = result.skins.clone();
    nightshade::ecs::loading::queue_gltf_load(world, &mut result);

    for prefab in &prefabs {
        let child = nightshade::ecs::prefab::spawn_prefab_with_skins(
            world,
            prefab,
            &animations,
            &skins,
            Vec3::zeros(),
        );
        world.core.add_components(child, IGNORE_PARENT_SCALE);
        update_parent(world, child, Some(Parent(Some(brush))));
        if let Some(transform) = world.core.get_local_transform_mut(child) {
            transform.translation = Vec3::zeros();
        }
        mark_local_transform_dirty(world, child);
        tag_final(world, child);
        crate::scene_writeback::register_subtree(
            &mut editor_world.resources.project,
            &mut editor_world.resources.editor_scene,
            world,
            child,
        );
    }

    editor_world.resources.greybox.show_final = true;
    apply_brush_visibility(world, true);
}

#[cfg(target_arch = "wasm32")]
pub fn upgrade_selection_with_gltf(_editor_world: &mut EditorWorld, _world: &mut World) {}