#![allow(unused)]
#[cfg(test)]
mod tests {
use crate::test_mesh_helpers::create_minimal_test_navmesh;
use crate::{
MAX_VERTS_PER_POLY, NavMesh, NavMeshParams, NavMeshQuery, PolyFlags, PolyRef, QueryFilter,
nav_mesh::encode_poly_ref_with_salt,
};
use glam::Vec3;
#[test]
fn test_empty_navmesh_operations() -> Result<(), Box<dyn std::error::Error>> {
let params = NavMeshParams {
origin: [0.0, 0.0, 0.0],
tile_width: 10.0,
tile_height: 10.0,
max_tiles: 1,
max_polys_per_tile: 100,
};
let nav_mesh = NavMesh::new(params)?;
let mut query = NavMeshQuery::new(&nav_mesh);
let filter = QueryFilter::default();
let pos = [5.0, 0.0, 5.0];
let extents = [1.0, 1.0, 1.0];
let result = query.find_nearest_poly(Vec3::from(pos), Vec3::from(extents), &filter);
match result {
Ok((poly_ref, _)) => assert!(
!poly_ref.is_valid(),
"Empty mesh should return invalid poly ref"
),
Err(_) => {} }
let invalid_ref = PolyRef::from(0);
let path_result = query.find_path(
invalid_ref,
invalid_ref,
Vec3::from(pos),
Vec3::from(pos),
&filter,
);
assert!(
path_result.is_err(),
"Pathfinding on empty mesh should fail"
);
Ok(())
}
#[test]
fn test_single_polygon_mesh() -> Result<(), Box<dyn std::error::Error>> {
let nav_mesh = create_minimal_test_navmesh()?;
let mut query = NavMeshQuery::new(&nav_mesh);
let filter = QueryFilter::default();
let center = [0.3, 0.0, 0.3];
let extents = [0.5, 0.5, 0.5];
let (poly_ref, _) =
query.find_nearest_poly(Vec3::from(center), Vec3::from(extents), &filter)?;
assert!(poly_ref.is_valid(), "Should find the single polygon");
let path = query.find_path(
poly_ref,
poly_ref,
Vec3::from(center),
Vec3::from(center),
&filter,
)?;
assert_eq!(path.len(), 1, "Path within same polygon should have 1 node");
assert_eq!(path[0], poly_ref, "Path should contain the polygon");
let start = Vec3::new(0.2, 0.0, 0.2);
let end = Vec3::new(0.4, 0.0, 0.4);
let (hit_ref, _, t) = query.raycast(poly_ref, start, end, 1.0, &filter)?;
assert_eq!(hit_ref, poly_ref, "Should stay in same polygon");
assert!(
(t - 1.0).abs() < 0.01 || t <= 1.0,
"Should reach full distance or hit boundary (t={:.6})",
t
);
Ok(())
}
#[test]
fn test_polygon_boundary_queries() -> Result<(), Box<dyn std::error::Error>> {
let nav_mesh = create_minimal_test_navmesh()?;
let mut query = NavMeshQuery::new(&nav_mesh);
let filter = QueryFilter::default();
let edge_point = [0.3, 0.0, 0.0]; let tiny_extent = [0.01, 0.01, 0.01];
let result =
query.find_nearest_poly(Vec3::from(edge_point), Vec3::from(tiny_extent), &filter);
assert!(result.is_ok(), "Should handle edge point query");
let vertex_point = [0.0, 0.0, 0.0]; let result =
query.find_nearest_poly(Vec3::from(vertex_point), Vec3::from(tiny_extent), &filter);
assert!(result.is_ok(), "Should handle vertex point query");
Ok(())
}
#[test]
fn test_maximum_polygon_vertices() -> Result<(), Box<dyn std::error::Error>> {
assert_eq!(
MAX_VERTS_PER_POLY, 6,
"Maximum vertices per polygon should be 6"
);
Ok(())
}
#[test]
fn test_degenerate_polygons() -> Result<(), Box<dyn std::error::Error>> {
let nav_mesh = create_minimal_test_navmesh()?;
let mut query = NavMeshQuery::new(&nav_mesh);
let pos = [0.3, 0.0, 0.3];
let zero_extent = [0.0, 0.0, 0.0];
let filter = QueryFilter::default();
let result = query.find_nearest_poly(Vec3::from(pos), Vec3::from(zero_extent), &filter);
assert!(result.is_ok(), "Zero extent search should work");
Ok(())
}
#[test]
fn test_extremely_long_paths() -> Result<(), Box<dyn std::error::Error>> {
let nav_mesh = create_minimal_test_navmesh()?;
let mut query = NavMeshQuery::new(&nav_mesh);
let filter = QueryFilter::default();
let start = [0.1, 0.0, 0.1];
let end = [0.5, 0.0, 0.5];
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 result = query.find_path(start_ref, end_ref, start_pos, end_pos, &filter);
assert!(
result.is_ok(),
"Path finding should handle any valid points"
);
}
Ok(())
}
#[test]
fn test_numerical_precision_edge_cases() -> Result<(), Box<dyn std::error::Error>> {
let nav_mesh = create_minimal_test_navmesh()?;
let mut query = NavMeshQuery::new(&nav_mesh);
let filter = QueryFilter::default();
let base_pos = [0.3, 0.0, 0.3];
let epsilon = f32::EPSILON;
let nearby_pos = [base_pos[0] + epsilon, base_pos[1], base_pos[2] + epsilon];
let extents = [0.1, 0.1, 0.1];
let (ref1, _) =
query.find_nearest_poly(Vec3::from(base_pos), Vec3::from(extents), &filter)?;
let (ref2, _) =
query.find_nearest_poly(Vec3::from(nearby_pos), Vec3::from(extents), &filter)?;
assert_eq!(ref1, ref2, "Epsilon differences should find same polygon");
let large_pos = [1000.0, 0.0, 1000.0];
let large_extents = [1000.0, 1000.0, 1000.0];
let result =
query.find_nearest_poly(Vec3::from(large_pos), Vec3::from(large_extents), &filter);
assert!(result.is_ok(), "Should handle large coordinate values");
Ok(())
}
#[test]
fn test_raycast_edge_cases() -> Result<(), Box<dyn std::error::Error>> {
let nav_mesh = create_minimal_test_navmesh()?;
let mut query = NavMeshQuery::new(&nav_mesh);
let filter = QueryFilter::default();
let center = [0.3, 0.0, 0.3];
let extents = [0.1, 0.1, 0.1];
let (poly_ref, pos) =
query.find_nearest_poly(Vec3::from(center), Vec3::from(extents), &filter)?;
let (hit_ref, hit_pos, t) = query.raycast(poly_ref, pos, pos, 0.0, &filter)?;
assert_eq!(
hit_ref, poly_ref,
"Zero-length ray should stay in same polygon"
);
assert_eq!(t, 0.0, "Zero-length ray should have t=0");
let start = Vec3::new(0.1, 0.0, 0.0);
let end = Vec3::new(0.5, 0.0, 0.0); let result = query.raycast(poly_ref, start, end, 1.0, &filter);
assert!(result.is_ok(), "Parallel ray should be handled");
let backward_end = Vec3::new(-0.1, 0.0, -0.1);
let result = query.raycast(poly_ref, Vec3::from(center), backward_end, 1.0, &filter);
assert!(result.is_ok(), "Backward ray should be handled");
Ok(())
}
#[test]
fn test_invalid_polygon_references() -> Result<(), Box<dyn std::error::Error>> {
let nav_mesh = create_minimal_test_navmesh()?;
let mut query = NavMeshQuery::new(&nav_mesh);
let filter = QueryFilter::default();
let null_ref = PolyRef::from(0);
let pos = [0.0, 0.0, 0.0];
let result = query.find_path(
null_ref,
null_ref,
Vec3::from(pos),
Vec3::from(pos),
&filter,
);
assert!(result.is_err(), "Null references should fail");
let invalid_ref = encode_poly_ref_with_salt(1, 0, 999);
let result = query.find_path(
invalid_ref,
invalid_ref,
Vec3::from(pos),
Vec3::from(pos),
&filter,
);
assert!(result.is_err(), "Invalid polygon ID should fail");
let result = query.closest_point_on_poly(null_ref, Vec3::from(pos));
assert!(
result.is_err(),
"Invalid ref should fail for closest_point_on_poly"
);
Ok(())
}
#[test]
fn test_query_filter_edge_cases() -> Result<(), Box<dyn std::error::Error>> {
let nav_mesh = create_minimal_test_navmesh()?;
let mut query = NavMeshQuery::new(&nav_mesh);
let mut exclusive_filter = QueryFilter::default();
exclusive_filter.exclude_flags = PolyFlags::all();
let pos = [0.3, 0.0, 0.3];
let extents = [0.5, 0.5, 0.5];
let result =
query.find_nearest_poly(Vec3::from(pos), Vec3::from(extents), &exclusive_filter);
match result {
Ok((poly_ref, _)) => {
assert!(!poly_ref.is_valid(), "Exclusive filter should find nothing")
}
Err(_) => {} }
let mut expensive_filter = QueryFilter::default();
expensive_filter.area_cost[0] = f32::MAX;
let result =
query.find_nearest_poly(Vec3::from(pos), Vec3::from(extents), &expensive_filter);
assert!(result.is_ok(), "High cost filter should still work");
Ok(())
}
#[test]
fn test_concurrent_queries() -> Result<(), Box<dyn std::error::Error>> {
use std::sync::Arc;
use std::thread;
let nav_mesh = Arc::new(create_minimal_test_navmesh()?);
let mut handles = vec![];
for i in 0..10 {
let mesh_clone = Arc::clone(&nav_mesh);
let handle = thread::spawn(move || {
let mut query = NavMeshQuery::new(&mesh_clone);
let filter = QueryFilter::default();
let pos = [0.3 + (i as f32 * 0.01), 0.0, 0.3];
let extents = [0.1, 0.1, 0.1];
for _ in 0..100 {
let result =
query.find_nearest_poly(Vec3::from(pos), Vec3::from(extents), &filter);
assert!(result.is_ok(), "Concurrent query should succeed");
}
});
handles.push(handle);
}
for handle in handles {
handle.join().expect("Thread should complete");
}
Ok(())
}
#[test]
#[ignore] fn test_pathfinding_with_dynamic_obstacles() -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}
#[test]
#[ignore] fn test_mesh_modification_edge_cases() -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}
}