axiolid_triangulate/mesh.rs
1// SPDX-License-Identifier: MPL-2.0
2
3//! The triangulation structure and the constrained Delaunay build.
4//!
5//! # Representation
6//!
7//! Triangles are stored in a flat array with a parallel neighbour array, the
8//! standard "triangle + halfedge" layout: halfedge `3t + i` belongs to
9//! triangle `t`, runs from vertex `i` to vertex `i + 1 mod 3`, and its twin
10//! is `halfedge[3t + i]`. A half-edge structure with explicit records would
11//! carry a pointer per edge and buy nothing here -- the triangulation is
12//! rebuilt rather than edited, so compactness and cache locality win.
13//!
14//! Vertices 0..n are the caller's points. Three extra vertices form a super
15//! triangle large enough to contain them all; they are removed at the end,
16//! together with every triangle that touches them.
17
18use axiolid_core::Point2;
19
20use crate::Constraint;
21
22/// Sentinel for "no neighbour across this halfedge".
23pub(crate) const NO_HALFEDGE: u32 = u32::MAX;
24
25/// Why a triangulation could not be produced.
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27#[non_exhaustive]
28pub enum TriangulationError {
29 /// Fewer than three points were supplied, so no triangle exists.
30 TooFewPoints,
31 /// Every input point is collinear, so the triangulation has zero area.
32 ///
33 /// Reported rather than returning an empty triangulation: a caller that
34 /// asked to triangulate a degenerate polygon has a bug upstream, and an
35 /// empty result would hide it.
36 AllPointsCollinear,
37 /// A vertex could not be placed in any triangle during insertion.
38 ///
39 /// Indicates a corrupted adjacency rather than bad input. Reported so a
40 /// missing vertex surfaces as an error instead of as a silent hole in an
41 /// otherwise valid-looking mesh.
42 VertexUnplaceable {
43 /// Index of the vertex that could not be placed.
44 index: u32,
45 },
46 /// A constraint referenced a vertex index that does not exist.
47 ConstraintOutOfRange {
48 /// The offending index.
49 index: u32,
50 },
51 /// A constraint could not be recovered after insertion.
52 ///
53 /// Only reachable when two constraints cross: an edge cannot survive if
54 /// another required edge passes through it. The crossing point would have
55 /// to be inserted as a vertex, which changes the caller's input, so this
56 /// is reported instead of silently repaired.
57 CrossingConstraints {
58 /// First endpoint of the constraint that could not be recovered.
59 a: u32,
60 /// Second endpoint.
61 b: u32,
62 },
63}
64
65impl core::fmt::Display for TriangulationError {
66 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
67 match self {
68 Self::TooFewPoints => write!(f, "a triangulation needs at least three points"),
69 Self::AllPointsCollinear => write!(f, "every input point is collinear"),
70 Self::VertexUnplaceable { index } => {
71 write!(f, "vertex {index} could not be placed in any triangle")
72 }
73 Self::ConstraintOutOfRange { index } => {
74 write!(f, "constraint references out-of-range vertex {index}")
75 }
76 Self::CrossingConstraints { a, b } => {
77 write!(f, "constraint ({a}, {b}) crosses another constraint")
78 }
79 }
80 }
81}
82
83impl core::error::Error for TriangulationError {}
84
85/// A planar triangulation over a point set, honouring constraint edges.
86#[derive(Debug, Clone)]
87pub struct Triangulation {
88 pub(crate) points: Vec<Point2>,
89 /// Three vertex indices per triangle, counter-clockwise.
90 pub(crate) triangles: Vec<u32>,
91 /// Twin halfedge per halfedge, or [`NO_HALFEDGE`].
92 pub(crate) halfedges: Vec<u32>,
93 /// Constraint edges, sorted, as vertex index pairs.
94 pub(crate) constraints: Vec<Constraint>,
95}
96
97impl Triangulation {
98 /// The triangle list, three counter-clockwise vertex indices each.
99 #[must_use]
100 pub fn triangles(&self) -> &[u32] {
101 &self.triangles
102 }
103
104 /// The vertex positions, including any Steiner points appended by
105 /// refinement.
106 #[must_use]
107 pub fn points(&self) -> &[Point2] {
108 &self.points
109 }
110
111 /// Number of triangles.
112 #[must_use]
113 pub fn triangle_count(&self) -> usize {
114 self.triangles.len() / 3
115 }
116
117 /// The constraint edges this triangulation was built to honour.
118 #[must_use]
119 pub fn constraints(&self) -> &[Constraint] {
120 &self.constraints
121 }
122
123 /// Whether `edge` is a constraint.
124 pub(crate) fn is_constrained(&self, a: u32, b: u32) -> bool {
125 self.constraints
126 .binary_search(&Constraint::new(a, b))
127 .is_ok()
128 }
129}