diff --git a/src/layers.rs b/src/layers.rs index 832da48..502afae 100644 --- a/src/layers.rs +++ b/src/layers.rs @@ -256,11 +256,88 @@ impl Layer { }); vertices } + + /// Find the closest point in the layer + /// + /// This will continue until it finds a point in the layer + pub fn get_closest_point(&self, point: Vec2, delta: f32) -> Vec2 { + let mut step = 0; + loop { + if let Some((new_point, _)) = self.get_closest_point_inner(point, delta, step) { + return new_point; + } + step += 1; + } + } + + #[inline(always)] + pub(crate) fn get_closest_point_inner( + &self, + point: Vec2, + delta: f32, + step: u32, + ) -> Option<(Vec2, u32)> { + let sample = 10; + for i in 0..=(sample * step) { + let angle = i as f32 * std::f32::consts::TAU / (sample * (step + 1)) as f32; + let (x, y) = angle.sin_cos(); + let new_point = point + vec2(x, y) * delta * step as f32; + + let poly = if self.baked_polygons.is_none() { + self.get_point_location_unit(new_point) + } else { + self.get_point_location_unit_baked(new_point) + }; + if poly != u32::MAX { + return Some((new_point, poly)); + } + } + return None; + } + + /// Find the closest point in the layer in the given direction + /// + /// This will stop after going `delta` * 100 distance in the `towards` direction + pub fn get_closest_point_towards( + &self, + point: Vec2, + delta: f32, + towards: Vec2, + ) -> Option { + let direction = -(point - towards).normalize(); + for step in 0..100 { + if let Some((new_point, _)) = + self.get_closest_point_towards_inner(point, delta, direction, step) + { + return Some(new_point); + } + } + return None; + } + + pub(crate) fn get_closest_point_towards_inner( + &self, + point: Vec2, + delta: f32, + direction: Vec2, + step: u32, + ) -> Option<(Vec2, u32)> { + let point = point + direction * delta * step as f32; + let poly = if self.baked_polygons.is_none() { + self.get_point_location_unit(point) + } else { + self.get_point_location_unit_baked(point) + }; + if poly != u32::MAX { + return Some((point, poly)); + } + return None; + } } #[cfg(test)] mod tests { - use std::collections::HashSet; + use std::{collections::HashSet, u32}; #[cfg(feature = "detailed-layers")] use crate::helpers::line_intersect_segment; @@ -633,14 +710,8 @@ mod tests { let mesh = mesh_overlapping_layers(); let path = dbg!(mesh .path( - Coords { - pos: vec2(2.5, 1.5), - layer: Some(0) - }, - Coords { - pos: vec2(2.5, 1.5), - layer: Some(1) - }, + Coords::on_layer(vec2(2.5, 1.5), 0), + Coords::on_layer(vec2(2.5, 1.5), 1), ) .unwrap()); assert_eq!( @@ -659,14 +730,8 @@ mod tests { let path_back = dbg!(mesh .path( - Coords { - pos: vec2(2.5, 1.5), - layer: Some(1) - }, - Coords { - pos: vec2(2.5, 1.5), - layer: Some(0) - }, + Coords::on_layer(vec2(2.5, 1.5), 1), + Coords::on_layer(vec2(2.5, 1.5), 0), ) .unwrap()); assert_eq!( @@ -688,17 +753,11 @@ mod tests { fn find_point_on_layer() { let mesh = mesh_overlapping_layers(); assert_eq!( - mesh.get_point_location(Coords { - pos: vec2(2.5, 1.5), - layer: Some(0) - }), + mesh.get_point_location(Coords::on_layer(vec2(2.5, 1.5), 0)), 1 ); assert_eq!( - mesh.get_point_location(Coords { - pos: vec2(2.5, 1.5), - layer: Some(1) - }), + mesh.get_point_location(Coords::on_layer(vec2(2.5, 1.5), 1)), u32::from_layer_and_polygon(1, 0) ); } @@ -715,4 +774,75 @@ mod tests { vec![0, 1, 2, 3] ); } + + #[test] + fn get_closest_point() { + let mesh = mesh_u_grid(); + + assert_eq!( + mesh.layers[0].get_closest_point(vec2(1.5, 1.5), 0.1), + vec2(1.5, 1.0) + ); + assert_eq!( + mesh.get_closest_point(vec2(1.5, 1.5), 0.1), + Coords { + pos: vec2(1.5, 1.0), + layer: Some(0), + polygon_index: 1, + } + ); + + assert_eq!( + mesh.layers[0].get_closest_point(vec2(1.25, 1.5), 0.01), + vec2(1.25, 1.0) + ); + assert_eq!( + mesh.layers[1].get_closest_point(vec2(1.25, 1.5), 0.01), + vec2(1.0, 1.5) + ); + assert_eq!( + mesh.get_closest_point(vec2(1.25, 1.5), 0.01), + Coords { + pos: vec2(1.0, 1.5), + layer: Some(1), + polygon_index: 0, + } + ); + } + + #[test] + fn get_closest_point_towards() { + let mesh = mesh_u_grid(); + + assert_eq!( + mesh.layers[0].get_closest_point_towards(vec2(1.5, 1.5), 0.1, vec2(1.5, 0.5)), + Some(vec2(1.5, 1.0)) + ); + assert_eq!( + mesh.get_closest_point_towards(vec2(1.5, 1.5), 0.1, vec2(1.5, 0.5)), + Some(Coords { + pos: vec2(1.5, 1.0), + layer: Some(0), + polygon_index: 1, + }) + ); + + assert_eq!( + mesh.layers[0].get_closest_point_towards(vec2(1.5, 1.5), 0.1, vec2(0.5, 1.5)), + None + ); + assert_eq!( + mesh.get_closest_point_towards(vec2(1.5, 1.5), 0.1, vec2(0.5, 1.5)), + Some(Coords { + pos: vec2(1.0, 1.5), + layer: Some(1), + polygon_index: 0, + }) + ); + + assert_eq!( + mesh.layers[0].get_closest_point_towards(vec2(1.5, 1.5), 0.2, vec2(1.5, 0.5)), + Some(vec2(1.5, 0.9)) + ); + } } diff --git a/src/lib.rs b/src/lib.rs index ae41f20..29ba202 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -88,6 +88,10 @@ pub struct Coords { /// /// If specified, the point will be searched in that layer only. pub layer: Option, + /// internal: this coords have been built by a search on the mesh that found the polygon index + /// if used for a path, this will be used directly instead of searching for it again in the mesh + /// default value is u32::MAX which means it hasn't been searched + polygon_index: u32, } impl From for Coords { @@ -95,6 +99,18 @@ impl From for Coords { Coords { pos: value, layer: None, + polygon_index: u32::MAX, + } + } +} + +impl Coords { + /// A point on the navigation mesh on the specified layer + pub fn on_layer(pos: Vec2, layer: u8) -> Self { + Coords { + pos, + layer: Some(layer), + polygon_index: u32::MAX, } } } @@ -218,11 +234,19 @@ impl Mesh { let from = from.into(); let to = to.into(); - let starting_polygon_index = self.get_point_location(from); + let starting_polygon_index = if from.polygon_index != u32::MAX { + from.polygon_index + } else { + self.get_point_location(from) + }; if starting_polygon_index == u32::MAX { return None; } - let ending_polygon = self.get_point_location(to); + let ending_polygon = if to.polygon_index != u32::MAX { + to.polygon_index + } else { + self.get_point_location(to) + }; if ending_polygon == u32::MAX { return None; } @@ -410,6 +434,7 @@ impl Mesh { .map(|p| Coords { pos: coords.pos, layer: Some(p.layer()), + polygon_index: *p, }) .collect() } @@ -470,6 +495,84 @@ impl Mesh { .collect() } } + + /// Find the closest point in the mesh + /// + /// This will continue until it finds a point in the layer + pub fn get_closest_point(&self, point: impl Into, delta: f32) -> Coords { + let point = point.into(); + let mut step = 0; + if let Some(layer_index) = point.layer { + loop { + if let Some((new_point, polygon)) = self.layers[layer_index as usize] + .get_closest_point_inner(point.pos, delta, step) + { + return Coords { + pos: new_point, + layer: Some(layer_index), + polygon_index: polygon, + }; + } + step += 1; + } + } else { + loop { + for (index, layer) in self.layers.iter().enumerate() { + if let Some((new_point, polygon)) = + layer.get_closest_point_inner(point.pos, delta, step) + { + return Coords { + pos: new_point, + layer: Some(index as u8), + polygon_index: polygon, + }; + } + } + step += 1; + } + } + } + + /// Find the closest point in the mesh in the given direction + /// + /// This will stop after going `delta` * 100 distance in the `towards` direction + pub fn get_closest_point_towards( + &self, + point: impl Into, + delta: f32, + towards: Vec2, + ) -> Option { + let point = point.into(); + let direction = -(point.pos - towards).normalize(); + if let Some(layer_index) = point.layer { + for step in 0..100 { + if let Some((new_point, polygon)) = self.layers[layer_index as usize] + .get_closest_point_towards_inner(point.pos, delta, direction, step) + { + return Some(Coords { + pos: new_point, + layer: Some(layer_index), + polygon_index: polygon, + }); + } + } + } else { + for step in 0..100 { + for (index, layer) in self.layers.iter().enumerate() { + if let Some((new_point, polygon)) = + layer.get_closest_point_towards_inner(point.pos, delta, direction, step) + { + return Some(Coords { + pos: new_point, + layer: Some(index as u8), + polygon_index: polygon, + }); + } + } + } + } + return None; + } } #[derive(PartialEq, Debug)]