Skip to main content

waymark/
nav_mesh_query.rs

1//! Navigation mesh query implementation for Detour
2//!
3//! This module contains the NavMeshQuery structure, which is used to perform
4//! pathfinding and other queries on the navigation mesh.
5
6use std::cmp::Ordering;
7use std::collections::BinaryHeap;
8use std::f32;
9
10use super::nav_mesh::{MeshTile, Poly, decode_poly_id};
11use super::raycast_hit::{RaycastHit, RaycastOptions, RaycastResult};
12use super::sliced_pathfinding::SlicedPathState;
13use super::{NavMesh, Path, PolyFlags, PolyRef, PolyType, QueryFilter};
14use crate::error::DetourError;
15use glam::Vec3;
16
17/// Maximum number of nodes in the search pool
18const DT_MAX_NODES: usize = 4096;
19
20/// Node in the A* search algorithm
21#[derive(Debug, Clone)]
22pub struct Node {
23    /// Polygon reference
24    pub poly: PolyRef,
25    /// Parent node
26    pub parent: Option<usize>,
27    /// Cost from start to this node
28    pub g: f32,
29    /// Estimated cost from this node to goal
30    pub h: f32,
31    /// Total cost (g + h)
32    pub f: f32,
33    /// State of the node in the search
34    pub state: NodeState,
35    /// Index in the node pool
36    #[allow(dead_code)]
37    pub index: usize,
38}
39
40impl Node {
41    /// Creates a new node
42    fn new(poly: PolyRef, index: usize) -> Self {
43        Self {
44            poly,
45            parent: None,
46            g: 0.0,
47            h: 0.0,
48            f: 0.0,
49            state: NodeState::New,
50            index,
51        }
52    }
53}
54
55/// State of a node in the search
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub enum NodeState {
58    /// Node hasn't been processed yet
59    New,
60    /// Node is in the open list
61    Open,
62    /// Node is in the closed list
63    Closed,
64}
65
66/// Result of a `move_along_surface` query
67#[derive(Debug, Clone)]
68pub struct MoveAlongSurfaceResult {
69    /// The resulting position after moving along the surface
70    pub position: Vec3,
71    /// Polygon references visited during the move
72    pub visited: Vec<PolyRef>,
73}
74
75/// Node wrapper for the binary heap (priority queue)
76#[derive(Debug, Clone, Copy)]
77struct HeapNode {
78    /// Reference to the node in the node pool
79    index: usize,
80    /// Total cost (f value)
81    f: f32,
82}
83
84impl PartialEq for HeapNode {
85    fn eq(&self, other: &Self) -> bool {
86        self.f == other.f
87    }
88}
89
90impl Eq for HeapNode {}
91
92impl PartialOrd for HeapNode {
93    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
94        Some(self.cmp(other))
95    }
96}
97
98impl Ord for HeapNode {
99    fn cmp(&self, other: &Self) -> Ordering {
100        // Implement reverse ordering for min-heap (lowest f value first)
101        // Use total ordering for f32 by handling NaN specially
102        match other.f.partial_cmp(&self.f) {
103            Some(ordering) => ordering,
104            None => {
105                // If either is NaN, order NaN values as greater
106                if other.f.is_nan() && !self.f.is_nan() {
107                    Ordering::Less
108                } else if !other.f.is_nan() && self.f.is_nan() {
109                    Ordering::Greater
110                } else {
111                    Ordering::Equal
112                }
113            }
114        }
115    }
116}
117
118/// Navigation mesh query structure
119#[derive(Debug)]
120pub struct NavMeshQuery<'a> {
121    /// Reference to the navigation mesh
122    nav_mesh: &'a NavMesh,
123    /// Node pool for A* search
124    node_pool: Vec<Node>,
125    /// Open list for A* search
126    open_list: BinaryHeap<HeapNode>,
127    /// Query extent bounds
128    query_extent: [f32; 3],
129    /// Random seed for random sample queries
130    random_seed: u32,
131    /// Sliced pathfinding state
132    sliced_state: SlicedPathfindingState,
133}
134
135/// State for sliced pathfinding within NavMeshQuery
136#[derive(Debug)]
137struct SlicedPathfindingState {
138    /// Is sliced pathfinding active?
139    active: bool,
140    /// Current state of the pathfinding
141    state: SlicedPathState,
142    /// Start polygon reference
143    start_ref: PolyRef,
144    /// End polygon reference
145    end_ref: PolyRef,
146    /// Start position
147    start_pos: [f32; 3],
148    /// End position
149    end_pos: [f32; 3],
150    /// Query filter
151    filter: QueryFilter,
152    /// Current path being built
153    current_path: Vec<PolyRef>,
154    /// Best node found so far (for partial paths)
155    best_node_idx: usize,
156    /// Best node cost (for partial paths)
157    best_node_cost: f32,
158}
159
160impl Default for SlicedPathfindingState {
161    fn default() -> Self {
162        Self {
163            active: false,
164            state: SlicedPathState::InProgress,
165            start_ref: PolyRef::new(0),
166            end_ref: PolyRef::new(0),
167            start_pos: [0.0; 3],
168            end_pos: [0.0; 3],
169            filter: QueryFilter::default(),
170            current_path: Vec::new(),
171            best_node_idx: 0,
172            best_node_cost: f32::MAX,
173        }
174    }
175}
176
177impl<'a> NavMeshQuery<'a> {
178    /// Creates a new navigation mesh query
179    pub fn new(nav_mesh: &'a NavMesh) -> Self {
180        let mut node_pool = Vec::with_capacity(DT_MAX_NODES);
181        for i in 0..DT_MAX_NODES {
182            node_pool.push(Node::new(PolyRef::new(0), i));
183        }
184
185        Self {
186            nav_mesh,
187            node_pool,
188            open_list: BinaryHeap::new(),
189            query_extent: [2.0, 4.0, 2.0], // Default query extent
190            random_seed: 1,
191            sliced_state: SlicedPathfindingState::default(),
192        }
193    }
194
195    pub fn set_query_extent(&mut self, extent: Vec3) {
196        self.query_extent = extent.to_array();
197    }
198
199    pub fn get_query_extent(&self) -> Vec3 {
200        Vec3::from(self.query_extent)
201    }
202
203    pub fn set_random_seed(&mut self, seed: u32) {
204        self.random_seed = seed;
205    }
206
207    /// Generates next random number (simple LCG)
208    fn next_random(&mut self) -> u32 {
209        self.random_seed = self
210            .random_seed
211            .wrapping_mul(1103515245)
212            .wrapping_add(12345);
213        self.random_seed
214    }
215
216    /// Generates a random float between 0.0 and 1.0
217    fn random_f32(&mut self) -> f32 {
218        (self.next_random() & 0x7FFFFFFF) as f32 / 2147483647.0
219    }
220
221    /// Gets a reference to the navigation mesh
222    pub fn nav_mesh(&self) -> &NavMesh {
223        self.nav_mesh
224    }
225
226    /// Finds the polygon nearest to the specified center point
227    pub fn find_nearest_poly(
228        &self,
229        center: Vec3,
230        half_extents: Vec3,
231        filter: &QueryFilter,
232    ) -> Result<(PolyRef, Vec3), DetourError> {
233        let center = center.to_array();
234        let half_extents = half_extents.to_array();
235        // Create search bounds
236        let bmin = [
237            center[0] - half_extents[0],
238            center[1] - half_extents[1],
239            center[2] - half_extents[2],
240        ];
241        let bmax = [
242            center[0] + half_extents[0],
243            center[1] + half_extents[1],
244            center[2] + half_extents[2],
245        ];
246
247        // Query all polygons within the search bounds
248        let polys = self.nav_mesh.query_polygons(&bmin, &bmax, filter)?;
249
250        let mut nearest_ref = PolyRef::new(0);
251        let mut nearest_point = [0.0; 3];
252        let mut nearest_distance_sqr = f32::MAX;
253
254        for poly_ref in polys {
255            let (closest_pt, is_over_poly) =
256                self.closest_point_on_poly(poly_ref, Vec3::from(center))?;
257            let closest_arr = closest_pt.to_array();
258
259            // Calculate distance
260            let diff = [
261                center[0] - closest_arr[0],
262                center[1] - closest_arr[1],
263                center[2] - closest_arr[2],
264            ];
265
266            let d = if is_over_poly {
267                // If point is directly over polygon and closer than climb height,
268                // favor that instead of straight line nearest point
269                let (_tile, _poly) = self.nav_mesh.get_tile_and_poly_by_ref(poly_ref)?;
270                // TODO: Get walkable_climb from navigation mesh params
271                let climb_height = 0.25; // Default climb height
272
273                let height_diff = diff[1].abs() - climb_height;
274                if height_diff > 0.0 {
275                    height_diff * height_diff
276                } else {
277                    0.0
278                }
279            } else {
280                // Regular distance squared
281                diff[0] * diff[0] + diff[1] * diff[1] + diff[2] * diff[2]
282            };
283
284            if d < nearest_distance_sqr {
285                nearest_point = closest_arr;
286                nearest_distance_sqr = d;
287                nearest_ref = poly_ref;
288            }
289        }
290
291        if !nearest_ref.is_valid() {
292            return Err(DetourError::PathNotFound);
293        }
294
295        Ok((nearest_ref, Vec3::from(nearest_point)))
296    }
297
298    /// Finds the polygon nearest to the specified center point (with is_over_poly flag)
299    ///
300    /// This overload also returns whether the nearest point lies directly over the polygon.
301    ///
302    /// # Arguments
303    /// * `center` - The center of the search box
304    /// * `half_extents` - The search distance along each axis
305    /// * `filter` - The polygon filter to apply to the query
306    ///
307    /// # Returns
308    /// A tuple containing:
309    /// * The polygon reference of the nearest polygon
310    /// * The nearest point on the polygon
311    /// * Whether the point's X/Z coordinate lies inside the polygon
312    pub fn find_nearest_poly_extended(
313        &self,
314        center: Vec3,
315        half_extents: Vec3,
316        filter: &QueryFilter,
317    ) -> Result<(PolyRef, Vec3, bool), DetourError> {
318        let center = center.to_array();
319        let half_extents = half_extents.to_array();
320        // Create search bounds
321        let bmin = [
322            center[0] - half_extents[0],
323            center[1] - half_extents[1],
324            center[2] - half_extents[2],
325        ];
326        let bmax = [
327            center[0] + half_extents[0],
328            center[1] + half_extents[1],
329            center[2] + half_extents[2],
330        ];
331
332        // Query all polygons within the search bounds
333        let polys = self.nav_mesh.query_polygons(&bmin, &bmax, filter)?;
334
335        let mut nearest_ref = PolyRef::new(0);
336        let mut nearest_point = [0.0; 3];
337        let mut nearest_distance_sqr = f32::MAX;
338        let mut nearest_is_over_poly = false;
339
340        for poly_ref in polys {
341            let (closest_pt, is_over_poly) =
342                self.closest_point_on_poly(poly_ref, Vec3::from(center))?;
343            let closest_arr = closest_pt.to_array();
344
345            // Calculate distance
346            let diff = [
347                center[0] - closest_arr[0],
348                center[1] - closest_arr[1],
349                center[2] - closest_arr[2],
350            ];
351
352            let d = if is_over_poly {
353                // If point is directly over polygon and closer than climb height,
354                // favor that instead of straight line nearest point
355                let (_tile, _poly) = self.nav_mesh.get_tile_and_poly_by_ref(poly_ref)?;
356                // TODO: Get walkable_climb from navigation mesh params
357                let climb_height = 0.25; // Default climb height
358
359                let height_diff = diff[1].abs() - climb_height;
360                if height_diff > 0.0 {
361                    height_diff * height_diff
362                } else {
363                    0.0
364                }
365            } else {
366                // Regular distance squared
367                diff[0] * diff[0] + diff[1] * diff[1] + diff[2] * diff[2]
368            };
369
370            if d < nearest_distance_sqr {
371                nearest_point = closest_arr;
372                nearest_distance_sqr = d;
373                nearest_ref = poly_ref;
374                nearest_is_over_poly = is_over_poly;
375            }
376        }
377
378        if !nearest_ref.is_valid() {
379            return Err(DetourError::PathNotFound);
380        }
381
382        Ok((nearest_ref, Vec3::from(nearest_point), nearest_is_over_poly))
383    }
384
385    /// Finds a path from start to end position
386    pub fn find_path(
387        &mut self,
388        start_ref: PolyRef,
389        end_ref: PolyRef,
390        start_pos: Vec3,
391        end_pos: Vec3,
392        filter: &QueryFilter,
393    ) -> Result<Vec<PolyRef>, DetourError> {
394        let start_pos = start_pos.to_array();
395        let end_pos = end_pos.to_array();
396        if !self.nav_mesh.is_valid_poly_ref(start_ref) || !self.nav_mesh.is_valid_poly_ref(end_ref)
397        {
398            return Err(DetourError::InvalidParam);
399        }
400
401        // Return early if start and end are the same
402        if start_ref == end_ref {
403            return Ok(vec![start_ref]);
404        }
405
406        for node in &mut self.node_pool {
407            node.parent = None;
408            node.state = NodeState::New;
409            node.poly = PolyRef::new(0);
410            node.g = 0.0;
411            node.h = 0.0;
412            node.f = 0.0;
413        }
414        self.open_list.clear();
415
416        let start_node_idx = 0;
417        let start_h = self.heuristic(&start_pos, &end_pos);
418        {
419            let start_node = &mut self.node_pool[start_node_idx];
420            start_node.poly = start_ref;
421            start_node.state = NodeState::Open;
422            start_node.g = 0.0;
423            start_node.h = start_h;
424            start_node.f = start_h;
425        }
426
427        // Push the start node to the open list
428        let start_f = self.node_pool[start_node_idx].f;
429        self.open_list.push(HeapNode {
430            index: start_node_idx,
431            f: start_f,
432        });
433
434        // Set the goal node
435        let goal_node_idx = 1;
436        self.node_pool[goal_node_idx].poly = end_ref;
437
438        // Iterate through the A* search
439        let mut best_node_idx = start_node_idx;
440        let mut best_node_cost = self.node_pool[start_node_idx].h;
441
442        let mut _iterations = 0;
443        while let Some(HeapNode {
444            index: current_idx, ..
445        }) = self.open_list.pop()
446        {
447            _iterations += 1;
448            let current_node = &mut self.node_pool[current_idx];
449            current_node.state = NodeState::Closed;
450
451            // If we reached the end, break
452            if current_node.poly == end_ref {
453                best_node_idx = current_idx;
454                break;
455            }
456
457            // Get better nodes for partial paths
458            if current_node.h < best_node_cost {
459                best_node_cost = current_node.h;
460                best_node_idx = current_idx;
461            }
462
463            // Expand neighbors
464            self.expand_neighbors(current_idx, goal_node_idx, &end_pos, filter)?;
465        }
466
467        // Reconstruct the path
468        let mut path = Vec::new();
469        let mut current_idx = best_node_idx;
470
471        while current_idx != start_node_idx {
472            let current_node = &self.node_pool[current_idx];
473            path.push(current_node.poly);
474
475            if let Some(parent_idx) = current_node.parent {
476                current_idx = parent_idx;
477            } else {
478                // Path is invalid
479                return Err(DetourError::PathNotFound);
480            }
481        }
482
483        // Add the start node
484        path.push(start_ref);
485
486        // Reverse the path
487        path.reverse();
488
489        Ok(path)
490    }
491
492    /// Expands the neighbors of a node in the A* search
493    fn expand_neighbors(
494        &mut self,
495        current_idx: usize,
496        goal_idx: usize,
497        end_pos: &[f32; 3],
498        filter: &QueryFilter,
499    ) -> Result<(), DetourError> {
500        let current_poly = self.node_pool[current_idx].poly;
501        let current_parent = self.node_pool[current_idx].parent;
502
503        // Get the tile and poly
504        let (tile, poly) = self.nav_mesh.get_tile_and_poly_by_ref(current_poly)?;
505
506        // Get the end node's poly
507        let goal_poly = self.node_pool[goal_idx].poly;
508
509        // Debug: Check neighbor expansion
510        let mut _neighbors_found = 0;
511
512        // Iterate through all neighbors
513        let mut link_idx = poly.first_link;
514        while let Some(idx) = link_idx {
515            let link = &tile.links[idx];
516            link_idx = link.next.map(|n| n as usize);
517
518            let neighbor_ref = link.reference;
519
520            // Skip invalid neighbors and don't expand back to parent (C++ compatibility)
521            if !self.nav_mesh.is_valid_poly_ref(neighbor_ref) {
522                continue;
523            }
524
525            // Skip if this is the parent polygon (avoid infinite loops)
526            if let Some(parent_idx) = current_parent {
527                if self.node_pool[parent_idx].poly == neighbor_ref {
528                    continue;
529                }
530            }
531
532            // Skip this neighbor if it doesn't pass the filter
533            if !self.pass_filter(neighbor_ref, tile, poly, filter)? {
534                continue;
535            }
536
537            _neighbors_found += 1;
538
539            // Find the node or allocate a new one
540            let mut neighbor_idx = 0;
541            while neighbor_idx < DT_MAX_NODES {
542                if self.node_pool[neighbor_idx].state == NodeState::New
543                    || self.node_pool[neighbor_idx].poly == neighbor_ref
544                {
545                    break;
546                }
547                neighbor_idx += 1;
548            }
549
550            // If no node could be allocated, abort
551            if neighbor_idx == DT_MAX_NODES {
552                return Err(DetourError::BufferTooSmall);
553            }
554
555            // Get node state to check if we should process it
556            let neighbor_state = self.node_pool[neighbor_idx].state;
557            let neighbor_g = self.node_pool[neighbor_idx].g;
558            let neighbor_h = self.node_pool[neighbor_idx].h;
559
560            // Initialize poly ref if node is new
561            if neighbor_state == NodeState::New {
562                self.node_pool[neighbor_idx].poly = neighbor_ref;
563            }
564
565            // If the node has already been processed, skip it
566            if neighbor_state == NodeState::Closed {
567                continue;
568            }
569
570            // Get edge midpoint positions (C++ compatibility: use shared edge midpoint)
571            let current_center = self.get_poly_center(current_poly)?.to_array();
572            let neighbor_center = match self.get_portal_points(current_poly, neighbor_ref) {
573                Ok((left, right)) => {
574                    // Use midpoint of shared edge between polygons
575                    ((left + right) * 0.5).to_array()
576                }
577                Err(_) => {
578                    // Fallback to polygon center if portal points not available
579                    self.get_poly_center(neighbor_ref)?.to_array()
580                }
581            };
582
583            // Cost for the neighbor
584            let cost = self.get_edge_cost(
585                current_poly,
586                neighbor_ref,
587                &current_center,
588                &neighbor_center,
589                filter,
590            )?;
591
592            // Calculate the new g value
593            let current_g = self.node_pool[current_idx].g;
594            let new_g = current_g + cost;
595
596            if neighbor_state == NodeState::New || new_g < neighbor_g {
597                // Calculate heuristic if needed
598                let h = if neighbor_state == NodeState::New {
599                    if neighbor_ref == goal_poly {
600                        0.0
601                    } else {
602                        let poly_center = self.get_poly_center(neighbor_ref)?.to_array();
603                        self.heuristic(&poly_center, end_pos)
604                    }
605                } else {
606                    neighbor_h
607                };
608
609                // Update the node
610                {
611                    let neighbor_node = &mut self.node_pool[neighbor_idx];
612                    neighbor_node.parent = Some(current_idx);
613                    neighbor_node.g = new_g;
614                    neighbor_node.h = h;
615                    neighbor_node.f = new_g + h;
616
617                    // Update state if it's a new node
618                    if neighbor_state == NodeState::New {
619                        neighbor_node.state = NodeState::Open;
620                    }
621                }
622
623                // Add to open list if it's a new node
624                if neighbor_state == NodeState::New {
625                    self.open_list.push(HeapNode {
626                        index: neighbor_idx,
627                        f: new_g + h,
628                    });
629                }
630            }
631        }
632
633        Ok(())
634    }
635
636    /// Expands off-mesh connections as potential neighbors in A* search
637    #[allow(dead_code)]
638    fn expand_off_mesh_connections(
639        &mut self,
640        current_idx: usize,
641        goal_idx: usize,
642        end_pos: &[f32; 3],
643        filter: &QueryFilter,
644    ) -> Result<(), DetourError> {
645        let current_node = &self.node_pool[current_idx];
646        let current_poly = current_node.poly;
647
648        // Get the tile and poly for the current node
649        let (tile, poly) = self.nav_mesh.get_tile_and_poly_by_ref(current_poly)?;
650        let goal_poly = self.node_pool[goal_idx].poly;
651
652        // Check all off-mesh connections in ALL tiles (not just current)
653        let all_tiles = self.nav_mesh.get_all_tiles();
654        for check_tile in all_tiles {
655            // Check all off-mesh connections in this tile
656            for connection in &check_tile.off_mesh_connections {
657                // Check if the connection passes the filter
658                if !filter.include_flags.intersects(connection.flags)
659                    || filter.exclude_flags.intersects(connection.flags)
660                {
661                    continue;
662                }
663
664                let connection_ref = connection.poly;
665
666                // Skip if this is the current polygon (can't connect to self)
667                if connection_ref == current_poly {
668                    continue;
669                }
670
671                // Get connection endpoints
672                let start_pos = connection.start_pos();
673                let end_pos_conn = connection.end_pos();
674
675                // Check if current polygon contains or is near the start/end points
676                let mut can_use_connection = false;
677
678                // Debug: Print connection info
679
680                // Check if we're on the polygon that contains the start point
681                if self
682                    .is_point_in_polygon(tile, poly, &start_pos)
683                    .unwrap_or(false)
684                {
685                    can_use_connection = connection.allows_start_to_end();
686                }
687                // Or if we're on the polygon that contains the end point
688                else if self
689                    .is_point_in_polygon(tile, poly, &end_pos_conn)
690                    .unwrap_or(false)
691                {
692                    can_use_connection = connection.allows_end_to_start();
693                }
694                // Or check if any vertex of current polygon is within connection radius
695                else {
696                    for i in 0..poly.vert_count as usize {
697                        let v_idx = poly.verts[i] as usize;
698                        let vert = [
699                            tile.verts[v_idx * 3],
700                            tile.verts[v_idx * 3 + 1],
701                            tile.verts[v_idx * 3 + 2],
702                        ];
703
704                        let dist_to_start = self.distance_3d(vert, start_pos);
705                        let dist_to_end = self.distance_3d(vert, end_pos_conn);
706
707                        if (dist_to_start <= connection.radius && connection.allows_start_to_end())
708                            || (dist_to_end <= connection.radius
709                                && connection.allows_end_to_start())
710                        {
711                            can_use_connection = true;
712                            break;
713                        }
714                    }
715                }
716
717                if !can_use_connection {
718                    continue;
719                }
720
721                // Find the node or allocate a new one for this off-mesh connection
722                let mut neighbor_idx = 0;
723                while neighbor_idx < DT_MAX_NODES {
724                    if self.node_pool[neighbor_idx].state == NodeState::New
725                        || self.node_pool[neighbor_idx].poly == connection_ref
726                    {
727                        break;
728                    }
729                    neighbor_idx += 1;
730                }
731
732                // If no node could be allocated, continue to next connection
733                if neighbor_idx == DT_MAX_NODES {
734                    continue;
735                }
736
737                // Get node state to check if we should process it
738                let neighbor_state = self.node_pool[neighbor_idx].state;
739                let neighbor_g = self.node_pool[neighbor_idx].g;
740                let neighbor_h = self.node_pool[neighbor_idx].h;
741
742                // Initialize poly ref if node is new
743                if neighbor_state == NodeState::New {
744                    self.node_pool[neighbor_idx].poly = connection_ref;
745                }
746
747                // If the node has already been processed, skip it
748                if neighbor_state == NodeState::Closed {
749                    continue;
750                }
751
752                // Determine which endpoint we'll move to
753                let (_from_pos, to_pos) = if can_use_connection && connection.allows_start_to_end()
754                {
755                    (start_pos, end_pos_conn)
756                } else {
757                    (end_pos_conn, start_pos)
758                };
759
760                // Get position on current polygon for cost calculation
761                let current_pos = self.get_poly_center(current_poly)?.to_array();
762
763                // Cost for traversing the off-mesh connection
764                let cost = self.get_off_mesh_connection_cost(
765                    current_poly,
766                    connection_ref,
767                    &current_pos,
768                    &to_pos,
769                    filter,
770                )?;
771
772                // Calculate the new g value
773                let current_g = self.node_pool[current_idx].g;
774                let new_g = current_g + cost;
775
776                if neighbor_state == NodeState::New || new_g < neighbor_g {
777                    // Calculate heuristic if needed
778                    let h = if neighbor_state == NodeState::New {
779                        if connection_ref == goal_poly {
780                            0.0
781                        } else {
782                            self.heuristic(&to_pos, end_pos)
783                        }
784                    } else {
785                        neighbor_h
786                    };
787
788                    // Update the node
789                    {
790                        let neighbor_node = &mut self.node_pool[neighbor_idx];
791                        neighbor_node.parent = Some(current_idx);
792                        neighbor_node.g = new_g;
793                        neighbor_node.h = h;
794                        neighbor_node.f = new_g + h;
795
796                        // Update state if it's a new node
797                        if neighbor_state == NodeState::New {
798                            neighbor_node.state = NodeState::Open;
799                        }
800                    }
801
802                    // Add to open list if it's a new node
803                    if neighbor_state == NodeState::New {
804                        self.open_list.push(HeapNode {
805                            index: neighbor_idx,
806                            f: new_g + h,
807                        });
808                    }
809                }
810            } // Close the off-mesh connections loop
811        } // Close the tiles loop
812
813        Ok(())
814    }
815
816    /// Calculates 3D distance between two points
817    fn distance_3d(&self, a: [f32; 3], b: [f32; 3]) -> f32 {
818        let dx = b[0] - a[0];
819        let dy = b[1] - a[1];
820        let dz = b[2] - a[2];
821        (dx * dx + dy * dy + dz * dz).sqrt()
822    }
823
824    /// Calculates the heuristic (straight-line distance)
825    /// Uses H_SCALE factor to ensure heuristic is slightly underestimated for A* optimality
826    fn heuristic(&self, start: &[f32; 3], end: &[f32; 3]) -> f32 {
827        const H_SCALE: f32 = 0.999; // C++ compatibility: maintain A* optimality
828        let dx = end[0] - start[0];
829        let dy = end[1] - start[1];
830        let dz = end[2] - start[2];
831
832        (dx * dx + dy * dy + dz * dz).sqrt() * H_SCALE
833    }
834
835    fn get_edge_cost(
836        &self,
837        from_ref: PolyRef,
838        to_ref: PolyRef,
839        from_pos: &[f32; 3],
840        to_pos: &[f32; 3],
841        filter: &QueryFilter,
842    ) -> Result<f32, DetourError> {
843        // Handle off-mesh connections specially
844        if self.nav_mesh.is_off_mesh_connection(to_ref)
845            || self.nav_mesh.is_off_mesh_connection(from_ref)
846        {
847            return self.get_off_mesh_connection_cost(from_ref, to_ref, from_pos, to_pos, filter);
848        }
849
850        // Get tile and polygon data
851        let (from_tile, from_poly) = self.nav_mesh.get_tile_and_poly_by_ref(from_ref)?;
852        let (to_tile, to_poly) = self.nav_mesh.get_tile_and_poly_by_ref(to_ref)?;
853
854        // Use filter to calculate cost
855        let cost = filter.get_cost(
856            from_pos,
857            to_pos,
858            PolyRef::new(0),
859            None,
860            None, // prev (not used for edge cost)
861            from_ref,
862            from_tile,
863            from_poly, // current
864            to_ref,
865            Some(to_tile),
866            Some(to_poly), // next
867        );
868
869        Ok(cost)
870    }
871
872    /// Calculates the cost of traversing an off-mesh connection
873    fn get_off_mesh_connection_cost(
874        &self,
875        from_ref: PolyRef,
876        to_ref: PolyRef,
877        from_pos: &[f32; 3],
878        to_pos: &[f32; 3],
879        filter: &QueryFilter,
880    ) -> Result<f32, DetourError> {
881        // Base distance cost
882        let dx = to_pos[0] - from_pos[0];
883        let dy = to_pos[1] - from_pos[1];
884        let dz = to_pos[2] - from_pos[2];
885        let base_cost = (dx * dx + dy * dy + dz * dz).sqrt();
886
887        // If either polygon is an off-mesh connection, get the area cost from it
888        let area = if self.nav_mesh.is_off_mesh_connection(to_ref) {
889            let connection = self.nav_mesh.get_off_mesh_connection(to_ref)?;
890            connection.area
891        } else if self.nav_mesh.is_off_mesh_connection(from_ref) {
892            let connection = self.nav_mesh.get_off_mesh_connection(from_ref)?;
893            connection.area
894        } else {
895            0 // Should not happen based on our check above
896        };
897
898        // Apply area cost multiplier
899        let area_index = (area as usize).min(31);
900        let area_cost = filter.area_cost[area_index];
901
902        Ok(base_cost * area_cost)
903    }
904
905    /// Checks if a polygon passes the filter
906    fn pass_filter(
907        &self,
908        poly_ref: PolyRef,
909        _tile: &MeshTile,
910        _poly: &Poly,
911        filter: &QueryFilter,
912    ) -> Result<bool, DetourError> {
913        // Get the polygon
914        let (_, neighbor_poly) = self.nav_mesh.get_tile_and_poly_by_ref(poly_ref)?;
915
916        // Check if the polygon is included by the filter
917        let include = (neighbor_poly.flags & filter.include_flags) != PolyFlags::empty();
918
919        // Check if the polygon is excluded by the filter
920        let exclude = (neighbor_poly.flags & filter.exclude_flags) != PolyFlags::empty();
921
922        // The polygon passes the filter if it is included and not excluded
923        Ok(include && !exclude)
924    }
925
926    pub fn get_poly_center(&self, poly_ref: PolyRef) -> Result<Vec3, DetourError> {
927        // Get the tile and poly
928        let (tile, poly) = self.nav_mesh.get_tile_and_poly_by_ref(poly_ref)?;
929
930        let mut center = [0.0; 3];
931        let mut count = 0;
932
933        // Calculate the center as the average of all vertices
934        for i in 0..poly.vert_count as usize {
935            let vert_idx = poly.verts[i] as usize;
936
937            center[0] += tile.verts[vert_idx * 3];
938            center[1] += tile.verts[vert_idx * 3 + 1];
939            center[2] += tile.verts[vert_idx * 3 + 2];
940
941            count += 1;
942        }
943
944        if count > 0 {
945            center[0] /= count as f32;
946            center[1] /= count as f32;
947            center[2] /= count as f32;
948        }
949
950        Ok(Vec3::from(center))
951    }
952
953    /// Finds the closest point on a polygon
954    pub fn closest_point_on_poly(
955        &self,
956        poly_ref: PolyRef,
957        pos: Vec3,
958    ) -> Result<(Vec3, bool), DetourError> {
959        let pos = pos.to_array();
960
961        // Handle off-mesh connections
962        if self.nav_mesh.is_off_mesh_connection(poly_ref) {
963            let (pt, over) = self.closest_point_on_off_mesh_connection(poly_ref, &pos)?;
964            return Ok((Vec3::from(pt), over));
965        }
966
967        // Get the tile and poly
968        let (tile, poly) = self.nav_mesh.get_tile_and_poly_by_ref(poly_ref)?;
969        let poly_idx = decode_poly_id(poly_ref) as usize;
970
971        let mut closest = pos;
972        let mut is_over_poly = false;
973
974        // Try to get height for the position within the polygon
975        if let Some(height) = self.nav_mesh.get_poly_height(tile, poly_idx, &pos)? {
976            closest[1] = height;
977            is_over_poly = true;
978            return Ok((Vec3::from(closest), is_over_poly));
979        }
980
981        // Skip the old off-mesh connection handling since we handle it above
982        if poly.poly_type == PolyType::OffMeshConnection {
983            return Ok((Vec3::from(pos), false));
984        }
985
986        // Point is outside polygon, find closest point on edges
987        let mut closest_dist_sqr = f32::MAX;
988
989        // Check each edge of the polygon
990        for i in 0..poly.vert_count as usize {
991            let j = (i + 1) % poly.vert_count as usize;
992
993            let vi_idx = poly.verts[i] as usize;
994            let vj_idx = poly.verts[j] as usize;
995
996            let vi = [
997                tile.verts[vi_idx * 3],
998                tile.verts[vi_idx * 3 + 1],
999                tile.verts[vi_idx * 3 + 2],
1000            ];
1001            let vj = [
1002                tile.verts[vj_idx * 3],
1003                tile.verts[vj_idx * 3 + 1],
1004                tile.verts[vj_idx * 3 + 2],
1005            ];
1006
1007            let vi_pt = Vec3::new(vi[0], vi[1], vi[2]);
1008            let vj_pt = Vec3::new(vj[0], vj[1], vj[2]);
1009            let pos_pt = Vec3::new(pos[0], pos[1], pos[2]);
1010
1011            let edge_closest_pt =
1012                landmark_common::closest_point_on_segment(&pos_pt, &vi_pt, &vj_pt);
1013            let edge_closest = [edge_closest_pt.x, edge_closest_pt.y, edge_closest_pt.z];
1014
1015            let dx = edge_closest[0] - pos[0];
1016            let dy = edge_closest[1] - pos[1];
1017            let dz = edge_closest[2] - pos[2];
1018            let dist_sqr = dx * dx + dy * dy + dz * dz;
1019
1020            if dist_sqr < closest_dist_sqr {
1021                closest_dist_sqr = dist_sqr;
1022                closest = edge_closest;
1023            }
1024        }
1025
1026        Ok((Vec3::from(closest), is_over_poly))
1027    }
1028
1029    /// Finds the closest point on an off-mesh connection
1030    fn closest_point_on_off_mesh_connection(
1031        &self,
1032        connection_ref: PolyRef,
1033        pos: &[f32; 3],
1034    ) -> Result<([f32; 3], bool), DetourError> {
1035        let connection = self.nav_mesh.get_off_mesh_connection(connection_ref)?;
1036
1037        let start_pos = connection.start_pos();
1038        let end_pos = connection.end_pos();
1039
1040        // Find the closest point on the line segment between start and end
1041        let start_pt = Vec3::new(start_pos[0], start_pos[1], start_pos[2]);
1042        let end_pt = Vec3::new(end_pos[0], end_pos[1], end_pos[2]);
1043        let pos_pt = Vec3::new(pos[0], pos[1], pos[2]);
1044
1045        let closest_pt = landmark_common::closest_point_on_segment(&pos_pt, &start_pt, &end_pt);
1046        let closest_arr = [closest_pt.x, closest_pt.y, closest_pt.z];
1047
1048        // Check if the point is within the connection radius
1049        let dist_to_closest = {
1050            let dx = pos[0] - closest_arr[0];
1051            let dy = pos[1] - closest_arr[1];
1052            let dz = pos[2] - closest_arr[2];
1053            (dx * dx + dy * dy + dz * dz).sqrt()
1054        };
1055
1056        let is_over_connection = dist_to_closest <= connection.radius;
1057
1058        Ok((closest_arr, is_over_connection))
1059    }
1060
1061    pub fn get_off_mesh_connection_endpoint(
1062        &self,
1063        connection_ref: PolyRef,
1064        start_pos: Vec3,
1065    ) -> Result<Vec3, DetourError> {
1066        let start_pos = start_pos.to_array();
1067        let connection = self.nav_mesh.get_off_mesh_connection(connection_ref)?;
1068
1069        let conn_start = connection.start_pos();
1070        let conn_end = connection.end_pos();
1071
1072        // Calculate distances to both endpoints
1073        let dist_to_start = {
1074            let dx = start_pos[0] - conn_start[0];
1075            let dy = start_pos[1] - conn_start[1];
1076            let dz = start_pos[2] - conn_start[2];
1077            dx * dx + dy * dy + dz * dz
1078        };
1079
1080        let dist_to_end = {
1081            let dx = start_pos[0] - conn_end[0];
1082            let dy = start_pos[1] - conn_end[1];
1083            let dz = start_pos[2] - conn_end[2];
1084            dx * dx + dy * dy + dz * dz
1085        };
1086
1087        // Return the endpoint that's further away (i.e., the destination)
1088        if dist_to_start < dist_to_end {
1089            // We're closer to the start, so end is the destination
1090            if connection.allows_start_to_end() {
1091                Ok(Vec3::from(conn_end))
1092            } else {
1093                Err(DetourError::InvalidParam)
1094            }
1095        } else {
1096            // We're closer to the end, so start is the destination
1097            if connection.allows_end_to_start() {
1098                Ok(Vec3::from(conn_start))
1099            } else {
1100                Err(DetourError::InvalidParam)
1101            }
1102        }
1103    }
1104
1105    /// Gets the ordered endpoints of an off-mesh connection based on the previous polygon.
1106    /// This method determines the travel direction through the off-mesh connection by looking
1107    /// at which polygon was used to reach the connection.
1108    ///
1109    /// # Arguments
1110    /// * `prev_ref` - Reference to the polygon we came from (used to determine direction)
1111    /// * `connection_ref` - Reference to the off-mesh connection polygon
1112    ///
1113    /// # Returns
1114    /// A tuple of (start_pos, end_pos) ordered for the travel direction.
1115    /// If prev_ref is invalid, returns the connection's natural start->end order.
1116    pub fn get_off_mesh_connection_poly_end_points(
1117        &self,
1118        prev_ref: PolyRef,
1119        connection_ref: PolyRef,
1120    ) -> Result<(Vec3, Vec3), DetourError> {
1121        let connection = self.nav_mesh.get_off_mesh_connection(connection_ref)?;
1122
1123        let conn_start = connection.start_pos();
1124        let conn_end = connection.end_pos();
1125
1126        // If we don't have a valid previous reference, return natural order
1127        if !prev_ref.is_valid() {
1128            return Ok((Vec3::from(conn_start), Vec3::from(conn_end)));
1129        }
1130
1131        // Get the previous polygon's position to determine approach direction
1132        match self.nav_mesh.get_tile_and_poly_by_ref(prev_ref) {
1133            Ok((tile, poly)) => {
1134                // Calculate the center of the previous polygon
1135                let prev_center = self.calculate_poly_center(tile, poly)?;
1136
1137                // Calculate distances from previous polygon center to both endpoints
1138                let dist_to_start = {
1139                    let dx = prev_center[0] - conn_start[0];
1140                    let dy = prev_center[1] - conn_start[1];
1141                    let dz = prev_center[2] - conn_start[2];
1142                    dx * dx + dy * dy + dz * dz
1143                };
1144
1145                let dist_to_end = {
1146                    let dx = prev_center[0] - conn_end[0];
1147                    let dy = prev_center[1] - conn_end[1];
1148                    let dz = prev_center[2] - conn_end[2];
1149                    dx * dx + dy * dy + dz * dz
1150                };
1151
1152                // The endpoint closer to the previous polygon is our "start",
1153                // and the farther one is our "end" for this traversal
1154                if dist_to_start < dist_to_end {
1155                    // We're approaching from the start side, go start->end
1156                    if connection.allows_start_to_end() {
1157                        Ok((Vec3::from(conn_start), Vec3::from(conn_end)))
1158                    } else {
1159                        Err(DetourError::InvalidParam)
1160                    }
1161                } else {
1162                    // We're approaching from the end side, go end->start
1163                    if connection.allows_end_to_start() {
1164                        Ok((Vec3::from(conn_end), Vec3::from(conn_start)))
1165                    } else {
1166                        Err(DetourError::InvalidParam)
1167                    }
1168                }
1169            }
1170            Err(_) => {
1171                // If we can't get the previous polygon, fall back to natural order
1172                Ok((Vec3::from(conn_start), Vec3::from(conn_end)))
1173            }
1174        }
1175    }
1176
1177    /// Helper method to calculate the center point of a polygon
1178    fn calculate_poly_center(&self, tile: &MeshTile, poly: &Poly) -> Result<[f32; 3], DetourError> {
1179        if poly.vert_count == 0 {
1180            return Err(DetourError::InvalidParam);
1181        }
1182
1183        let mut center = [0.0f32; 3];
1184        let vert_count = poly.vert_count as usize;
1185
1186        // Sum all vertex positions
1187        for i in 0..vert_count {
1188            let vert_idx = poly.verts[i] as usize;
1189            if vert_idx * 3 + 2 >= tile.verts.len() {
1190                return Err(DetourError::InvalidParam);
1191            }
1192
1193            center[0] += tile.verts[vert_idx * 3];
1194            center[1] += tile.verts[vert_idx * 3 + 1];
1195            center[2] += tile.verts[vert_idx * 3 + 2];
1196        }
1197
1198        // Average to get center
1199        let count = vert_count as f32;
1200        center[0] /= count;
1201        center[1] /= count;
1202        center[2] /= count;
1203
1204        Ok(center)
1205    }
1206
1207    /// Moves from the start to the end position constrained to the navigation mesh surface
1208    ///
1209    /// This uses the same algorithm as the C++ version: a breadth-first search with
1210    /// a search radius constraint to find the best path.
1211    ///
1212    /// # Algorithm
1213    /// Uses breadth-first search (BFS) to explore neighboring polygons within a search radius.
1214    /// The search radius is calculated as quarter distance between start and end positions
1215    /// plus a small epsilon to ensure numeric stability.
1216    ///
1217    /// # Performance
1218    /// Time complexity: O(n) where n is the number of polygons within search radius (max 16)
1219    /// Space complexity: O(n) for the node storage and traversal queue
1220    ///
1221    /// Uses VecDeque for efficient O(1) front operations during BFS traversal.
1222    pub fn move_along_surface(
1223        &self,
1224        start_ref: PolyRef,
1225        start_pos: Vec3,
1226        end_pos: Vec3,
1227        filter: &QueryFilter,
1228    ) -> Result<MoveAlongSurfaceResult, DetourError> {
1229        use landmark_common::{dist_point_segment_sqr_2d_with_t, point_in_polygon_2d};
1230        use std::collections::VecDeque;
1231
1232        let start_pos = start_pos.to_array();
1233        let end_pos = end_pos.to_array();
1234        if !self.nav_mesh.is_valid_poly_ref(start_ref) {
1235            return Err(DetourError::InvalidParam);
1236        }
1237
1238        // Constants matching C++
1239        const MAX_STACK: usize = 48;
1240        const MAX_VISITED: usize = 16;
1241
1242        // Node structure for breadth-first search
1243        // Removed Clone derive to avoid unnecessary cloning
1244        struct SurfaceNode {
1245            poly_ref: PolyRef,
1246            parent_idx: Option<usize>,
1247        }
1248
1249        let mut nodes = Vec::new();
1250        // Use VecDeque for efficient O(1) front removal in BFS
1251        let mut stack = VecDeque::new();
1252
1253        let start_node = SurfaceNode {
1254            poly_ref: start_ref,
1255            parent_idx: None,
1256        };
1257        nodes.push(start_node);
1258        stack.push_back(0); // Index of start node
1259
1260        let mut best_pos = start_pos;
1261        let mut best_dist = f32::MAX;
1262        let mut best_node_idx = 0;
1263
1264        // Search constraints - midpoint and radius for search area
1265        let search_pos = [
1266            (start_pos[0] + end_pos[0]) * 0.5,
1267            (start_pos[1] + end_pos[1]) * 0.5,
1268            (start_pos[2] + end_pos[2]) * 0.5,
1269        ];
1270
1271        let dx = start_pos[0] - end_pos[0];
1272        let dy = start_pos[1] - end_pos[1];
1273        let dz = start_pos[2] - end_pos[2];
1274        // Search radius: quarter of distance between start and end, plus epsilon for stability
1275        // 0.25 = quarter distance multiplier to constrain search area
1276        // 0.001 = minimum search radius to prevent degenerate cases
1277        let search_rad_sqr = (dx * dx + dy * dy + dz * dz) * 0.25 + 0.001;
1278
1279        while nodes.len() < MAX_VISITED {
1280            // Pop front (breadth-first) - now O(1) with VecDeque
1281            let Some(cur_node_idx) = stack.pop_front() else {
1282                break;
1283            };
1284            let cur_poly_ref = nodes[cur_node_idx].poly_ref;
1285
1286            // Get poly and tile
1287            let (cur_tile, cur_poly) = self.nav_mesh.get_tile_and_poly_by_ref(cur_poly_ref)?;
1288
1289            // Collect vertices
1290            let mut verts = Vec::new();
1291            for i in 0..cur_poly.vert_count as usize {
1292                let idx = cur_poly.verts[i] as usize * 3;
1293                verts.push(cur_tile.verts[idx]);
1294                verts.push(cur_tile.verts[idx + 1]);
1295                verts.push(cur_tile.verts[idx + 2]);
1296            }
1297
1298            // If target is inside the poly, we found it
1299            if point_in_polygon_2d(&end_pos, &verts, cur_poly.vert_count as usize) {
1300                best_node_idx = cur_node_idx;
1301                best_pos = end_pos;
1302                break;
1303            }
1304
1305            // Find wall edges and nearest point inside the walls
1306            for i in 0..cur_poly.vert_count as usize {
1307                let j = if i == 0 {
1308                    cur_poly.vert_count as usize - 1
1309                } else {
1310                    i - 1
1311                };
1312
1313                // Find links to neighbors
1314                let mut neighbors = Vec::new();
1315
1316                if let Some(link) = self.nav_mesh.find_link(cur_tile, cur_poly, j as u8) {
1317                    if link.reference.is_valid() {
1318                        if let Ok((nei_tile, nei_poly)) =
1319                            self.nav_mesh.get_tile_and_poly_by_ref(link.reference)
1320                        {
1321                            if filter.pass_filter(link.reference, nei_tile, nei_poly) {
1322                                neighbors.push(link.reference);
1323                            }
1324                        }
1325                    }
1326                }
1327
1328                let vi_idx = cur_poly.verts[i] as usize * 3;
1329                let vj_idx = cur_poly.verts[j] as usize * 3;
1330                let vi = &cur_tile.verts[vi_idx..vi_idx + 3];
1331                let vj = &cur_tile.verts[vj_idx..vj_idx + 3];
1332
1333                // If wall edge, check distance
1334                if neighbors.is_empty() {
1335                    // Wall edge, calc distance
1336                    let (dist_sqr, t) = dist_point_segment_sqr_2d_with_t(&end_pos, vj, vi);
1337                    if dist_sqr < best_dist {
1338                        // Update nearest distance
1339                        best_pos = [
1340                            vj[0] + (vi[0] - vj[0]) * t,
1341                            vj[1] + (vi[1] - vj[1]) * t,
1342                            vj[2] + (vi[2] - vj[2]) * t,
1343                        ];
1344                        best_dist = dist_sqr;
1345                        best_node_idx = cur_node_idx;
1346                    }
1347                } else {
1348                    // Has neighbors, check them
1349                    for &nei_ref in &neighbors {
1350                        // Check if already visited
1351                        let already_visited = nodes.iter().any(|n| n.poly_ref == nei_ref);
1352                        if already_visited {
1353                            continue;
1354                        }
1355
1356                        // Skip if too far from search constraint
1357                        let (dist_sqr, _) = dist_point_segment_sqr_2d_with_t(&search_pos, vj, vi);
1358                        if dist_sqr > search_rad_sqr {
1359                            continue;
1360                        }
1361
1362                        // Add to nodes and stack
1363                        if stack.len() < MAX_STACK && nodes.len() < MAX_VISITED {
1364                            let new_node = SurfaceNode {
1365                                poly_ref: nei_ref,
1366                                parent_idx: Some(cur_node_idx),
1367                            };
1368                            let new_idx = nodes.len();
1369                            nodes.push(new_node);
1370                            stack.push_back(new_idx);
1371                        }
1372                    }
1373                }
1374            }
1375        }
1376
1377        // Trace back path from best node
1378        let mut path_indices = Vec::new();
1379        let mut current_idx = Some(best_node_idx);
1380
1381        while let Some(idx) = current_idx {
1382            path_indices.push(idx);
1383            current_idx = nodes[idx].parent_idx;
1384        }
1385
1386        // Reverse to get path from start to end
1387        path_indices.reverse();
1388
1389        // Collect visited polygons
1390        let visited = path_indices
1391            .iter()
1392            .map(|&idx| nodes[idx].poly_ref)
1393            .collect();
1394
1395        Ok(MoveAlongSurfaceResult {
1396            position: Vec3::from(best_pos),
1397            visited,
1398        })
1399    }
1400
1401    /// Casts a ray along the navigation mesh
1402    #[allow(unused_assignments)]
1403    pub fn raycast(
1404        &self,
1405        start_ref: PolyRef,
1406        start_pos: Vec3,
1407        dir: Vec3,
1408        max_dist: f32,
1409        filter: &QueryFilter,
1410    ) -> Result<(PolyRef, Vec3, f32), DetourError> {
1411        let start_pos = start_pos.to_array();
1412        let dir = dir.to_array();
1413        if !self.nav_mesh.is_valid_poly_ref(start_ref) {
1414            return Err(DetourError::InvalidParam);
1415        }
1416
1417        // Handle zero-length raycast (C++ compatibility)
1418        if max_dist <= 0.0 {
1419            return Ok((start_ref, Vec3::from(start_pos), 0.0));
1420        }
1421
1422        // Calculate end position
1423        let end_pos = [
1424            start_pos[0] + dir[0] * max_dist,
1425            start_pos[1] + dir[1] * max_dist,
1426            start_pos[2] + dir[2] * max_dist,
1427        ];
1428
1429        let mut cur_ref = start_ref;
1430        let mut cur_pos = start_pos;
1431        let mut _last_pos = start_pos;
1432
1433        const MAX_ITERATIONS: usize = 256;
1434
1435        for _ in 0..MAX_ITERATIONS {
1436            // Get current tile and polygon
1437            let (tile, poly) = self.nav_mesh.get_tile_and_poly_by_ref(cur_ref)?;
1438
1439            // Check if we've reached the end position
1440            let dx = end_pos[0] - cur_pos[0];
1441            let dz = end_pos[2] - cur_pos[2];
1442            if dx * dx + dz * dz < 1e-6 {
1443                break;
1444            }
1445
1446            // Find the edge to cross
1447            let mut next_ref = PolyRef::new(0);
1448            let mut next_pos = end_pos;
1449            let mut next_t = 1.0;
1450
1451            // Check each edge of the polygon
1452            for i in 0..poly.vert_count as usize {
1453                let j = (i + 1) % poly.vert_count as usize;
1454
1455                let vi_idx = poly.verts[i] as usize;
1456                let vj_idx = poly.verts[j] as usize;
1457
1458                let vi = [
1459                    tile.verts[vi_idx * 3],
1460                    tile.verts[vi_idx * 3 + 1],
1461                    tile.verts[vi_idx * 3 + 2],
1462                ];
1463                let vj = [
1464                    tile.verts[vj_idx * 3],
1465                    tile.verts[vj_idx * 3 + 1],
1466                    tile.verts[vj_idx * 3 + 2],
1467                ];
1468
1469                // Check if ray intersects this edge
1470                if let Some((edge_t, seg_t)) =
1471                    Self::intersect_ray_segment_2d(&cur_pos, &end_pos, &vi, &vj)
1472                {
1473                    if edge_t > 0.0 && edge_t < next_t {
1474                        // For neighbor traversal, intersection must be within edge segment
1475                        if (0.0..=1.0).contains(&seg_t) {
1476                            // Get the neighbor polygon through this edge
1477                            if let Some(link) = self.nav_mesh.find_link(tile, poly, i as u8) {
1478                                if link.reference.is_valid()
1479                                    && filter.pass_filter(link.reference, tile, poly)
1480                                {
1481                                    next_ref = link.reference;
1482                                    next_t = edge_t;
1483
1484                                    // Calculate intersection point
1485                                    next_pos = [
1486                                        cur_pos[0] + (end_pos[0] - cur_pos[0]) * edge_t,
1487                                        cur_pos[1] + (end_pos[1] - cur_pos[1]) * edge_t,
1488                                        cur_pos[2] + (end_pos[2] - cur_pos[2]) * edge_t,
1489                                    ];
1490                                } else {
1491                                    // This edge has no valid neighbor - it's a wall
1492                                    next_t = edge_t;
1493                                    next_pos = [
1494                                        cur_pos[0] + (end_pos[0] - cur_pos[0]) * edge_t,
1495                                        cur_pos[1] + (end_pos[1] - cur_pos[1]) * edge_t,
1496                                        cur_pos[2] + (end_pos[2] - cur_pos[2]) * edge_t,
1497                                    ];
1498                                    // Don't set next_ref - it stays invalid to indicate wall hit
1499                                    break; // Found wall intersection, stop looking for closer ones
1500                                }
1501                            } else {
1502                                // No link found - this is definitely a wall edge
1503                                next_t = edge_t;
1504                                next_pos = [
1505                                    cur_pos[0] + (end_pos[0] - cur_pos[0]) * edge_t,
1506                                    cur_pos[1] + (end_pos[1] - cur_pos[1]) * edge_t,
1507                                    cur_pos[2] + (end_pos[2] - cur_pos[2]) * edge_t,
1508                                ];
1509                                // Don't set next_ref - it stays invalid to indicate wall hit
1510                                break; // Found wall intersection, stop looking for closer ones
1511                            }
1512                        } else {
1513                            // Ray intersects extended edge line but not the segment itself
1514                            // This indicates we're hitting a wall at the polygon boundary
1515                            next_t = edge_t;
1516                            next_pos = [
1517                                cur_pos[0] + (end_pos[0] - cur_pos[0]) * edge_t,
1518                                cur_pos[1] + (end_pos[1] - cur_pos[1]) * edge_t,
1519                                cur_pos[2] + (end_pos[2] - cur_pos[2]) * edge_t,
1520                            ];
1521                            // Don't set next_ref - it stays invalid to indicate wall hit
1522                            break; // Found wall intersection, stop looking for closer ones
1523                        }
1524                    }
1525                }
1526            }
1527
1528            // Also check for off-mesh connections from this position (only if we found a valid neighbor)
1529            if next_ref.is_valid() {
1530                next_ref = self.check_off_mesh_connections_for_raycast(
1531                    &cur_pos,
1532                    &end_pos,
1533                    tile,
1534                    filter,
1535                    &mut next_pos,
1536                    &mut next_t,
1537                )?;
1538            }
1539
1540            // If we couldn't find a valid next polygon, we hit a wall
1541            if !next_ref.is_valid() {
1542                // If next_t is 1.0, it means we didn't find any edge intersection,
1543                // so the ray travels the full distance within this polygon
1544                if next_t >= 1.0 {
1545                    return Ok((cur_ref, Vec3::from(end_pos), max_dist));
1546                }
1547
1548                // Calculate the hit distance based on next_t
1549                let hit_dist = max_dist * next_t;
1550                return Ok((cur_ref, Vec3::from(next_pos), hit_dist));
1551            }
1552
1553            // Move to the next polygon
1554            _last_pos = cur_pos;
1555            cur_pos = next_pos;
1556            cur_ref = next_ref;
1557        }
1558
1559        // Reached the end position
1560        Ok((cur_ref, Vec3::from(end_pos), max_dist))
1561    }
1562
1563    /// Checks off-mesh connections for raycast traversal
1564    fn check_off_mesh_connections_for_raycast(
1565        &self,
1566        cur_pos: &[f32; 3],
1567        end_pos: &[f32; 3],
1568        tile: &MeshTile,
1569        filter: &QueryFilter,
1570        next_pos: &mut [f32; 3],
1571        next_t: &mut f32,
1572    ) -> Result<PolyRef, DetourError> {
1573        let mut best_ref = PolyRef::new(0);
1574        let mut best_t = *next_t;
1575
1576        // Check all off-mesh connections in this tile
1577        for connection in &tile.off_mesh_connections {
1578            // Check if the connection passes the filter
1579            if !filter.include_flags.intersects(connection.flags)
1580                || filter.exclude_flags.intersects(connection.flags)
1581            {
1582                continue;
1583            }
1584
1585            let start_conn = connection.start_pos();
1586            let end_conn = connection.end_pos();
1587
1588            // Check if ray passes close to either endpoint
1589            let ray_dir = [
1590                end_pos[0] - cur_pos[0],
1591                end_pos[1] - cur_pos[1],
1592                end_pos[2] - cur_pos[2],
1593            ];
1594            let ray_len =
1595                (ray_dir[0] * ray_dir[0] + ray_dir[1] * ray_dir[1] + ray_dir[2] * ray_dir[2])
1596                    .sqrt();
1597
1598            if ray_len < 1e-6 {
1599                continue;
1600            }
1601
1602            let ray_dir_norm = [
1603                ray_dir[0] / ray_len,
1604                ray_dir[1] / ray_len,
1605                ray_dir[2] / ray_len,
1606            ];
1607
1608            // Check distance to start point along the ray
1609            let to_start = [
1610                start_conn[0] - cur_pos[0],
1611                start_conn[1] - cur_pos[1],
1612                start_conn[2] - cur_pos[2],
1613            ];
1614            let proj_start = to_start[0] * ray_dir_norm[0]
1615                + to_start[1] * ray_dir_norm[1]
1616                + to_start[2] * ray_dir_norm[2];
1617
1618            // Check distance to end point along the ray
1619            let to_end = [
1620                end_conn[0] - cur_pos[0],
1621                end_conn[1] - cur_pos[1],
1622                end_conn[2] - cur_pos[2],
1623            ];
1624            let proj_end = to_end[0] * ray_dir_norm[0]
1625                + to_end[1] * ray_dir_norm[1]
1626                + to_end[2] * ray_dir_norm[2];
1627
1628            // Check if we can reach the start point and use the connection
1629            if proj_start > 0.0 && proj_start < ray_len && proj_start < best_t * ray_len {
1630                let closest_on_ray = [
1631                    cur_pos[0] + ray_dir_norm[0] * proj_start,
1632                    cur_pos[1] + ray_dir_norm[1] * proj_start,
1633                    cur_pos[2] + ray_dir_norm[2] * proj_start,
1634                ];
1635
1636                let dist_to_start = self.distance_3d(closest_on_ray, start_conn);
1637                if dist_to_start <= connection.radius && connection.allows_start_to_end() {
1638                    let t = proj_start / ray_len;
1639                    if t < best_t {
1640                        best_ref = connection.poly;
1641                        best_t = t;
1642                        *next_pos = end_conn; // Jump to the other end of the connection
1643                    }
1644                }
1645            }
1646
1647            // Check if we can reach the end point and use the connection
1648            if proj_end > 0.0 && proj_end < ray_len && proj_end < best_t * ray_len {
1649                let closest_on_ray = [
1650                    cur_pos[0] + ray_dir_norm[0] * proj_end,
1651                    cur_pos[1] + ray_dir_norm[1] * proj_end,
1652                    cur_pos[2] + ray_dir_norm[2] * proj_end,
1653                ];
1654
1655                let dist_to_end = self.distance_3d(closest_on_ray, end_conn);
1656                if dist_to_end <= connection.radius && connection.allows_end_to_start() {
1657                    let t = proj_end / ray_len;
1658                    if t < best_t {
1659                        best_ref = connection.poly;
1660                        best_t = t;
1661                        *next_pos = start_conn; // Jump to the other end of the connection
1662                    }
1663                }
1664            }
1665        }
1666
1667        if best_ref.is_valid() {
1668            *next_t = best_t;
1669        }
1670
1671        Ok(best_ref)
1672    }
1673
1674    /// Intersects a ray with a line segment in 2D (ignoring Y)
1675    fn intersect_ray_segment_2d(
1676        ray_start: &[f32; 3],
1677        ray_end: &[f32; 3],
1678        seg_start: &[f32; 3],
1679        seg_end: &[f32; 3],
1680    ) -> Option<(f32, f32)> {
1681        let dx = ray_end[0] - ray_start[0];
1682        let dz = ray_end[2] - ray_start[2];
1683        let seg_dx = seg_end[0] - seg_start[0];
1684        let seg_dz = seg_end[2] - seg_start[2];
1685
1686        let d = seg_dx * dz - seg_dz * dx;
1687        if d.abs() < 1e-6 {
1688            return None; // Parallel
1689        }
1690
1691        let t = ((seg_start[0] - ray_start[0]) * dz - (seg_start[2] - ray_start[2]) * dx) / d;
1692        let s =
1693            ((seg_start[0] - ray_start[0]) * seg_dz - (seg_start[2] - ray_start[2]) * seg_dx) / d;
1694
1695        Some((s, t))
1696    }
1697
1698    /// Enhanced raycast method that returns detailed hit information
1699    /// Matches C++ API: raycast(..., dtRaycastHit*, ...)
1700    pub fn raycast_enhanced(
1701        &self,
1702        start_ref: PolyRef,
1703        start_pos: Vec3,
1704        end_pos: Vec3,
1705        filter: &QueryFilter,
1706        options: &RaycastOptions,
1707        prev_ref: Option<PolyRef>,
1708    ) -> Result<RaycastResult, DetourError> {
1709        let start_pos = start_pos.to_array();
1710        let end_pos = end_pos.to_array();
1711        if !self.nav_mesh.is_valid_poly_ref(start_ref) {
1712            return Err(DetourError::InvalidParam);
1713        }
1714
1715        // Calculate ray direction and distance
1716        let dir = [
1717            end_pos[0] - start_pos[0],
1718            end_pos[1] - start_pos[1],
1719            end_pos[2] - start_pos[2],
1720        ];
1721        let max_dist = (dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2]).sqrt();
1722
1723        let mut hit = RaycastHit::no_hit();
1724        let mut visited_path = Vec::new();
1725        let mut path_cost = 0.0;
1726
1727        // Handle zero-length ray
1728        if max_dist <= 0.0 {
1729            let result = RaycastResult::new(start_ref, Vec3::from(start_pos), hit);
1730            return Ok(result);
1731        }
1732
1733        // Track current position
1734        let mut cur_ref = start_ref;
1735        let mut cur_pos = start_pos;
1736        let mut last_ref = prev_ref.unwrap_or(PolyRef::new(0));
1737
1738        if options.include_path {
1739            visited_path.push(start_ref);
1740        }
1741
1742        const MAX_ITERATIONS: usize = 256;
1743
1744        for iter in 0..MAX_ITERATIONS {
1745            // Get current tile and polygon
1746            let (tile, poly) = self.nav_mesh.get_tile_and_poly_by_ref(cur_ref)?;
1747
1748            // Check if we've reached the end position
1749            let dx = end_pos[0] - cur_pos[0];
1750            let dz = end_pos[2] - cur_pos[2];
1751            if dx * dx + dz * dz < 1e-6 {
1752                break;
1753            }
1754
1755            // Find the edge to cross
1756            let mut next_ref = PolyRef::new(0);
1757            let mut next_pos = end_pos;
1758            let mut edge_t = 1.0;
1759            let mut edge_index = -1;
1760
1761            // Check each edge of the polygon
1762            for i in 0..poly.vert_count as usize {
1763                let j = (i + 1) % poly.vert_count as usize;
1764
1765                let vi_idx = poly.verts[i] as usize;
1766                let vj_idx = poly.verts[j] as usize;
1767
1768                let vi = [
1769                    tile.verts[vi_idx * 3],
1770                    tile.verts[vi_idx * 3 + 1],
1771                    tile.verts[vi_idx * 3 + 2],
1772                ];
1773                let vj = [
1774                    tile.verts[vj_idx * 3],
1775                    tile.verts[vj_idx * 3 + 1],
1776                    tile.verts[vj_idx * 3 + 2],
1777                ];
1778
1779                // Check if ray intersects this edge
1780                if let Some((ray_t, seg_t)) =
1781                    Self::intersect_ray_segment_2d(&cur_pos, &end_pos, &vi, &vj)
1782                {
1783                    if ray_t > 0.0 && ray_t < edge_t {
1784                        // Check if this is a neighbor edge or a wall
1785                        if let Some(link) = self.nav_mesh.find_link(tile, poly, i as u8) {
1786                            if link.reference.is_valid()
1787                                && link.reference != last_ref
1788                                && filter.pass_filter(link.reference, tile, poly)
1789                                && (0.0..=1.0).contains(&seg_t)
1790                            {
1791                                // Valid neighbor
1792                                next_ref = link.reference;
1793                                edge_t = ray_t;
1794                                edge_index = i as i32;
1795                                next_pos = [
1796                                    cur_pos[0] + dir[0] * ray_t,
1797                                    cur_pos[1] + dir[1] * ray_t,
1798                                    cur_pos[2] + dir[2] * ray_t,
1799                                ];
1800                            }
1801                        } else if (0.0..=1.0).contains(&seg_t) {
1802                            // Hit a wall
1803                            edge_t = ray_t;
1804                            edge_index = i as i32;
1805                            next_pos = [
1806                                cur_pos[0] + dir[0] * ray_t,
1807                                cur_pos[1] + dir[1] * ray_t,
1808                                cur_pos[2] + dir[2] * ray_t,
1809                            ];
1810                            // Calculate normal
1811                            let edge_dx = vj[0] - vi[0];
1812                            let edge_dz = vj[2] - vi[2];
1813                            let edge_len = (edge_dx * edge_dx + edge_dz * edge_dz).sqrt();
1814                            if edge_len > 1e-6 {
1815                                hit.hit_normal =
1816                                    Vec3::new(edge_dz / edge_len, 0.0, -edge_dx / edge_len);
1817                            }
1818                            // Mark as wall hit
1819                            next_ref = PolyRef::new(0);
1820                            break;
1821                        }
1822                    }
1823                }
1824            }
1825
1826            // Update cost if requested
1827            if options.include_cost && next_ref.is_valid() {
1828                let edge_cost =
1829                    self.get_edge_cost(cur_ref, next_ref, &cur_pos, &next_pos, filter)?;
1830                path_cost += edge_cost;
1831            }
1832
1833            // If we hit a wall, finalize the hit info
1834            if !next_ref.is_valid() {
1835                hit.t = edge_t * max_dist / max_dist; // Normalize to [0,1]
1836                hit.hit_edge_index = edge_index;
1837
1838                if options.include_path {
1839                    hit = hit.with_path(visited_path);
1840                }
1841                if options.include_cost {
1842                    hit = hit.with_cost(path_cost);
1843                }
1844
1845                let result = RaycastResult::new(cur_ref, Vec3::from(next_pos), hit);
1846                return Ok(result);
1847            }
1848
1849            // Move to the next polygon
1850            if options.include_path {
1851                visited_path.push(next_ref);
1852            }
1853            last_ref = cur_ref;
1854            cur_ref = next_ref;
1855            cur_pos = next_pos;
1856
1857            // Prevent infinite loops
1858            if iter >= MAX_ITERATIONS - 1 {
1859                break;
1860            }
1861        }
1862
1863        // Reached the end position without hitting a wall
1864        if options.include_path {
1865            hit = hit.with_path(visited_path);
1866        }
1867        if options.include_cost {
1868            hit = hit.with_cost(path_cost);
1869        }
1870
1871        let result = RaycastResult::new(cur_ref, Vec3::from(end_pos), hit);
1872        Ok(result)
1873    }
1874
1875    /// Gets a path from the explored nodes in the previous Dijkstra search
1876    /// Matches C++ API: getPathFromDijkstraSearch
1877    pub fn get_path_from_dijkstra_search(
1878        &self,
1879        end_ref: PolyRef,
1880        max_path: usize,
1881    ) -> Result<Vec<PolyRef>, DetourError> {
1882        if !self.nav_mesh.is_valid_poly_ref(end_ref) {
1883            return Err(DetourError::InvalidParam);
1884        }
1885
1886        // Find the end node in our node pool
1887        let mut end_node_idx = None;
1888        for i in 0..self.node_pool.len() {
1889            if self.node_pool[i].poly == end_ref && self.node_pool[i].state == NodeState::Closed {
1890                end_node_idx = Some(i);
1891                break;
1892            }
1893        }
1894
1895        let end_idx = end_node_idx.ok_or_else(|| DetourError::NotFound)?;
1896
1897        // Build path by following parent links
1898        let mut path = Vec::new();
1899        let mut current_idx = end_idx;
1900        let mut safety_counter = 0;
1901
1902        loop {
1903            if safety_counter >= max_path {
1904                break; // Path too long
1905            }
1906            safety_counter += 1;
1907
1908            let node = &self.node_pool[current_idx];
1909            path.push(node.poly);
1910
1911            match node.parent {
1912                Some(parent_idx) => {
1913                    current_idx = parent_idx;
1914                }
1915                None => {
1916                    // Reached the start
1917                    break;
1918                }
1919            }
1920        }
1921
1922        // Reverse to get start->end order
1923        path.reverse();
1924
1925        // Truncate if needed
1926        if path.len() > max_path {
1927            path.truncate(max_path);
1928        }
1929
1930        Ok(path)
1931    }
1932
1933    /// Finds a straight path from start to end
1934    #[allow(unused_assignments)]
1935    pub fn find_straight_path(
1936        &self,
1937        start_pos: Vec3,
1938        end_pos: Vec3,
1939        path: &[PolyRef],
1940    ) -> Result<Path, DetourError> {
1941        let start_pos = start_pos.to_array();
1942        let end_pos = end_pos.to_array();
1943        if path.is_empty() {
1944            return Err(DetourError::InvalidParam);
1945        }
1946
1947        let mut result = Path::new();
1948
1949        // Add the start position
1950        result.waypoints.push(Vec3::from(start_pos));
1951        result.poly_refs.push(path[0]);
1952
1953        // If the path is just one polygon, add the end position and return
1954        if path.len() == 1 {
1955            result.waypoints.push(Vec3::from(end_pos));
1956            result.poly_refs.push(path[0]);
1957            return Ok(result);
1958        }
1959
1960        // Use the funnel algorithm to find the straight path
1961        let mut portal_apex = start_pos;
1962        let mut portal_left = portal_apex;
1963        let mut portal_right = portal_apex;
1964        let mut apex_index = 0;
1965        let mut left_index = 0;
1966        let mut right_index = 0;
1967
1968        for i in 0..path.len() - 1 {
1969            // Get portal points between current and next polygon
1970            let (left_v, right_v) = self.get_portal_points(path[i], path[i + 1])?;
1971            let left = left_v.to_array();
1972            let right = right_v.to_array();
1973
1974            // If starting really close to the portal, skip it
1975            if i == 0 {
1976                let dx = portal_apex[0] - left[0];
1977                let dz = portal_apex[2] - left[2];
1978                let dist_sqr = dx * dx + dz * dz;
1979                if dist_sqr < 0.001 * 0.001 {
1980                    continue;
1981                }
1982            }
1983
1984            // Right vertex
1985            if Self::tri_area_2d(&portal_apex, &portal_right, &right) <= 0.0 {
1986                if Self::v_equal(&portal_apex, &portal_right)
1987                    || Self::tri_area_2d(&portal_apex, &portal_left, &right) > 0.0
1988                {
1989                    portal_right = right;
1990                    right_index = i + 1;
1991                } else {
1992                    // Append apex vertex
1993                    result.waypoints.push(Vec3::from(portal_left));
1994                    result.poly_refs.push(path[left_index]);
1995
1996                    // Restart funnel
1997                    portal_apex = portal_left;
1998                    apex_index = left_index;
1999                    portal_left = portal_apex;
2000                    portal_right = portal_apex;
2001                    left_index = apex_index;
2002                    right_index = apex_index;
2003                    continue;
2004                }
2005            }
2006
2007            // Left vertex
2008            if Self::tri_area_2d(&portal_apex, &portal_left, &left) >= 0.0 {
2009                if Self::v_equal(&portal_apex, &portal_left)
2010                    || Self::tri_area_2d(&portal_apex, &portal_right, &left) < 0.0
2011                {
2012                    portal_left = left;
2013                    left_index = i + 1;
2014                } else {
2015                    // Append apex vertex
2016                    result.waypoints.push(Vec3::from(portal_right));
2017                    result.poly_refs.push(path[right_index]);
2018
2019                    // Restart funnel
2020                    portal_apex = portal_right;
2021                    apex_index = right_index;
2022                    portal_left = portal_apex;
2023                    portal_right = portal_apex;
2024                    left_index = apex_index;
2025                    right_index = apex_index;
2026                    continue;
2027                }
2028            }
2029        }
2030
2031        // Add the end position
2032        result.waypoints.push(Vec3::from(end_pos));
2033        result.poly_refs.push(path[path.len() - 1]);
2034
2035        Ok(result)
2036    }
2037
2038    /// Finds the straight path from start to end position with options
2039    ///
2040    /// This overload accepts additional options for path computation.
2041    ///
2042    /// # Arguments
2043    /// * `start_pos` - Path start position
2044    /// * `end_pos` - Path end position  
2045    /// * `path` - An array of polygon references that represent the path corridor
2046    /// * `options` - Options for straight path computation (0 = default behavior)
2047    ///
2048    /// # Returns
2049    /// The straight path with waypoints and polygon references
2050    pub fn find_straight_path_with_options(
2051        &self,
2052        start_pos: Vec3,
2053        end_pos: Vec3,
2054        path: &[PolyRef],
2055        options: u32,
2056    ) -> Result<Path, DetourError> {
2057        // For now, options parameter is accepted but not used
2058        // This maintains C++ API compatibility
2059        let _ = options;
2060
2061        // Delegate to the main implementation
2062        self.find_straight_path(start_pos, end_pos, path)
2063    }
2064
2065    /// Gets portal points between two adjacent polygons
2066    ///
2067    /// # Arguments
2068    ///
2069    /// * `from_ref` - The reference of the polygon to traverse from
2070    /// * `to_ref` - The reference of the polygon to traverse to
2071    ///
2072    /// # Returns
2073    ///
2074    /// A tuple containing the left and right portal points
2075    pub fn get_portal_points(
2076        &self,
2077        from_ref: PolyRef,
2078        to_ref: PolyRef,
2079    ) -> Result<(Vec3, Vec3), DetourError> {
2080        let (from_tile, from_poly) = self.nav_mesh.get_tile_and_poly_by_ref(from_ref)?;
2081        let (_to_tile, to_poly) = self.nav_mesh.get_tile_and_poly_by_ref(to_ref)?;
2082
2083        // Special handling for off-mesh connections
2084        if from_poly.poly_type == PolyType::OffMeshConnection {
2085            // For off-mesh connections, find the link that points to the 'to' polygon
2086            // and return the vertex position as both left and right (like C++)
2087            let mut link_idx = from_poly.first_link;
2088            while let Some(idx) = link_idx {
2089                if idx < from_tile.links.len() {
2090                    let link = &from_tile.links[idx];
2091                    if link.reference == to_ref {
2092                        // Get the vertex position from the edge index
2093                        let v_idx = from_poly.verts[link.edge_index as usize] as usize;
2094                        if v_idx * 3 + 2 < from_tile.verts.len() {
2095                            let pos = [
2096                                from_tile.verts[v_idx * 3],
2097                                from_tile.verts[v_idx * 3 + 1],
2098                                from_tile.verts[v_idx * 3 + 2],
2099                            ];
2100                            // Return same position for both left and right
2101                            let pos = Vec3::from(pos);
2102                            return Ok((pos, pos));
2103                        }
2104                    }
2105                    link_idx = link.next.map(|n| n as usize);
2106                }
2107            }
2108            return Err(DetourError::InvalidParam);
2109        }
2110
2111        if to_poly.poly_type == PolyType::OffMeshConnection {
2112            // For connections TO off-mesh connections, find the link in the off-mesh connection
2113            // that points back to 'from' and use that vertex
2114            let mut link_idx = to_poly.first_link;
2115            while let Some(idx) = link_idx {
2116                if idx < _to_tile.links.len() {
2117                    let link = &_to_tile.links[idx];
2118                    if link.reference == from_ref {
2119                        // Get the vertex position from the edge index
2120                        let v_idx = to_poly.verts[link.edge_index as usize] as usize;
2121                        if v_idx * 3 + 2 < _to_tile.verts.len() {
2122                            let pos = [
2123                                _to_tile.verts[v_idx * 3],
2124                                _to_tile.verts[v_idx * 3 + 1],
2125                                _to_tile.verts[v_idx * 3 + 2],
2126                            ];
2127                            // Return same position for both left and right
2128                            let pos = Vec3::from(pos);
2129                            return Ok((pos, pos));
2130                        }
2131                    }
2132                    link_idx = link.next.map(|n| n as usize);
2133                }
2134            }
2135            return Err(DetourError::InvalidParam);
2136        }
2137
2138        // Regular polygon-to-polygon edge finding
2139        // Find the shared edge
2140        for i in 0..from_poly.vert_count as usize {
2141            // Check if this edge connects to the target polygon
2142            if let Some(link) = self.nav_mesh.find_link(from_tile, from_poly, i as u8) {
2143                if link.reference == to_ref {
2144                    let j = (i + 1) % from_poly.vert_count as usize;
2145
2146                    let vi_idx = from_poly.verts[i] as usize;
2147                    let vj_idx = from_poly.verts[j] as usize;
2148
2149                    let left = [
2150                        from_tile.verts[vi_idx * 3],
2151                        from_tile.verts[vi_idx * 3 + 1],
2152                        from_tile.verts[vi_idx * 3 + 2],
2153                    ];
2154                    let right = [
2155                        from_tile.verts[vj_idx * 3],
2156                        from_tile.verts[vj_idx * 3 + 1],
2157                        from_tile.verts[vj_idx * 3 + 2],
2158                    ];
2159
2160                    return Ok((Vec3::from(left), Vec3::from(right)));
2161                }
2162            }
2163        }
2164
2165        // If we couldn't find a direct link, the polygons might not be adjacent
2166        Err(DetourError::InvalidParam)
2167    }
2168
2169    /// Calculates the signed area of a triangle in 2D (ignoring Y)
2170    fn tri_area_2d(a: &[f32; 3], b: &[f32; 3], c: &[f32; 3]) -> f32 {
2171        let abx = b[0] - a[0];
2172        let abz = b[2] - a[2];
2173        let acx = c[0] - a[0];
2174        let acz = c[2] - a[2];
2175        acx * abz - abx * acz
2176    }
2177
2178    /// Checks if two positions are approximately equal (ignoring Y)
2179    fn v_equal(a: &[f32; 3], b: &[f32; 3]) -> bool {
2180        let dx = a[0] - b[0];
2181        let dz = a[2] - b[2];
2182        dx * dx + dz * dz < 0.00001
2183    }
2184
2185    /// Calculates the squared 2D distance from a point to a line segment (ignoring Y)
2186    /// Based on the C++ dtDistancePtSegSqr2D function
2187    fn distance_point_to_segment_2d_squared(
2188        &self,
2189        point: &[f32; 3],
2190        segment_start: &[f32; 3],
2191        segment_end: &[f32; 3],
2192    ) -> f32 {
2193        let px = point[0];
2194        let pz = point[2];
2195        let ax = segment_start[0];
2196        let az = segment_start[2];
2197        let bx = segment_end[0];
2198        let bz = segment_end[2];
2199
2200        let dx = bx - ax;
2201        let dz = bz - az;
2202        let d = dx * dx + dz * dz;
2203
2204        if d > 0.0 {
2205            let t = ((px - ax) * dx + (pz - az) * dz) / d;
2206            let t_clamped = t.clamp(0.0, 1.0);
2207
2208            let closest_x = ax + t_clamped * dx;
2209            let closest_z = az + t_clamped * dz;
2210
2211            let dist_x = px - closest_x;
2212            let dist_z = pz - closest_z;
2213
2214            dist_x * dist_x + dist_z * dist_z
2215        } else {
2216            // Degenerate segment - distance to point
2217            let dist_x = px - ax;
2218            let dist_z = pz - az;
2219            dist_x * dist_x + dist_z * dist_z
2220        }
2221    }
2222
2223    /// Finds all polygons within a circular area around a center point
2224    pub fn find_polys_around_circle(
2225        &self,
2226        center_ref: PolyRef,
2227        center_pos: Vec3,
2228        radius: f32,
2229        filter: &QueryFilter,
2230    ) -> Result<Vec<PolyRef>, DetourError> {
2231        let center_pos = center_pos.to_array();
2232        if !self.nav_mesh.is_valid_poly_ref(center_ref) {
2233            return Err(DetourError::InvalidParam);
2234        }
2235
2236        let mut node_pool = std::collections::HashMap::new();
2237        let mut open_list = BinaryHeap::new();
2238        let mut result_polys = Vec::new();
2239
2240        let radius_squared = radius * radius;
2241
2242        // Create start node
2243        let start_node = Node {
2244            poly: center_ref,
2245            parent: None,
2246            g: 0.0,
2247            h: 0.0,
2248            f: 0.0,
2249            state: NodeState::Open,
2250            index: 0,
2251        };
2252
2253        // Add start node to pool and open list
2254        node_pool.insert(center_ref.id(), start_node);
2255        open_list.push(HeapNode {
2256            index: center_ref.id() as usize,
2257            f: 0.0,
2258        });
2259
2260        // Main search loop - Dijkstra-like algorithm
2261        while let Some(heap_node) = open_list.pop() {
2262            let current_ref_id = heap_node.index as u32;
2263            let current_ref = PolyRef::new(current_ref_id);
2264
2265            // Mark node as closed and get current cost
2266            let current_cost = {
2267                match node_pool.get_mut(&current_ref_id) {
2268                    Some(node) => {
2269                        node.state = NodeState::Closed;
2270                        node.g
2271                    }
2272                    None => continue,
2273                }
2274            };
2275
2276            // Get tile and polygon data
2277            let (current_tile, current_poly) =
2278                match self.nav_mesh.get_tile_and_poly_by_ref(current_ref) {
2279                    Ok((tile, poly)) => (tile, poly),
2280                    Err(_) => continue,
2281                };
2282
2283            // Check if this polygon passes the filter
2284            if !filter.pass_filter(current_ref, current_tile, current_poly) {
2285                continue;
2286            }
2287
2288            // Add current polygon to results
2289            result_polys.push(current_ref);
2290
2291            // Explore neighbors through links
2292            if let Some(first_link_idx) = current_poly.first_link {
2293                let mut link_idx = first_link_idx;
2294
2295                loop {
2296                    if link_idx >= current_tile.links.len() {
2297                        break;
2298                    }
2299
2300                    let link = &current_tile.links[link_idx];
2301                    let neighbor_ref = link.reference;
2302
2303                    // Skip invalid neighbors
2304                    if !neighbor_ref.is_valid() {
2305                        if let Some(next_idx) = link.next {
2306                            link_idx = next_idx as usize;
2307                            continue;
2308                        } else {
2309                            break;
2310                        }
2311                    }
2312
2313                    // Get neighbor tile and polygon
2314                    let (neighbor_tile, neighbor_poly) =
2315                        match self.nav_mesh.get_tile_and_poly_by_ref(neighbor_ref) {
2316                            Ok((tile, poly)) => (tile, poly),
2317                            Err(_) => {
2318                                if let Some(next_idx) = link.next {
2319                                    link_idx = next_idx as usize;
2320                                    continue;
2321                                } else {
2322                                    break;
2323                                }
2324                            }
2325                        };
2326
2327                    // Check filter
2328                    if !filter.pass_filter(neighbor_ref, neighbor_tile, neighbor_poly) {
2329                        if let Some(next_idx) = link.next {
2330                            link_idx = next_idx as usize;
2331                            continue;
2332                        } else {
2333                            break;
2334                        }
2335                    }
2336
2337                    // Test circle intersection with portal edge
2338                    let portal_result = self.get_portal_points(current_ref, neighbor_ref);
2339                    let within_circle = match portal_result {
2340                        Ok((left_portal, right_portal)) => {
2341                            // Calculate 2D distance from circle center to portal edge
2342                            let left_arr = left_portal.to_array();
2343                            let right_arr = right_portal.to_array();
2344                            let dist_squared = self.distance_point_to_segment_2d_squared(
2345                                &center_pos,
2346                                &left_arr,
2347                                &right_arr,
2348                            );
2349                            dist_squared <= radius_squared
2350                        }
2351                        Err(_) => {
2352                            // If we can't get portal points, fall back to polygon center distance
2353                            if let Ok(neighbor_center) = self.get_poly_center(neighbor_ref) {
2354                                let dx = neighbor_center[0] - center_pos[0];
2355                                let dz = neighbor_center[2] - center_pos[2];
2356                                let dist_squared = dx * dx + dz * dz;
2357                                dist_squared <= radius_squared
2358                            } else {
2359                                false
2360                            }
2361                        }
2362                    };
2363
2364                    // Skip if not within circle
2365                    if !within_circle {
2366                        if let Some(next_idx) = link.next {
2367                            link_idx = next_idx as usize;
2368                            continue;
2369                        } else {
2370                            break;
2371                        }
2372                    }
2373
2374                    // Check if neighbor already processed
2375                    let neighbor_id = neighbor_ref.id();
2376                    if let Some(existing_node) = node_pool.get(&neighbor_id) {
2377                        if existing_node.state == NodeState::Closed {
2378                            // Already processed, skip
2379                            if let Some(next_idx) = link.next {
2380                                link_idx = next_idx as usize;
2381                                continue;
2382                            } else {
2383                                break;
2384                            }
2385                        }
2386                    }
2387
2388                    // Calculate cost (for now, use unit cost)
2389                    let cost = 1.0;
2390                    let total_cost = current_cost + cost;
2391
2392                    // Update or create neighbor node
2393                    let should_add_to_open = match node_pool.get_mut(&neighbor_id) {
2394                        Some(neighbor_node) => {
2395                            if neighbor_node.state == NodeState::New || total_cost < neighbor_node.g
2396                            {
2397                                neighbor_node.g = total_cost;
2398                                neighbor_node.f = total_cost;
2399                                neighbor_node.parent = Some(current_ref_id as usize);
2400                                neighbor_node.state = NodeState::Open;
2401                                true
2402                            } else {
2403                                false
2404                            }
2405                        }
2406                        None => {
2407                            // Create new node
2408                            let new_node = Node {
2409                                poly: neighbor_ref,
2410                                parent: Some(current_ref_id as usize),
2411                                g: total_cost,
2412                                h: 0.0,
2413                                f: total_cost,
2414                                state: NodeState::Open,
2415                                index: neighbor_id as usize,
2416                            };
2417                            node_pool.insert(neighbor_id, new_node);
2418                            true
2419                        }
2420                    };
2421
2422                    // Add to open list if needed
2423                    if should_add_to_open {
2424                        open_list.push(HeapNode {
2425                            index: neighbor_id as usize,
2426                            f: total_cost,
2427                        });
2428                    }
2429
2430                    // Move to next link
2431                    if let Some(next_idx) = link.next {
2432                        link_idx = next_idx as usize;
2433                    } else {
2434                        break;
2435                    }
2436                }
2437            }
2438        }
2439
2440        Ok(result_polys)
2441    }
2442
2443    /// Checks if a point is inside a polygon (2D check ignoring Y)
2444    fn is_point_in_polygon(
2445        &self,
2446        tile: &super::nav_mesh::MeshTile,
2447        poly: &super::nav_mesh::Poly,
2448        point: &[f32; 3],
2449    ) -> Result<bool, DetourError> {
2450        let mut inside = false;
2451        let px = point[0];
2452        let pz = point[2];
2453
2454        let mut j = poly.vert_count as usize - 1;
2455        for i in 0..poly.vert_count as usize {
2456            let vi_idx = poly.verts[i] as usize;
2457            let vj_idx = poly.verts[j] as usize;
2458
2459            let vix = tile.verts[vi_idx * 3];
2460            let viz = tile.verts[vi_idx * 3 + 2];
2461            let vjx = tile.verts[vj_idx * 3];
2462            let vjz = tile.verts[vj_idx * 3 + 2];
2463
2464            if ((viz > pz) != (vjz > pz)) && (px < (vjx - vix) * (pz - viz) / (vjz - viz) + vix) {
2465                inside = !inside;
2466            }
2467            j = i;
2468        }
2469
2470        Ok(inside)
2471    }
2472
2473    /// Finds the closest point on a line segment to a given point
2474    fn closest_point_on_segment(
2475        &self,
2476        seg_start: &[f32; 3],
2477        seg_end: &[f32; 3],
2478        point: &[f32; 3],
2479    ) -> [f32; 3] {
2480        let dx = seg_end[0] - seg_start[0];
2481        let dz = seg_end[2] - seg_start[2];
2482
2483        let seg_len_squared = dx * dx + dz * dz;
2484
2485        if seg_len_squared < 1e-6 {
2486            // Degenerate segment, return start point
2487            return *seg_start;
2488        }
2489
2490        let px = point[0] - seg_start[0];
2491        let pz = point[2] - seg_start[2];
2492
2493        let t = (px * dx + pz * dz) / seg_len_squared;
2494        let t_clamped = t.clamp(0.0, 1.0);
2495
2496        [
2497            seg_start[0] + dx * t_clamped,
2498            seg_start[1] + (seg_end[1] - seg_start[1]) * t_clamped,
2499            seg_start[2] + dz * t_clamped,
2500        ]
2501    }
2502
2503    /// Finds the distance to the nearest wall or obstacle from a given position
2504    pub fn find_distance_to_wall(
2505        &self,
2506        start_ref: PolyRef,
2507        center_pos: Vec3,
2508        radius: f32,
2509        filter: &QueryFilter,
2510    ) -> Result<(f32, Vec3, Vec3), DetourError> {
2511        let center_pos = center_pos.to_array();
2512        if !self.nav_mesh.is_valid_poly_ref(start_ref) {
2513            return Err(DetourError::InvalidParam);
2514        }
2515
2516        let mut min_distance = radius;
2517        let mut wall_hit = center_pos;
2518        let mut wall_normal = [0.0, 0.0, 1.0];
2519
2520        // Number of rays to cast for wall detection
2521        const NUM_RAYS: usize = 16;
2522        let angle_step = 2.0 * std::f32::consts::PI / NUM_RAYS as f32;
2523
2524        // Cast rays in multiple directions to find walls
2525        for i in 0..NUM_RAYS {
2526            let angle = i as f32 * angle_step;
2527            let ray_dir = [angle.cos(), 0.0, angle.sin()];
2528
2529            // Perform raycast to find wall intersection
2530            match self.raycast(
2531                start_ref,
2532                Vec3::from(center_pos),
2533                Vec3::from(ray_dir),
2534                radius,
2535                filter,
2536            ) {
2537                Ok((_, hit_pos_v, t)) => {
2538                    let hit_pos = hit_pos_v.to_array();
2539                    let hit_distance = t * radius;
2540
2541                    if hit_distance < min_distance {
2542                        min_distance = hit_distance;
2543                        wall_hit = hit_pos;
2544
2545                        // Calculate wall normal (pointing toward the center)
2546                        let dx = center_pos[0] - hit_pos[0];
2547                        let dz = center_pos[2] - hit_pos[2];
2548                        let len = (dx * dx + dz * dz).sqrt();
2549
2550                        if len > 1e-6 {
2551                            wall_normal = [dx / len, 0.0, dz / len];
2552                        }
2553                    }
2554                }
2555                Err(_) => {
2556                    // No wall hit in this direction within radius
2557                }
2558            }
2559        }
2560
2561        // If no walls found within radius, check polygon boundaries more carefully
2562        if min_distance >= radius {
2563            if let Ok(boundary_result) =
2564                self.find_polygon_boundary_distance(start_ref, &center_pos, radius, filter)
2565            {
2566                if boundary_result.0 < min_distance {
2567                    min_distance = boundary_result.0;
2568                    wall_hit = boundary_result.1;
2569                    wall_normal = boundary_result.2;
2570                }
2571            }
2572        }
2573
2574        Ok((min_distance, Vec3::from(wall_hit), Vec3::from(wall_normal)))
2575    }
2576
2577    /// Finds the distance to the nearest polygon boundary (more precise than raycast)
2578    fn find_polygon_boundary_distance(
2579        &self,
2580        start_ref: PolyRef,
2581        center_pos: &[f32; 3],
2582        max_radius: f32,
2583        filter: &QueryFilter,
2584    ) -> Result<(f32, [f32; 3], [f32; 3]), DetourError> {
2585        let mut min_distance = max_radius;
2586        let mut wall_hit = *center_pos;
2587        let mut wall_normal = [0.0, 0.0, 1.0];
2588
2589        let mut visited = std::collections::HashSet::new();
2590        let mut queue = std::collections::VecDeque::new();
2591
2592        // Start BFS from the current polygon
2593        queue.push_back(start_ref);
2594        visited.insert(start_ref.id());
2595
2596        while let Some(current_ref) = queue.pop_front() {
2597            let (tile, poly) = match self.nav_mesh.get_tile_and_poly_by_ref(current_ref) {
2598                Ok((tile, poly)) => (tile, poly),
2599                Err(_) => continue,
2600            };
2601
2602            if !filter.pass_filter(current_ref, tile, poly) {
2603                continue;
2604            }
2605
2606            // Check each edge of this polygon
2607            for i in 0..poly.vert_count as usize {
2608                let j = (i + 1) % poly.vert_count as usize;
2609
2610                let vert1_idx = poly.verts[i] as usize;
2611                let vert2_idx = poly.verts[j] as usize;
2612
2613                let vert1 = [
2614                    tile.verts[vert1_idx * 3],
2615                    tile.verts[vert1_idx * 3 + 1],
2616                    tile.verts[vert1_idx * 3 + 2],
2617                ];
2618                let vert2 = [
2619                    tile.verts[vert2_idx * 3],
2620                    tile.verts[vert2_idx * 3 + 1],
2621                    tile.verts[vert2_idx * 3 + 2],
2622                ];
2623
2624                // Check if this edge has a connection (not a wall)
2625                let has_connection = self.nav_mesh.find_link(tile, poly, i as u8).is_some();
2626
2627                if !has_connection {
2628                    // This is a wall edge, calculate distance
2629                    let closest_point = self.closest_point_on_segment(&vert1, &vert2, center_pos);
2630                    let dx = closest_point[0] - center_pos[0];
2631                    let dz = closest_point[2] - center_pos[2];
2632                    let distance = (dx * dx + dz * dz).sqrt();
2633
2634                    if distance < min_distance {
2635                        min_distance = distance;
2636                        wall_hit = closest_point;
2637
2638                        // Calculate wall normal
2639                        if distance > 1e-6 {
2640                            wall_normal = [dx / distance, 0.0, dz / distance];
2641                        }
2642                    }
2643                } else {
2644                    // Add connected polygon to exploration queue
2645                    if let Some(link) = self.nav_mesh.find_link(tile, poly, i as u8) {
2646                        let neighbor_ref = link.reference;
2647                        if neighbor_ref.is_valid() && !visited.contains(&neighbor_ref.id()) {
2648                            // Only explore if the neighbor is within reasonable distance
2649                            if let Ok(neighbor_center) = self.get_poly_center(neighbor_ref) {
2650                                let dx = neighbor_center[0] - center_pos[0];
2651                                let dz = neighbor_center[2] - center_pos[2];
2652                                let dist = (dx * dx + dz * dz).sqrt();
2653
2654                                if dist <= max_radius + 5.0 {
2655                                    // Small buffer for exploration
2656                                    queue.push_back(neighbor_ref);
2657                                    visited.insert(neighbor_ref.id());
2658                                }
2659                            }
2660                        }
2661                    }
2662                }
2663            }
2664        }
2665
2666        Ok((min_distance, wall_hit, wall_normal))
2667    }
2668
2669    /// Finds a random point on the navigation mesh
2670    pub fn find_random_point(
2671        &mut self,
2672        filter: &QueryFilter,
2673    ) -> Result<(PolyRef, Vec3), DetourError> {
2674        self.find_random_point_around(PolyRef::new(0), Vec3::ZERO, f32::MAX, filter)
2675    }
2676
2677    /// Finds a random point on the navigation mesh using custom random function
2678    ///
2679    /// This method matches the C++ API that accepts a function pointer for random number generation.
2680    ///
2681    /// # Arguments
2682    /// * `filter` - The polygon filter to apply to the query
2683    /// * `frand` - Function returning a random number [0..1)
2684    ///
2685    /// # Returns
2686    /// * A tuple containing the polygon reference and the random point position
2687    pub fn find_random_point_with_custom_rand<F>(
2688        &mut self,
2689        filter: &QueryFilter,
2690        frand: F,
2691    ) -> Result<(PolyRef, Vec3), DetourError>
2692    where
2693        F: FnMut() -> f32,
2694    {
2695        self.find_random_point_around_circle(PolyRef::new(0), Vec3::ZERO, f32::MAX, filter, frand)
2696    }
2697
2698    /// Gets the height of the polygon at the provided position
2699    ///
2700    /// This method provides accurate height interpolation using the detail mesh.
2701    ///
2702    /// # Arguments
2703    /// * `ref` - The reference id of the polygon
2704    /// * `pos` - A position within the xz-bounds of the polygon
2705    ///
2706    /// # Returns
2707    /// * The height at the surface of the polygon, or None if the position is outside the polygon
2708    pub fn get_poly_height(
2709        &self,
2710        poly_ref: PolyRef,
2711        pos: Vec3,
2712    ) -> Result<Option<f32>, DetourError> {
2713        let pos = pos.to_array();
2714        // Validate the polygon reference
2715        if !self.nav_mesh.is_valid_poly_ref(poly_ref) {
2716            return Err(DetourError::InvalidParam);
2717        }
2718
2719        // Get the tile and polygon index
2720        let (tile, _poly) = self.nav_mesh.get_tile_and_poly_by_ref(poly_ref)?;
2721        let poly_idx = decode_poly_id(poly_ref) as usize;
2722
2723        // Delegate to the NavMesh implementation
2724        self.nav_mesh.get_poly_height(tile, poly_idx, &pos)
2725    }
2726
2727    /// Finds a random point within a given radius of a center point using custom random function
2728    ///
2729    /// This method matches the C++ API that accepts a function pointer for random number generation.
2730    ///
2731    /// # Arguments
2732    /// * `center_ref` - The reference id of the polygon where the search starts
2733    /// * `center_pos` - The center of the search circle
2734    /// * `radius` - The radius of the search circle
2735    /// * `filter` - The polygon filter to apply to the query
2736    /// * `frand` - Function returning a random number [0..1)
2737    ///
2738    /// # Returns
2739    /// * A tuple containing the polygon reference and the random point position
2740    pub fn find_random_point_around_circle<F>(
2741        &mut self,
2742        center_ref: PolyRef,
2743        center_pos: Vec3,
2744        radius: f32,
2745        filter: &QueryFilter,
2746        mut frand: F,
2747    ) -> Result<(PolyRef, Vec3), DetourError>
2748    where
2749        F: FnMut() -> f32,
2750    {
2751        // Save the current internal random generator state
2752        let saved_seed = self.random_seed;
2753
2754        // Temporarily replace random generation with the provided function
2755        // by calling it and using it to seed our internal generator
2756        let rand_val = frand();
2757        self.random_seed = (rand_val * 2147483647.0) as u32;
2758
2759        // Call the existing implementation
2760        let result = self.find_random_point_around(center_ref, center_pos, radius, filter);
2761
2762        // Restore the original seed
2763        self.random_seed = saved_seed;
2764
2765        result
2766    }
2767
2768    /// Finds a random point within a given radius of a center point
2769    pub fn find_random_point_around(
2770        &mut self,
2771        center_ref: PolyRef,
2772        center_pos: Vec3,
2773        radius: f32,
2774        filter: &QueryFilter,
2775    ) -> Result<(PolyRef, Vec3), DetourError> {
2776        let center_pos = center_pos.to_array();
2777        // If no center is provided (invalid ref), find random point on entire mesh
2778        if !center_ref.is_valid() || !self.nav_mesh.is_valid_poly_ref(center_ref) {
2779            return self.find_random_point_on_mesh(filter);
2780        }
2781
2782        // Get all polygons within the circle area
2783        let polys_in_circle =
2784            self.find_polys_around_circle(center_ref, Vec3::from(center_pos), radius, filter)?;
2785
2786        if polys_in_circle.is_empty() {
2787            return Err(DetourError::InvalidParam);
2788        }
2789
2790        // Pick a random polygon from those within the circle
2791        let random_index = (self.random_f32() * polys_in_circle.len() as f32) as usize;
2792        let random_poly = polys_in_circle[random_index];
2793
2794        // Generate a random point within that polygon
2795        match self.get_random_point_in_polygon(random_poly) {
2796            Ok(pos) => {
2797                // Double-check that the generated point is within the radius
2798                let dx = pos[0] - center_pos[0];
2799                let dz = pos[2] - center_pos[2];
2800                let distance = (dx * dx + dz * dz).sqrt();
2801
2802                if distance <= radius {
2803                    Ok((random_poly, Vec3::from(pos)))
2804                } else {
2805                    // Fall back to polygon center if random point is outside radius
2806                    match self.get_poly_center(random_poly) {
2807                        Ok(center) => Ok((random_poly, center)),
2808                        Err(e) => Err(e),
2809                    }
2810                }
2811            }
2812            Err(_) => {
2813                // Fallback: get polygon center
2814                match self.get_poly_center(random_poly) {
2815                    Ok(center) => Ok((random_poly, center)),
2816                    Err(e) => Err(e),
2817                }
2818            }
2819        }
2820    }
2821
2822    /// Finds a random point anywhere on the navigation mesh
2823    fn find_random_point_on_mesh(
2824        &mut self,
2825        filter: &QueryFilter,
2826    ) -> Result<(PolyRef, Vec3), DetourError> {
2827        // Get all valid polygons that pass the filter
2828        let valid_polys = self.get_all_valid_polygons(filter)?;
2829
2830        if valid_polys.is_empty() {
2831            return Err(DetourError::InvalidParam);
2832        }
2833
2834        // Pick a random polygon
2835        let random_index = (self.random_f32() * valid_polys.len() as f32) as usize;
2836        let random_poly = valid_polys[random_index];
2837
2838        // Generate random point within that polygon
2839        match self.get_random_point_in_polygon(random_poly) {
2840            Ok(pos) => Ok((random_poly, Vec3::from(pos))),
2841            Err(_) => {
2842                // Fallback: get polygon center
2843                match self.get_poly_center(random_poly) {
2844                    Ok(center) => Ok((random_poly, center)),
2845                    Err(e) => Err(e),
2846                }
2847            }
2848        }
2849    }
2850
2851    /// Gets all valid polygons that pass the filter
2852    fn get_all_valid_polygons(&self, filter: &QueryFilter) -> Result<Vec<PolyRef>, DetourError> {
2853        let mut valid_polys = Vec::new();
2854
2855        // Use a much more efficient approach: get polygons from a large search area
2856        // Start from origin and search in a very large area to find all polygons
2857
2858        // Try to find a polygon anywhere on the mesh first
2859        if let Ok(start_polys) = self.nav_mesh.query_polygons(
2860            &[-1000.0, -1000.0, -1000.0],
2861            &[1000.0, 1000.0, 1000.0],
2862            filter,
2863        ) {
2864            // We found some polygons, add them all (query_polygons already filters by the filter)
2865            valid_polys.extend(start_polys);
2866        }
2867
2868        // If that didn't work, fall back to the brute force approach but with limits
2869        if valid_polys.is_empty() {
2870            // Try a smaller search space first
2871            for tile_id in 0..10.min(self.nav_mesh.get_max_tiles()) {
2872                for poly_id in 0..100.min(self.nav_mesh.get_max_polys_per_tile()) {
2873                    let poly_ref = super::nav_mesh::encode_poly_ref(tile_id as u32, poly_id as u32);
2874
2875                    // Check if this polygon reference is valid
2876                    if self.nav_mesh.is_valid_poly_ref(poly_ref) {
2877                        // Get the tile and polygon data
2878                        if let Ok((tile, poly)) = self.nav_mesh.get_tile_and_poly_by_ref(poly_ref) {
2879                            // Check if polygon passes filter
2880                            if filter.pass_filter(poly_ref, tile, poly) {
2881                                valid_polys.push(poly_ref);
2882                            }
2883                        }
2884                    }
2885                }
2886            }
2887        }
2888
2889        Ok(valid_polys)
2890    }
2891
2892    /// Generates a random point within a specific polygon
2893    fn get_random_point_in_polygon(&mut self, poly_ref: PolyRef) -> Result<[f32; 3], DetourError> {
2894        let (tile, poly) = self.nav_mesh.get_tile_and_poly_by_ref(poly_ref)?;
2895
2896        if poly.vert_count < 3 {
2897            return Err(DetourError::InvalidParam);
2898        }
2899
2900        // Get polygon vertices
2901        let mut verts = Vec::with_capacity(poly.vert_count as usize);
2902        for i in 0..poly.vert_count as usize {
2903            let vert_idx = poly.verts[i] as usize;
2904            verts.push([
2905                tile.verts[vert_idx * 3],
2906                tile.verts[vert_idx * 3 + 1],
2907                tile.verts[vert_idx * 3 + 2],
2908            ]);
2909        }
2910
2911        // Use triangle sampling for polygons with more than 3 vertices
2912        if poly.vert_count == 3 {
2913            // Simple triangle sampling
2914            Ok(self.random_point_in_triangle(&verts[0], &verts[1], &verts[2]))
2915        } else {
2916            // For polygons with more vertices, triangulate and pick a random triangle
2917            let triangle_count = poly.vert_count - 2;
2918            let random_triangle = (self.random_f32() * triangle_count as f32) as usize;
2919
2920            // Create triangle from vertex 0 and two consecutive vertices
2921            let v1_idx = 1 + random_triangle;
2922            let v2_idx = 2 + random_triangle;
2923
2924            if v1_idx < verts.len() && v2_idx < verts.len() {
2925                Ok(self.random_point_in_triangle(&verts[0], &verts[v1_idx], &verts[v2_idx]))
2926            } else {
2927                // Fallback to first triangle
2928                Ok(self.random_point_in_triangle(&verts[0], &verts[1], &verts[2]))
2929            }
2930        }
2931    }
2932
2933    /// Generates a random point within a triangle using barycentric coordinates
2934    fn random_point_in_triangle(&mut self, a: &[f32; 3], b: &[f32; 3], c: &[f32; 3]) -> [f32; 3] {
2935        let r1 = self.random_f32();
2936        let r2 = self.random_f32();
2937
2938        // Ensure uniform distribution in triangle
2939        let sqrt_r1 = r1.sqrt();
2940        let u = 1.0 - sqrt_r1;
2941        let v = r2 * sqrt_r1;
2942        let w = 1.0 - u - v;
2943
2944        [
2945            a[0] * u + b[0] * v + c[0] * w,
2946            a[1] * u + b[1] * v + c[1] * w,
2947            a[2] * u + b[2] * v + c[2] * w,
2948        ]
2949    }
2950
2951    /// Finds the non-overlapping navigation polygons in the local neighbourhood around the center position
2952    pub fn find_local_neighbourhood(
2953        &self,
2954        start_ref: PolyRef,
2955        center_pos: Vec3,
2956        radius: f32,
2957        filter: &QueryFilter,
2958        max_result: usize,
2959    ) -> Result<(Vec<PolyRef>, Vec<PolyRef>), DetourError> {
2960        let center_pos = center_pos.to_array();
2961        if !self.nav_mesh.is_valid_poly_ref(start_ref) || radius < 0.0 || max_result == 0 {
2962            return Err(DetourError::InvalidParam);
2963        }
2964
2965        let mut result_refs = Vec::new();
2966        let mut result_parents = Vec::new();
2967        let mut stack = Vec::new();
2968        let mut visited = std::collections::HashSet::new();
2969
2970        let radius_sqr = radius * radius;
2971        const MAX_STACK_SIZE: usize = 48;
2972
2973        // Start with the initial polygon
2974        stack.push(start_ref);
2975        visited.insert(start_ref.id());
2976
2977        // Add start polygon to results
2978        result_refs.push(start_ref);
2979        result_parents.push(PolyRef::new(0)); // Start polygon has no parent
2980
2981        while !stack.is_empty() && stack.len() < MAX_STACK_SIZE {
2982            let current_ref = stack.remove(0); // Pop front for breadth-first search
2983
2984            // Get current polygon data
2985            let (current_tile, current_poly) =
2986                self.nav_mesh.get_tile_and_poly_by_ref(current_ref)?;
2987
2988            // Explore all neighbors
2989            for i in 0..current_poly.vert_count as usize {
2990                if let Some(link) = self.nav_mesh.find_link(current_tile, current_poly, i as u8) {
2991                    let neighbor_ref = link.reference;
2992
2993                    // Skip invalid neighbors
2994                    if !neighbor_ref.is_valid() {
2995                        continue;
2996                    }
2997
2998                    // Skip already visited neighbors
2999                    if visited.contains(&neighbor_ref.id()) {
3000                        continue;
3001                    }
3002
3003                    // Get neighbor polygon data
3004                    let (neighbor_tile, neighbor_poly) =
3005                        match self.nav_mesh.get_tile_and_poly_by_ref(neighbor_ref) {
3006                            Ok((tile, poly)) => (tile, poly),
3007                            Err(_) => continue,
3008                        };
3009
3010                    // Skip off-mesh connections
3011                    if neighbor_poly.poly_type == PolyType::OffMeshConnection {
3012                        continue;
3013                    }
3014
3015                    // Check if neighbor passes filter
3016                    if !filter.pass_filter(neighbor_ref, neighbor_tile, neighbor_poly) {
3017                        continue;
3018                    }
3019
3020                    // Get edge vertices between current and neighbor
3021                    let (va_v, vb_v) = match self.get_portal_points(current_ref, neighbor_ref) {
3022                        Ok((a, b)) => (a, b),
3023                        Err(_) => continue,
3024                    };
3025                    let va = va_v.to_array();
3026                    let vb = vb_v.to_array();
3027
3028                    // Check if the circle (radius around center) touches this edge
3029                    let closest_on_edge = self.closest_point_on_segment(&va, &vb, &center_pos);
3030                    let dist_sqr = {
3031                        let dx = center_pos[0] - closest_on_edge[0];
3032                        let dy = center_pos[1] - closest_on_edge[1];
3033                        let dz = center_pos[2] - closest_on_edge[2];
3034                        dx * dx + dy * dy + dz * dz
3035                    };
3036
3037                    // Skip if edge is too far from the search radius
3038                    if dist_sqr > radius_sqr {
3039                        continue;
3040                    }
3041
3042                    // Mark as visited early to avoid revisiting
3043                    visited.insert(neighbor_ref.id());
3044
3045                    // Check for polygon overlap with existing result polygons
3046                    let neighbor_verts = self.get_polygon_vertices(neighbor_tile, neighbor_poly)?;
3047                    let mut overlaps = false;
3048
3049                    for &existing_ref in &result_refs {
3050                        // Skip overlap check for connected polygons
3051                        if self.are_polygons_connected(current_ref, existing_ref)? {
3052                            continue;
3053                        }
3054
3055                        // Get existing polygon vertices
3056                        let (existing_tile, existing_poly) =
3057                            self.nav_mesh.get_tile_and_poly_by_ref(existing_ref)?;
3058                        let existing_verts =
3059                            self.get_polygon_vertices(existing_tile, existing_poly)?;
3060
3061                        // Check for 2D polygon overlap
3062                        if self.polygons_overlap_2d(&neighbor_verts, &existing_verts) {
3063                            overlaps = true;
3064                            break;
3065                        }
3066                    }
3067
3068                    // Skip if this polygon overlaps with existing ones
3069                    if overlaps {
3070                        continue;
3071                    }
3072
3073                    // Add to results if we have space
3074                    if result_refs.len() < max_result {
3075                        result_refs.push(neighbor_ref);
3076                        result_parents.push(current_ref);
3077                    }
3078
3079                    // Add to stack for further exploration
3080                    if stack.len() < MAX_STACK_SIZE {
3081                        stack.push(neighbor_ref);
3082                    }
3083                }
3084            }
3085        }
3086
3087        Ok((result_refs, result_parents))
3088    }
3089
3090    /// Gets the vertices of a polygon as a flat array
3091    fn get_polygon_vertices(
3092        &self,
3093        tile: &super::nav_mesh::MeshTile,
3094        poly: &super::nav_mesh::Poly,
3095    ) -> Result<Vec<[f32; 3]>, DetourError> {
3096        let mut vertices = Vec::new();
3097
3098        for i in 0..poly.vert_count as usize {
3099            let vert_idx = poly.verts[i] as usize;
3100            vertices.push([
3101                tile.verts[vert_idx * 3],
3102                tile.verts[vert_idx * 3 + 1],
3103                tile.verts[vert_idx * 3 + 2],
3104            ]);
3105        }
3106
3107        Ok(vertices)
3108    }
3109
3110    /// Checks if two polygons are directly connected by a link
3111    fn are_polygons_connected(
3112        &self,
3113        poly_a: PolyRef,
3114        poly_b: PolyRef,
3115    ) -> Result<bool, DetourError> {
3116        let (tile_a, poly_a_data) = self.nav_mesh.get_tile_and_poly_by_ref(poly_a)?;
3117
3118        // Check if poly_a has a link to poly_b
3119        for i in 0..poly_a_data.vert_count as usize {
3120            if let Some(link) = self.nav_mesh.find_link(tile_a, poly_a_data, i as u8) {
3121                if link.reference == poly_b {
3122                    return Ok(true);
3123                }
3124            }
3125        }
3126
3127        Ok(false)
3128    }
3129
3130    /// Checks if two polygons overlap in 2D (ignoring Y axis)
3131    fn polygons_overlap_2d(&self, poly_a: &[[f32; 3]], poly_b: &[[f32; 3]]) -> bool {
3132        // Convert to 2D points for overlap test
3133        let poly_a_2d: Vec<[f32; 2]> = poly_a.iter().map(|v| [v[0], v[2]]).collect();
3134        let poly_b_2d: Vec<[f32; 2]> = poly_b.iter().map(|v| [v[0], v[2]]).collect();
3135
3136        // Use Separating Axis Theorem (SAT) for convex polygon overlap
3137        self.convex_polygons_overlap_2d(&poly_a_2d, &poly_b_2d)
3138    }
3139
3140    /// Checks if two convex polygons overlap using Separating Axis Theorem
3141    fn convex_polygons_overlap_2d(&self, poly_a: &[[f32; 2]], poly_b: &[[f32; 2]]) -> bool {
3142        // Check separating axes from polygon A
3143        for i in 0..poly_a.len() {
3144            let j = (i + 1) % poly_a.len();
3145            let edge = [poly_a[j][0] - poly_a[i][0], poly_a[j][1] - poly_a[i][1]];
3146            let normal = [-edge[1], edge[0]]; // Perpendicular to edge
3147
3148            if self.polygons_separated_by_axis(&normal, poly_a, poly_b) {
3149                return false; // Separating axis found
3150            }
3151        }
3152
3153        // Check separating axes from polygon B
3154        for i in 0..poly_b.len() {
3155            let j = (i + 1) % poly_b.len();
3156            let edge = [poly_b[j][0] - poly_b[i][0], poly_b[j][1] - poly_b[i][1]];
3157            let normal = [-edge[1], edge[0]]; // Perpendicular to edge
3158
3159            if self.polygons_separated_by_axis(&normal, poly_a, poly_b) {
3160                return false; // Separating axis found
3161            }
3162        }
3163
3164        true // No separating axis found, polygons overlap
3165    }
3166
3167    /// Checks if two polygons are separated by a given axis
3168    fn polygons_separated_by_axis(
3169        &self,
3170        axis: &[f32; 2],
3171        poly_a: &[[f32; 2]],
3172        poly_b: &[[f32; 2]],
3173    ) -> bool {
3174        // Project polygon A onto the axis
3175        let mut min_a = f32::MAX;
3176        let mut max_a = f32::MIN;
3177        for vertex in poly_a {
3178            let projection = vertex[0] * axis[0] + vertex[1] * axis[1];
3179            min_a = min_a.min(projection);
3180            max_a = max_a.max(projection);
3181        }
3182
3183        // Project polygon B onto the axis
3184        let mut min_b = f32::MAX;
3185        let mut max_b = f32::MIN;
3186        for vertex in poly_b {
3187            let projection = vertex[0] * axis[0] + vertex[1] * axis[1];
3188            min_b = min_b.min(projection);
3189            max_b = max_b.max(projection);
3190        }
3191
3192        // Check if projections are separated
3193        max_a < min_b || max_b < min_a
3194    }
3195
3196    /// Initializes a sliced pathfinding query for large-scale pathfinding
3197    ///
3198    /// This method starts a pathfinding request that can be processed over multiple frames
3199    /// to avoid performance hitches with very long paths.
3200    ///
3201    /// # Arguments
3202    /// * `start_ref` - Start polygon reference
3203    /// * `end_ref` - End polygon reference
3204    /// * `start_pos` - Start position in world coordinates
3205    /// * `end_pos` - End position in world coordinates
3206    /// * `filter` - Query filter for polygon selection
3207    ///
3208    /// # Returns
3209    /// Result indicating success or failure of initialization
3210    pub fn init_sliced_find_path(
3211        &mut self,
3212        start_ref: PolyRef,
3213        end_ref: PolyRef,
3214        start_pos: Vec3,
3215        end_pos: Vec3,
3216        filter: &QueryFilter,
3217        options: u32,
3218    ) -> Result<(), DetourError> {
3219        let start_pos = start_pos.to_array();
3220        let end_pos = end_pos.to_array();
3221        if !self.nav_mesh.is_valid_poly_ref(start_ref) || !self.nav_mesh.is_valid_poly_ref(end_ref)
3222        {
3223            return Err(DetourError::InvalidParam);
3224        }
3225
3226        // Initialize sliced pathfinding state
3227        self.sliced_state.active = true;
3228        self.sliced_state.state = if start_ref == end_ref {
3229            SlicedPathState::Success
3230        } else {
3231            SlicedPathState::InProgress
3232        };
3233        self.sliced_state.start_ref = start_ref;
3234        self.sliced_state.end_ref = end_ref;
3235        self.sliced_state.start_pos = start_pos;
3236        self.sliced_state.end_pos = end_pos;
3237        self.sliced_state.filter = filter.clone();
3238        self.sliced_state.current_path.clear();
3239        self.sliced_state.best_node_idx = 0;
3240        self.sliced_state.best_node_cost = f32::MAX;
3241
3242        // If start and end are the same, we're done immediately
3243        if start_ref == end_ref {
3244            self.sliced_state.current_path.push(start_ref);
3245            return Ok(());
3246        }
3247
3248        for node in &mut self.node_pool {
3249            node.parent = None;
3250            node.state = NodeState::New;
3251            node.poly = PolyRef::new(0);
3252            node.g = 0.0;
3253            node.h = 0.0;
3254            node.f = 0.0;
3255        }
3256        self.open_list.clear();
3257
3258        let start_h = self.heuristic(&start_pos, &end_pos);
3259        let start_node = &mut self.node_pool[0];
3260        start_node.poly = start_ref;
3261        start_node.state = NodeState::Open;
3262        start_node.g = 0.0;
3263        start_node.h = start_h;
3264        start_node.f = start_h;
3265
3266        // Push start node to open list
3267        self.open_list.push(HeapNode {
3268            index: 0,
3269            f: start_h,
3270        });
3271
3272        // Set goal node
3273        self.node_pool[1].poly = end_ref;
3274
3275        // Initialize best node tracking
3276        self.sliced_state.best_node_idx = 0;
3277        self.sliced_state.best_node_cost = start_h;
3278
3279        // Handle DT_FINDPATH_ANY_ANGLE option
3280        const DT_FINDPATH_ANY_ANGLE: u32 = 0x02;
3281        if options & DT_FINDPATH_ANY_ANGLE != 0 {
3282            // TODO: Implement any-angle pathfinding with raycasts
3283            // For now, just use regular pathfinding
3284        }
3285
3286        Ok(())
3287    }
3288
3289    /// Initializes a sliced path query with default options
3290    ///
3291    /// This is a convenience method that calls init_sliced_find_path with options = 0
3292    /// to match the C++ API default parameter behavior.
3293    ///
3294    /// # Arguments
3295    /// * `start_ref` - The reference id of the start polygon
3296    /// * `end_ref` - The reference id of the end polygon  
3297    /// * `start_pos` - A position within the start polygon
3298    /// * `end_pos` - A position within the end polygon
3299    /// * `filter` - The polygon filter to apply to the query
3300    pub fn init_sliced_find_path_default(
3301        &mut self,
3302        start_ref: PolyRef,
3303        end_ref: PolyRef,
3304        start_pos: Vec3,
3305        end_pos: Vec3,
3306        filter: &QueryFilter,
3307    ) -> Result<(), DetourError> {
3308        self.init_sliced_find_path(start_ref, end_ref, start_pos, end_pos, filter, 0)
3309    }
3310
3311    /// Updates the sliced pathfinding query for a limited number of iterations
3312    ///
3313    /// This method continues processing the pathfinding request. It should be called
3314    /// each frame until it returns a state other than InProgress.
3315    ///
3316    /// # Arguments
3317    /// * `max_iterations` - Maximum number of iterations to process this frame
3318    ///
3319    /// # Returns
3320    /// The current state of the pathfinding (InProgress, Success, Failed, PartialPath)
3321    pub fn update_sliced_find_path(
3322        &mut self,
3323        max_iter: i32,
3324    ) -> Result<(i32, SlicedPathState), DetourError> {
3325        if !self.sliced_state.active {
3326            return Err(DetourError::InvalidParam);
3327        }
3328
3329        if self.sliced_state.state != SlicedPathState::InProgress {
3330            return Ok((0, self.sliced_state.state));
3331        }
3332
3333        let mut iter_count = 0i32;
3334
3335        while iter_count < max_iter {
3336            // Get the node with the lowest f cost
3337            let Some(HeapNode {
3338                index: current_idx, ..
3339            }) = self.open_list.pop()
3340            else {
3341                break;
3342            };
3343
3344            // Mark as closed
3345            self.node_pool[current_idx].state = NodeState::Closed;
3346
3347            let current_poly = self.node_pool[current_idx].poly;
3348
3349            // Check if we reached the goal
3350            if current_poly == self.sliced_state.end_ref {
3351                self.sliced_state.state = SlicedPathState::Success;
3352                self.sliced_state.current_path = self.reconstruct_path(current_idx)?;
3353                return Ok((iter_count + 1, SlicedPathState::Success));
3354            }
3355
3356            // Update best node for partial path
3357            let current_cost = self.node_pool[current_idx].h;
3358            if current_cost < self.sliced_state.best_node_cost {
3359                self.sliced_state.best_node_idx = current_idx;
3360                self.sliced_state.best_node_cost = current_cost;
3361            }
3362
3363            // Expand neighbors
3364            self.expand_neighbors_sliced(current_idx)?;
3365
3366            iter_count += 1;
3367        }
3368
3369        // Check if we exhausted the open list
3370        if self.open_list.is_empty() {
3371            self.sliced_state.state = SlicedPathState::PartialPath;
3372            self.sliced_state.current_path =
3373                self.reconstruct_path(self.sliced_state.best_node_idx)?;
3374        }
3375
3376        Ok((iter_count, self.sliced_state.state))
3377    }
3378
3379    /// Finalizes the sliced pathfinding and returns the computed path
3380    ///
3381    /// This method should be called after update_sliced_find_path returns Success
3382    /// or PartialPath to retrieve the final path result.
3383    ///
3384    /// # Returns
3385    /// The computed path as a vector of polygon references
3386    pub fn finalize_sliced_find_path(
3387        &mut self,
3388        max_path: usize,
3389    ) -> Result<Vec<PolyRef>, DetourError> {
3390        if !self.sliced_state.active {
3391            return Err(DetourError::InvalidParam);
3392        }
3393
3394        let result = match self.sliced_state.state {
3395            SlicedPathState::Success | SlicedPathState::PartialPath => {
3396                let mut path = self.sliced_state.current_path.clone();
3397                if path.len() > max_path {
3398                    path.truncate(max_path);
3399                }
3400                Ok(path)
3401            }
3402            SlicedPathState::InProgress => Err(DetourError::InProgress),
3403            SlicedPathState::Failed => Err(DetourError::PathNotFound),
3404        };
3405
3406        // Clear the sliced state
3407        self.sliced_state.active = false;
3408        self.sliced_state.current_path.clear();
3409
3410        result
3411    }
3412
3413    /// Finalizes a sliced pathfinding query with a partial path
3414    ///
3415    /// This method is used to retrieve a partial path when the pathfinding
3416    /// cannot reach the goal but has explored some nodes. It attempts to
3417    /// find the furthest node along the existing path that was visited.
3418    ///
3419    /// # Arguments
3420    /// * `existing` - An existing path to try to extend from
3421    /// * `max_path` - Maximum number of polygons in the result path
3422    ///
3423    /// # Returns
3424    /// The computed partial path as a vector of polygon references
3425    pub fn finalize_sliced_find_path_partial(
3426        &mut self,
3427        existing: &[PolyRef],
3428        max_path: usize,
3429    ) -> Result<Vec<PolyRef>, DetourError> {
3430        if !self.sliced_state.active {
3431            return Err(DetourError::InvalidParam);
3432        }
3433
3434        if existing.is_empty() || max_path == 0 {
3435            return Err(DetourError::InvalidParam);
3436        }
3437
3438        // Check if the query has failed
3439        if self.sliced_state.state == SlicedPathState::Failed {
3440            self.sliced_state.active = false;
3441            return Err(DetourError::PathNotFound);
3442        }
3443
3444        let mut path = Vec::with_capacity(max_path);
3445
3446        // Special case: start and end are the same
3447        if self.sliced_state.start_ref == self.sliced_state.end_ref {
3448            path.push(self.sliced_state.start_ref);
3449        } else {
3450            // Find furthest existing node that was visited
3451            let mut found_node_idx = None;
3452
3453            // Search from the end of the existing path backwards
3454            for &poly_ref in existing.iter().rev() {
3455                for (idx, node) in self.node_pool.iter().enumerate() {
3456                    if node.poly == poly_ref && node.state != NodeState::New {
3457                        found_node_idx = Some(idx);
3458                        break;
3459                    }
3460                }
3461                if found_node_idx.is_some() {
3462                    break;
3463                }
3464            }
3465
3466            // If no node from existing path was found, use the best node
3467            let node_idx = found_node_idx.unwrap_or(self.sliced_state.best_node_idx);
3468
3469            // Build the path by following parent links
3470            let mut current_idx = node_idx;
3471            let mut reverse_path = Vec::new();
3472
3473            while current_idx < self.node_pool.len() {
3474                let node = &self.node_pool[current_idx];
3475                reverse_path.push(node.poly);
3476
3477                if let Some(parent_idx) = node.parent {
3478                    current_idx = parent_idx;
3479                } else {
3480                    break;
3481                }
3482
3483                // Prevent infinite loops
3484                if reverse_path.len() >= max_path {
3485                    break;
3486                }
3487            }
3488
3489            // Reverse the path to get start->end order
3490            path.extend(reverse_path.iter().rev().copied());
3491
3492            // Truncate if necessary
3493            if path.len() > max_path {
3494                path.truncate(max_path);
3495            }
3496        }
3497
3498        // Clear the sliced state
3499        self.sliced_state.active = false;
3500        self.sliced_state.current_path.clear();
3501
3502        Ok(path)
3503    }
3504
3505    /// Gets the current state of the sliced pathfinding query
3506    ///
3507    /// # Returns
3508    /// The current pathfinding state, or None if no sliced query is active
3509    pub fn get_sliced_path_state(&self) -> Option<SlicedPathState> {
3510        if self.sliced_state.active {
3511            Some(self.sliced_state.state)
3512        } else {
3513            None
3514        }
3515    }
3516
3517    /// Cancels the current sliced pathfinding query
3518    pub fn cancel_sliced_find_path(&mut self) {
3519        self.sliced_state.active = false;
3520        self.sliced_state.current_path.clear();
3521    }
3522
3523    /// Expands neighbors for sliced pathfinding (helper method)
3524    fn expand_neighbors_sliced(&mut self, current_idx: usize) -> Result<(), DetourError> {
3525        let current_poly = self.node_pool[current_idx].poly;
3526        let current_g = self.node_pool[current_idx].g;
3527
3528        // Get the tile and polygon for the current node
3529        let (tile, poly) = self.nav_mesh.get_tile_and_poly_by_ref(current_poly)?;
3530
3531        // Iterate through all links/neighbors
3532        if let Some(first_link) = poly.first_link {
3533            for i in 0..poly.vert_count {
3534                let link_idx = first_link + i as usize;
3535                if link_idx >= tile.links.len() {
3536                    continue;
3537                }
3538
3539                let link = &tile.links[link_idx];
3540                if !link.reference.is_valid() {
3541                    continue;
3542                }
3543
3544                let neighbor_ref = link.reference;
3545
3546                // Check if neighbor passes filter
3547                let (neighbor_tile, neighbor_poly) =
3548                    match self.nav_mesh.get_tile_and_poly_by_ref(neighbor_ref) {
3549                        Ok((tile, poly)) => (tile, poly),
3550                        Err(_) => continue, // Skip invalid neighbor
3551                    };
3552
3553                if !self
3554                    .sliced_state
3555                    .filter
3556                    .pass_filter(neighbor_ref, neighbor_tile, neighbor_poly)
3557                {
3558                    continue;
3559                }
3560
3561                // Find or create node for neighbor
3562                let neighbor_idx = self.find_or_create_node(neighbor_ref)?;
3563                let neighbor_node = &self.node_pool[neighbor_idx];
3564
3565                // Skip if already closed
3566                if neighbor_node.state == NodeState::Closed {
3567                    continue;
3568                }
3569
3570                // Calculate movement cost - simplified version for sliced pathfinding
3571                let movement_cost = 1.0; // Simple cost for now, could be improved
3572                let tentative_g = current_g + movement_cost;
3573
3574                // If not in open list or this path is better
3575                if neighbor_node.state == NodeState::New || tentative_g < neighbor_node.g {
3576                    let neighbor_pos = self.get_poly_center(neighbor_ref)?.to_array();
3577                    let h = self.heuristic(&neighbor_pos, &self.sliced_state.end_pos);
3578
3579                    let neighbor_node = &mut self.node_pool[neighbor_idx];
3580                    neighbor_node.parent = Some(current_idx);
3581                    neighbor_node.g = tentative_g;
3582                    neighbor_node.h = h;
3583                    neighbor_node.f = tentative_g + h;
3584
3585                    if neighbor_node.state == NodeState::New {
3586                        neighbor_node.state = NodeState::Open;
3587                        self.open_list.push(HeapNode {
3588                            index: neighbor_idx,
3589                            f: neighbor_node.f,
3590                        });
3591                    }
3592                }
3593            }
3594        }
3595
3596        Ok(())
3597    }
3598
3599    /// Finds or creates a node for the given polygon reference
3600    fn find_or_create_node(&mut self, poly_ref: PolyRef) -> Result<usize, DetourError> {
3601        // Look for existing node
3602        for (i, node) in self.node_pool.iter().enumerate() {
3603            if node.poly == poly_ref {
3604                return Ok(i);
3605            }
3606        }
3607
3608        // Find unused node
3609        for (i, node) in self.node_pool.iter().enumerate() {
3610            if node.state == NodeState::New && !node.poly.is_valid() {
3611                let node = &mut self.node_pool[i];
3612                node.poly = poly_ref;
3613                return Ok(i);
3614            }
3615        }
3616
3617        // No free nodes available
3618        Err(DetourError::OutOfMemory)
3619    }
3620
3621    /// Reconstructs the path from the goal node back to the start
3622    fn reconstruct_path(&self, goal_idx: usize) -> Result<Vec<PolyRef>, DetourError> {
3623        let mut path = Vec::new();
3624        let mut current_idx = goal_idx;
3625
3626        loop {
3627            let node = &self.node_pool[current_idx];
3628            path.push(node.poly);
3629
3630            if let Some(parent_idx) = node.parent {
3631                current_idx = parent_idx;
3632            } else {
3633                break;
3634            }
3635        }
3636
3637        path.reverse();
3638        Ok(path)
3639    }
3640
3641    /// Gets the wall segments of a polygon
3642    ///
3643    /// Wall segments are edges of the polygon that don't connect to other walkable polygons.
3644    /// Each segment is represented by two 3D points [start_x, start_y, start_z, end_x, end_y, end_z].
3645    ///
3646    /// # Arguments
3647    /// * `poly_ref` - The polygon reference to get wall segments for
3648    /// * `filter` - Query filter to determine which polygons are walkable
3649    /// * `max_segments` - Maximum number of segments to return
3650    ///
3651    /// # Returns
3652    /// Vector of wall segments, where each segment is [start_x, start_y, start_z, end_x, end_y, end_z]
3653    /// Finds polygons that overlap the search box
3654    ///
3655    /// # Arguments
3656    ///
3657    /// * `center` - The center of the search box
3658    /// * `half_extents` - The search distance along each axis
3659    /// * `filter` - The polygon filter to apply to the query
3660    ///
3661    /// # Returns
3662    ///
3663    /// A vector of polygon references that overlap the query box
3664    ///
3665    /// Finds the polygons along the navigation graph that touch the specified convex polygon
3666    ///
3667    /// # Arguments
3668    ///
3669    /// * `start_ref` - The reference id of the polygon where the search starts
3670    /// * `verts` - The vertices describing the convex polygon (CCW). [(x, y, z) * nverts]
3671    /// * `filter` - The polygon filter to apply to the query
3672    ///
3673    /// # Returns
3674    ///
3675    /// A vector of tuples containing:
3676    /// - The polygon reference
3677    /// - The parent polygon reference (None if it's the start polygon)
3678    /// - The search cost from the centroid to the polygon
3679    pub fn find_polys_around_shape(
3680        &mut self,
3681        start_ref: PolyRef,
3682        verts: &[Vec3],
3683        filter: &QueryFilter,
3684    ) -> Result<Vec<(PolyRef, Option<PolyRef>, f32)>, DetourError> {
3685        use landmark_common::intersect_segment_poly_2d;
3686
3687        if !self.nav_mesh.is_valid_poly_ref(start_ref) {
3688            return Err(DetourError::InvalidParam);
3689        }
3690
3691        if verts.len() < 3 {
3692            return Err(DetourError::InvalidParam);
3693        }
3694
3695        // Calculate the center of the shape
3696        let mut center = [0.0, 0.0, 0.0];
3697        for vert in verts {
3698            let v = vert.to_array();
3699            center[0] += v[0];
3700            center[1] += v[1];
3701            center[2] += v[2];
3702        }
3703        let nverts = verts.len() as f32;
3704        center[0] /= nverts;
3705        center[1] /= nverts;
3706        center[2] /= nverts;
3707
3708        // Flatten verts to a single array for intersect_segment_poly_2d
3709        let mut flat_verts = Vec::with_capacity(verts.len() * 3);
3710        for vert in verts {
3711            let v = vert.to_array();
3712            flat_verts.push(v[0]);
3713            flat_verts.push(v[1]);
3714            flat_verts.push(v[2]);
3715        }
3716
3717        // Initialize search
3718        self.node_pool.clear();
3719        self.open_list.clear();
3720
3721        let start_node = Node::new(start_ref, 0);
3722        self.node_pool.push(start_node);
3723        self.node_pool[0].g = 0.0;
3724        self.node_pool[0].f = 0.0;
3725        self.node_pool[0].state = NodeState::Open;
3726        self.open_list.push(HeapNode { index: 0, f: 0.0 });
3727
3728        let mut result = Vec::new();
3729
3730        while let Some(heap_node) = self.open_list.pop() {
3731            let best_idx = heap_node.index;
3732            if best_idx >= self.node_pool.len() {
3733                continue;
3734            }
3735
3736            // Update node state
3737            self.node_pool[best_idx].state = NodeState::Closed;
3738
3739            let best_ref = self.node_pool[best_idx].poly;
3740            let best_cost = self.node_pool[best_idx].g;
3741
3742            // Get poly and tile
3743            let (best_tile, best_poly) = match self.nav_mesh.get_tile_and_poly_by_ref(best_ref) {
3744                Ok(val) => val,
3745                Err(_) => continue,
3746            };
3747
3748            // Get parent reference
3749            let parent_ref = self.node_pool[best_idx]
3750                .parent
3751                .map(|idx| self.node_pool[idx].poly);
3752
3753            // Add to results
3754            result.push((best_ref, parent_ref, best_cost));
3755
3756            // Process neighbors
3757            for i in 0..best_poly.vert_count as usize {
3758                let link = match self.nav_mesh.find_link(best_tile, best_poly, i as u8) {
3759                    Some(l) => l,
3760                    None => continue,
3761                };
3762
3763                let neighbor_ref = link.reference;
3764
3765                // Skip invalid neighbors and back to parent
3766                if !neighbor_ref.is_valid() || Some(neighbor_ref) == parent_ref {
3767                    continue;
3768                }
3769
3770                // Get neighbor tile and poly
3771                let (neighbor_tile, neighbor_poly) =
3772                    match self.nav_mesh.get_tile_and_poly_by_ref(neighbor_ref) {
3773                        Ok(val) => val,
3774                        Err(_) => continue,
3775                    };
3776
3777                // Check filter
3778                if !filter.pass_filter(neighbor_ref, neighbor_tile, neighbor_poly) {
3779                    continue;
3780                }
3781
3782                // Get portal points
3783                let (va_v, vb_v) = match self.get_portal_points(best_ref, neighbor_ref) {
3784                    Ok(val) => val,
3785                    Err(_) => continue,
3786                };
3787                let va = va_v.to_array();
3788                let vb = vb_v.to_array();
3789
3790                // Check if the edge intersects the shape
3791                match intersect_segment_poly_2d(&va, &vb, &flat_verts, verts.len()) {
3792                    Some((tmin, tmax, _, _)) => {
3793                        // Check if the intersection is valid
3794                        if tmin > 1.0 || tmax < 0.0 {
3795                            continue;
3796                        }
3797                    }
3798                    None => continue,
3799                }
3800
3801                // Calculate position on edge (midpoint)
3802                let edge_mid = [
3803                    (va[0] + vb[0]) * 0.5,
3804                    (va[1] + vb[1]) * 0.5,
3805                    (va[2] + vb[2]) * 0.5,
3806                ];
3807
3808                // Calculate cost
3809                // Get parent tile and poly if available
3810                let (parent_tile, parent_poly) = if let Some(parent_ref) = parent_ref {
3811                    match self.nav_mesh.get_tile_and_poly_by_ref(parent_ref) {
3812                        Ok((t, p)) => (Some(t), Some(p)),
3813                        Err(_) => (None, None),
3814                    }
3815                } else {
3816                    (None, None)
3817                };
3818
3819                let cost = filter.get_cost(
3820                    &center,
3821                    &edge_mid,
3822                    parent_ref.unwrap_or(PolyRef::new(0)),
3823                    parent_tile,
3824                    parent_poly,
3825                    best_ref,
3826                    best_tile,
3827                    best_poly,
3828                    neighbor_ref,
3829                    Some(neighbor_tile),
3830                    Some(neighbor_poly),
3831                );
3832
3833                let total = best_cost + cost;
3834
3835                // Check if node already exists
3836                let mut neighbor_idx = None;
3837                for (idx, node) in self.node_pool.iter().enumerate() {
3838                    if node.poly == neighbor_ref {
3839                        neighbor_idx = Some(idx);
3840                        break;
3841                    }
3842                }
3843
3844                if let Some(idx) = neighbor_idx {
3845                    // Node exists
3846                    if self.node_pool[idx].state == NodeState::Closed {
3847                        continue;
3848                    }
3849
3850                    // Update if better path found
3851                    if total < self.node_pool[idx].g {
3852                        self.node_pool[idx].parent = Some(best_idx);
3853                        self.node_pool[idx].g = total;
3854                        self.node_pool[idx].f = total;
3855
3856                        if self.node_pool[idx].state == NodeState::Open {
3857                            // Update position in heap
3858                            self.open_list.push(HeapNode {
3859                                index: idx,
3860                                f: total,
3861                            });
3862                        } else {
3863                            self.node_pool[idx].state = NodeState::Open;
3864                            self.open_list.push(HeapNode {
3865                                index: idx,
3866                                f: total,
3867                            });
3868                        }
3869                    }
3870                } else {
3871                    // New node
3872                    if self.node_pool.len() >= DT_MAX_NODES {
3873                        continue;
3874                    }
3875
3876                    let mut new_node = Node::new(neighbor_ref, self.node_pool.len());
3877                    new_node.parent = Some(best_idx);
3878                    new_node.g = total;
3879                    new_node.f = total;
3880                    new_node.state = NodeState::Open;
3881
3882                    let node_idx = self.node_pool.len();
3883                    self.node_pool.push(new_node);
3884                    self.open_list.push(HeapNode {
3885                        index: node_idx,
3886                        f: total,
3887                    });
3888                }
3889            }
3890        }
3891
3892        Ok(result)
3893    }
3894
3895    /// Returns a point on the boundary closest to the source point if the source point is outside the polygon's xz-bounds
3896    ///
3897    /// # Arguments
3898    ///
3899    /// * `poly_ref` - The reference id to the polygon
3900    /// * `pos` - The position to check
3901    ///
3902    /// # Returns
3903    ///
3904    /// The closest point on the polygon boundary
3905    pub fn closest_point_on_poly_boundary(
3906        &self,
3907        poly_ref: PolyRef,
3908        pos: Vec3,
3909    ) -> Result<Vec3, DetourError> {
3910        use landmark_common::distance_pt_poly_edges_sqr;
3911
3912        let pos = pos.to_array();
3913        let (tile, poly) = self.nav_mesh.get_tile_and_poly_by_ref(poly_ref)?;
3914
3915        // Collect vertices
3916        let mut verts = Vec::with_capacity(poly.vert_count as usize * 3);
3917        for i in 0..poly.vert_count as usize {
3918            let idx = poly.verts[i] as usize;
3919            verts.push(tile.verts[idx * 3]);
3920            verts.push(tile.verts[idx * 3 + 1]);
3921            verts.push(tile.verts[idx * 3 + 2]);
3922        }
3923
3924        let (inside, edge_dists, edge_ts) =
3925            distance_pt_poly_edges_sqr(&pos, &verts, poly.vert_count as usize);
3926
3927        if inside {
3928            // Point is inside the polygon, return the point itself
3929            Ok(Vec3::from(pos))
3930        } else {
3931            // Point is outside, find closest edge
3932            let mut min_dist = edge_dists[0];
3933            let mut min_idx = 0;
3934
3935            for (i, &dist) in edge_dists.iter().enumerate().skip(1) {
3936                if dist < min_dist {
3937                    min_dist = dist;
3938                    min_idx = i;
3939                }
3940            }
3941
3942            // Calculate the closest point on the nearest edge
3943            let va_idx = min_idx * 3;
3944            let vb_idx = ((min_idx + 1) % poly.vert_count as usize) * 3;
3945
3946            let va = [verts[va_idx], verts[va_idx + 1], verts[va_idx + 2]];
3947            let vb = [verts[vb_idx], verts[vb_idx + 1], verts[vb_idx + 2]];
3948            let t = edge_ts[min_idx];
3949
3950            // Linear interpolation
3951            Ok(Vec3::from([
3952                va[0] + (vb[0] - va[0]) * t,
3953                va[1] + (vb[1] - va[1]) * t,
3954                va[2] + (vb[2] - va[2]) * t,
3955            ]))
3956        }
3957    }
3958
3959    /// Returns edge mid point between two polygons
3960    ///
3961    /// # Arguments
3962    ///
3963    /// * `from_ref` - The reference of the polygon to traverse from
3964    /// * `to_ref` - The reference of the polygon to traverse to
3965    ///
3966    /// # Returns
3967    ///
3968    /// The midpoint of the edge between the two polygons
3969    pub fn get_edge_mid_point(
3970        &self,
3971        from_ref: PolyRef,
3972        to_ref: PolyRef,
3973    ) -> Result<Vec3, DetourError> {
3974        let (left, right) = self.get_portal_points(from_ref, to_ref)?;
3975
3976        // Calculate midpoint
3977        Ok((left + right) * 0.5)
3978    }
3979
3980    /// Gets a path from the explored nodes in the previous search
3981    ///
3982    /// This method depends on the state from a previous Dijkstra search
3983    /// (findPolysAroundCircle or findPolysAroundShape). It should only be used
3984    /// immediately after one of these searches.
3985    ///
3986    /// # Arguments
3987    ///
3988    /// * `end_ref` - The reference id of the end polygon
3989    ///
3990    /// # Returns
3991    ///
3992    /// An ordered list of polygon references representing the path (start to end)
3993    /// Finds polygons that overlap the search box.
3994    ///
3995    /// This method matches the C++ API: dtNavMeshQuery::queryPolygons
3996    pub fn query_polygons(
3997        &self,
3998        center: Vec3,
3999        half_extents: Vec3,
4000        filter: &QueryFilter,
4001        max_polys: usize,
4002    ) -> Result<Vec<PolyRef>, DetourError> {
4003        let mut collect_query = super::poly_query::CollectPolysQuery::new(max_polys);
4004        self.query_polygons_with_query(center, half_extents, filter, &mut collect_query)?;
4005        Ok(collect_query.polys().to_vec())
4006    }
4007
4008    /// Finds polygons that overlap the search box and processes them with a custom query.
4009    ///
4010    /// This method matches the C++ API: dtNavMeshQuery::queryPolygons with dtPolyQuery
4011    pub fn query_polygons_with_query(
4012        &self,
4013        center: Vec3,
4014        half_extents: Vec3,
4015        filter: &QueryFilter,
4016        query: &mut dyn super::poly_query::PolyQuery,
4017    ) -> Result<(), DetourError> {
4018        let center = center.to_array();
4019        let half_extents = half_extents.to_array();
4020        // Calculate search bounds
4021        let bmin = [
4022            center[0] - half_extents[0],
4023            center[1] - half_extents[1],
4024            center[2] - half_extents[2],
4025        ];
4026        let bmax = [
4027            center[0] + half_extents[0],
4028            center[1] + half_extents[1],
4029            center[2] + half_extents[2],
4030        ];
4031
4032        // Get all tiles that overlap the search bounds
4033        let tiles = self.nav_mesh.get_tiles_in_bounds(&bmin, &bmax)?;
4034
4035        // Process each tile
4036        for tile in tiles {
4037            let mut batch_polys = Vec::new();
4038            let mut batch_refs = Vec::new();
4039            const BATCH_SIZE: usize = 32;
4040
4041            // Process polygons in the tile
4042            for (poly_idx, poly) in tile.polys.iter().enumerate() {
4043                // Get polygon reference
4044                let poly_ref = self.nav_mesh.get_poly_ref_base(tile) | (poly_idx as u32);
4045                let poly_ref = PolyRef::new(poly_ref);
4046
4047                // Check if polygon passes filter
4048                if !filter.pass_filter(poly_ref, tile, poly) {
4049                    continue;
4050                }
4051
4052                // Check if polygon bounds overlap search bounds
4053                let poly_bounds = self.nav_mesh.get_poly_bounds(tile, poly)?;
4054                if !Self::overlap_bounds(&poly_bounds.0, &poly_bounds.1, &bmin, &bmax) {
4055                    continue;
4056                }
4057
4058                // Add to batch
4059                batch_polys.push(poly);
4060                batch_refs.push(poly_ref);
4061
4062                // Process batch when full
4063                if batch_polys.len() >= BATCH_SIZE {
4064                    query.process(tile, &batch_polys, &batch_refs);
4065                    batch_polys.clear();
4066                    batch_refs.clear();
4067                }
4068            }
4069
4070            // Process remaining polygons
4071            if !batch_polys.is_empty() {
4072                query.process(tile, &batch_polys, &batch_refs);
4073            }
4074        }
4075
4076        Ok(())
4077    }
4078
4079    /// Checks if two axis-aligned bounding boxes overlap
4080    fn overlap_bounds(amin: &[f32; 3], amax: &[f32; 3], bmin: &[f32; 3], bmax: &[f32; 3]) -> bool {
4081        !(amin[0] > bmax[0]
4082            || amax[0] < bmin[0]
4083            || amin[1] > bmax[1]
4084            || amax[1] < bmin[1]
4085            || amin[2] > bmax[2]
4086            || amax[2] < bmin[2])
4087    }
4088
4089    pub fn get_poly_wall_segments(
4090        &self,
4091        poly_ref: PolyRef,
4092        filter: &QueryFilter,
4093        max_segments: usize,
4094    ) -> Result<Vec<(Vec3, Vec3)>, DetourError> {
4095        if !poly_ref.is_valid() {
4096            return Err(DetourError::InvalidParam);
4097        }
4098
4099        let (tile, poly) = self.nav_mesh.get_tile_and_poly_by_ref(poly_ref)?;
4100
4101        // Check if the polygon passes the filter
4102        if !filter.pass_filter(poly_ref, tile, poly) {
4103            return Ok(Vec::new());
4104        }
4105
4106        let mut wall_segments = Vec::new();
4107
4108        // Check each edge of the polygon
4109        for i in 0..poly.vert_count as usize {
4110            if wall_segments.len() >= max_segments {
4111                break;
4112            }
4113
4114            // Get current and next vertex indices
4115            let curr_vert_idx = poly.verts[i] as usize;
4116            let next_vert_idx = poly.verts[(i + 1) % poly.vert_count as usize] as usize;
4117
4118            // Get vertex coordinates
4119            if curr_vert_idx * 3 + 2 >= tile.verts.len()
4120                || next_vert_idx * 3 + 2 >= tile.verts.len()
4121            {
4122                continue;
4123            }
4124
4125            let curr_vert = [
4126                tile.verts[curr_vert_idx * 3],
4127                tile.verts[curr_vert_idx * 3 + 1],
4128                tile.verts[curr_vert_idx * 3 + 2],
4129            ];
4130            let next_vert = [
4131                tile.verts[next_vert_idx * 3],
4132                tile.verts[next_vert_idx * 3 + 1],
4133                tile.verts[next_vert_idx * 3 + 2],
4134            ];
4135
4136            // Check if this edge is a wall (no walkable neighbor)
4137            // Use the link system to find the actual neighbor connection
4138            let is_wall = if let Some(link) = self.nav_mesh.find_link(tile, poly, i as u8) {
4139                // There's a link to a neighbor - check if it's walkable
4140                if link.reference.is_valid() {
4141                    // Get the neighbor tile and polygon
4142                    if let Ok((neighbor_tile, neighbor_poly)) =
4143                        self.nav_mesh.get_tile_and_poly_by_ref(link.reference)
4144                    {
4145                        // If neighbor doesn't pass filter, this edge is a wall
4146                        !filter.pass_filter(link.reference, neighbor_tile, neighbor_poly)
4147                    } else {
4148                        // Can't access neighbor - treat as wall
4149                        true
4150                    }
4151                } else {
4152                    // Invalid neighbor reference - it's a wall
4153                    true
4154                }
4155            } else {
4156                // No link to neighbor - definitely a wall
4157                true
4158            };
4159
4160            if is_wall {
4161                // Add this edge as a wall segment
4162                wall_segments.push((Vec3::from(curr_vert), Vec3::from(next_vert)));
4163            }
4164        }
4165
4166        Ok(wall_segments)
4167    }
4168
4169    /// Checks if a polygon reference is valid and passes the filter restrictions
4170    pub fn is_valid_poly_ref(&self, poly_ref: PolyRef, filter: &QueryFilter) -> bool {
4171        if !poly_ref.is_valid() {
4172            return false;
4173        }
4174
4175        // Try to get the tile and polygon
4176        if let Ok((tile, poly)) = self.nav_mesh.get_tile_and_poly_by_ref(poly_ref) {
4177            // Check if the polygon passes the filter
4178            filter.pass_filter(poly_ref, tile, poly)
4179        } else {
4180            false
4181        }
4182    }
4183
4184    /// Checks if a polygon reference is in the closed list
4185    pub fn is_in_closed_list(&self, poly_ref: PolyRef) -> bool {
4186        // Search through the node pool for this polygon
4187        for node in &self.node_pool {
4188            if node.poly == poly_ref && node.state == NodeState::Closed {
4189                return true;
4190            }
4191        }
4192        false
4193    }
4194
4195    /// Queries polygons with a custom query implementation
4196    ///
4197    /// This is an alias for `query_polygons_with_query` with a more intuitive name.
4198    /// It allows custom processing of polygons during spatial queries.
4199    ///
4200    /// # Example
4201    ///
4202    /// ```no_run
4203    /// use waymark::{PolyQuery, NavMesh, NavMeshQuery, PolyRef, MeshTile, Poly, QueryFilter};
4204    /// use glam::Vec3;
4205    ///
4206    /// struct MyCustomQuery {
4207    ///     count: usize,
4208    /// }
4209    ///
4210    /// impl PolyQuery for MyCustomQuery {
4211    ///     fn process(&mut self, tile: &MeshTile, polys: &[&Poly], refs: &[PolyRef]) {
4212    ///         self.count += polys.len();
4213    ///     }
4214    /// }
4215    ///
4216    /// # fn example(query: &NavMeshQuery) -> Result<(), Box<dyn std::error::Error>> {
4217    /// let filter = QueryFilter::default();
4218    /// let mut custom_query = MyCustomQuery { count: 0 };
4219    /// query.query_polygons_custom(
4220    ///     Vec3::new(0.0, 0.0, 0.0),
4221    ///     Vec3::new(10.0, 10.0, 10.0),
4222    ///     &filter,
4223    ///     &mut custom_query,
4224    /// )?;
4225    /// println!("Found {} polygons", custom_query.count);
4226    /// # Ok(())
4227    /// # }
4228    /// ```
4229    pub fn query_polygons_custom<Q: super::poly_query::PolyQuery>(
4230        &self,
4231        center: Vec3,
4232        extents: Vec3,
4233        filter: &QueryFilter,
4234        query: &mut Q,
4235    ) -> Result<(), DetourError> {
4236        self.query_polygons_with_query(center, extents, filter, query)
4237    }
4238
4239    /// Gets the internal node pool (for debugging and advanced use)
4240    ///
4241    /// Note: The returned nodes are only valid during the current query.
4242    /// The node pool is reused between queries.
4243    pub fn get_node_pool(&self) -> &[Node] {
4244        &self.node_pool
4245    }
4246
4247    /// Gets the attached navigation mesh
4248    pub fn get_attached_nav_mesh(&self) -> &NavMesh {
4249        self.nav_mesh
4250    }
4251}
4252
4253#[cfg(test)]
4254mod tests {
4255    use super::super::{
4256        NavMeshBuilder, NavMeshCreateParams, NavMeshParams, PolyFlags, encode_poly_ref,
4257    };
4258    use super::*;
4259    use crate::{CollectPolysQuery, FindNearestPolyQuery, nav_mesh::encode_poly_ref_with_salt};
4260    use landmark::MESH_NULL_IDX;
4261
4262    #[test]
4263    fn test_basic_query() {
4264        // Create a simple navigation mesh
4265        let params = NavMeshParams {
4266            origin: [0.0, 0.0, 0.0],
4267            tile_width: 100.0,
4268            tile_height: 100.0,
4269            max_tiles: 128,
4270            max_polys_per_tile: 1024,
4271        };
4272
4273        let nav_mesh = NavMesh::new(params).unwrap();
4274
4275        // Create a query
4276        let query = NavMeshQuery::new(&nav_mesh);
4277
4278        // Check query extent default values
4279        assert_eq!(query.get_query_extent(), Vec3::new(2.0, 4.0, 2.0));
4280    }
4281
4282    #[test]
4283    fn test_set_query_extent() {
4284        // Create a simple navigation mesh
4285        let params = NavMeshParams {
4286            origin: [0.0, 0.0, 0.0],
4287            tile_width: 100.0,
4288            tile_height: 100.0,
4289            max_tiles: 128,
4290            max_polys_per_tile: 1024,
4291        };
4292
4293        let nav_mesh = NavMesh::new(params).unwrap();
4294
4295        // Create a query
4296        let mut query = NavMeshQuery::new(&nav_mesh);
4297
4298        // Set query extent
4299        let extent = Vec3::new(10.0, 20.0, 10.0);
4300        query.set_query_extent(extent);
4301
4302        // Check query extent
4303        assert_eq!(query.get_query_extent(), extent);
4304    }
4305
4306    #[test]
4307    fn test_pathfinding_with_off_mesh_connections() {
4308        use super::super::{NavMeshParams, PolyFlags};
4309
4310        // Create a simple navigation mesh
4311        let params = NavMeshParams {
4312            origin: [0.0, 0.0, 0.0],
4313            tile_width: 100.0,
4314            tile_height: 100.0,
4315            max_tiles: 128,
4316            max_polys_per_tile: 1024,
4317        };
4318
4319        let mut nav_mesh = NavMesh::new(params).unwrap();
4320
4321        // Create a simple tile with an off-mesh connection
4322        nav_mesh.create_test_tile(0, 0, 0).unwrap();
4323
4324        // Add an off-mesh connection
4325        let connection_ref = nav_mesh
4326            .add_off_mesh_connection(
4327                [10.0, 0.0, 10.0], // start
4328                [20.0, 0.0, 20.0], // end
4329                2.0,               // radius
4330                PolyFlags::WALK,   // flags
4331                1,                 // area
4332                0,                 // bidirectional
4333                42,                // user_id
4334            )
4335            .unwrap();
4336
4337        // Create a query
4338        let query = NavMeshQuery::new(&nav_mesh);
4339
4340        // Test that off-mesh connections are detected
4341        assert!(nav_mesh.is_off_mesh_connection(connection_ref));
4342
4343        // Test closest point on off-mesh connection
4344        let (closest, _is_over) = query
4345            .closest_point_on_off_mesh_connection(connection_ref, &[15.0, 0.0, 15.0])
4346            .unwrap();
4347
4348        // The closest point should be on the line segment between start and end
4349        assert!(closest[0] >= 10.0 && closest[0] <= 20.0);
4350        assert!(closest[2] >= 10.0 && closest[2] <= 20.0);
4351
4352        // Test off-mesh connection endpoint calculation
4353        let endpoint = query
4354            .get_off_mesh_connection_endpoint(
4355                connection_ref,
4356                Vec3::new(11.0, 0.0, 11.0), // Close to start
4357            )
4358            .unwrap();
4359
4360        // Should return the end position since we're closer to start
4361        assert_eq!(endpoint, Vec3::new(20.0, 0.0, 20.0));
4362
4363        // Test from the other direction
4364        let endpoint2 = query
4365            .get_off_mesh_connection_endpoint(
4366                connection_ref,
4367                Vec3::new(19.0, 0.0, 19.0), // Close to end
4368            )
4369            .unwrap();
4370
4371        // Should return the start position since we're closer to end
4372        assert_eq!(endpoint2, Vec3::new(10.0, 0.0, 10.0));
4373    }
4374
4375    #[test]
4376    fn test_off_mesh_connection_cost_calculation() {
4377        use super::super::{NavMeshParams, PolyFlags, QueryFilter};
4378
4379        let params = NavMeshParams {
4380            origin: [0.0, 0.0, 0.0],
4381            tile_width: 100.0,
4382            tile_height: 100.0,
4383            max_tiles: 128,
4384            max_polys_per_tile: 1024,
4385        };
4386
4387        let mut nav_mesh = NavMesh::new(params).unwrap();
4388
4389        // Create a simple tile
4390        nav_mesh.create_test_tile(0, 0, 0).unwrap();
4391
4392        // Add an off-mesh connection with specific area
4393        let connection_ref = nav_mesh
4394            .add_off_mesh_connection(
4395                [0.0, 0.0, 0.0],
4396                [10.0, 0.0, 0.0],
4397                1.0,
4398                PolyFlags::WALK,
4399                5,
4400                0,
4401                1,
4402            )
4403            .unwrap();
4404
4405        let query = NavMeshQuery::new(&nav_mesh);
4406        let mut filter = QueryFilter::default();
4407        filter.area_cost[5] = 2.0; // Higher cost for area 5
4408
4409        // Calculate cost
4410        let cost = query
4411            .get_off_mesh_connection_cost(
4412                PolyRef::new(1), // dummy from ref
4413                connection_ref,
4414                &[0.0, 0.0, 0.0],
4415                &[10.0, 0.0, 0.0],
4416                &filter,
4417            )
4418            .unwrap();
4419
4420        // Cost should be distance (10.0) * area cost (2.0) = 20.0
4421        assert!((cost - 20.0).abs() < 1e-6);
4422    }
4423
4424    #[test]
4425    fn test_off_mesh_connection_direction_constraints() {
4426        use super::super::{NavMeshParams, PolyFlags};
4427
4428        let params = NavMeshParams {
4429            origin: [0.0, 0.0, 0.0],
4430            tile_width: 100.0,
4431            tile_height: 100.0,
4432            max_tiles: 128,
4433            max_polys_per_tile: 1024,
4434        };
4435
4436        let mut nav_mesh = NavMesh::new(params).unwrap();
4437
4438        // Create a simple tile
4439        nav_mesh.create_test_tile(0, 0, 0).unwrap();
4440
4441        // Add a unidirectional connection (start to end only)
4442        let connection_ref = nav_mesh
4443            .add_off_mesh_connection(
4444                [0.0, 0.0, 0.0],
4445                [10.0, 0.0, 0.0],
4446                1.0,
4447                PolyFlags::WALK,
4448                1,
4449                1,
4450                1, // direction = 1 (A->B)
4451            )
4452            .unwrap();
4453
4454        let query = NavMeshQuery::new(&nav_mesh);
4455
4456        // Should work from start to end
4457        let endpoint1 = query.get_off_mesh_connection_endpoint(
4458            connection_ref,
4459            Vec3::new(1.0, 0.0, 0.0), // Close to start
4460        );
4461        assert!(endpoint1.is_ok());
4462        assert_eq!(endpoint1.unwrap(), Vec3::new(10.0, 0.0, 0.0));
4463
4464        // Should NOT work from end to start
4465        let endpoint2 = query.get_off_mesh_connection_endpoint(
4466            connection_ref,
4467            Vec3::new(9.0, 0.0, 0.0), // Close to end
4468        );
4469        assert!(endpoint2.is_err()); // Should fail due to direction constraint
4470    }
4471
4472    #[test]
4473    fn test_off_mesh_connection_poly_end_points() {
4474        use super::super::{NavMeshParams, PolyFlags};
4475
4476        let params = NavMeshParams {
4477            origin: [0.0, 0.0, 0.0],
4478            tile_width: 100.0,
4479            tile_height: 100.0,
4480            max_tiles: 128,
4481            max_polys_per_tile: 1024,
4482        };
4483
4484        let mut nav_mesh = NavMesh::new(params).unwrap();
4485
4486        // Create test tiles
4487        nav_mesh.create_test_tile(0, 0, 0).unwrap();
4488
4489        // Add a bidirectional off-mesh connection
4490        let connection_ref = nav_mesh
4491            .add_off_mesh_connection(
4492                [10.0, 0.0, 10.0], // Start point
4493                [20.0, 0.0, 20.0], // End point
4494                2.0,
4495                PolyFlags::WALK,
4496                1,
4497                0, // bidirectional
4498                42,
4499            )
4500            .unwrap();
4501
4502        let query = NavMeshQuery::new(&nav_mesh);
4503
4504        // Test with invalid previous reference - should return natural order
4505        let (start, end) = query
4506            .get_off_mesh_connection_poly_end_points(PolyRef::new(0), connection_ref)
4507            .unwrap();
4508        assert_eq!(start, Vec3::new(10.0, 0.0, 10.0));
4509        assert_eq!(end, Vec3::new(20.0, 0.0, 20.0));
4510
4511        // Test with a mock previous polygon reference
4512        // In a real scenario, this would be a valid polygon reference
4513        let mock_prev_ref = PolyRef::new(1);
4514        let result = query.get_off_mesh_connection_poly_end_points(mock_prev_ref, connection_ref);
4515        // Since we can't easily create valid polygon references in this test,
4516        // we expect it to fall back to natural order
4517        if let Ok((start, end)) = result {
4518            let a = Vec3::new(10.0, 0.0, 10.0);
4519            let b = Vec3::new(20.0, 0.0, 20.0);
4520            // Should still work, either in natural order or reversed based on approach
4521            assert!((start == a && end == b) || (start == b && end == a));
4522        }
4523        // If it fails due to invalid prev_ref, that's also acceptable behavior
4524    }
4525
4526    #[test]
4527    fn test_off_mesh_connection_straight_path_integration() {
4528        use super::super::{NavMeshParams, PolyFlags};
4529
4530        let params = NavMeshParams {
4531            origin: [0.0, 0.0, 0.0],
4532            tile_width: 100.0,
4533            tile_height: 100.0,
4534            max_tiles: 128,
4535            max_polys_per_tile: 1024,
4536        };
4537
4538        let mut nav_mesh = NavMesh::new(params).unwrap();
4539
4540        // Create test tiles
4541        nav_mesh.create_test_tile(0, 0, 0).unwrap();
4542
4543        // Add an off-mesh connection
4544        let connection_ref = nav_mesh
4545            .add_off_mesh_connection(
4546                [5.0, 0.0, 5.0],
4547                [15.0, 0.0, 15.0],
4548                1.0,
4549                PolyFlags::WALK,
4550                1,
4551                0, // bidirectional
4552                1,
4553            )
4554            .unwrap();
4555
4556        let query = NavMeshQuery::new(&nav_mesh);
4557
4558        // Create a simple path containing the off-mesh connection
4559        let path = vec![PolyRef::new(1), connection_ref, PolyRef::new(2)];
4560
4561        // Test that straight path generation doesn't crash with off-mesh connections
4562        let result = query.find_straight_path(Vec3::ZERO, Vec3::new(20.0, 0.0, 20.0), &path);
4563
4564        // The exact behavior depends on the implementation details,
4565        // but it should not crash and should return some result
4566        match result {
4567            Ok(straight_path) => {
4568                // Should have at least start and end points
4569                assert!(straight_path.waypoints.len() >= 2);
4570                assert_eq!(straight_path.waypoints[0], Vec3::ZERO);
4571                assert_eq!(
4572                    straight_path.waypoints[straight_path.waypoints.len() - 1],
4573                    Vec3::new(20.0, 0.0, 20.0)
4574                );
4575            }
4576            Err(_) => {
4577                // It's acceptable for this to fail in the current implementation
4578                // since we don't have fully valid polygon data
4579            }
4580        }
4581    }
4582
4583    #[test]
4584    fn test_get_poly_wall_segments() -> std::result::Result<(), Box<dyn std::error::Error>> {
4585        // Create a simple navigation mesh with a single isolated polygon
4586        let nav_params = NavMeshParams {
4587            origin: [0.0, 0.0, 0.0],
4588            tile_width: 32.0,
4589            tile_height: 32.0,
4590            max_tiles: 1,
4591            max_polys_per_tile: 256,
4592        };
4593
4594        let mut nav_mesh = NavMesh::new(nav_params.clone())?;
4595
4596        // Create a simple square polygon with no neighbors (all walls)
4597        let params = NavMeshCreateParams {
4598            nav_mesh_params: nav_params,
4599            verts: vec![
4600                0.0, 0.0, 0.0, // 0
4601                10.0, 0.0, 0.0, // 1
4602                10.0, 0.0, 10.0, // 2
4603                0.0, 0.0, 10.0, // 3
4604            ],
4605            vert_count: 4,
4606            polys: vec![
4607                0,
4608                1,
4609                2,
4610                3,             // vertex indices
4611                MESH_NULL_IDX, // neighbor 0 (none)
4612                MESH_NULL_IDX, // neighbor 1 (none)
4613                MESH_NULL_IDX, // neighbor 2 (none)
4614                MESH_NULL_IDX, // neighbor 3 (none)
4615            ],
4616            poly_flags: vec![PolyFlags::WALK],
4617            poly_areas: vec![0],
4618            poly_count: 1,
4619            nvp: 4,
4620            detail_meshes: vec![0, 0, 0, 2],
4621            detail_verts: vec![],
4622            detail_vert_count: 0,
4623            detail_tris: vec![0, 1, 2, 0, 2, 3],
4624            detail_tri_count: 2,
4625            off_mesh_con_verts: vec![],
4626            off_mesh_con_rad: vec![],
4627            off_mesh_con_flags: vec![],
4628            off_mesh_con_areas: vec![],
4629            off_mesh_con_dir: vec![],
4630            off_mesh_con_user_id: vec![],
4631            off_mesh_con_count: 0,
4632            bmin: [0.0, 0.0, 0.0],
4633            bmax: [10.0, 0.0, 10.0],
4634            walkable_height: 2.0,
4635            walkable_radius: 0.6,
4636            walkable_climb: 0.9,
4637            cs: 0.3,
4638            ch: 0.2,
4639            build_bv_tree: true,
4640        };
4641
4642        let tile = NavMeshBuilder::build_tile(&params)?;
4643        let _tile_ref = nav_mesh.add_mesh_tile(tile)?;
4644
4645        // Create query
4646        let query = NavMeshQuery::new(&nav_mesh);
4647        let filter = QueryFilter::default();
4648
4649        // Get the polygon reference using find_nearest_poly to ensure correct salt
4650        let center = [5.0, 0.0, 5.0];
4651        let half_extents = [5.0, 5.0, 5.0];
4652        let (poly_ref, _) =
4653            query.find_nearest_poly(Vec3::from(center), Vec3::from(half_extents), &filter)?;
4654
4655        // Get wall segments (should have 4 since all edges are walls)
4656        let wall_segments = query.get_poly_wall_segments(poly_ref, &filter, 10)?;
4657
4658        // Should have 4 wall segments (one for each edge)
4659        assert_eq!(wall_segments.len(), 4);
4660
4661        // Verify the wall segments form a closed loop
4662        let expected_edges = [
4663            ([0.0, 0.0, 0.0], [10.0, 0.0, 0.0]),   // bottom edge
4664            ([10.0, 0.0, 0.0], [10.0, 0.0, 10.0]), // right edge
4665            ([10.0, 0.0, 10.0], [0.0, 0.0, 10.0]), // top edge
4666            ([0.0, 0.0, 10.0], [0.0, 0.0, 0.0]),   // left edge
4667        ];
4668
4669        for (i, segment) in wall_segments.iter().enumerate() {
4670            let start = segment.0.to_array();
4671            let end = segment.1.to_array();
4672            let expected = expected_edges[i];
4673
4674            // Allow for floating point precision
4675            for j in 0..3 {
4676                assert!(
4677                    (start[j] - expected.0[j]).abs() < 0.001,
4678                    "Segment {} start mismatch: expected {:?}, got {:?}",
4679                    i,
4680                    expected.0,
4681                    start
4682                );
4683                assert!(
4684                    (end[j] - expected.1[j]).abs() < 0.001,
4685                    "Segment {} end mismatch: expected {:?}, got {:?}",
4686                    i,
4687                    expected.1,
4688                    end
4689                );
4690            }
4691        }
4692
4693        // Test with max_segments limit
4694        let limited_segments = query.get_poly_wall_segments(poly_ref, &filter, 2)?;
4695        assert_eq!(limited_segments.len(), 2); // Should be limited to 2
4696
4697        // Test with invalid polygon reference
4698        let invalid_ref = encode_poly_ref(999, 999);
4699        let result = query.get_poly_wall_segments(invalid_ref, &filter, 10);
4700        assert!(result.is_err());
4701
4702        Ok(())
4703    }
4704
4705    #[test]
4706    fn test_get_poly_wall_segments_with_neighbors()
4707    -> std::result::Result<(), Box<dyn std::error::Error>> {
4708        // Create a 2-tile navigation mesh to test cross-tile neighbor detection
4709        let nav_params = NavMeshParams {
4710            origin: [0.0, 0.0, 0.0],
4711            tile_width: 10.0,
4712            tile_height: 10.0,
4713            max_tiles: 4,
4714            max_polys_per_tile: 256,
4715        };
4716
4717        let mut nav_mesh = NavMesh::new(nav_params.clone())?;
4718
4719        // Create two adjacent tiles
4720        for tx in 0..2 {
4721            let x_offset = tx as f32 * 10.0;
4722
4723            let params = NavMeshCreateParams {
4724                nav_mesh_params: nav_params.clone(),
4725                verts: vec![
4726                    x_offset + 0.0,
4727                    0.0,
4728                    0.0, // 0
4729                    x_offset + 10.0,
4730                    0.0,
4731                    0.0, // 1
4732                    x_offset + 10.0,
4733                    0.0,
4734                    10.0, // 2
4735                    x_offset + 0.0,
4736                    0.0,
4737                    10.0, // 3
4738                ],
4739                vert_count: 4,
4740                polys: vec![
4741                    0,
4742                    1,
4743                    2,
4744                    3,
4745                    MESH_NULL_IDX,
4746                    MESH_NULL_IDX,
4747                    MESH_NULL_IDX,
4748                    MESH_NULL_IDX,
4749                ],
4750                poly_flags: vec![PolyFlags::WALK],
4751                poly_areas: vec![0],
4752                poly_count: 1,
4753                nvp: 4,
4754                detail_meshes: vec![0, 0, 0, 2],
4755                detail_verts: vec![],
4756                detail_vert_count: 0,
4757                detail_tris: vec![0, 1, 2, 0, 2, 3],
4758                detail_tri_count: 2,
4759                off_mesh_con_verts: vec![],
4760                off_mesh_con_rad: vec![],
4761                off_mesh_con_flags: vec![],
4762                off_mesh_con_areas: vec![],
4763                off_mesh_con_dir: vec![],
4764                off_mesh_con_user_id: vec![],
4765                off_mesh_con_count: 0,
4766                bmin: [x_offset, 0.0, 0.0],
4767                bmax: [x_offset + 10.0, 0.0, 10.0],
4768                walkable_height: 2.0,
4769                walkable_radius: 0.6,
4770                walkable_climb: 0.9,
4771                cs: 0.3,
4772                ch: 0.2,
4773                build_bv_tree: true,
4774            };
4775
4776            let mut tile = NavMeshBuilder::build_tile(&params)?;
4777
4778            // Set tile coordinates
4779            if let Some(header) = tile.header.as_mut() {
4780                header.x = tx;
4781                header.y = 0;
4782                header.layer = 0;
4783            }
4784
4785            nav_mesh.add_mesh_tile(tile)?;
4786        }
4787
4788        // Manually connect the tiles (this would normally be done automatically)
4789        // For now, just test that the method works with disconnected tiles
4790
4791        let query = NavMeshQuery::new(&nav_mesh);
4792        let filter = QueryFilter::default();
4793
4794        // Get polygon reference using find_nearest_poly to ensure correct salt
4795        let center = [5.0, 0.0, 5.0];
4796        let half_extents = [5.0, 5.0, 5.0];
4797        let (poly_ref, _) =
4798            query.find_nearest_poly(Vec3::from(center), Vec3::from(half_extents), &filter)?;
4799
4800        // Get wall segments - may vary depending on whether tiles auto-connect
4801        let wall_segments = query.get_poly_wall_segments(poly_ref, &filter, 10)?;
4802
4803        // Should have at least 2 wall segments (some edges may not be walls if tiles connect)
4804        assert!(wall_segments.len() >= 2);
4805        assert!(wall_segments.len() <= 4);
4806
4807        // Verify that each segment has non-zero length
4808        for segment in &wall_segments {
4809            // Start and end points should be different
4810            let dist_sqr = segment.0.distance_squared(segment.1);
4811            assert!(dist_sqr > 0.001, "Wall segment should have non-zero length");
4812        }
4813
4814        Ok(())
4815    }
4816
4817    // More tests would be needed for full coverage, but we need a more complex
4818    // navigation mesh setup to test pathfinding properly.
4819
4820    // Helper function to create a simple nav mesh for testing
4821    fn create_simple_nav_mesh() -> Result<NavMesh, DetourError> {
4822        let params = NavMeshParams {
4823            origin: [0.0, 0.0, 0.0],
4824            tile_width: 10.0,
4825            tile_height: 10.0,
4826            max_tiles: 1,
4827            max_polys_per_tile: 1,
4828        };
4829
4830        let mut nav_mesh = NavMesh::new(params.clone()).unwrap();
4831
4832        // Create a simple tile with one polygon
4833        let tile_params = NavMeshCreateParams {
4834            nav_mesh_params: params,
4835            verts: vec![
4836                0.0, 0.0, 0.0, 10.0, 0.0, 0.0, 10.0, 0.0, 10.0, 0.0, 0.0, 10.0,
4837            ],
4838            vert_count: 4,
4839            polys: vec![0, 1, 2, 3, 0xffff, 0xffff],
4840            poly_flags: vec![PolyFlags::WALK],
4841            poly_areas: vec![0],
4842            poly_count: 1,
4843            nvp: 6,
4844            detail_meshes: vec![0, 4, 0, 2],
4845            detail_verts: vec![
4846                0.0, 0.0, 0.0, 10.0, 0.0, 0.0, 10.0, 0.0, 10.0, 0.0, 0.0, 10.0,
4847            ],
4848            detail_vert_count: 4,
4849            detail_tris: vec![0, 1, 2, 0, 2, 3],
4850            detail_tri_count: 2,
4851            off_mesh_con_verts: vec![],
4852            off_mesh_con_rad: vec![],
4853            off_mesh_con_flags: vec![],
4854            off_mesh_con_areas: vec![],
4855            off_mesh_con_dir: vec![],
4856            off_mesh_con_user_id: vec![],
4857            off_mesh_con_count: 0,
4858            bmin: [0.0, 0.0, 0.0],
4859            bmax: [10.0, 0.0, 10.0],
4860            walkable_height: 2.0,
4861            walkable_radius: 0.6,
4862            walkable_climb: 0.9,
4863            cs: 0.3,
4864            ch: 0.2,
4865            build_bv_tree: true,
4866        };
4867
4868        let mut tile = NavMeshBuilder::build_tile(&tile_params)?;
4869
4870        // Set tile header info
4871        if let Some(header) = tile.header.as_mut() {
4872            header.x = 0;
4873            header.y = 0;
4874            header.layer = 0;
4875        }
4876
4877        nav_mesh.add_mesh_tile(tile)?;
4878
4879        // Debug: Check if tile was added
4880        let tiles = nav_mesh.get_all_tiles();
4881        println!("Number of tiles: {}", tiles.len());
4882        if !tiles.is_empty() {
4883            let first_tile = &tiles[0];
4884            println!("First tile has {} polygons", first_tile.polys.len());
4885            if !first_tile.polys.is_empty() {
4886                println!("First polygon: {:?}", first_tile.polys[0]);
4887            }
4888        }
4889
4890        Ok(nav_mesh)
4891    }
4892
4893    #[test]
4894    fn test_sliced_pathfinding_simple() -> Result<(), DetourError> {
4895        let nav_mesh = create_simple_nav_mesh()?;
4896        let mut query = NavMeshQuery::new(&nav_mesh);
4897        let filter = QueryFilter::default();
4898
4899        // Get the correct polygon reference from the first tile
4900        let tiles = nav_mesh.get_all_tiles();
4901        if tiles.is_empty() {
4902            panic!("No tiles in nav mesh!");
4903        }
4904
4905        // Get the tile's salt value to create valid poly ref
4906        let first_tile = &tiles[0];
4907        let tile_salt = first_tile.salt;
4908        println!("Tile salt: {}", tile_salt);
4909
4910        // Try different tile IDs to find the valid one
4911        let mut poly_ref = PolyRef::new(0);
4912        for tile_id in 1..10 {
4913            let test_ref = encode_poly_ref_with_salt(tile_salt, tile_id, 0);
4914            if nav_mesh.is_valid_poly_ref(test_ref) {
4915                println!("Found valid tile_id: {}", tile_id);
4916                poly_ref = test_ref;
4917                println!(
4918                    "Using poly_ref: {:?} (hex: 0x{:08x})",
4919                    poly_ref,
4920                    poly_ref.id()
4921                );
4922                break;
4923            }
4924        }
4925
4926        // If we still don't have a valid ref, fall back to find_nearest_poly
4927        if !poly_ref.is_valid() {
4928            let center = [5.0, 0.0, 5.0];
4929            let half_extents = [1.0, 1.0, 1.0];
4930            match query.find_nearest_poly(Vec3::from(center), Vec3::from(half_extents), &filter) {
4931                Ok((found_ref, _pos)) => poly_ref = found_ref,
4932                Err(_) => poly_ref = encode_poly_ref_with_salt(tile_salt, 1, 0),
4933            }
4934        }
4935        println!(
4936            "Final poly_ref: {:?} (hex: 0x{:08x})",
4937            poly_ref,
4938            poly_ref.id()
4939        );
4940        println!(
4941            "Is valid poly_ref: {}",
4942            nav_mesh.is_valid_poly_ref(poly_ref)
4943        );
4944
4945        let start_pos = [5.0, 0.0, 5.0];
4946        let end_pos = [8.0, 0.0, 8.0];
4947
4948        // Initialize sliced pathfinding
4949        query.init_sliced_find_path(
4950            poly_ref,
4951            poly_ref,
4952            Vec3::from(start_pos),
4953            Vec3::from(end_pos),
4954            &filter,
4955            0,
4956        )?;
4957
4958        // Update until complete
4959        let (iterations, state) = query.update_sliced_find_path(100)?;
4960        assert_eq!(state, SlicedPathState::Success);
4961        assert_eq!(iterations, 0); // Should complete immediately for same poly
4962
4963        // Finalize path
4964        let path = query.finalize_sliced_find_path(10)?;
4965        assert_eq!(path.len(), 1);
4966        assert_eq!(path[0], poly_ref);
4967
4968        Ok(())
4969    }
4970
4971    #[test]
4972    fn test_sliced_pathfinding_with_iterations() -> Result<(), DetourError> {
4973        // Create a 3x3 grid nav mesh
4974        let nav_mesh = create_3x3_grid_navmesh();
4975        let mut query = NavMeshQuery::new(&nav_mesh);
4976        let filter = QueryFilter::default();
4977
4978        // Path from bottom-left to top-right using find_nearest_poly to get correct refs
4979        let start_center = [5.0, 0.0, 5.0]; // Center of first polygon
4980        let end_center = [25.0, 0.0, 25.0]; // Center of last polygon
4981        let half_extents = [20.0, 10.0, 20.0]; // Large search area
4982
4983        println!("Finding start polygon near {:?}", start_center);
4984        let start_result =
4985            query.find_nearest_poly(Vec3::from(start_center), Vec3::from(half_extents), &filter);
4986        let (start_ref, start_nearest) = match start_result {
4987            Ok(result) => {
4988                println!("Found start polygon: {:?} at {:?}", result.0, result.1);
4989                result
4990            }
4991            Err(e) => {
4992                println!("Failed to find start polygon: {:?}", e);
4993                return Err(e);
4994            }
4995        };
4996
4997        println!("Finding end polygon near {:?}", end_center);
4998        let end_result =
4999            query.find_nearest_poly(Vec3::from(end_center), Vec3::from(half_extents), &filter);
5000        let (end_ref, end_nearest) = match end_result {
5001            Ok(result) => {
5002                println!("Found end polygon: {:?} at {:?}", result.0, result.1);
5003                result
5004            }
5005            Err(e) => {
5006                println!("Failed to find end polygon: {:?}", e);
5007                return Err(e);
5008            }
5009        };
5010        let start_pos = start_nearest.to_array();
5011        let end_pos = end_nearest.to_array();
5012
5013        // Initialize sliced pathfinding
5014        println!(
5015            "Initializing sliced pathfinding from {:?} to {:?}",
5016            start_ref, end_ref
5017        );
5018        match query.init_sliced_find_path(
5019            start_ref,
5020            end_ref,
5021            Vec3::from(start_pos),
5022            Vec3::from(end_pos),
5023            &filter,
5024            0,
5025        ) {
5026            Ok(()) => println!("Sliced pathfinding initialized successfully"),
5027            Err(e) => {
5028                println!("Failed to initialize sliced pathfinding: {:?}", e);
5029
5030                // Check if polygons are valid
5031                println!(
5032                    "Checking if start_ref is valid: {}",
5033                    nav_mesh.is_valid_poly_ref(start_ref)
5034                );
5035                println!(
5036                    "Checking if end_ref is valid: {}",
5037                    nav_mesh.is_valid_poly_ref(end_ref)
5038                );
5039
5040                return Err(e);
5041            }
5042        }
5043
5044        // Update with limited iterations
5045        let mut total_iterations = 0;
5046        let mut state = SlicedPathState::InProgress;
5047
5048        while state == SlicedPathState::InProgress {
5049            println!(
5050                "Updating sliced pathfinding, iteration {}",
5051                total_iterations
5052            );
5053            match query.update_sliced_find_path(2) {
5054                Ok((iter, new_state)) => {
5055                    println!("  Updated {} iterations, new state: {:?}", iter, new_state);
5056                    total_iterations += iter;
5057                    state = new_state;
5058                }
5059                Err(e) => {
5060                    println!("  Update failed: {:?}", e);
5061                    return Err(e);
5062                }
5063            }
5064
5065            // Safety check
5066            if total_iterations > 100 {
5067                panic!("Too many iterations");
5068            }
5069        }
5070
5071        // Should eventually succeed
5072        assert_eq!(state, SlicedPathState::Success);
5073
5074        // Finalize path
5075        let path = query.finalize_sliced_find_path(20)?;
5076        assert!(path.len() > 1); // Should have multiple polygons
5077        assert_eq!(path[0], start_ref);
5078        assert_eq!(path[path.len() - 1], end_ref);
5079
5080        Ok(())
5081    }
5082
5083    #[test]
5084    fn test_sliced_pathfinding_partial() -> Result<(), DetourError> {
5085        let nav_mesh = create_simple_nav_mesh()?;
5086        let mut query = NavMeshQuery::new(&nav_mesh);
5087        let filter = QueryFilter::default();
5088
5089        // Get a valid start reference using find_nearest_poly
5090        let start_center = [5.0, 0.0, 5.0];
5091        let half_extents = [2.0, 2.0, 2.0];
5092        let (start_ref, _) =
5093            query.find_nearest_poly(Vec3::from(start_center), Vec3::from(half_extents), &filter)?;
5094        let invalid_end_ref = PolyRef::new(999); // Doesn't exist
5095        let start_pos = [5.0, 0.0, 5.0];
5096        let end_pos = [50.0, 0.0, 50.0];
5097
5098        // This should fail during init
5099        let result = query.init_sliced_find_path(
5100            start_ref,
5101            invalid_end_ref,
5102            Vec3::from(start_pos),
5103            Vec3::from(end_pos),
5104            &filter,
5105            0,
5106        );
5107        assert!(result.is_err());
5108
5109        Ok(())
5110    }
5111
5112    #[test]
5113    fn test_sliced_pathfinding_any_angle() -> Result<(), DetourError> {
5114        let nav_mesh = create_simple_nav_mesh()?;
5115        let mut query = NavMeshQuery::new(&nav_mesh);
5116        let filter = QueryFilter::default();
5117
5118        // Get polygon reference using find_nearest_poly to ensure correct salt
5119        let center = [5.0, 0.0, 5.0];
5120        let half_extents = [2.0, 2.0, 2.0];
5121        let (poly_ref, _) =
5122            query.find_nearest_poly(Vec3::from(center), Vec3::from(half_extents), &filter)?;
5123        let start_pos = [5.0, 0.0, 5.0];
5124        let end_pos = [8.0, 0.0, 8.0];
5125
5126        // Initialize with DT_FINDPATH_ANY_ANGLE option
5127        const DT_FINDPATH_ANY_ANGLE: u32 = 0x02;
5128        query.init_sliced_find_path(
5129            poly_ref,
5130            poly_ref,
5131            Vec3::from(start_pos),
5132            Vec3::from(end_pos),
5133            &filter,
5134            DT_FINDPATH_ANY_ANGLE,
5135        )?;
5136
5137        // Should still work (any-angle is a TODO)
5138        let (_, state) = query.update_sliced_find_path(100)?;
5139        assert_eq!(state, SlicedPathState::Success);
5140
5141        Ok(())
5142    }
5143
5144    #[test]
5145    fn test_query_polygons() -> Result<(), DetourError> {
5146        let nav_mesh = create_simple_nav_mesh()?;
5147        let query = NavMeshQuery::new(&nav_mesh);
5148        let filter = QueryFilter::default();
5149
5150        // Query polygons in the center of the mesh
5151        let center = [5.0, 0.0, 5.0];
5152        let half_extents = [3.0, 2.0, 3.0];
5153
5154        let polys =
5155            query.query_polygons(Vec3::from(center), Vec3::from(half_extents), &filter, 10)?;
5156
5157        // Should find at least one polygon
5158        assert!(!polys.is_empty());
5159
5160        Ok(())
5161    }
5162
5163    #[test]
5164    fn test_query_polygons_with_custom_query() -> Result<(), DetourError> {
5165        let nav_mesh = create_simple_nav_mesh()?;
5166        let query = NavMeshQuery::new(&nav_mesh);
5167        let filter = QueryFilter::default();
5168
5169        // Use a custom query to collect polygons
5170        let mut collect_query = CollectPolysQuery::new(5);
5171        let center = [5.0, 0.0, 5.0];
5172        let half_extents = [3.0, 2.0, 3.0];
5173
5174        query.query_polygons_with_query(
5175            Vec3::from(center),
5176            Vec3::from(half_extents),
5177            &filter,
5178            &mut collect_query,
5179        )?;
5180
5181        // Should have collected at least one polygon
5182        assert!(collect_query.num_collected() > 0);
5183        assert!(!collect_query.overflow());
5184
5185        Ok(())
5186    }
5187
5188    #[test]
5189    fn test_find_nearest_poly_query() -> Result<(), DetourError> {
5190        let nav_mesh = create_simple_nav_mesh()?;
5191        let query = NavMeshQuery::new(&nav_mesh);
5192        let filter = QueryFilter::default();
5193
5194        // Use FindNearestPolyQuery
5195        let center = [5.0, 0.0, 5.0];
5196        let half_extents = [5.0, 2.0, 5.0];
5197        let mut nearest_query = FindNearestPolyQuery::new(&query, &center);
5198
5199        query.query_polygons_with_query(
5200            Vec3::from(center),
5201            Vec3::from(half_extents),
5202            &filter,
5203            &mut nearest_query,
5204        )?;
5205
5206        // Should have found a nearest polygon
5207        assert!(nearest_query.nearest_ref().is_valid());
5208        assert!(nearest_query.nearest_distance_sqr() < f32::MAX);
5209
5210        Ok(())
5211    }
5212
5213    // Helper function to create a 3x3 grid navigation mesh for testing
5214    fn create_3x3_grid_navmesh() -> NavMesh {
5215        let params = NavMeshParams {
5216            origin: [0.0, 0.0, 0.0],
5217            tile_width: 30.0,
5218            tile_height: 30.0,
5219            max_tiles: 1,
5220            max_polys_per_tile: 9,
5221        };
5222
5223        let mut nav_mesh = NavMesh::new(params.clone()).unwrap();
5224
5225        // Create 3x3 grid of polygons (0-8)
5226        let cell_size = 10.0;
5227
5228        // Create vertices for a 3x3 grid (4x4 vertices)
5229        // Store as f32 values directly - they'll be used as-is in the tile
5230        let mut verts = Vec::new();
5231        for z in 0..4 {
5232            for x in 0..4 {
5233                verts.push(x as f32 * cell_size);
5234                verts.push(0.0);
5235                verts.push(z as f32 * cell_size);
5236            }
5237        }
5238
5239        // Create 9 polygons in a 3x3 grid
5240        let mut polys = Vec::new();
5241        let mut poly_flags = Vec::new();
5242        let mut poly_areas = Vec::new();
5243
5244        for row in 0..3 {
5245            for col in 0..3 {
5246                let base_idx = row * 4 + col;
5247                let _poly_idx = row * 3 + col;
5248
5249                // Add polygon vertices (counter-clockwise)
5250                polys.push(base_idx as u16);
5251                polys.push((base_idx + 1) as u16);
5252                polys.push((base_idx + 5) as u16);
5253                polys.push((base_idx + 4) as u16);
5254
5255                // Add neighbor information (bottom, right, top, left)
5256                // Bottom neighbor
5257                polys.push(if row > 0 {
5258                    ((row - 1) * 3 + col) as u16
5259                } else {
5260                    0xffff
5261                });
5262                // Right neighbor
5263                polys.push(if col < 2 {
5264                    (row * 3 + col + 1) as u16
5265                } else {
5266                    0xffff
5267                });
5268                // Top neighbor
5269                polys.push(if row < 2 {
5270                    ((row + 1) * 3 + col) as u16
5271                } else {
5272                    0xffff
5273                });
5274                // Left neighbor
5275                polys.push(if col > 0 {
5276                    (row * 3 + col - 1) as u16
5277                } else {
5278                    0xffff
5279                });
5280
5281                poly_flags.push(PolyFlags::WALK);
5282                poly_areas.push(0);
5283            }
5284        }
5285
5286        // Create detail mesh
5287        let mut detail_meshes = Vec::new();
5288        let mut detail_tris = Vec::new();
5289
5290        for i in 0..9 {
5291            detail_meshes.push(0); // base vertex
5292            detail_meshes.push(4); // vertex count
5293            detail_meshes.push((i * 2) as u32); // base tri
5294            detail_meshes.push(2); // tri count
5295
5296            // Two triangles per polygon
5297            detail_tris.push(0);
5298            detail_tris.push(1);
5299            detail_tris.push(2);
5300            detail_tris.push(0);
5301            detail_tris.push(2);
5302            detail_tris.push(3);
5303        }
5304
5305        let tile_params = NavMeshCreateParams {
5306            nav_mesh_params: params.clone(),
5307            verts: verts.clone(),
5308            vert_count: 16,
5309            polys,
5310            poly_flags,
5311            poly_areas,
5312            poly_count: 9,
5313            nvp: 6,
5314            detail_meshes,
5315            detail_verts: verts,
5316            detail_vert_count: 16,
5317            detail_tris,
5318            detail_tri_count: 18,
5319            off_mesh_con_verts: vec![],
5320            off_mesh_con_rad: vec![],
5321            off_mesh_con_flags: vec![],
5322            off_mesh_con_areas: vec![],
5323            off_mesh_con_dir: vec![],
5324            off_mesh_con_user_id: vec![],
5325            off_mesh_con_count: 0,
5326            bmin: [0.0, 0.0, 0.0],
5327            bmax: [30.0, 0.0, 30.0],
5328            walkable_height: 2.0,
5329            walkable_radius: 0.6,
5330            walkable_climb: 0.9,
5331            cs: 0.3,
5332            ch: 0.2,
5333            build_bv_tree: true,
5334        };
5335
5336        let tile = NavMeshBuilder::build_tile(&tile_params).unwrap();
5337        nav_mesh.add_mesh_tile(tile).unwrap();
5338        nav_mesh
5339    }
5340
5341    #[test]
5342    fn test_get_poly_height() -> Result<(), DetourError> {
5343        let nav_mesh = create_simple_nav_mesh()?;
5344        let query = NavMeshQuery::new(&nav_mesh);
5345        let filter = QueryFilter::default();
5346
5347        // Find a valid polygon
5348        let center = [5.0, 0.0, 5.0];
5349        let half_extents = [2.0, 2.0, 2.0];
5350
5351        // Find nearest polygon
5352        let (poly_ref, nearest_pt) =
5353            query.find_nearest_poly(Vec3::from(center), Vec3::from(half_extents), &filter)?;
5354
5355        // If we found a valid polygon
5356        if poly_ref.is_valid() {
5357            // Test get_poly_height with the nearest point (should be inside the polygon)
5358            let height = query.get_poly_height(poly_ref, nearest_pt)?;
5359
5360            // For simple nav mesh without detail mesh, height might be None
5361            // This is still a valid test - we're testing the API exists and works
5362            if height.is_some() {
5363                // If we have height, it should be reasonable
5364                assert!(height.unwrap() >= -100.0 && height.unwrap() <= 100.0);
5365            }
5366
5367            // Test with a position definitely outside any polygon
5368            let outside_pos = Vec3::new(1000.0, 0.0, 1000.0);
5369            let height_outside = query.get_poly_height(poly_ref, outside_pos)?;
5370
5371            // Height should be None for a position outside the polygon
5372            assert!(height_outside.is_none());
5373        }
5374
5375        Ok(())
5376    }
5377
5378    #[test]
5379    fn test_find_random_point_with_custom_rand() -> Result<(), DetourError> {
5380        let nav_mesh = create_3x3_grid_navmesh();
5381        let mut query = NavMeshQuery::new(&nav_mesh);
5382        let filter = QueryFilter::default();
5383
5384        // Test with a simple custom random function
5385        let counter = std::cell::Cell::new(0);
5386        let custom_rand = || {
5387            let val = counter.get();
5388            counter.set(val + 1);
5389            // Return a predictable sequence
5390            (val as f32 * 0.123) % 1.0
5391        };
5392
5393        let (poly_ref, point) = query.find_random_point_with_custom_rand(&filter, custom_rand)?;
5394
5395        assert!(poly_ref.is_valid());
5396        // Point should be within the navigation mesh bounds
5397        assert!(point[0] >= 0.0 && point[0] <= 30.0);
5398        assert!(point[2] >= 0.0 && point[2] <= 30.0);
5399
5400        Ok(())
5401    }
5402
5403    #[test]
5404    fn test_find_random_point_around_circle_with_custom_rand() -> Result<(), DetourError> {
5405        let nav_mesh = create_3x3_grid_navmesh();
5406        let mut query = NavMeshQuery::new(&nav_mesh);
5407        let filter = QueryFilter::default();
5408
5409        // Find a center polygon
5410        let center = [15.0, 0.0, 15.0];
5411        let half_extents = [5.0, 2.0, 5.0];
5412        let (center_ref, _) =
5413            query.find_nearest_poly(Vec3::from(center), Vec3::from(half_extents), &filter)?;
5414
5415        // Test with custom random function
5416        let seed = std::cell::Cell::new(42u32);
5417        let custom_rand = || {
5418            // Simple LCG
5419            let val = seed.get();
5420            let new_val = val.wrapping_mul(1103515245).wrapping_add(12345);
5421            seed.set(new_val);
5422            (new_val & 0x7FFFFFFF) as f32 / 2147483647.0
5423        };
5424
5425        let radius = 10.0;
5426        let (poly_ref, point) = query.find_random_point_around_circle(
5427            center_ref,
5428            Vec3::from(center),
5429            radius,
5430            &filter,
5431            custom_rand,
5432        )?;
5433
5434        assert!(poly_ref.is_valid());
5435
5436        // Check that the point is within the radius
5437        let dx = point[0] - center[0];
5438        let dz = point[2] - center[2];
5439        let distance = (dx * dx + dz * dz).sqrt();
5440        assert!(distance <= radius);
5441
5442        Ok(())
5443    }
5444
5445    #[test]
5446    fn test_api_harmonization_methods() -> Result<(), DetourError> {
5447        let nav_mesh = create_simple_nav_mesh()?;
5448        let mut query = NavMeshQuery::new(&nav_mesh);
5449        let filter = QueryFilter::default();
5450
5451        // Use very large search area to ensure we find the single polygon
5452        let center = [15.0, 0.0, 15.0];
5453        let half_extents = [20.0, 10.0, 20.0];
5454
5455        // Test find_nearest_poly_extended (new overload with is_over_poly)
5456        match query.find_nearest_poly_extended(
5457            Vec3::from(center),
5458            Vec3::from(half_extents),
5459            &filter,
5460        ) {
5461            Ok((nearest_ref, nearest_pt, _is_over_poly)) => {
5462                assert!(nearest_ref.is_valid());
5463
5464                // Test basic find_nearest_poly returns same result
5465                let (nearest_ref_basic, nearest_pt_basic) = query.find_nearest_poly(
5466                    Vec3::from(center),
5467                    Vec3::from(half_extents),
5468                    &filter,
5469                )?;
5470                assert_eq!(nearest_ref, nearest_ref_basic);
5471                assert_eq!(nearest_pt, nearest_pt_basic);
5472
5473                // Test find_straight_path_with_options (new overload with options)
5474                let path_refs = vec![nearest_ref];
5475                let start_pos = nearest_pt.to_array();
5476                let end_pos = nearest_pt.to_array(); // Same position to avoid complexity
5477
5478                let path_with_options = query.find_straight_path_with_options(
5479                    Vec3::from(start_pos),
5480                    Vec3::from(end_pos),
5481                    &path_refs,
5482                    0,
5483                )?;
5484                let path_basic = query.find_straight_path(
5485                    Vec3::from(start_pos),
5486                    Vec3::from(end_pos),
5487                    &path_refs,
5488                )?;
5489
5490                // Should return the same result
5491                assert_eq!(
5492                    path_with_options.waypoint_count(),
5493                    path_basic.waypoint_count()
5494                );
5495
5496                // Test init_sliced_find_path_default (new overload with default options)
5497                query.init_sliced_find_path_default(
5498                    nearest_ref,
5499                    nearest_ref,
5500                    Vec3::from(start_pos),
5501                    Vec3::from(end_pos),
5502                    &filter,
5503                )?;
5504
5505                // Test that it's equivalent to calling with options = 0
5506                let mut query2 = NavMeshQuery::new(&nav_mesh);
5507                query2.init_sliced_find_path(
5508                    nearest_ref,
5509                    nearest_ref,
5510                    Vec3::from(start_pos),
5511                    Vec3::from(end_pos),
5512                    &filter,
5513                    0,
5514                )?;
5515
5516                // Both should be in the same state
5517                let (_iter1, state1) = query.update_sliced_find_path(1)?;
5518                let (_iter2, state2) = query2.update_sliced_find_path(1)?;
5519                assert_eq!(state1, state2);
5520            }
5521            Err(_) => {
5522                // If we can't find a polygon, just test that the API methods exist and can be called
5523                // This ensures API compatibility even if we don't have proper test data
5524
5525                // These should fail gracefully but the APIs should exist
5526                assert!(
5527                    query
5528                        .find_nearest_poly(Vec3::from(center), Vec3::from(half_extents), &filter)
5529                        .is_err()
5530                );
5531                assert!(
5532                    query
5533                        .find_nearest_poly_extended(
5534                            Vec3::from(center),
5535                            Vec3::from(half_extents),
5536                            &filter
5537                        )
5538                        .is_err()
5539                );
5540
5541                // Test straight path methods with empty path (should handle gracefully)
5542                let empty_path = vec![];
5543                let pos = [0.0, 0.0, 0.0];
5544                assert!(
5545                    query
5546                        .find_straight_path(Vec3::from(pos), Vec3::from(pos), &empty_path)
5547                        .is_err()
5548                );
5549                assert!(
5550                    query
5551                        .find_straight_path_with_options(
5552                            Vec3::from(pos),
5553                            Vec3::from(pos),
5554                            &empty_path,
5555                            0
5556                        )
5557                        .is_err()
5558                );
5559            }
5560        }
5561
5562        Ok(())
5563    }
5564}