nightshade-api 0.43.0

Procedural high level API for the nightshade game engine
Documentation
//! Pathfinding in three calls: bake, spawn a walker, tell it where to go.
//! The engine's navmesh systems run in the frame schedule, so walkers move on
//! their own once given a destination.

use nightshade::prelude::nalgebra_glm::Mat4;
use nightshade::prelude::*;

/// Bakes a navigation mesh from the world's current static geometry: every
/// visible mesh that is not a dynamic physics body, an agent, or api
/// scaffolding, transformed into world space. Call once after the level is
/// built, and again after the level changes.
pub fn bake_navmesh(world: &mut World) {
    let mut sources: Vec<(Entity, String, Mat4)> = Vec::new();
    world
        .core
        .query()
        .with(RENDER_MESH | GLOBAL_TRANSFORM)
        .without(NAVMESH_AGENT | CLOTH)
        .iter(|entity, table, index| {
            sources.push((
                entity,
                table.render_mesh[index].name.clone(),
                table.global_transform[index].0,
            ));
        });

    let mut vertices: Vec<[f32; 3]> = Vec::new();
    let mut indices: Vec<[u32; 3]> = Vec::new();
    for (entity, source_mesh_name, matrix) in sources {
        if world
            .core
            .get_visibility(entity)
            .is_some_and(|visibility| !visibility.visible)
        {
            continue;
        }
        if world
            .core
            .get_name(entity)
            .is_some_and(|name| name.0.starts_with(crate::runner::RESERVED_PREFIX))
        {
            continue;
        }
        #[cfg(feature = "physics")]
        if world.core.get_rigid_body(entity).is_some_and(|body| {
            !matches!(
                body.body_type,
                nightshade::ecs::physics::types::RigidBodyType::Fixed
            )
        }) {
            continue;
        }
        let Some(mesh) = registry_entry_by_name(
            &world.resources.assets.mesh_cache.registry,
            &source_mesh_name,
        ) else {
            continue;
        };
        let base_index = vertices.len() as u32;
        for vertex in &mesh.vertices {
            let position = matrix
                * vec4(
                    vertex.position[0],
                    vertex.position[1],
                    vertex.position[2],
                    1.0,
                );
            vertices.push([position.x, position.y, position.z]);
        }
        for triangle in mesh.indices.chunks_exact(3) {
            indices.push([
                base_index + triangle[0],
                base_index + triangle[1],
                base_index + triangle[2],
            ]);
        }
    }

    if let Some(navmesh) =
        generate_navmesh_recast(&vertices, &indices, &RecastNavMeshConfig::default())
    {
        world.resources.navmesh = navmesh;
    }
}

/// Spawns an agent that walks the baked navmesh at three units per second,
/// adjustable with [`set_walk_speed`]. Returns the agent entity, whose
/// [`position`](crate::prelude::position) tracks it as it moves and which
/// renders as a capsule. Parent your own mesh to it with
/// [`set_parent`](crate::prelude::set_parent) for a custom look.
pub fn spawn_walker(world: &mut World, position: Vec3) -> Entity {
    spawn_navmesh_agent(world, position, 3.0)
}

/// Sends the agent toward `destination` along the navmesh.
#[inline]
pub fn walk_to(world: &mut World, agent: Entity, destination: Vec3) {
    set_agent_destination(world, agent, destination);
}

/// Sets the agent's movement speed in units per second.
#[inline]
pub fn set_walk_speed(world: &mut World, agent: Entity, speed: f32) {
    set_agent_speed(world, agent, speed);
}

/// Stops the agent where it stands.
#[inline]
pub fn stop_walking(world: &mut World, agent: Entity) {
    stop_agent(world, agent);
}