diff --git a/Cargo.toml b/Cargo.toml index 74911e2..137d544 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,7 @@ glam = { version = "0.27", features = ["approx"] } smallvec = { version = "1.9", features = ["union", "const_generics"] } bvh2d = { version = "0.5", git = "https://github.com/mockersf/bvh2d" } serde = { version = "1.0", features = ["derive"], optional = true } -spade = "2.2" +spade = "2.9" geo = "0.28.0" log = "0.4" thiserror = "1" diff --git a/benches/merger.rs b/benches/merger.rs index 7f8fcc1..3b7452c 100644 --- a/benches/merger.rs +++ b/benches/merger.rs @@ -140,7 +140,7 @@ fn merger(c: &mut Criterion) { triangulation.add_obstacle(ARENA_OBSTACLES[2].to_vec()); triangulation.add_obstacle(ARENA_OBSTACLES[3].to_vec()); triangulation.add_obstacle(ARENA_OBSTACLES[4].to_vec()); - let mesh: Mesh = triangulation.as_navmesh().unwrap(); + let mesh: Mesh = triangulation.as_navmesh(); b.iter(|| { let mut mesh = mesh.clone(); @@ -1970,9 +1970,8 @@ fn random_with_many_obstacles() -> Triangulation { fn merger_many_overlapping(c: &mut Criterion) { c.bench_function(&"merger many overlapping".to_string(), |b| { - let mut triangulation = random_with_many_obstacles(); - triangulation.merge_overlapping_obstacles(); - let mesh: Mesh = triangulation.as_navmesh().unwrap(); + let triangulation = random_with_many_obstacles(); + let mesh: Mesh = triangulation.as_navmesh(); b.iter(|| { let mut mesh = mesh.clone(); while mesh.merge_polygons() {} @@ -1983,9 +1982,8 @@ fn merger_many_overlapping(c: &mut Criterion) { fn merger_many_overlapping_once(c: &mut Criterion) { c.bench_function(&"merger many overlapping (once)".to_string(), |b| { - let mut triangulation = random_with_many_obstacles(); - triangulation.merge_overlapping_obstacles(); - let mesh: Mesh = triangulation.as_navmesh().unwrap(); + let triangulation = random_with_many_obstacles(); + let mesh: Mesh = triangulation.as_navmesh(); b.iter(|| { let mut mesh = mesh.clone(); mesh.merge_polygons(); diff --git a/benches/triangulation.rs b/benches/triangulation.rs index 8be0b43..2e3aac1 100644 --- a/benches/triangulation.rs +++ b/benches/triangulation.rs @@ -142,7 +142,7 @@ fn triangulation(c: &mut Criterion) { triangulation.add_obstacle(ARENA_OBSTACLES[3].to_vec()); triangulation.add_obstacle(ARENA_OBSTACLES[4].to_vec()); - let mesh: Mesh = triangulation.as_navmesh().unwrap(); + let mesh: Mesh = triangulation.as_navmesh(); black_box(mesh); }) }); @@ -161,7 +161,7 @@ fn triangulation_bulk(c: &mut Criterion) { ARENA_OBSTACLES[3].to_vec(), ARENA_OBSTACLES[4].to_vec(), ]); - let mesh: Mesh = triangulation.as_navmesh().unwrap(); + let mesh: Mesh = triangulation.as_navmesh(); black_box(mesh); }) }); @@ -178,9 +178,8 @@ fn triangulation_overlapping(c: &mut Criterion) { triangulation.add_obstacle(ARENA_OBSTACLES[2].to_vec()); triangulation.add_obstacle(ARENA_OBSTACLES[3].to_vec()); triangulation.add_obstacle(ARENA_OBSTACLES[4].to_vec()); - triangulation.merge_overlapping_obstacles(); - let mesh: Mesh = triangulation.as_navmesh().unwrap(); + let mesh: Mesh = triangulation.as_navmesh(); black_box(mesh); }) }); @@ -219,7 +218,7 @@ fn triangulation_square(c: &mut Criterion) { vec2(7.5, 7.5), vec2(7.5, 5.01), ]); - let mesh: Mesh = triangulation.as_navmesh().unwrap(); + let mesh: Mesh = triangulation.as_navmesh(); black_box(mesh); }) }); @@ -258,8 +257,7 @@ fn triangulation_square_overlapping(c: &mut Criterion) { vec2(7.5, 7.5), vec2(7.5, 4.0), ]); - triangulation.merge_overlapping_obstacles(); - let mesh: Mesh = triangulation.as_navmesh().unwrap(); + let mesh: Mesh = triangulation.as_navmesh(); black_box(mesh); }) }); @@ -2086,9 +2084,8 @@ fn random_with_many_obstacles() -> Triangulation { fn triangulation_many_overlapping(c: &mut Criterion) { c.bench_function(&"triangulation many overlapping".to_string(), |b| { b.iter(|| { - let mut triangulation = random_with_many_obstacles(); - triangulation.merge_overlapping_obstacles(); - let mesh: Mesh = triangulation.as_navmesh().unwrap(); + let triangulation = random_with_many_obstacles(); + let mesh: Mesh = triangulation.as_navmesh(); black_box(mesh); }) }); @@ -2100,9 +2097,8 @@ fn triangulation_many_overlapping_simplified(c: &mut Criterion) { |b| { b.iter(|| { let mut triangulation = random_with_many_obstacles(); - triangulation.merge_overlapping_obstacles(); triangulation.simplify(0.005); - let mesh: Mesh = triangulation.as_navmesh().unwrap(); + let mesh: Mesh = triangulation.as_navmesh(); black_box(mesh); }) }, diff --git a/examples/traced/src/bin/merged.rs b/examples/traced/src/bin/merged.rs index 28c33bf..061d1cc 100644 --- a/examples/traced/src/bin/merged.rs +++ b/examples/traced/src/bin/merged.rs @@ -1826,9 +1826,8 @@ fn main() { vec2(2.7777152, 2.6639614), ], ]); - triangulation.merge_overlapping_obstacles(); triangulation.simplify(0.005); - let mut mesh = triangulation.as_navmesh().unwrap(); + let mut mesh = triangulation.as_navmesh(); while mesh.merge_polygons() {} } } diff --git a/examples/traced/src/bin/triangulation.rs b/examples/traced/src/bin/triangulation.rs index ce2e7eb..151c64d 100644 --- a/examples/traced/src/bin/triangulation.rs +++ b/examples/traced/src/bin/triangulation.rs @@ -1826,8 +1826,7 @@ fn main() { vec2(2.7777152, 2.6639614), ], ]); - triangulation.merge_overlapping_obstacles(); triangulation.simplify(0.005); - triangulation.as_navmesh().unwrap(); + triangulation.as_navmesh(); } } diff --git a/src/input/triangulation.rs b/src/input/triangulation.rs index 3bf769e..563def0 100644 --- a/src/input/triangulation.rs +++ b/src/input/triangulation.rs @@ -4,10 +4,7 @@ use std::collections::VecDeque; use tracing::instrument; pub use geo::LineString; -use geo::{ - BooleanOps, Contains, Coord, CoordsIter, Intersects, MultiPolygon, Polygon as GeoPolygon, - SimplifyVwPreserve, -}; +use geo::{Contains, Coord, Polygon as GeoPolygon, SimplifyVwPreserve}; use glam::{vec2, Vec2}; use spade::{ConstrainedDelaunayTriangulation, Point2, Triangulation as SpadeTriangulation}; @@ -31,9 +28,6 @@ impl Triangulation { } /// Add an obstacle delimited by the list of points on its edges. - /// - /// Obstacles *MUST NOT* overlap. If some obstacles do overlap, use [`Triangulation::merge_overlapping_obstacles`] - /// before calling [`Triangulation::as_navmesh`]. pub fn add_obstacle(&mut self, edges: Vec) { self.inner.interiors_push(LineString::from( edges.iter().map(|v| (v.x, v.y)).collect::>(), @@ -41,9 +35,6 @@ impl Triangulation { } /// Add obstacles delimited by the list of points on their edges. - /// - /// Obstacles *MUST NOT* overlap. If some obstacles do overlap, use [`Triangulation::merge_overlapping_obstacles`] - /// before calling [`Triangulation::as_navmesh`]. pub fn add_obstacles(&mut self, obstacles: impl IntoIterator>) { let (exterior, interiors) = std::mem::replace(&mut self.inner, GeoPolygon::new(LineString(vec![]), vec![])) @@ -64,73 +55,6 @@ impl Triangulation { ); } - /// Merge overlapping obstacles. - /// - /// This must be called before converting the triangulation into a [`Mesh`] if there are overlapping obstacles, - /// otherwise it will fail. - #[cfg_attr(feature = "tracing", instrument(skip_all))] - pub fn merge_overlapping_obstacles(&mut self) { - let (mut exterior, interiors) = - std::mem::replace(&mut self.inner, GeoPolygon::new(LineString(vec![]), vec![])) - .into_inner(); - - let mut not_intersecting: Vec> = vec![]; - for poly in interiors.into_iter() { - let intersecting = not_intersecting - .iter() - .enumerate() - .filter(|(_, other)| poly.intersects(*other)) - .map(|(i, _)| i) - .collect::>(); - - let to_keep = if intersecting.is_empty() { - poly - } else { - #[cfg(feature = "tracing")] - let _merging_span = tracing::info_span!("merging polygons").entered(); - - let mut merged = MultiPolygon::( - intersecting - .iter() - .rev() - .map(|other| GeoPolygon::new(not_intersecting.remove(*other), vec![])) - .collect(), - ); - merged = merged.union(&GeoPolygon::new(poly, vec![]).into()); - LineString(merged.exterior_coords_iter().collect::>()) - }; - - if to_keep.intersects(&exterior) { - let new_exterior = - GeoPolygon::new(exterior, vec![]).difference(&GeoPolygon::new(to_keep, vec![])); - // Keep the biggest of the new exterior polygons - if new_exterior.0.len() > 1 { - let mut biggest = 0; - let mut biggest_length = 0; - for (i, poly) in new_exterior.0.iter().enumerate() { - let exterior_length = poly.exterior_coords_iter().len(); - if exterior_length > biggest_length { - biggest = i; - biggest_length = exterior_length; - } - } - exterior = LineString( - new_exterior.0[biggest] - .exterior_coords_iter() - .collect::>(), - ); - } else { - exterior = - LineString(new_exterior.0[0].exterior_coords_iter().collect::>()); - } - } else { - not_intersecting.push(to_keep); - } - } - - self.inner = GeoPolygon::new(exterior, not_intersecting); - } - /// Simplify the outer edge and obstacles, using a topology-preserving variant of the /// [Visvalingam-Whyatt algorithm](https://www.tandfonline.com/doi/abs/10.1179/000870493786962263). /// @@ -145,18 +69,20 @@ impl Triangulation { fn add_constraint_edges( cdt: &mut ConstrainedDelaunayTriangulation>, edges: &LineString, - ) -> Option<()> { + ) { let mut edge_iter = edges.coords().peekable(); + let mut next_point = None; loop { let from = edge_iter.next().unwrap(); let next = edge_iter.peek(); - let point_a = cdt - .insert(Point2 { + let point_a = next_point.unwrap_or_else(|| { + cdt.insert(Point2 { x: from.x, y: from.y, }) - .unwrap(); + .unwrap() + }); let point_b = if let Some(next) = next { cdt.insert(Point2 { x: next.x, @@ -164,22 +90,11 @@ impl Triangulation { }) .unwrap() } else { - cdt.insert(Point2 { - x: edges[0].x, - y: edges[0].y, - }) - .unwrap() - }; - if cdt.can_add_constraint(point_a, point_b) { - cdt.add_constraint(point_a, point_b); - } else { - return None; - } - if next.is_none() { break; - } + }; + cdt.add_constraint_and_split(point_a, point_b, |v| v); + next_point = Some(point_b); } - Some(()) } /// Convert the triangulation into a [`Mesh`]. @@ -191,7 +106,7 @@ impl Triangulation { /// # use glam::vec2; /// # use polyanya::Triangulation; /// # let triangulation = Triangulation::from_outer_edges(&[vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(0.0, 1.0)]); - /// let mut mesh = triangulation.as_navmesh().unwrap(); + /// let mut mesh = triangulation.as_navmesh(); /// /// // Merge polygons at least once before baking. /// mesh.merge_polygons(); @@ -200,18 +115,14 @@ impl Triangulation { /// mesh.bake(); /// ``` #[cfg_attr(feature = "tracing", instrument(skip_all))] - pub fn as_navmesh(&self) -> Option { + pub fn as_navmesh(&self) -> Mesh { let mut cdt = ConstrainedDelaunayTriangulation::>::new(); - Triangulation::add_constraint_edges(&mut cdt, self.inner.exterior())?; + Triangulation::add_constraint_edges(&mut cdt, self.inner.exterior()); - if self - .inner + self.inner .interiors() .iter() - .any(|obstacle| Triangulation::add_constraint_edges(&mut cdt, obstacle).is_none()) - { - return None; - } + .for_each(|obstacle| Triangulation::add_constraint_edges(&mut cdt, obstacle)); #[cfg(feature = "tracing")] let polygon_span = tracing::info_span!("listing polygons").entered(); @@ -278,10 +189,10 @@ impl Triangulation { #[cfg(feature = "tracing")] drop(vertex_span); - Some(Mesh { + Mesh { vertices, polygons, ..Default::default() - }) + } } } diff --git a/src/merger.rs b/src/merger.rs index 0060b85..998b432 100644 --- a/src/merger.rs +++ b/src/merger.rs @@ -283,7 +283,7 @@ mod test { Vec2::new(1., 1.), Vec2::new(1., -1.), ]); - let mut mesh = triangulation.as_navmesh().unwrap(); + let mut mesh = triangulation.as_navmesh(); // println!("{:#?}", mesh); while mesh.merge_polygons() { // println!("{:#?}", mesh); @@ -316,7 +316,7 @@ mod test { ], ]); triangulation.simplify(0.001); - let mut mesh = triangulation.as_navmesh().unwrap(); + let mut mesh = triangulation.as_navmesh(); mesh.unbake(); // println!("{:#?}", mesh); diff --git a/tests/arena-triangulation.rs b/tests/arena-triangulation.rs index f7ebf8b..5832764 100644 --- a/tests/arena-triangulation.rs +++ b/tests/arena-triangulation.rs @@ -136,7 +136,7 @@ fn arena_mesh() -> Mesh { vec2(26., 7.), vec2(26., 10.), ]); - triangulation.as_navmesh().unwrap() + triangulation.as_navmesh() } #[test] diff --git a/tests/triangulation.rs b/tests/triangulation.rs index 9d45519..139cb23 100644 --- a/tests/triangulation.rs +++ b/tests/triangulation.rs @@ -15,7 +15,7 @@ fn is_in_mesh() { vec2(7.5, 7.5), vec2(7.5, 2.5), ]); - let mesh: Mesh = triangulation.as_navmesh().unwrap(); + let mesh: Mesh = triangulation.as_navmesh(); for i in 0..10 { for j in 0..10 { if i > 2 && i < 8 && j > 2 && j < 8 { @@ -59,9 +59,8 @@ fn is_in_mesh_4_obstacles() { vec2(7.5, 7.5), vec2(7.5, 5.0), ]); - triangulation.merge_overlapping_obstacles(); triangulation.simplify(0.5); - let mesh: Mesh = triangulation.as_navmesh().unwrap(); + let mesh: Mesh = triangulation.as_navmesh(); dbg!(mesh.polygons.len()); for i in 0..10 { @@ -76,7 +75,6 @@ fn is_in_mesh_4_obstacles() { } #[test] -#[should_panic] fn is_in_mesh_overlapping() { let mut triangulation = Triangulation::from_outer_edges(&[ vec2(0.0, 0.0), @@ -108,7 +106,7 @@ fn is_in_mesh_overlapping() { vec2(7.5, 7.5), vec2(7.5, 4.0), ]); - let mesh: Mesh = triangulation.as_navmesh().unwrap(); + let mesh: Mesh = triangulation.as_navmesh(); for i in 0..10 { for j in 0..10 { if i > 2 && i < 8 && j > 2 && j < 8 { @@ -152,56 +150,7 @@ fn is_in_mesh_overlapping_merged() { vec2(7.5, 7.5), vec2(7.5, 4.0), ]); - triangulation.merge_overlapping_obstacles(); - let mesh: Mesh = triangulation.as_navmesh().unwrap(); - for i in 0..10 { - for j in 0..10 { - if i > 2 && i < 8 && j > 2 && j < 8 { - assert!(!mesh.point_in_mesh(vec2(i as f32, j as f32))); - } else { - assert!(mesh.point_in_mesh(vec2(i as f32, j as f32))); - } - } - } -} - -#[test] -fn is_in_mesh_overlapping_simplified() { - let mut triangulation = Triangulation::from_outer_edges(&[ - vec2(0.0, 0.0), - vec2(10.0, 0.0), - vec2(10.0, 10.0), - vec2(0.0, 10.0), - ]); - triangulation.add_obstacle(vec![ - vec2(2.5, 2.5), - vec2(2.5, 6.0), - vec2(6.0, 6.0), - vec2(6.0, 2.5), - ]); - triangulation.add_obstacle(vec![ - vec2(2.5, 4.0), - vec2(2.5, 7.5), - vec2(6.0, 7.5), - vec2(6.0, 4.0), - ]); - triangulation.add_obstacle(vec![ - vec2(4.0, 2.5), - vec2(4.0, 6.0), - vec2(7.5, 6.0), - vec2(7.5, 2.5), - ]); - triangulation.add_obstacle(vec![ - vec2(4.0, 4.0), - vec2(4.0, 7.5), - vec2(7.5, 7.5), - vec2(7.5, 4.0), - ]); - triangulation.merge_overlapping_obstacles(); - let polygons_before = triangulation.as_navmesh().unwrap().polygons; - triangulation.simplify(1.0); - let mesh: Mesh = triangulation.as_navmesh().unwrap(); - assert!(dbg!(polygons_before.len()) > dbg!(mesh.polygons.len())); + let mesh: Mesh = triangulation.as_navmesh(); for i in 0..10 { for j in 0..10 { if i > 2 && i < 8 && j > 2 && j < 8 {