honeycomb_kernels/cell_insertion/vertices.rs
1//! Vertex isnertion routines
2
3use honeycomb_core::{
4 attributes::AttributeError,
5 cmap::{CMap2, DartIdType, EdgeIdType, LinkError, NULL_DART_ID, SewError},
6 geometry::{CoordsFloat, Vertex2},
7 stm::{Transaction, TransactionClosureResult, abort, try_or_coerce},
8};
9
10// -- error type
11
12/// Error-modeling enum for vertex insertion routines.
13#[derive(thiserror::Error, Debug, PartialEq, Eq)]
14pub enum VertexInsertionError {
15 /// A core operation failed.
16 #[error("core operation failed: {0}")]
17 FailedCoreOp(#[from] SewError),
18 /// Relative position of the new vertex isn't located on the edge.
19 #[error("vertex placement for split is not in ]0;1[")]
20 VertexBound,
21 /// One or both vertices of the edge are undefined.
22 #[error("edge isn't defined correctly")]
23 UndefinedEdge,
24 /// Darts passed to the function do not match requirements.
25 #[error("passed darts should be free & non-null - {0}")]
26 InvalidDarts(&'static str),
27 /// The number of darts passed to create the new segments is too low. The `usize` value
28 /// is the number of missing darts.
29 #[error("wrong # of darts - expected `{0}`, got {1}")]
30 WrongAmountDarts(usize, usize),
31}
32
33impl From<LinkError> for VertexInsertionError {
34 fn from(value: LinkError) -> Self {
35 Self::FailedCoreOp(value.into())
36 }
37}
38
39impl From<AttributeError> for VertexInsertionError {
40 fn from(value: AttributeError) -> Self {
41 Self::FailedCoreOp(value.into())
42 }
43}
44
45// -- routines
46
47/// Insert a vertex in an edge, cutting it into two segments.
48///
49/// <div class="warning">
50/// This implementation is 2D specific.
51/// </div>
52///
53/// # Arguments
54///
55/// - `cmap: &mut CMap2<T>` -- Reference to the modified map.
56/// - `t: &mut Transaction` -- Associated transaction.
57/// - `edge_id: EdgeIdentifier` -- Target edge.
58/// - `new_darts: (DartIdentifier, DartIdentifier)` -- Dart IDs used to build the new vertex/segments.
59/// - `midpoint_vertex: Option<T>` -- Relative position of the new vertex, starting from the
60/// vertex of the dart sharing `edge_id` as its identifier.
61///
62/// ## Dart IDs Requirements & Usage
63///
64/// Because of the dimension, the number of dart needed to perform this operation is at most
65/// two. These are the requirements for these two darts:
66/// - identifiers are passed as a tuple.
67/// - the first dart of the tuple will always be used if the operation is successful.
68/// - the second dart of the tuple will only be used if the original edge is made of two darts;
69/// if that is not the case, the second dart ID can be `NULL_DART_ID`.
70/// - both of these darts should be free
71///
72/// # Errors
73///
74/// This function will abort and raise an error if:
75/// - the transaction cannot be completed,
76/// - a hypothesis over input isn't verified (see [`VertexInsertionError`]),
77/// - an internal operation failed.
78///
79/// The returned error can be used in conjunction with transaction control to avoid any
80/// modifications in case of failure at attribute level. The user can then choose to retry or
81/// abort as he wishes using `Transaction::with_control_and_err`.
82#[allow(clippy::too_many_lines)]
83pub fn insert_vertex_on_edge<T: CoordsFloat>(
84 cmap: &CMap2<T>,
85 t: &mut Transaction,
86 edge_id: EdgeIdType,
87 new_darts: (DartIdType, DartIdType), // 2D => statically known number of darts
88 midpoint_vertex: Option<T>,
89) -> TransactionClosureResult<(), VertexInsertionError> {
90 // midpoint check
91 if midpoint_vertex.is_some_and(|p| (p >= T::one()) | (p <= T::zero())) {
92 abort(VertexInsertionError::VertexBound)?;
93 }
94
95 // base darts making up the edge
96 let base_dart1 = edge_id as DartIdType;
97 let base_dart2 = cmap.beta_tx::<2>(t, base_dart1)?;
98
99 if new_darts.0 == NULL_DART_ID || !cmap.is_free_tx(t, new_darts.0)? {
100 abort(VertexInsertionError::InvalidDarts(
101 "first dart is null or not free",
102 ))?;
103 }
104 if base_dart2 != NULL_DART_ID
105 && (new_darts.1 == NULL_DART_ID || !cmap.is_free_tx(t, new_darts.1)?)
106 {
107 abort(VertexInsertionError::InvalidDarts(
108 "second dart is null or not free",
109 ))?;
110 }
111
112 // base darts making up the edge
113 let base_dart2 = cmap.beta_tx::<2>(t, base_dart1)?;
114 if base_dart2 == NULL_DART_ID {
115 let b1d1_old = cmap.beta_tx::<1>(t, base_dart1)?;
116 let b1d1_new = new_darts.0;
117 let (vid1, vid2) = (
118 cmap.vertex_id_tx(t, base_dart1)?,
119 cmap.vertex_id_tx(t, b1d1_old)?,
120 );
121 let (Some(v1), Some(v2)) = (cmap.read_vertex_tx(t, vid1)?, cmap.read_vertex_tx(t, vid2)?)
122 else {
123 abort(VertexInsertionError::UndefinedEdge)?
124 };
125 // unsew current dart
126 if b1d1_old != NULL_DART_ID {
127 try_or_coerce!(cmap.unlink_tx::<1>(t, base_dart1), VertexInsertionError);
128 }
129 // rebuild the edge
130 try_or_coerce!(
131 cmap.link_tx::<1>(t, base_dart1, b1d1_new),
132 VertexInsertionError
133 );
134 try_or_coerce!(
135 cmap.link_tx::<1>(t, b1d1_new, b1d1_old),
136 VertexInsertionError
137 );
138 // insert the new vertex
139 let seg = v2 - v1;
140 let vnew = cmap.vertex_id_tx(t, b1d1_new)?;
141 cmap.write_vertex_tx(
142 t,
143 vnew,
144 midpoint_vertex.map_or(Vertex2::average(&v1, &v2), |t| v1 + seg * t),
145 )?;
146 Ok(())
147 } else {
148 let b1d1_old = cmap.beta_tx::<1>(t, base_dart1)?;
149 let b1d2_old = cmap.beta_tx::<1>(t, base_dart2)?;
150 let (b1d1_new, b1d2_new) = new_darts;
151 let (vid1, vid2) = (
152 cmap.vertex_id_tx(t, base_dart1)?,
153 cmap.vertex_id_tx(t, base_dart2)?,
154 );
155 let (Some(v1), Some(v2)) = (cmap.read_vertex_tx(t, vid1)?, cmap.read_vertex_tx(t, vid2)?)
156 else {
157 abort(VertexInsertionError::UndefinedEdge)?
158 };
159 // unsew current darts
160 if b1d1_old != NULL_DART_ID {
161 try_or_coerce!(cmap.unlink_tx::<1>(t, base_dart1), VertexInsertionError);
162 }
163 if b1d2_old != NULL_DART_ID {
164 try_or_coerce!(cmap.unlink_tx::<1>(t, base_dart2), VertexInsertionError);
165 }
166 // cmap.set_beta::<1>(base_dart1, 0);
167 // cmap.set_beta::<0>(b1d1_old, 0);
168 // cmap.set_beta::<1>(base_dart2, 0);
169 // cmap.set_beta::<0>(b1d2_old, 0);
170 try_or_coerce!(cmap.unlink_tx::<2>(t, base_dart1), VertexInsertionError);
171 // rebuild the edge
172 try_or_coerce!(
173 cmap.link_tx::<1>(t, base_dart1, b1d1_new),
174 VertexInsertionError
175 );
176 if b1d1_old != NULL_DART_ID {
177 try_or_coerce!(
178 cmap.link_tx::<1>(t, b1d1_new, b1d1_old),
179 VertexInsertionError
180 );
181 }
182 try_or_coerce!(
183 cmap.link_tx::<1>(t, base_dart2, b1d2_new),
184 VertexInsertionError
185 );
186 if b1d2_old != NULL_DART_ID {
187 try_or_coerce!(
188 cmap.link_tx::<1>(t, b1d2_new, b1d2_old),
189 VertexInsertionError
190 );
191 }
192 try_or_coerce!(
193 cmap.link_tx::<2>(t, base_dart1, b1d2_new),
194 VertexInsertionError
195 );
196 try_or_coerce!(
197 cmap.link_tx::<2>(t, base_dart2, b1d1_new),
198 VertexInsertionError
199 );
200 // insert the new vertex
201 let seg = v2 - v1;
202 let vnew = cmap.vertex_id_tx(t, b1d1_new)?;
203 cmap.write_vertex_tx(
204 t,
205 vnew,
206 midpoint_vertex.map_or(Vertex2::average(&v1, &v2), |t| v1 + seg * t),
207 )?;
208 Ok(())
209 }
210}
211
212#[allow(clippy::missing_errors_doc)]
213/// Insert `n` vertices in an edge, cutting it into `n+1` segments.
214///
215/// <div class="warning">
216/// This implementation is 2D specific.
217/// </div>
218///
219/// # Arguments
220///
221/// - `cmap: &mut CMap2<T>` -- Reference to the modified map.
222/// - `t: &mut Transaction` -- Associated transaction.
223/// - `edge_id: EdgeIdentifier` -- Target edge.
224/// - `new_darts: &[DartIdentifier]` -- Dart IDs used to build the new vertices/segments.
225/// - `midpoint_vertices: &[T]` -- Relative positions of new vertices, starting from the
226/// vertex of the dart sharing `edge_id` as its identifier.
227///
228/// ## Dart IDs Requirements & Usage
229///
230/// Because of the dimension, we can easily compute the number of dart needed to perform this
231/// operation. These are the requirements for the darts:
232/// - identifiers are passed as a slice:
233/// - slice length should verify `new_darts.len() == 2 * midpoint_vertices.len()`
234/// - the first half of the slice will always be used if the operation is successful.
235/// - the second half of the slice will only be used if the original edge is made of two darts;
236/// if that is not the case, the second half IDs can all be `NULL_DART_ID`s.
237/// - all of these darts should be free
238///
239/// # Errors
240///
241/// This function will abort and raise an error if:
242/// - the transaction cannot be completed,
243/// - a hypothesis over input isn't verified (see [`VertexInsertionError`]),
244/// - an internal operation failed.
245///
246/// The returned error can be used in conjunction with transaction control to avoid any
247/// modifications in case of failure at attribute level. The user can then choose to retry or
248/// abort as he wishes using `Transaction::with_control_and_err`.
249///
250/// # Example
251///
252/// ```
253/// # use honeycomb_core::cmap::{CMap2, CMapBuilder, NULL_DART_ID};
254/// # use honeycomb_core::geometry::Vertex2;
255/// # use honeycomb_core::stm::atomically_with_err;
256/// # use honeycomb_kernels::cell_insertion::insert_vertices_on_edge;
257/// // before
258/// // <--2---
259/// // 1 2
260/// // ---1-->
261///
262/// let mut map: CMap2<_> = CMapBuilder::<2>::from_n_darts(2)
263/// .build()
264/// .unwrap();
265/// map.link::<2>(1, 2);
266/// map.write_vertex(1, (0.0, 0.0));
267/// map.write_vertex(2, (1.0, 0.0));
268///
269/// let nd = map.allocate_unused_darts(6);
270///
271/// // split
272/// assert!(
273/// atomically_with_err(|t| insert_vertices_on_edge(
274/// &map,
275/// t,
276/// 1,
277/// &[nd, nd + 1, nd + 2, nd + 3, nd + 4, nd + 5],
278/// &[0.25, 0.50, 0.75],
279/// )).is_ok()
280/// );
281///
282/// // after
283/// // <-<-<-<
284/// // 1 -3-4-5- 2
285/// // >->->->
286///
287/// // checks
288/// let new_darts = [
289/// map.beta::<1>(1),
290/// map.beta::<1>(map.beta::<1>(1)),
291/// map.beta::<1>(map.beta::<1>(map.beta::<1>(1))),
292/// ];
293/// assert_eq!(&new_darts, &[3, 4, 5]);
294/// assert_eq!(map.read_vertex(3), Some(Vertex2(0.25, 0.0)));
295/// assert_eq!(map.read_vertex(4), Some(Vertex2(0.50, 0.0)));
296/// assert_eq!(map.read_vertex(5), Some(Vertex2(0.75, 0.0)));
297///
298/// assert_eq!(map.beta::<1>(1), 3);
299/// assert_eq!(map.beta::<1>(3), 4);
300/// assert_eq!(map.beta::<1>(4), 5);
301/// assert_eq!(map.beta::<1>(5), NULL_DART_ID);
302///
303/// assert_eq!(map.beta::<1>(2), 6);
304/// assert_eq!(map.beta::<1>(6), 7);
305/// assert_eq!(map.beta::<1>(7), 8);
306/// assert_eq!(map.beta::<1>(8), NULL_DART_ID);
307///
308/// assert_eq!(map.beta::<2>(1), 8);
309/// assert_eq!(map.beta::<2>(3), 7);
310/// assert_eq!(map.beta::<2>(4), 6);
311/// assert_eq!(map.beta::<2>(5), 2);
312/// ```
313pub fn insert_vertices_on_edge<T: CoordsFloat>(
314 cmap: &CMap2<T>,
315 t: &mut Transaction,
316 edge_id: EdgeIdType,
317 new_darts: &[DartIdType],
318 midpoint_vertices: &[T],
319) -> TransactionClosureResult<(), VertexInsertionError> {
320 // check pre-allocated darts reqs
321 let n_t = midpoint_vertices.len();
322 let n_d = new_darts.len();
323 if n_d != 2 * n_t {
324 abort(VertexInsertionError::WrongAmountDarts(2 * n_t, n_d))?;
325 }
326 for d in new_darts {
327 if !cmap.is_free_tx(t, *d)? {
328 abort(VertexInsertionError::InvalidDarts("one dart is not free"))?;
329 }
330 }
331 // get the first and second halves
332 let darts_fh = &new_darts[..n_t];
333 let darts_sh = &new_darts[n_t..];
334
335 // base darts making up the edge
336 let base_dart1 = edge_id as DartIdType;
337 let base_dart2 = cmap.beta_tx::<2>(t, base_dart1)?;
338
339 if darts_fh.contains(&NULL_DART_ID) {
340 abort(VertexInsertionError::InvalidDarts(
341 "one dart of the first half is null",
342 ))?;
343 }
344 if base_dart2 != NULL_DART_ID && darts_sh.contains(&NULL_DART_ID) {
345 abort(VertexInsertionError::InvalidDarts(
346 "one dart of the second half is null",
347 ))?;
348 }
349
350 if midpoint_vertices
351 .iter()
352 .any(|p| (*p >= T::one()) | (*p <= T::zero()))
353 {
354 abort(VertexInsertionError::VertexBound)?;
355 }
356
357 let base_dart2 = cmap.beta_tx::<2>(t, base_dart1)?;
358 let b1d1_old = cmap.beta_tx::<1>(t, base_dart1)?;
359
360 let (vid1, vid2) = (
361 cmap.vertex_id_tx(t, base_dart1)?,
362 cmap.vertex_id_tx(
363 t,
364 if b1d1_old != NULL_DART_ID {
365 b1d1_old
366 } else if base_dart2 != NULL_DART_ID {
367 base_dart2
368 } else {
369 abort(VertexInsertionError::UndefinedEdge)?
370 },
371 )?,
372 );
373 let (Some(v1), Some(v2)) = (cmap.read_vertex_tx(t, vid1)?, cmap.read_vertex_tx(t, vid2)?)
374 else {
375 abort(VertexInsertionError::UndefinedEdge)?
376 };
377 let seg = v2 - v1;
378
379 // unsew current dart
380 if b1d1_old != NULL_DART_ID {
381 try_or_coerce!(cmap.unlink_tx::<1>(t, base_dart1), VertexInsertionError);
382 }
383 //
384 if base_dart2 != NULL_DART_ID {
385 try_or_coerce!(cmap.unlink_tx::<2>(t, base_dart1), VertexInsertionError);
386 }
387 // insert new vertices / darts on base_dart1's side
388 let mut prev_d = base_dart1;
389 for (&p, &new_d) in midpoint_vertices.iter().zip(darts_fh.iter()) {
390 let new_v = v1 + seg * p;
391 try_or_coerce!(cmap.link_tx::<1>(t, prev_d, new_d), VertexInsertionError);
392 cmap.write_vertex_tx(t, new_d, new_v)?;
393 prev_d = new_d;
394 }
395 try_or_coerce!(cmap.link_tx::<1>(t, prev_d, b1d1_old), VertexInsertionError);
396
397 // if b2(base_dart1) is defined, insert vertices / darts on its side too
398 if base_dart2 != NULL_DART_ID {
399 let b1d2_old = cmap.beta_tx::<1>(t, base_dart2)?;
400 if b1d2_old != NULL_DART_ID {
401 try_or_coerce!(cmap.unlink_tx::<1>(t, base_dart2), VertexInsertionError);
402 }
403 let mut prev_d = base_dart2;
404 for (d, new_d) in darts_fh.iter().rev().zip(darts_sh.iter()) {
405 try_or_coerce!(cmap.link_tx::<2>(t, prev_d, *d), VertexInsertionError);
406 try_or_coerce!(cmap.link_tx::<1>(t, prev_d, *new_d), VertexInsertionError);
407 prev_d = *new_d;
408 }
409 if b1d2_old != NULL_DART_ID {
410 try_or_coerce!(cmap.link_tx::<1>(t, prev_d, b1d2_old), VertexInsertionError);
411 }
412 try_or_coerce!(
413 cmap.link_tx::<2>(t, prev_d, base_dart1),
414 VertexInsertionError
415 );
416 }
417
418 Ok(())
419}