1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
//! 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);
}