Skip to main content

waymark/
lib.rs

1//! Detour component for navigation mesh pathfinding
2//!
3//! Detour is a pathfinding component that works with navigation meshes
4//! generated by Recast, or navigation meshes created by other means.
5
6// Allow unused code in tests - test code often has intentionally unused variables
7// for demonstration or future use
8#![cfg_attr(test, allow(unused, unused_comparisons))]
9
10#[cfg(feature = "serialization")]
11pub mod binary_format;
12pub mod bvh_tree;
13pub mod detour_common;
14pub mod detour_math;
15pub mod dt_status;
16pub mod error;
17mod hierarchical_pathfinding;
18pub mod nav_mesh;
19mod nav_mesh_builder;
20mod nav_mesh_query;
21pub mod node_pool;
22mod path_queue;
23pub mod poly_query;
24pub mod raycast_hit;
25mod sliced_pathfinding;
26mod status;
27
28#[cfg(test)]
29mod advanced_navigation_function_tests;
30#[cfg(test)]
31mod concurrency_safety_tests;
32#[cfg(all(test, feature = "serialization"))]
33mod cross_platform_serialization_tests;
34#[cfg(test)]
35mod detour_common_geometry_tests;
36#[cfg(test)]
37mod detour_edge_cases_tests;
38#[cfg(test)]
39mod detour_math_operations_tests;
40#[cfg(test)]
41mod detour_memory_scalability_tests;
42#[cfg(test)]
43mod detour_multi_tile_tests;
44#[cfg(test)]
45mod detour_off_mesh_connection_tests;
46#[cfg(test)]
47mod detour_parameter_validation_tests;
48#[cfg(test)]
49mod detour_query_filter_tests;
50#[cfg(test)]
51mod detour_raycast_comprehensive_tests;
52#[cfg(test)]
53mod detour_spatial_query_tests;
54#[cfg(test)]
55mod platform_compatibility_tests;
56#[cfg(test)]
57mod test_mesh_helpers;
58
59use glam::Vec3;
60
61pub use dt_status::{
62    DtStatus, dt_status_detail, dt_status_failed, dt_status_in_progress, dt_status_succeed,
63};
64pub use error::DetourError;
65pub use hierarchical_pathfinding::{
66    Cluster, ClusterConnection, HierarchicalConfig, HierarchicalPath, HierarchicalPathfinder,
67    Portal,
68};
69pub use nav_mesh::{
70    BVNode, Link, MeshTile, NavMesh, OffMeshConnection, Poly, PolyDetail, TileHeader,
71    decode_poly_ref, encode_poly_ref,
72};
73pub use nav_mesh_builder::{ExternalLinkRequest, NavMeshBuilder};
74pub use nav_mesh_query::{MoveAlongSurfaceResult, NavMeshQuery, Node, NodeState};
75pub use node_pool::{DT_NULL_IDX, DtNode, DtNodePool, DtNodeQueue, NodeFlags, NodeIndex};
76pub use path_queue::{DT_PATHQ_INVALID, DtPathQueue, DtPathQueueRef};
77pub use poly_query::{CollectPolysQuery, FindNearestPolyQuery, PolyQuery};
78pub use raycast_hit::{RaycastHit, RaycastOptions, RaycastResult};
79pub use sliced_pathfinding::{
80    SlicedPathConfig, SlicedPathState, SlicedPathfindingQuery, find_path_sliced,
81};
82pub use status::Status;
83
84/// External link flag - indicates a polygon edge connects to an adjacent tile
85pub const DT_EXT_LINK: u16 = 0x8000;
86
87/// Navigation mesh parameters
88#[derive(Debug, Clone)]
89#[non_exhaustive]
90#[cfg_attr(
91    feature = "serialization",
92    derive(serde::Serialize, serde::Deserialize)
93)]
94pub struct NavMeshParams {
95    /// Origin of the navigation mesh
96    pub origin: [f32; 3],
97    /// Tile width and height
98    pub tile_width: f32,
99    pub tile_height: f32,
100    /// Maximum number of tiles in the mesh
101    pub max_tiles: i32,
102    /// Maximum number of polygons per tile
103    pub max_polys_per_tile: i32,
104}
105
106/// Parameters for building navigation mesh tiles with off-mesh connections
107#[derive(Debug, Clone)]
108#[non_exhaustive]
109#[cfg_attr(
110    feature = "serialization",
111    derive(serde::Serialize, serde::Deserialize)
112)]
113pub struct NavMeshCreateParams {
114    /// Navigation mesh parameters
115    pub nav_mesh_params: NavMeshParams,
116    /// Vertices of the navigation mesh polygon [x,y,z,...]
117    pub verts: Vec<f32>,
118    /// Vertex count
119    pub vert_count: i32,
120    /// Polygons [first_index, index_count, ...]
121    pub polys: Vec<u16>,
122    /// Polygon flags
123    pub poly_flags: Vec<PolyFlags>,
124    /// Polygon areas
125    pub poly_areas: Vec<u8>,
126    /// Polygon count
127    pub poly_count: i32,
128    /// Max vertices per polygon
129    pub nvp: i32,
130    /// Detailed mesh vertices [x,y,z,...]
131    pub detail_meshes: Vec<u32>,
132    /// Detailed mesh vertices [x,y,z,...]
133    pub detail_verts: Vec<f32>,
134    /// Detail vert count
135    pub detail_vert_count: i32,
136    /// Detailed mesh triangles [vert0, vert1, vert2, ...]
137    pub detail_tris: Vec<u8>,
138    /// Detail triangle count
139    pub detail_tri_count: i32,
140
141    // Off-mesh connection parameters
142    /// Off-mesh connection vertices [(ax,ay,az,bx,by,bz) * count]
143    pub off_mesh_con_verts: Vec<f32>,
144    /// Off-mesh connection radii
145    pub off_mesh_con_rad: Vec<f32>,
146    /// Off-mesh connection flags
147    pub off_mesh_con_flags: Vec<PolyFlags>,
148    /// Off-mesh connection area IDs
149    pub off_mesh_con_areas: Vec<u8>,
150    /// Off-mesh connection directions (0=bidir, 1=A->B, 2=B->A)
151    pub off_mesh_con_dir: Vec<u8>,
152    /// Off-mesh connection user IDs
153    pub off_mesh_con_user_id: Vec<u32>,
154    /// Number of off-mesh connections
155    pub off_mesh_con_count: i32,
156
157    /// Tile bounds
158    pub bmin: [f32; 3],
159    pub bmax: [f32; 3],
160    /// Walkable height
161    pub walkable_height: f32,
162    /// Walkable radius
163    pub walkable_radius: f32,
164    /// Walkable climb
165    pub walkable_climb: f32,
166    /// Cell size
167    pub cs: f32,
168    /// Cell height
169    pub ch: f32,
170    /// Build BVH tree
171    pub build_bv_tree: bool,
172}
173
174impl Default for NavMeshParams {
175    fn default() -> Self {
176        Self {
177            origin: [0.0, 0.0, 0.0],
178            tile_width: 0.0,
179            tile_height: 0.0,
180            max_tiles: 0,
181            max_polys_per_tile: 0,
182        }
183    }
184}
185
186impl Default for NavMeshCreateParams {
187    fn default() -> Self {
188        Self {
189            nav_mesh_params: NavMeshParams::default(),
190            verts: Vec::new(),
191            vert_count: 0,
192            polys: Vec::new(),
193            poly_flags: Vec::new(),
194            poly_areas: Vec::new(),
195            poly_count: 0,
196            nvp: 6,
197            detail_meshes: Vec::new(),
198            detail_verts: Vec::new(),
199            detail_vert_count: 0,
200            detail_tris: Vec::new(),
201            detail_tri_count: 0,
202            off_mesh_con_verts: Vec::new(),
203            off_mesh_con_rad: Vec::new(),
204            off_mesh_con_flags: Vec::new(),
205            off_mesh_con_areas: Vec::new(),
206            off_mesh_con_dir: Vec::new(),
207            off_mesh_con_user_id: Vec::new(),
208            off_mesh_con_count: 0,
209            bmin: [0.0, 0.0, 0.0],
210            bmax: [0.0, 0.0, 0.0],
211            walkable_height: 0.0,
212            walkable_radius: 0.0,
213            walkable_climb: 0.0,
214            cs: 0.3,
215            ch: 0.2,
216            build_bv_tree: true,
217        }
218    }
219}
220
221impl NavMeshCreateParams {
222    /// Sets the walkable height (world units).
223    pub fn with_walkable_height(mut self, height: f32) -> Self {
224        self.walkable_height = height;
225        self
226    }
227
228    /// Sets the walkable radius (world units).
229    pub fn with_walkable_radius(mut self, radius: f32) -> Self {
230        self.walkable_radius = radius;
231        self
232    }
233
234    /// Sets the walkable climb height (world units).
235    pub fn with_walkable_climb(mut self, climb: f32) -> Self {
236        self.walkable_climb = climb;
237        self
238    }
239
240    /// Sets the cell size (xz-plane resolution).
241    pub fn with_cell_size(mut self, cs: f32) -> Self {
242        self.cs = cs;
243        self
244    }
245
246    /// Sets the cell height (y-axis resolution).
247    pub fn with_cell_height(mut self, ch: f32) -> Self {
248        self.ch = ch;
249        self
250    }
251
252    /// Sets the AABB bounds.
253    pub fn with_bounds(mut self, bmin: [f32; 3], bmax: [f32; 3]) -> Self {
254        self.bmin = bmin;
255        self.bmax = bmax;
256        self
257    }
258
259    /// Sets whether to build the BV tree for spatial queries.
260    pub fn with_build_bv_tree(mut self, build: bool) -> Self {
261        self.build_bv_tree = build;
262        self
263    }
264
265    /// Sets the navigation mesh parameters.
266    pub fn with_nav_mesh_params(mut self, params: NavMeshParams) -> Self {
267        self.nav_mesh_params = params;
268        self
269    }
270
271    /// Validates and returns the params.
272    ///
273    /// Checks that vertex/polygon counts match the corresponding data arrays
274    /// and that cell dimensions are positive. Returns `Err(DetourError::InvalidParam)`
275    /// on mismatch.
276    pub fn build(self) -> Result<Self, DetourError> {
277        self.validate()?;
278        Ok(self)
279    }
280
281    /// Validates the create params without consuming them.
282    pub fn validate(&self) -> Result<(), DetourError> {
283        if self.cs <= 0.0 || self.ch <= 0.0 {
284            return Err(DetourError::InvalidParam);
285        }
286
287        if self.vert_count > 0 && self.verts.len() != self.vert_count as usize * 3 {
288            return Err(DetourError::InvalidParam);
289        }
290
291        if self.poly_count > 0 {
292            let expected_poly_data = self.poly_count as usize * 2 * self.nvp as usize;
293            if self.polys.len() < expected_poly_data {
294                return Err(DetourError::InvalidParam);
295            }
296        }
297
298        if self.detail_tri_count > 0 && self.detail_tris.len() < self.detail_tri_count as usize * 4
299        {
300            return Err(DetourError::InvalidParam);
301        }
302
303        if self.off_mesh_con_count > 0 {
304            let n = self.off_mesh_con_count as usize;
305            if self.off_mesh_con_verts.len() < n * 6 || self.off_mesh_con_rad.len() < n {
306                return Err(DetourError::InvalidParam);
307            }
308        }
309
310        Ok(())
311    }
312}
313
314bitflags::bitflags! {
315    /// Navigation mesh parameter flags
316    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
317    #[cfg_attr(feature = "serialization", derive(serde::Serialize, serde::Deserialize))]
318    pub struct NavMeshFlags: u16 {
319        /// Enables off-mesh connections
320        const OFF_MESH_CONNECTIONS = 0x01;
321    }
322}
323
324/// Polygon types in the navigation mesh
325#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
326#[cfg_attr(
327    feature = "serialization",
328    derive(serde::Serialize, serde::Deserialize)
329)]
330#[repr(u8)]
331pub enum PolyType {
332    /// Regular ground polygon
333    Ground = 0,
334    /// Off-mesh connection
335    OffMeshConnection = 1,
336}
337
338bitflags::bitflags! {
339    /// Flags describing polygon attributes
340    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
341    #[cfg_attr(feature = "serialization", derive(serde::Serialize, serde::Deserialize))]
342    pub struct PolyFlags: u16 {
343        /// Polygon is walkable
344        const WALK = 0x01;
345        /// Polygon is swimmable
346        const SWIM = 0x02;
347        /// Polygon is door
348        const DOOR = 0x04;
349        /// Polygon is jump
350        const JUMP = 0x08;
351        /// Polygon is disabled
352        const DISABLED = 0x10;
353        /// Polygon is climbable
354        const CLIMB = 0x20;
355    }
356}
357
358bitflags::bitflags! {
359    /// Flags describing query filter options
360    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
361    #[cfg_attr(feature = "serialization", derive(serde::Serialize, serde::Deserialize))]
362    pub struct QueryFilterFlags: u16 {
363        /// Include polygons with `PolyFlags::WALK`
364        const WALK = 0x01;
365        /// Include polygons with `PolyFlags::SWIM`
366        const SWIM = 0x02;
367        /// Include polygons with `PolyFlags::DOOR`
368        const DOOR = 0x04;
369        /// Include polygons with `PolyFlags::JUMP`
370        const JUMP = 0x08;
371        /// Include polygons with `PolyFlags::DISABLED`
372        const DISABLED = 0x10;
373        /// Include polygons with `PolyFlags::CLIMB`
374        const CLIMB = 0x20;
375    }
376}
377
378/// Navigation mesh query filter
379#[derive(Debug, Clone)]
380#[cfg_attr(
381    feature = "serialization",
382    derive(serde::Serialize, serde::Deserialize)
383)]
384pub struct QueryFilter {
385    /// Filter flags
386    pub flags: QueryFilterFlags,
387    /// Costs for different area types
388    pub area_cost: [f32; 32],
389    /// Include flags
390    pub include_flags: PolyFlags,
391    /// Exclude flags
392    pub exclude_flags: PolyFlags,
393}
394
395impl Default for QueryFilter {
396    fn default() -> Self {
397        let mut area_cost = [1.0; 32];
398
399        // Default costs for different areas
400        // These costs can be adjusted for different movement types
401        area_cost[0] = 1.0; // Walkable ground
402        area_cost[1] = 2.0; // Shallow water
403        area_cost[2] = 5.0; // Deep water
404
405        Self {
406            flags: QueryFilterFlags::WALK,
407            area_cost,
408            include_flags: PolyFlags::WALK,
409            exclude_flags: PolyFlags::DISABLED,
410        }
411    }
412}
413
414impl QueryFilter {
415    /// Checks if a polygon passes the filter
416    pub fn pass_filter(
417        &self,
418        _poly_ref: PolyRef,
419        _tile: &nav_mesh::MeshTile,
420        poly: &nav_mesh::Poly,
421    ) -> bool {
422        // If include_flags is empty, include all polygons (unless explicitly excluded)
423        let include = if self.include_flags.is_empty() {
424            true
425        } else {
426            (poly.flags & self.include_flags) != PolyFlags::empty()
427        };
428
429        // Check if polygon has any of the exclude flags
430        let exclude = (poly.flags & self.exclude_flags) != PolyFlags::empty();
431
432        // Pass if included and not excluded
433        include && !exclude
434    }
435
436    /// Gets the cost to move from one position to another
437    #[allow(clippy::too_many_arguments)]
438    pub fn get_cost(
439        &self,
440        pa: &[f32; 3],
441        pb: &[f32; 3],
442        _prev_ref: PolyRef,
443        _prev_tile: Option<&nav_mesh::MeshTile>,
444        _prev_poly: Option<&nav_mesh::Poly>,
445        _cur_ref: PolyRef,
446        _cur_tile: &nav_mesh::MeshTile,
447        cur_poly: &nav_mesh::Poly,
448        _next_ref: PolyRef,
449        _next_tile: Option<&nav_mesh::MeshTile>,
450        _next_poly: Option<&nav_mesh::Poly>,
451    ) -> f32 {
452        // Calculate distance
453        let dx = pb[0] - pa[0];
454        let dy = pb[1] - pa[1];
455        let dz = pb[2] - pa[2];
456        let dist = (dx * dx + dy * dy + dz * dz).sqrt();
457
458        // Get area cost
459        let area = cur_poly.area.min(31) as usize;
460
461        // Return distance * area cost
462        dist * self.area_cost[area]
463    }
464}
465
466/// Polygon reference (unique identifier for a polygon)
467#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
468#[cfg_attr(
469    feature = "serialization",
470    derive(serde::Serialize, serde::Deserialize)
471)]
472pub struct PolyRef(u32);
473
474impl PolyRef {
475    /// Creates a new polygon reference
476    pub fn new(id: u32) -> Self {
477        Self(id)
478    }
479
480    /// Returns the raw ID of the polygon reference
481    pub fn id(&self) -> u32 {
482        self.0
483    }
484
485    /// Checks if the polygon reference is valid
486    pub fn is_valid(&self) -> bool {
487        self.0 != 0
488    }
489}
490
491impl From<u32> for PolyRef {
492    fn from(id: u32) -> Self {
493        Self(id)
494    }
495}
496
497impl From<PolyRef> for u32 {
498    fn from(reference: PolyRef) -> Self {
499        reference.0
500    }
501}
502
503/// Maximum number of vertices per navigation polygon
504pub const MAX_VERTS_PER_POLY: usize = 6;
505
506/// A path result from a pathfinding query
507#[derive(Debug, Clone)]
508#[cfg_attr(
509    feature = "serialization",
510    derive(serde::Serialize, serde::Deserialize)
511)]
512pub struct Path {
513    /// Polygon references along the path
514    pub poly_refs: Vec<PolyRef>,
515    /// Waypoints along the path
516    pub waypoints: Vec<Vec3>,
517}
518
519impl Default for Path {
520    fn default() -> Self {
521        Self::new()
522    }
523}
524
525impl Path {
526    /// Creates a new empty path
527    pub fn new() -> Self {
528        Self {
529            poly_refs: Vec::new(),
530            waypoints: Vec::new(),
531        }
532    }
533
534    /// Returns the number of polygons in the path
535    pub fn poly_count(&self) -> usize {
536        self.poly_refs.len()
537    }
538
539    /// Returns the number of waypoints in the path
540    pub fn waypoint_count(&self) -> usize {
541        self.waypoints.len()
542    }
543
544    /// Checks if the path is empty
545    pub fn is_empty(&self) -> bool {
546        self.poly_refs.is_empty()
547    }
548
549    /// Clears the path
550    pub fn clear(&mut self) {
551        self.poly_refs.clear();
552        self.waypoints.clear();
553    }
554}