nightshade 0.13.1

A cross-platform data-oriented game engine.
Documentation
use super::components::NavMeshAgentState;
use crate::ecs::world::{
    BOUNDING_VOLUME, GLOBAL_TRANSFORM, LOCAL_TRANSFORM, LOCAL_TRANSFORM_DIRTY, MATERIAL_REF, NAME,
    NAVMESH_AGENT, RENDER_MESH, VISIBILITY, World,
};
use nalgebra_glm::Vec3;

pub fn spawn_navmesh_agent(world: &mut World, position: Vec3, speed: f32) -> freecs::Entity {
    let entity = world.spawn_entities(
        NAME | LOCAL_TRANSFORM
            | GLOBAL_TRANSFORM
            | LOCAL_TRANSFORM_DIRTY
            | RENDER_MESH
            | MATERIAL_REF
            | BOUNDING_VOLUME
            | VISIBILITY
            | NAVMESH_AGENT,
        1,
    )[0];

    if let Some(name) = world.core.get_name_mut(entity) {
        name.0 = "NavMesh Agent".to_string();
    }

    if let Some(transform) = world.core.get_local_transform_mut(entity) {
        transform.translation = position;
        transform.scale = nalgebra_glm::vec3(0.4, 0.8, 0.4);
    }

    if let Some(mesh) = world.core.get_render_mesh_mut(entity) {
        mesh.name = "Capsule".to_string();
    }

    if let Some(agent) = world.core.get_navmesh_agent_mut(entity) {
        agent.movement_speed = speed;
    }

    if let Some(bounding_volume) = world.core.get_bounding_volume_mut(entity) {
        *bounding_volume =
            crate::ecs::bounding_volume::components::BoundingVolume::from_mesh_type("Capsule");
    }

    if let Some(visibility) = world.core.get_visibility_mut(entity) {
        visibility.visible = true;
    }

    entity
}

pub fn set_agent_destination(world: &mut World, entity: freecs::Entity, destination: Vec3) {
    if let Some(agent) = world.core.get_navmesh_agent_mut(entity) {
        agent.target_position = Some(destination);
        agent.state = NavMeshAgentState::PathPending;
    }
}

pub fn stop_agent(world: &mut World, entity: freecs::Entity) {
    if let Some(agent) = world.core.get_navmesh_agent_mut(entity) {
        agent.clear_destination();
    }
}

pub fn set_agent_speed(world: &mut World, entity: freecs::Entity, speed: f32) {
    if let Some(agent) = world.core.get_navmesh_agent_mut(entity) {
        agent.movement_speed = speed;
    }
}

pub fn clear_navmesh(world: &mut World) {
    world.resources.navmesh.clear();
}

pub fn set_navmesh_debug_draw(world: &mut World, enabled: bool) {
    world.resources.navmesh.debug_draw = enabled;
}

pub fn get_agent_state(world: &World, entity: freecs::Entity) -> Option<NavMeshAgentState> {
    world
        .core
        .get_navmesh_agent(entity)
        .map(|agent| agent.state)
}

pub fn get_agent_path_length(world: &World, entity: freecs::Entity) -> Option<usize> {
    world
        .core
        .get_navmesh_agent(entity)
        .map(|agent| agent.remaining_waypoints())
}

#[cfg(all(feature = "physics", feature = "navmesh"))]
pub fn generate_navmesh_from_world(
    world: &mut World,
    config: &super::generation::RecastNavMeshConfig,
) {
    let mut vertices: Vec<[f32; 3]> = Vec::new();
    let mut indices: Vec<[u32; 3]> = Vec::new();

    for (_, collider) in world.resources.physics.collider_set.iter() {
        let parent_handle = collider.parent();
        let is_static = if let Some(parent_handle) = parent_handle {
            world
                .resources
                .physics
                .rigid_body_set
                .get(parent_handle)
                .is_some_and(|rb| !rb.is_dynamic())
        } else {
            true
        };

        if !is_static {
            continue;
        }

        let position = collider.position();
        let shape = collider.shape();

        if let Some(trimesh) = shape.as_trimesh() {
            let base_index = vertices.len() as u32;
            for vertex in trimesh.vertices() {
                let world_vertex = position * vertex;
                vertices.push([world_vertex.x, world_vertex.y, world_vertex.z]);
            }
            for triangle in trimesh.indices() {
                indices.push([
                    base_index + triangle[0],
                    base_index + triangle[1],
                    base_index + triangle[2],
                ]);
            }
        } else if let Some(cuboid) = shape.as_cuboid() {
            let half = cuboid.half_extents;
            let base_index = vertices.len() as u32;

            let corners = [
                rapier3d::na::Point3::new(-half.x, -half.y, -half.z),
                rapier3d::na::Point3::new(half.x, -half.y, -half.z),
                rapier3d::na::Point3::new(half.x, half.y, -half.z),
                rapier3d::na::Point3::new(-half.x, half.y, -half.z),
                rapier3d::na::Point3::new(-half.x, -half.y, half.z),
                rapier3d::na::Point3::new(half.x, -half.y, half.z),
                rapier3d::na::Point3::new(half.x, half.y, half.z),
                rapier3d::na::Point3::new(-half.x, half.y, half.z),
            ];

            for corner in &corners {
                let world_vertex = position * corner;
                vertices.push([world_vertex.x, world_vertex.y, world_vertex.z]);
            }

            let cube_triangles: [[u32; 3]; 12] = [
                [0, 1, 2],
                [0, 2, 3],
                [4, 6, 5],
                [4, 7, 6],
                [0, 4, 5],
                [0, 5, 1],
                [2, 6, 7],
                [2, 7, 3],
                [0, 3, 7],
                [0, 7, 4],
                [1, 5, 6],
                [1, 6, 2],
            ];

            for triangle in &cube_triangles {
                indices.push([
                    base_index + triangle[0],
                    base_index + triangle[1],
                    base_index + triangle[2],
                ]);
            }
        }
    }

    if vertices.is_empty() || indices.is_empty() {
        return;
    }

    if let Some(navmesh) = super::generation::generate_navmesh_recast(&vertices, &indices, config) {
        world.resources.navmesh = navmesh;
    }
}