1#![warn(missing_docs)]
27
28use crate::{
29 core::{
30 algebra::{Point3, Vector3},
31 arrayvec::ArrayVec,
32 math::{self, plane::Plane, ray::Ray, PositionProvider, TriangleDefinition, Vector3Ext},
33 reflect::prelude::*,
34 visitor::{Visit, VisitResult, Visitor},
35 },
36 scene::mesh::{
37 buffer::{VertexAttributeUsage, VertexReadTrait},
38 Mesh,
39 },
40 utils::{
41 astar::{Graph, PathError, PathKind, VertexData, VertexDataProvider},
42 raw_mesh::{RawMeshBuilder, RawVertex},
43 },
44};
45use fxhash::{FxBuildHasher, FxHashMap};
46use fyrox_core::math::octree::{Octree, OctreeNode};
47use std::ops::{Deref, DerefMut};
48
49#[derive(Clone, Debug, Default, Visit)]
50struct Vertex {
51 triangle_index: usize,
52 data: VertexData,
53}
54
55impl Deref for Vertex {
56 type Target = VertexData;
57
58 fn deref(&self) -> &Self::Target {
59 &self.data
60 }
61}
62
63impl DerefMut for Vertex {
64 fn deref_mut(&mut self) -> &mut Self::Target {
65 &mut self.data
66 }
67}
68
69impl PositionProvider for Vertex {
70 fn position(&self) -> Vector3<f32> {
71 self.data.position
72 }
73}
74
75impl VertexDataProvider for Vertex {}
76
77#[derive(Clone, Debug, Default, Reflect)]
79#[reflect(hide_all)]
80pub struct Navmesh {
81 octree: Octree,
82 triangles: Vec<TriangleDefinition>,
83 vertices: Vec<Vector3<f32>>,
84 graph: Graph<Vertex>,
85}
86
87impl PartialEq for Navmesh {
88 fn eq(&self, other: &Self) -> bool {
89 self.triangles == other.triangles && self.vertices == other.vertices
90 }
91}
92
93impl Visit for Navmesh {
94 fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
95 let mut region = visitor.enter_region(name)?;
96
97 self.vertices.visit("Vertices", &mut region)?;
98 self.triangles.visit("Triangles", &mut region)?;
99
100 drop(region);
101
102 if visitor.is_reading() {
104 let raw_triangles = self
105 .triangles
106 .iter()
107 .map(|t| {
108 [
109 self.vertices[t[0] as usize],
110 self.vertices[t[1] as usize],
111 self.vertices[t[2] as usize],
112 ]
113 })
114 .collect::<Vec<[Vector3<f32>; 3]>>();
115
116 self.octree = Octree::new(&raw_triangles, 32);
117 }
118
119 let graph = make_graph(&self.triangles, &self.vertices);
120 self.graph = graph;
121
122 Ok(())
123 }
124}
125
126#[derive(Copy, Clone, Debug)]
127struct Portal {
128 left: usize,
129 right: usize,
130}
131
132fn triangle_area_2d(a: Vector3<f32>, b: Vector3<f32>, c: Vector3<f32>) -> f32 {
133 let abx = b[0] - a[0];
134 let abz = b[2] - a[2];
135 let acx = c[0] - a[0];
136 let acz = c[2] - a[2];
137 acx * abz - abx * acz
138}
139
140#[derive(PartialEq, Clone, Copy, Eq)]
141enum Winding {
142 Clockwise,
143 CounterClockwise,
144}
145
146fn winding(a: Vector3<f32>, b: Vector3<f32>, c: Vector3<f32>) -> Winding {
147 if triangle_area_2d(a, b, c) > 0.0 {
148 Winding::Clockwise
149 } else {
150 Winding::CounterClockwise
151 }
152}
153
154fn make_graph(triangles: &[TriangleDefinition], vertices: &[Vector3<f32>]) -> Graph<Vertex> {
155 let mut graph = Graph::new();
156
157 for (triangle_index, triangle) in triangles.iter().enumerate() {
159 let a = vertices[triangle[0] as usize];
160 let b = vertices[triangle[1] as usize];
161 let c = vertices[triangle[2] as usize];
162
163 let center = (a + b + c).scale(1.0 / 3.0);
164 graph.add_vertex(Vertex {
165 triangle_index,
166 data: VertexData::new(center),
167 });
168 }
169
170 #[derive(Copy, Clone, PartialEq, Hash, Eq)]
172 struct Edge {
173 a: usize,
174 b: usize,
175 }
176
177 let total_edge_count = triangles.len() * 3;
178 let mut edge_triangle_map =
179 FxHashMap::with_capacity_and_hasher(total_edge_count, FxBuildHasher::default());
180
181 for (triangle_index, triangle) in triangles.iter().enumerate() {
182 for edge in triangle.edges() {
183 edge_triangle_map.insert(
184 Edge {
185 a: edge.a as usize,
186 b: edge.b as usize,
187 },
188 triangle_index,
189 );
190 }
191 }
192
193 for (triangle_index, triangle) in triangles.iter().enumerate() {
195 for edge in triangle.edges() {
196 let adjacent_edge = Edge {
198 a: edge.b as usize,
199 b: edge.a as usize,
200 };
201
202 if let Some(adjacent_triangle_index) = edge_triangle_map.get(&adjacent_edge) {
203 graph.link_bidirect(triangle_index, *adjacent_triangle_index);
204 }
205 }
206 }
207
208 graph
209}
210
211pub struct NavmeshModificationContext<'a> {
214 navmesh: &'a mut Navmesh,
215}
216
217impl Drop for NavmeshModificationContext<'_> {
218 fn drop(&mut self) {
219 let graph = make_graph(&self.navmesh.triangles, &self.navmesh.vertices);
220 self.navmesh.graph = graph;
221 }
222}
223
224impl NavmeshModificationContext<'_> {
225 pub fn add_triangle(&mut self, triangle: TriangleDefinition) -> u32 {
228 let index = self.navmesh.triangles.len();
229 self.navmesh.triangles.push(triangle);
230 index as u32
231 }
232
233 pub fn remove_triangle(&mut self, index: usize) -> TriangleDefinition {
235 self.navmesh.triangles.remove(index)
236 }
237
238 pub fn pop_triangle(&mut self) -> Option<TriangleDefinition> {
241 if self.navmesh.triangles.is_empty() {
242 None
243 } else {
244 Some(self.remove_triangle(self.navmesh.triangles.len() - 1))
245 }
246 }
247
248 pub fn remove_vertex(&mut self, index: usize) -> Vector3<f32> {
251 let mut i = 0;
253 while i < self.navmesh.triangles.len() {
254 if self.navmesh.triangles[i]
255 .indices()
256 .contains(&(index as u32))
257 {
258 self.remove_triangle(i);
259 } else {
260 i += 1;
261 }
262 }
263
264 for triangle in self.navmesh.triangles.iter_mut() {
276 for other_vertex_index in triangle.indices_mut() {
277 if *other_vertex_index > index as u32 {
278 *other_vertex_index -= 1;
279 }
280 }
281 }
282
283 self.navmesh.vertices.remove(index)
284 }
285
286 pub fn vertices_mut(&mut self) -> &mut [Vector3<f32>] {
288 &mut self.navmesh.vertices
289 }
290
291 pub fn add_vertex(&mut self, vertex: Vector3<f32>) -> u32 {
293 let index = self.navmesh.vertices.len();
294 self.navmesh.vertices.push(vertex);
295 index as u32
296 }
297
298 pub fn pop_vertex(&mut self) -> Option<Vector3<f32>> {
300 if self.navmesh.vertices.is_empty() {
301 None
302 } else {
303 Some(self.remove_vertex(self.navmesh.vertices.len() - 1))
304 }
305 }
306
307 pub fn insert_vertex(&mut self, index: u32, vertex: Vector3<f32>) {
309 self.navmesh.vertices.insert(index as usize, vertex);
310
311 for triangle in self.navmesh.triangles.iter_mut() {
323 for other_vertex_index in triangle.indices_mut() {
324 if *other_vertex_index >= index {
325 *other_vertex_index += 1;
326 }
327 }
328 }
329 }
330}
331
332impl Navmesh {
333 pub fn new(triangles: Vec<TriangleDefinition>, vertices: Vec<Vector3<f32>>) -> Self {
337 let raw_triangles = triangles
339 .iter()
340 .map(|t| {
341 [
342 vertices[t[0] as usize],
343 vertices[t[1] as usize],
344 vertices[t[2] as usize],
345 ]
346 })
347 .collect::<Vec<[Vector3<f32>; 3]>>();
348
349 Self {
350 graph: make_graph(&triangles, &vertices),
351 triangles,
352 vertices,
353 octree: Octree::new(&raw_triangles, 32),
354 }
355 }
356
357 pub fn from_mesh(mesh: &Mesh) -> Self {
376 let mut builder = RawMeshBuilder::<RawVertex>::default();
378 let global_transform = mesh.global_transform();
379 for surface in mesh.surfaces() {
380 let shared_data = surface.data();
381 let shared_data = shared_data.data_ref();
382
383 let vertex_buffer = &shared_data.vertex_buffer;
384 for triangle in shared_data.geometry_buffer.iter() {
385 builder.insert(RawVertex::from(
386 global_transform
387 .transform_point(&Point3::from(
388 vertex_buffer
389 .get(triangle[0] as usize)
390 .unwrap()
391 .read_3_f32(VertexAttributeUsage::Position)
392 .unwrap(),
393 ))
394 .coords,
395 ));
396 builder.insert(RawVertex::from(
397 global_transform
398 .transform_point(&Point3::from(
399 vertex_buffer
400 .get(triangle[1] as usize)
401 .unwrap()
402 .read_3_f32(VertexAttributeUsage::Position)
403 .unwrap(),
404 ))
405 .coords,
406 ));
407 builder.insert(RawVertex::from(
408 global_transform
409 .transform_point(&Point3::from(
410 vertex_buffer
411 .get(triangle[2] as usize)
412 .unwrap()
413 .read_3_f32(VertexAttributeUsage::Position)
414 .unwrap(),
415 ))
416 .coords,
417 ));
418 }
419 }
420
421 let mesh = builder.build();
422
423 Navmesh::new(
424 mesh.triangles,
425 mesh.vertices
426 .into_iter()
427 .map(|v| Vector3::new(v.x, v.y, v.z))
428 .collect::<Vec<_>>(),
429 )
430 }
431
432 pub fn query_closest(&self, query_point: Vector3<f32>) -> Option<(Vector3<f32>, usize)> {
442 let mut closest = None;
443 let mut closest_distance = f32::MAX;
444
445 self.query_closest_internal(
467 &mut closest,
468 &mut closest_distance,
469 0..self.triangles.len(),
470 query_point,
471 );
472
473 closest
474 }
475
476 fn query_closest_internal(
477 &self,
478 closest: &mut Option<(Vector3<f32>, usize)>,
479 closest_distance: &mut f32,
480 triangles: impl Iterator<Item = usize>,
481 query_point: Vector3<f32>,
482 ) {
483 for triangle_index in triangles {
484 let triangle = &self.triangles[triangle_index];
485 let a = self.vertices[triangle[0] as usize];
486 let b = self.vertices[triangle[1] as usize];
487 let c = self.vertices[triangle[2] as usize];
488
489 let Some(plane) = Plane::from_triangle(&a, &b, &c) else {
490 continue;
491 };
492 let projection = plane.project(&query_point);
493 if math::is_point_inside_triangle(&projection, &[a, b, c]) {
494 let sqr_distance = query_point.sqr_distance(&projection);
495 if sqr_distance < *closest_distance {
496 *closest_distance = sqr_distance;
497 *closest = Some((projection, triangle_index));
498 }
499 }
500
501 for edge in self.triangles[triangle_index].edges() {
504 let a = self.vertices[edge.a as usize];
505 let b = self.vertices[edge.b as usize];
506 let ray = Ray::from_two_points(a, b);
507 let t = ray.project_point(&query_point);
508 if (0.0..=1.0).contains(&t) {
509 let edge_pt_projection = ray.get_point(t);
510 let sqr_distance = query_point.sqr_distance(&edge_pt_projection);
511 if sqr_distance < *closest_distance {
512 *closest_distance = sqr_distance;
513 *closest = Some((ray.get_point(t), triangle_index));
514 }
515 }
516 }
517
518 for pt in [a, b, c] {
520 let sqr_distance = query_point.sqr_distance(&pt);
521 if sqr_distance < *closest_distance {
522 *closest_distance = sqr_distance;
523 *closest = Some((pt, triangle_index));
524 }
525 }
526 }
527 }
528
529 pub fn modify(&mut self) -> NavmeshModificationContext {
532 NavmeshModificationContext { navmesh: self }
533 }
534
535 pub fn triangles(&self) -> &[TriangleDefinition] {
537 &self.triangles
538 }
539
540 pub fn vertices(&self) -> &[Vector3<f32>] {
542 &self.vertices
543 }
544
545 pub fn octree(&self) -> &Octree {
547 &self.octree
548 }
549
550 pub fn build_path(
569 &self,
570 from: usize,
571 to: usize,
572 path: &mut Vec<Vector3<f32>>,
573 ) -> Result<PathKind, PathError> {
574 self.graph.build_positional_path(from, to, path)
575 }
576
577 pub fn ray_cast(&self, ray: Ray) -> Option<(Vector3<f32>, usize)> {
579 let mut buffer = ArrayVec::<usize, 128>::new();
580
581 self.octree.ray_query_static(&ray, &mut buffer);
582
583 let mut closest_distance = f32::MAX;
584 let mut result = None;
585 for node in buffer.into_iter() {
586 if let OctreeNode::Leaf { indices, .. } = self.octree.node(node) {
587 for &index in indices {
588 let triangle = self.triangles[index as usize];
589 let a = self.vertices()[triangle[0] as usize];
590 let b = self.vertices()[triangle[1] as usize];
591 let c = self.vertices()[triangle[2] as usize];
592
593 if let Some(intersection) = ray.triangle_intersection_point(&[a, b, c]) {
594 let distance = intersection.metric_distance(&ray.origin);
595 if distance < closest_distance {
596 closest_distance = distance;
597 result = Some((intersection, index as usize));
598 }
599 }
600 }
601 } else {
602 unreachable!()
603 }
604 }
605
606 result
607 }
608
609 fn portal_between(&self, src_triangle: usize, dest_triangle: usize) -> Option<Portal> {
610 let src_triangle = self.triangles.get(src_triangle)?;
611 let dest_triangle = self.triangles.get(dest_triangle)?;
612 for src_triangle_edge in src_triangle.edges() {
613 for dest_triangle_edge in dest_triangle.edges() {
614 if src_triangle_edge == dest_triangle_edge {
615 let a = self.vertices[src_triangle[0] as usize];
616 let b = self.vertices[src_triangle[1] as usize];
617 let c = self.vertices[src_triangle[2] as usize];
618
619 return if winding(a, b, c) == Winding::Clockwise {
620 Some(Portal {
621 left: src_triangle_edge.a as usize,
622 right: src_triangle_edge.b as usize,
623 })
624 } else {
625 Some(Portal {
626 left: src_triangle_edge.b as usize,
627 right: src_triangle_edge.a as usize,
628 })
629 };
630 }
631 }
632 }
633 None
634 }
635}
636
637#[derive(Visit, Clone, Debug)]
640#[visit(optional)]
641pub struct NavmeshAgent {
642 path: Vec<Vector3<f32>>,
643 current: u32,
644 position: Vector3<f32>,
645 last_warp_position: Vector3<f32>,
646 target: Vector3<f32>,
647 last_target_position: Vector3<f32>,
648 recalculation_threshold: f32,
649 speed: f32,
650 path_dirty: bool,
651 radius: f32,
652 interpolator: f32,
653}
654
655impl Default for NavmeshAgent {
656 fn default() -> Self {
657 Self::new()
658 }
659}
660
661impl NavmeshAgent {
662 pub fn new() -> Self {
664 Self {
665 path: vec![],
666 current: 0,
667 position: Default::default(),
668 last_warp_position: Default::default(),
669 target: Default::default(),
670 last_target_position: Default::default(),
671 recalculation_threshold: 0.25,
672 speed: 1.5,
673 path_dirty: true,
674 radius: 0.2,
675 interpolator: 0.0,
676 }
677 }
678
679 pub fn position(&self) -> Vector3<f32> {
681 self.position
682 }
683
684 pub fn path(&self) -> &[Vector3<f32>] {
686 &self.path
687 }
688
689 pub fn set_speed(&mut self, speed: f32) {
691 self.speed = speed;
692 }
693
694 pub fn speed(&self) -> f32 {
696 self.speed
697 }
698
699 pub fn set_threshold(&mut self, threshold: f32) {
703 self.recalculation_threshold = threshold;
704 }
705
706 pub fn threshold(&self) -> f32 {
709 self.recalculation_threshold
710 }
711
712 pub fn set_radius(&mut self, radius: f32) {
716 self.radius = radius;
717 }
718
719 pub fn radius(&self) -> f32 {
722 self.radius
723 }
724}
725
726impl NavmeshAgent {
727 pub fn calculate_path(
730 &mut self,
731 navmesh: &Navmesh,
732 src_point: Vector3<f32>,
733 dest_point: Vector3<f32>,
734 ) -> Result<PathKind, PathError> {
735 self.path.clear();
736
737 self.current = 0;
738 self.interpolator = 0.0;
739
740 if let Some((src_point_on_navmesh, src_triangle)) = navmesh.query_closest(src_point) {
741 if let Some((dest_point_on_navmesh, dest_triangle)) = navmesh.query_closest(dest_point)
742 {
743 if src_triangle == dest_triangle {
744 self.path.push(src_point_on_navmesh);
745 self.path.push(dest_point_on_navmesh);
746
747 return Ok(PathKind::Full);
748 }
749
750 let mut path_triangle_indices = Vec::new();
751 let path_kind = navmesh.graph.build_indexed_path(
752 src_triangle,
753 dest_triangle,
754 &mut path_triangle_indices,
755 )?;
756
757 path_triangle_indices.reverse();
758
759 self.straighten_path(
760 navmesh,
761 src_point_on_navmesh,
762 dest_point_on_navmesh,
763 &path_triangle_indices,
764 );
765
766 return Ok(path_kind);
767 }
768 }
769
770 Err(PathError::Empty)
771 }
772
773 fn straighten_path(
774 &mut self,
775 navmesh: &Navmesh,
776 src_position: Vector3<f32>,
777 dest_position: Vector3<f32>,
778 path_triangles: &[usize],
779 ) {
780 self.path.push(src_position);
781
782 if path_triangles.len() > 1 {
783 let mut funnel_apex = src_position;
784 let mut funnel_vertices = [funnel_apex; 2];
785 let mut side_indices = [0; 2];
786 let side_signs = [1.0, -1.0];
787
788 let mut i = 0;
789 while i < path_triangles.len() {
790 let portal_vertices = if i + 1 < path_triangles.len() {
791 let portal = navmesh
792 .portal_between(path_triangles[i], path_triangles[i + 1])
793 .unwrap();
794
795 let mut left = navmesh.vertices[portal.left];
796 let mut right = navmesh.vertices[portal.right];
797
798 if self.radius > 0.0 {
799 let delta = right - left;
800 let len = delta.norm();
801 let offset = delta.scale(self.radius.min(len * 0.5) / len);
802
803 left += offset;
804 right -= offset;
805 }
806
807 [left, right]
808 } else {
809 [dest_position, dest_position]
810 };
811
812 for current in 0..2 {
813 let opposite = 1 - current;
814 let side_sign = side_signs[current];
815 if side_sign
816 * triangle_area_2d(
817 funnel_apex,
818 funnel_vertices[current],
819 portal_vertices[current],
820 )
821 >= 0.0
822 {
823 if funnel_apex == funnel_vertices[current]
824 || side_sign
825 * triangle_area_2d(
826 funnel_apex,
827 funnel_vertices[opposite],
828 portal_vertices[current],
829 )
830 < 0.0
831 {
832 funnel_vertices[current] = portal_vertices[current];
833 side_indices[current] = i;
834 } else {
835 funnel_apex = funnel_vertices[opposite];
836 funnel_vertices = [funnel_apex; 2];
837
838 self.path.push(funnel_apex);
839
840 i = side_indices[opposite];
841 side_indices[current] = i;
842
843 break;
844 }
845 }
846 }
847
848 i += 1;
849 }
850 }
851
852 self.path.push(dest_position);
853 }
854
855 pub fn update(&mut self, dt: f32, navmesh: &Navmesh) -> Result<PathKind, PathError> {
858 if self.path_dirty {
859 self.calculate_path(navmesh, self.position, self.target)?;
860 self.path_dirty = false;
861 }
862
863 if let Some(source) = self.path.get(self.current as usize) {
864 if let Some(destination) = self.path.get((self.current + 1) as usize) {
865 let len = destination.metric_distance(source);
866 self.position = source.lerp(destination, self.interpolator.clamp(0.0, 1.0));
867 self.interpolator += (self.speed * dt) / len.max(f32::EPSILON);
868 if self.interpolator >= 1.0 {
869 self.current += 1;
870 self.interpolator = 0.0;
871 } else if self.interpolator < 0.0 {
872 self.current = self.current.saturating_sub(1);
873 self.interpolator = 1.0;
874 }
875 }
876 }
877
878 Ok(PathKind::Full)
879 }
880
881 pub fn steering_target(&self) -> Option<Vector3<f32>> {
884 self.path
885 .get(self.current as usize + 1)
886 .or_else(|| self.path.last())
887 .cloned()
888 }
889
890 pub fn set_target(&mut self, new_target: Vector3<f32>) {
892 if new_target.metric_distance(&self.last_target_position) >= self.recalculation_threshold {
893 self.path_dirty = true;
894 self.last_target_position = new_target;
895 }
896
897 self.target = new_target;
898 }
899
900 pub fn target(&self) -> Vector3<f32> {
902 self.target
903 }
904
905 pub fn set_position(&mut self, new_position: Vector3<f32>) {
907 if new_position.metric_distance(&self.last_warp_position) >= self.recalculation_threshold {
908 self.path_dirty = true;
909 self.last_warp_position = new_position;
910 }
911
912 self.position = new_position;
913 }
914}
915
916pub struct NavmeshAgentBuilder {
918 position: Vector3<f32>,
919 target: Vector3<f32>,
920 recalculation_threshold: f32,
921 speed: f32,
922}
923
924impl Default for NavmeshAgentBuilder {
925 fn default() -> Self {
926 Self::new()
927 }
928}
929
930impl NavmeshAgentBuilder {
931 pub fn new() -> Self {
933 Self {
934 position: Default::default(),
935 target: Default::default(),
936 recalculation_threshold: 0.25,
937 speed: 1.5,
938 }
939 }
940
941 pub fn with_position(mut self, position: Vector3<f32>) -> Self {
943 self.position = position;
944 self
945 }
946
947 pub fn with_target(mut self, position: Vector3<f32>) -> Self {
949 self.target = position;
950 self
951 }
952
953 pub fn with_recalculation_threshold(mut self, threshold: f32) -> Self {
955 self.recalculation_threshold = threshold;
956 self
957 }
958
959 pub fn with_speed(mut self, speed: f32) -> Self {
961 self.speed = speed;
962 self
963 }
964
965 pub fn build(self) -> NavmeshAgent {
967 NavmeshAgent {
968 position: self.position,
969 last_warp_position: self.position,
970 target: self.target,
971 last_target_position: self.target,
972 recalculation_threshold: self.recalculation_threshold,
973 speed: self.speed,
974 ..Default::default()
975 }
976 }
977}
978
979#[cfg(test)]
980mod test {
981 use crate::{
982 core::{algebra::Vector3, math::TriangleDefinition},
983 utils::navmesh::{Navmesh, NavmeshAgent},
984 };
985
986 #[test]
987 fn test_navmesh() {
988 let navmesh = Navmesh::new(
989 vec![
990 TriangleDefinition([0, 1, 3]),
991 TriangleDefinition([1, 2, 3]),
992 TriangleDefinition([2, 5, 3]),
993 TriangleDefinition([2, 4, 5]),
994 TriangleDefinition([4, 7, 5]),
995 TriangleDefinition([4, 6, 7]),
996 ],
997 vec![
998 Vector3::new(0.0, 0.0, 0.0),
999 Vector3::new(0.0, 0.0, 1.0),
1000 Vector3::new(1.0, 0.0, 1.0),
1001 Vector3::new(1.0, 0.0, 0.0),
1002 Vector3::new(2.0, 0.0, 1.0),
1003 Vector3::new(2.0, 0.0, 0.0),
1004 Vector3::new(3.0, 0.0, 1.0),
1005 Vector3::new(3.0, 0.0, 0.0),
1006 ],
1007 );
1008
1009 let mut agent = NavmeshAgent::new();
1010
1011 agent.set_target(Vector3::new(3.0, 0.0, 1.0));
1012 agent.update(1.0 / 60.0, &navmesh).unwrap();
1013
1014 let graph = &navmesh.graph;
1015
1016 assert_eq!(graph.vertices.len(), 6);
1017 assert_eq!(graph.vertices[0].neighbours[0], 1);
1018
1019 assert_eq!(graph.vertices[1].neighbours[0], 0);
1020 assert_eq!(graph.vertices[1].neighbours[1], 2);
1021
1022 assert_eq!(graph.vertices[2].neighbours[0], 1);
1023 assert_eq!(graph.vertices[2].neighbours[1], 3);
1024
1025 assert_eq!(graph.vertices[3].neighbours[0], 2);
1026 assert_eq!(graph.vertices[3].neighbours[1], 4);
1027
1028 assert_eq!(graph.vertices[4].neighbours[0], 3);
1029 assert_eq!(graph.vertices[4].neighbours[1], 5);
1030
1031 assert_eq!(graph.vertices[5].neighbours[0], 4);
1032
1033 assert_eq!(
1034 agent.path,
1035 vec![
1036 Vector3::new(0.0, 0.0, 0.0),
1037 Vector3::new(3.0, 0.0, 1.0),
1038 Vector3::new(3.0, 0.0, 1.0)
1039 ]
1040 );
1041 }
1042}