1extern crate static_assertions;
2
3#[cfg(feature = "recast")]
4#[allow(non_camel_case_types, non_snake_case, non_upper_case_globals)]
5mod ffi_recast {
6 include!(concat!(env!("OUT_DIR"), "/recast.rs"));
7}
8
9#[cfg(feature = "detour")]
10#[allow(non_camel_case_types, non_snake_case, non_upper_case_globals)]
11mod ffi_detour {
12 include!(concat!(env!("OUT_DIR"), "/detour.rs"));
13
14 #[cfg(feature = "detour_large_nav_meshes")]
15 static_assertions::assert_eq_size!(dtPolyRef, u64);
16
17 #[cfg(not(feature = "detour_large_nav_meshes"))]
18 static_assertions::assert_eq_size!(dtPolyRef, u32);
19}
20
21#[cfg(feature = "detour_crowd")]
22#[allow(non_camel_case_types, non_snake_case, non_upper_case_globals)]
23mod ffi_detour_crowd {
24 use crate::ffi_detour::*;
25
26 include!(concat!(env!("OUT_DIR"), "/detour_crowd.rs"));
27}
28
29#[cfg(feature = "detour_tile_cache")]
30#[allow(non_camel_case_types, non_snake_case, non_upper_case_globals)]
31mod ffi_detour_tile_cache {
32 use crate::ffi_detour::*;
33
34 include!(concat!(env!("OUT_DIR"), "/detour_tile_cache.rs"));
35}
36
37#[cfg(any(feature = "recast", feature = "detour_tile_cache"))]
38#[allow(non_camel_case_types, non_snake_case, non_upper_case_globals)]
39mod ffi_inline {
40 #[cfg(feature = "detour")]
41 use crate::ffi_detour::*;
42 #[cfg(feature = "detour_tile_cache")]
43 use crate::ffi_detour_tile_cache::*;
44 #[cfg(feature = "recast")]
45 use crate::ffi_recast::*;
46
47 include!(concat!(env!("OUT_DIR"), "/inline.rs"));
48}
49
50#[cfg(feature = "detour")]
51pub use ffi_detour::*;
52#[cfg(feature = "detour_crowd")]
53pub use ffi_detour_crowd::*;
54#[cfg(feature = "detour_tile_cache")]
55pub use ffi_detour_tile_cache::*;
56#[cfg(any(feature = "recast", feature = "detour_tile_cache"))]
57pub use ffi_inline::*;
58#[cfg(feature = "recast")]
59pub use ffi_recast::*;
60
61#[cfg(test)]
62mod tests {
63 use crate::*;
64
65 #[cfg(feature = "recast")]
66 #[test]
67 fn recast_create_simple_nav_mesh() {
68 let context = unsafe { CreateContext(false) };
69 let heightfield = unsafe { rcAllocHeightfield() };
70
71 unsafe {
72 rcCreateHeightfield(
73 context,
74 heightfield,
75 5,
76 5,
77 [0.0, 0.0, 0.0].as_ptr(),
78 [5.0, 5.0, 5.0].as_ptr(),
79 1.0,
80 1.0,
81 )
82 };
83
84 let verts = [
85 0.0, 0.5, 0.0, 5.0, 0.5, 0.0, 5.0, 0.5, 5.0, 0.0, 0.5, 5.0, ];
90 let triangles: &[i32] = &[0, 1, 2, 2, 3, 0];
91 let area_ids: &[u8] = &[RC_WALKABLE_AREA, RC_WALKABLE_AREA];
92
93 assert!(
94 unsafe {
95 rcRasterizeTriangles(
96 context,
97 verts.as_ptr(),
98 verts.len() as i32 / 3,
99 triangles.as_ptr(),
100 area_ids.as_ptr(),
101 triangles.len() as i32 / 3,
102 heightfield,
103 1,
104 )
105 },
106 "Expected rasterization to succeed."
107 );
108
109 let compact_heightfield = unsafe { rcAllocCompactHeightfield() };
110 assert!(unsafe {
111 rcBuildCompactHeightfield(context, 2, 1, heightfield, compact_heightfield)
112 });
113 unsafe { rcFreeHeightField(heightfield) };
114
115 assert!(unsafe { rcErodeWalkableArea(context, 1, compact_heightfield) });
116 assert!(unsafe { rcBuildDistanceField(context, compact_heightfield) });
117 assert!(unsafe {
118 rcBuildRegions(
119 context,
120 compact_heightfield,
121 0,
122 0,
123 0,
124 )
125 });
126
127 let contour_set = unsafe { rcAllocContourSet() };
128
129 assert!(unsafe {
130 rcBuildContours(
131 context,
132 compact_heightfield,
133 0.0,
134 0,
135 contour_set,
136 rcBuildContoursFlags_RC_CONTOUR_TESS_WALL_EDGES as i32,
137 )
138 });
139
140 unsafe { rcFreeCompactHeightfield(compact_heightfield) };
141
142 let mesh = unsafe { rcAllocPolyMesh() };
143 assert!(unsafe {
144 rcBuildPolyMesh(context, contour_set, 8, mesh)
145 });
146 unsafe { rcFreeContourSet(contour_set) };
147 unsafe { DeleteContext(context) };
148
149 let mesh_ref = unsafe { &mut *mesh };
150
151 assert_eq!(mesh_ref.npolys, 1);
152 assert_eq!(mesh_ref.nverts, 4);
153 let node_slice = unsafe {
154 std::slice::from_raw_parts(
155 mesh_ref.polys,
156 (mesh_ref.npolys * mesh_ref.nvp * 2) as usize,
157 )
158 };
159 assert_eq!(
160 &node_slice[..(mesh_ref.npolys * mesh_ref.nvp) as usize],
161 &[
162 0,
163 1,
164 2,
165 3,
166 RC_MESH_NULL_IDX,
167 RC_MESH_NULL_IDX,
168 RC_MESH_NULL_IDX,
169 RC_MESH_NULL_IDX,
170 ]
171 );
172 let vert_slice = unsafe {
173 std::slice::from_raw_parts(mesh_ref.verts, mesh_ref.nverts as usize * 3)
174 };
175 let expected_verts = &[
176 1, 1, 1, 1, 1, 4, 4, 1, 4, 4, 1, 1, ];
181 assert_eq!(vert_slice, expected_verts);
182
183 unsafe { rcFreePolyMesh(mesh) };
184 }
185
186 #[cfg(feature = "detour")]
187 #[test]
188 fn detour_finds_simple_path() {
189 let verts = vec![
190 1, 0, 1, 1, 0, 0, 2, 0, 0, 2, 0, 1, 3, 0, 1, 3, 0, 2, 2, 0, 2, 1, 0, 2, 0, 0, 2, 0, 0, 1, ];
201
202 const N: u16 = 0xffff;
203
204 let polys = vec![
205 0, 1, 2, N, N, 1, 2, 3, 0, N, 2, 0, 0, 3, 6, 1, 4, 3, 0, 6, 7, 2, N, 6, 6, 3, 4, 2, N, 5, 6, 4, 5, 4, N, N, 0, 7, 8, 3, N, 7, 0, 8, 9, 6, N, N, ];
214
215 let poly_flags = vec![1, 1, 1, 1, 1, 1, 1, 1];
216 let poly_areas = vec![0, 0, 0, 0, 0, 0, 0, 0];
217
218 let mut nav_mesh_create_data = dtNavMeshCreateParams {
219 verts: verts.as_ptr(),
220 vertCount: verts.len() as i32,
221 polys: polys.as_ptr(),
222 polyFlags: poly_flags.as_ptr(),
223 polyAreas: poly_areas.as_ptr(),
224 polyCount: polys.len() as i32 / 6,
225 nvp: 3,
226 detailMeshes: std::ptr::null(),
227 detailVerts: std::ptr::null(),
228 detailVertsCount: 0,
229 detailTris: std::ptr::null(),
230 detailTriCount: 0,
231 offMeshConVerts: std::ptr::null(),
232 offMeshConRad: std::ptr::null(),
233 offMeshConFlags: std::ptr::null(),
234 offMeshConAreas: std::ptr::null(),
235 offMeshConDir: std::ptr::null(),
236 offMeshConUserID: std::ptr::null(),
237 offMeshConCount: 0,
238 userId: 0,
239 tileX: 0,
240 tileY: 0,
241 tileLayer: 0,
242 bmin: [0.0, 0.0, 0.0],
243 bmax: [3.0, 2.0, 2.0],
244 walkableHeight: 1.0,
245 walkableRadius: 1.0,
246 walkableClimb: 1.0,
247 cs: 1.0,
248 ch: 1.0,
249 buildBvTree: false,
250 };
251
252 let mut data: *mut u8 = std::ptr::null_mut();
253 let mut data_size: i32 = 0;
254
255 assert!(unsafe {
256 dtCreateNavMeshData(&mut nav_mesh_create_data, &mut data, &mut data_size)
257 });
258
259 let nav_mesh = unsafe { &mut *dtAllocNavMesh() };
260 assert_ne!(nav_mesh as *mut dtNavMesh, std::ptr::null_mut());
261 assert_eq!(
262 unsafe {
263 nav_mesh.init1(data, data_size, dtTileFlags_DT_TILE_FREE_DATA as i32)
264 },
265 DT_SUCCESS
266 );
267
268 let nav_mesh_query = unsafe { &mut *dtAllocNavMeshQuery() };
269 assert_ne!(nav_mesh as *mut dtNavMesh, std::ptr::null_mut());
270 assert_eq!(unsafe { nav_mesh_query.init(nav_mesh, 512) }, DT_SUCCESS);
271
272 fn find_poly_ref(query: &dtNavMeshQuery, pos: &[f32; 3]) -> dtPolyRef {
273 let extents = [0.1, 100.0, 0.1];
274
275 let mut poly_ref: dtPolyRef = 0;
276
277 let query_filter = dtQueryFilter {
278 m_areaCost: [1.0; 64],
279 m_includeFlags: 0xffff,
280 m_excludeFlags: 0,
281 };
282
283 assert_eq!(
284 unsafe {
285 query.findNearestPoly(
286 pos.as_ptr(),
287 extents.as_ptr(),
288 &query_filter,
289 &mut poly_ref,
290 std::ptr::null_mut(),
291 )
292 },
293 DT_SUCCESS
294 );
295
296 poly_ref
297 }
298
299 let start_point = [1.1, 0.0, 0.1];
300 let end_point = [2.9, 0.0, 1.9];
301
302 let start_poly_ref = find_poly_ref(&nav_mesh_query, &start_point);
303 let end_poly_ref = find_poly_ref(&nav_mesh_query, &end_point);
304
305 assert_eq!(start_poly_ref & 0b111, 0);
306 assert_eq!(end_poly_ref & 0b111, 5);
307
308 let query_filter = dtQueryFilter {
309 m_areaCost: [1.0; 64],
310 m_includeFlags: 0xffff,
311 m_excludeFlags: 0,
312 };
313
314 let mut path = [0; 10];
315 let mut path_count = 0;
316
317 assert_eq!(
318 unsafe {
319 nav_mesh_query.findPath(
320 start_poly_ref,
321 end_poly_ref,
322 start_point.as_ptr(),
323 end_point.as_ptr(),
324 &query_filter,
325 path.as_mut_ptr(),
326 &mut path_count,
327 path.len() as i32,
328 )
329 },
330 DT_SUCCESS
331 );
332
333 assert_eq!(
334 path
335 .iter()
336 .map(|polyref| if *polyref == 0 { None } else { Some(polyref & 0b111) })
337 .collect::<Vec<_>>(),
338 [
339 Some(0),
340 Some(1),
341 Some(2),
342 Some(4),
343 Some(5),
344 None,
345 None,
346 None,
347 None,
348 None
349 ]
350 );
351
352 unsafe { dtFreeNavMeshQuery(nav_mesh_query) };
353 unsafe { dtFreeNavMesh(nav_mesh) };
354 }
355
356 #[cfg(feature = "detour_crowd")]
357 #[test]
358 fn detour_crowd_basic_path_following() {
359 let verts = vec![
360 1, 0, 1, 1, 0, 0, 2, 0, 0, 2, 0, 1, 3, 0, 1, 3, 0, 2, 2, 0, 2, 1, 0, 2, 0, 0, 2, 0, 0, 1, ];
371
372 const N: u16 = 0xffff;
373
374 let polys = vec![
375 0, 1, 2, N, N, 1, 2, 3, 0, N, 2, 0, 0, 3, 6, 1, 4, 3, 0, 6, 7, 2, N, 6, 6, 3, 4, 2, N, 5, 6, 4, 5, 4, N, N, 0, 7, 8, 3, N, 7, 0, 8, 9, 6, N, N, ];
384
385 let poly_flags = vec![1, 1, 1, 1, 1, 1, 1, 1];
386 let poly_areas = vec![0, 0, 0, 0, 0, 0, 0, 0];
387
388 let mut nav_mesh_create_data = dtNavMeshCreateParams {
389 verts: verts.as_ptr(),
390 vertCount: verts.len() as i32,
391 polys: polys.as_ptr(),
392 polyFlags: poly_flags.as_ptr(),
393 polyAreas: poly_areas.as_ptr(),
394 polyCount: polys.len() as i32 / 6,
395 nvp: 3,
396 detailMeshes: std::ptr::null(),
397 detailVerts: std::ptr::null(),
398 detailVertsCount: 0,
399 detailTris: std::ptr::null(),
400 detailTriCount: 0,
401 offMeshConVerts: std::ptr::null(),
402 offMeshConRad: std::ptr::null(),
403 offMeshConFlags: std::ptr::null(),
404 offMeshConAreas: std::ptr::null(),
405 offMeshConDir: std::ptr::null(),
406 offMeshConUserID: std::ptr::null(),
407 offMeshConCount: 0,
408 userId: 0,
409 tileX: 0,
410 tileY: 0,
411 tileLayer: 0,
412 bmin: [0.0, 0.0, 0.0],
413 bmax: [3.0, 2.0, 2.0],
414 walkableHeight: 1.0,
415 walkableRadius: 1.0,
416 walkableClimb: 1.0,
417 cs: 1.0,
418 ch: 1.0,
419 buildBvTree: false,
420 };
421
422 let mut data: *mut u8 = std::ptr::null_mut();
423 let mut data_size: i32 = 0;
424
425 assert!(unsafe {
426 dtCreateNavMeshData(&mut nav_mesh_create_data, &mut data, &mut data_size)
427 });
428
429 let nav_mesh = unsafe { &mut *dtAllocNavMesh() };
430 assert_ne!(nav_mesh as *mut dtNavMesh, std::ptr::null_mut());
431 assert_eq!(
432 unsafe {
433 nav_mesh.init1(data, data_size, dtTileFlags_DT_TILE_FREE_DATA as i32)
434 },
435 DT_SUCCESS
436 );
437
438 let crowd = unsafe { &mut *dtAllocCrowd() };
439 assert!(unsafe { crowd.init(10, 10.0, nav_mesh) });
440
441 let agent_position = [1.1, 0.0, 0.1];
442 let agent_params = dtCrowdAgentParams {
443 radius: 0.25,
444 height: 1.0,
445 maxAcceleration: 0.5,
446 maxSpeed: 1.0,
447 collisionQueryRange: 1.0,
448 pathOptimizationRange: 10.0,
449 separationWeight: 1.0,
450 updateFlags: 0,
451 obstacleAvoidanceType: 0,
452 queryFilterType: 0,
453 userData: std::ptr::null_mut(),
454 };
455
456 assert_eq!(
457 unsafe { crowd.addAgent(agent_position.as_ptr(), &agent_params) },
458 0
459 );
460
461 unsafe { crowd.update(0.1, std::ptr::null_mut()) };
462
463 let updated_position = unsafe { &(*crowd.getAgent(0)).npos };
464 assert_eq!(updated_position, &agent_position);
465
466 let target_point = [2.9, 0.0, 1.9];
467 let extents = [0.1, 100.0, 0.1];
468
469 let mut target_poly_ref: dtPolyRef = 0;
470
471 let query_filter = dtQueryFilter {
472 m_areaCost: [1.0; 64],
473 m_includeFlags: 0xffff,
474 m_excludeFlags: 0,
475 };
476
477 assert_eq!(
478 unsafe {
479 (*crowd.m_navquery).findNearestPoly(
480 target_point.as_ptr(),
481 extents.as_ptr(),
482 &query_filter,
483 &mut target_poly_ref,
484 std::ptr::null_mut(),
485 )
486 },
487 DT_SUCCESS
488 );
489
490 assert!(unsafe {
491 crowd.requestMoveTarget(0, target_poly_ref, target_point.as_ptr())
492 });
493
494 for _ in 0..200 {
495 unsafe { crowd.update(0.1, std::ptr::null_mut()) };
496 }
497
498 let updated_position = unsafe { &(*crowd.getAgent(0)).npos };
499 let delta = [
500 updated_position[0] - target_point[0],
501 updated_position[1] - target_point[1],
502 updated_position[2] - target_point[2],
503 ];
504 assert!(
505 (delta[0] * delta[0] + delta[1] * delta[1] + delta[2] * delta[2]).sqrt()
506 < 0.01,
507 "\n\nleft: {:?}\nright: {:?}",
508 updated_position,
509 target_point
510 );
511
512 unsafe { dtFreeCrowd(crowd) };
513 unsafe { dtFreeNavMesh(nav_mesh) };
514 }
515
516 #[cfg(feature = "detour_tile_cache")]
517 #[test]
518 fn detour_tile_cache_simple_caching() {
519 let cache_params = dtTileCacheParams {
520 orig: [0.0, 0.0, 0.0],
521 cs: 1.0,
522 ch: 1.0,
523 width: 5,
524 height: 5,
525 walkableHeight: 1.0,
526 walkableRadius: 1.0,
527 walkableClimb: 1.0,
528 maxSimplificationError: 0.01,
529 maxTiles: 1000,
530 maxObstacles: 10,
531 };
532
533 let alloc = unsafe { CreateDefaultTileCacheAlloc() };
534
535 extern "C" fn max_compressed_size(
536 _object_ptr: *mut std::ffi::c_void,
537 buffer_size: i32,
538 ) -> i32 {
539 buffer_size
540 }
541
542 extern "C" fn compress(
543 _object_ptr: *mut std::ffi::c_void,
544 buffer: *const u8,
545 buffer_size: i32,
546 compressed: *mut u8,
547 max_compressed_size: i32,
548 compressed_size: *mut i32,
549 ) -> u32 {
550 assert!(
551 buffer_size <= max_compressed_size,
552 "\n\nleft: {}\nright: {}",
553 buffer_size,
554 max_compressed_size
555 );
556
557 let buffer_slice =
558 unsafe { std::slice::from_raw_parts(buffer, buffer_size as usize) };
559
560 unsafe { *compressed_size = buffer_size };
561
562 let compressed_slice = unsafe {
563 std::slice::from_raw_parts_mut(compressed, *compressed_size as usize)
564 };
565
566 compressed_slice.copy_from_slice(buffer_slice);
567
568 DT_SUCCESS
569 }
570
571 extern "C" fn decompress(
572 object_ptr: *mut std::ffi::c_void,
573 compressed: *const u8,
574 compressed_size: i32,
575 buffer: *mut u8,
576 max_buffer_size: i32,
577 buffer_size: *mut i32,
578 ) -> u32 {
579 compress(
582 object_ptr,
583 compressed,
584 compressed_size,
585 buffer,
586 max_buffer_size,
587 buffer_size,
588 )
589 }
590 let forwarded_compressor = unsafe {
591 CreateForwardedTileCacheCompressor(
592 std::ptr::null_mut(),
593 Some(max_compressed_size),
594 Some(compress),
595 Some(decompress),
596 )
597 };
598
599 extern "C" fn set_poly_flags(
600 _: *mut std::ffi::c_void,
601 params: *mut dtNavMeshCreateParams,
602 _areas: *mut u8,
603 flags: *mut u16,
604 ) {
605 let params = unsafe { &*params };
606 let flags = unsafe {
607 std::slice::from_raw_parts_mut(flags, params.polyCount as usize)
608 };
609
610 flags.fill(1);
611 }
612 let forwarded_mesh_process = unsafe {
613 CreateForwardedTileCacheMeshProcess(
614 std::ptr::null_mut(),
615 Some(set_poly_flags),
616 )
617 };
618
619 let tile_cache = unsafe { &mut *dtAllocTileCache() };
620 unsafe {
621 tile_cache.init(
622 &cache_params,
623 alloc,
624 forwarded_compressor,
625 forwarded_mesh_process,
626 )
627 };
628
629 let nav_mesh = unsafe { &mut *dtAllocNavMesh() };
630 let nav_mesh_params = dtNavMeshParams {
631 orig: [0.0, 0.0, 0.0],
632 tileWidth: 5.0,
633 tileHeight: 5.0,
634 maxTiles: 1000,
635 maxPolys: 10,
636 };
637 unsafe { nav_mesh.init(&nav_mesh_params) };
638
639 for _ in 0..10 {
640 let mut up_to_date = false;
641 unsafe { tile_cache.update(1.0, nav_mesh, &mut up_to_date) };
642 assert!(up_to_date);
643 }
644
645 let mut header = dtTileCacheLayerHeader {
646 magic: DT_TILECACHE_MAGIC,
647 version: DT_TILECACHE_VERSION,
648 tx: 0,
649 ty: 0,
650 tlayer: 0,
651 bmin: [0.0, 1.0, 0.0],
652 bmax: [5.0, 1.0, 5.0],
653 width: 5 as u8,
654 height: 5 as u8,
655 minx: 0,
656 maxx: 4,
657 miny: 0,
658 maxy: 4,
659 hmin: 1,
660 hmax: 1,
661 };
662
663 const N: u8 = 255;
664
665 let heights = [
666 N, N, 0, N, N, N, N, 0, N, N, N, N, 0, N, N, N, N, 0, N, N, 0, 0, 0, 0, 0, ];
672
673 const W: u8 = DT_TILECACHE_WALKABLE_AREA;
674
675 let areas = [
676 0, 0, W, 0, 0, 0, 0, W, 0, 0, 0, 0, W, 0, 0, 0, 0, W, 0, 0, W, W, W, W, W, ];
682
683 let cons = [
685 0, 0, 2, 0, 0, 0, 0, 10, 0, 0, 0, 0, 10, 0, 0, 0, 0, 10, 0, 0, 4, 5, 13, 5, 1, ];
691
692 let mut data: *mut u8 = std::ptr::null_mut();
693 let mut data_size: i32 = 0;
694
695 assert_eq!(
696 unsafe {
697 dtBuildTileCacheLayer(
698 forwarded_compressor,
699 &mut header,
700 heights.as_ptr(),
701 areas.as_ptr(),
702 cons.as_ptr(),
703 &mut data,
704 &mut data_size,
705 )
706 },
707 DT_SUCCESS
708 );
709
710 assert_eq!(
711 unsafe {
712 tile_cache.addTile(
713 data,
714 data_size,
715 dtTileFlags_DT_TILE_FREE_DATA as u8,
716 std::ptr::null_mut(),
717 )
718 },
719 DT_SUCCESS
720 );
721
722 assert_eq!(
723 unsafe { tile_cache.buildNavMeshTilesAt(0, 0, nav_mesh) },
724 DT_SUCCESS
725 );
726
727 let query = unsafe { &mut *dtAllocNavMeshQuery() };
728 assert_eq!(unsafe { query.init(nav_mesh, 10) }, DT_SUCCESS);
729
730 let query_filter = dtQueryFilter {
731 m_areaCost: [1.0; 64],
732 m_includeFlags: 0xffff,
733 m_excludeFlags: 0,
734 };
735
736 let mut path = [0; 10];
737 let mut path_count = 0;
738
739 let start_point = [2.1, 1.0, 0.1];
740 let end_point = [4.9, 1.0, 4.9];
741
742 let mut start_point_ref = 0;
743 assert_eq!(
744 unsafe {
745 query.findNearestPoly(
746 start_point.as_ptr(),
747 [0.1, 100.0, 0.1].as_ptr(),
748 &query_filter,
749 &mut start_point_ref,
750 std::ptr::null_mut(),
751 )
752 },
753 DT_SUCCESS
754 );
755 assert_ne!(start_point_ref, 0);
756
757 let mut end_point_ref = 0;
758 assert_eq!(
759 unsafe {
760 query.findNearestPoly(
761 end_point.as_ptr(),
762 [0.1, 100.0, 0.1].as_ptr(),
763 &query_filter,
764 &mut end_point_ref,
765 std::ptr::null_mut(),
766 )
767 },
768 DT_SUCCESS
769 );
770 assert_ne!(end_point_ref, 0);
771
772 assert_eq!(
773 unsafe {
774 query.findPath(
775 start_point_ref,
776 end_point_ref,
777 start_point.as_ptr(),
778 end_point.as_ptr(),
779 &query_filter,
780 path.as_mut_ptr(),
781 &mut path_count,
782 path.len() as i32,
783 )
784 },
785 DT_SUCCESS
786 );
787
788 assert_eq!(
789 path
790 .iter()
791 .map(|polyref| if *polyref == 0 {
792 None
793 } else {
794 Some(polyref & 0b11111111111111)
795 })
796 .collect::<Vec<_>>(),
797 [Some(1), Some(3), Some(0), None, None, None, None, None, None, None]
798 );
799
800 unsafe { dtFreeNavMeshQuery(query) };
801 unsafe { dtFreeNavMesh(nav_mesh) };
802 unsafe { dtFreeTileCache(tile_cache) };
803 unsafe { DeleteTileCacheMeshProcess(forwarded_mesh_process) };
804 unsafe { DeleteTileCacheCompressor(forwarded_compressor) };
805 unsafe { DeleteTileCacheAlloc(alloc) };
806 }
807}