honeycomb_kernels/remeshing/
swap.rs

1use honeycomb_core::{
2    cmap::{CMap2, DartIdType, EdgeIdType, NULL_DART_ID, NULL_EDGE_ID, SewError},
3    geometry::CoordsFloat,
4    stm::{Transaction, TransactionClosureResult, abort, try_or_coerce},
5};
6
7use crate::utils::{FaceAnchor, VertexAnchor};
8
9/// Error-modeling enum for edge swap routine.
10#[derive(thiserror::Error, Debug, PartialEq, Eq)]
11pub enum EdgeSwapError {
12    /// A core operation failed.
13    #[error("core operation failed: {0}")]
14    FailedCoreOp(#[from] SewError),
15    /// The edge cannot be swapped due to geometrical or anchoring constraints.
16    #[error("cannot swap edge due to constraints: {0}")]
17    NotSwappable(&'static str),
18    /// The edge passed as argument is null.
19    #[error("cannot swap null edge")]
20    NullEdge,
21    /// The edge passed as argument is made of single dart, hence doesn't have a cell on each side.
22    #[error("cannot swap an edge adjacent to a single cell")]
23    IncompleteEdge,
24    /// One or both of the cells adjacent to the edge are not triangles.
25    #[error("cannot swap an edge adjacent to a non-triangular cell")]
26    BadTopology,
27}
28
29/// Tip over an edge shared by two triangles.
30///
31/// The edge is tipped in the clockwise direction. Vertices that were shared become exclusive
32/// to each new triangle and vice versa:
33///
34/// ```text
35///
36///       +                   +
37///      / \                 /|\
38///     /   \               / | \
39///    /     \             /  |  \
40///   /       \           /   |   \
41///  /         \         /    |    \
42/// +-----------+  -->  +     |     +
43///  \    e    /         \    |    /
44///   \       /           \   |   /
45///    \     /             \  |  /
46///     \   /               \ | /
47///      \ /                 \|/
48///       +                   +
49///
50/// ```
51///
52/// This function expects to operate on a triangular mesh. At the moment, calling it on another type
53/// of mesh may result in non-explicit errors (e.g. an internal sew operation will consistently fail
54/// due to a dart being non-free) as there is no check on each faces' degree.
55///
56/// # Arguments
57///
58/// - `t: &mut Transaction` -- Associated transaction.
59/// - `map: &mut CMap2` -- Edited map.
60/// - `e: EdgeIdType` -- Edge to move.
61///
62/// # Panics
63///
64/// This function will panic if there is no cell on one side of the edge.
65///
66/// # Errors
67///
68/// This function will abort and raise an error if:
69/// - the transaction cannot be completed,
70/// - one internal sew operation fails.
71///
72/// The returned error can be used in conjunction with transaction control to avoid any
73/// modifications in case of failure at attribute level. The user can then choose to retry or
74/// abort as he wishes using `Transaction::with_control_and_err`.
75#[inline]
76pub fn swap_edge<T: CoordsFloat>(
77    t: &mut Transaction,
78    map: &CMap2<T>,
79    e: EdgeIdType,
80) -> TransactionClosureResult<(), EdgeSwapError> {
81    if e == NULL_EDGE_ID {
82        abort(EdgeSwapError::NullEdge)?;
83    }
84    let (l, r) = (e as DartIdType, map.beta_transac::<2>(t, e as DartIdType)?);
85    if r == NULL_DART_ID {
86        abort(EdgeSwapError::IncompleteEdge)?;
87    }
88    let (l_a, r_a) = if map.contains_attribute::<FaceAnchor>() {
89        let l_fid = map.face_id_transac(t, l)?;
90        let r_fid = map.face_id_transac(t, r)?;
91        let l_a = map.remove_attribute::<FaceAnchor>(t, l_fid)?;
92        let r_a = map.remove_attribute::<FaceAnchor>(t, r_fid)?;
93        if l_a != r_a {
94            abort(EdgeSwapError::NotSwappable(
95                "edge separates two distinct surfaces",
96            ))?;
97        }
98        (l_a, r_a)
99    } else {
100        (None, None)
101    };
102
103    let (b1l, b1r) = (map.beta_transac::<1>(t, l)?, map.beta_transac::<1>(t, r)?);
104    let (b0l, b0r) = (map.beta_transac::<0>(t, l)?, map.beta_transac::<0>(t, r)?);
105    if map.beta_transac::<1>(t, b1l)? != b0l || map.beta_transac::<1>(t, b1r)? != b0r {
106        abort(EdgeSwapError::BadTopology)?;
107    }
108
109    try_or_coerce!(map.unsew::<1>(t, l), EdgeSwapError);
110    try_or_coerce!(map.unsew::<1>(t, r), EdgeSwapError);
111    try_or_coerce!(map.unsew::<1>(t, b0l), EdgeSwapError);
112    try_or_coerce!(map.unsew::<1>(t, b0r), EdgeSwapError);
113    try_or_coerce!(map.unsew::<1>(t, b1l), EdgeSwapError);
114    try_or_coerce!(map.unsew::<1>(t, b1r), EdgeSwapError);
115
116    // remove vertex attributes to keep existing values unchanged
117    let l_vid = map.vertex_id_transac(t, l)?;
118    let r_vid = map.vertex_id_transac(t, r)?;
119    let _ = map.remove_vertex(t, l_vid)?;
120    let _ = map.remove_vertex(t, r_vid)?;
121    if map.contains_attribute::<VertexAnchor>() {
122        map.remove_attribute::<VertexAnchor>(t, l_vid)?;
123        map.remove_attribute::<VertexAnchor>(t, r_vid)?;
124    }
125
126    try_or_coerce!(map.sew::<1>(t, l, b0r), EdgeSwapError);
127    try_or_coerce!(map.sew::<1>(t, b0r, b1l), EdgeSwapError);
128    try_or_coerce!(map.sew::<1>(t, b1l, l), EdgeSwapError);
129    try_or_coerce!(map.sew::<1>(t, r, b0l), EdgeSwapError);
130    try_or_coerce!(map.sew::<1>(t, b0l, b1r), EdgeSwapError);
131    try_or_coerce!(map.sew::<1>(t, b1r, r), EdgeSwapError);
132
133    // update anchors
134    match (l_a, r_a) {
135        (Some(l_a), Some(r_a)) => {
136            let l_fid = map.face_id_transac(t, l)?;
137            let r_fid = map.face_id_transac(t, r)?;
138            map.write_attribute(t, l_fid, l_a)?;
139            map.write_attribute(t, r_fid, r_a)?;
140        }
141        (Some(_), None) | (None, Some(_)) => unreachable!(),
142        (None, None) => {}
143    }
144
145    Ok(())
146}