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}