nightshade-editor 0.14.2

Interactive map editor for the Nightshade game engine
use crate::ecs::EditorWorld;
use nightshade::ecs::camera::commands::spawn_pan_orbit_camera_with_config;
use nightshade::ecs::camera::components::PanOrbitCamera;
use nightshade::prelude::*;

pub fn spawn_default(editor_world: &mut EditorWorld, world: &mut World) {
    let camera_entity = spawn_pan_orbit_camera_with_config(
        world,
        "Main Camera".to_string(),
        PanOrbitCamera::new(Vec3::new(0.0, 0.0, 0.0), 5.0).with_yaw_pitch(0.0, 0.3),
    );
    editor_world.resources.camera.camera_entity = Some(camera_entity);
    world.resources.active_camera = Some(camera_entity);
    editor_world
        .resources
        .editor_scene
        .register_scaffolding(camera_entity);
}

pub fn handle_reset_input(editor_world: &mut EditorWorld, world: &mut World) {
    let reset_pressed = world.resources.input.keyboard.is_key_pressed(KeyCode::KeyC);
    let typing_in_text_field = world
        .resources
        .retained_ui
        .interaction
        .focused_entity
        .is_some_and(|entity| {
            world.ui.get_ui_text_input(entity).is_some()
                || world.ui.get_ui_text_area(entity).is_some()
                || world.ui.get_ui_drag_value(entity).is_some()
        });
    if reset_pressed
        && !editor_world.resources.camera.reset_camera_was_pressed
        && !typing_in_text_field
    {
        frame_scene(editor_world, world);
    }
    editor_world.resources.camera.reset_camera_was_pressed = reset_pressed;
}

pub fn focus_point(editor_world: &EditorWorld, world: &World) -> Vec3 {
    let Some(camera_entity) = editor_world.resources.camera.camera_entity else {
        return Vec3::zeros();
    };
    world
        .core
        .get_pan_orbit_camera(camera_entity)
        .map(|orbit| orbit.target_focus)
        .unwrap_or_else(Vec3::zeros)
}

pub fn frame_scene(editor_world: &mut EditorWorld, world: &mut World) {
    let roots = editor_world.resources.loading.model_entities.clone();
    frame_entities(editor_world, world, &roots);
}

pub fn frame_entities(editor_world: &mut EditorWorld, world: &mut World, roots: &[Entity]) {
    let Some(camera_entity) = editor_world.resources.camera.camera_entity else {
        return;
    };

    let mut min = Vec3::new(f32::INFINITY, f32::INFINITY, f32::INFINITY);
    let mut max = Vec3::new(f32::NEG_INFINITY, f32::NEG_INFINITY, f32::NEG_INFINITY);
    let mut found_any = false;

    for &root in roots {
        accumulate_bounds(world, root, &mut min, &mut max, &mut found_any);
        for descendant in nightshade::ecs::transform::queries::query_descendants(world, root) {
            accumulate_bounds(world, descendant, &mut min, &mut max, &mut found_any);
        }
    }

    let fov_rad = world
        .core
        .get_camera(camera_entity)
        .and_then(|camera| match camera.projection {
            nightshade::ecs::camera::components::Projection::Perspective(perspective) => {
                Some(perspective.y_fov_rad)
            }
            _ => None,
        })
        .unwrap_or(45.0_f32.to_radians());

    let (center, radius, half_diagonal) = if found_any {
        let center = (min + max) * 0.5;
        let half_diagonal = ((max - min) * 0.5).norm().max(0.001);
        (
            center,
            half_diagonal / (fov_rad * 0.5).sin() * 1.2,
            half_diagonal,
        )
    } else {
        (Vec3::zeros(), 5.0, 1.0)
    };

    if let Some(camera) = world.core.get_camera_mut(camera_entity)
        && let nightshade::ecs::camera::components::Projection::Perspective(perspective) =
            &mut camera.projection
    {
        perspective.z_near = (half_diagonal * 0.001).clamp(0.001, 0.1);
    }

    if let Some(pan_orbit) = world.core.get_pan_orbit_camera_mut(camera_entity) {
        pan_orbit.target_focus = center;
        pan_orbit.target_radius = radius;
        pan_orbit.target_yaw = 0.0;
        pan_orbit.target_pitch = 0.3;
        pan_orbit.limits.zoom_lower = half_diagonal * 0.001;
        pan_orbit.pan_distance = Some(radius);
    }
}

fn accumulate_bounds(
    world: &World,
    entity: Entity,
    min: &mut Vec3,
    max: &mut Vec3,
    found_any: &mut bool,
) {
    let Some(bounding_volume) = world.core.get_bounding_volume(entity) else {
        return;
    };
    let Some(global_transform) = world.core.get_global_transform(entity) else {
        return;
    };
    let world_obb = bounding_volume.obb.transform(&global_transform.0);
    for corner in world_obb.get_corners() {
        min.x = min.x.min(corner.x);
        min.y = min.y.min(corner.y);
        min.z = min.z.min(corner.z);
        max.x = max.x.max(corner.x);
        max.y = max.y.max(corner.y);
        max.z = max.z.max(corner.z);
        *found_any = true;
    }
}