spade/delaunay_core/refinement.rs
1#[cfg(not(feature = "std"))]
2use hashbrown::{HashMap, HashSet};
3#[cfg(feature = "std")]
4use std::collections::{HashMap, HashSet};
5
6use alloc::collections::VecDeque;
7use alloc::vec::Vec;
8
9use num_traits::Float;
10
11use crate::{
12 delaunay_core::{dcel_operations::append_unconnected_vertex, math},
13 CdtEdge, ConstrainedDelaunayTriangulation, HasPosition, HintGenerator, Point2,
14 PositionInTriangulation, SpadeNum, Triangulation,
15};
16
17use super::{
18 DirectedEdgeHandle, FaceHandle, FixedFaceHandle, FixedUndirectedEdgeHandle, FixedVertexHandle,
19 InnerTag, TriangulationExt, UndirectedEdgeHandle,
20};
21
22/// Contains details about the outcome of a refinement procedure.
23///
24/// *See [ConstrainedDelaunayTriangulation::refine]*
25#[derive(Debug, Clone)]
26pub struct RefinementResult {
27 /// A `Vec` containing all outer faces that were excluded from refinement.
28 ///
29 /// This `Vec` will be empty unless [RefinementParameters::exclude_outer_faces] has been set.
30 /// In this case, the `Vec` contains all finite outer faces, including any additional outer faces
31 /// that were created during the refinement.
32 pub excluded_faces: Vec<FixedFaceHandle<InnerTag>>,
33
34 /// Set to `true` if the refinement could be completed regularly.
35 ///
36 /// This will be `false` if the refinement ran out of additional vertices
37 /// (see [RefinementParameters::with_max_additional_vertices]). Consider adapting the refinement parameters in this case,
38 /// either by using a higher additional vertex count or by e.g. lowering the [angle limit](RefinementParameters::with_angle_limit).
39 pub refinement_complete: bool,
40}
41
42/// Specifies the minimum allowed angle that should be kept after a refinement procedure.
43///
44/// The refinement algorithm will attempt to keep the *minimum angle in the triangulation* greater than
45/// an angle limit specified with this struct.
46///
47/// *See [ConstrainedDelaunayTriangulation::refine], [RefinementParameters::with_angle_limit]*
48#[derive(Copy, Clone, PartialEq, PartialOrd)]
49pub struct AngleLimit {
50 radius_to_shortest_edge_limit: f64,
51}
52
53impl AngleLimit {
54 /// Create a new angle limit from an angle given in degrees.
55 ///
56 /// Note that angles larger than 30 degrees will quickly lead to overrefinement as the algorithm
57 /// cannot necessarily guarantee termination (other than by limiting the number of additional inserted vertices).
58 ///
59 /// Defaults to 30°. An angle of 0 degrees will disable refining due to small angles.
60 ///
61 /// *See also [from_rad](AngleLimit::from_rad)*
62 pub fn from_deg(degree: f64) -> Self {
63 Self::from_rad(degree.to_radians())
64 }
65
66 /// Create a new angle limit from an angle given in radians.
67 ///
68 /// Note angles larger than 30 degrees (≈0.52rad = PI / 6) will quickly lead to poor refinement quality.
69 /// Passing in an angle of 0rad will disable refining due to small angles.
70 ///
71 /// *See also [from_deg](AngleLimit::from_deg)*
72 pub fn from_rad(rad: f64) -> Self {
73 let sin = rad.sin();
74 if sin == 0.0 {
75 Self::from_radius_to_shortest_edge_ratio(f64::INFINITY)
76 } else {
77 Self::from_radius_to_shortest_edge_ratio(0.5 / sin)
78 }
79 }
80
81 /// Returns the radius to shortest edge limit corresponding to this angle limit.
82 ///
83 /// See [from_radius_to_shortest_edge_ratio](AngleLimit::from_radius_to_shortest_edge_ratio) for more
84 /// information.
85 pub fn radius_to_shortest_edge_limit(&self) -> f64 {
86 self.radius_to_shortest_edge_limit
87 }
88
89 /// Creates a new angle limit by specifying the circumradius to shortest edge ratio that must be kept.
90 ///
91 /// For each face, this ratio is calculated by dividing the circumradius of the face by the length of its shortest
92 /// edge.
93 /// This ratio is related directly to the minimum allowed angle by the formula
94 /// `ratio = 1 / (2 sin * (min_angle))`.
95 /// The *larger* the allowed min angle is, the *smaller* ratio becomes.
96 ///
97 /// Larger ratio values will lead to a less refined triangulation. Passing in `f64::INFINITY` will disable
98 /// refining due to small angles.
99 ///
100 /// Defaults to 1.0 (30 degrees).
101 ///
102 /// # Example values
103 ///
104 /// | ratio | Bound on smallest angle (deg) | Bound on smallest angle (rad) |
105 /// |-------|-------------------------------|-------------------------------|
106 /// | 0.58 | 60.00° | 1.05 |
107 /// | 0.60 | 56.44° | 0.99 |
108 /// | 0.70 | 45.58° | 0.80 |
109 /// | 0.80 | 38.68° | 0.68 |
110 /// | 0.90 | 33.75° | 0.59 |
111 /// | 1.00 | 30.00° | 0.52 |
112 /// | 1.10 | 27.04° | 0.47 |
113 /// | 1.20 | 24.62° | 0.43 |
114 /// | 1.30 | 22.62° | 0.39 |
115 /// | +INF | 0° | 0 |
116 pub fn from_radius_to_shortest_edge_ratio(ratio: f64) -> Self {
117 Self {
118 radius_to_shortest_edge_limit: ratio,
119 }
120 }
121}
122
123impl alloc::fmt::Debug for AngleLimit {
124 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> alloc::fmt::Result {
125 f.debug_struct("AngleLimit")
126 .field(
127 "angle limit (deg)",
128 &(0.5 / self.radius_to_shortest_edge_limit)
129 .asin()
130 .to_degrees(),
131 )
132 .finish()
133 }
134}
135
136impl Default for AngleLimit {
137 fn default() -> Self {
138 Self::from_radius_to_shortest_edge_ratio(1.0)
139 }
140}
141
142#[derive(Debug, PartialEq, PartialOrd, Clone, Copy, Hash)]
143enum RefinementHint {
144 Ignore,
145 ShouldRefine,
146 MustRefine,
147}
148
149/// Controls how Delaunay refinement is performed.
150///
151/// Refer to [ConstrainedDelaunayTriangulation::refine] and methods implemented by this type for more details
152/// about which parameters are supported.
153///
154/// # Example
155///
156/// ```
157/// use spade::{AngleLimit, ConstrainedDelaunayTriangulation, Point2, RefinementParameters};
158///
159/// fn refine_cdt(cdt: &mut ConstrainedDelaunayTriangulation<Point2<f64>>) {
160/// let params = RefinementParameters::<f64>::new()
161/// .exclude_outer_faces(true)
162/// .keep_constraint_edges()
163/// .with_min_required_area(0.0001)
164/// .with_max_allowed_area(0.5)
165/// .with_angle_limit(AngleLimit::from_deg(25.0));
166///
167/// cdt.refine(params);
168/// }
169/// ```
170#[derive(Debug, PartialEq, Clone)]
171pub struct RefinementParameters<S: SpadeNum + Float> {
172 max_additional_vertices: Option<usize>,
173
174 angle_limit: AngleLimit,
175 min_area: Option<S>,
176 max_area: Option<S>,
177 keep_constraint_edges: bool,
178 exclude_outer_faces: bool,
179}
180
181impl<S: SpadeNum + Float> Default for RefinementParameters<S> {
182 fn default() -> Self {
183 Self {
184 max_additional_vertices: None,
185 angle_limit: AngleLimit::from_radius_to_shortest_edge_ratio(1.0),
186 min_area: None,
187 max_area: None,
188 exclude_outer_faces: false,
189 keep_constraint_edges: false,
190 }
191 }
192}
193
194impl<S: SpadeNum + Float> RefinementParameters<S> {
195 /// Creates a new set of `RefinementParameters`.
196 ///
197 /// The following values will be used by `new` and `Self::default`:
198 /// * `exclude_outer_faces`: disabled - all faces are used for refinement
199 /// * `keep_constraint_edges`: disabled
200 /// * `min_required_area`: disabled - no lower area limit is used
201 /// * `max_allowed_area`: disabled - no upper area limit is used
202 /// * `angle_limit`: 30 degrees by default.
203 /// * `num_additional_vertices`: 10 times the number of vertices in the triangulation
204 pub fn new() -> Self {
205 Self::default()
206 }
207
208 /// Specifies the smallest allowed inner angle in a refined triangulation.
209 ///
210 /// The refinement algorithm will attempt to insert additional points (called steiner points) until the
211 /// minimum angle is larger than the angle bound specified by the refinement parameters.
212 ///
213 /// Defaults to 30 degrees.
214 ///
215 /// Note that angle limits much larger than 30 degrees may not always terminate successfully - consider checking
216 /// [RefinementResult::refinement_complete] to make sure that the angle limit could actually be applied everywhere.
217 ///
218 /// # Examples of different angle limits
219 /// <table>
220 /// <tr><th>0° (no angle refinement)</th><th>20°</th><th>30°</th><th>34°</th></tr>
221 /// <tr><td>
222 #[doc = concat!(include_str!("../../images/angle_limit_00.svg"), "</td><td>")]
223 #[doc = concat!(include_str!("../../images/angle_limit_20.svg"), "</td><td>")]
224 #[doc = concat!(include_str!("../../images/angle_limit_30.svg"), "</td><td>")]
225 #[doc = concat!(include_str!("../../images/angle_limit_34.svg"), "</td></tr></table>")]
226 ///
227 /// *See also [ConstrainedDelaunayTriangulation::refine]*
228 pub fn with_angle_limit(mut self, angle_limit: AngleLimit) -> Self {
229 self.angle_limit = angle_limit;
230 self
231 }
232
233 /// Specifies a lower bound for a triangles area.
234 ///
235 /// The algorithm will attempt to ignore any triangle with an area below this limit. This can also prevent an
236 /// exhaustion of additionally available vertices (see [Self::with_max_additional_vertices]).
237 ///
238 /// Note that there is no guarantee that no face below this area bound will be kept intact - in some cases, a split
239 /// will still be required to restore the triangulation's Delaunay property. Also, this value does not specify a lower
240 /// bound for the smallest possible triangle in the triangulation.
241 ///
242 /// Should be set to something lower than [Self::with_max_allowed_area]. If this method is not called, no lower
243 /// bound check will be performed.
244 pub fn with_min_required_area(mut self, min_area: S) -> Self {
245 self.min_area = Some(min_area);
246 self
247 }
248
249 /// Specifies an upper bound for triangle areas in the triangulation.
250 ///
251 /// By default, the refinement tries to be conservative in how many vertices it adds. This will lead to an uneven
252 /// triangle size distribution - areas with larger input features will contain fewer, larger triangles whereas
253 /// regions with small features will contain more densely packed triangles.
254 /// By specifying an upper area bound for triangles, the resulting triangle sizes can be made more similar
255 /// as any large triangle above the bound will be split into smaller parts.
256 ///
257 /// # Examples of different maximum area values
258 #[doc = concat!(include_str!("../../images/refinement_maximum_area_no_limit.svg"))]
259 #[doc = concat!(include_str!("../../images/refinement_maximum_area_200.svg"))]
260 #[doc = concat!(include_str!("../../images/refinement_maximum_area_100.svg"))]
261 ///
262 /// Should be set to something larger than [Self::with_min_required_area]. If this method is not called, no upper area
263 /// bound check will be performed.
264 pub fn with_max_allowed_area(mut self, max_area: S) -> Self {
265 self.max_area = Some(max_area);
266 self
267 }
268
269 /// Specifies how many additional vertices may be inserted during Delaunay refinement.
270 ///
271 /// Refinement may, in some cases, fail to terminate if the angle limit is set too high
272 /// (see [with_angle_limit](Self::with_angle_limit)). Simply stopping the refinement after a certain number of vertices
273 /// has been inserted is an easy way to enforce termination. However, the resulting mesh may exhibit very poor quality
274 /// in this case - some areas may have become overly refined, others might be overlooked completely. Consider changing
275 /// the parameters (most notably the angle limit) if the refinement runs out of vertices.
276 ///
277 /// Use [RefinementResult::refinement_complete] to check if the number of additional vertices was sufficient.
278 pub fn with_max_additional_vertices(mut self, max_additional_vertices: usize) -> Self {
279 self.max_additional_vertices = Some(max_additional_vertices);
280 self
281 }
282
283 /// Prevents constraint edges from being split during refinement.
284 ///
285 /// By default, constraint edges may be split in order to restore the triangulation's Delaunay property.
286 /// The resulting two new edges will *become new constraint edges*, hence the original shape outlined by
287 /// constraint edges remains the same - no "gaps" or deviations are introduced.
288 ///
289 /// Enabling this option will, in general, reduce the quality of the resulting mesh - it is not necessarily
290 /// Delaunay anymore and faces adjacent to long constraint edges may violate the configured [AngleLimit].
291 pub fn keep_constraint_edges(mut self) -> Self {
292 self.keep_constraint_edges = true;
293 self
294 }
295
296 /// Allows to exclude outer faces from the refinement process.
297 ///
298 /// This is useful if the constraint edges form a *closed shape* with a clearly defined inner and outer part.
299 /// Spade will determine inner and outer faces by identifying which faces can be reached from the outer face
300 /// without "crossing" a constraint edge, similar to a flood fill algorithm.
301 ///
302 /// Any holes in the triangulation will also be excluded. More specifically, any point with an odd winding number
303 /// is considered to be inner (see e.g. [Wikipedia](https://en.wikipedia.org/wiki/Point_in_polygon#Winding_number_algorithm)).
304 ///
305 /// Note that excluded faces may still be subdivided if a neighboring edge needs to be split. However, they will never be the
306 /// *cause* for a subdivision - their angle and area is ignored.
307 ///
308 /// The resulting outer faces of the triangulation are returned by the call to [refine](ConstrainedDelaunayTriangulation::refine),
309 /// see [RefinementResult::excluded_faces].
310 ///
311 /// # Example
312 /// <table>
313 /// <tr><th>Unrefined</th><th>Refined</th></tr>
314 /// <tr><td>
315 #[doc = concat!(include_str!("../../images/exclude_outer_faces_unrefined.svg"), "</td><td>",include_str!("../../images/exclude_outer_faces_refined.svg"), " </td></tr></table>")]
316 ///
317 /// *A refinement operation configured to exclude outer faces. All colored faces are considered outer faces and are
318 /// ignored during refinement. Note that the inner part of the "A" shape forms a hole and is also excluded.*
319 pub fn exclude_outer_faces(mut self, exclude: bool) -> Self {
320 self.exclude_outer_faces = exclude;
321 self
322 }
323
324 fn get_refinement_hint<V, DE, UE, F>(
325 &self,
326 face: FaceHandle<InnerTag, V, DE, UE, F>,
327 ) -> RefinementHint
328 where
329 V: HasPosition<Scalar = S>,
330 {
331 if let Some(max_area) = self.max_area {
332 if face.area() > max_area {
333 return RefinementHint::MustRefine;
334 }
335 }
336
337 if let Some(min_area) = self.min_area {
338 if face.area() < min_area {
339 return RefinementHint::Ignore;
340 }
341 }
342
343 let (_, length2) = face.shortest_edge();
344 let (_, radius2) = face.circumcircle();
345
346 let ratio2 = radius2 / length2;
347 let angle_limit = self.angle_limit.radius_to_shortest_edge_limit;
348 if ratio2.into() > angle_limit * angle_limit {
349 RefinementHint::ShouldRefine
350 } else {
351 RefinementHint::Ignore
352 }
353 }
354}
355
356impl<V, DE, UE, F, L> ConstrainedDelaunayTriangulation<V, DE, UE, F, L>
357where
358 V: HasPosition + From<Point2<<V as HasPosition>::Scalar>>,
359 DE: Default,
360 UE: Default,
361 F: Default,
362 L: HintGenerator<<V as HasPosition>::Scalar>,
363 <V as HasPosition>::Scalar: Float,
364{
365 /// Refines a triangulation by inserting additional points to improve the quality of its mesh.
366 ///
367 /// *Mesh quality*, when applied to constrained delaunay triangulations (CDT), usually refers to how skewed its
368 /// triangles are. A skewed triangle is a triangle with very large or very small (acute) inner angles.
369 /// Some applications (e.g. interpolation and finite element methods) perform poorly in the presence of skewed triangles.
370 ///
371 /// Refining by inserting additional points (called "steiner points") may increase the minimum angle. The given
372 /// [RefinementParameters] should be used to modify the refinement behavior.
373 ///
374 /// # General usage
375 ///
376 /// The vertex type must implement `From<Point2<...>>` - otherwise, Spade cannot construct new steiner points at a
377 /// certain location. The refinement itself happens *in place* and will result in a valid CDT.
378 ///
379 /// ```
380 /// use spade::{ConstrainedDelaunayTriangulation, RefinementParameters, Point2, InsertionError, Triangulation};
381 ///
382 /// fn get_refined_triangulation(vertices: Vec<Point2<f64>>) ->
383 /// Result<ConstrainedDelaunayTriangulation<Point2<f64>>, InsertionError>
384 /// {
385 /// let mut cdt = ConstrainedDelaunayTriangulation::bulk_load(vertices)?;
386 /// let result = cdt.refine(RefinementParameters::default());
387 /// if !result.refinement_complete {
388 /// panic!("Refinement failed - I should consider using different parameters.")
389 /// }
390 ///
391 /// return Ok(cdt)
392 /// }
393 /// ```
394 ///
395 /// # Example image
396 ///
397 /// <table>
398 /// <tr><th>Unrefined</th><th>Refined</th></tr>
399 /// <tr><td>
400 #[doc = concat!(include_str!("../../images/unrefined.svg"), "</td><td>",include_str!("../../images/refined.svg"), " </td></tr></table>")]
401 ///
402 /// *A refinement example. The CDT on the left has some acute angles and skewed triangles.
403 /// The refined CDT on the right contains several additional points that prevents such triangles from appearing while keeping
404 /// all input vertices and constraint edges.*
405 ///
406 /// # Quality guarantees
407 ///
408 /// Refinement will ensure that the resulting triangulation fulfills a few properties:
409 /// - The triangulation's minimum angle will be larger than the angle specified by
410 /// [with_angle_limit](RefinementParameters::with_angle_limit).<br>
411 /// *Exception*: Acute input angles (small angles between initial constraint edges) cannot be maximized as the constraint edges
412 /// must be kept intact. The algorithm will, for the most part, leave those places unchanged.<br>
413 /// *Exception*: The refinement will often not be able to increase the minimal angle much above 30 degrees as every newly
414 /// inserted steiner point may create additional skewed triangles.
415 /// - The refinement will fullfil the *Delaunay Property*: Every triangle's circumcenter will not contain any other vertex.<br>
416 /// *Exception*: Refining with [keep_constraint_edges](RefinementParameters::keep_constraint_edges) cannot restore
417 /// the Delaunay property if doing so would require splitting a constraint edge.<br>
418 /// *Exception*: Refining with [exclude_outer_faces](RefinementParameters::exclude_outer_faces) will not
419 /// restore the Delaunay property of any outer face.
420 /// - Spade allows to specify a [maximum allowed triangle area](RefinementParameters::with_max_allowed_area).
421 /// The algorithm will attempt to subdivide any triangle with an area larger than this, independent of its smallest angle.
422 /// - Spade allows to specify a [minimum required triangle area](RefinementParameters::with_min_required_area).
423 /// The refinement will attempt to ignore any triangle with an area smaller than this parameter. This can prevent the
424 /// refinement algorithm from over-refining in some cases.
425 ///
426 /// # General limitations
427 ///
428 /// The algorithm may fail to terminate in some cases for a minimum angle limit larger than 30 degrees. Such a limit can
429 /// result in an endless loop: Every additionally inserted point creates more triangles that need to be refined.
430 ///
431 /// To prevent this, spade limits the number of additionally inserted steiner points
432 /// (see [RefinementParameters::with_max_additional_vertices]). However, this may leave the refinement in an incomplete state -
433 /// some areas of the input mesh may not have been triangulated at all, some will be overly refined.
434 /// Use [RefinementResult::refinement_complete] to identify if a refinement operation has succeeded without running out of
435 /// vertices.
436 ///
437 /// For mitigation, consider either lowering the minimum angle limit
438 /// (see [RefinementParameters::with_angle_limit]) or introduce a
439 /// [minimum required area](RefinementParameters::with_min_required_area).
440 ///
441 /// Meshes with very small input angles (angles between two constraint edges) may lead to poorly refined results.
442 /// Please consider providing a bug report if you encounter an input mesh which you think isn't refined well.
443 ///
444 /// # Stability guarantees
445 ///
446 /// While changing the interface of this method is considered to be a breaking change, changes to the specific
447 /// refinement process (e.g. which faces are split in which order) are not. Any patch release may change how
448 /// the same input mesh is being refined.
449 ///
450 /// # References
451 ///
452 /// This is an adaption of the classical refinement algorithms introduced by Jim Ruppert and Paul Chew.
453 ///
454 /// For a good introduction to the topic, refer to the slides from a short course at the thirteenth and fourteenth
455 /// International Meshing Roundtables (2005) by Jonathan Richard Shewchuk:
456 /// <https://people.eecs.berkeley.edu/~jrs/papers/imrtalk.pdf>
457 ///
458 /// Wikipedia: <https://en.wikipedia.org/wiki/Delaunay_refinement>
459 ///
460 ///
461 #[doc(alias = "Refinement")]
462 #[doc(alias = "Delaunay Refinement")]
463 pub fn refine(&mut self, parameters: RefinementParameters<V::Scalar>) -> RefinementResult {
464 use PositionInTriangulation::*;
465
466 let mut excluded_faces = if parameters.exclude_outer_faces {
467 calculate_outer_faces(self)
468 } else {
469 HashSet::new()
470 };
471
472 let mut legalize_edges_buffer = Vec::with_capacity(20);
473 let mut forcibly_split_segments_buffer = Vec::with_capacity(5);
474
475 // Maps each steiner point on an input edge onto the two vertices of that
476 // input edge. This helps in identifying when two steiner points share a common
477 // input angle
478 let mut constraint_edge_map = HashMap::new();
479
480 // Stores all edges that should be checked for encroachment
481 let mut encroached_segment_candidates =
482 VecDeque::with_capacity(self.num_constraints() + self.convex_hull_size());
483
484 encroached_segment_candidates.extend(
485 self.undirected_edges()
486 .filter(|edge| {
487 if parameters.keep_constraint_edges {
488 edge.is_part_of_convex_hull()
489 } else {
490 Self::is_fixed_edge(*edge)
491 }
492 })
493 .map(|edge| edge.fix()),
494 );
495
496 // Stores all faces that should be checked for their area and angles ("skinniness").
497 let mut skinny_triangle_candidates: VecDeque<_> = self.fixed_inner_faces().collect();
498
499 let num_initial_vertices: usize = self.num_vertices();
500 let num_additional_vertices = parameters
501 .max_additional_vertices
502 .unwrap_or(num_initial_vertices * 10);
503 let max_allowed_vertices =
504 usize::saturating_add(num_initial_vertices, num_additional_vertices);
505
506 let mut refinement_complete = true;
507
508 // Main loop of the algorithm
509 //
510 // Some terminology:
511 // - "Skinny triangle" refers to any triangle that has a minimum inner angle less than the allowed limit specified
512 // by the refinement parameters. The algorithm will attempt to insert steiner points to increase their minimal
513 // angle.
514 // - An edge is *encroached* by a point if that point lies in the diametral circle of the edge (the smallest circle
515 // fully containing the edge)
516 // - a "fixed" edge is a constraint edge or an edge of the convex hull. These are special as they may not be
517 // flipped - the input geometry must remain the same.
518 // - "input angle" is any angle between two fixed edges. Small input angles cannot be refined away as
519 // the input geometry must be kept intact.
520 // - "excluded faces" may exist if the triangulation's outer faces should not be refined. They are excluded from
521 // the third step in the main loop (see below). We don't simply delete these faces to keep the triangulation's
522 // convexity.
523 //
524 // Every iteration performs up to three checks:
525 // - First, check if any edges that must be split exists (`forcibly_split_segments_buffer`).
526 // - Second, check if any segment is encroached. If found, resolve the offending encroachment.
527 // Checking segments first makes sure that the algorithm
528 // restores the Delaunay property as quickly as possible.
529 // - Third, search for skinny triangles. Attempt to insert a new vertex at the triangle's circumcenter. If inserting
530 // such a vertex would encroach any fixed edge, add the encroached edge to the forcibly split segments buffer
531 // and revisit the face later.
532 //
533 // See method `resolve_encroachment` for more details on how step 1 and 2 manage to split edges in order to resolve
534 // an encroachment.
535 'main_loop: loop {
536 if self.num_vertices() >= max_allowed_vertices {
537 refinement_complete = false;
538 break;
539 }
540
541 // Step 1: Check for forcibly split segments.
542 if let Some(forcibly_split_segment) = forcibly_split_segments_buffer.pop() {
543 self.resolve_encroachment(
544 &mut encroached_segment_candidates,
545 &mut skinny_triangle_candidates,
546 &mut constraint_edge_map,
547 forcibly_split_segment,
548 &mut excluded_faces,
549 );
550 continue;
551 }
552
553 // Step 2: Check for encroached segments.
554 if let Some(segment_candidate) = encroached_segment_candidates.pop_front() {
555 // Check both adjacent faces of any candidate for encroachment.
556 for edge in segment_candidate.directed_edges() {
557 let edge = self.directed_edge(edge);
558
559 let is_excluded = edge
560 .face()
561 .as_inner()
562 .map(|face| excluded_faces.contains(&face.fix()))
563 .unwrap_or(true);
564
565 if is_excluded {
566 continue;
567 }
568
569 if let Some(opposite_position) = edge.opposite_position() {
570 if is_encroaching_edge(
571 edge.from().position(),
572 edge.to().position(),
573 opposite_position,
574 ) {
575 // The edge is encroaching
576 self.resolve_encroachment(
577 &mut encroached_segment_candidates,
578 &mut skinny_triangle_candidates,
579 &mut constraint_edge_map,
580 segment_candidate,
581 &mut excluded_faces,
582 );
583 }
584 }
585 }
586
587 continue;
588 }
589
590 // Step 3: Take the next skinny triangle candidate
591 if let Some(face) = skinny_triangle_candidates.pop_front() {
592 if excluded_faces.contains(&face) {
593 continue;
594 }
595
596 let face = self.face(face);
597
598 let (shortest_edge, _) = face.shortest_edge();
599
600 let refinement_hint = parameters.get_refinement_hint(face);
601
602 if refinement_hint == RefinementHint::Ignore {
603 // Triangle is fine as is and can be skipped
604 continue;
605 }
606
607 if refinement_hint == RefinementHint::ShouldRefine
608 && !Self::is_fixed_edge(shortest_edge.as_undirected())
609 {
610 // Check if the shortest edge ends in two input edges that span a small
611 // input angle.
612 //
613 // Such an input angle cannot be maximized as that would require flipping at least one of its edges.
614 //
615 // See Miller, Gary; Pav, Steven; Walkington, Noel (2005). "When and why Delaunay refinement algorithms work".
616 // for more details on this idea.
617 let original_from = constraint_edge_map
618 .get(&shortest_edge.from().fix())
619 .copied();
620 let original_to = constraint_edge_map.get(&shortest_edge.to().fix()).copied();
621
622 for from_input_vertex in original_from.iter().flatten() {
623 for to_input_vertex in original_to.iter().flatten() {
624 if from_input_vertex == to_input_vertex {
625 // The two edges are input segments and join a common segment.
626 // Don't attempt to subdivide it any further, this is as good as we can get.
627 continue 'main_loop;
628 }
629 }
630 }
631 }
632
633 // Continue to resolve the skinny face
634 let circumcenter = face.circumcenter();
635
636 let locate_hint = face.vertices()[0].fix();
637
638 assert!(forcibly_split_segments_buffer.is_empty());
639 legalize_edges_buffer.clear();
640
641 // "Simulate" inserting the circumcenter by locating the insertion site and identifying which edges
642 // would need to be flipped (legalized) by the insertion. If any of these edges is fixed, an
643 // encroachment with this edge is found.
644 //
645 // First step: fill `legalize_edges_buffer` with the initial set of edges that would need to be legalized
646 // if the triangle's circumcenter would be inserted.
647 match self.locate_with_hint(circumcenter, locate_hint) {
648 OnEdge(edge) => {
649 let edge = self.directed_edge(edge);
650 if parameters.keep_constraint_edges && edge.is_constraint_edge() {
651 continue;
652 }
653
654 if edge.is_constraint_edge() {
655 // Splitting constraint edges may require updating the `excluded_faces` set.
656 // This is a little cumbersome, we'll re-use the existing implementation of edge
657 // splitting (see function resolve_encroachment).
658 forcibly_split_segments_buffer.push(edge.fix().as_undirected());
659 continue;
660 }
661
662 for edge in [edge, edge.rev()] {
663 if !edge.is_outer_edge() {
664 legalize_edges_buffer.extend([edge.next().fix(), edge.prev().fix()])
665 }
666 }
667 }
668 OnFace(face_under_circumcenter) => {
669 if excluded_faces.contains(&face_under_circumcenter) {
670 continue;
671 }
672 legalize_edges_buffer.extend(
673 self.face(face_under_circumcenter)
674 .adjacent_edges()
675 .map(|edge| edge.fix()),
676 );
677 }
678 OutsideOfConvexHull(_) => continue,
679 OnVertex(_) => continue,
680 NoTriangulation => unreachable!(),
681 };
682
683 let mut is_encroaching = false;
684
685 // Next step: Perform the regular legalization procedure by "simulating" edge flips
686 while let Some(edge) = legalize_edges_buffer.pop() {
687 let edge = self.directed_edge(edge);
688 let [from, to] = edge.as_undirected().positions();
689 if Self::is_fixed_edge(edge.as_undirected()) {
690 if is_encroaching_edge(from, to, circumcenter) {
691 // We found an encroaching edge! Makes sure that we won't attempt to
692 // insert the circumcenter.
693 is_encroaching = true;
694
695 if !parameters.keep_constraint_edges || !edge.is_constraint_edge() {
696 // New circumcenter would encroach a constraint edge. Don't insert the circumcenter
697 // but force splitting the segment
698 forcibly_split_segments_buffer.push(edge.as_undirected().fix());
699 }
700 }
701 continue; // Don't actually flip the edge as it's fixed - continue with any other edge instead.
702 }
703
704 // edge is not a fixed edge. Check if it needs to be legalized.
705 // We've already checked that this edge is not part of the convex hull - unwrap is safe
706 let opposite = edge.rev().opposite_position().unwrap();
707 let from = edge.from().position();
708 let to = edge.to().position();
709 let should_flip =
710 math::contained_in_circumference(opposite, to, from, circumcenter);
711
712 if should_flip {
713 let e1 = edge.rev().next().fix();
714 let e2 = edge.rev().prev().fix();
715
716 legalize_edges_buffer.push(e1);
717 legalize_edges_buffer.push(e2);
718 }
719 }
720
721 if !is_encroaching {
722 // The circumcenter doesn't encroach any segment. Continue really inserting it.
723 let new_vertex = self
724 .insert_with_hint(circumcenter.into(), locate_hint)
725 .expect("Failed to insert circumcenter, likely due to loss of precision. Consider refining with fewer additional vertices.");
726
727 // Add all new and changed faces to the skinny candidate list
728 skinny_triangle_candidates.extend(
729 self.vertex(new_vertex)
730 .out_edges()
731 .flat_map(|edge| edge.face().fix().as_inner()),
732 );
733 } else if !forcibly_split_segments_buffer.is_empty() {
734 // Revisit this face later. Since the encroached edge will have been split in the next iteration,
735 // inserting the circumcenter might succeed this time around.
736 skinny_triangle_candidates.push_back(face.fix());
737 }
738 } else {
739 // Done! This branch is reached if no skinny triangle could be identified anymore.
740 break;
741 }
742 }
743
744 RefinementResult {
745 excluded_faces: excluded_faces.iter().copied().collect(),
746 refinement_complete,
747 }
748 }
749
750 fn is_fixed_edge(edge: UndirectedEdgeHandle<V, DE, CdtEdge<UE>, F>) -> bool {
751 edge.is_constraint_edge() || edge.is_part_of_convex_hull()
752 }
753
754 fn resolve_encroachment(
755 &mut self,
756 encroached_segments_buffer: &mut VecDeque<FixedUndirectedEdgeHandle>,
757 encroached_faces_buffer: &mut VecDeque<FixedFaceHandle<InnerTag>>,
758 constraint_edge_map: &mut HashMap<FixedVertexHandle, [FixedVertexHandle; 2]>,
759 encroached_edge: FixedUndirectedEdgeHandle,
760 excluded_faces: &mut HashSet<FixedFaceHandle<InnerTag>>,
761 ) {
762 // Resolves an encroachment by splitting the encroached edge. Since this reduces the diametral circle, this will
763 // eventually get rid of the encroachment completely.
764 //
765 // There are a few details that make this more complicated:
766 //
767 // # Runaway encroachment
768 // Any input angle less than 45 degrees may lead to a "runaway encroachment". In such a situation, any of the
769 // angle's edges will encroach *the other* edge. This goes on forever, subdividing the edges infinitely.
770 //
771 // To work around this, spade will split edges at their center position only *once*.
772 // Any subsegment will not be split at its center position but *rounded towards the nearest power of 2*.
773 // With this behavior, neighboring edges will eventually share vertices equally far away from the offending angle's
774 // apex vertex. Points and edges in such a configuration cannot encroach each other. Refer to the original paper
775 // by Ruppert for more details.
776 //
777 // # Keeping track of which edges and faces have changed
778 // Since `resolve_encroachment` will create new edges and faces, we need to add those to the existing buffers as
779 // appropriate. This becomes a little convoluted when supporting all different refinement modes, e.g. excluded faces.
780
781 let segment = self.directed_edge(encroached_edge.as_directed());
782
783 let [v0, v1] = segment.vertices();
784
785 let half = Into::<V::Scalar>::into(0.5f32);
786
787 let v0_constraint_vertex = constraint_edge_map.get(&v0.fix()).copied();
788 let v1_constraint_vertex = constraint_edge_map.get(&v1.fix()).copied();
789
790 let (weight0, weight1) = match (v0_constraint_vertex, v1_constraint_vertex) {
791 (None, None) => {
792 // Split the segment exactly in the middle if it has not been split before.
793 (half, half)
794 }
795 _ => {
796 // One point is a steiner point, another point isn't. This will trigger rounding the distance to
797 // the nearest power of two to prevent runaway encroachment.
798
799 let half_length = segment.length_2().sqrt() * half;
800
801 let nearest_power_of_two = nearest_power_of_two(half_length);
802 let other_vertex_weight = half * nearest_power_of_two / half_length;
803 let original_vertex_weight = Into::<V::Scalar>::into(1.0) - other_vertex_weight;
804
805 if v0_constraint_vertex.is_none() {
806 // Orient the weight towards to original vertex. This makes sure that any edge participating in
807 // a runaway encroachment will end up with the same distance to the non-steiner (original) point.
808 (original_vertex_weight, other_vertex_weight)
809 } else {
810 (other_vertex_weight, original_vertex_weight)
811 }
812 }
813 };
814
815 let final_position = v0.position().mul(weight0).add(v1.position().mul(weight1));
816
817 if !validate_constructed_vertex(final_position, segment) {
818 return;
819 }
820
821 let [is_left_side_excluded, is_right_side_excluded] =
822 [segment.face(), segment.rev().face()].map(|face| {
823 face.as_inner()
824 .is_some_and(|face| excluded_faces.contains(&face.fix()))
825 });
826
827 let is_constraint_edge = segment.is_constraint_edge();
828
829 // Perform the actual split!
830 let segment = segment.fix();
831 let (v0, v1) = (v0.fix(), v1.fix());
832
833 let new_vertex = append_unconnected_vertex(self.s_mut(), final_position.into());
834 let [e1, e2] = self.insert_on_edge(segment, new_vertex);
835 self.hint_generator_mut()
836 .notify_vertex_inserted(new_vertex, final_position);
837
838 let original_vertices = v0_constraint_vertex
839 .or(v1_constraint_vertex)
840 .unwrap_or([v0, v1]);
841 constraint_edge_map.insert(new_vertex, original_vertices);
842
843 if is_constraint_edge {
844 // Make sure to update the constraint edges count as required.
845 self.handle_legal_edge_split([e1, e2]);
846 }
847
848 let (h1, h2) = (self.directed_edge(e1), self.directed_edge(e2));
849
850 if is_left_side_excluded {
851 // Any newly added face on the left becomes an excluded face
852 excluded_faces.insert(h1.face().fix().as_inner().unwrap());
853 excluded_faces.insert(h2.face().fix().as_inner().unwrap());
854 }
855
856 if is_right_side_excluded {
857 // Any newly added face on the right becomes an excluded face
858 excluded_faces.insert(h1.rev().face().fix().as_inner().unwrap());
859 excluded_faces.insert(h2.rev().face().fix().as_inner().unwrap());
860 }
861
862 self.legalize_vertex(new_vertex);
863
864 // Any of the faces that share an outgoing edge may be changed by the vertex insertion. Make sure that all of them
865 // will be revisited.
866 encroached_faces_buffer.extend(
867 self.vertex(new_vertex)
868 .out_edges()
869 .flat_map(|edge| edge.face().fix().as_inner()),
870 );
871
872 // Neighboring edges may have become encroached. Check if they need to be added to the encroached segment buffer.
873 encroached_segments_buffer.extend(
874 self.vertex(new_vertex)
875 .out_edges()
876 .filter(|edge| !edge.is_outer_edge())
877 .map(|edge| edge.next().as_undirected())
878 .filter(|edge| Self::is_fixed_edge(*edge))
879 .map(|edge| edge.fix()),
880 );
881
882 // Update encroachment candidates - any of the resulting edges may still be in an encroaching state.
883 encroached_segments_buffer.push_back(e1.as_undirected());
884 encroached_segments_buffer.push_back(e2.as_undirected());
885 }
886}
887
888/// Check if final_position would violate an ordering constraint. This is needed since final_position is constructed
889/// with imprecise calculations and may not even be representable in the underlying floating point type. In rare cases,
890/// this means that the newly formed triangles would not be ordered ccw.
891/// We'll simply skip these refinements steps as it should only happen for very bad input geometries.
892///
893/// Before (v0 = segment.from(), v1 = segment.to()):
894/// v2
895/// / \
896/// v0 --> v1
897/// \ /
898/// v3
899///
900/// After (before legalizing) - return if any face would be ordered cw
901/// v2
902/// / | \
903/// v0 -v-> v1
904/// \ | /
905/// v3
906fn validate_constructed_vertex<V, DE, UE, F>(
907 final_position: Point2<V::Scalar>,
908 segment: DirectedEdgeHandle<V, DE, UE, F>,
909) -> bool
910where
911 V: HasPosition,
912{
913 use math::is_ordered_ccw;
914 let [v0, v1] = segment.positions();
915
916 if math::validate_vertex(&final_position).is_err() {
917 return false;
918 }
919
920 if let Some(v2) = segment.opposite_position() {
921 if is_ordered_ccw(v0, v2, final_position) || is_ordered_ccw(v2, v1, final_position) {
922 return false;
923 }
924 }
925
926 if let Some(v3) = segment.rev().opposite_position() {
927 if is_ordered_ccw(v3, v0, final_position) || is_ordered_ccw(v1, v3, final_position) {
928 return false;
929 }
930 }
931
932 true
933}
934
935fn is_encroaching_edge<S: SpadeNum + Float>(
936 edge_from: Point2<S>,
937 edge_to: Point2<S>,
938 query_point: Point2<S>,
939) -> bool {
940 let edge_center = edge_from.add(edge_to).mul(0.5f32.into());
941 let radius_2 = edge_from.distance_2(edge_to) * 0.25.into();
942
943 query_point.distance_2(edge_center) < radius_2
944}
945
946fn nearest_power_of_two<S: Float + SpadeNum>(input: S) -> S {
947 input.log2().round().exp2()
948}
949
950fn calculate_outer_faces<V: HasPosition, DE: Default, UE: Default, F: Default, L>(
951 triangulation: &ConstrainedDelaunayTriangulation<V, DE, UE, F, L>,
952) -> HashSet<FixedFaceHandle<InnerTag>>
953where
954 L: HintGenerator<<V as HasPosition>::Scalar>,
955{
956 if triangulation.all_vertices_on_line() {
957 return HashSet::new();
958 }
959
960 // Determine excluded faces by "peeling of" outer layers and adding them to an outer layer set.
961 // This needs to be done repeatedly to also get inner "holes" within the triangulation
962 let mut inner_faces = HashSet::new();
963 let mut outer_faces = HashSet::new();
964
965 let mut current_todo_list: Vec<_> =
966 triangulation.convex_hull().map(|edge| edge.rev()).collect();
967 let mut next_todo_list = Vec::new();
968
969 let mut return_outer_faces = true;
970
971 loop {
972 // Every iteration of the outer while loop will peel of the outmost layer and pre-populate the
973 // next, inner layer.
974 while let Some(next_edge) = current_todo_list.pop() {
975 let (list, face_set) = if next_edge.is_constraint_edge() {
976 // We're crossing a constraint edge - add that face to the *next* todo list
977 (&mut next_todo_list, &mut inner_faces)
978 } else {
979 (&mut current_todo_list, &mut outer_faces)
980 };
981
982 if let Some(inner) = next_edge.face().as_inner() {
983 if face_set.insert(inner.fix()) {
984 list.push(next_edge.prev().rev());
985 list.push(next_edge.next().rev());
986 }
987 }
988 }
989
990 if next_todo_list.is_empty() {
991 break;
992 }
993 core::mem::swap(&mut inner_faces, &mut outer_faces);
994 core::mem::swap(&mut next_todo_list, &mut current_todo_list);
995
996 return_outer_faces = !return_outer_faces;
997 }
998
999 if return_outer_faces {
1000 outer_faces
1001 } else {
1002 inner_faces
1003 }
1004}
1005
1006#[cfg(test)]
1007mod test {
1008 use super::HashSet;
1009
1010 use crate::{
1011 test_utilities::{random_points_with_seed, SEED},
1012 AngleLimit, ConstrainedDelaunayTriangulation, InsertionError, Point2, RefinementParameters,
1013 Triangulation as _,
1014 };
1015
1016 pub type Cdt = ConstrainedDelaunayTriangulation<Point2<f64>>;
1017
1018 #[test]
1019 fn test_zero_angle_limit_dbg() {
1020 let limit = AngleLimit::from_deg(0.0);
1021 let debug_string = alloc::format!("{:?}", limit);
1022 assert_eq!(debug_string, "AngleLimit { angle limit (deg): 0.0 }");
1023 }
1024
1025 #[test]
1026 fn test_zero_angle_limit() -> Result<(), InsertionError> {
1027 let limit = AngleLimit::from_deg(0.0);
1028
1029 assert_eq!(limit.radius_to_shortest_edge_limit(), f64::INFINITY);
1030
1031 let mut vertices = random_points_with_seed(20, SEED);
1032
1033 // Insert an artificial outer boundary that will prevent the convex hull from being encroached.
1034 // This should prevent any refinement.
1035 vertices.push(Point2::new(100.0, 100.0));
1036 vertices.push(Point2::new(100.0, 0.0));
1037 vertices.push(Point2::new(100.0, -100.0));
1038 vertices.push(Point2::new(0.0, -100.0));
1039 vertices.push(Point2::new(-100.0, -100.0));
1040 vertices.push(Point2::new(-100.0, 0.0));
1041 vertices.push(Point2::new(-100.0, 100.0));
1042 vertices.push(Point2::new(0.0, 100.0));
1043
1044 let mut cdt = Cdt::bulk_load(vertices)?;
1045
1046 let initial_num_vertices = cdt.num_vertices();
1047 cdt.refine(RefinementParameters::new().with_angle_limit(limit));
1048
1049 assert_eq!(initial_num_vertices, cdt.num_vertices());
1050
1051 cdt.refine(RefinementParameters::new());
1052 assert!(initial_num_vertices < cdt.num_vertices());
1053
1054 Ok(())
1055 }
1056
1057 #[test]
1058 fn test_nearest_power_of_two() {
1059 use super::nearest_power_of_two;
1060
1061 for i in 1..400u32 {
1062 let log = (i as f64).log2() as u32;
1063 let nearest = nearest_power_of_two(i as f64);
1064
1065 assert!((1 << log) as f64 == nearest || (1 << (log + 1)) as f64 == nearest);
1066 }
1067
1068 assert_eq!(0.25, nearest_power_of_two(0.25));
1069 assert_eq!(0.25, nearest_power_of_two(0.27));
1070 assert_eq!(0.5, nearest_power_of_two(0.5));
1071 assert_eq!(1.0, nearest_power_of_two(0.75));
1072 assert_eq!(2.0, nearest_power_of_two(1.5));
1073 assert_eq!(2.0, nearest_power_of_two(2.5));
1074 assert_eq!(4.0, nearest_power_of_two(3.231));
1075 assert_eq!(4.0, nearest_power_of_two(4.0));
1076 }
1077
1078 #[test]
1079 fn test_small_refinement() -> Result<(), InsertionError> {
1080 let vertices = random_points_with_seed(20, SEED);
1081 let mut cdt = Cdt::bulk_load(vertices)?;
1082
1083 let mut peekable = cdt.fixed_vertices().peekable();
1084 while let (Some(p0), Some(p1)) = (peekable.next(), peekable.peek()) {
1085 if !cdt.can_add_constraint(p0, *p1) {
1086 cdt.add_constraint(p0, *p1);
1087 }
1088 }
1089
1090 cdt.refine(Default::default());
1091
1092 cdt.cdt_sanity_check_with_params(false);
1093
1094 Ok(())
1095 }
1096
1097 #[test]
1098 fn test_sharp_angle_refinement() -> Result<(), InsertionError> {
1099 let mut cdt = Cdt::new();
1100
1101 // This sharp angle should only be subdivided once rather than infinitely often.
1102 cdt.add_constraint_edge(Point2::new(-40.0, 80.0), Point2::new(0.0, 0.0))?;
1103 cdt.add_constraint_edge(Point2::new(0.0, 0.0), Point2::new(4.0, 90.0))?;
1104
1105 let refinement_parameters = RefinementParameters::new()
1106 .with_max_additional_vertices(10)
1107 .with_angle_limit(AngleLimit::from_radius_to_shortest_edge_ratio(1.0));
1108
1109 let result = cdt.refine(refinement_parameters);
1110
1111 assert!(result.refinement_complete);
1112 cdt.cdt_sanity_check();
1113 Ok(())
1114 }
1115
1116 #[test]
1117 fn test_simple_exclude_outer_faces() -> Result<(), InsertionError> {
1118 let mut cdt = Cdt::new();
1119 let vertices = [
1120 Point2::new(-1.0, 1.0),
1121 Point2::new(0.0, 0.5),
1122 Point2::new(1.0, 1.0),
1123 Point2::new(1.0, -1.0),
1124 Point2::new(-1.0, -1.0),
1125 Point2::new(-1.0, 1.0),
1126 ];
1127
1128 for points in vertices.windows(2) {
1129 let p1 = points[0];
1130 let p2 = points[1];
1131 cdt.add_constraint_edge(p1, p2)?;
1132 }
1133
1134 let excluded_faces = super::calculate_outer_faces(&cdt);
1135
1136 let v2 = cdt.locate_vertex(Point2::new(1.0, 1.0)).unwrap().fix();
1137 let v0 = cdt.locate_vertex(Point2::new(-1.0, 1.0)).unwrap().fix();
1138
1139 let excluded_face = cdt
1140 .get_edge_from_neighbors(v2, v0)
1141 .and_then(|edge| edge.face().as_inner())
1142 .unwrap()
1143 .fix();
1144
1145 assert_eq!(
1146 excluded_faces,
1147 HashSet::from_iter(core::iter::once(excluded_face))
1148 );
1149
1150 Ok(())
1151 }
1152
1153 fn test_shape() -> [Point2<f64>; 6] {
1154 [
1155 Point2::new(-1.0, 1.0),
1156 Point2::new(0.0, 0.7),
1157 Point2::new(1.0, 1.0),
1158 Point2::new(2.0, 0.0),
1159 Point2::new(0.5, -1.0),
1160 Point2::new(-0.5, -2.0),
1161 ]
1162 }
1163
1164 #[test]
1165 fn test_exclude_outer_faces() -> Result<(), InsertionError> {
1166 let mut cdt = Cdt::new();
1167
1168 let scale_factors = [1.0, 2.0, 3.0, 4.0];
1169
1170 let mut current_excluded_faces = super::calculate_outer_faces(&cdt);
1171
1172 assert!(current_excluded_faces.is_empty());
1173
1174 for factor in scale_factors {
1175 cdt.add_constraint_edges(test_shape().iter().map(|p| p.mul(factor)), true)?;
1176
1177 let next_excluded_faces = super::calculate_outer_faces(&cdt);
1178
1179 assert!(current_excluded_faces.len() < next_excluded_faces.len());
1180
1181 current_excluded_faces = next_excluded_faces;
1182 assert!(current_excluded_faces.len() < cdt.num_inner_faces());
1183 }
1184
1185 Ok(())
1186 }
1187
1188 #[test]
1189 fn test_exclude_outer_faces_with_non_closed_mesh() -> Result<(), InsertionError> {
1190 let mut cdt = Cdt::new();
1191 cdt.add_constraint_edges(test_shape(), false)?;
1192
1193 let refinement_result = cdt.refine(
1194 RefinementParameters::new()
1195 .exclude_outer_faces(true)
1196 .with_angle_limit(AngleLimit::from_radius_to_shortest_edge_ratio(
1197 f64::INFINITY,
1198 )),
1199 );
1200
1201 assert_eq!(
1202 refinement_result.excluded_faces.len(),
1203 cdt.num_inner_faces()
1204 );
1205
1206 cdt.refine(RefinementParameters::new().exclude_outer_faces(true));
1207
1208 Ok(())
1209 }
1210
1211 #[test]
1212 fn test_failing_refinement() -> Result<(), InsertionError> {
1213 // f32 is important - only then, rounding errors will lead to violating the ccw property when
1214 // the refinement splits an edge. See issue #96
1215 let mut cdt = ConstrainedDelaunayTriangulation::<Point2<f32>>::new();
1216
1217 #[rustfmt::skip]
1218 let vert = [
1219 [Point2 { x: -50.023544f32, y: -25.29227 }, Point2 { x: 754.23883, y: -25.29227 }],
1220 [Point2 { x: 754.23883, y: 508.68994 }, Point2 { x: -50.023544, y: 508.68994 }],
1221 [Point2 { x: -44.20742, y: 316.5185 }, Point2 { x: -50.023544, y: 318.19534 }],
1222 [Point2 { x: 11.666269, y: 339.4947 }, Point2 { x: 15.110367, y: 335.44138 }],
1223 [Point2 { x: 335.06403, y: 122.91455 }, Point2 { x: 340.15436, y: 132.04283 }],
1224 [Point2 { x: 446.92468, y: -6.7025666 }, Point2 { x: 458.70944, y: 14.341333 }],
1225 [Point2 { x: 458.70944, y: 14.341333 }, Point2 { x: 471.58313, y: 7.1453195 }],
1226 [Point2 { x: 467.80966, y: 0.40460825 }, Point2 { x: 468.6454, y: -0.061800003 }],
1227 [Point2 { x: 464.55957, y: -7.3688636 }, Point2 { x: 465.48816, y: -7.890797 }],
1228 [Point2 { x: 465.48816, y: -7.890797 }, Point2 { x: 461.57117, y: -14.898027 }],
1229 [Point2 { x: 465.42877, y: 10.587858 }, Point2 { x: 453.93112, y: 17.01763 }],
1230 ];
1231
1232 for [start, end] in vert {
1233 cdt.add_constraint_edge(start, end)?;
1234 }
1235
1236 cdt.refine(Default::default());
1237 cdt.cdt_sanity_check();
1238
1239 Ok(())
1240 }
1241}