From 444b77921ac4bc97fb1f1f959fea368d6680b2fd Mon Sep 17 00:00:00 2001 From: ohchase Date: Sat, 20 Apr 2024 09:22:13 -0400 Subject: [PATCH 1/2] docs and fixes --- Cargo.toml | 18 ++++---- README.md | 23 ++-------- examples/path_finding.rs | 97 ++++++++-------------------------------- src/lib.rs | 88 +++++++++++++++++++++--------------- 4 files changed, 80 insertions(+), 146 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d81e8c5..4018282 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,24 +3,22 @@ name = "divert" version = "0.5.0" edition = "2021" description = "Rust Lang bindings for Recast Navigation" -homepage = "https://github.com/0xFounders/divert" -repository = "https://github.com/0xFounders/divert" +homepage = "https://github.com/ohchase/divert" +repository = "https://github.com/ohchase/divert" license = "Zlib" [lib] crate-type = ["lib"] [dependencies] -bitflags = "1.3" -thiserror = "1.0.31" -log = "0.4.17" -recastnavigation-sys = {version = "1.0.1", features = ["detour_large_nav_meshes"]} +bitflags = "2.5.0" +thiserror = "1.0.58" +recastnavigation-sys = { version = "1.0.3", features = [ + "detour_large_nav_meshes", +] } # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dev-dependencies] byteorder = "1.4.3" -log = "0.4.16" -pretty_env_logger = "0.4.0" -lazy_static = "1.4.0" thiserror = "1.0.37" -rand = "0.8.5" \ No newline at end of file +rand = "0.8.5" diff --git a/README.md b/README.md index 8e8b6ed..b4c0eab 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,21 @@ -[![Actions Status](https://github.com/0xFounders/divert/workflows/Continuous%20integration/badge.svg)](https://github.com/0xFounders/divert/actions) +[![Actions Status](https://github.com/ohchase/divert/workflows/Continuous%20integration/badge.svg)](https://github.com/ohchase/divert/actions) [![Crate](https://img.shields.io/crates/v/divert.svg)](https://crates.io/crates/divert) # Divert Rust bindings for [Recast Navigation](https://github.com/recastnavigation/recastnavigation). - ## Purpose Provide safe bindings to [Recast Navigation](https://github.com/recastnavigation/recastnavigation) to allow for 3d navigation in a rust application. -## Overview -### `src/extern.cpp` -C function definitions wrapping Detour C++ Functions. -### `src/binding.rs` -Rust bindings to the C functions exposed in `src/extern.cpp`. -### `src/lib.rs` -Safe Rust abstractions of Detour components e.g ensuring correct freeing of DtNavMesh and DtNavMeshQuery. - ## How to Build ``` -git clone --recurse-submodules https://github.com/0xFounders/divert.git +git clone --recurse-submodules https://github.com/ohchase/divert.git cargo build ``` ## Use Case Refer to `examples/pathfinding.rs` for a demonstration of loading geometry generated with [Trinity Core](https://github.com/TrinityCore/TrinityCore). In the below, Proof of Concept, section the paths generated are projected to in-game space. In this repository the resources for generating paths is provided, but drawing/projecting points in the game is not in scope of this project. No questions or issues should be opened requesting help or information about video game specific applications. - ``` cargo run --example path_finding Compiling divert v0.1.0 (...\divert) @@ -39,11 +29,4 @@ cargo run --example path_finding INFO path_finding > DtVector { y: 4352.9404, z: 3.0786226, x: -2051.5564 } INFO path_finding > DtVector { y: 4354.9106, z: 3.0870965, x: -2051.213 } INFO path_finding > DtVector { y: 4356.881, z: 2.9974942, x: -2050.8694 } -``` - -## Proof Of Concept -Demonstration of my independent work using this library to generate navigation paths in a third party video game. - -![Navigation Demo 1](resources/docs/demo_nav.PNG) - -![Navigation Demo 2](resources/docs/demo_nav_2.PNG) \ No newline at end of file +``` \ No newline at end of file diff --git a/examples/path_finding.rs b/examples/path_finding.rs index 2dc8c1a..84beb6c 100644 --- a/examples/path_finding.rs +++ b/examples/path_finding.rs @@ -1,11 +1,8 @@ use byteorder::{LittleEndian, ReadBytesExt}; -use log::{info, LevelFilter}; - use divert::{ DivertError, DivertResult, DtStraightPathFlags, NavMesh, NavMeshParams, NavMeshQuery, PolyRef, QueryFilter, TileRef, Vector, }; -use rand::Rng; use thiserror::Error; use std::{ @@ -14,20 +11,13 @@ use std::{ error::Error, fs::File, io::{self, Read, Seek, SeekFrom}, - sync::Mutex, }; -trait DataProvider: Send + Sync { - fn read_map_params(&self) -> io::Result; - - fn read_tile_data(&self, tile_x: &i32, tile_y: &i32) -> io::Result>; -} - struct TrinityDataProvider { map_id: i32, } -impl DataProvider for TrinityDataProvider { +impl TrinityDataProvider { fn read_map_params(&self) -> io::Result { let mut map_params_file = File::open(format!("resources/geometry/{:03}.mmap", self.map_id))?; @@ -90,12 +80,12 @@ impl Default for PathFindingSettings { struct TrinityNavEngine { nav_mesh: NavMesh, - data_provider: Box, + data_provider: TrinityDataProvider, loaded_tiles: HashSet, } impl TrinityNavEngine { - pub fn new(data_provider: Box) -> Result> { + pub fn new(data_provider: TrinityDataProvider) -> Result> { let map_params = data_provider.read_map_params()?; let nav_mesh = NavMesh::new(&map_params)?; @@ -121,7 +111,7 @@ impl TrinityNavEngine { pub fn add_tile(&mut self, tile_x: &i32, tile_y: &i32) -> DivertResult { if self.has_tile(tile_x, tile_y) { - info!( + println!( "[NavEngine] Add tile called for already loaded tile ({}, {})", tile_x, tile_y ); @@ -130,7 +120,7 @@ impl TrinityNavEngine { return Ok(TileRef::default()); } - info!("[NavEngine] Adding tile ({}, {})", tile_x, tile_y); + println!("[NavEngine] Adding tile ({}, {})", tile_x, tile_y); let tile_ref = self .nav_mesh .add_tile(self.data_provider.read_tile_data(tile_x, tile_y).unwrap())?; @@ -140,12 +130,6 @@ impl TrinityNavEngine { } } -lazy_static::lazy_static! { - static ref NAV_ENGINE: Mutex = Mutex::new( - TrinityNavEngine::new(Box::new(TrinityDataProvider { map_id: 530 })) - .expect("Unable to initialize global navigation engine, example resources not available.")); -} - #[derive(Error, Debug)] enum NavigationError { #[error("Divert related error")] @@ -159,7 +143,6 @@ enum NavigationError { } type NavigationResult = std::result::Result; - struct TrinityNavigator<'a> { query: NavMeshQuery<'a>, query_filter: QueryFilter, @@ -167,12 +150,7 @@ struct TrinityNavigator<'a> { } impl<'a> TrinityNavigator<'a> { - fn new(query_filter: QueryFilter) -> DivertResult { - let nav_engine = NAV_ENGINE - .lock() - .expect("Global Nav Engine Mutex has been poisoned"); - let query = nav_engine.create_query(2048)?; - + fn initialize(query: NavMeshQuery<'a>, query_filter: QueryFilter) -> DivertResult { Ok(Self { query, query_filter, @@ -286,6 +264,7 @@ impl<'a> TrinityNavigator<'a> { Ok(None) } + /// Try to find a smooth path with available tiles fn find_smooth_path( &self, start_pos: &Vector, @@ -357,27 +336,17 @@ impl<'a> TrinityNavigator<'a> { Ok(smooth_path) } + /// Try to find a path without loading any tiles pub fn find_path( &self, input_start: &Vector, input_end: &Vector, ) -> NavigationResult> { - log::info!( + println!( "[TrinityNavigator] Generating path from {:?} to {:?}", - input_start, - input_end + input_start, input_end ); - { - let nav_engine = &mut NAV_ENGINE - .lock() - .expect("Global Nav Engine Mutex has been poisoned"); - let start_tile = Self::world_to_trinity(input_start.x, input_start.y); - let end_tile = Self::world_to_trinity(input_end.x, input_end.y); - nav_engine.add_tile(&start_tile.0, &start_tile.1)?; - nav_engine.add_tile(&end_tile.0, &end_tile.1)?; - } - let (start_poly, start_pos) = self.find_nearest_poly(input_start)?; let (end_poly, end_pos) = self.find_nearest_poly(input_end)?; @@ -391,7 +360,7 @@ impl<'a> TrinityNavigator<'a> { )?; let smooth_path = self.find_smooth_path(&start_pos, &end_pos, poly_path)?; - log::info!( + println!( "[TrinityNavigator] Successfully generated path of len: {}", smooth_path.len() ); @@ -401,15 +370,13 @@ impl<'a> TrinityNavigator<'a> { } fn main() -> Result<(), Box> { - pretty_env_logger::formatted_builder() - .filter_level(LevelFilter::Info) - .init(); - let mut query_filter = QueryFilter::new(); query_filter.set_include_flags(1 | 8 | 4 | 2); query_filter.set_exclude_flags(0); - let navigator = TrinityNavigator::new(query_filter)?; + let mut nav_engine = TrinityNavEngine::new(TrinityDataProvider { map_id: 530 })?; + let nav_mesh_query = nav_engine.create_query(2048)?; + let navigator = TrinityNavigator::initialize(nav_mesh_query, query_filter)?; // Simple path // Shat Bridge (35,22) -> (35, 22) @@ -429,39 +396,11 @@ fn main() -> Result<(), Box> { // Terrokar (35,22) -> (35, 23) let start_position = Vector::from_xyz(-2051.9, 4350.97, 2.25); let end_position = Vector::from_xyz(-1916.12, 4894.67, 2.21); + let start_tile = TrinityNavigator::world_to_trinity(start_position.x, start_position.y); + let end_tile = TrinityNavigator::world_to_trinity(end_position.x, end_position.y); + nav_engine.add_tile(&start_tile.0, &start_tile.1)?; + nav_engine.add_tile(&end_tile.0, &end_tile.1)?; navigator.find_path(&start_position, &end_position)?; - // path.iter().for_each(|position| info!("{:?}", position)); - // Terrokar - - extern "C" fn frand() -> f32 { - let mut rng = rand::thread_rng(); - rng.gen::() - } - - match navigator - .query - .find_random_point(frand, &navigator.query_filter) - { - Ok((poly_ref, pos)) => { - log::info!("Random Position: {:?} {:?}", poly_ref, pos); - - let points = navigator.query.find_polys_around_circle( - poly_ref, - &pos, - 25.0, - &navigator.query_filter, - 256, - )?; - - for (poly_ref, parent_ref, cost) in points.iter() { - log::info!("Poly {:?} {:?} {}", poly_ref, parent_ref, cost); - } - } - Err(err) => log::warn!("Random Position Err: {:#?}", err), - }; - - // Cross zones - // ... Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index 75e00d9..c233005 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,7 +9,9 @@ use thiserror::Error; use recastnavigation_sys::*; bitflags! { + /// bitflags wrapper for Detour bit mask statuses #[repr(C)] + #[derive(Debug, Clone, Copy)] pub struct DtStatus: dtStatus { // High level status. const SUCCESS = DT_SUCCESS; // Operation failed. @@ -29,14 +31,17 @@ bitflags! { } impl DtStatus { + /// Checks whether the status contains the success flag pub fn is_success(&self) -> bool { self.contains(DtStatus::SUCCESS) } + /// Checks whether the status contains the in progress flag pub fn is_in_progress(&self) -> bool { self.contains(DtStatus::IN_PROGRESS) } + /// Checks whether the status contains the failed flag pub fn is_failed(&self) -> bool { self.contains(DtStatus::FAILURE) } @@ -54,6 +59,7 @@ bitflags! { } } +/// Error type across Divert Ecosystem #[derive(Error, Debug)] pub enum DivertError { #[error("detour internal status failure `{0:?}")] @@ -92,8 +98,11 @@ pub type DivertResult = std::result::Result; #[repr(C)] #[derive(Debug, Clone, Copy, Default, PartialEq)] pub struct Vector { + /// y component of vector pub y: f32, + /// z component of vector pub z: f32, + /// x component of vector pub x: f32, } @@ -152,6 +161,7 @@ impl Mul for Vector { } } +/// Paramters required to initialize a nav mesh #[derive(Debug)] pub struct NavMeshParams { pub origin: [f32; 3], @@ -173,7 +183,7 @@ impl NavMeshParams { } } -/// New Type to DdPolyRef +/// New Type to dtPolyRef #[derive(Debug, Default, PartialEq, Eq, Copy, Clone)] pub struct PolyRef(dtPolyRef); @@ -260,6 +270,7 @@ impl MeshTile { } } +/// A view of a Triangle in the Mesh #[derive(Debug)] pub struct Triangle<'a> { pub a: &'a Vector, @@ -290,12 +301,14 @@ impl PolygonDetail { unsafe { (*self.handle).triCount as usize } } + /// Access the slice of tri details corresponding to this polygon pub fn tris<'a>(&self, detail_tris: &'a [DtDetailTri]) -> &'a [DtDetailTri] { let tri_base = self.tri_base(); let tri_count = self.tri_count(); &detail_tris[tri_base..tri_base + tri_count] } + /// Access the slice of vertices corresponding to this polygon pub fn vertices<'a>(&self, detail_vertices: &'a [Vector]) -> &'a [Vector] { let vert_base = self.vert_base(); let vert_count = self.vert_count(); @@ -370,8 +383,6 @@ impl<'a> Iterator for PolygonTriangleIterator<'a> { flag: tri_flags[3], }; - log::info!("TriFlags: {:?}", tri_flags); - self.index += 1; Some(triangle) } @@ -455,16 +466,15 @@ pub struct NavMesh { handle: *mut dtNavMesh, } -unsafe impl Send for NavMesh {} - /// Provides functionality to interact with NavMesh and its underlying dtNavMesh impl NavMesh { + /// Gets the tile and polygon for the specified polygon reference. pub fn get_tile_and_poly_by_ref(&self, poly_ref: PolyRef) -> DivertResult<(MeshTile, Polygon)> { let mut output_tile = std::ptr::null(); let mut output_poly = std::ptr::null(); let status = unsafe { - DtStatus::from_bits_unchecked(dtNavMesh_getTileAndPolyByRef( + DtStatus::from_bits_retain(dtNavMesh_getTileAndPolyByRef( self.handle, *poly_ref, &mut output_tile, @@ -486,6 +496,7 @@ impl NavMesh { )) } + /// Calculates the tile grid location for the specified world position. pub fn calc_tile_location(&self, position: &Vector) -> (i32, i32) { unsafe { let mut tile_x: i32 = -1; @@ -500,6 +511,7 @@ impl NavMesh { } } + /// Gets the polygon reference for the tile's base polygon. pub fn get_poly_ref_base(&self, tile: &dtMeshTile) -> Option { unsafe { let poly_ref = dtNavMesh_getPolyRefBase(self.handle, tile); @@ -510,6 +522,7 @@ impl NavMesh { } } + /// Gets the tile at the specified grid location. pub fn get_tile_at(&self, tile_x: i32, tile_y: i32, layer: i32) -> Option<&dtMeshTile> { unsafe { dtNavMesh_getTileAt(self.handle, tile_x, tile_y, layer).as_ref() } } @@ -524,7 +537,7 @@ impl NavMesh { } let init_status = unsafe { - DtStatus::from_bits_unchecked(dtNavMesh_init( + DtStatus::from_bits_retain(dtNavMesh_init( dt_nav_mesh, &nav_mesh_params.to_detour_params(), )) @@ -548,7 +561,7 @@ impl NavMesh { let mut tile_ref = TileRef::default(); let add_tile_status = unsafe { - DtStatus::from_bits_unchecked(dtNavMesh_addTile( + DtStatus::from_bits_retain(dtNavMesh_addTile( self.handle, data, data_size as i32, @@ -576,7 +589,7 @@ impl NavMesh { } let init_status = unsafe { - DtStatus::from_bits_unchecked(dtNavMeshQuery_init( + DtStatus::from_bits_retain(dtNavMeshQuery_init( dt_nav_mesh_query, self.handle, max_nodes, @@ -602,7 +615,7 @@ impl Drop for NavMesh { } } -/// Safe bindings to dtQueryFilter +/// New type wrapper around dtQueryFilter pub struct QueryFilter(dtQueryFilter); /// Provides functionality to interact with QueryFilter and its underlying dtQueryFilter @@ -646,10 +659,9 @@ pub struct NavMeshQuery<'a> { _phantom: marker::PhantomData<&'a dtNavMeshQuery>, } -unsafe impl Send for NavMeshQuery<'_> {} - /// Provides functionality to interact with NavMeshQuery and its underlying dtNavMeshQuery impl<'a> NavMeshQuery<'a> { + /// Finds the polygons along the navigation graph that touch the specified circle. pub fn find_polys_around_circle( &self, start_ref: PolyRef, @@ -665,7 +677,7 @@ impl<'a> NavMeshQuery<'a> { let mut result_count = 0; let status = unsafe { - DtStatus::from_bits_unchecked(dtNavMeshQuery_findPolysAroundCircle( + DtStatus::from_bits_retain(dtNavMeshQuery_findPolysAroundCircle( self.handle, *start_ref, center as *const Vector as _, @@ -692,14 +704,17 @@ impl<'a> NavMeshQuery<'a> { let find_result = result_refs .into_iter() - .zip(result_parents.into_iter()) - .zip(result_costs.into_iter()) + .zip(result_parents) + .zip(result_costs) .map(|((poly_ref, parent_ref), result_cost)| (poly_ref, parent_ref, result_cost)) .collect(); Ok(find_result) } + /// Returns random location on navmesh. + /// Polygons are chosen weighted by area. + /// The search runs in linear related to number of polygon. pub fn find_random_point( &self, frand: extern "C" fn() -> f32, @@ -709,14 +724,13 @@ impl<'a> NavMeshQuery<'a> { let mut output_poly_ref = PolyRef::default(); let mut output_point = Vector::default(); - let random_point_status = - DtStatus::from_bits_unchecked(dtNavMeshQuery_findRandomPoint( - self.handle, - &filter.0, - Some(frand), - &mut output_poly_ref as *mut _ as *mut dtPolyRef, - &mut output_point as *mut Vector as _, - )); + let random_point_status = DtStatus::from_bits_retain(dtNavMeshQuery_findRandomPoint( + self.handle, + &filter.0, + Some(frand), + &mut output_poly_ref as *mut _ as *mut dtPolyRef, + &mut output_point as *mut Vector as _, + )); if random_point_status.is_failed() { return Err(DivertError::FindRandomPoint(random_point_status)); @@ -732,7 +746,7 @@ impl<'a> NavMeshQuery<'a> { let mut height: f32 = 0.0; let get_poly_height_status = unsafe { - DtStatus::from_bits_unchecked(dtNavMeshQuery_getPolyHeight( + DtStatus::from_bits_retain(dtNavMeshQuery_getPolyHeight( self.handle, *poly_ref, position as *const Vector as _, @@ -759,7 +773,7 @@ impl<'a> NavMeshQuery<'a> { let mut nearest_ref = PolyRef::default(); let nearest_status = unsafe { - DtStatus::from_bits_unchecked(dtNavMeshQuery_findNearestPoly( + DtStatus::from_bits_retain(dtNavMeshQuery_findNearestPoly( self.handle, center as *const Vector as _, extents as *const Vector as _, @@ -787,7 +801,7 @@ impl<'a> NavMeshQuery<'a> { let mut position_over_poly = false; let nearest_status = unsafe { - DtStatus::from_bits_unchecked(dtNavMeshQuery_closestPointOnPoly( + DtStatus::from_bits_retain(dtNavMeshQuery_closestPointOnPoly( self.handle, *poly_ref, position as *const Vector as _, @@ -813,7 +827,7 @@ impl<'a> NavMeshQuery<'a> { let mut closest_point = Vector::default(); let dt_result = unsafe { - DtStatus::from_bits_unchecked(dtNavMeshQuery_closestPointOnPolyBoundary( + DtStatus::from_bits_retain(dtNavMeshQuery_closestPointOnPolyBoundary( self.handle, *poly_ref, position as *const Vector as _, @@ -844,7 +858,7 @@ impl<'a> NavMeshQuery<'a> { let mut path_count = 0; let find_path_status = unsafe { - DtStatus::from_bits_unchecked(dtNavMeshQuery_findPath( + DtStatus::from_bits_retain(dtNavMeshQuery_findPath( self.handle, *start_ref, *end_ref, @@ -883,7 +897,7 @@ impl<'a> NavMeshQuery<'a> { let mut path: Vec = Vec::with_capacity(max_path.try_into().unwrap()); let find_path_status = unsafe { - DtStatus::from_bits_unchecked(dtNavMeshQuery_findPath( + DtStatus::from_bits_retain(dtNavMeshQuery_findPath( self.handle, *start_ref, *end_ref, @@ -925,13 +939,13 @@ impl<'a> NavMeshQuery<'a> { let mut straight_path_count = 0; let find_path_status = unsafe { - DtStatus::from_bits_unchecked(dtNavMeshQuery_findStraightPath( + DtStatus::from_bits_retain(dtNavMeshQuery_findStraightPath( self.handle, start_pos as *const Vector as _, end_pos as *const Vector as _, poly_path.as_ptr() as *const dtPolyRef, poly_path.len().try_into().unwrap(), - straight_path_points.as_mut_ptr() as *mut Vector as _, + straight_path_points.as_mut_ptr() as _, straight_path_flags.as_mut_ptr() as _, straight_path_polys.as_mut_ptr() as *mut dtPolyRef, &mut straight_path_count, @@ -973,13 +987,13 @@ impl<'a> NavMeshQuery<'a> { Vec::with_capacity(max_path.try_into().unwrap()); let straight_path_status = unsafe { - DtStatus::from_bits_unchecked(dtNavMeshQuery_findStraightPath( + DtStatus::from_bits_retain(dtNavMeshQuery_findStraightPath( self.handle, start_pos as *const Vector as _, end_pos as *const Vector as _, poly_path.as_ptr() as *const dtPolyRef, poly_path.len().try_into().unwrap(), - straight_path_points.as_mut_ptr() as *mut Vector as _, + straight_path_points.as_mut_ptr() as _, straight_path_flags.as_mut_ptr() as _, straight_path_polys.as_mut_ptr() as *mut dtPolyRef, &mut straight_path_count, @@ -1006,8 +1020,8 @@ impl<'a> NavMeshQuery<'a> { let path_result = straight_path_points .into_iter() - .zip(straight_path_flags.into_iter()) - .zip(straight_path_polys.into_iter()) + .zip(straight_path_flags) + .zip(straight_path_polys) .map(|((pos, flags), poly_ref)| (pos, flags, poly_ref)) .collect(); @@ -1030,7 +1044,7 @@ impl<'a> NavMeshQuery<'a> { let mut visited_count = 0; let move_along_surface_result = unsafe { - DtStatus::from_bits_unchecked(dtNavMeshQuery_moveAlongSurface( + DtStatus::from_bits_retain(dtNavMeshQuery_moveAlongSurface( self.handle, *start_ref, start_pos as *const Vector as _, @@ -1071,7 +1085,7 @@ impl<'a> NavMeshQuery<'a> { let mut result_pos = Vector::default(); let move_along_surface_result = unsafe { - DtStatus::from_bits_unchecked(dtNavMeshQuery_moveAlongSurface( + DtStatus::from_bits_retain(dtNavMeshQuery_moveAlongSurface( self.handle, *start_ref, start_pos as *const Vector as _, From f50a94ad206d0a37ec5433ef9c5010d6cec7ea66 Mon Sep 17 00:00:00 2001 From: ohchase Date: Sat, 20 Apr 2024 09:26:01 -0400 Subject: [PATCH 2/2] Version 0.6.0, more breaking changes and dependency upgrades --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4018282..0d446a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "divert" -version = "0.5.0" +version = "0.6.0" edition = "2021" description = "Rust Lang bindings for Recast Navigation" homepage = "https://github.com/ohchase/divert"