honeycomb_kernels/splits/edge_multiple.rs
1//! standard and no-alloc variants of the `splitn_edge` functions
2
3use honeycomb_core::cmap::{CMap2, DartIdType, EdgeIdType, NULL_DART_ID};
4use honeycomb_core::geometry::CoordsFloat;
5use honeycomb_core::stm::{
6 abort, atomically_with_err, try_or_coerce, Transaction, TransactionClosureResult,
7};
8
9use crate::splits::SplitEdgeError;
10
11#[allow(clippy::missing_errors_doc)]
12/// Split an edge into `n` segments.
13///
14/// <div class="warning">
15/// This implementation is 2D specific.
16/// </div>
17///
18/// # Arguments
19///
20/// - `cmap: &mut CMap2<T>` -- Reference to the modified map.
21/// - `edge_id: EdgeIdentifier` -- Edge to split in two.
22/// - `midpoint_vertices: I` -- Relative positions of new vertices, starting from the
23/// vertex of the dart sharing `edge_id` as its identifier.
24///
25/// ## Generics
26///
27/// - `I: Iterator<Item = T>` -- Iterator over `T` values. These should be in the `]0; 1[` open range.
28///
29/// # Return / Errors
30///
31/// This method will return:
32/// - `Ok(())` if the operation is successful & the edge was split
33/// - `Err(SplitEdgeError)` if the operation fails & the edge is left unchanged. Causes of failure
34/// are described in [`SplitEdgeError`]'s documentation.
35///
36/// # Example
37///
38/// ```
39/// # use honeycomb_core::cmap::{CMap2, CMapBuilder, NULL_DART_ID};
40/// # use honeycomb_core::geometry::Vertex2;
41/// # use honeycomb_kernels::splits::splitn_edge;
42/// // before
43/// // <--2---
44/// // 1 2
45/// // ---1-->
46/// let mut map: CMap2<f64> = CMapBuilder::default()
47/// .n_darts(2)
48/// .build()
49/// .unwrap();
50/// map.force_link::<2>(1, 2);
51/// map.force_write_vertex(1, (0.0, 0.0));
52/// map.force_write_vertex(2, (1.0, 0.0));
53/// // split
54/// assert!(splitn_edge(&mut map, 1, [0.25, 0.50, 0.75]).is_ok());
55/// // after
56/// // <-<-<-<
57/// // 1 -3-4-5- 2
58/// // >->->->
59/// let new_darts = [
60/// map.beta::<1>(1),
61/// map.beta::<1>(map.beta::<1>(1)),
62/// map.beta::<1>(map.beta::<1>(map.beta::<1>(1))),
63/// ];
64/// assert_eq!(&new_darts, &[3, 4, 5]);
65/// assert_eq!(map.force_read_vertex(3), Some(Vertex2(0.25, 0.0)));
66/// assert_eq!(map.force_read_vertex(4), Some(Vertex2(0.50, 0.0)));
67/// assert_eq!(map.force_read_vertex(5), Some(Vertex2(0.75, 0.0)));
68///
69/// assert_eq!(map.beta::<1>(1), 3);
70/// assert_eq!(map.beta::<1>(3), 4);
71/// assert_eq!(map.beta::<1>(4), 5);
72/// assert_eq!(map.beta::<1>(5), NULL_DART_ID);
73///
74/// assert_eq!(map.beta::<1>(2), 6);
75/// assert_eq!(map.beta::<1>(6), 7);
76/// assert_eq!(map.beta::<1>(7), 8);
77/// assert_eq!(map.beta::<1>(8), NULL_DART_ID);
78///
79/// assert_eq!(map.beta::<2>(1), 8);
80/// assert_eq!(map.beta::<2>(3), 7);
81/// assert_eq!(map.beta::<2>(4), 6);
82/// assert_eq!(map.beta::<2>(5), 2);
83/// ```
84#[allow(clippy::cast_possible_truncation)]
85pub fn splitn_edge<T: CoordsFloat>(
86 cmap: &mut CMap2<T>,
87 edge_id: EdgeIdType,
88 midpoint_vertices: impl IntoIterator<Item = T>,
89) -> Result<(), SplitEdgeError> {
90 // check pre-allocated darts reqs
91 let midpoint_vertices = midpoint_vertices.into_iter().collect::<Vec<_>>();
92 let n_t = midpoint_vertices.len();
93
94 // base darts making up the edge
95 let base_dart1 = edge_id as DartIdType;
96 let base_dart2 = cmap.beta::<2>(base_dart1);
97
98 let new_darts = if base_dart2 == NULL_DART_ID {
99 let tmp = cmap.add_free_darts(n_t);
100 (tmp..tmp + n_t as DartIdType)
101 .chain((0..n_t).map(|_| NULL_DART_ID))
102 .collect::<Vec<_>>()
103 } else {
104 let tmp = cmap.add_free_darts(2 * n_t);
105 (tmp..tmp + 2 * n_t as DartIdType).collect::<Vec<_>>()
106 };
107 // get the first and second halves
108 let (darts_fh, darts_sh) = (&new_darts[..n_t], &new_darts[n_t..]);
109
110 atomically_with_err(|trans| {
111 inner_splitn(
112 cmap,
113 trans,
114 base_dart1,
115 darts_fh,
116 darts_sh,
117 &midpoint_vertices,
118 )
119 })
120}
121
122#[allow(clippy::missing_errors_doc)]
123/// Split an edge into `n` segments.
124///
125/// <div class="warning">
126/// This implementation is 2D specific.
127/// </div>
128///
129/// This method is a variant of [`splitn_edge`] where inline dart allocations are removed. The
130/// aim of this variant is to enhance performance by enabling the user to pre-allocate a number
131/// of darts.
132///
133/// The method follows the same logic as the regular [`splitn_edge`], the only difference being
134/// that the new darts won't be added to the map on the fly. Instead, the method uses darts
135/// passed as argument (`new_darts`) to build the new segments. Consequently, there is no
136/// guarantee that IDs will be consistent between this and the regular method.
137///
138/// # Arguments
139///
140/// - `cmap: &mut CMap2<T>` -- Reference to the modified map.
141/// - `edge_id: EdgeIdentifier` -- Edge to split in two.
142/// - `new_darts: &[DartIdentifier]` -- Dart IDs used to build the new segments.
143/// - `midpoint_vertices: &[T]` -- Relative positions of new vertices, starting from the
144/// vertex of the dart sharing `edge_id` as its identifier.
145///
146/// ## Dart IDs Requirements & Usage
147///
148/// Because of the dimension, we can easily compute the number of dart needed to perform this
149/// operation. These are the requirements for the darts:
150/// - identifiers are passed as a slice:
151/// - slice length should verify `new_darts.len() == 2 * midpoint_vertices.len()`
152/// - the first half of the slice will always be used if the operation is successful.
153/// - the second half of the slice will only be used if the original edge is made of two darts;
154/// if that is not the case, the second half IDs can all be `NULL_DART_ID`s.
155/// - all of these darts should be free
156///
157/// # Return / Errors
158///
159/// This method will return:
160/// - `Ok(())` if the operation is successful & the edge was split
161/// - `Err(SplitEdgeError)` if the operation fails & the edge is left unchanged. Causes of failure
162/// are described in [`SplitEdgeError`]'s documentation and in requirements mentionned above.
163pub fn splitn_edge_transac<T: CoordsFloat>(
164 cmap: &CMap2<T>,
165 trans: &mut Transaction,
166 edge_id: EdgeIdType,
167 new_darts: &[DartIdType],
168 midpoint_vertices: &[T],
169) -> TransactionClosureResult<(), SplitEdgeError> {
170 // check pre-allocated darts reqs
171 let n_t = midpoint_vertices.len();
172 let n_d = new_darts.len();
173 if n_d != 2 * n_t {
174 abort(SplitEdgeError::WrongAmountDarts(2 * n_t, n_d))?;
175 }
176 // FIXME: is_free should be transactional
177 if new_darts.iter().any(|d| !cmap.is_free(*d)) {
178 abort(SplitEdgeError::InvalidDarts("one dart is not free"))?;
179 }
180 // get the first and second halves
181 let darts_fh = &new_darts[..n_t];
182 let darts_sh = &new_darts[n_t..];
183
184 // base darts making up the edge
185 let base_dart1 = edge_id as DartIdType;
186 let base_dart2 = cmap.beta_transac::<2>(trans, base_dart1)?;
187
188 if darts_fh.iter().any(|d| *d == NULL_DART_ID) {
189 abort(SplitEdgeError::InvalidDarts(
190 "one dart of the first half is null",
191 ))?;
192 }
193 if base_dart2 != NULL_DART_ID && darts_sh.iter().any(|d| *d == NULL_DART_ID) {
194 abort(SplitEdgeError::InvalidDarts(
195 "one dart of the second half is null",
196 ))?;
197 }
198
199 inner_splitn(
200 cmap,
201 trans,
202 base_dart1,
203 darts_fh,
204 darts_sh,
205 midpoint_vertices,
206 )
207}
208
209// --- common inner routine
210
211fn inner_splitn<T: CoordsFloat>(
212 cmap: &CMap2<T>,
213 trans: &mut Transaction,
214 base_dart1: DartIdType,
215 darts_fh: &[DartIdType], //first half
216 darts_sh: &[DartIdType], //second half
217 midpoint_vertices: &[T],
218) -> TransactionClosureResult<(), SplitEdgeError> {
219 if midpoint_vertices
220 .iter()
221 .any(|t| (*t >= T::one()) | (*t <= T::zero()))
222 {
223 abort(SplitEdgeError::VertexBound)?;
224 }
225
226 let base_dart2 = cmap.beta_transac::<2>(trans, base_dart1)?;
227 let b1d1_old = cmap.beta_transac::<1>(trans, base_dart1)?;
228
229 let (vid1, vid2) = (
230 cmap.vertex_id_transac(trans, base_dart1)?,
231 cmap.vertex_id_transac(
232 trans,
233 if b1d1_old != NULL_DART_ID {
234 b1d1_old
235 } else if base_dart2 != NULL_DART_ID {
236 base_dart2
237 } else {
238 abort(SplitEdgeError::UndefinedEdge)?
239 },
240 )?,
241 );
242 let (Some(v1), Some(v2)) = (
243 cmap.read_vertex(trans, vid1)?,
244 cmap.read_vertex(trans, vid2)?,
245 ) else {
246 abort(SplitEdgeError::UndefinedEdge)?
247 };
248 let seg = v2 - v1;
249
250 // unsew current dart
251 if b1d1_old != NULL_DART_ID {
252 try_or_coerce!(cmap.unlink::<1>(trans, base_dart1), SplitEdgeError);
253 }
254 //
255 if base_dart2 != NULL_DART_ID {
256 try_or_coerce!(cmap.unlink::<2>(trans, base_dart1), SplitEdgeError);
257 }
258 // insert new vertices / darts on base_dart1's side
259 let mut prev_d = base_dart1;
260 for (&t, &new_d) in midpoint_vertices.iter().zip(darts_fh.iter()) {
261 let new_v = v1 + seg * t;
262 try_or_coerce!(cmap.link::<1>(trans, prev_d, new_d), SplitEdgeError);
263 cmap.write_vertex(trans, new_d, new_v)?;
264 prev_d = new_d;
265 }
266 try_or_coerce!(cmap.link::<1>(trans, prev_d, b1d1_old), SplitEdgeError);
267
268 // if b2(base_dart1) is defined, insert vertices / darts on its side too
269 if base_dart2 != NULL_DART_ID {
270 let b1d2_old = cmap.beta_transac::<1>(trans, base_dart2)?;
271 if b1d2_old != NULL_DART_ID {
272 try_or_coerce!(cmap.unlink::<1>(trans, base_dart2), SplitEdgeError);
273 }
274 let mut prev_d = base_dart2;
275 for (d, new_d) in darts_fh.iter().rev().zip(darts_sh.iter()) {
276 try_or_coerce!(cmap.link::<2>(trans, prev_d, *d), SplitEdgeError);
277 try_or_coerce!(cmap.link::<1>(trans, prev_d, *new_d), SplitEdgeError);
278 prev_d = *new_d;
279 }
280 if b1d2_old != NULL_DART_ID {
281 try_or_coerce!(cmap.link::<1>(trans, prev_d, b1d2_old), SplitEdgeError);
282 }
283 try_or_coerce!(cmap.link::<2>(trans, prev_d, base_dart1), SplitEdgeError);
284 }
285
286 Ok(())
287}