Skip to main content

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}