use std::collections::VecDeque;
use glam::Vec3;
use super::nav_mesh_query::NavMeshQuery;
use super::{PolyRef, QueryFilter};
use crate::error::DetourError;
const DEFAULT_MAX_SLICE_SIZE: usize = 256;
const MAX_PATHFIND_ITERATIONS: usize = 2048;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SlicedPathState {
InProgress,
Success,
Failed,
PartialPath,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct SlicedPathConfig {
pub max_slice_size: usize,
pub max_iterations: usize,
}
impl Default for SlicedPathConfig {
fn default() -> Self {
Self {
max_slice_size: DEFAULT_MAX_SLICE_SIZE,
max_iterations: MAX_PATHFIND_ITERATIONS,
}
}
}
#[derive(Debug)]
pub struct SlicedPathfindingQuery<'a> {
query: &'a mut NavMeshQuery<'a>,
config: SlicedPathConfig,
state: SlicedPathState,
start_ref: PolyRef,
end_ref: PolyRef,
start_pos: [f32; 3],
end_pos: [f32; 3],
filter: QueryFilter,
current_path: Vec<PolyRef>,
goal_queue: VecDeque<(PolyRef, [f32; 3])>,
iterations: usize,
current_segment: usize,
}
impl<'a> SlicedPathfindingQuery<'a> {
pub fn new(
query: &'a mut NavMeshQuery<'a>,
start_ref: PolyRef,
end_ref: PolyRef,
start_pos: [f32; 3],
end_pos: [f32; 3],
filter: QueryFilter,
) -> Result<Self, DetourError> {
if !query.nav_mesh().is_valid_poly_ref(start_ref) {
return Err(DetourError::InvalidParam);
}
if !query.nav_mesh().is_valid_poly_ref(end_ref) {
return Err(DetourError::InvalidParam);
}
let mut sliced_query = Self {
query,
config: SlicedPathConfig::default(),
state: SlicedPathState::InProgress,
start_ref,
end_ref,
start_pos,
end_pos,
filter,
current_path: Vec::new(),
goal_queue: VecDeque::new(),
iterations: 0,
current_segment: 0,
};
sliced_query.initialize_pathfinding()?;
Ok(sliced_query)
}
pub fn new_with_config(
query: &'a mut NavMeshQuery<'a>,
start_ref: PolyRef,
end_ref: PolyRef,
start_pos: [f32; 3],
end_pos: [f32; 3],
filter: QueryFilter,
config: SlicedPathConfig,
) -> Result<Self, DetourError> {
let mut sliced_query = Self::new(query, start_ref, end_ref, start_pos, end_pos, filter)?;
sliced_query.config = config;
Ok(sliced_query)
}
fn initialize_pathfinding(&mut self) -> Result<(), DetourError> {
if self.start_ref == self.end_ref {
self.current_path.push(self.start_ref);
self.state = SlicedPathState::Success;
return Ok(());
}
let straight_line_distance = self.calculate_distance(&self.start_pos, &self.end_pos);
let max_segment_distance = 100.0; if straight_line_distance > max_segment_distance {
self.create_intermediate_waypoints(max_segment_distance)?;
} else {
self.goal_queue.push_back((self.end_ref, self.end_pos));
}
Ok(())
}
fn create_intermediate_waypoints(
&mut self,
max_segment_distance: f32,
) -> Result<(), DetourError> {
let total_distance = self.calculate_distance(&self.start_pos, &self.end_pos);
let num_segments = (total_distance / max_segment_distance).ceil() as usize;
for i in 1..=num_segments {
let t = i as f32 / num_segments as f32;
let intermediate_pos = [
self.start_pos[0] + (self.end_pos[0] - self.start_pos[0]) * t,
self.start_pos[1] + (self.end_pos[1] - self.start_pos[1]) * t,
self.start_pos[2] + (self.end_pos[2] - self.start_pos[2]) * t,
];
let half_extents = self.query.get_query_extent();
if let Ok((poly_ref, _)) = self.query.find_nearest_poly(
Vec3::from(intermediate_pos),
half_extents,
&self.filter,
) {
if i == num_segments {
self.goal_queue.push_back((self.end_ref, self.end_pos));
} else {
self.goal_queue.push_back((poly_ref, intermediate_pos));
}
} else {
self.goal_queue.clear();
self.goal_queue.push_back((self.end_ref, self.end_pos));
break;
}
}
Ok(())
}
pub fn execute_slice(&mut self) -> Result<SlicedPathState, DetourError> {
if self.state != SlicedPathState::InProgress {
return Ok(self.state);
}
if self.iterations >= self.config.max_iterations {
self.state = SlicedPathState::Failed;
return Ok(self.state);
}
if self.goal_queue.is_empty() {
self.state = if self.current_path.is_empty() {
SlicedPathState::Failed
} else {
SlicedPathState::Success
};
return Ok(self.state);
}
let Some(&(goal_ref, goal_pos)) = self.goal_queue.front() else {
self.state = SlicedPathState::Failed;
return Ok(self.state);
};
let segment_start_ref = if let Some(&last) = self.current_path.last() {
last
} else {
self.start_ref
};
let segment_start_pos = if self.current_path.is_empty() {
self.start_pos
} else {
self.query.get_poly_center(segment_start_ref)?.to_array()
};
match self.find_path_segment(segment_start_ref, goal_ref, &segment_start_pos, &goal_pos) {
Ok(segment_path) => {
self.merge_segment_path(segment_path);
self.goal_queue.pop_front();
self.current_segment += 1;
if self.goal_queue.is_empty() {
self.state = SlicedPathState::Success;
} else {
self.state = SlicedPathState::InProgress;
}
}
Err(_) => {
if self.current_path.is_empty() {
self.state = SlicedPathState::Failed;
} else {
self.state = SlicedPathState::PartialPath;
}
}
}
Ok(self.state)
}
fn find_path_segment(
&mut self,
start_ref: PolyRef,
end_ref: PolyRef,
start_pos: &[f32; 3],
end_pos: &[f32; 3],
) -> Result<Vec<PolyRef>, DetourError> {
let _max_iterations_backup = self.config.max_iterations;
let segment_path = self.query.find_path(
start_ref,
end_ref,
Vec3::from(*start_pos),
Vec3::from(*end_pos),
&self.filter,
)?;
self.iterations += segment_path.len();
Ok(segment_path)
}
fn merge_segment_path(&mut self, mut segment_path: Vec<PolyRef>) {
if self.current_path.is_empty() {
self.current_path = segment_path;
} else {
if let Some(&last) = self.current_path.last() {
if !segment_path.is_empty() && segment_path[0] == last {
segment_path.remove(0);
}
}
self.current_path.extend(segment_path);
}
}
pub fn execute_to_completion(&mut self) -> Result<SlicedPathState, DetourError> {
while self.state == SlicedPathState::InProgress {
self.execute_slice()?;
if self.iterations >= self.config.max_iterations {
self.state = SlicedPathState::Failed;
break;
}
}
Ok(self.state)
}
pub fn get_state(&self) -> SlicedPathState {
self.state
}
pub fn get_path(&self) -> &[PolyRef] {
&self.current_path
}
pub fn get_path_mut(&mut self) -> &mut Vec<PolyRef> {
&mut self.current_path
}
pub fn get_iterations(&self) -> usize {
self.iterations
}
pub fn get_current_segment(&self) -> usize {
self.current_segment
}
pub fn get_total_segments(&self) -> usize {
self.current_segment + self.goal_queue.len()
}
fn calculate_distance(&self, a: &[f32; 3], b: &[f32; 3]) -> f32 {
let dx = b[0] - a[0];
let dy = b[1] - a[1];
let dz = b[2] - a[2];
(dx * dx + dy * dy + dz * dz).sqrt()
}
pub fn reset(&mut self) -> Result<(), DetourError> {
self.state = SlicedPathState::InProgress;
self.current_path.clear();
self.goal_queue.clear();
self.iterations = 0;
self.current_segment = 0;
self.initialize_pathfinding()
}
pub fn update_end_goal(
&mut self,
new_end_ref: PolyRef,
new_end_pos: [f32; 3],
) -> Result<(), DetourError> {
if !self.query.nav_mesh().is_valid_poly_ref(new_end_ref) {
return Err(DetourError::InvalidParam);
}
self.end_ref = new_end_ref;
self.end_pos = new_end_pos;
if let Some(last_goal) = self.goal_queue.back_mut() {
*last_goal = (new_end_ref, new_end_pos);
} else if !self.goal_queue.is_empty() {
self.goal_queue.clear();
self.goal_queue.push_back((new_end_ref, new_end_pos));
}
Ok(())
}
}
pub fn find_path_sliced<'a>(
query: &'a mut NavMeshQuery<'a>,
start_ref: PolyRef,
end_ref: PolyRef,
start_pos: [f32; 3],
end_pos: [f32; 3],
filter: &QueryFilter,
config: Option<SlicedPathConfig>,
) -> Result<Vec<PolyRef>, DetourError> {
let config = config.unwrap_or_default();
let mut sliced_query = SlicedPathfindingQuery::new_with_config(
query,
start_ref,
end_ref,
start_pos,
end_pos,
filter.clone(),
config,
)?;
let final_state = sliced_query.execute_to_completion()?;
match final_state {
SlicedPathState::Success | SlicedPathState::PartialPath => {
Ok(sliced_query.get_path().to_vec())
}
SlicedPathState::Failed => Err(DetourError::PathNotFound),
SlicedPathState::InProgress => {
Err(DetourError::Failure)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::nav_mesh::NavMesh;
use crate::{NavMeshParams, QueryFilter};
#[test]
fn test_sliced_path_config() {
let config = SlicedPathConfig::default();
assert_eq!(config.max_slice_size, DEFAULT_MAX_SLICE_SIZE);
assert_eq!(config.max_iterations, MAX_PATHFIND_ITERATIONS);
let custom_config = SlicedPathConfig {
max_slice_size: 128,
max_iterations: 1000,
};
assert_eq!(custom_config.max_slice_size, 128);
assert_eq!(custom_config.max_iterations, 1000);
}
#[test]
fn test_sliced_pathfinding_creation() {
let params = NavMeshParams {
origin: [0.0, 0.0, 0.0],
tile_width: 100.0,
tile_height: 100.0,
max_tiles: 128,
max_polys_per_tile: 1024,
};
let mut nav_mesh = NavMesh::new(params).unwrap();
nav_mesh.create_test_tile(0, 0, 0).unwrap();
let mut query = NavMeshQuery::new(&nav_mesh);
let filter = QueryFilter::default();
let start_ref = PolyRef::new(1);
let end_ref = PolyRef::new(2);
let result = SlicedPathfindingQuery::new(
&mut query,
start_ref,
end_ref,
[0.0, 0.0, 0.0],
[10.0, 0.0, 10.0],
filter,
);
assert!(result.is_err());
}
#[test]
fn test_distance_calculation() {
let params = NavMeshParams {
origin: [0.0, 0.0, 0.0],
tile_width: 100.0,
tile_height: 100.0,
max_tiles: 128,
max_polys_per_tile: 1024,
};
let nav_mesh = NavMesh::new(params).unwrap();
let _query = NavMeshQuery::new(&nav_mesh);
let _filter = QueryFilter::default();
let start_pos = [0.0, 0.0, 0.0];
let end_pos = [3.0, 4.0, 0.0];
let dx = end_pos[0] - start_pos[0];
let dy = end_pos[1] - start_pos[1];
let dz = end_pos[2] - start_pos[2];
let distance = ((dx * dx + dy * dy + dz * dz) as f32).sqrt();
assert!((distance - 5.0_f32).abs() < 1e-6);
}
#[test]
fn test_sliced_pathfinding_state_transitions() {
let state = SlicedPathState::InProgress;
assert_eq!(state, SlicedPathState::InProgress);
assert_ne!(state, SlicedPathState::Success);
let states = [
SlicedPathState::InProgress,
SlicedPathState::Success,
SlicedPathState::Failed,
SlicedPathState::PartialPath,
];
for (i, &state1) in states.iter().enumerate() {
for (j, &state2) in states.iter().enumerate() {
if i == j {
assert_eq!(state1, state2);
} else {
assert_ne!(state1, state2);
}
}
}
}
#[test]
fn test_helper_function_with_invalid_params() {
let params = NavMeshParams {
origin: [0.0, 0.0, 0.0],
tile_width: 100.0,
tile_height: 100.0,
max_tiles: 128,
max_polys_per_tile: 1024,
};
let nav_mesh = NavMesh::new(params).unwrap();
let mut query = NavMeshQuery::new(&nav_mesh);
let filter = QueryFilter::default();
let result = find_path_sliced(
&mut query,
PolyRef::new(0), PolyRef::new(1),
[0.0, 0.0, 0.0],
[10.0, 0.0, 10.0],
&filter,
None,
);
assert!(result.is_err());
}
}