recastnavigation_sys/
lib.rs

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, //
86      5.0, 0.5, 0.0, //
87      5.0, 0.5, 5.0, //
88      0.0, 0.5, 5.0, //
89    ];
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        /*borderSize=*/ 0,
122        /*minRegionArea=*/ 0,
123        /*mergeRegionArea=*/ 0,
124      )
125    });
126
127    let contour_set = unsafe { rcAllocContourSet() };
128
129    assert!(unsafe {
130      rcBuildContours(
131        context,
132        compact_heightfield,
133        /*maxError=*/ 0.0,
134        /*maxEdgeLen=*/ 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, /*nvp=*/ 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, //
177      1, 1, 4, //
178      4, 1, 4, //
179      4, 1, 1, //
180    ];
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, //
191      1, 0, 0, //
192      2, 0, 0, //
193      2, 0, 1, //
194      3, 0, 1, //
195      3, 0, 2, //
196      2, 0, 2, //
197      1, 0, 2, //
198      0, 0, 2, //
199      0, 0, 1, //
200    ];
201
202    const N: u16 = 0xffff;
203
204    let polys = vec![
205      0, 1, 2, N, N, 1, //
206      2, 3, 0, N, 2, 0, //
207      0, 3, 6, 1, 4, 3, //
208      0, 6, 7, 2, N, 6, //
209      6, 3, 4, 2, N, 5, //
210      6, 4, 5, 4, N, N, //
211      0, 7, 8, 3, N, 7, //
212      0, 8, 9, 6, N, N, //
213    ];
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, //
361      1, 0, 0, //
362      2, 0, 0, //
363      2, 0, 1, //
364      3, 0, 1, //
365      3, 0, 2, //
366      2, 0, 2, //
367      1, 0, 2, //
368      0, 0, 2, //
369      0, 0, 1, //
370    ];
371
372    const N: u16 = 0xffff;
373
374    let polys = vec![
375      0, 1, 2, N, N, 1, //
376      2, 3, 0, N, 2, 0, //
377      0, 3, 6, 1, 4, 3, //
378      0, 6, 7, 2, N, 6, //
379      6, 3, 4, 2, N, 5, //
380      6, 4, 5, 4, N, N, //
381      0, 7, 8, 3, N, 7, //
382      0, 8, 9, 6, N, N, //
383    ];
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      // Since compress just copies the source to destination, decompress is
580      // the exact same.
581      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, //
667      N, N, 0, N, N, //
668      N, N, 0, N, N, //
669      N, N, 0, N, N, //
670      0, 0, 0, 0, 0, //
671    ];
672
673    const W: u8 = DT_TILECACHE_WALKABLE_AREA;
674
675    let areas = [
676      0, 0, W, 0, 0, //
677      0, 0, W, 0, 0, //
678      0, 0, W, 0, 0, //
679      0, 0, W, 0, 0, //
680      W, W, W, W, W, //
681    ];
682
683    // Neighbour connectivity.
684    let cons = [
685      0, 0, 2, 0, 0, //
686      0, 0, 10, 0, 0, //
687      0, 0, 10, 0, 0, //
688      0, 0, 10, 0, 0, //
689      4, 5, 13, 5, 1, //
690    ];
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}