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(t, vid1)?, cmap.read_vertex(t, vid2)?) else {
122 abort(VertexInsertionError::UndefinedEdge)?
123 };
124 // unsew current dart
125 if b1d1_old != NULL_DART_ID {
126 try_or_coerce!(cmap.unlink::<1>(t, base_dart1), VertexInsertionError);
127 }
128 // rebuild the edge
129 try_or_coerce!(
130 cmap.link::<1>(t, base_dart1, b1d1_new),
131 VertexInsertionError
132 );
133 try_or_coerce!(cmap.link::<1>(t, b1d1_new, b1d1_old), VertexInsertionError);
134 // insert the new vertex
135 let seg = v2 - v1;
136 let vnew = cmap.vertex_id_tx(t, b1d1_new)?;
137 cmap.write_vertex(
138 t,
139 vnew,
140 midpoint_vertex.map_or(Vertex2::average(&v1, &v2), |t| v1 + seg * t),
141 )?;
142 Ok(())
143 } else {
144 let b1d1_old = cmap.beta_tx::<1>(t, base_dart1)?;
145 let b1d2_old = cmap.beta_tx::<1>(t, base_dart2)?;
146 let (b1d1_new, b1d2_new) = new_darts;
147 let (vid1, vid2) = (
148 cmap.vertex_id_tx(t, base_dart1)?,
149 cmap.vertex_id_tx(t, base_dart2)?,
150 );
151 let (Some(v1), Some(v2)) = (cmap.read_vertex(t, vid1)?, cmap.read_vertex(t, vid2)?) else {
152 abort(VertexInsertionError::UndefinedEdge)?
153 };
154 // unsew current darts
155 if b1d1_old != NULL_DART_ID {
156 try_or_coerce!(cmap.unlink::<1>(t, base_dart1), VertexInsertionError);
157 }
158 if b1d2_old != NULL_DART_ID {
159 try_or_coerce!(cmap.unlink::<1>(t, base_dart2), VertexInsertionError);
160 }
161 // cmap.set_beta::<1>(base_dart1, 0);
162 // cmap.set_beta::<0>(b1d1_old, 0);
163 // cmap.set_beta::<1>(base_dart2, 0);
164 // cmap.set_beta::<0>(b1d2_old, 0);
165 try_or_coerce!(cmap.unlink::<2>(t, base_dart1), VertexInsertionError);
166 // rebuild the edge
167 try_or_coerce!(
168 cmap.link::<1>(t, base_dart1, b1d1_new),
169 VertexInsertionError
170 );
171 if b1d1_old != NULL_DART_ID {
172 try_or_coerce!(cmap.link::<1>(t, b1d1_new, b1d1_old), VertexInsertionError);
173 }
174 try_or_coerce!(
175 cmap.link::<1>(t, base_dart2, b1d2_new),
176 VertexInsertionError
177 );
178 if b1d2_old != NULL_DART_ID {
179 try_or_coerce!(cmap.link::<1>(t, b1d2_new, b1d2_old), VertexInsertionError);
180 }
181 try_or_coerce!(
182 cmap.link::<2>(t, base_dart1, b1d2_new),
183 VertexInsertionError
184 );
185 try_or_coerce!(
186 cmap.link::<2>(t, base_dart2, b1d1_new),
187 VertexInsertionError
188 );
189 // insert the new vertex
190 let seg = v2 - v1;
191 let vnew = cmap.vertex_id_tx(t, b1d1_new)?;
192 cmap.write_vertex(
193 t,
194 vnew,
195 midpoint_vertex.map_or(Vertex2::average(&v1, &v2), |t| v1 + seg * t),
196 )?;
197 Ok(())
198 }
199}
200
201#[allow(clippy::missing_errors_doc)]
202/// Insert `n` vertices in an edge, cutting it into `n+1` segments.
203///
204/// <div class="warning">
205/// This implementation is 2D specific.
206/// </div>
207///
208/// # Arguments
209///
210/// - `cmap: &mut CMap2<T>` -- Reference to the modified map.
211/// - `t: &mut Transaction` -- Associated transaction.
212/// - `edge_id: EdgeIdentifier` -- Target edge.
213/// - `new_darts: &[DartIdentifier]` -- Dart IDs used to build the new vertices/segments.
214/// - `midpoint_vertices: &[T]` -- Relative positions of new vertices, starting from the
215/// vertex of the dart sharing `edge_id` as its identifier.
216///
217/// ## Dart IDs Requirements & Usage
218///
219/// Because of the dimension, we can easily compute the number of dart needed to perform this
220/// operation. These are the requirements for the darts:
221/// - identifiers are passed as a slice:
222/// - slice length should verify `new_darts.len() == 2 * midpoint_vertices.len()`
223/// - the first half of the slice will always be used if the operation is successful.
224/// - the second half of the slice will only be used if the original edge is made of two darts;
225/// if that is not the case, the second half IDs can all be `NULL_DART_ID`s.
226/// - all of these darts should be free
227///
228/// # Errors
229///
230/// This function will abort and raise an error if:
231/// - the transaction cannot be completed,
232/// - a hypothesis over input isn't verified (see [`VertexInsertionError`]),
233/// - an internal operation failed.
234///
235/// The returned error can be used in conjunction with transaction control to avoid any
236/// modifications in case of failure at attribute level. The user can then choose to retry or
237/// abort as he wishes using `Transaction::with_control_and_err`.
238///
239/// # Example
240///
241/// ```
242/// # use honeycomb_core::cmap::{CMap2, CMapBuilder, NULL_DART_ID};
243/// # use honeycomb_core::geometry::Vertex2;
244/// # use honeycomb_core::stm::atomically_with_err;
245/// # use honeycomb_kernels::cell_insertion::insert_vertices_on_edge;
246/// // before
247/// // <--2---
248/// // 1 2
249/// // ---1-->
250///
251/// let mut map: CMap2<_> = CMapBuilder::<2, _>::from_n_darts(2)
252/// .build()
253/// .unwrap();
254/// map.force_link::<2>(1, 2);
255/// map.force_write_vertex(1, (0.0, 0.0));
256/// map.force_write_vertex(2, (1.0, 0.0));
257///
258/// let nd = map.allocate_unused_darts(6);
259///
260/// // split
261/// assert!(
262/// atomically_with_err(|t| insert_vertices_on_edge(
263/// &map,
264/// t,
265/// 1,
266/// &[nd, nd + 1, nd + 2, nd + 3, nd + 4, nd + 5],
267/// &[0.25, 0.50, 0.75],
268/// )).is_ok()
269/// );
270///
271/// // after
272/// // <-<-<-<
273/// // 1 -3-4-5- 2
274/// // >->->->
275///
276/// // checks
277/// let new_darts = [
278/// map.beta::<1>(1),
279/// map.beta::<1>(map.beta::<1>(1)),
280/// map.beta::<1>(map.beta::<1>(map.beta::<1>(1))),
281/// ];
282/// assert_eq!(&new_darts, &[3, 4, 5]);
283/// assert_eq!(map.force_read_vertex(3), Some(Vertex2(0.25, 0.0)));
284/// assert_eq!(map.force_read_vertex(4), Some(Vertex2(0.50, 0.0)));
285/// assert_eq!(map.force_read_vertex(5), Some(Vertex2(0.75, 0.0)));
286///
287/// assert_eq!(map.beta::<1>(1), 3);
288/// assert_eq!(map.beta::<1>(3), 4);
289/// assert_eq!(map.beta::<1>(4), 5);
290/// assert_eq!(map.beta::<1>(5), NULL_DART_ID);
291///
292/// assert_eq!(map.beta::<1>(2), 6);
293/// assert_eq!(map.beta::<1>(6), 7);
294/// assert_eq!(map.beta::<1>(7), 8);
295/// assert_eq!(map.beta::<1>(8), NULL_DART_ID);
296///
297/// assert_eq!(map.beta::<2>(1), 8);
298/// assert_eq!(map.beta::<2>(3), 7);
299/// assert_eq!(map.beta::<2>(4), 6);
300/// assert_eq!(map.beta::<2>(5), 2);
301/// ```
302pub fn insert_vertices_on_edge<T: CoordsFloat>(
303 cmap: &CMap2<T>,
304 t: &mut Transaction,
305 edge_id: EdgeIdType,
306 new_darts: &[DartIdType],
307 midpoint_vertices: &[T],
308) -> TransactionClosureResult<(), VertexInsertionError> {
309 // check pre-allocated darts reqs
310 let n_t = midpoint_vertices.len();
311 let n_d = new_darts.len();
312 if n_d != 2 * n_t {
313 abort(VertexInsertionError::WrongAmountDarts(2 * n_t, n_d))?;
314 }
315 for d in new_darts {
316 if !cmap.is_free_tx(t, *d)? {
317 abort(VertexInsertionError::InvalidDarts("one dart is not free"))?;
318 }
319 }
320 // get the first and second halves
321 let darts_fh = &new_darts[..n_t];
322 let darts_sh = &new_darts[n_t..];
323
324 // base darts making up the edge
325 let base_dart1 = edge_id as DartIdType;
326 let base_dart2 = cmap.beta_tx::<2>(t, base_dart1)?;
327
328 if darts_fh.contains(&NULL_DART_ID) {
329 abort(VertexInsertionError::InvalidDarts(
330 "one dart of the first half is null",
331 ))?;
332 }
333 if base_dart2 != NULL_DART_ID && darts_sh.contains(&NULL_DART_ID) {
334 abort(VertexInsertionError::InvalidDarts(
335 "one dart of the second half is null",
336 ))?;
337 }
338
339 if midpoint_vertices
340 .iter()
341 .any(|p| (*p >= T::one()) | (*p <= T::zero()))
342 {
343 abort(VertexInsertionError::VertexBound)?;
344 }
345
346 let base_dart2 = cmap.beta_tx::<2>(t, base_dart1)?;
347 let b1d1_old = cmap.beta_tx::<1>(t, base_dart1)?;
348
349 let (vid1, vid2) = (
350 cmap.vertex_id_tx(t, base_dart1)?,
351 cmap.vertex_id_tx(
352 t,
353 if b1d1_old != NULL_DART_ID {
354 b1d1_old
355 } else if base_dart2 != NULL_DART_ID {
356 base_dart2
357 } else {
358 abort(VertexInsertionError::UndefinedEdge)?
359 },
360 )?,
361 );
362 let (Some(v1), Some(v2)) = (cmap.read_vertex(t, vid1)?, cmap.read_vertex(t, vid2)?) else {
363 abort(VertexInsertionError::UndefinedEdge)?
364 };
365 let seg = v2 - v1;
366
367 // unsew current dart
368 if b1d1_old != NULL_DART_ID {
369 try_or_coerce!(cmap.unlink::<1>(t, base_dart1), VertexInsertionError);
370 }
371 //
372 if base_dart2 != NULL_DART_ID {
373 try_or_coerce!(cmap.unlink::<2>(t, base_dart1), VertexInsertionError);
374 }
375 // insert new vertices / darts on base_dart1's side
376 let mut prev_d = base_dart1;
377 for (&p, &new_d) in midpoint_vertices.iter().zip(darts_fh.iter()) {
378 let new_v = v1 + seg * p;
379 try_or_coerce!(cmap.link::<1>(t, prev_d, new_d), VertexInsertionError);
380 cmap.write_vertex(t, new_d, new_v)?;
381 prev_d = new_d;
382 }
383 try_or_coerce!(cmap.link::<1>(t, prev_d, b1d1_old), VertexInsertionError);
384
385 // if b2(base_dart1) is defined, insert vertices / darts on its side too
386 if base_dart2 != NULL_DART_ID {
387 let b1d2_old = cmap.beta_tx::<1>(t, base_dart2)?;
388 if b1d2_old != NULL_DART_ID {
389 try_or_coerce!(cmap.unlink::<1>(t, base_dart2), VertexInsertionError);
390 }
391 let mut prev_d = base_dart2;
392 for (d, new_d) in darts_fh.iter().rev().zip(darts_sh.iter()) {
393 try_or_coerce!(cmap.link::<2>(t, prev_d, *d), VertexInsertionError);
394 try_or_coerce!(cmap.link::<1>(t, prev_d, *new_d), VertexInsertionError);
395 prev_d = *new_d;
396 }
397 if b1d2_old != NULL_DART_ID {
398 try_or_coerce!(cmap.link::<1>(t, prev_d, b1d2_old), VertexInsertionError);
399 }
400 try_or_coerce!(cmap.link::<2>(t, prev_d, base_dart1), VertexInsertionError);
401 }
402
403 Ok(())
404}