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, GraphVertex, 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 if region.is_reading() {
99 let mut pathfinder = Graph::<GraphVertex>::new();
100 if pathfinder.visit("PathFinder", &mut region).is_ok() {
101 self.vertices = pathfinder
102 .vertices
103 .iter()
104 .map(|v| v.position)
105 .collect::<Vec<_>>();
106 } else {
107 self.vertices.visit("Vertices", &mut region)?;
108 }
109 } else {
110 self.vertices.visit("Vertices", &mut region)?;
111 }
112
113 self.triangles.visit("Triangles", &mut region)?;
114
115 drop(region);
116
117 if visitor.is_reading() {
119 let raw_triangles = self
120 .triangles
121 .iter()
122 .map(|t| {
123 [
124 self.vertices[t[0] as usize],
125 self.vertices[t[1] as usize],
126 self.vertices[t[2] as usize],
127 ]
128 })
129 .collect::<Vec<[Vector3<f32>; 3]>>();
130
131 self.octree = Octree::new(&raw_triangles, 32);
132 }
133
134 let graph = make_graph(&self.triangles, &self.vertices);
135 self.graph = graph;
136
137 Ok(())
138 }
139}
140
141#[derive(Copy, Clone, Debug)]
142struct Portal {
143 left: usize,
144 right: usize,
145}
146
147fn triangle_area_2d(a: Vector3<f32>, b: Vector3<f32>, c: Vector3<f32>) -> f32 {
148 let abx = b[0] - a[0];
149 let abz = b[2] - a[2];
150 let acx = c[0] - a[0];
151 let acz = c[2] - a[2];
152 acx * abz - abx * acz
153}
154
155#[derive(PartialEq, Clone, Copy, Eq)]
156enum Winding {
157 Clockwise,
158 CounterClockwise,
159}
160
161fn winding(a: Vector3<f32>, b: Vector3<f32>, c: Vector3<f32>) -> Winding {
162 if triangle_area_2d(a, b, c) > 0.0 {
163 Winding::Clockwise
164 } else {
165 Winding::CounterClockwise
166 }
167}
168
169fn make_graph(triangles: &[TriangleDefinition], vertices: &[Vector3<f32>]) -> Graph<Vertex> {
170 let mut graph = Graph::new();
171
172 for (triangle_index, triangle) in triangles.iter().enumerate() {
174 let a = vertices[triangle[0] as usize];
175 let b = vertices[triangle[1] as usize];
176 let c = vertices[triangle[2] as usize];
177
178 let center = (a + b + c).scale(1.0 / 3.0);
179 graph.add_vertex(Vertex {
180 triangle_index,
181 data: VertexData::new(center),
182 });
183 }
184
185 #[derive(Copy, Clone, PartialEq, Hash, Eq)]
187 struct Edge {
188 a: usize,
189 b: usize,
190 }
191
192 let total_edge_count = triangles.len() * 3;
193 let mut edge_triangle_map =
194 FxHashMap::with_capacity_and_hasher(total_edge_count, FxBuildHasher::default());
195
196 for (triangle_index, triangle) in triangles.iter().enumerate() {
197 for edge in triangle.edges() {
198 edge_triangle_map.insert(
199 Edge {
200 a: edge.a as usize,
201 b: edge.b as usize,
202 },
203 triangle_index,
204 );
205 }
206 }
207
208 for (triangle_index, triangle) in triangles.iter().enumerate() {
210 for edge in triangle.edges() {
211 let adjacent_edge = Edge {
213 a: edge.b as usize,
214 b: edge.a as usize,
215 };
216
217 if let Some(adjacent_triangle_index) = edge_triangle_map.get(&adjacent_edge) {
218 graph.link_bidirect(triangle_index, *adjacent_triangle_index);
219 }
220 }
221 }
222
223 graph
224}
225
226pub struct NavmeshModificationContext<'a> {
229 navmesh: &'a mut Navmesh,
230}
231
232impl Drop for NavmeshModificationContext<'_> {
233 fn drop(&mut self) {
234 let graph = make_graph(&self.navmesh.triangles, &self.navmesh.vertices);
235 self.navmesh.graph = graph;
236 }
237}
238
239impl NavmeshModificationContext<'_> {
240 pub fn add_triangle(&mut self, triangle: TriangleDefinition) -> u32 {
243 let index = self.navmesh.triangles.len();
244 self.navmesh.triangles.push(triangle);
245 index as u32
246 }
247
248 pub fn remove_triangle(&mut self, index: usize) -> TriangleDefinition {
250 self.navmesh.triangles.remove(index)
251 }
252
253 pub fn pop_triangle(&mut self) -> Option<TriangleDefinition> {
256 if self.navmesh.triangles.is_empty() {
257 None
258 } else {
259 Some(self.remove_triangle(self.navmesh.triangles.len() - 1))
260 }
261 }
262
263 pub fn remove_vertex(&mut self, index: usize) -> Vector3<f32> {
266 let mut i = 0;
268 while i < self.navmesh.triangles.len() {
269 if self.navmesh.triangles[i]
270 .indices()
271 .contains(&(index as u32))
272 {
273 self.remove_triangle(i);
274 } else {
275 i += 1;
276 }
277 }
278
279 for triangle in self.navmesh.triangles.iter_mut() {
291 for other_vertex_index in triangle.indices_mut() {
292 if *other_vertex_index > index as u32 {
293 *other_vertex_index -= 1;
294 }
295 }
296 }
297
298 self.navmesh.vertices.remove(index)
299 }
300
301 pub fn vertices_mut(&mut self) -> &mut [Vector3<f32>] {
303 &mut self.navmesh.vertices
304 }
305
306 pub fn add_vertex(&mut self, vertex: Vector3<f32>) -> u32 {
308 let index = self.navmesh.vertices.len();
309 self.navmesh.vertices.push(vertex);
310 index as u32
311 }
312
313 pub fn pop_vertex(&mut self) -> Option<Vector3<f32>> {
315 if self.navmesh.vertices.is_empty() {
316 None
317 } else {
318 Some(self.remove_vertex(self.navmesh.vertices.len() - 1))
319 }
320 }
321
322 pub fn insert_vertex(&mut self, index: u32, vertex: Vector3<f32>) {
324 self.navmesh.vertices.insert(index as usize, vertex);
325
326 for triangle in self.navmesh.triangles.iter_mut() {
338 for other_vertex_index in triangle.indices_mut() {
339 if *other_vertex_index >= index {
340 *other_vertex_index += 1;
341 }
342 }
343 }
344 }
345}
346
347impl Navmesh {
348 pub fn new(triangles: Vec<TriangleDefinition>, vertices: Vec<Vector3<f32>>) -> Self {
352 let raw_triangles = triangles
354 .iter()
355 .map(|t| {
356 [
357 vertices[t[0] as usize],
358 vertices[t[1] as usize],
359 vertices[t[2] as usize],
360 ]
361 })
362 .collect::<Vec<[Vector3<f32>; 3]>>();
363
364 Self {
365 graph: make_graph(&triangles, &vertices),
366 triangles,
367 vertices,
368 octree: Octree::new(&raw_triangles, 32),
369 }
370 }
371
372 pub fn from_mesh(mesh: &Mesh) -> Self {
391 let mut builder = RawMeshBuilder::<RawVertex>::default();
393 let global_transform = mesh.global_transform();
394 for surface in mesh.surfaces() {
395 let shared_data = surface.data();
396 let shared_data = shared_data.data_ref();
397
398 let vertex_buffer = &shared_data.vertex_buffer;
399 for triangle in shared_data.geometry_buffer.iter() {
400 builder.insert(RawVertex::from(
401 global_transform
402 .transform_point(&Point3::from(
403 vertex_buffer
404 .get(triangle[0] as usize)
405 .unwrap()
406 .read_3_f32(VertexAttributeUsage::Position)
407 .unwrap(),
408 ))
409 .coords,
410 ));
411 builder.insert(RawVertex::from(
412 global_transform
413 .transform_point(&Point3::from(
414 vertex_buffer
415 .get(triangle[1] as usize)
416 .unwrap()
417 .read_3_f32(VertexAttributeUsage::Position)
418 .unwrap(),
419 ))
420 .coords,
421 ));
422 builder.insert(RawVertex::from(
423 global_transform
424 .transform_point(&Point3::from(
425 vertex_buffer
426 .get(triangle[2] as usize)
427 .unwrap()
428 .read_3_f32(VertexAttributeUsage::Position)
429 .unwrap(),
430 ))
431 .coords,
432 ));
433 }
434 }
435
436 let mesh = builder.build();
437
438 Navmesh::new(
439 mesh.triangles,
440 mesh.vertices
441 .into_iter()
442 .map(|v| Vector3::new(v.x, v.y, v.z))
443 .collect::<Vec<_>>(),
444 )
445 }
446
447 pub fn query_closest(&self, query_point: Vector3<f32>) -> Option<(Vector3<f32>, usize)> {
457 let mut closest = None;
458 let mut closest_distance = f32::MAX;
459
460 self.query_closest_internal(
482 &mut closest,
483 &mut closest_distance,
484 0..self.triangles.len(),
485 query_point,
486 );
487
488 closest
489 }
490
491 fn query_closest_internal(
492 &self,
493 closest: &mut Option<(Vector3<f32>, usize)>,
494 closest_distance: &mut f32,
495 triangles: impl Iterator<Item = usize>,
496 query_point: Vector3<f32>,
497 ) {
498 for triangle_index in triangles {
499 let triangle = &self.triangles[triangle_index];
500 let a = self.vertices[triangle[0] as usize];
501 let b = self.vertices[triangle[1] as usize];
502 let c = self.vertices[triangle[2] as usize];
503
504 let Some(plane) = Plane::from_triangle(&a, &b, &c) else {
505 continue;
506 };
507 let projection = plane.project(&query_point);
508 if math::is_point_inside_triangle(&projection, &[a, b, c]) {
509 let sqr_distance = query_point.sqr_distance(&projection);
510 if sqr_distance < *closest_distance {
511 *closest_distance = sqr_distance;
512 *closest = Some((projection, triangle_index));
513 }
514 }
515
516 for edge in self.triangles[triangle_index].edges() {
519 let a = self.vertices[edge.a as usize];
520 let b = self.vertices[edge.b as usize];
521 let ray = Ray::from_two_points(a, b);
522 let t = ray.project_point(&query_point);
523 if (0.0..=1.0).contains(&t) {
524 let edge_pt_projection = ray.get_point(t);
525 let sqr_distance = query_point.sqr_distance(&edge_pt_projection);
526 if sqr_distance < *closest_distance {
527 *closest_distance = sqr_distance;
528 *closest = Some((ray.get_point(t), triangle_index));
529 }
530 }
531 }
532
533 for pt in [a, b, c] {
535 let sqr_distance = query_point.sqr_distance(&pt);
536 if sqr_distance < *closest_distance {
537 *closest_distance = sqr_distance;
538 *closest = Some((pt, triangle_index));
539 }
540 }
541 }
542 }
543
544 pub fn modify(&mut self) -> NavmeshModificationContext {
547 NavmeshModificationContext { navmesh: self }
548 }
549
550 pub fn triangles(&self) -> &[TriangleDefinition] {
552 &self.triangles
553 }
554
555 pub fn vertices(&self) -> &[Vector3<f32>] {
557 &self.vertices
558 }
559
560 pub fn octree(&self) -> &Octree {
562 &self.octree
563 }
564
565 pub fn build_path(
584 &self,
585 from: usize,
586 to: usize,
587 path: &mut Vec<Vector3<f32>>,
588 ) -> Result<PathKind, PathError> {
589 self.graph.build_positional_path(from, to, path)
590 }
591
592 pub fn ray_cast(&self, ray: Ray) -> Option<(Vector3<f32>, usize)> {
594 let mut buffer = ArrayVec::<usize, 128>::new();
595
596 self.octree.ray_query_static(&ray, &mut buffer);
597
598 let mut closest_distance = f32::MAX;
599 let mut result = None;
600 for node in buffer.into_iter() {
601 if let OctreeNode::Leaf { indices, .. } = self.octree.node(node) {
602 for &index in indices {
603 let triangle = self.triangles[index as usize];
604 let a = self.vertices()[triangle[0] as usize];
605 let b = self.vertices()[triangle[1] as usize];
606 let c = self.vertices()[triangle[2] as usize];
607
608 if let Some(intersection) = ray.triangle_intersection_point(&[a, b, c]) {
609 let distance = intersection.metric_distance(&ray.origin);
610 if distance < closest_distance {
611 closest_distance = distance;
612 result = Some((intersection, index as usize));
613 }
614 }
615 }
616 } else {
617 unreachable!()
618 }
619 }
620
621 result
622 }
623
624 fn portal_between(&self, src_triangle: usize, dest_triangle: usize) -> Option<Portal> {
625 let src_triangle = self.triangles.get(src_triangle)?;
626 let dest_triangle = self.triangles.get(dest_triangle)?;
627 for src_triangle_edge in src_triangle.edges() {
628 for dest_triangle_edge in dest_triangle.edges() {
629 if src_triangle_edge == dest_triangle_edge {
630 let a = self.vertices[src_triangle[0] as usize];
631 let b = self.vertices[src_triangle[1] as usize];
632 let c = self.vertices[src_triangle[2] as usize];
633
634 return if winding(a, b, c) == Winding::Clockwise {
635 Some(Portal {
636 left: src_triangle_edge.a as usize,
637 right: src_triangle_edge.b as usize,
638 })
639 } else {
640 Some(Portal {
641 left: src_triangle_edge.b as usize,
642 right: src_triangle_edge.a as usize,
643 })
644 };
645 }
646 }
647 }
648 None
649 }
650}
651
652#[derive(Visit, Clone, Debug)]
655#[visit(optional)]
656pub struct NavmeshAgent {
657 path: Vec<Vector3<f32>>,
658 current: u32,
659 position: Vector3<f32>,
660 last_warp_position: Vector3<f32>,
661 target: Vector3<f32>,
662 last_target_position: Vector3<f32>,
663 recalculation_threshold: f32,
664 speed: f32,
665 path_dirty: bool,
666 radius: f32,
667 interpolator: f32,
668}
669
670impl Default for NavmeshAgent {
671 fn default() -> Self {
672 Self::new()
673 }
674}
675
676impl NavmeshAgent {
677 pub fn new() -> Self {
679 Self {
680 path: vec![],
681 current: 0,
682 position: Default::default(),
683 last_warp_position: Default::default(),
684 target: Default::default(),
685 last_target_position: Default::default(),
686 recalculation_threshold: 0.25,
687 speed: 1.5,
688 path_dirty: true,
689 radius: 0.2,
690 interpolator: 0.0,
691 }
692 }
693
694 pub fn position(&self) -> Vector3<f32> {
696 self.position
697 }
698
699 pub fn path(&self) -> &[Vector3<f32>] {
701 &self.path
702 }
703
704 pub fn set_speed(&mut self, speed: f32) {
706 self.speed = speed;
707 }
708
709 pub fn speed(&self) -> f32 {
711 self.speed
712 }
713
714 pub fn set_threshold(&mut self, threshold: f32) {
718 self.recalculation_threshold = threshold;
719 }
720
721 pub fn threshold(&self) -> f32 {
724 self.recalculation_threshold
725 }
726
727 pub fn set_radius(&mut self, radius: f32) {
731 self.radius = radius;
732 }
733
734 pub fn radius(&self) -> f32 {
737 self.radius
738 }
739}
740
741impl NavmeshAgent {
742 pub fn calculate_path(
745 &mut self,
746 navmesh: &Navmesh,
747 src_point: Vector3<f32>,
748 dest_point: Vector3<f32>,
749 ) -> Result<PathKind, PathError> {
750 self.path.clear();
751
752 self.current = 0;
753 self.interpolator = 0.0;
754
755 if let Some((src_point_on_navmesh, src_triangle)) = navmesh.query_closest(src_point) {
756 if let Some((dest_point_on_navmesh, dest_triangle)) = navmesh.query_closest(dest_point)
757 {
758 if src_triangle == dest_triangle {
759 self.path.push(src_point_on_navmesh);
760 self.path.push(dest_point_on_navmesh);
761
762 return Ok(PathKind::Full);
763 }
764
765 let mut path_triangle_indices = Vec::new();
766 let path_kind = navmesh.graph.build_indexed_path(
767 src_triangle,
768 dest_triangle,
769 &mut path_triangle_indices,
770 )?;
771
772 path_triangle_indices.reverse();
773
774 self.straighten_path(
775 navmesh,
776 src_point_on_navmesh,
777 dest_point_on_navmesh,
778 &path_triangle_indices,
779 );
780
781 return Ok(path_kind);
782 }
783 }
784
785 Err(PathError::Empty)
786 }
787
788 fn straighten_path(
789 &mut self,
790 navmesh: &Navmesh,
791 src_position: Vector3<f32>,
792 dest_position: Vector3<f32>,
793 path_triangles: &[usize],
794 ) {
795 self.path.push(src_position);
796
797 if path_triangles.len() > 1 {
798 let mut funnel_apex = src_position;
799 let mut funnel_vertices = [funnel_apex; 2];
800 let mut side_indices = [0; 2];
801 let side_signs = [1.0, -1.0];
802
803 let mut i = 0;
804 while i < path_triangles.len() {
805 let portal_vertices = if i + 1 < path_triangles.len() {
806 let portal = navmesh
807 .portal_between(path_triangles[i], path_triangles[i + 1])
808 .unwrap();
809
810 let mut left = navmesh.vertices[portal.left];
811 let mut right = navmesh.vertices[portal.right];
812
813 if self.radius > 0.0 {
814 let delta = right - left;
815 let len = delta.norm();
816 let offset = delta.scale(self.radius.min(len * 0.5) / len);
817
818 left += offset;
819 right -= offset;
820 }
821
822 [left, right]
823 } else {
824 [dest_position, dest_position]
825 };
826
827 for current in 0..2 {
828 let opposite = 1 - current;
829 let side_sign = side_signs[current];
830 if side_sign
831 * triangle_area_2d(
832 funnel_apex,
833 funnel_vertices[current],
834 portal_vertices[current],
835 )
836 >= 0.0
837 {
838 if funnel_apex == funnel_vertices[current]
839 || side_sign
840 * triangle_area_2d(
841 funnel_apex,
842 funnel_vertices[opposite],
843 portal_vertices[current],
844 )
845 < 0.0
846 {
847 funnel_vertices[current] = portal_vertices[current];
848 side_indices[current] = i;
849 } else {
850 funnel_apex = funnel_vertices[opposite];
851 funnel_vertices = [funnel_apex; 2];
852
853 self.path.push(funnel_apex);
854
855 i = side_indices[opposite];
856 side_indices[current] = i;
857
858 break;
859 }
860 }
861 }
862
863 i += 1;
864 }
865 }
866
867 self.path.push(dest_position);
868 }
869
870 pub fn update(&mut self, dt: f32, navmesh: &Navmesh) -> Result<PathKind, PathError> {
873 if self.path_dirty {
874 self.calculate_path(navmesh, self.position, self.target)?;
875 self.path_dirty = false;
876 }
877
878 if let Some(source) = self.path.get(self.current as usize) {
879 if let Some(destination) = self.path.get((self.current + 1) as usize) {
880 let len = destination.metric_distance(source);
881 self.position = source.lerp(destination, self.interpolator.clamp(0.0, 1.0));
882 self.interpolator += (self.speed * dt) / len.max(f32::EPSILON);
883 if self.interpolator >= 1.0 {
884 self.current += 1;
885 self.interpolator = 0.0;
886 } else if self.interpolator < 0.0 {
887 self.current = self.current.saturating_sub(1);
888 self.interpolator = 1.0;
889 }
890 }
891 }
892
893 Ok(PathKind::Full)
894 }
895
896 pub fn steering_target(&self) -> Option<Vector3<f32>> {
899 self.path
900 .get(self.current as usize + 1)
901 .or_else(|| self.path.last())
902 .cloned()
903 }
904
905 pub fn set_target(&mut self, new_target: Vector3<f32>) {
907 if new_target.metric_distance(&self.last_target_position) >= self.recalculation_threshold {
908 self.path_dirty = true;
909 self.last_target_position = new_target;
910 }
911
912 self.target = new_target;
913 }
914
915 pub fn target(&self) -> Vector3<f32> {
917 self.target
918 }
919
920 pub fn set_position(&mut self, new_position: Vector3<f32>) {
922 if new_position.metric_distance(&self.last_warp_position) >= self.recalculation_threshold {
923 self.path_dirty = true;
924 self.last_warp_position = new_position;
925 }
926
927 self.position = new_position;
928 }
929}
930
931pub struct NavmeshAgentBuilder {
933 position: Vector3<f32>,
934 target: Vector3<f32>,
935 recalculation_threshold: f32,
936 speed: f32,
937}
938
939impl Default for NavmeshAgentBuilder {
940 fn default() -> Self {
941 Self::new()
942 }
943}
944
945impl NavmeshAgentBuilder {
946 pub fn new() -> Self {
948 Self {
949 position: Default::default(),
950 target: Default::default(),
951 recalculation_threshold: 0.25,
952 speed: 1.5,
953 }
954 }
955
956 pub fn with_position(mut self, position: Vector3<f32>) -> Self {
958 self.position = position;
959 self
960 }
961
962 pub fn with_target(mut self, position: Vector3<f32>) -> Self {
964 self.target = position;
965 self
966 }
967
968 pub fn with_recalculation_threshold(mut self, threshold: f32) -> Self {
970 self.recalculation_threshold = threshold;
971 self
972 }
973
974 pub fn with_speed(mut self, speed: f32) -> Self {
976 self.speed = speed;
977 self
978 }
979
980 pub fn build(self) -> NavmeshAgent {
982 NavmeshAgent {
983 position: self.position,
984 last_warp_position: self.position,
985 target: self.target,
986 last_target_position: self.target,
987 recalculation_threshold: self.recalculation_threshold,
988 speed: self.speed,
989 ..Default::default()
990 }
991 }
992}
993
994#[cfg(test)]
995mod test {
996 use crate::{
997 core::{algebra::Vector3, math::TriangleDefinition},
998 utils::navmesh::{Navmesh, NavmeshAgent},
999 };
1000
1001 #[test]
1002 fn test_navmesh() {
1003 let navmesh = Navmesh::new(
1004 vec![
1005 TriangleDefinition([0, 1, 3]),
1006 TriangleDefinition([1, 2, 3]),
1007 TriangleDefinition([2, 5, 3]),
1008 TriangleDefinition([2, 4, 5]),
1009 TriangleDefinition([4, 7, 5]),
1010 TriangleDefinition([4, 6, 7]),
1011 ],
1012 vec![
1013 Vector3::new(0.0, 0.0, 0.0),
1014 Vector3::new(0.0, 0.0, 1.0),
1015 Vector3::new(1.0, 0.0, 1.0),
1016 Vector3::new(1.0, 0.0, 0.0),
1017 Vector3::new(2.0, 0.0, 1.0),
1018 Vector3::new(2.0, 0.0, 0.0),
1019 Vector3::new(3.0, 0.0, 1.0),
1020 Vector3::new(3.0, 0.0, 0.0),
1021 ],
1022 );
1023
1024 let mut agent = NavmeshAgent::new();
1025
1026 agent.set_target(Vector3::new(3.0, 0.0, 1.0));
1027 agent.update(1.0 / 60.0, &navmesh).unwrap();
1028
1029 let graph = &navmesh.graph;
1030
1031 assert_eq!(graph.vertices.len(), 6);
1032 assert_eq!(graph.vertices[0].neighbours[0], 1);
1033
1034 assert_eq!(graph.vertices[1].neighbours[0], 0);
1035 assert_eq!(graph.vertices[1].neighbours[1], 2);
1036
1037 assert_eq!(graph.vertices[2].neighbours[0], 1);
1038 assert_eq!(graph.vertices[2].neighbours[1], 3);
1039
1040 assert_eq!(graph.vertices[3].neighbours[0], 2);
1041 assert_eq!(graph.vertices[3].neighbours[1], 4);
1042
1043 assert_eq!(graph.vertices[4].neighbours[0], 3);
1044 assert_eq!(graph.vertices[4].neighbours[1], 5);
1045
1046 assert_eq!(graph.vertices[5].neighbours[0], 4);
1047
1048 assert_eq!(
1049 agent.path,
1050 vec![
1051 Vector3::new(0.0, 0.0, 0.0),
1052 Vector3::new(3.0, 0.0, 1.0),
1053 Vector3::new(3.0, 0.0, 1.0)
1054 ]
1055 );
1056 }
1057}