use honeycomb_core::{
attributes::{AttributeError, AttributeUpdate},
cmap::{
CMap2, DartIdType, DartReleaseError, EdgeIdType, LinkError, NULL_DART_ID, NULL_EDGE_ID,
NULL_VERTEX_ID, SewError, VertexIdType,
},
geometry::CoordsFloat,
stm::{Transaction, TransactionClosureResult, abort, try_or_coerce, unwrap_or_retry},
};
use crate::utils::{EdgeAnchor, FaceAnchor, VertexAnchor, is_orbit_orientation_consistent};
#[derive(thiserror::Error, Debug, PartialEq, Eq)]
pub enum EdgeCollapseError {
#[error("core operation failed: {0}")]
FailedCoreOp(#[from] SewError),
#[error("dart release failed: {0}")]
FailedDartRelease(#[from] DartReleaseError),
#[error("cannot collapse edge: {0}")]
NonCollapsibleEdge(&'static str),
#[error("collapsing would result in an inversion of geometry orientation")]
InvertedOrientation,
#[error("cannot collapse null edge")]
NullEdge,
#[error("cannot collapse an edge adjacent to a non-triangular cell")]
BadTopology,
}
impl From<LinkError> for EdgeCollapseError {
fn from(value: LinkError) -> Self {
Self::FailedCoreOp(SewError::FailedLink(value))
}
}
#[allow(clippy::missing_errors_doc)]
#[allow(clippy::many_single_char_names)]
pub fn collapse_edge<T: CoordsFloat>(
t: &mut Transaction,
map: &CMap2<T>,
e: EdgeIdType,
) -> TransactionClosureResult<VertexIdType, EdgeCollapseError> {
if e == NULL_EDGE_ID {
abort(EdgeCollapseError::NullEdge)?;
}
let (l, r) = (e as DartIdType, map.beta_tx::<2>(t, e as DartIdType)?);
if r == NULL_DART_ID {
abort(EdgeCollapseError::NonCollapsibleEdge(
"Boundary collapses are unimplemented",
))?;
}
let (b0l, b1l) = (map.beta_tx::<0>(t, l)?, map.beta_tx::<1>(t, l)?);
let (b0r, b1r) = (map.beta_tx::<0>(t, r)?, map.beta_tx::<1>(t, r)?);
if map.beta_tx::<1>(t, b1l)? != b0l {
abort(EdgeCollapseError::BadTopology)?;
}
if r != NULL_DART_ID && map.beta_tx::<1>(t, b1r)? != b0r {
abort(EdgeCollapseError::BadTopology)?;
}
let new_vid = match is_collapsible(t, map, e)? {
Collapsible::Average => try_or_coerce!(
collapse_edge_to_midpoint(t, map, (b0l, l, b1l), (b0r, r, b1r)),
EdgeCollapseError
),
Collapsible::Left => try_or_coerce!(
collapse_edge_to_base(t, map, (b0l, l, b1l), (b0r, r, b1r)),
EdgeCollapseError
),
Collapsible::Right => try_or_coerce!(
collapse_edge_to_base(t, map, (b0r, r, b1r), (b0l, l, b1l)),
EdgeCollapseError
),
};
if new_vid != NULL_VERTEX_ID && !is_orbit_orientation_consistent(t, map, new_vid)? {
abort(EdgeCollapseError::InvertedOrientation)?;
}
Ok(new_vid)
}
enum Collapsible {
Average,
Left,
Right,
}
fn is_collapsible<T: CoordsFloat>(
t: &mut Transaction,
map: &CMap2<T>,
e: EdgeIdType,
) -> TransactionClosureResult<Collapsible, EdgeCollapseError> {
if !map.contains_attribute::<VertexAnchor>() {
return Ok(Collapsible::Average);
}
let (l, b1l) = (e as DartIdType, map.beta_tx::<1>(t, e as DartIdType)?);
let (l_vid, r_vid) = (map.vertex_id_tx(t, l)?, map.vertex_id_tx(t, b1l)?);
let (l_anchor, r_anchor, edge_anchor) = (
unwrap_or_retry(map.read_attribute_tx::<VertexAnchor>(t, l_vid)?)?,
unwrap_or_retry(map.read_attribute_tx::<VertexAnchor>(t, r_vid)?)?,
unwrap_or_retry(map.read_attribute_tx::<EdgeAnchor>(t, e)?)?,
);
match AttributeUpdate::merge(l_anchor, r_anchor) {
Ok(val) => {
if edge_anchor.anchor_dim() == l_anchor.anchor_dim()
|| edge_anchor.anchor_dim() == r_anchor.anchor_dim()
{
match (val == l_anchor, val == r_anchor) {
(true, true) => Ok(Collapsible::Average),
(true, false) => Ok(Collapsible::Left),
(false, true) => Ok(Collapsible::Right),
(false, false) => unreachable!(),
}
} else {
abort(EdgeCollapseError::NonCollapsibleEdge(
"collapsing along this edge is impossible",
))
}
}
Err(AttributeError::FailedMerge(_, _)) => abort(EdgeCollapseError::NonCollapsibleEdge(
"vertex have incompatible anchors",
)),
Err(AttributeError::FailedSplit(_, _) | AttributeError::InsufficientData(_, _)) => {
unreachable!();
}
}
}
fn collapse_edge_to_midpoint<T: CoordsFloat>(
t: &mut Transaction,
map: &CMap2<T>,
(b0l, l, b1l): (DartIdType, DartIdType, DartIdType),
(b0r, r, b1r): (DartIdType, DartIdType, DartIdType),
) -> TransactionClosureResult<VertexIdType, EdgeCollapseError> {
let b2b1r = map.beta_tx::<2>(t, b1r)?;
let b1b2b1r = map.beta_tx::<1>(t, b2b1r)?;
if r != NULL_DART_ID {
try_or_coerce!(map.unsew_tx::<2>(t, r), EdgeCollapseError);
collapse_halfcell_to_midpoint(t, map, (b0r, r, b1r))?;
}
let b2b0l = map.beta_tx::<2>(t, b0l)?; collapse_halfcell_to_midpoint(t, map, (b0l, l, b1l))?;
Ok(if b2b0l != NULL_DART_ID {
map.vertex_id_tx(t, b2b0l)?
} else if r != NULL_DART_ID {
map.vertex_id_tx(t, b1b2b1r)?
} else {
NULL_VERTEX_ID
})
}
fn collapse_halfcell_to_midpoint<T: CoordsFloat>(
t: &mut Transaction,
map: &CMap2<T>,
(b0d, d, b1d): (DartIdType, DartIdType, DartIdType),
) -> TransactionClosureResult<(), EdgeCollapseError> {
try_or_coerce!(map.unsew_tx::<1>(t, d), EdgeCollapseError);
try_or_coerce!(map.unsew_tx::<1>(t, b1d), EdgeCollapseError);
try_or_coerce!(map.unsew_tx::<1>(t, b0d), EdgeCollapseError);
let (b2b0d, b2b1d) = (map.beta_tx::<2>(t, b0d)?, map.beta_tx::<2>(t, b1d)?);
match (b2b0d == NULL_DART_ID, b2b1d == NULL_DART_ID) {
(false, false) => {
try_or_coerce!(map.unsew_tx::<2>(t, b0d), EdgeCollapseError);
try_or_coerce!(map.unsew_tx::<2>(t, b1d), EdgeCollapseError);
try_or_coerce!(map.sew_tx::<2>(t, b2b0d, b2b1d), EdgeCollapseError);
}
(true, false) => {
try_or_coerce!(map.unsew_tx::<2>(t, b1d), EdgeCollapseError);
}
(false, true) => {
try_or_coerce!(map.unsew_tx::<2>(t, b0d), EdgeCollapseError);
}
(true, true) => {}
}
try_or_coerce!(map.release_dart_tx(t, d), EdgeCollapseError);
try_or_coerce!(map.release_dart_tx(t, b0d), EdgeCollapseError);
try_or_coerce!(map.release_dart_tx(t, b1d), EdgeCollapseError);
TransactionClosureResult::Ok(())
}
fn collapse_edge_to_base<T: CoordsFloat>(
t: &mut Transaction,
map: &CMap2<T>,
(b0l, l, b1l): (DartIdType, DartIdType, DartIdType), (b0r, r, b1r): (DartIdType, DartIdType, DartIdType),
) -> TransactionClosureResult<VertexIdType, EdgeCollapseError> {
let b2b1l = map.beta_tx::<2>(t, b1l)?;
let b2b0r = map.beta_tx::<2>(t, b0r)?;
let l_vid = map.vertex_id_tx(t, l)?;
let l_fid = map.face_id_tx(t, b2b1l)?;
let r_fid = map.face_id_tx(t, b2b0r)?;
let tmp_vertex = map.read_vertex_tx(t, l_vid)?;
let tmp_anchor = map.read_attribute_tx::<VertexAnchor>(t, l_vid)?;
let l_face_anchor = map.read_attribute_tx::<FaceAnchor>(t, l_fid)?;
let r_face_anchor = map.read_attribute_tx::<FaceAnchor>(t, r_fid)?;
if r != NULL_DART_ID {
try_or_coerce!(map.unsew_tx::<2>(t, l), EdgeCollapseError);
try_or_coerce!(
collapse_halfcell_to_base(t, map, (b1r, r, b0r)),
EdgeCollapseError
);
}
let b2b0l = map.beta_tx::<2>(t, b0l)?; try_or_coerce!(
collapse_halfcell_to_base(t, map, (b0l, l, b1l)),
EdgeCollapseError
);
let new_vid = if b2b0l != NULL_DART_ID {
map.vertex_id_tx(t, b2b0l)?
} else if r != NULL_DART_ID {
map.vertex_id_tx(t, b1r)?
} else {
NULL_VERTEX_ID
};
if new_vid != NULL_VERTEX_ID {
if let Some(v) = tmp_vertex {
map.write_vertex_tx(t, new_vid, v)?;
} if let Some(a) = tmp_anchor {
map.write_attribute_tx(t, new_vid, a)?;
}
}
if let Some(f_a) = l_face_anchor
&& !map.is_unused_tx(t, b0l)?
{
let new_fid = map.face_id_tx(t, b0l)?;
map.write_attribute_tx(t, new_fid, f_a)?;
}
if let Some(f_a) = r_face_anchor {
let new_fid = map.face_id_tx(t, b1r)?;
map.write_attribute_tx(t, new_fid, f_a)?;
}
Ok(new_vid)
}
fn collapse_halfcell_to_base<T: CoordsFloat>(
t: &mut Transaction,
map: &CMap2<T>,
(d_pe, d_e, d_ne): (DartIdType, DartIdType, DartIdType),
) -> TransactionClosureResult<(), EdgeCollapseError> {
let b2d_ne = map.beta_tx::<2>(t, d_ne)?;
let b0b2d_ne = map.beta_tx::<0>(t, b2d_ne)?;
let b1b2d_ne = map.beta_tx::<1>(t, b2d_ne)?;
try_or_coerce!(map.unsew_tx::<1>(t, d_e), EdgeCollapseError);
try_or_coerce!(map.unsew_tx::<1>(t, d_pe), EdgeCollapseError);
try_or_coerce!(map.unsew_tx::<1>(t, d_ne), EdgeCollapseError);
if b2d_ne == NULL_DART_ID {
try_or_coerce!(map.unsew_tx::<2>(t, d_pe), EdgeCollapseError);
try_or_coerce!(map.release_dart_tx(t, d_pe), EdgeCollapseError);
} else {
try_or_coerce!(map.unsew_tx::<1>(t, b2d_ne), EdgeCollapseError);
try_or_coerce!(map.unsew_tx::<1>(t, b0b2d_ne), EdgeCollapseError);
try_or_coerce!(map.unlink_tx::<2>(t, d_ne), EdgeCollapseError);
try_or_coerce!(map.release_dart_tx(t, b2d_ne), EdgeCollapseError);
try_or_coerce!(map.sew_tx::<1>(t, d_pe, b1b2d_ne), EdgeCollapseError);
try_or_coerce!(map.sew_tx::<1>(t, b0b2d_ne, d_pe), EdgeCollapseError);
}
try_or_coerce!(map.release_dart_tx(t, d_e), EdgeCollapseError);
try_or_coerce!(map.release_dart_tx(t, d_ne), EdgeCollapseError);
Ok(())
}