Skip to main content

hassium_navigation/resource/
nav_mesh.rs

1use crate::{
2    resource::{NavVec3, ZERO_TRESHOLD},
3    Scalar,
4};
5use core::id::ID;
6use petgraph::{algo::astar, graph::NodeIndex, visit::EdgeRef, Graph, Undirected};
7#[cfg(feature = "parallel")]
8use rayon::prelude::*;
9use serde::{Deserialize, Serialize};
10use spade::{rtree::RTree, BoundingRect, SpatialObject};
11use std::{
12    collections::HashMap,
13    hash::{Hash, Hasher},
14    result::Result as StdResult,
15};
16
17#[cfg(feature = "parallel")]
18macro_rules! iter {
19    ($v:expr) => {
20        $v.par_iter()
21    };
22}
23#[cfg(not(feature = "parallel"))]
24macro_rules! iter {
25    ($v:expr) => {
26        $v.iter()
27    };
28}
29#[cfg(feature = "parallel")]
30macro_rules! into_iter {
31    ($v:expr) => {
32        $v.into_par_iter()
33    };
34}
35#[cfg(not(feature = "parallel"))]
36macro_rules! into_iter {
37    ($v:expr) => {
38        $v.into_iter()
39    };
40}
41
42/// Nav mash identifier.
43pub type NavMeshID = ID<NavMesh>;
44
45/// Error data.
46#[derive(Debug, Clone)]
47pub enum Error {
48    /// Trying to construct triangle with vertice index out of vertices list.
49    /// (triangle index, local vertice index, global vertice index)
50    TriangleVerticeIndexOutOfBounds(u32, u8, u32),
51}
52
53/// Result data.
54pub type NavResult<T> = StdResult<T, Error>;
55
56/// Nav mesh triangle description - lists used vertices indices.
57#[repr(C)]
58#[derive(Debug, Default, Copy, Clone, Serialize, Deserialize)]
59pub struct NavTriangle {
60    pub first: u32,
61    pub second: u32,
62    pub third: u32,
63}
64
65impl From<(u32, u32, u32)> for NavTriangle {
66    fn from(value: (u32, u32, u32)) -> Self {
67        Self {
68            first: value.0,
69            second: value.1,
70            third: value.2,
71        }
72    }
73}
74
75/// Nav mesh area descriptor. Nav mesh area holds information about specific nav mesh triangle.
76#[repr(C)]
77#[derive(Debug, Default, Clone, Serialize, Deserialize)]
78pub struct NavArea {
79    /// Triangle index.
80    pub triangle: u32,
81    /// Area size (triangle area value).
82    pub size: Scalar,
83    /// Traverse cost factor. Big values tells that this area is hard to traverse, smaller tells
84    /// the opposite.
85    pub cost: Scalar,
86    /// Triangle center point.
87    pub center: NavVec3,
88    /// Radius of sphere that contains this triangle.
89    pub radius: Scalar,
90    /// Squared version of `radius`.
91    pub radius_sqr: Scalar,
92}
93
94impl NavArea {
95    /// Calculate triangle area value.
96    ///
97    /// # Arguments
98    /// * `a` - first vertice point.
99    /// * `b` - second vertice point.
100    /// * `c` - thirs vertice point.
101    #[inline]
102    pub fn calculate_area(a: NavVec3, b: NavVec3, c: NavVec3) -> Scalar {
103        let ab = b - a;
104        let ac = c - a;
105        ab.cross(ac).magnitude() * 0.5
106    }
107
108    /// Calculate triangle center point.
109    ///
110    /// # Arguments
111    /// * `a` - first vertice point.
112    /// * `b` - second vertice point.
113    /// * `c` - thirs vertice point.
114    #[inline]
115    pub fn calculate_center(a: NavVec3, b: NavVec3, c: NavVec3) -> NavVec3 {
116        let v = a + b + c;
117        NavVec3::new(v.x / 3.0, v.y / 3.0, v.z / 3.0)
118    }
119}
120
121#[derive(Debug, Default, Copy, Clone, Eq, Serialize, Deserialize)]
122struct NavConnection(pub u32, pub u32);
123
124impl Hash for NavConnection {
125    fn hash<H: Hasher>(&self, state: &mut H) {
126        let first = self.0.min(self.1);
127        let second = self.0.max(self.1);
128        first.hash(state);
129        second.hash(state);
130    }
131}
132
133impl PartialEq for NavConnection {
134    fn eq(&self, other: &Self) -> bool {
135        let first = self.0.min(self.1);
136        let second = self.0.max(self.1);
137        let ofirst = other.0.min(other.1);
138        let osecond = other.0.max(other.1);
139        first == ofirst && second == osecond
140    }
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize)]
144pub(crate) struct NavSpatialObject {
145    pub index: usize,
146    pub a: NavVec3,
147    pub b: NavVec3,
148    pub c: NavVec3,
149    ab: NavVec3,
150    bc: NavVec3,
151    ca: NavVec3,
152    normal: NavVec3,
153    dab: NavVec3,
154    dbc: NavVec3,
155    dca: NavVec3,
156}
157
158impl NavSpatialObject {
159    pub fn new(index: usize, a: NavVec3, b: NavVec3, c: NavVec3) -> Self {
160        let ab = b - a;
161        let bc = c - b;
162        let ca = a - c;
163        let normal = (a - b).cross(a - c).normalize();
164        let dab = normal.cross(ab);
165        let dbc = normal.cross(bc);
166        let dca = normal.cross(ca);
167        Self {
168            index,
169            a,
170            b,
171            c,
172            ab,
173            bc,
174            ca,
175            normal,
176            dab,
177            dbc,
178            dca,
179        }
180    }
181
182    #[inline]
183    pub fn normal(&self) -> NavVec3 {
184        self.normal
185    }
186
187    pub fn closest_point(&self, point: NavVec3) -> NavVec3 {
188        let pab = point.project(self.a, self.b);
189        let pbc = point.project(self.b, self.c);
190        let pca = point.project(self.c, self.a);
191        if pca > 1.0 && pab < 0.0 {
192            return self.a;
193        } else if pab > 1.0 && pbc < 0.0 {
194            return self.b;
195        } else if pbc > 1.0 && pca < 0.0 {
196            return self.c;
197        } else if pab >= 0.0 && pab <= 1.0 && !point.is_above_plane(self.a, self.dab) {
198            return NavVec3::unproject(self.a, self.b, pab);
199        } else if pbc >= 0.0 && pbc <= 1.0 && !point.is_above_plane(self.b, self.dbc) {
200            return NavVec3::unproject(self.b, self.c, pbc);
201        } else if pca >= 0.0 && pca <= 1.0 && !point.is_above_plane(self.c, self.dca) {
202            return NavVec3::unproject(self.c, self.a, pca);
203        }
204        point.project_on_plane(self.a, self.normal)
205    }
206}
207
208impl SpatialObject for NavSpatialObject {
209    type Point = NavVec3;
210
211    fn mbr(&self) -> BoundingRect<Self::Point> {
212        let min = NavVec3::new(
213            self.a.x.min(self.b.x).min(self.c.x),
214            self.a.y.min(self.b.y).min(self.c.y),
215            self.a.z.min(self.b.z).min(self.c.z),
216        );
217        let max = NavVec3::new(
218            self.a.x.max(self.b.x).max(self.c.x),
219            self.a.y.max(self.b.y).max(self.c.y),
220            self.a.z.max(self.b.z).max(self.c.z),
221        );
222        BoundingRect::from_corners(&min, &max)
223    }
224
225    fn distance2(&self, point: &Self::Point) -> Scalar {
226        (*point - self.closest_point(*point)).sqr_magnitude()
227    }
228}
229
230/// Quality of querying a point on nav mesh.
231#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
232pub enum NavQuery {
233    /// Best quality, totally accurate.
234    Accuracy,
235    /// Medium quality, finds point in closest triangle.
236    Closest,
237    /// Low quality, finds first triangle in range of query.
238    ClosestFirst,
239}
240
241/// Quality of finding path.
242#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
243pub enum NavPathMode {
244    /// Best quality, finds shortest path.
245    Accuracy,
246    /// Medium quality, finds shortest path througs triangles midpoints.
247    MidPoints,
248}
249
250/// ECS resource that holds and manages nav meshes.
251#[derive(Debug, Default)]
252pub struct NavMeshesRes(pub(crate) HashMap<NavMeshID, NavMesh>);
253
254impl NavMeshesRes {
255    /// Register new nav mesh.
256    ///
257    /// # Arguments
258    /// * `mesh` - nav mesh object.
259    ///
260    /// # Returns
261    /// Identifier of registered nav mesh.
262    #[inline]
263    pub fn register(&mut self, mesh: NavMesh) -> NavMeshID {
264        let id = mesh.id();
265        self.0.insert(id, mesh);
266        id
267    }
268
269    /// Unregister nav mesh.
270    ///
271    /// # Arguments
272    /// * `id` - nav mesh identifier.
273    ///
274    /// # Returns
275    /// `Some` with nav mesh object if nav mesh with given identifier was found, `None` otherwise.
276    #[inline]
277    pub fn unregister(&mut self, id: NavMeshID) -> Option<NavMesh> {
278        self.0.remove(&id)
279    }
280
281    /// Unregister all nav meshes.
282    #[inline]
283    pub fn unregister_all(&mut self) {
284        self.0.clear()
285    }
286
287    /// Get nav meshes iterator.
288    #[inline]
289    pub fn meshes_iter(&self) -> impl Iterator<Item = &NavMesh> {
290        self.0.values()
291    }
292
293    /// Find nav mesh by its identifier.
294    ///
295    /// # Arguments
296    /// * `id` - nav mesh identifier.
297    ///
298    /// # Returns
299    /// `Some` with nav mesh if exists or `None` otherwise.
300    #[inline]
301    pub fn find_mesh(&self, id: NavMeshID) -> Option<&NavMesh> {
302        self.0.get(&id)
303    }
304
305    /// Find nav mesh by its identifier.
306    ///
307    /// # Arguments
308    /// * `id` - nav mesh identifier.
309    ///
310    /// # Returns
311    /// `Some` with mutable nav mesh if exists or `None` otherwise.
312    #[inline]
313    pub fn find_mesh_mut(&mut self, id: NavMeshID) -> Option<&mut NavMesh> {
314        self.0.get_mut(&id)
315    }
316
317    /// Find closest point on nav meshes.
318    ///
319    /// # Arguments
320    /// * `point` - query point.
321    /// * `query` - query quality.
322    ///
323    /// # Returns
324    /// `Some` with nav mesh identifier and point on nav mesh if found or `None` otherwise.
325    pub fn closest_point(&self, point: NavVec3, query: NavQuery) -> Option<(NavMeshID, NavVec3)> {
326        iter!(self.0)
327            .filter_map(|(id, mesh)| {
328                mesh.closest_point(point, query)
329                    .map(|p| (p, (p - point).sqr_magnitude(), *id))
330            })
331            .min_by(|a, b| a.1.partial_cmp(&b.1).unwrap())
332            .map(|(p, _, id)| (id, p))
333    }
334}
335
336/// Nav mesh object used to find shortest path between two points.
337#[derive(Debug, Default, Clone)]
338pub struct NavMesh {
339    id: NavMeshID,
340    vertices: Vec<NavVec3>,
341    triangles: Vec<NavTriangle>,
342    areas: Vec<NavArea>,
343    // {triangle connection: (distance sqr, vertex connection)}
344    connections: HashMap<NavConnection, (Scalar, NavConnection)>,
345    graph: Graph<(), Scalar, Undirected>,
346    nodes: Vec<NodeIndex>,
347    nodes_map: HashMap<NodeIndex, usize>,
348    rtree: RTree<NavSpatialObject>,
349    spatials: Vec<NavSpatialObject>,
350    // {triangle index: [(from, to)]}
351    hard_edges: HashMap<usize, Vec<(NavVec3, NavVec3)>>,
352}
353
354impl NavMesh {
355    /// Create new nav mesh object from vertices and triangles.
356    ///
357    /// # Arguments
358    /// * `vertices` - list of vertices points.
359    /// * `triangles` - list of vertices indices that produces triangles.
360    ///
361    /// # Returns
362    /// `Ok` with nav mesh object or `Err` with `Error::TriangleVerticeIndexOutOfBounds` if input
363    /// data is invalid.
364    ///
365    /// # Example
366    /// ```
367    /// use hassium_navigation::prelude::*;
368    ///
369    /// let vertices = vec![
370    ///     (0.0, 0.0, 0.0).into(), // 0
371    ///     (1.0, 0.0, 0.0).into(), // 1
372    ///     (2.0, 0.0, 1.0).into(), // 2
373    ///     (0.0, 1.0, 0.0).into(), // 3
374    ///     (1.0, 1.0, 0.0).into(), // 4
375    ///     (2.0, 1.0, 1.0).into(), // 5
376    /// ];
377    /// let triangles = vec![
378    ///     (0, 1, 4).into(), // 0
379    ///     (4, 3, 0).into(), // 1
380    ///     (1, 2, 5).into(), // 2
381    ///     (5, 4, 1).into(), // 3
382    /// ];
383    ///
384    /// let mesh = NavMesh::new(vertices, triangles).unwrap();
385    /// ```
386    pub fn new(vertices: Vec<NavVec3>, triangles: Vec<NavTriangle>) -> NavResult<Self> {
387        let areas = iter!(triangles)
388            .enumerate()
389            .map(|(i, triangle)| {
390                if triangle.first >= vertices.len() as u32 {
391                    return Err(Error::TriangleVerticeIndexOutOfBounds(
392                        i as u32,
393                        0,
394                        triangle.first,
395                    ));
396                }
397                if triangle.second >= vertices.len() as u32 {
398                    return Err(Error::TriangleVerticeIndexOutOfBounds(
399                        i as u32,
400                        1,
401                        triangle.second,
402                    ));
403                }
404                if triangle.third >= vertices.len() as u32 {
405                    return Err(Error::TriangleVerticeIndexOutOfBounds(
406                        i as u32,
407                        2,
408                        triangle.third,
409                    ));
410                }
411                let first = vertices[triangle.first as usize];
412                let second = vertices[triangle.second as usize];
413                let third = vertices[triangle.third as usize];
414                let center = NavArea::calculate_center(first, second, third);
415                let radius = (first - center)
416                    .magnitude()
417                    .max((second - center).magnitude())
418                    .max((third - center).magnitude());
419                Ok(NavArea {
420                    triangle: i as u32,
421                    size: NavArea::calculate_area(first, second, third),
422                    cost: 1.0,
423                    center,
424                    radius,
425                    radius_sqr: radius * radius,
426                })
427            })
428            .collect::<NavResult<Vec<_>>>()?;
429
430        // {edge: [triangle index]}
431        let mut edges = HashMap::<NavConnection, Vec<usize>>::with_capacity(triangles.len() * 3);
432        for (index, triangle) in triangles.iter().enumerate() {
433            let edge_a = NavConnection(triangle.first, triangle.second);
434            let edge_b = NavConnection(triangle.second, triangle.third);
435            let edge_c = NavConnection(triangle.third, triangle.first);
436            if let Some(tris) = edges.get_mut(&edge_a) {
437                tris.push(index);
438            } else {
439                edges.insert(edge_a, vec![index]);
440            }
441            if let Some(tris) = edges.get_mut(&edge_b) {
442                tris.push(index);
443            } else {
444                edges.insert(edge_b, vec![index]);
445            }
446            if let Some(tris) = edges.get_mut(&edge_c) {
447                tris.push(index);
448            } else {
449                edges.insert(edge_c, vec![index]);
450            }
451        }
452
453        let connections = into_iter!(iter!(edges)
454            .flat_map(|(verts, tris)| {
455                let mut result = HashMap::with_capacity(tris.len() * tris.len());
456                for a in tris {
457                    for b in tris {
458                        if a != b {
459                            result.insert(NavConnection(*a as u32, *b as u32), *verts);
460                        }
461                    }
462                }
463                result
464            })
465            .collect::<HashMap<_, _>>())
466        .map(|(tri_conn, vert_conn)| {
467            let a = areas[tri_conn.0 as usize].center;
468            let b = areas[tri_conn.1 as usize].center;
469            let weight = (b - a).sqr_magnitude();
470            (tri_conn, (weight, vert_conn))
471        })
472        .collect::<HashMap<_, _>>();
473
474        let mut graph = Graph::<(), Scalar, Undirected>::new_undirected();
475        let nodes = (0..triangles.len())
476            .map(|_| graph.add_node(()))
477            .collect::<Vec<_>>();
478        graph.extend_with_edges(
479            connections
480                .iter()
481                .map(|(conn, (w, _))| (nodes[conn.0 as usize], nodes[conn.1 as usize], w)),
482        );
483        let nodes_map = iter!(nodes).enumerate().map(|(i, n)| (*n, i)).collect();
484
485        let spatials = iter!(triangles)
486            .enumerate()
487            .map(|(index, triangle)| {
488                NavSpatialObject::new(
489                    index,
490                    vertices[triangle.first as usize],
491                    vertices[triangle.second as usize],
492                    vertices[triangle.third as usize],
493                )
494            })
495            .collect::<Vec<_>>();
496
497        let mut rtree = RTree::new();
498        for spatial in &spatials {
499            rtree.insert(spatial.clone());
500        }
501
502        let hard_edges = iter!(triangles)
503            .enumerate()
504            .filter_map(|(index, triangle)| {
505                let edge_a = NavConnection(triangle.first, triangle.second);
506                let edge_b = NavConnection(triangle.second, triangle.third);
507                let edge_c = NavConnection(triangle.third, triangle.first);
508                let mut planes = vec![];
509                if edges[&edge_a].len() < 2 {
510                    planes.push((
511                        vertices[triangle.first as usize],
512                        vertices[triangle.second as usize],
513                    ));
514                }
515                if edges[&edge_b].len() < 2 {
516                    planes.push((
517                        vertices[triangle.second as usize],
518                        vertices[triangle.third as usize],
519                    ));
520                }
521                if edges[&edge_c].len() < 2 {
522                    planes.push((
523                        vertices[triangle.third as usize],
524                        vertices[triangle.first as usize],
525                    ));
526                }
527                if planes.is_empty() {
528                    None
529                } else {
530                    Some((index, planes))
531                }
532            })
533            .collect::<HashMap<_, _>>();
534
535        Ok(Self {
536            id: ID::new(),
537            vertices,
538            triangles,
539            areas,
540            connections,
541            graph,
542            nodes,
543            nodes_map,
544            rtree,
545            spatials,
546            hard_edges,
547        })
548    }
549
550    /// Nav mesh identifier.
551    #[inline]
552    pub fn id(&self) -> NavMeshID {
553        self.id
554    }
555
556    /// Reference to list of nav mesh vertices points.
557    #[inline]
558    pub fn vertices(&self) -> &[NavVec3] {
559        &self.vertices
560    }
561
562    /// Reference to list of nav mesh triangles.
563    #[inline]
564    pub fn triangles(&self) -> &[NavTriangle] {
565        &self.triangles
566    }
567
568    /// Reference to list of nav mesh area descriptors.
569    #[inline]
570    pub fn areas(&self) -> &[NavArea] {
571        &self.areas
572    }
573
574    /// Set area cost by triangle index.
575    ///
576    /// # Arguments
577    /// * `index` - triangle index.
578    /// * `cost` - cost factor.
579    ///
580    /// # Returns
581    /// Old area cost value.
582    #[inline]
583    pub fn set_area_cost(&mut self, index: usize, cost: Scalar) -> Scalar {
584        let area = &mut self.areas[index];
585        let old = area.cost;
586        let cost = cost.max(0.0);
587        area.cost = cost;
588        old
589    }
590
591    /// Find closest point on nav mesh.
592    ///
593    /// # Arguments
594    /// * `point` - query point.
595    /// * `query` - query quality.
596    ///
597    /// # Returns
598    /// `Some` with point on nav mesh if found or `None` otherwise.
599    pub fn closest_point(&self, point: NavVec3, query: NavQuery) -> Option<NavVec3> {
600        self.find_closest_triangle(point, query)
601            .map(|triangle| self.spatials[triangle].closest_point(point))
602    }
603
604    /// Find shortest path on nav mesh between two points.
605    ///
606    /// # Arguments
607    /// * `from` - query point from.
608    /// * `to` - query point to.
609    /// * `query` - query quality.
610    /// * `mode` - path finding quality.
611    ///
612    /// # Returns
613    /// `Some` with path points on nav mesh if found or `None` otherwise.
614    ///
615    /// # Example
616    /// ```
617    /// use hassium_navigation::prelude::*;
618    ///
619    /// let vertices = vec![
620    ///     (0.0, 0.0, 0.0).into(), // 0
621    ///     (1.0, 0.0, 0.0).into(), // 1
622    ///     (2.0, 0.0, 1.0).into(), // 2
623    ///     (0.0, 1.0, 0.0).into(), // 3
624    ///     (1.0, 1.0, 0.0).into(), // 4
625    ///     (2.0, 1.0, 1.0).into(), // 5
626    /// ];
627    /// let triangles = vec![
628    ///     (0, 1, 4).into(), // 0
629    ///     (4, 3, 0).into(), // 1
630    ///     (1, 2, 5).into(), // 2
631    ///     (5, 4, 1).into(), // 3
632    /// ];
633    ///
634    /// let mesh = NavMesh::new(vertices, triangles).unwrap();
635    /// let path = mesh
636    ///     .find_path(
637    ///         (0.0, 1.0, 0.0).into(),
638    ///         (1.5, 0.25, 0.5).into(),
639    ///         NavQuery::Accuracy,
640    ///         NavPathMode::MidPoints,
641    ///     )
642    ///     .unwrap();
643    /// assert_eq!(
644    ///     path.into_iter()
645    ///         .map(|v| (
646    ///             (v.x * 10.0) as i32,
647    ///             (v.y * 10.0) as i32,
648    ///             (v.z * 10.0) as i32,
649    ///         ))
650    ///         .collect::<Vec<_>>(),
651    ///     vec![(0, 10, 0), (10, 5, 0), (15, 2, 5),]
652    /// );
653    /// ```
654    pub fn find_path(
655        &self,
656        from: NavVec3,
657        to: NavVec3,
658        query: NavQuery,
659        mode: NavPathMode,
660    ) -> Option<Vec<NavVec3>> {
661        if (to - from).sqr_magnitude() < ZERO_TRESHOLD {
662            return None;
663        }
664        let start = if let Some(start) = self.find_closest_triangle(from, query) {
665            start
666        } else {
667            return None;
668        };
669        let end = if let Some(end) = self.find_closest_triangle(to, query) {
670            end
671        } else {
672            return None;
673        };
674        let from = self.spatials[start].closest_point(from);
675        let to = self.spatials[end].closest_point(to);
676        if let Some((triangles, _)) = self.find_path_triangles(start, end) {
677            if triangles.is_empty() {
678                return None;
679            } else if triangles.len() == 1 {
680                return Some(vec![from, to]);
681            }
682            match mode {
683                NavPathMode::Accuracy => {
684                    return Some(self.find_path_accuracy(from, to, &triangles));
685                }
686                NavPathMode::MidPoints => {
687                    return Some(self.find_path_midpoints(from, to, &triangles));
688                }
689            }
690        }
691        None
692    }
693
694    fn find_path_accuracy(&self, from: NavVec3, to: NavVec3, triangles: &[usize]) -> Vec<NavVec3> {
695        #[derive(Debug)]
696        enum Node {
697            Point(NavVec3),
698            // (a, b, normal)
699            LevelChange(NavVec3, NavVec3, NavVec3),
700        }
701
702        // TODO: reduce allocations.
703        if triangles.len() == 2 {
704            let NavConnection(a, b) =
705                self.connections[&NavConnection(triangles[0] as u32, triangles[1] as u32)].1;
706            let a = self.vertices[a as usize];
707            let b = self.vertices[b as usize];
708            let n = self.spatials[triangles[0]].normal();
709            let m = self.spatials[triangles[1]].normal();
710            if !NavVec3::is_line_between_points(from, to, a, b, n) {
711                let da = (from - a).sqr_magnitude();
712                let db = (from - b).sqr_magnitude();
713                let point = if da < db { a } else { b };
714                return vec![from, point, to];
715            } else if n.dot(m) < 1.0 - ZERO_TRESHOLD {
716                let n = (b - a).normalize().cross(n);
717                if let Some(point) = NavVec3::raycast_line(from, to, a, b, n) {
718                    return vec![from, point, to];
719                }
720            }
721            return vec![from, to];
722        }
723        let mut start = from;
724        let mut last_normal = self.spatials[triangles[0]].normal();
725        let mut nodes = Vec::with_capacity(triangles.len() - 1);
726        for triplets in triangles.windows(3) {
727            let NavConnection(a, b) =
728                self.connections[&NavConnection(triplets[0] as u32, triplets[1] as u32)].1;
729            let a = self.vertices[a as usize];
730            let b = self.vertices[b as usize];
731            let NavConnection(c, d) =
732                self.connections[&NavConnection(triplets[1] as u32, triplets[2] as u32)].1;
733            let c = self.vertices[c as usize];
734            let d = self.vertices[d as usize];
735            let n = self.spatials[triplets[1]].normal();
736            let old_last_normal = last_normal;
737            last_normal = n;
738            if !NavVec3::is_line_between_points(start, c, a, b, n)
739                || !NavVec3::is_line_between_points(start, d, a, b, n)
740            {
741                let da = (start - a).sqr_magnitude();
742                let db = (start - b).sqr_magnitude();
743                start = if da < db { a } else { b };
744                nodes.push(Node::Point(start));
745            } else if old_last_normal.dot(n) < 1.0 - ZERO_TRESHOLD {
746                let n = self.spatials[triplets[0]].normal();
747                let n = (b - a).normalize().cross(n);
748                nodes.push(Node::LevelChange(a, b, n));
749            }
750        }
751        {
752            let NavConnection(a, b) = self.connections[&NavConnection(
753                triangles[triangles.len() - 2] as u32,
754                triangles[triangles.len() - 1] as u32,
755            )]
756                .1;
757            let a = self.vertices[a as usize];
758            let b = self.vertices[b as usize];
759            let n = self.spatials[triangles[triangles.len() - 2]].normal();
760            let m = self.spatials[triangles[triangles.len() - 1]].normal();
761            if !NavVec3::is_line_between_points(start, to, a, b, n) {
762                let da = (start - a).sqr_magnitude();
763                let db = (start - b).sqr_magnitude();
764                let point = if da < db { a } else { b };
765                nodes.push(Node::Point(point));
766            } else if n.dot(m) < 1.0 - ZERO_TRESHOLD {
767                let n = (b - a).normalize().cross(n);
768                nodes.push(Node::LevelChange(a, b, n));
769            }
770        }
771
772        let mut points = Vec::with_capacity(nodes.len() + 2);
773        points.push(from);
774        let mut point = from;
775        for i in 0..nodes.len() {
776            match nodes[i] {
777                Node::Point(p) => {
778                    point = p;
779                    points.push(p);
780                }
781                Node::LevelChange(a, b, n) => {
782                    let next = nodes
783                        .iter()
784                        .skip(i + 1)
785                        .find_map(|n| match n {
786                            Node::Point(p) => Some(*p),
787                            _ => None,
788                        })
789                        .unwrap_or(to);
790                    if let Some(p) = NavVec3::raycast_line(point, next, a, b, n) {
791                        points.push(p);
792                    }
793                }
794            }
795        }
796        points.push(to);
797        points.dedup();
798        points
799    }
800
801    fn find_path_midpoints(&self, from: NavVec3, to: NavVec3, triangles: &[usize]) -> Vec<NavVec3> {
802        if triangles.len() == 2 {
803            let NavConnection(a, b) =
804                self.connections[&NavConnection(triangles[0] as u32, triangles[1] as u32)].1;
805            let a = self.vertices[a as usize];
806            let b = self.vertices[b as usize];
807            let n = self.spatials[triangles[0]].normal();
808            let m = self.spatials[triangles[1]].normal();
809            if n.dot(m) < 1.0 - ZERO_TRESHOLD || !NavVec3::is_line_between_points(from, to, a, b, n)
810            {
811                return vec![from, (a + b) * 0.5, to];
812            } else {
813                return vec![from, to];
814            }
815        }
816        let mut start = from;
817        let mut last_normal = self.spatials[triangles[0]].normal();
818        let mut points = Vec::with_capacity(triangles.len() + 1);
819        points.push(from);
820        for triplets in triangles.windows(3) {
821            let NavConnection(a, b) =
822                self.connections[&NavConnection(triplets[0] as u32, triplets[1] as u32)].1;
823            let a = self.vertices[a as usize];
824            let b = self.vertices[b as usize];
825            let point = (a + b) * 0.5;
826            let n = self.spatials[triplets[1]].normal();
827            let old_last_normal = last_normal;
828            last_normal = n;
829            if old_last_normal.dot(n) < 1.0 - ZERO_TRESHOLD {
830                start = point;
831                points.push(start);
832            } else {
833                let NavConnection(c, d) =
834                    self.connections[&NavConnection(triplets[1] as u32, triplets[2] as u32)].1;
835                let c = self.vertices[c as usize];
836                let d = self.vertices[d as usize];
837                let end = (c + d) * 0.5;
838                if !NavVec3::is_line_between_points(start, end, a, b, n) {
839                    start = point;
840                    points.push(start);
841                }
842            }
843        }
844        {
845            let NavConnection(a, b) = self.connections[&NavConnection(
846                triangles[triangles.len() - 2] as u32,
847                triangles[triangles.len() - 1] as u32,
848            )]
849                .1;
850            let a = self.vertices[a as usize];
851            let b = self.vertices[b as usize];
852            let n = self.spatials[triangles[triangles.len() - 2]].normal();
853            let m = self.spatials[triangles[triangles.len() - 1]].normal();
854            if n.dot(m) < 1.0 - ZERO_TRESHOLD
855                || !NavVec3::is_line_between_points(start, to, a, b, n)
856            {
857                points.push((a + b) * 0.5);
858            }
859        }
860        points.push(to);
861        points.dedup();
862        points
863    }
864
865    /// Find shortest path on nav mesh between two points.
866    ///
867    /// # Arguments
868    /// * `from` - query point from.
869    /// * `to` - query point to.
870    /// * `query` - query quality.
871    /// * `mode` - path finding quality.
872    ///
873    /// # Returns
874    /// `Some` with path points on nav mesh and path length if found or `None` otherwise.
875    ///
876    /// # Example
877    /// ```
878    /// use hassium_navigation::prelude::*;
879    ///
880    /// let vertices = vec![
881    ///     (0.0, 0.0, 0.0).into(), // 0
882    ///     (1.0, 0.0, 0.0).into(), // 1
883    ///     (2.0, 0.0, 1.0).into(), // 2
884    ///     (0.0, 1.0, 0.0).into(), // 3
885    ///     (1.0, 1.0, 0.0).into(), // 4
886    ///     (2.0, 1.0, 1.0).into(), // 5
887    /// ];
888    /// let triangles = vec![
889    ///     (0, 1, 4).into(), // 0
890    ///     (4, 3, 0).into(), // 1
891    ///     (1, 2, 5).into(), // 2
892    ///     (5, 4, 1).into(), // 3
893    /// ];
894    ///
895    /// let mesh = NavMesh::new(vertices, triangles).unwrap();
896    /// let path = mesh.find_path_triangles(1, 2).unwrap().0;
897    /// assert_eq!(path, vec![1, 0, 3, 2]);
898    /// ```
899    #[inline]
900    pub fn find_path_triangles(&self, from: usize, to: usize) -> Option<(Vec<usize>, Scalar)> {
901        let to = self.nodes[to];
902        astar(
903            &self.graph,
904            self.nodes[from],
905            |n| n == to,
906            |e| {
907                let a = self.areas[self.nodes_map[&e.source()]].cost;
908                let b = self.areas[self.nodes_map[&e.target()]].cost;
909                *e.weight() * a * b
910            },
911            |_| 0.0,
912        )
913        .map(|(c, v)| (iter!(v).map(|v| self.nodes_map[&v]).collect(), c))
914    }
915
916    /// Find closest triangle on nav mesh closest to given point.
917    ///
918    /// # Arguments
919    /// * `point` - query point.
920    /// * `query` - query quality.
921    ///
922    /// # Returns
923    /// `Some` with nav mesh triangle index if found or `None` otherwise.
924    pub fn find_closest_triangle(&self, point: NavVec3, query: NavQuery) -> Option<usize> {
925        match query {
926            NavQuery::Accuracy => self.rtree.nearest_neighbor(&point).map(|t| t.index),
927            NavQuery::ClosestFirst => self.rtree.close_neighbor(&point).map(|t| t.index),
928            NavQuery::Closest => self
929                .rtree
930                .nearest_neighbors(&point)
931                .into_iter()
932                .map(|o| (o.distance2(&point), o))
933                .fold(None, |a: Option<(Scalar, &NavSpatialObject)>, i| {
934                    if let Some(a) = a {
935                        if i.0 < a.0 {
936                            Some(i)
937                        } else {
938                            Some(a)
939                        }
940                    } else {
941                        Some(i)
942                    }
943                })
944                .map(|(_, t)| t.index),
945        }
946    }
947
948    /// Find target point on nav mesh path.
949    ///
950    /// # Arguments
951    /// * `path` - path points.
952    /// * `point` - source point.
953    /// * `offset` - target point offset from the source on path.
954    ///
955    /// # Returns
956    /// `Some` with point and distance from path start point if found or `None` otherwise.
957    pub fn path_target_point(
958        path: &[NavVec3],
959        point: NavVec3,
960        offset: Scalar,
961    ) -> Option<(NavVec3, Scalar)> {
962        let s = Self::project_on_path(path, point, offset);
963        if let Some(p) = Self::point_on_path(path, s) {
964            Some((p, s))
965        } else {
966            None
967        }
968    }
969
970    /// Project point on nav mesh path.
971    ///
972    /// # Arguments
973    /// * `path` - path points.
974    /// * `point` - source point.
975    /// * `offset` - target point offset from the source on path.
976    ///
977    /// # Returns
978    /// Distance from path start point.
979    pub fn project_on_path(path: &[NavVec3], point: NavVec3, offset: Scalar) -> Scalar {
980        let p = match path.len() {
981            0 | 1 => 0.0,
982            2 => Self::project_on_line(path[0], path[1], point),
983            _ => {
984                path.windows(2)
985                    .scan(0.0, |state, pair| {
986                        let dist = *state;
987                        *state += (pair[1] - pair[0]).magnitude();
988                        Some((dist, pair))
989                    })
990                    .map(|(dist, pair)| {
991                        let (p, s) = Self::point_on_line(pair[0], pair[1], point);
992                        (dist + s, (p - point).sqr_magnitude())
993                    })
994                    .min_by(|(_, a), (_, b)| a.partial_cmp(&b).unwrap())
995                    .unwrap()
996                    .0
997            }
998        };
999        (p + offset).max(0.0).min(Self::path_length(path))
1000    }
1001
1002    /// Find point on nav mesh path at given distance.
1003    ///
1004    /// # Arguments
1005    /// * `path` - path points.
1006    /// * `s` - Distance from path start point.
1007    ///
1008    /// # Returns
1009    /// `Some` with point on path ot `None` otherwise.
1010    pub fn point_on_path(path: &[NavVec3], mut s: Scalar) -> Option<NavVec3> {
1011        match path.len() {
1012            0 | 1 => None,
1013            2 => Some(NavVec3::unproject(
1014                path[0],
1015                path[1],
1016                s / Self::path_length(path),
1017            )),
1018            _ => {
1019                for pair in path.windows(2) {
1020                    let d = (pair[1] - pair[0]).magnitude();
1021                    if s <= d {
1022                        return Some(NavVec3::unproject(pair[0], pair[1], s / d));
1023                    }
1024                    s -= d;
1025                }
1026                None
1027            }
1028        }
1029    }
1030
1031    /// Calculate path length.
1032    ///
1033    /// # Arguments
1034    /// * `path` - path points.
1035    ///
1036    /// # Returns
1037    /// Path length.
1038    pub fn path_length(path: &[NavVec3]) -> Scalar {
1039        match path.len() {
1040            0 | 1 => 0.0,
1041            2 => (path[1] - path[0]).magnitude(),
1042            _ => path
1043                .windows(2)
1044                .fold(0.0, |a, pair| a + (pair[1] - pair[0]).magnitude()),
1045        }
1046    }
1047
1048    fn project_on_line(from: NavVec3, to: NavVec3, point: NavVec3) -> Scalar {
1049        let d = (to - from).magnitude();
1050        let p = point.project(from, to);
1051        d * p
1052    }
1053
1054    fn point_on_line(from: NavVec3, to: NavVec3, point: NavVec3) -> (NavVec3, Scalar) {
1055        let d = (to - from).magnitude();
1056        let p = point.project(from, to);
1057        if p <= 0.0 {
1058            (from, 0.0)
1059        } else if p >= 1.0 {
1060            (to, d)
1061        } else {
1062            (NavVec3::unproject(from, to, p), p * d)
1063        }
1064    }
1065}