From 63d91c2a80d9c30144ff89d0e1d641bb7cca982c Mon Sep 17 00:00:00 2001 From: Stefan Altmayer Date: Sun, 19 Nov 2023 12:23:11 +0100 Subject: [PATCH] Small cleanup for DelaunayRefinement. Also contains a bugfix: If a skinny face's circumcenter was lying _on_ a constraint edge, it would split that edge and _not_ update the `excluded_faces` buffer. --- src/cdt.rs | 6 +-- src/delaunay_core/mod.rs | 3 -- src/delaunay_core/refinement.rs | 53 +++++++++++++--------- src/delaunay_core/refinement_fuzz_tests.rs | 52 --------------------- 4 files changed, 35 insertions(+), 79 deletions(-) delete mode 100644 src/delaunay_core/refinement_fuzz_tests.rs diff --git a/src/cdt.rs b/src/cdt.rs index acbf1e3..adf3e3c 100644 --- a/src/cdt.rs +++ b/src/cdt.rs @@ -1098,7 +1098,7 @@ mod test { const NUM_VERTICES: usize = 51; let vertices = (0..NUM_VERTICES).map(|i| { - let angle = std::f64::consts::PI * 2.0 * i as f64 / NUM_VERTICES as f64; + let angle = core::f64::consts::PI * 2.0 * i as f64 / NUM_VERTICES as f64; let (sin, cos) = angle.sin_cos(); Point2::new(sin, cos) }); @@ -1121,8 +1121,8 @@ mod test { fn test_add_constraint_edges_empty() -> Result<(), InsertionError> { let mut cdt = Cdt::new(); - cdt.add_constraint_edges(std::iter::empty(), false)?; - cdt.add_constraint_edges(std::iter::empty(), true)?; + cdt.add_constraint_edges(core::iter::empty(), false)?; + cdt.add_constraint_edges(core::iter::empty(), true)?; assert_eq!(cdt.num_vertices(), 0); assert_eq!(cdt.num_constraints(), 0); diff --git a/src/delaunay_core/mod.rs b/src/delaunay_core/mod.rs index 9dbb5fa..3217313 100644 --- a/src/delaunay_core/mod.rs +++ b/src/delaunay_core/mod.rs @@ -3,9 +3,6 @@ mod bulk_load; #[cfg(test)] mod bulk_load_fuzz_tests; -#[cfg(test)] -mod refinement_fuzz_tests; - mod dcel; pub mod dcel_operations; mod handles; diff --git a/src/delaunay_core/refinement.rs b/src/delaunay_core/refinement.rs index 04834e2..d4fc97b 100644 --- a/src/delaunay_core/refinement.rs +++ b/src/delaunay_core/refinement.rs @@ -1,5 +1,9 @@ -use std::collections::{HashMap, HashSet, VecDeque}; +#[cfg(not(feature = "std"))] +use hashbrown::{HashMap, HashSet}; +#[cfg(feature = "std")] +use std::collections::{HashMap, HashSet}; +use alloc::collections::VecDeque; use alloc::vec::Vec; use num_traits::Float; @@ -115,8 +119,8 @@ impl AngleLimit { } } -impl std::fmt::Debug for AngleLimit { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl alloc::fmt::Debug for AngleLimit { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> alloc::fmt::Result { f.debug_struct("AngleLimit") .field( "angle limit (deg)", @@ -141,7 +145,7 @@ enum RefinementHint { MustRefine, } -/// Controls how a refinement is performed. +/// Controls how Delaunay refinement is performed. /// /// Refer to [ConstrainedDelaunayTriangulation::refine] and methods implemented by this type for more details /// about which parameters are supported. @@ -269,7 +273,7 @@ impl RefinementParameters { /// in this case - some areas may have become overly refined, others might be overlooked completely. Consider changing /// the parameters (most notably the angle limit) if the refinement runs out of vertices. /// - /// Use [RefinementResult::refinement_complete] to check if the refinement has completed successfully. + /// Use [RefinementResult::refinement_complete] to check if the number of additional vertices was sufficient. pub fn with_max_additional_vertices(mut self, max_additional_vertices: usize) -> Self { self.max_additional_vertices = Some(max_additional_vertices); self @@ -352,8 +356,8 @@ impl RefinementParameters { if next_todo_list.is_empty() { break; } - std::mem::swap(&mut inner_faces, &mut outer_faces); - std::mem::swap(&mut next_todo_list, &mut current_todo_list); + core::mem::swap(&mut inner_faces, &mut outer_faces); + core::mem::swap(&mut next_todo_list, &mut current_todo_list); return_outer_faces = !return_outer_faces; } @@ -509,7 +513,7 @@ where pub fn refine(&mut self, mut parameters: RefinementParameters) -> RefinementResult { use PositionInTriangulation::*; - let mut excluded_faces = std::mem::take(&mut parameters.excluded_faces); + let mut excluded_faces = core::mem::take(&mut parameters.excluded_faces); let mut legalize_edges_buffer = Vec::with_capacity(20); let mut forcibly_split_segments_buffer = Vec::with_capacity(5); @@ -542,7 +546,8 @@ where let num_additional_vertices = parameters .max_additional_vertices .unwrap_or(num_initial_vertices * 10); - let max_allowed_vertices = num_initial_vertices + num_additional_vertices; + let max_allowed_vertices = + usize::saturating_add(num_initial_vertices, num_additional_vertices); let mut refinement_complete = true; @@ -692,6 +697,14 @@ where continue; } + if edge.is_constraint_edge() { + // Splitting constraint edges may require updating the "excluded faces" buffer. + // This is a little cumbersome, we'll re-use the existing implementation of edge + // splitting (see function resolve_encroachment). + forcibly_split_segments_buffer.push(edge.fix().as_undirected()); + continue; + } + for edge in [edge, edge.rev()] { if !edge.is_outer_edge() { legalize_edges_buffer.extend([edge.next().fix(), edge.prev().fix()]) @@ -699,6 +712,9 @@ where } } OnFace(face_under_circumcenter) => { + if parameters.excluded_faces.contains(&face_under_circumcenter) { + continue; + } legalize_edges_buffer.extend( self.face(face_under_circumcenter) .adjacent_edges() @@ -845,16 +861,11 @@ where let final_position = v0.position().mul(weight0).add(v1.position().mul(weight1)); let (v0, v1) = (v0.fix(), v1.fix()); - let is_left_side_excluded = segment - .face() - .as_inner() - .map(|face| excluded_faces.contains(&face.fix())); - - let is_right_side_excluded = segment - .rev() - .face() - .as_inner() - .map(|face| excluded_faces.contains(&face.fix())); + let [is_left_side_excluded, is_right_side_excluded] = + [segment.face(), segment.rev().face()].map(|face| { + face.as_inner() + .map(|face| excluded_faces.contains(&face.fix())) + }); let is_constraint_edge = segment.is_constraint_edge(); @@ -929,7 +940,7 @@ fn nearest_power_of_two(input: S) -> S { #[cfg(test)] mod test { - use std::collections::HashSet; + use super::HashSet; use crate::{ test_utilities::{random_points_with_seed, SEED}, @@ -1070,7 +1081,7 @@ mod test { assert_eq!( excluded_faces, - HashSet::from_iter(std::iter::once(excluded_face)) + HashSet::from_iter(core::iter::once(excluded_face)) ); Ok(()) diff --git a/src/delaunay_core/refinement_fuzz_tests.rs b/src/delaunay_core/refinement_fuzz_tests.rs deleted file mode 100644 index ce399eb..0000000 --- a/src/delaunay_core/refinement_fuzz_tests.rs +++ /dev/null @@ -1,52 +0,0 @@ -use crate::{ - ConstrainedDelaunayTriangulation, InsertionError, Point2, RefinementParameters, Triangulation, - TriangulationExt, -}; - -use alloc::vec; -use alloc::vec::Vec; - -fn refinement_parameters() -> RefinementParameters { - RefinementParameters::new().with_max_additional_vertices(1000) -} - -fn fuzz_test( - vertices: Vec>, - edges: Vec<[Point2; 2]>, -) -> Result<(), InsertionError> { - let mut triangulation = ConstrainedDelaunayTriangulation::<_>::bulk_load(vertices)?; - let result = triangulation.refine(refinement_parameters()); - for edge in edges { - triangulation.add_constraint_edge(edge[0], edge[1])?; - } - - assert!(result.refinement_complete); - - triangulation.sanity_check(); - Ok(()) -} - -#[test] -fn refine_fuzz_1() -> Result<(), InsertionError> { - fuzz_test( - vec![ - Point2::new(5.0, 0.0), - Point2::new(1.0, 2.0), - Point2::new(1.0, 16.0), - ], - vec![], - ) -} - -#[test] -pub fn refine_fuzz_2() -> Result<(), InsertionError> { - fuzz_test( - vec![], - vec![ - [Point2::new(0.0, 0.0), Point2::new(60.0, 0.0)], - [Point2::new(0.0, 0.0), Point2::new(60.0, 10.0)], - [Point2::new(0.0, 0.0), Point2::new(40.0, 0.0)], - [Point2::new(0.0, 0.0), Point2::new(0.0, -40.0)], - ], - ) -}