honeycomb_kernels/triangulation/fan.rs
1use honeycomb_core::cmap::{CMap2, DartIdType, FaceIdType, Orbit2, OrbitPolicy};
2use honeycomb_core::geometry::CoordsFloat;
3
4use crate::triangulation::{
5 check_requirements, crossp_from_verts, fetch_face_vertices, TriangulateError,
6};
7
8#[allow(clippy::missing_panics_doc)]
9/// Triangulates a face using a fan triangulation method.
10///
11/// This function triangulates a cell (face) in a 2D combinatorial map by creating a fan of
12/// triangles from a chosen vertex to all other vertices of the polygon, if such a vertex exist.
13///
14/// Note that this function will not have any effect if the polygon isn't fannable.
15///
16/// # Arguments
17///
18/// - `cmap: &mut CMap2` - A mutable reference to the modified `CMap2`.
19/// - `face_id: FaceIdentifier` - Identifier of the face to triangulate within the map.
20/// - `new_darts: &[DartIdentifier]` - Identifiers of pre-allocated darts for the new edges;
21/// the slice length should match the expected number of edges created by the triangulation. For
22/// an `n`-sided polygon, the number of created edge is `n-3`, so the number of dart is `(n-3)*2`.
23///
24/// # Behavior
25///
26/// - The function begins by checking if the face has 3 or fewer vertices, in which case
27/// it's already triangulated or cannot be further processed.
28/// - It verifies if the number of new darts matches the expected number for triangulation.
29/// - The function then attempts to find a vertex from which all other vertices can be seen
30/// (a star vertex), using the orientation properties of N-maps.
31/// - If such a star vertex is found, the function proceeds to create triangles by linking
32/// new darts in a fan-like structure from this vertex. Otherwise, the cell is left unchanged
33///
34/// # Errors
35///
36/// This function will return an error if the face wasn't triangulated. There can be multiple
37/// reason for this:
38/// - The face is incompatible with the operation (made of 1, 2 or 3 vertices)
39/// - The number of pre-allocated darts did not match the expected number (see [#arguments])
40/// - The face contains one or more undefined vertices
41/// - The face isn't starrable
42///
43/// Note that in any of these cases, the face will remain the same as it was before the function
44/// call.
45pub fn process_cell<T: CoordsFloat>(
46 cmap: &mut CMap2<T>,
47 face_id: FaceIdType,
48 new_darts: &[DartIdType],
49) -> Result<(), TriangulateError> {
50 // fetch darts using a custom orbit so that they're ordered
51 let darts: Vec<_> =
52 Orbit2::new(cmap, OrbitPolicy::Custom(&[1]), face_id as DartIdType).collect();
53 let n = darts.len();
54
55 // early checks - check # of darts & face size
56 check_requirements(n, new_darts.len())?;
57
58 // get associated vertices - check for undefined vertices
59 let vertices = fetch_face_vertices(cmap, &darts)?;
60
61 // iterating by ref so that we can still access the list
62 let star = darts
63 .iter()
64 .zip(vertices.iter())
65 .enumerate()
66 .find_map(|(id, (d0, v0))| {
67 let mut tmp = vertices
68 .windows(2)
69 .enumerate()
70 // remove segments directly attached to v0
71 .filter(|(i_seg, _)| !((n + i_seg) % n == id || (n + i_seg - 1) % n == id))
72 .map(|(_, val)| {
73 let [v1, v2] = val else { unreachable!() };
74 crossp_from_verts(v0, v1, v2)
75 });
76 let signum = tmp.next().map(T::signum).unwrap();
77 for v in tmp {
78 if v.signum() != signum || v.abs() < T::epsilon() {
79 return None;
80 }
81 }
82 Some(d0)
83 });
84
85 if let Some(sdart) = star {
86 // if we found a dart from the previous computations, it means the polygon is "fannable"
87 // THIS CANNOT BE PARALLELIZED AS IS
88 let b0_sdart = cmap.beta::<0>(*sdart);
89 let v0 = cmap.force_read_vertex(cmap.vertex_id(*sdart)).unwrap();
90 cmap.force_unsew::<1>(b0_sdart).unwrap();
91 let mut d0 = *sdart;
92 for sl in new_darts.chunks_exact(2) {
93 let [d1, d2] = sl else { unreachable!() };
94 let b1_d0 = cmap.beta::<1>(d0);
95 let b1b1_d0 = cmap.beta::<1>(cmap.beta::<1>(d0));
96 cmap.force_unsew::<1>(b1_d0).unwrap();
97 cmap.force_link::<2>(*d1, *d2).unwrap();
98 cmap.force_link::<1>(*d2, b1b1_d0).unwrap();
99 cmap.force_sew::<1>(b1_d0, *d1).unwrap();
100 cmap.force_sew::<1>(*d1, d0).unwrap();
101 d0 = *d2;
102 }
103 cmap.force_sew::<1>(cmap.beta::<1>(cmap.beta::<1>(d0)), d0)
104 .unwrap();
105 cmap.force_write_vertex(cmap.vertex_id(*sdart), v0);
106 } else {
107 // println!("W: face {face_id} isn't fannable -- skipping triangulation");
108 return Err(TriangulateError::NonFannable);
109 }
110
111 Ok(())
112}
113
114#[allow(clippy::missing_panics_doc)]
115/// Triangulates a face using a fan triangulation method.
116///
117/// This function triangulates a cell (face) in a 2D combinatorial map by creating a fan of
118/// triangles from a the first vertex of
119///
120/// **Note that this function assumes the polygon is convex and correctly defined (i.e. all vertices
121/// are) and may fail or produce incorrect results if called on a cell that does not verify these
122/// requirements**.
123///
124/// # Arguments
125///
126/// - `cmap: &mut CMap2` - A mutable reference to the modified `CMap2`.
127/// - `face_id: FaceIdentifier` - Identifier of the face to triangulate within the map.
128/// - `new_darts: &[DartIdentifier]` - Identifiers of pre-allocated darts for the new edges;
129/// the slice length should match the expected number of edges created by the triangulation. For
130/// an `n`-sided polygon, the number of created edge is `n-3`, so the number of dart is `(n-3)*2`.
131///
132/// # Behavior
133///
134/// - The function begins by checking if the face has 3 or fewer vertices, in which case
135/// it's already triangulated or cannot be further processed.
136/// - It verifies if the number of new darts matches the expected number for triangulation.
137/// - The function creates triangles by linking new darts in a fan-like structure to the first
138/// vertex of the polygon. **This is done unconditionnally, whether the polygon is convex or not**.
139///
140/// # Errors
141///
142/// This function will return an error if the face wasn't triangulated. There can be multiple
143/// reason for this:
144/// - The face is incompatible with the operation (made of 1, 2 or 3 vertices)
145/// - The number of pre-allocated darts did not match the expected number (see [#arguments])
146///
147/// Note that in any of these cases, the face will remain the same as it was before the function
148/// call.
149pub fn process_convex_cell<T: CoordsFloat>(
150 cmap: &mut CMap2<T>,
151 face_id: FaceIdType,
152 new_darts: &[DartIdType],
153) -> Result<(), TriangulateError> {
154 let n = Orbit2::new(cmap, OrbitPolicy::Custom(&[1]), face_id as DartIdType).count();
155
156 // early rets
157 check_requirements(n, new_darts.len())?;
158
159 // we assume the polygon is convex (== starrable from any vertex)
160 let sdart = face_id as DartIdType;
161
162 // THIS CANNOT BE PARALLELIZED AS IS
163 let b0_sdart = cmap.beta::<0>(sdart);
164 let v0 = cmap.force_read_vertex(cmap.vertex_id(sdart)).unwrap();
165 cmap.force_unsew::<1>(b0_sdart).unwrap();
166 let mut d0 = sdart;
167 for sl in new_darts.chunks_exact(2) {
168 let [d1, d2] = sl else { unreachable!() };
169 let b1_d0 = cmap.beta::<1>(d0);
170 let b1b1_d0 = cmap.beta::<1>(cmap.beta::<1>(d0));
171 cmap.force_unsew::<1>(b1_d0).unwrap();
172 cmap.force_link::<2>(*d1, *d2).unwrap();
173 cmap.force_link::<1>(*d2, b1b1_d0).unwrap();
174 cmap.force_sew::<1>(b1_d0, *d1).unwrap();
175 cmap.force_sew::<1>(*d1, d0).unwrap();
176 d0 = *d2;
177 }
178 cmap.force_sew::<1>(cmap.beta::<1>(cmap.beta::<1>(d0)), d0)
179 .unwrap();
180 cmap.force_write_vertex(cmap.vertex_id(sdart), v0);
181
182 Ok(())
183}