use honeycomb_core::{
attributes::{AttrSparseVec, AttributeStorage, UnknownAttributeStorage},
cmap::{CMap2, CMapBuilder, DartIdType, NULL_DART_ID, OrbitPolicy},
stm::{atomically, atomically_with_err},
};
use rustc_hash::FxHashSet as HashSet;
use crate::{
grisubal::Clip,
remeshing::{
ClassificationError, EdgeSwapError, capture_geometry, classify_capture,
collapse::EdgeCollapseError, collapse_edge, swap_edge,
},
utils::{EdgeAnchor, FaceAnchor, VertexAnchor},
};
#[cfg(test)]
mod anchors {
use super::*;
#[test]
fn merge_vertex_eq_dim() {
let storage: AttrSparseVec<VertexAnchor> = AttrSparseVec::new(13);
atomically(|t| {
storage.write(t, 1, VertexAnchor::Node(1))?;
storage.write(t, 2, VertexAnchor::Node(1))?;
storage.write(t, 3, VertexAnchor::Node(2))?;
storage.write(t, 4, VertexAnchor::Curve(1))?;
storage.write(t, 5, VertexAnchor::Curve(1))?;
storage.write(t, 6, VertexAnchor::Curve(2))?;
storage.write(t, 7, VertexAnchor::Surface(3))?;
storage.write(t, 8, VertexAnchor::Surface(3))?;
storage.write(t, 9, VertexAnchor::Surface(4))?;
storage.write(t, 10, VertexAnchor::Body(5))?;
storage.write(t, 11, VertexAnchor::Body(6))?;
storage.write(t, 12, VertexAnchor::Body(6))?;
Ok(())
});
assert!(atomically_with_err(|t| storage.merge(t, 1, 1, 2)).is_ok());
assert!(atomically(|t| storage.read(t, 1)).is_some());
assert!(atomically(|t| storage.read(t, 2)).is_none());
assert!(atomically_with_err(|t| storage.merge(t, 1, 1, 3)).is_err());
assert!(atomically(|t| storage.read(t, 3)).is_some());
assert!(atomically_with_err(|t| storage.merge(t, 4, 4, 5)).is_ok());
assert!(atomically(|t| storage.read(t, 4)).is_some());
assert!(atomically(|t| storage.read(t, 5)).is_none());
assert!(atomically_with_err(|t| storage.merge(t, 6, 4, 6)).is_err());
assert!(atomically(|t| storage.read(t, 6)).is_some());
assert!(atomically_with_err(|t| storage.merge(t, 7, 7, 8)).is_ok());
assert!(atomically(|t| storage.read(t, 7)).is_some());
assert!(atomically(|t| storage.read(t, 8)).is_none());
assert!(atomically_with_err(|t| storage.merge(t, 7, 7, 9)).is_err());
assert!(atomically(|t| storage.read(t, 9)).is_some());
assert!(atomically_with_err(|t| storage.merge(t, 11, 12, 11)).is_ok());
assert!(atomically(|t| storage.read(t, 11)).is_some());
assert!(atomically(|t| storage.read(t, 12)).is_none());
assert!(atomically_with_err(|t| storage.merge(t, 10, 10, 11)).is_err());
assert!(atomically(|t| storage.read(t, 11)).is_some());
}
#[test]
fn merge_vertex_diff_dim() {
let storage: AttrSparseVec<VertexAnchor> = AttrSparseVec::new(11);
atomically(|t| {
storage.write(t, 1, VertexAnchor::Node(1))?;
storage.write(t, 2, VertexAnchor::Curve(2))?;
storage.write(t, 3, VertexAnchor::Surface(3))?;
storage.write(t, 4, VertexAnchor::Body(4))?;
storage.write(t, 5, VertexAnchor::Body(5))?;
storage.write(t, 6, VertexAnchor::Node(10))?;
storage.write(t, 7, VertexAnchor::Curve(9))?;
storage.write(t, 8, VertexAnchor::Surface(8))?;
storage.write(t, 9, VertexAnchor::Body(7))?;
storage.write(t, 10, VertexAnchor::Body(6))?;
Ok(())
});
assert!(atomically_with_err(|t| storage.merge(t, 3, 4, 3)).is_ok());
assert!(atomically(|t| storage.read(t, 3)).is_some_and(|v| v == VertexAnchor::Surface(3)));
assert!(atomically_with_err(|t| storage.merge(t, 2, 3, 2)).is_ok());
assert!(atomically(|t| storage.read(t, 2)).is_some_and(|v| v == VertexAnchor::Curve(2)));
assert!(atomically_with_err(|t| storage.merge(t, 1, 2, 1)).is_ok());
assert!(atomically(|t| storage.read(t, 1)).is_some_and(|v| v == VertexAnchor::Node(1)));
assert!(atomically_with_err(|t| storage.merge(t, 5, 1, 5)).is_ok());
assert!(atomically(|t| storage.read(t, 5)).is_some_and(|v| v == VertexAnchor::Node(1)));
assert!(atomically_with_err(|t| storage.merge(t, 6, 10, 6)).is_ok());
assert!(atomically(|t| storage.read(t, 6)).is_some_and(|v| v == VertexAnchor::Node(10)));
assert!(atomically_with_err(|t| storage.merge(t, 8, 8, 9)).is_ok());
assert!(atomically(|t| storage.read(t, 8)).is_some_and(|v| v == VertexAnchor::Surface(8)));
assert!(atomically_with_err(|t| storage.merge(t, 7, 7, 8)).is_ok());
assert!(atomically(|t| storage.read(t, 7)).is_some_and(|v| v == VertexAnchor::Curve(9)));
assert!(atomically_with_err(|t| storage.merge(t, 6, 6, 7)).is_ok());
assert!(atomically(|t| storage.read(t, 6)).is_some_and(|v| v == VertexAnchor::Node(10)));
}
#[test]
fn merge_edge_eq_dim() {
let storage: AttrSparseVec<EdgeAnchor> = AttrSparseVec::new(13);
atomically(|t| {
storage.write(t, 4, EdgeAnchor::Curve(1))?;
storage.write(t, 5, EdgeAnchor::Curve(1))?;
storage.write(t, 6, EdgeAnchor::Curve(2))?;
storage.write(t, 7, EdgeAnchor::Surface(3))?;
storage.write(t, 8, EdgeAnchor::Surface(3))?;
storage.write(t, 9, EdgeAnchor::Surface(4))?;
storage.write(t, 10, EdgeAnchor::Body(5))?;
storage.write(t, 11, EdgeAnchor::Body(6))?;
storage.write(t, 12, EdgeAnchor::Body(6))?;
Ok(())
});
assert!(atomically_with_err(|t| storage.merge(t, 4, 4, 5)).is_ok());
assert!(atomically(|t| storage.read(t, 4)).is_some());
assert!(atomically(|t| storage.read(t, 5)).is_none());
assert!(atomically_with_err(|t| storage.merge(t, 6, 4, 6)).is_err());
assert!(atomically(|t| storage.read(t, 6)).is_some());
assert!(atomically_with_err(|t| storage.merge(t, 7, 7, 8)).is_ok());
assert!(atomically(|t| storage.read(t, 7)).is_some());
assert!(atomically(|t| storage.read(t, 8)).is_none());
assert!(atomically_with_err(|t| storage.merge(t, 7, 7, 9)).is_err());
assert!(atomically(|t| storage.read(t, 9)).is_some());
assert!(atomically_with_err(|t| storage.merge(t, 11, 12, 11)).is_ok());
assert!(atomically(|t| storage.read(t, 11)).is_some());
assert!(atomically(|t| storage.read(t, 12)).is_none());
assert!(atomically_with_err(|t| storage.merge(t, 10, 10, 11)).is_err());
assert!(atomically(|t| storage.read(t, 11)).is_some());
}
#[test]
fn merge_edge_diff_dim() {
let storage: AttrSparseVec<EdgeAnchor> = AttrSparseVec::new(11);
atomically(|t| {
storage.write(t, 2, EdgeAnchor::Curve(2))?;
storage.write(t, 3, EdgeAnchor::Surface(3))?;
storage.write(t, 4, EdgeAnchor::Body(4))?;
storage.write(t, 5, EdgeAnchor::Body(5))?;
storage.write(t, 7, EdgeAnchor::Curve(9))?;
storage.write(t, 8, EdgeAnchor::Surface(8))?;
storage.write(t, 9, EdgeAnchor::Body(7))?;
storage.write(t, 10, EdgeAnchor::Body(6))?;
Ok(())
});
assert!(atomically_with_err(|t| storage.merge(t, 3, 4, 3)).is_ok());
assert!(atomically(|t| storage.read(t, 3)).is_some_and(|v| v == EdgeAnchor::Surface(3)));
assert!(atomically_with_err(|t| storage.merge(t, 2, 3, 2)).is_ok());
assert!(atomically(|t| storage.read(t, 2)).is_some_and(|v| v == EdgeAnchor::Curve(2)));
assert!(atomically_with_err(|t| storage.merge(t, 5, 2, 5)).is_ok());
assert!(atomically(|t| storage.read(t, 5)).is_some_and(|v| v == EdgeAnchor::Curve(2)));
assert!(atomically_with_err(|t| storage.merge(t, 8, 8, 9)).is_ok());
assert!(atomically(|t| storage.read(t, 8)).is_some_and(|v| v == EdgeAnchor::Surface(8)));
assert!(atomically_with_err(|t| storage.merge(t, 7, 7, 8)).is_ok());
assert!(atomically(|t| storage.read(t, 7)).is_some_and(|v| v == EdgeAnchor::Curve(9)));
assert!(atomically_with_err(|t| storage.merge(t, 10, 10, 7)).is_ok());
assert!(atomically(|t| storage.read(t, 10)).is_some_and(|v| v == EdgeAnchor::Curve(9)));
}
#[test]
fn merge_face_eq_dim() {
let storage: AttrSparseVec<FaceAnchor> = AttrSparseVec::new(13);
atomically(|t| {
storage.write(t, 7, FaceAnchor::Surface(3))?;
storage.write(t, 8, FaceAnchor::Surface(3))?;
storage.write(t, 9, FaceAnchor::Surface(4))?;
storage.write(t, 10, FaceAnchor::Body(5))?;
storage.write(t, 11, FaceAnchor::Body(6))?;
storage.write(t, 12, FaceAnchor::Body(6))?;
Ok(())
});
assert!(atomically_with_err(|t| storage.merge(t, 7, 7, 8)).is_ok());
assert!(atomically(|t| storage.read(t, 7)).is_some());
assert!(atomically(|t| storage.read(t, 8)).is_none());
assert!(atomically_with_err(|t| storage.merge(t, 7, 7, 9)).is_err());
assert!(atomically(|t| storage.read(t, 9)).is_some());
assert!(atomically_with_err(|t| storage.merge(t, 11, 12, 11)).is_ok());
assert!(atomically(|t| storage.read(t, 11)).is_some());
assert!(atomically(|t| storage.read(t, 12)).is_none());
assert!(atomically_with_err(|t| storage.merge(t, 10, 10, 11)).is_err());
assert!(atomically(|t| storage.read(t, 11)).is_some());
}
#[test]
fn merge_face_diff_dim() {
let storage: AttrSparseVec<FaceAnchor> = AttrSparseVec::new(11);
atomically(|t| {
storage.write(t, 3, FaceAnchor::Surface(3))?;
storage.write(t, 4, FaceAnchor::Body(4))?;
storage.write(t, 5, FaceAnchor::Body(5))?;
storage.write(t, 8, FaceAnchor::Surface(8))?;
storage.write(t, 9, FaceAnchor::Body(7))?;
storage.write(t, 10, FaceAnchor::Body(6))?;
Ok(())
});
assert!(atomically_with_err(|t| storage.merge(t, 3, 4, 3)).is_ok());
assert!(atomically(|t| storage.read(t, 3)).is_some_and(|v| v == FaceAnchor::Surface(3)));
assert!(atomically_with_err(|t| storage.merge(t, 5, 3, 5)).is_ok());
assert!(atomically(|t| storage.read(t, 5)).is_some_and(|v| v == FaceAnchor::Surface(3)));
assert!(atomically_with_err(|t| storage.merge(t, 8, 8, 9)).is_ok());
assert!(atomically(|t| storage.read(t, 8)).is_some_and(|v| v == FaceAnchor::Surface(8)));
assert!(atomically_with_err(|t| storage.merge(t, 10, 10, 8)).is_ok());
assert!(atomically(|t| storage.read(t, 10)).is_some_and(|v| v == FaceAnchor::Surface(8)));
}
#[test]
fn split_anchors() {
let storage: AttrSparseVec<VertexAnchor> = AttrSparseVec::new(13);
atomically(|t| {
storage.write(t, 1, VertexAnchor::Node(1))?;
storage.write(t, 4, VertexAnchor::Curve(1))?;
storage.write(t, 7, VertexAnchor::Surface(3))?;
storage.write(t, 10, VertexAnchor::Body(5))?;
Ok(())
});
atomically(|t| {
assert!(storage.split(t, 2, 3, 1).is_ok());
assert!(storage.read(t, 1)?.is_none());
assert!(storage.read(t, 2)?.is_some());
assert!(storage.read(t, 3)?.is_some());
assert!(storage.split(t, 5, 6, 4).is_ok());
assert!(storage.read(t, 4)?.is_none());
assert!(storage.read(t, 5)?.is_some());
assert!(storage.read(t, 6)?.is_some());
assert!(storage.split(t, 9, 8, 7).is_ok());
assert!(storage.read(t, 7)?.is_none());
assert!(storage.read(t, 8)?.is_some());
assert!(storage.read(t, 9)?.is_some());
assert!(storage.split(t, 11, 12, 10).is_ok());
assert!(storage.read(t, 10)?.is_none());
assert!(storage.read(t, 11)?.is_some());
assert!(storage.read(t, 12)?.is_some());
Ok(())
});
}
}
#[cfg(test)]
mod capture_and_classify {
use crate::grid_generation::GridBuilder;
use super::*;
#[test]
fn capture_example() {
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/../applications/shape.vtk");
let map = capture_geometry(path, [1.0; 2], Clip::Right).unwrap();
assert_eq!(
map.iter_vertices()
.filter_map(|v| map.read_attribute::<VertexAnchor>(v))
.count(),
13
);
classify_capture(&map).unwrap();
let mut set = HashSet::default();
map.iter_edges()
.filter_map(|e| map.read_attribute::<EdgeAnchor>(e))
.for_each(|a| {
set.insert(a);
});
assert_eq!(
set.iter()
.filter(|a| matches!(*a, EdgeAnchor::Curve(_)))
.count(),
13
);
assert!(
set.iter()
.filter(|a| matches!(*a, EdgeAnchor::Surface(_)))
.all(|a| match a {
EdgeAnchor::Surface(id) => *id == 0,
_ => unreachable!(),
}),
);
}
#[test]
fn classify_without_anchors() {
let map: CMap2<f64> = CMapBuilder::<2>::from_n_darts(10).build().unwrap();
assert!(
classify_capture(&map)
.is_err_and(|e| matches!(e, ClassificationError::MissingAttribute(_)))
);
let map: CMap2<f64> = CMapBuilder::<2>::from_n_darts(10)
.add_attribute::<VertexAnchor>()
.build()
.unwrap();
assert!(
classify_capture(&map)
.is_err_and(|e| matches!(e, ClassificationError::MissingAttribute(_)))
);
let map: CMap2<f64> = CMapBuilder::<2>::from_n_darts(10)
.add_attribute::<VertexAnchor>()
.add_attribute::<EdgeAnchor>()
.build()
.unwrap();
assert!(
classify_capture(&map)
.is_err_and(|e| matches!(e, ClassificationError::MissingAttribute(_)))
);
}
#[test]
fn classify_without_anchored_vertex_values() {
let mut map: CMap2<f64> = GridBuilder::<2, f64>::default()
.n_cells([4; 2])
.len_per_cell([1.0; 2])
.add_attribute::<VertexAnchor>()
.add_attribute::<EdgeAnchor>()
.add_attribute::<FaceAnchor>()
.build()
.unwrap();
let d1 = map.allocate_used_darts(4);
let (d2, d3, d4) = (d1 + 1, d1 + 2, d1 + 3);
let _ = map.link::<1>(d1, d2);
let _ = map.link::<1>(d2, d3);
let _ = map.link::<1>(d3, d4);
let _ = map.link::<1>(d4, d1);
assert_eq!(classify_capture(&map), Ok(()));
let (on_boundary, off_boundary): (Vec<_>, Vec<_>) = map.iter_vertices().partition(|v| {
map.orbit(OrbitPolicy::Vertex, *v)
.any(|dd| map.beta::<2>(dd) == NULL_DART_ID)
});
assert!(on_boundary.into_iter().all(|v| matches!(
map.read_attribute::<VertexAnchor>(v),
Some(VertexAnchor::Curve(_))
)));
assert!(off_boundary.into_iter().all(|v| matches!(
map.read_attribute::<VertexAnchor>(v),
Some(VertexAnchor::Surface(_))
)));
let (on_boundary, off_boundary): (Vec<_>, Vec<_>) = map
.iter_edges()
.partition(|e| map.beta::<2>(*e) == NULL_DART_ID);
assert!(on_boundary.into_iter().all(|e| matches!(
map.read_attribute::<EdgeAnchor>(e),
Some(EdgeAnchor::Curve(_))
)));
assert!(off_boundary.into_iter().all(|e| matches!(
map.read_attribute::<EdgeAnchor>(e),
Some(EdgeAnchor::Surface(_))
)));
assert!(map.iter_faces().all(|f| matches!(
map.read_attribute::<FaceAnchor>(f),
Some(FaceAnchor::Surface(_))
)));
assert!(matches!(
map.read_attribute::<VertexAnchor>(1),
Some(VertexAnchor::Curve(_))
));
assert!(matches!(
map.read_attribute::<VertexAnchor>(d1),
Some(VertexAnchor::Curve(_))
));
assert_ne!(
map.read_attribute::<VertexAnchor>(1),
map.read_attribute::<VertexAnchor>(d1)
);
}
#[test]
fn classify_with_anchored_vertex_values() {
let map: CMap2<f64> = GridBuilder::<2, f64>::default()
.n_cells([2; 2])
.len_per_cell([1.0; 2])
.add_attribute::<VertexAnchor>()
.add_attribute::<EdgeAnchor>()
.add_attribute::<FaceAnchor>()
.build()
.unwrap();
map.write_attribute(1, VertexAnchor::Node(1));
map.write_attribute(6, VertexAnchor::Node(2));
map.write_attribute(12, VertexAnchor::Node(3));
map.write_attribute(15, VertexAnchor::Node(4));
assert_eq!(classify_capture(&map), Ok(()));
let (on_boundary, off_boundary): (Vec<_>, Vec<_>) = map.iter_vertices().partition(|v| {
map.orbit(OrbitPolicy::Vertex, *v)
.any(|dd| map.beta::<2>(dd) == NULL_DART_ID)
});
assert!(on_boundary.into_iter().all(|v| matches!(
map.read_attribute::<VertexAnchor>(v),
Some(VertexAnchor::Curve(_) | VertexAnchor::Node(_)) )));
assert!(off_boundary.into_iter().all(|v| matches!(
map.read_attribute::<VertexAnchor>(v),
Some(VertexAnchor::Surface(_))
)));
let (on_boundary, off_boundary): (Vec<_>, Vec<_>) = map
.iter_edges()
.partition(|e| map.beta::<2>(*e) == NULL_DART_ID);
assert!(on_boundary.into_iter().all(|e| matches!(
map.read_attribute::<EdgeAnchor>(e),
Some(EdgeAnchor::Curve(_))
)));
assert!(off_boundary.into_iter().all(|e| matches!(
map.read_attribute::<EdgeAnchor>(e),
Some(EdgeAnchor::Surface(_))
)));
assert!(map.iter_faces().all(|f| matches!(
map.read_attribute::<FaceAnchor>(f),
Some(FaceAnchor::Surface(_))
)));
}
}
#[cfg(test)]
mod triangulate_and_classify {
use crate::{
grid_generation::GridBuilder,
triangulation::{TriangulateError, earclip_cell_countercw},
};
use super::*;
#[test]
#[should_panic = "called `Result::unwrap()` on an `Err` value: OpFailed"]
fn tri_before_class_no_anchor_value() {
let mut map: CMap2<f64> = GridBuilder::<2, f64>::default()
.n_cells([4; 2])
.len_per_cell([1.0; 2])
.add_attribute::<VertexAnchor>()
.add_attribute::<EdgeAnchor>()
.add_attribute::<FaceAnchor>()
.build()
.unwrap();
let faces = map.iter_faces().collect::<Vec<_>>();
for f in faces {
let nd = map.allocate_used_darts(2);
atomically_with_err(|t| earclip_cell_countercw(t, &map, f, &[nd, nd + 1])).unwrap();
}
}
#[test]
fn tri_before_class() {
let mut map: CMap2<f64> = GridBuilder::<2, f64>::default()
.n_cells([2; 2])
.len_per_cell([1.0; 2])
.add_attribute::<VertexAnchor>()
.add_attribute::<EdgeAnchor>()
.add_attribute::<FaceAnchor>()
.build()
.unwrap();
map.write_attribute(1, VertexAnchor::Node(1));
map.write_attribute(6, VertexAnchor::Node(2));
map.write_attribute(12, VertexAnchor::Node(3));
map.write_attribute(15, VertexAnchor::Node(4));
let faces = map.iter_faces().collect::<Vec<_>>();
for f in faces {
let nd = map.allocate_used_darts(2);
assert!(
atomically_with_err(|t| earclip_cell_countercw(t, &map, f, &[nd, nd + 1]))
.is_err_and(|e| matches!(e, TriangulateError::OpFailed(_)))
);
}
assert_eq!(classify_capture(&map), Ok(()));
}
#[test]
fn tri_after_class_no_anchor_value() {
let mut map: CMap2<f64> = GridBuilder::<2, f64>::default()
.n_cells([4; 2])
.len_per_cell([1.0; 2])
.add_attribute::<VertexAnchor>()
.add_attribute::<EdgeAnchor>()
.add_attribute::<FaceAnchor>()
.build()
.unwrap();
assert_eq!(classify_capture(&map), Ok(()));
let faces = map.iter_faces().collect::<Vec<_>>();
for f in faces {
let nd = map.allocate_used_darts(2);
atomically_with_err(|t| {
let anchor = map.remove_attribute_tx::<FaceAnchor>(t, f)?;
if let Some(a) = anchor {
map.write_attribute_tx(t, nd, EdgeAnchor::from(a))?;
} else {
unreachable!()
}
earclip_cell_countercw(t, &map, f, &[nd, nd + 1])?;
if let Some(a) = anchor {
let l_face_id = map.face_id_tx(t, nd)?;
let r_face_id = map.face_id_tx(t, nd + 1)?;
map.write_attribute_tx(t, l_face_id, a)?;
map.write_attribute_tx(t, r_face_id, a)?;
} else {
unreachable!()
}
Ok(())
})
.unwrap();
}
let (on_boundary, off_boundary): (Vec<_>, Vec<_>) = map
.iter_edges()
.partition(|e| map.beta::<2>(*e) == NULL_DART_ID);
assert!(on_boundary.into_iter().all(|e| matches!(
map.read_attribute::<EdgeAnchor>(e).unwrap(),
EdgeAnchor::Curve(_)
)));
assert!(off_boundary.into_iter().all(|e| matches!(
map.read_attribute::<EdgeAnchor>(e).unwrap(),
EdgeAnchor::Surface(_)
)));
assert!(map.iter_faces().all(|f| matches!(
map.read_attribute::<FaceAnchor>(f).unwrap(),
FaceAnchor::Surface(_)
)));
}
#[test]
fn tri_after_class() {
let mut map: CMap2<f64> = GridBuilder::<2, f64>::default()
.n_cells([2; 2])
.len_per_cell([1.0; 2])
.add_attribute::<VertexAnchor>()
.add_attribute::<EdgeAnchor>()
.add_attribute::<FaceAnchor>()
.build()
.unwrap();
map.write_attribute(1, VertexAnchor::Node(1));
map.write_attribute(6, VertexAnchor::Node(2));
map.write_attribute(12, VertexAnchor::Node(3));
map.write_attribute(15, VertexAnchor::Node(4));
assert_eq!(classify_capture(&map), Ok(()));
let faces = map.iter_faces().collect::<Vec<_>>();
for f in faces {
let nd = map.allocate_used_darts(2);
atomically_with_err(|t| {
let anchor = map.remove_attribute_tx::<FaceAnchor>(t, f)?;
if let Some(a) = anchor {
map.write_attribute_tx(t, nd, EdgeAnchor::from(a))?;
} else {
unreachable!()
}
earclip_cell_countercw(t, &map, f, &[nd, nd + 1])?;
if let Some(a) = anchor {
let l_face_id = map.face_id_tx(t, nd)?;
let r_face_id = map.face_id_tx(t, nd + 1)?;
map.write_attribute_tx(t, l_face_id, a)?;
map.write_attribute_tx(t, r_face_id, a)?;
} else {
unreachable!()
}
Ok(())
})
.unwrap();
}
let (on_boundary, off_boundary): (Vec<_>, Vec<_>) = map.iter_vertices().partition(|v| {
map.orbit(OrbitPolicy::Vertex, *v)
.any(|dd| map.beta::<2>(dd) == NULL_DART_ID)
});
assert!(on_boundary.into_iter().all(|v| matches!(
map.read_attribute::<VertexAnchor>(v),
Some(VertexAnchor::Curve(_) | VertexAnchor::Node(_)) )));
assert!(off_boundary.into_iter().all(|v| matches!(
map.read_attribute::<VertexAnchor>(v),
Some(VertexAnchor::Surface(_))
)));
let (on_boundary, off_boundary): (Vec<_>, Vec<_>) = map
.iter_edges()
.partition(|e| map.beta::<2>(*e) == NULL_DART_ID);
assert!(on_boundary.into_iter().all(|e| matches!(
map.read_attribute::<EdgeAnchor>(e).unwrap(),
EdgeAnchor::Curve(_)
)));
assert!(off_boundary.into_iter().all(|e| matches!(
map.read_attribute::<EdgeAnchor>(e).unwrap(),
EdgeAnchor::Surface(_)
)));
assert!(map.iter_faces().all(|f| matches!(
map.read_attribute::<FaceAnchor>(f).unwrap(),
FaceAnchor::Surface(_)
)));
}
#[cfg(test)]
mod collapse {
use super::*;
#[test]
fn collapse_edge_errs() {
let map: CMap2<f64> = GridBuilder::<2, f64>::default()
.n_cells([2; 2])
.len_per_cell([1.0; 2])
.add_attribute::<VertexAnchor>()
.add_attribute::<EdgeAnchor>()
.add_attribute::<FaceAnchor>()
.build()
.unwrap();
classify_capture(&map).unwrap();
assert_eq!(
atomically_with_err(|t| collapse_edge(t, &map, NULL_DART_ID)),
Err(EdgeCollapseError::NullEdge)
);
assert_eq!(
atomically_with_err(|t| collapse_edge(t, &map, 2)),
Err(EdgeCollapseError::BadTopology)
);
let map: CMap2<f64> = GridBuilder::<2, f64>::default()
.n_cells([2; 2])
.len_per_cell([1.0; 2])
.split_cells(true)
.add_attribute::<VertexAnchor>()
.add_attribute::<EdgeAnchor>()
.add_attribute::<FaceAnchor>()
.build()
.unwrap();
classify_capture(&map).unwrap();
assert!(matches!(
atomically_with_err(|t| collapse_edge(t, &map, 2)),
Err(EdgeCollapseError::NonCollapsibleEdge(_))
));
}
#[test]
fn collapse_edge_seq() {
let map: CMap2<f64> = GridBuilder::<2, f64>::default()
.n_cells([3; 2])
.len_per_cell([1.0; 2])
.split_cells(true)
.add_attribute::<VertexAnchor>()
.add_attribute::<EdgeAnchor>()
.add_attribute::<FaceAnchor>()
.build()
.unwrap();
classify_capture(&map).unwrap();
let new_v = atomically_with_err(|t| collapse_edge(t, &map, 30)).unwrap();
assert_eq!(new_v, 24);
assert!(
map.orbit(OrbitPolicy::Vertex, new_v as DartIdType)
.all(|d| map.read_attribute::<EdgeAnchor>(map.edge_id(d)).is_some())
);
assert!(
map.orbit(OrbitPolicy::Vertex, new_v as DartIdType)
.all(|d| map.read_attribute::<FaceAnchor>(map.face_id(d)).is_some())
);
let new_v = atomically_with_err(|t| collapse_edge(t, &map, 5)).unwrap();
assert_eq!(new_v, 2);
assert!(
map.orbit(OrbitPolicy::Vertex, new_v as DartIdType)
.all(|d| map.read_attribute::<EdgeAnchor>(map.edge_id(d)).is_some())
);
assert!(
map.orbit(OrbitPolicy::Vertex, new_v as DartIdType)
.all(|d| map.read_attribute::<FaceAnchor>(map.face_id(d)).is_some())
);
let new_v = atomically_with_err(|t| collapse_edge(t, &map, 24)).unwrap();
assert_eq!(new_v, 21);
assert!(
map.orbit(OrbitPolicy::Vertex, new_v as DartIdType)
.all(|d| map.read_attribute::<EdgeAnchor>(map.edge_id(d)).is_some())
);
assert!(
map.orbit(OrbitPolicy::Vertex, new_v as DartIdType)
.all(|d| map.read_attribute::<FaceAnchor>(map.face_id(d)).is_some())
);
}
}
}
#[cfg(test)]
mod swap {
use crate::grid_generation::GridBuilder;
use super::*;
#[test]
fn swap_edge_errs() {
let map: CMap2<f64> = GridBuilder::<2, f64>::unit_triangles(1);
assert!(
atomically_with_err(|t| swap_edge(t, &map, 0))
.is_err_and(|e| e == EdgeSwapError::NullEdge)
);
assert!(
atomically_with_err(|t| swap_edge(t, &map, 1))
.is_err_and(|e| e == EdgeSwapError::IncompleteEdge)
);
let map: CMap2<f64> = GridBuilder::<2, f64>::unit_grid(2);
assert!(
atomically_with_err(|t| swap_edge(t, &map, 2))
.is_err_and(|e| e == EdgeSwapError::BadTopology)
);
}
#[test]
fn swap_edge_seq() {
let map: CMap2<f64> = GridBuilder::<2, f64>::unit_triangles(1);
let o1: Vec<_> = map.orbit(OrbitPolicy::FaceLinear, 1).collect();
assert_eq!(&o1, &[1, 2, 3]);
let o6: Vec<_> = map.orbit(OrbitPolicy::FaceLinear, 6).collect();
assert_eq!(&o6, &[6, 4, 5]);
assert!(atomically_with_err(|t| swap_edge(t, &map, 2)).is_ok());
let o1: Vec<_> = map.orbit(OrbitPolicy::FaceLinear, 1).collect();
assert_eq!(&o1, &[1, 5, 4]);
let o6: Vec<_> = map.orbit(OrbitPolicy::FaceLinear, 6).collect();
assert_eq!(&o6, &[6, 3, 2]);
}
}