1use std::collections::HashSet;
7use std::error::Error;
8use std::fmt;
9
10use crate::predicates::{incircle, orient2d};
11
12const NONE: usize = usize::MAX;
14
15#[derive(Debug, Clone)]
17pub struct Cdt {
18 vertices: Vec<[f64; 2]>,
20 triangles: Vec<[usize; 3]>,
22 adjacency: Vec<[usize; 3]>,
24 constraints: HashSet<(usize, usize)>,
26 n_super: usize,
28 last_triangle: usize,
30}
31
32#[derive(Debug, Clone, PartialEq, Eq)]
34#[non_exhaustive]
35pub enum CdtError {
36 ConstraintRecoveryDidNotConverge {
38 edge: (usize, usize),
40 iterations: usize,
42 },
43}
44
45impl fmt::Display for CdtError {
46 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47 match self {
48 Self::ConstraintRecoveryDidNotConverge { edge, iterations } => write!(
49 f,
50 "constraint edge ({}, {}) recovery did not converge after {} iterations",
51 edge.0, edge.1, iterations
52 ),
53 }
54 }
55}
56
57impl Error for CdtError {}
58
59#[inline]
61fn edge_key(a: usize, b: usize) -> (usize, usize) {
62 if a < b {
63 (a, b)
64 } else {
65 (b, a)
66 }
67}
68
69impl Cdt {
70 pub fn new(bounds: (f64, f64, f64, f64)) -> Self {
74 let (min_x, min_y, max_x, max_y) = bounds;
75 let dx = max_x - min_x;
76 let dy = max_y - min_y;
77 let d = dx.max(dy).max(1e-10);
78 let cx = (min_x + max_x) * 0.5;
79 let cy = (min_y + max_y) * 0.5;
80
81 let margin = 10.0 * d;
83 let v0 = [cx - margin, cy - margin];
84 let v1 = [cx + margin, cy - margin];
85 let v2 = [cx, cy + margin];
86
87 Cdt {
88 vertices: vec![v0, v1, v2],
89 triangles: vec![[0, 1, 2]],
90 adjacency: vec![[NONE; 3]],
91 constraints: HashSet::new(),
92 n_super: 3,
93 last_triangle: 0,
94 }
95 }
96
97 pub fn insert(&mut self, x: f64, y: f64) -> usize {
101 let vi = self.vertices.len();
102 self.vertices.push([x, y]);
103
104 let ti = match self.locate(x, y) {
106 Some(t) => t,
107 None => {
108 self.nearest_triangle(x, y)
111 }
112 };
113
114 let bad = self.find_bad_triangles(vi, ti);
116
117 let boundary = self.find_boundary(&bad);
119
120 self.re_triangulate(vi, &bad, &boundary);
122
123 vi
124 }
125
126 pub fn add_constraint_edges(
131 &mut self,
132 points: &[[f64; 2]],
133 closed: bool,
134 ) -> Result<(), CdtError> {
135 if points.is_empty() {
136 return Ok(());
137 }
138
139 let indices: Vec<usize> = points
142 .iter()
143 .map(|p| self.find_or_insert_vertex(p[0], p[1]))
144 .collect();
145
146 let n = indices.len();
148 for i in 0..n {
149 let next = if i + 1 < n {
150 i + 1
151 } else if closed {
152 0
153 } else {
154 break;
155 };
156 let a = indices[i];
157 let b = indices[next];
158 if a == b {
159 continue;
160 }
161 let chain = self.constraint_chain(a, b);
162 for edge in chain.windows(2) {
163 let start = edge[0];
164 let end = edge[1];
165 if start != end {
166 self.enforce_constraint(start, end)?;
167 }
168 }
169 }
170
171 Ok(())
172 }
173
174 pub fn n_user_vertices(&self) -> usize {
176 self.vertices.len() - self.n_super
177 }
178
179 #[inline]
181 fn user_index(&self, vi: usize) -> usize {
182 debug_assert!(vi >= self.n_super);
183 vi - self.n_super
184 }
185
186 pub fn triangles(&self) -> Vec<[usize; 3]> {
189 let mut result = Vec::new();
190 for tri in &self.triangles {
191 if tri[0] < self.n_super || tri[1] < self.n_super || tri[2] < self.n_super {
193 continue;
194 }
195 result.push([
196 self.user_index(tri[0]),
197 self.user_index(tri[1]),
198 self.user_index(tri[2]),
199 ]);
200 }
201 result
202 }
203
204 pub fn user_vertices(&self) -> &[[f64; 2]] {
206 &self.vertices[self.n_super..]
207 }
208
209 fn locate(&self, x: f64, y: f64) -> Option<usize> {
213 if self.triangles.is_empty() {
214 return None;
215 }
216
217 let p = [x, y];
218 let mut ti = self
219 .last_triangle
220 .min(self.triangles.len().saturating_sub(1));
221
222 for _ in 0..self.triangles.len() {
223 let tri = self.triangles[ti];
224 let a = self.vertices[tri[0]];
225 let b = self.vertices[tri[1]];
226 let c = self.vertices[tri[2]];
227
228 let ab = orient2d(a, b, p);
229 let bc = orient2d(b, c, p);
230 let ca = orient2d(c, a, p);
231
232 if ab >= 0.0 && bc >= 0.0 && ca >= 0.0 {
233 return Some(ti);
234 }
235
236 let edge = if ab < bc && ab < ca {
237 0
238 } else if bc < ca {
239 1
240 } else {
241 2
242 };
243
244 let adj = self.adjacency[ti][edge];
245 if adj == NONE {
246 return self.locate_linear(x, y);
247 }
248 ti = adj;
249 }
250
251 self.locate_linear(x, y)
252 }
253
254 fn locate_linear(&self, x: f64, y: f64) -> Option<usize> {
256 let p = [x, y];
257 for (ti, tri) in self.triangles.iter().enumerate() {
258 let a = self.vertices[tri[0]];
259 let b = self.vertices[tri[1]];
260 let c = self.vertices[tri[2]];
261 if orient2d(a, b, p) >= 0.0 && orient2d(b, c, p) >= 0.0 && orient2d(c, a, p) >= 0.0 {
262 return Some(ti);
263 }
264 }
265 None
266 }
267
268 fn nearest_triangle(&self, x: f64, y: f64) -> usize {
270 let mut best = 0;
271 let mut best_dist = f64::INFINITY;
272 for (ti, tri) in self.triangles.iter().enumerate() {
273 let cx =
274 (self.vertices[tri[0]][0] + self.vertices[tri[1]][0] + self.vertices[tri[2]][0])
275 / 3.0;
276 let cy =
277 (self.vertices[tri[0]][1] + self.vertices[tri[1]][1] + self.vertices[tri[2]][1])
278 / 3.0;
279 let d = (cx - x) * (cx - x) + (cy - y) * (cy - y);
280 if d < best_dist {
281 best_dist = d;
282 best = ti;
283 }
284 }
285 best
286 }
287
288 fn find_or_insert_vertex(&mut self, x: f64, y: f64) -> usize {
289 if let Some((offset, _)) = self.vertices[self.n_super..]
290 .iter()
291 .enumerate()
292 .find(|(_, v)| v[0] == x && v[1] == y)
293 {
294 return self.n_super + offset;
295 }
296 self.insert(x, y)
297 }
298
299 fn find_bad_triangles(&self, vi: usize, start: usize) -> Vec<usize> {
301 let p = self.vertices[vi];
302 let mut bad = Vec::new();
303 let mut stack = vec![start];
304 let mut visited = vec![false; self.triangles.len()];
305
306 while let Some(ti) = stack.pop() {
307 if visited[ti] {
308 continue;
309 }
310 visited[ti] = true;
311 let tri = self.triangles[ti];
312 let a = self.vertices[tri[0]];
313 let b = self.vertices[tri[1]];
314 let c = self.vertices[tri[2]];
315
316 let o = orient2d(a, b, c);
317 let in_circle = if o > 0.0 {
318 incircle(a, b, c, p) > 0.0
319 } else if o < 0.0 {
320 incircle(b, a, c, p) > 0.0
321 } else {
322 true
323 };
324
325 if in_circle {
326 bad.push(ti);
327 for &adj in &self.adjacency[ti] {
328 if adj != NONE {
329 stack.push(adj);
330 }
331 }
332 }
333 }
334 bad
335 }
336
337 fn find_boundary(&self, bad: &[usize]) -> Vec<(usize, usize, usize)> {
339 let mut is_bad = vec![false; self.triangles.len()];
340 for &ti in bad {
341 is_bad[ti] = true;
342 }
343 let mut boundary = Vec::new();
344
345 for &ti in bad {
346 let tri = self.triangles[ti];
347 for i in 0..3 {
348 let adj = self.adjacency[ti][i];
349 let va = tri[(i + 1) % 3];
350 let vb = tri[(i + 2) % 3];
351 if adj == NONE || !is_bad[adj] {
352 boundary.push((va, vb, adj));
353 }
354 }
355 }
356 boundary
357 }
358
359 fn re_triangulate(&mut self, vi: usize, bad: &[usize], boundary: &[(usize, usize, usize)]) {
361 let mut bad_sorted: Vec<usize> = bad.to_vec();
362 bad_sorted.sort_unstable();
363
364 let n_new = boundary.len();
365
366 let mut removed: HashSet<usize> = bad_sorted.iter().copied().collect();
367
368 let mut new_indices = Vec::with_capacity(n_new);
369
370 let mut reuse: Vec<usize> = bad_sorted.clone();
371 for _ in 0..n_new {
372 if let Some(slot) = reuse.pop() {
373 new_indices.push(slot);
374 removed.remove(&slot);
375 } else {
376 let idx = self.triangles.len();
377 self.triangles.push([0; 3]);
378 self.adjacency.push([NONE; 3]);
379 new_indices.push(idx);
380 }
381 }
382
383 let mut remaining_bad: Vec<usize> = removed.into_iter().collect();
384 remaining_bad.sort_unstable();
385
386 use std::collections::HashMap;
387 let mut vertex_to_new: HashMap<usize, usize> = HashMap::new();
388 for (k, &(va, _vb, _)) in boundary.iter().enumerate() {
389 vertex_to_new.insert(va, k);
390 }
391
392 for (k, &(va, vb, adj_outside)) in boundary.iter().enumerate() {
393 let new_ti = new_indices[k];
394 self.triangles[new_ti] = [vi, va, vb];
395
396 self.adjacency[new_ti][0] = adj_outside;
397
398 self.adjacency[new_ti][1] = if let Some(&other_k) = vertex_to_new.get(&vb) {
399 new_indices[other_k]
400 } else {
401 NONE
402 };
403
404 let mut adj2 = NONE;
405 for (j, &(_jva, jvb, _)) in boundary.iter().enumerate() {
406 if jvb == va {
407 adj2 = new_indices[j];
408 break;
409 }
410 }
411 self.adjacency[new_ti][2] = adj2;
412
413 if adj_outside != NONE {
414 for i in 0..3 {
415 if bad_sorted.contains(&self.adjacency[adj_outside][i])
416 || self.adjacency[adj_outside][i] == NONE
417 {
418 let ot = self.triangles[adj_outside];
419 let ea = ot[(i + 1) % 3];
420 let eb = ot[(i + 2) % 3];
421 if (ea == va && eb == vb) || (ea == vb && eb == va) {
422 self.adjacency[adj_outside][i] = new_ti;
423 break;
424 }
425 }
426 }
427 }
428 }
429
430 remaining_bad.sort_unstable_by(|a, b| b.cmp(a));
431 for &slot in &remaining_bad {
432 let last = self.triangles.len() - 1;
433 if slot < last {
434 self.triangles[slot] = self.triangles[last];
435 self.adjacency[slot] = self.adjacency[last];
436 for i in 0..3 {
437 let adj = self.adjacency[slot][i];
438 if adj != NONE && adj < self.triangles.len() {
439 for j in 0..3 {
440 if self.adjacency[adj][j] == last {
441 self.adjacency[adj][j] = slot;
442 }
443 }
444 }
445 }
446 for ni in new_indices.iter_mut() {
447 if *ni == last {
448 *ni = slot;
449 }
450 }
451 }
452 self.triangles.pop();
453 self.adjacency.pop();
454 }
455
456 self.last_triangle = new_indices.first().copied().unwrap_or(0);
457 }
458
459 fn enforce_constraint(&mut self, a: usize, b: usize) -> Result<(), CdtError> {
461 let key = edge_key(a, b);
462 self.constraints.insert(key);
463
464 if self.find_edge_triangle(a, b).is_some() {
465 return Ok(());
466 }
467
468 let max_iter = self.triangles.len() * 4;
469 for _ in 0..max_iter {
470 if self.find_edge_triangle(a, b).is_some() {
471 return Ok(());
472 }
473
474 if !self.flip_one_crossing_edge(a, b) {
475 if self.find_edge_triangle(a, b).is_some() {
476 return Ok(());
477 }
478 return Err(CdtError::ConstraintRecoveryDidNotConverge {
479 edge: (self.user_index(a), self.user_index(b)),
480 iterations: max_iter,
481 });
482 }
483 }
484
485 Err(CdtError::ConstraintRecoveryDidNotConverge {
486 edge: (self.user_index(a), self.user_index(b)),
487 iterations: max_iter,
488 })
489 }
490
491 fn constraint_chain(&self, a: usize, b: usize) -> Vec<usize> {
492 let pa = self.vertices[a];
493 let pb = self.vertices[b];
494 let ab = [pb[0] - pa[0], pb[1] - pa[1]];
495 let ab_len_sq = ab[0] * ab[0] + ab[1] * ab[1];
496 if ab_len_sq <= 1e-24 {
497 return vec![a, b];
498 }
499
500 let tol = 1e-12 * ab_len_sq.sqrt().max(1.0);
501 let mut interior = Vec::new();
502 for vi in self.n_super..self.vertices.len() {
503 if vi == a || vi == b {
504 continue;
505 }
506 let p = self.vertices[vi];
507 let ap = [p[0] - pa[0], p[1] - pa[1]];
508 let cross = orient2d(pa, pb, p).abs();
509 if cross > tol {
510 continue;
511 }
512 let dot = ap[0] * ab[0] + ap[1] * ab[1];
513 if dot <= tol || dot >= ab_len_sq - tol {
514 continue;
515 }
516 interior.push((dot / ab_len_sq, vi));
517 }
518
519 interior.sort_unstable_by(|(ta, _), (tb, _)| ta.total_cmp(tb));
520 let mut chain = Vec::with_capacity(interior.len() + 2);
521 chain.push(a);
522 chain.extend(interior.into_iter().map(|(_, vi)| vi));
523 chain.push(b);
524 chain
525 }
526
527 fn find_edge_triangle(&self, a: usize, b: usize) -> Option<(usize, usize)> {
528 for (ti, tri) in self.triangles.iter().enumerate() {
529 for i in 0..3 {
530 let va = tri[(i + 1) % 3];
531 let vb = tri[(i + 2) % 3];
532 if (va == a && vb == b) || (va == b && vb == a) {
533 return Some((ti, i));
534 }
535 }
536 }
537 None
538 }
539
540 fn flip_one_crossing_edge(&mut self, a: usize, b: usize) -> bool {
541 let pa = self.vertices[a];
542 let pb = self.vertices[b];
543
544 let n_tri = self.triangles.len();
545 for ti in 0..n_tri {
546 let tri = self.triangles[ti];
547 for i in 0..3 {
548 let va = tri[(i + 1) % 3];
549 let vb = tri[(i + 2) % 3];
550
551 if va == a || va == b || vb == a || vb == b {
552 continue;
553 }
554 if self.is_constraint(va, vb) {
555 continue;
556 }
557
558 let pva = self.vertices[va];
559 let pvb = self.vertices[vb];
560
561 if segments_intersect(pa, pb, pva, pvb) {
562 let adj = self.adjacency[ti][i];
563 if adj != NONE {
564 self.flip_edge(ti, adj, i);
565 return true;
566 }
567 }
568 }
569 }
570 false
571 }
572
573 #[inline]
574 fn is_constraint(&self, a: usize, b: usize) -> bool {
575 self.constraints.contains(&edge_key(a, b))
576 }
577
578 fn flip_edge(&mut self, t1: usize, t2: usize, edge_in_t1: usize) {
579 let tri1 = self.triangles[t1];
580 let tri2 = self.triangles[t2];
581
582 let e = edge_in_t1;
583 let shared_a = tri1[(e + 1) % 3];
584 let shared_b = tri1[(e + 2) % 3];
585 let apex1 = tri1[e];
586
587 let edge_in_t2 = self.find_local_edge(t2, shared_a, shared_b);
588 if edge_in_t2 == NONE {
589 return;
590 }
591 let apex2 = tri2[edge_in_t2];
592
593 self.triangles[t1] = [apex1, apex2, shared_b];
594 self.triangles[t2] = [apex2, apex1, shared_a];
595
596 let old_adj1 = self.adjacency[t1];
597 let old_adj2 = self.adjacency[t2];
598
599 let t1_adj_sb_apex1 = old_adj1[(e + 1) % 3];
600 let t1_adj_apex1_sa = old_adj1[(e + 2) % 3];
601
602 let t2_adj_sb_apex2 = {
603 let mut found = NONE;
604 for i in 0..3 {
605 let va = tri2[(i + 1) % 3];
606 let vb = tri2[(i + 2) % 3];
607 if (va == shared_b && vb == apex2) || (va == apex2 && vb == shared_b) {
608 found = old_adj2[i];
609 break;
610 }
611 }
612 found
613 };
614
615 let t2_adj_apex2_sa = {
616 let mut found = NONE;
617 for i in 0..3 {
618 let va = tri2[(i + 1) % 3];
619 let vb = tri2[(i + 2) % 3];
620 if (va == shared_a && vb == apex2) || (va == apex2 && vb == shared_a) {
621 found = old_adj2[i];
622 break;
623 }
624 }
625 found
626 };
627
628 self.adjacency[t1] = [t2_adj_sb_apex2, t1_adj_sb_apex1, t2];
629 self.adjacency[t2] = [t1_adj_apex1_sa, t2_adj_apex2_sa, t1];
630
631 if t2_adj_sb_apex2 != NONE {
632 self.update_adjacency_ref(t2_adj_sb_apex2, t2, t1);
633 }
634 if t1_adj_apex1_sa != NONE {
635 self.update_adjacency_ref(t1_adj_apex1_sa, t1, t2);
636 }
637 }
638
639 fn update_adjacency_ref(&mut self, tri_idx: usize, old_ref: usize, new_ref: usize) {
640 for i in 0..3 {
641 if self.adjacency[tri_idx][i] == old_ref {
642 self.adjacency[tri_idx][i] = new_ref;
643 return;
644 }
645 }
646 }
647
648 fn find_local_edge(&self, t: usize, a: usize, b: usize) -> usize {
649 let tri = self.triangles[t];
650 for i in 0..3 {
651 let va = tri[(i + 1) % 3];
652 let vb = tri[(i + 2) % 3];
653 if (va == a && vb == b) || (va == b && vb == a) {
654 return i;
655 }
656 }
657 NONE
658 }
659}
660
661fn segments_intersect(p1: [f64; 2], p2: [f64; 2], p3: [f64; 2], p4: [f64; 2]) -> bool {
663 let d1 = orient2d(p3, p4, p1);
664 let d2 = orient2d(p3, p4, p2);
665 let d3 = orient2d(p1, p2, p3);
666 let d4 = orient2d(p1, p2, p4);
667
668 if ((d1 > 0.0 && d2 < 0.0) || (d1 < 0.0 && d2 > 0.0))
669 && ((d3 > 0.0 && d4 < 0.0) || (d3 < 0.0 && d4 > 0.0))
670 {
671 return true;
672 }
673 false
674}
675
676#[cfg(test)]
677mod tests {
678 use super::*;
679 use crate::orient3d;
680
681 fn triangle_area(a: [f64; 2], b: [f64; 2], c: [f64; 2]) -> f64 {
682 ((b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1])).abs() * 0.5
683 }
684
685 fn total_triangle_area(cdt: &Cdt) -> f64 {
686 let tris = cdt.triangles();
687 let verts = cdt.user_vertices();
688 tris.iter()
689 .map(|t| triangle_area(verts[t[0]], verts[t[1]], verts[t[2]]))
690 .sum()
691 }
692
693 fn polygon_area(points: &[[f64; 2]]) -> f64 {
694 let mut sum = 0.0;
695 for i in 0..points.len() {
696 let j = (i + 1) % points.len();
697 sum += points[i][0] * points[j][1] - points[j][0] * points[i][1];
698 }
699 sum.abs() * 0.5
700 }
701
702 fn collect_edges(tris: &[[usize; 3]]) -> HashSet<(usize, usize)> {
703 let mut edges = HashSet::new();
704 for t in tris {
705 for i in 0..3 {
706 edges.insert(edge_key(t[i], t[(i + 1) % 3]));
707 }
708 }
709 edges
710 }
711
712 #[test]
713 fn insert_single_point() {
714 let mut cdt = Cdt::new((0.0, 0.0, 1.0, 1.0));
715 cdt.insert(0.5, 0.5);
716 let tris = cdt.triangles();
717 assert!(tris.is_empty());
718 }
719
720 #[test]
721 fn insert_triangle() {
722 let mut cdt = Cdt::new((0.0, 0.0, 1.0, 1.0));
723 cdt.insert(0.0, 0.0);
724 cdt.insert(1.0, 0.0);
725 cdt.insert(0.5, 1.0);
726 let tris = cdt.triangles();
727 assert_eq!(tris.len(), 1);
728 }
729
730 #[test]
731 fn square_triangulation() {
732 let mut cdt = Cdt::new((0.0, 0.0, 1.0, 1.0));
733 cdt.insert(0.0, 0.0);
734 cdt.insert(1.0, 0.0);
735 cdt.insert(1.0, 1.0);
736 cdt.insert(0.0, 1.0);
737 let tris = cdt.triangles();
738 assert_eq!(tris.len(), 2);
739
740 let verts = cdt.user_vertices();
741 let total_area: f64 = tris
742 .iter()
743 .map(|t| {
744 let a = verts[t[0]];
745 let b = verts[t[1]];
746 let c = verts[t[2]];
747 ((b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1])).abs() * 0.5
748 })
749 .sum();
750 assert!((total_area - 1.0).abs() < 1e-10);
751 }
752
753 #[test]
754 fn constraint_edges_preserved() {
755 let pts = [[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]];
756 let mut cdt = Cdt::new((-0.5, -0.5, 1.5, 1.5));
757 cdt.add_constraint_edges(&pts, true).unwrap();
758
759 let tris = cdt.triangles();
760 let mut edges: HashSet<(usize, usize)> = HashSet::new();
761 for t in &tris {
762 for i in 0..3 {
763 edges.insert(edge_key(t[i], t[(i + 1) % 3]));
764 }
765 }
766
767 let n = pts.len();
768 for i in 0..n {
769 let j = (i + 1) % n;
770 assert!(
771 edges.contains(&edge_key(i, j)),
772 "constraint edge ({}, {}) not found in triangulation",
773 i,
774 j
775 );
776 }
777 }
778
779 #[test]
780 fn delaunay_property() {
781 let points = [
782 [0.1, 0.2],
783 [0.8, 0.1],
784 [0.9, 0.9],
785 [0.2, 0.8],
786 [0.5, 0.5],
787 [0.3, 0.4],
788 [0.7, 0.6],
789 ];
790 let mut cdt = Cdt::new((-0.5, -0.5, 1.5, 1.5));
791 for p in &points {
792 cdt.insert(p[0], p[1]);
793 }
794
795 let tris = cdt.triangles();
796 let verts = cdt.user_vertices();
797
798 for tri in &tris {
799 let a = verts[tri[0]];
800 let b = verts[tri[1]];
801 let c = verts[tri[2]];
802 let (a, b, c) = if orient2d(a, b, c) >= 0.0 {
803 (a, b, c)
804 } else {
805 (a, c, b)
806 };
807
808 for (vi, v) in verts.iter().enumerate() {
809 if vi == tri[0] || vi == tri[1] || vi == tri[2] {
810 continue;
811 }
812 let ic = incircle(a, b, c, *v);
813 assert!(
814 ic <= 1e-10,
815 "Delaunay violation: triangle {:?}, vertex {} (incircle={})",
816 tri,
817 vi,
818 ic
819 );
820 }
821 }
822 }
823
824 #[test]
825 fn closed_constraint_polygon_is_order_invariant() {
826 let points = [[0.0, 0.0], [2.0, 0.0], [2.5, 1.0], [1.0, 2.0], [-0.5, 1.0]];
827
828 let mut forward = Cdt::new((-1.0, -1.0, 3.0, 3.0));
829 forward.add_constraint_edges(&points, true).unwrap();
830
831 let mut reversed_points = points;
832 reversed_points.reverse();
833 let mut reversed = Cdt::new((-1.0, -1.0, 3.0, 3.0));
834 reversed
835 .add_constraint_edges(&reversed_points, true)
836 .unwrap();
837
838 let expected_area = polygon_area(&points);
839 let forward_area = total_triangle_area(&forward);
840 let reversed_area = total_triangle_area(&reversed);
841 let forward_edges = collect_edges(&forward.triangles());
842 let reversed_edges = collect_edges(&reversed.triangles());
843
844 assert_eq!(forward.n_user_vertices(), points.len());
845 assert_eq!(reversed.n_user_vertices(), points.len());
846 assert!(!forward.triangles().is_empty());
847 assert!(!reversed.triangles().is_empty());
848 assert_eq!(forward.triangles().len(), reversed.triangles().len());
849 for i in 0..points.len() {
850 let j = (i + 1) % points.len();
851 let edge = edge_key(i, j);
852 assert!(forward_edges.contains(&edge));
853 assert!(reversed_edges.contains(&edge));
854 }
855 assert!((forward_area - expected_area).abs() < 1e-10);
856 assert!((reversed_area - expected_area).abs() < 1e-10);
857 }
858
859 #[test]
860 fn nearly_degenerate_constraint_chain_remains_valid() {
861 let points = [
862 [0.0, 0.0],
863 [1.0e-12, 1.0e-12],
864 [2.0, 0.0],
865 [2.0, 1.0],
866 [1.0, 1.0 + 1.0e-12],
867 [0.0, 1.0],
868 ];
869
870 let mut cdt = Cdt::new((-0.5, -0.5, 2.5, 1.5));
871 cdt.add_constraint_edges(&points, true).unwrap();
872
873 let tris = cdt.triangles();
874 let total_area = total_triangle_area(&cdt);
875
876 assert_eq!(cdt.n_user_vertices(), points.len());
877 assert!(!tris.is_empty());
878 assert!((total_area - polygon_area(&points)).abs() < 1e-10);
879
880 let edges = collect_edges(&tris);
881 for i in 0..points.len() {
882 let j = (i + 1) % points.len();
883 assert!(
884 edges.contains(&edge_key(i, j)),
885 "constraint edge ({}, {}) not found in triangulation",
886 i,
887 j
888 );
889 }
890 }
891
892 #[test]
893 fn orient_sign_contract_is_fixed() {
894 assert!(orient2d([0.0, 0.0], [1.0, 0.0], [0.0, 1.0]) > 0.0);
895 assert!(orient2d([0.0, 0.0], [0.0, 1.0], [1.0, 0.0]) < 0.0);
896 assert_eq!(orient2d([0.0, 0.0], [1.0, 0.0], [2.0, 0.0]), 0.0);
897
898 assert!(
899 orient3d(
900 [0.0, 0.0, 0.0],
901 [1.0, 0.0, 0.0],
902 [0.0, 1.0, 0.0],
903 [0.0, 0.0, -1.0]
904 ) > 0.0
905 );
906 assert!(
907 orient3d(
908 [0.0, 0.0, 0.0],
909 [1.0, 0.0, 0.0],
910 [0.0, 1.0, 0.0],
911 [0.0, 0.0, 1.0]
912 ) < 0.0
913 );
914 assert_eq!(
915 orient3d(
916 [0.0, 0.0, 0.0],
917 [1.0, 0.0, 0.0],
918 [0.0, 1.0, 0.0],
919 [0.5, 0.5, 0.0]
920 ),
921 0.0
922 );
923 }
924
925 #[test]
926 fn constraint_chain_splits_at_existing_collinear_vertices() {
927 let mut cdt = Cdt::new((-0.5, -0.5, 2.5, 1.5));
928 let a = cdt.insert(0.0, 0.0);
929 let b = cdt.insert(2.0, 0.0);
930 let mid = cdt.insert(1.0, 0.0);
931 cdt.insert(0.5, 1.0);
932 cdt.insert(1.5, 1.0);
933
934 let chain = cdt.constraint_chain(a, b);
935 assert_eq!(chain, vec![a, mid, b]);
936
937 for edge in chain.windows(2) {
938 cdt.enforce_constraint(edge[0], edge[1]).unwrap();
939 }
940
941 let edges = collect_edges(&cdt.triangles());
942 assert!(edges.contains(&edge_key(0, 2)));
943 assert!(edges.contains(&edge_key(1, 2)));
944 }
945}