nightshade 0.13.3

A cross-platform data-oriented game engine.
Documentation
use crate::ecs::world::World;
use crate::mcp::McpResponse;

pub(crate) fn mcp_spawn_light(
    world: &mut World,
    request: crate::mcp::SpawnLightRequest,
) -> McpResponse {
    use crate::ecs::light::components::{Light, LightType};
    use crate::ecs::world::{
        GLOBAL_TRANSFORM, LIGHT, LOCAL_TRANSFORM, LOCAL_TRANSFORM_DIRTY, NAME,
    };

    if world.resources.entity_names.contains_key(&request.name) {
        return McpResponse::Error(format!("Entity '{}' already exists", request.name));
    }

    let light_type_enum = match request.light_type.to_lowercase().as_str() {
        "point" => LightType::Point,
        "spot" => LightType::Spot,
        "directional" => LightType::Directional,
        _ => {
            return McpResponse::Error(format!("Unknown light type: {}", request.light_type));
        }
    };

    let entity = world.spawn_entities(
        NAME | LOCAL_TRANSFORM | LOCAL_TRANSFORM_DIRTY | GLOBAL_TRANSFORM | LIGHT,
        1,
    )[0];

    world.core.set_name(
        entity,
        crate::ecs::name::components::Name(request.name.clone()),
    );
    world.core.set_local_transform(
        entity,
        crate::ecs::transform::components::LocalTransform {
            translation: nalgebra_glm::Vec3::new(
                request.position[0],
                request.position[1],
                request.position[2],
            ),
            rotation: nalgebra_glm::Quat::identity(),
            scale: nalgebra_glm::Vec3::new(1.0, 1.0, 1.0),
        },
    );
    world.core.set_local_transform_dirty(
        entity,
        crate::ecs::transform::components::LocalTransformDirty,
    );
    world.core.set_global_transform(
        entity,
        crate::ecs::transform::components::GlobalTransform::default(),
    );

    let color_vec = request
        .color
        .map(|c| nalgebra_glm::Vec3::new(c[0], c[1], c[2]))
        .unwrap_or(nalgebra_glm::Vec3::new(1.0, 1.0, 1.0));

    world.core.set_light(
        entity,
        Light {
            light_type: light_type_enum,
            color: color_vec,
            intensity: request.intensity.unwrap_or(5.0),
            range: request.range.unwrap_or(100.0),
            inner_cone_angle: request
                .inner_cone_angle
                .map(|a| a.to_radians())
                .unwrap_or(std::f32::consts::PI / 6.0),
            outer_cone_angle: request
                .outer_cone_angle
                .map(|a| a.to_radians())
                .unwrap_or(std::f32::consts::PI / 4.0),
            cast_shadows: request.cast_shadows.unwrap_or(false),
            shadow_bias: 0.0005,
        },
    );

    world
        .resources
        .entity_names
        .insert(request.name.clone(), entity);
    McpResponse::Success(format!(
        "Spawned {} light '{}'",
        request.light_type, request.name
    ))
}

pub(crate) fn mcp_set_light(
    world: &mut World,
    request: crate::mcp::SetLightRequest,
) -> Result<McpResponse, McpResponse> {
    let entity = super::resolve_entity(world, &request.name)?;

    let Some(light) = world.core.get_light_mut(entity) else {
        return Err(McpResponse::Error(format!(
            "Entity '{}' has no light component",
            request.name
        )));
    };

    if let Some(c) = request.color {
        light.color = nalgebra_glm::Vec3::new(c[0], c[1], c[2]);
    }
    if let Some(i) = request.intensity {
        light.intensity = i;
    }
    if let Some(r) = request.range {
        light.range = r;
    }
    if let Some(angle) = request.inner_cone_angle {
        light.inner_cone_angle = angle.to_radians();
    }
    if let Some(angle) = request.outer_cone_angle {
        light.outer_cone_angle = angle.to_radians();
    }
    if let Some(shadows) = request.cast_shadows {
        light.cast_shadows = shadows;
    }
    if let Some(bias) = request.shadow_bias {
        light.shadow_bias = bias;
    }

    Ok(McpResponse::Success(format!(
        "Updated light '{}'",
        request.name
    )))
}

pub(crate) fn mcp_set_camera(
    world: &mut World,
    request: crate::mcp::SetCameraRequest,
) -> McpResponse {
    use crate::ecs::camera::components::Projection;

    let Some(camera_entity) = world.resources.active_camera else {
        return McpResponse::Error("No active camera".to_string());
    };

    if let Some(pos) = request.position
        && let Some(transform) = world.core.get_local_transform_mut(camera_entity)
    {
        transform.translation = nalgebra_glm::Vec3::new(pos[0], pos[1], pos[2]);
        crate::ecs::transform::commands::mark_local_transform_dirty(world, camera_entity);
    }

    if let Some(target_pos) = request.target
        && let Some(transform) = world.core.get_local_transform_mut(camera_entity)
    {
        let eye = transform.translation;
        let target = nalgebra_glm::Vec3::new(target_pos[0], target_pos[1], target_pos[2]);
        let up = nalgebra_glm::Vec3::new(0.0, 1.0, 0.0);

        let view = nalgebra_glm::look_at(&eye, &target, &up);
        let rotation_matrix = view.fixed_view::<3, 3>(0, 0).transpose();
        transform.rotation = nalgebra_glm::mat3_to_quat(&rotation_matrix);
        crate::ecs::transform::commands::mark_local_transform_dirty(world, camera_entity);
    }

    if let Some(fov_degrees) = request.fov
        && let Some(camera) = world.core.get_camera_mut(camera_entity)
        && let Projection::Perspective(ref mut perspective) = camera.projection
    {
        perspective.y_fov_rad = fov_degrees.to_radians();
    }

    McpResponse::Success("Camera updated".to_string())
}