#[cfg(test)]
mod tests {
use crate::{
NavMesh, NavMeshCreateParams, NavMeshParams, NavMeshQuery, PolyFlags, QueryFilter,
};
use glam::Vec3;
fn create_grid_mesh(grid_size: i32) -> Result<NavMesh, Box<dyn std::error::Error>> {
let cell_size = 2.0;
let params = NavMeshParams {
origin: [0.0, 0.0, 0.0],
tile_width: grid_size as f32 * cell_size,
tile_height: grid_size as f32 * cell_size,
max_tiles: 1,
max_polys_per_tile: grid_size * grid_size,
};
let mut vertices = Vec::new();
for y in 0..=grid_size {
for x in 0..=grid_size {
vertices.extend_from_slice(&[x as f32 * cell_size, 0.0, y as f32 * cell_size]);
}
}
let mut polys = Vec::new();
let mut poly_areas = Vec::new();
let mut poly_flags = Vec::new();
for y in 0..grid_size {
for x in 0..grid_size {
let base = y * (grid_size + 1) + x;
let poly_idx = y * grid_size + x;
polys.extend_from_slice(&[
base as u16,
(base + 1) as u16,
(base + grid_size + 2) as u16,
(base + grid_size + 1) as u16,
0xffff, 0xffff, ]);
polys.push(if y > 0 {
((y - 1) * grid_size + x) as u16
} else {
0xffff
});
polys.push(if x < grid_size - 1 {
(y * grid_size + x + 1) as u16
} else {
0xffff
});
polys.push(if y < grid_size - 1 {
((y + 1) * grid_size + x) as u16
} else {
0xffff
});
polys.push(if x > 0 {
(y * grid_size + x - 1) as u16
} else {
0xffff
});
polys.push(0xffff);
polys.push(0xffff);
poly_areas.push(0);
poly_flags.push(PolyFlags::WALK);
}
}
let detail_meshes: Vec<u32> = (0..grid_size * grid_size)
.flat_map(|i| vec![i as u32 * 4, 4, i as u32 * 2, 2])
.collect();
let detail_verts = vertices.clone();
let detail_tris: Vec<u8> = (0..grid_size * grid_size)
.flat_map(|_| vec![0, 1, 2, 0, 0, 2, 3, 0])
.collect();
let create_params = NavMeshCreateParams {
nav_mesh_params: params.clone(),
verts: vertices,
vert_count: ((grid_size + 1) * (grid_size + 1)) as i32,
polys,
poly_flags,
poly_areas,
poly_count: grid_size * grid_size,
nvp: 6,
detail_meshes,
detail_verts,
detail_vert_count: ((grid_size + 1) * (grid_size + 1)) as i32,
detail_tris,
detail_tri_count: grid_size * grid_size * 2,
off_mesh_con_verts: Vec::new(),
off_mesh_con_rad: Vec::new(),
off_mesh_con_dir: Vec::new(),
off_mesh_con_areas: Vec::new(),
off_mesh_con_flags: Vec::new(),
off_mesh_con_user_id: Vec::new(),
off_mesh_con_count: 0,
bmin: [0.0, 0.0, 0.0],
bmax: [
grid_size as f32 * cell_size,
1.0,
grid_size as f32 * cell_size,
],
walkable_height: 2.0,
walkable_radius: 0.6,
walkable_climb: 0.9,
cs: 0.3,
ch: 0.2,
build_bv_tree: true,
};
let mut nav_mesh = NavMesh::new(params)?;
nav_mesh.add_tile_from_params(&create_params, 0, 0, 0)?;
Ok(nav_mesh)
}
#[test]
fn test_query_polygons_in_bounds() -> Result<(), Box<dyn std::error::Error>> {
let nav_mesh = create_grid_mesh(5)?;
let query = NavMeshQuery::new(&nav_mesh);
let filter = QueryFilter::default();
let bounds_min = [4.0, -1.0, 4.0];
let bounds_max = [6.0, 1.0, 6.0];
let polygons = nav_mesh.query_polygons(&bounds_min, &bounds_max, &filter)?;
assert!(!polygons.is_empty(), "Should find polygons in bounds");
for poly_ref in &polygons {
}
Ok(())
}
#[test]
fn test_query_empty_bounds() -> Result<(), Box<dyn std::error::Error>> {
let nav_mesh = create_grid_mesh(5)?;
let query = NavMeshQuery::new(&nav_mesh);
let filter = QueryFilter::default();
let point = [5.0, 0.0, 5.0];
let _polygons = nav_mesh.query_polygons(&point, &point, &filter);
Ok(())
}
#[test]
fn test_query_inverted_bounds() -> Result<(), Box<dyn std::error::Error>> {
let nav_mesh = create_grid_mesh(5)?;
let query = NavMeshQuery::new(&nav_mesh);
let filter = QueryFilter::default();
let bounds_min = [6.0, 1.0, 6.0];
let bounds_max = [4.0, -1.0, 4.0];
let _polygons = nav_mesh.query_polygons(&bounds_min, &bounds_max, &filter);
Ok(())
}
#[test]
fn test_query_entire_mesh() -> Result<(), Box<dyn std::error::Error>> {
let grid_size = 5;
let nav_mesh = create_grid_mesh(grid_size)?;
let _query = NavMeshQuery::new(&nav_mesh);
let filter = QueryFilter::default();
let bounds_min = [-100.0, -100.0, -100.0];
let bounds_max = [100.0, 100.0, 100.0];
let polygons = nav_mesh.query_polygons(&bounds_min, &bounds_max, &filter)?;
assert_eq!(
polygons.len(),
(grid_size * grid_size) as usize,
"Should find all polygons in mesh"
);
Ok(())
}
#[test]
fn test_find_polys_around_circle() -> Result<(), Box<dyn std::error::Error>> {
let nav_mesh = create_grid_mesh(5)?;
let mut query = NavMeshQuery::new(&nav_mesh);
let filter = QueryFilter::default();
let center = [5.0, 0.0, 5.0];
let extents = [0.5, 0.5, 0.5];
let (start_ref, _) =
query.find_nearest_poly(Vec3::from(center), Vec3::from(extents), &filter)?;
if start_ref.is_valid() {
let radius = 3.0;
let result =
query.find_polys_around_circle(start_ref, Vec3::from(center), radius, &filter);
match result {
Ok(polys) => {
assert!(!polys.is_empty(), "Should find polygons around circle");
}
Err(_) => {
}
}
}
Ok(())
}
#[test]
fn test_find_polys_circle_zero_radius() -> Result<(), Box<dyn std::error::Error>> {
let nav_mesh = create_grid_mesh(5)?;
let mut query = NavMeshQuery::new(&nav_mesh);
let filter = QueryFilter::default();
let center = [5.0, 0.0, 5.0];
let (start_ref, _) =
query.find_nearest_poly(Vec3::from(center), Vec3::new(0.5, 0.5, 0.5), &filter)?;
if start_ref.is_valid() {
let result =
query.find_polys_around_circle(start_ref, Vec3::from(center), 0.0, &filter);
match result {
Ok(polys) => {
assert_eq!(polys.len(), 1, "Zero radius should only find start polygon");
assert_eq!(polys[0], start_ref, "Should be start polygon");
}
Err(_) => {
}
}
}
Ok(())
}
#[test]
fn test_find_polys_circle_large_radius() -> Result<(), Box<dyn std::error::Error>> {
let grid_size = 5;
let nav_mesh = create_grid_mesh(grid_size)?;
let mut query = NavMeshQuery::new(&nav_mesh);
let filter = QueryFilter::default();
let center = [5.0, 0.0, 5.0];
let (start_ref, _) =
query.find_nearest_poly(Vec3::from(center), Vec3::new(0.5, 0.5, 0.5), &filter)?;
if start_ref.is_valid() {
let radius = 100.0;
let result =
query.find_polys_around_circle(start_ref, Vec3::from(center), radius, &filter);
match result {
Ok(polys) => {
println!("Found {} polygons with radius {}", polys.len(), radius);
if polys.len() <= 3 {
println!(
"Only found {} polygons, checking if neighbors are reachable...",
polys.len()
);
if let Some(tile) = nav_mesh.get_tile_by_index(0) {
println!(
"Tile has {} polygons and {} links",
tile.polys.len(),
tile.links.len()
);
if !polys.is_empty() {
let found_poly_ref = polys[0];
if let Ok((tile, poly)) =
nav_mesh.get_tile_and_poly_by_ref(found_poly_ref)
{
println!(
"Found polygon has {} links",
if poly.first_link.is_some() {
"some"
} else {
"no"
}
);
}
}
}
}
assert!(
polys.len() > 1,
"Large radius should find multiple polygons, but found {}",
polys.len()
);
}
Err(e) => {
println!("find_polys_around_circle failed: {:?}", e);
}
}
}
Ok(())
}
#[test]
#[ignore] fn test_find_polys_in_path() -> Result<(), Box<dyn std::error::Error>> {
let nav_mesh = create_grid_mesh(5)?;
let mut query = NavMeshQuery::new(&nav_mesh);
let filter = QueryFilter::default();
let start = [1.0, 0.0, 1.0];
let end = [9.0, 0.0, 9.0];
let extents = [1.0, 1.0, 1.0];
let (start_ref, start_pos) =
query.find_nearest_poly(Vec3::from(start), Vec3::from(extents), &filter)?;
let (end_ref, end_pos) =
query.find_nearest_poly(Vec3::from(end), Vec3::from(extents), &filter)?;
if start_ref.is_valid() && end_ref.is_valid() {
let path = query.find_path(start_ref, end_ref, start_pos, end_pos, &filter)?;
if !path.is_empty() {
}
}
Ok(())
}
#[test]
fn test_spatial_queries_with_filters() -> Result<(), Box<dyn std::error::Error>> {
let nav_mesh = create_grid_mesh(5)?;
let query = NavMeshQuery::new(&nav_mesh);
let mut exclusive_filter = QueryFilter::default();
exclusive_filter.exclude_flags = PolyFlags::DISABLED;
let bounds_min = [0.0, -1.0, 0.0];
let bounds_max = [10.0, 1.0, 10.0];
let filtered_polys =
nav_mesh.query_polygons(&bounds_min, &bounds_max, &exclusive_filter)?;
let all_polys =
nav_mesh.query_polygons(&bounds_min, &bounds_max, &QueryFilter::default())?;
assert!(
filtered_polys.len() <= all_polys.len(),
"Filtered query should not return more polygons"
);
Ok(())
}
#[test]
#[ignore] fn test_query_performance_large_bounds() -> Result<(), Box<dyn std::error::Error>> {
let nav_mesh = create_grid_mesh(50)?; let query = NavMeshQuery::new(&nav_mesh);
let filter = QueryFilter::default();
let bounds_min = [0.0, -10.0, 0.0];
let bounds_max = [100.0, 10.0, 100.0];
let start_time = std::time::Instant::now();
for _ in 0..1000 {
let _ = nav_mesh.query_polygons(&bounds_min, &bounds_max, &filter)?;
}
let elapsed = start_time.elapsed();
println!("1000 spatial queries took: {:?}", elapsed);
assert!(elapsed.as_millis() < 1000, "Spatial queries should be fast");
Ok(())
}
#[test]
fn test_query_with_bvh_optimization() -> Result<(), Box<dyn std::error::Error>> {
let nav_mesh = create_grid_mesh(10)?;
let _query = NavMeshQuery::new(&nav_mesh);
let filter = QueryFilter::default();
let bounds_min = [5.0, -1.0, 5.0];
let bounds_max = [7.0, 1.0, 7.0];
let polygons = nav_mesh.query_polygons(&bounds_min, &bounds_max, &filter)?;
assert!(!polygons.is_empty(), "Should find polygons");
assert!(
polygons.len() <= 4,
"Should not return too many polygons for small bounds (expected <=4, got {})",
polygons.len()
);
Ok(())
}
#[test]
fn test_overlapping_spatial_queries() -> Result<(), Box<dyn std::error::Error>> {
let nav_mesh = create_grid_mesh(5)?;
let query = NavMeshQuery::new(&nav_mesh);
let filter = QueryFilter::default();
let bounds1_min = [2.0, -1.0, 2.0];
let bounds1_max = [6.0, 1.0, 6.0];
let bounds2_min = [4.0, -1.0, 4.0];
let bounds2_max = [8.0, 1.0, 8.0];
let polys1 = nav_mesh.query_polygons(&bounds1_min, &bounds1_max, &filter)?;
let polys2 = nav_mesh.query_polygons(&bounds2_min, &bounds2_max, &filter)?;
let overlap: Vec<_> = polys1.iter().filter(|p| polys2.contains(p)).collect();
assert!(
!overlap.is_empty(),
"Overlapping queries should share some polygons"
);
Ok(())
}
#[test]
fn test_query_at_mesh_boundaries() -> Result<(), Box<dyn std::error::Error>> {
let nav_mesh = create_grid_mesh(5)?;
let query = NavMeshQuery::new(&nav_mesh);
let filter = QueryFilter::default();
let bounds_min = [-1.0, -1.0, -1.0];
let bounds_max = [1.0, 1.0, 1.0];
let polygons = nav_mesh.query_polygons(&bounds_min, &bounds_max, &filter)?;
assert!(
!polygons.is_empty(),
"Should find polygons at mesh boundary"
);
Ok(())
}
}