use std::{
collections::{BTreeMap, BTreeSet},
fmt::Debug,
};
use crate::{
prelude::*,
hsize,
test_utils::cmp_rotated,
};
#[allow(unused_macros)]
macro_rules! assert_eq_set {
($left:expr, $right:expr $(,)?) => {
crate::core::tests::util::assert_eq_set_fn(
$left,
$right,
stringify!($left),
stringify!($right),
);
}
}
#[allow(dead_code)]
pub fn assert_eq_set_fn<T: Debug + Clone + Eq + Ord>(
left: impl IntoIterator<Item = T>,
right: impl IntoIterator<Item = T>,
left_str: &str,
right_str: &str,
)
{
let left = set(left);
let right = set(right);
if left != right {
panic!(
"assert_eq_set!({}, {}) failed:\n| left: {:?}\n| right: {:?} ",
left_str,
right_str,
left,
right,
);
}
}
fn set<T>(src: impl IntoIterator<Item = T>) -> BTreeSet<T>
where
T: Clone + Eq + Ord,
{
src.into_iter().collect()
}
#[derive(Debug)]
pub(crate) struct MeshCheck {
pub(crate) vertices: ElementCheck<ElementInfo<VertexHandle>>,
pub(crate) faces: ElementCheck<ElementInfo<FaceHandle>>,
pub(crate) edges: ElementCheck<EdgeInfo>,
}
#[derive(Debug)]
#[allow(dead_code)]
pub(crate) enum ElementCheck<E> {
Full(Vec<E>),
Partial {
elements: Vec<E>,
len: Option<usize>,
},
NoCheck,
}
#[derive(Debug)]
pub(crate) struct ElementInfo<H: Handle> {
pub(crate) handle: H,
pub(crate) boundary: Option<bool>,
pub(crate) adjacent_faces: NeighborCheck<FaceHandle>,
pub(crate) adjacent_vertices: NeighborCheck<VertexHandle>,
}
#[derive(Debug)]
pub(crate) struct EdgeInfo {
pub(crate) vertices: [VertexHandle; 2],
pub(crate) handle: Option<EdgeHandle>,
pub(crate) boundary: Option<bool>,
pub(crate) adjacent_faces: NeighborCheck<FaceHandle>,
}
#[derive(Debug)]
pub(crate) enum NeighborCheck<H: Handle> {
OrderDefined(Vec<H>),
OrderUndefined(Vec<H>),
Partial {
partial_neighbors: Vec<H>,
len: Option<usize>,
},
NoCheck,
}
impl<E> ElementCheck<E> {
fn len(&self) -> Option<usize> {
match self {
ElementCheck::Full(v) => Some(v.len()),
ElementCheck::Partial { len, .. } => *len,
ElementCheck::NoCheck => None,
}
}
fn elements(&self) -> &[E] {
match self {
ElementCheck::Full(v) => v,
ElementCheck::Partial { elements, .. } => elements,
ElementCheck::NoCheck => &[],
}
}
}
trait ElementHandle: Handle {
const SINGULAR: &'static str;
const PLURAL: &'static str;
fn for_all<M: Mesh>(mesh: &M, visit: impl FnMut(Self));
const ARE_ADJACENT_FN: &'static str;
fn are_adjacent<M: FullAdj>(mesh: &M, a: Self, b: Self) -> bool;
const IS_BOUNDARY_FN: &'static str;
fn is_boundary<M: FullAdj>(mesh: &M, h: Self) -> bool;
fn neighbors<H: Handle>(info: &ElementInfo<H>) -> &NeighborCheck<Self>;
}
impl ElementHandle for VertexHandle {
const SINGULAR: &'static str = "vertex";
const PLURAL: &'static str = "vertices";
fn for_all<M: Mesh>(mesh: &M, visit: impl FnMut(Self)) {
mesh.vertex_handles().for_each(visit)
}
const ARE_ADJACENT_FN: &'static str = "are_vertices_adjacent";
fn are_adjacent<M: FullAdj>(mesh: &M, a: Self, b: Self) -> bool {
mesh.are_vertices_adjacent(a, b)
}
const IS_BOUNDARY_FN: &'static str = "is_boundary_vertex";
fn is_boundary<M: FullAdj>(mesh: &M, h: Self) -> bool {
mesh.is_boundary_vertex(h)
}
fn neighbors<H: Handle>(info: &ElementInfo<H>) -> &NeighborCheck<Self> {
&info.adjacent_vertices
}
}
impl ElementHandle for FaceHandle {
const SINGULAR: &'static str = "face";
const PLURAL: &'static str = "faces";
fn for_all<M: Mesh>(mesh: &M, visit: impl FnMut(Self)) {
mesh.face_handles().for_each(visit)
}
const ARE_ADJACENT_FN: &'static str = "are_faces_adjacent";
fn are_adjacent<M: FullAdj>(mesh: &M, a: Self, b: Self) -> bool {
mesh.are_faces_adjacent(a, b)
}
const IS_BOUNDARY_FN: &'static str = "is_boundary_face";
fn is_boundary<M: FullAdj>(mesh: &M, h: Self) -> bool {
mesh.is_boundary_face(h)
}
fn neighbors<H: Handle>(info: &ElementInfo<H>) -> &NeighborCheck<Self> {
&info.adjacent_faces
}
}
impl<H: Handle> ElementInfo<H> {
fn check_full_adj<M: FullAdj>(&self, mesh: &M)
where
H: ElementHandle,
{
if let Some(expected_boundary) = self.boundary {
if expected_boundary != H::is_boundary(mesh, self.handle) {
panic!(
"mesh says {:?} is {}a boundary {}, but it is{}",
self.handle,
if expected_boundary { "not " } else { "" },
H::SINGULAR,
if expected_boundary { "" } else { " not" },
);
}
}
match H::neighbors(&self) {
NeighborCheck::OrderDefined(neighbors) | NeighborCheck::OrderUndefined(neighbors) => {
H::for_all(mesh, |other| {
let actual_adjacent = H::are_adjacent(mesh, self.handle, other);
let expected_adjacent = neighbors.contains(&other);
if actual_adjacent != expected_adjacent {
panic!(
"{}({:?}, {:?}) returned {}, but those {} \
are expected to{} be adjacent",
H::ARE_ADJACENT_FN,
self.handle,
other,
actual_adjacent,
H::PLURAL,
if expected_adjacent { "" } else { " not" },
);
}
})
}
_ => {},
}
}
}
impl<H: Handle> NeighborCheck<H> {
fn potentially_partial(&self) -> &[H] {
match self {
NeighborCheck::NoCheck => &[],
NeighborCheck::Partial { partial_neighbors, .. }=> &partial_neighbors,
NeighborCheck::OrderDefined(neighbors) => &neighbors,
NeighborCheck::OrderUndefined(neighbors) => &neighbors,
}
}
fn check(&self, actual: &[H], actual_str: &str) {
match self {
NeighborCheck::NoCheck => {}
NeighborCheck::OrderDefined(expected) => {
if let Err(rotated) = cmp_rotated(actual, expected) {
panic!(
"wrong neighbors returned by `{}` (order respecting comparison)\n\
| expected: {:?} (original: {:?})\n\
| actual: {:?}\n",
actual_str,
rotated,
expected,
actual,
);
}
}
NeighborCheck::OrderUndefined(expected) => {
if set(expected) != set(actual) {
panic!(
"wrong neighbors returned by `{}` (set comparison)\n\
| expected: {:?}\n\
| actual: {:?}\n",
actual_str,
set(expected),
set(actual),
);
}
}
NeighborCheck::Partial { partial_neighbors, len } => {
for expected in partial_neighbors {
if !actual.contains(&expected) {
panic!(
"the result of `{}` does not contain {:?}, but that was expected! \n\
| expected in result: {:?}\n\
| actually returned: {:?}\n",
actual_str,
expected,
partial_neighbors,
actual,
);
}
}
if let Some(len) = len {
if actual.len() != *len {
panic!(
"the result of`{}` was expected to have the length {}, but \
it actually has the length {} ({:?})",
actual_str,
len,
actual.len(),
actual,
);
}
}
}
}
}
}
impl MeshCheck {
pub(crate) fn check_basic<M: Mesh>(&self, mesh: &M) {
macro_rules! gen_check {
(
$field:ident, $num_fn:ident, $handles_fn:ident, $contains_fn:ident,
$next_from_fn:ident, $last_fn:ident,
) => {
if let Some(len) = self.$field.len() {
if mesh.$num_fn() != len as hsize {
panic!(
"`{}()` returned {}, but is supposed to return {}",
stringify!($num_fn),
mesh.$num_fn(),
len,
);
}
}
let actual_handles = set(mesh.$handles_fn());
match &self.$field {
ElementCheck::Full(expected) => {
let expected = set(expected.iter().map(|v| v.handle));
if actual_handles != expected {
panic!(
"unexpected {} in mesh (check for set equality).\n\
| expected: {:?}\n\
| actual: {:?} (via `mesh.{}()`)\n",
stringify!($field),
expected,
actual_handles,
stringify!($handles_fn),
);
}
let max_idx = expected.iter().map(|h| h.idx()).max().unwrap_or(0);
for idx in max_idx + 1..max_idx + 5 {
let handle = Handle::new(idx);
if mesh.$contains_fn(handle) {
panic!(
"mesh.{}({:?}) returned true, but should return false",
stringify!($contains_fn),
handle,
);
}
}
}
ElementCheck::Partial { elements, .. } => {
for e in elements {
if !actual_handles.contains(&e.handle) {
panic!(
"{:?} not part of mesh, but should be.\n\
| expected: {:?} (partial!)\n\
| actual: {:?} (via `mesh.{}()`)\n",
e.handle,
set(elements.iter().map(|e| e.handle)),
actual_handles,
stringify!($handles_fn),
);
}
}
},
ElementCheck::NoCheck => {},
}
for e in self.$field.elements() {
if !mesh.$contains_fn(e.handle) {
panic!(
"mesh.{}({:?}) returned false, should return true",
stringify!($contains_fn),
e.handle,
);
}
let next = mesh.$next_from_fn(e.handle);
if next != Some(e.handle) {
panic!(
"mesh.{}({:?}) returned {:?}, should return {:?}",
stringify!($next_from_fn),
e.handle,
next,
e.handle,
);
}
let last_handle = mesh.$last_fn()
.expect(concat!("`", stringify!($last_fn), "` returned `None`"));
if e.handle.idx() > last_handle.idx() {
panic!(
"mesh.{}() returned {:?} which has a smaller index than {:?} \
(which is part of the mesh)",
stringify!($last_fn),
last_handle,
e.handle,
);
}
}
};
}
mesh.check_integrity();
gen_check!(
vertices, num_vertices, vertex_handles, contains_vertex,
next_vertex_handle_from, last_vertex_handle,
);
gen_check!(
faces, num_faces, face_handles, contains_face,
next_face_handle_from, last_face_handle,
);
}
pub(crate) fn check_basic_edge<M: EdgeMesh>(&self, mesh: &M) {
if let Some(len) = self.edges.len() {
if mesh.num_edges() != len as hsize {
panic!(
"`num_edges()` returned {}, but is supposed to return {}",
mesh.num_edges(),
len,
);
}
}
enum SetCheck {
Equality(BTreeSet<EdgeHandle>),
Subset(BTreeSet<EdgeHandle>),
None,
}
let actual_handles = set(mesh.edge_handles());
let expected_handles = match &self.edges {
ElementCheck::Full(expected) => {
let expected_handles = set(expected.iter().filter_map(|e| e.handle));
if expected_handles.len() == expected.len() {
SetCheck::Equality(expected_handles)
} else {
SetCheck::Subset(expected_handles)
}
}
ElementCheck::Partial { elements, .. } => {
SetCheck::Subset(set(elements.iter().filter_map(|e| e.handle)))
}
ElementCheck::NoCheck => SetCheck::None,
};
match expected_handles {
SetCheck::Equality(expected) => {
if actual_handles != expected {
panic!(
"unexpected edges in mesh (check for set equality).\n\
| expected: {:?}\n\
| actual: {:?} (via `mesh.edge_handles()`)\n",
expected,
actual_handles,
);
}
let max_idx = expected.iter().map(|h| h.idx()).max().unwrap_or(0);
for idx in max_idx + 1..max_idx + 5 {
let handle = Handle::new(idx);
if mesh.contains_edge(handle) {
panic!(
"mesh.contains_edge({:?}) returned true, but should return false",
handle,
);
}
}
}
SetCheck::Subset(expected) => {
for eh in &expected {
if !actual_handles.contains(eh) {
panic!(
"{:?} not part of mesh, but should be.\n\
| expected: {:?} (partial!)\n\
| actual: {:?} (via `mesh.edge_handles()`)\n",
eh,
expected,
actual_handles,
);
}
}
},
SetCheck::None => {},
}
for eh in self.edges.elements().iter().filter_map(|e| e.handle) {
if !mesh.contains_edge(eh) {
panic!(
"mesh.contains_edge({:?}) returned false, should return true",
eh,
);
}
let next = mesh.next_edge_handle_from(eh);
if next != Some(eh) {
panic!(
"mesh.next_edge_handle_from({:?}) returned {:?}, should return {:?}",
eh,
next,
eh,
);
}
let last_handle = mesh.last_edge_handle()
.expect(concat!("`last_edge_handle` returned `None`"));
if eh.idx() > last_handle.idx() {
panic!(
"mesh.last_edge_handle() returned {:?} which has a smaller index than {:?} \
(which is part of the mesh)",
last_handle,
eh,
);
}
}
}
pub(crate) fn check_basic_adj<M: BasicAdj>(&self, mesh: &M) {
for f in self.faces.elements() {
f.adjacent_vertices.check(
&mesh.vertices_around_face(f.handle).into_vec(),
&format!("mesh.vertices_around_face({:?})", f.handle),
);
}
}
pub(crate) fn check_basic_adj_tri<M: BasicAdj + TriMesh>(&self, mesh: &M) {
for f in self.faces.elements() {
f.adjacent_vertices.check(
&mesh.vertices_around_triangle(f.handle),
&format!("mesh.vertices_around_triangle({:?})", f.handle),
);
}
}
pub(crate) fn check_full_adj<M: FullAdj>(&self, mesh: &M) {
for f in self.faces.elements() {
f.check_full_adj(mesh);
f.adjacent_faces.check(
&mesh.faces_around_face(f.handle).into_vec(),
&format!("mesh.faces_around_face({:?})", f.handle),
);
}
for v in self.vertices.elements() {
v.check_full_adj(mesh);
v.adjacent_faces.check(
&mesh.faces_around_vertex(v.handle).into_vec(),
&format!("mesh.faces_around_vertex({:?})", v.handle),
);
v.adjacent_vertices.check(
&mesh.vertices_around_vertex(v.handle).into_vec(),
&format!("mesh.vertices_around_vertex({:?})", v.handle),
);
match &v.adjacent_vertices {
NeighborCheck::OrderDefined(n) | NeighborCheck::OrderUndefined(n) => {
if mesh.is_isolated_vertex(v.handle) != n.is_empty() {
panic!(
"mesh says {:?} is {}an isolated vertex, but it is{}",
v.handle,
if n.is_empty() { "not " } else { "" },
if n.is_empty() { "" } else { " not" },
);
}
}
_ => {}
}
}
}
pub(crate) fn check_full_adj_tri<M: FullAdj + TriMesh>(&self, mesh: &M) {
for f in self.faces.elements() {
f.adjacent_faces.check(
&mesh.faces_around_triangle(f.handle).into_vec(),
&format!("mesh.faces_around_triangle({:?})", f.handle),
);
}
}
pub(crate) fn check_edge_adj<M: EdgeAdj>(&self, mesh: &M) {
for e in self.edges.elements() {
let handle = match mesh.edge_between_vertices(e.vertices[0], e.vertices[1]) {
Some(h) => h,
None => {
panic!(
"`mesh.edge_between_vertices({:?}, {:?})` returned `None` but {} \
between those vertices was expected",
e.vertices[0],
e.vertices[1],
e.handle.map(|h| format!("{:?}", h)).unwrap_or("an edge".into()),
);
}
};
if let Some(expected_handle) = e.handle {
if expected_handle != handle {
panic!(
"`mesh.edge_between_vertices({:?}, {:?})` returned unexpected edge.\n\
| expected: {:?}\n\
| actual: {:?}\n",
e.vertices[0],
e.vertices[1],
expected_handle,
handle,
);
}
}
let edge_id = format!("{:?} ({:?} -- {:?})", handle, e.vertices[0], e.vertices[1]);
if let Some(expected_boundary) = e.boundary {
if expected_boundary != mesh.is_boundary_edge(handle) {
panic!(
"mesh says {} is {}a boundary edge, but the opposite was expected",
edge_id,
if expected_boundary { "not " } else { "" },
);
}
}
e.adjacent_faces.check(
&mesh.faces_of_edge(handle).into_vec(),
&format!("mesh.faces_of_edge({})", edge_id),
);
let expected_vertices = set(e.vertices.iter().cloned());
let actual_vertices = set(mesh.endpoints_of_edge(handle).iter().cloned());
if actual_vertices != expected_vertices {
panic!(
"wrong neighbors returned by `endpoints_of_edge({})` (set comparison)\n\
| expected: {:?}\n\
| actual: {:?}\n",
edge_id,
expected_vertices,
actual_vertices,
);
}
}
for f in self.faces.elements() {
if let NeighborCheck::OrderDefined(vertices) = &f.adjacent_vertices {
let actual_edges = mesh.edges_around_face(f.handle).collect::<Vec<_>>();
let expected_edges = (0..vertices.len()).map(|i| {
let va = vertices[i];
let vb = vertices[(i + 1) % vertices.len()];
mesh.edge_between_vertices(va, vb).unwrap_or_else(|| {
panic!(
"`mesh.edge_between_vertices({:?}, {:?})` returned `None`, but an \
edge was expected, as both vertices are part of {:?}",
va,
vb,
f.handle,
);
})
}).collect::<Vec<_>>();
if let Err(rotated) = cmp_rotated(&actual_edges, &expected_edges) {
panic!(
"wrong edges returned by `edges_around_face({:?})` \
(order respecting comparison)\n\
| expected: {:?} (original: {:?}, obtained from ordered \
vertices around face)\n\
| actual: {:?}\n",
f.handle,
rotated,
expected_edges,
actual_edges,
);
}
}
}
for center in self.vertices.elements() {
let edges_to_vertices = center.adjacent_vertices.potentially_partial()
.iter()
.map(|other| {
mesh.edge_between_vertices(center.handle, *other).unwrap_or_else(|| {
panic!(
"`mesh.edge_between_vertices({:?}, {:?})` returned `None`, but \
an edge was expected, as both vertices are adjacent",
center.handle,
other,
);
})
})
.collect::<Vec<_>>();
let adjacent_edges = mesh.edges_around_vertex(center.handle).collect::<Vec<_>>();
match ¢er.adjacent_vertices {
NeighborCheck::OrderDefined(_) => {
if let Err(rotated) = cmp_rotated(&edges_to_vertices, &adjacent_edges) {
panic!(
"edges returned by `edges_around_vertex({:?})` differ from \
edges returned from `edge_between_vertices` called with \
{:?} and its neighbor vertices (order respecting comparison)\n\
| from `edges_around_vertex`: {:?} (original: {:?})\n\
| from `edge_between_vertices`: {:?}\n",
center.handle,
center.handle,
rotated,
adjacent_edges,
edges_to_vertices,
);
}
}
NeighborCheck::OrderUndefined(_) => {
if set(&edges_to_vertices) != set(&adjacent_edges) {
panic!(
"edges returned by `edges_around_vertex({:?})` differ from \
edges returned from `edge_between_vertices` called with \
{:?} and its neighbor vertices (set comparison)\n\
| from `edges_around_vertex`: {:?}\n\
| from `edge_between_vertices`: {:?}\n",
center.handle,
center.handle,
set(&adjacent_edges),
set(&edges_to_vertices),
);
}
}
NeighborCheck::Partial { .. } => {
for (i, eh) in edges_to_vertices.iter().enumerate() {
if !adjacent_edges.contains(&eh) {
panic!(
"{:?} is not in the result of edges_around_vertex({:?}), but it\
should be, as it's the edge between {:?} and {:?}.\n\
| actually returned by edges_around_vertex: {:?}\n",
eh,
center.handle,
center.handle,
center.adjacent_vertices.potentially_partial()[i],
adjacent_edges,
);
}
}
}
NeighborCheck::NoCheck => {}
}
}
}
}
#[derive(Debug, Empty)]
pub(crate) struct Symbols {
vertices: BTreeMap<VertexHandle, &'static str>,
faces: BTreeMap<FaceHandle, &'static str>,
edges: BTreeMap<EdgeHandle, &'static str>,
}
pub(crate) trait SymbolHelper<H: Handle> {
fn add(&mut self, h: H, ident: &'static str);
}
impl SymbolHelper<VertexHandle> for Symbols {
fn add(&mut self, h: VertexHandle, ident: &'static str) {
self.vertices.insert(h, ident);
}
}
impl SymbolHelper<FaceHandle> for Symbols {
fn add(&mut self, h: FaceHandle, ident: &'static str) {
self.faces.insert(h, ident);
}
}
impl SymbolHelper<EdgeHandle> for Symbols {
fn add(&mut self, h: EdgeHandle, ident: &'static str) {
self.edges.insert(h, ident);
}
}
macro_rules! check_mesh {
($mesh:ident; $extras:tt; {
vertices: $vertex_checks:tt,
faces: $face_checks:tt,
edges: $edge_checks:tt $(,)?
}) => {{
#[allow(unused_imports)] use crate::core::tests::util::{
MeshCheck, EdgeInfo, ElementCheck, ElementInfo, NeighborCheck, Symbols, SymbolHelper,
};
#[allow(unused_mut)]
let mut symbols = Symbols::empty();
let checker = MeshCheck {
vertices: check_mesh!(@element_check symbols; $vertex_checks),
faces: check_mesh!(@element_check symbols; $face_checks),
edges: check_mesh!(@edge_check symbols; $edge_checks),
};
let res = std::panic::catch_unwind(|| {
checker.check_basic(&$mesh);
test_helper!(@if BasicAdj in $extras => {
checker.check_basic_adj(&$mesh);
test_helper!(@if TriMesh in $extras => {
checker.check_basic_adj_tri(&$mesh);
});
});
test_helper!(@if FullAdj in $extras => {
checker.check_full_adj(&$mesh);
test_helper!(@if TriMesh in $extras => {
checker.check_full_adj_tri(&$mesh);
});
});
test_helper!(@if EdgeMesh in $extras => {
checker.check_basic_edge(&$mesh);
test_helper!(@if EdgeAdj in $extras => {
checker.check_edge_adj(&$mesh);
});
});
});
if let Err(e) = res {
eprintln!();
eprintln!("+++++ Additional failure information +++++");
eprintln!("symbols: {:#?}", symbols);
eprintln!();
eprintln!("mesh: {:#?}", $mesh);
std::panic::resume_unwind(e);
}
}};
(@element_check $sym:ident; no_check) => { ElementCheck::NoCheck };
(@element_check $sym:ident; {
$(
$h:ident => $nf:tt, $nv:tt, $boundary:ident
);*
$(;)?
... $(; $len:expr)?
} ) => {
ElementCheck::Partial {
elements: vec![$(
check_mesh!(@make_element $sym; $h => $nf, $nv, $boundary)
),* ],
len: [$($len)?].get(0).copied(),
}
};
(@element_check $sym:ident; {
$(
$h:ident => $nf:tt, $nv:tt, $boundary:ident
);*
$(;)?
} ) => {
ElementCheck::Full(vec![$(
check_mesh!(@make_element $sym; $h => $nf, $nv, $boundary)
),* ])
};
(@make_element $sym:ident; $h:ident => $nf:tt, $nv:tt, $boundary:ident) => {{
$sym.add($h, stringify!($h));
ElementInfo {
handle: $h,
boundary: check_mesh!(@is_boundary $boundary),
adjacent_faces: check_mesh!(@neighbor_check $nf),
adjacent_vertices: check_mesh!(@neighbor_check $nv),
}
}};
(@edge_check $sym:ident; no_check) => { ElementCheck::NoCheck };
(@edge_check $sym:ident; {
$(
$va:ident -- $vb:ident $(@ $eh:ident)? => $nf:tt, $boundary:ident
);*
$(;)?
... $(; $len:expr)?
} ) => {
ElementCheck::Partial {
elements: vec![$(
check_mesh!(@make_edge $sym; $va -- $vb $(@ $eh)? => $nf, $boundary)
),* ],
len: [$($len)?].get(0).copied(),
}
};
(@edge_check $sym:ident; {
$(
$va:ident -- $vb:ident $(@ $eh:ident)? => $nf:tt, $boundary:ident
);*
$(;)?
} ) => {
ElementCheck::Full(vec![$(
check_mesh!(@make_edge $sym; $va -- $vb $(@ $eh)? => $nf, $boundary)
),* ])
};
(@make_edge $sym:ident;
$va:ident -- $vb:ident $(@ $eh:ident)? => $nf:tt, $boundary:ident
) => {{
$( $sym.add($eh, stringify!($eh)); )?
EdgeInfo {
vertices: [$va, $vb],
handle: [$($eh)?].get(0).copied(),
boundary: check_mesh!(@is_boundary $boundary),
adjacent_faces: check_mesh!(@neighbor_check $nf),
}
}};
(@neighbor_check no_check) => { NeighborCheck::NoCheck };
(@neighbor_check [$($n:ident),*]) => {
NeighborCheck::OrderDefined(vec![$($n),*])
};
(@neighbor_check {$($n:ident),* ... $(; $len:expr)? }) => {
NeighborCheck::Partial {
partial_neighbors: vec![$($n),*],
len: [$($len)?].get(0).copied(),
}
};
(@neighbor_check {$($n:ident),*}) => {
NeighborCheck::OrderUndefined(vec![$($n),*])
};
(@is_boundary no_check) => { None };
(@is_boundary boundary) => { Some(true) };
(@is_boundary interior) => { Some(false) };
}
macro_rules! test_helper {
() => { compile_error!("internal macro: don't use anywhere else") };
(@if $needle:ident in [] => $body:tt) => {{
test_helper!(@is_valid_extra_trait $needle);
}};
(@if $needle:ident in [$head:ident $(, $tail:ident)*] => $body:tt) => {{
macro_rules! __inner_helper {
($needle $needle) => { $body };
($needle $head) => { test_helper!(@if $needle in [$($tail),*] => $body) }
}
__inner_helper!($needle $head)
}};
(@if_item [$needle:ident] in [] => { $($body:tt)* }) => {
test_helper!(@is_valid_extra_trait $needle);
};
(@if_item [$needle:ident] in [$head:ident $(, $tail:ident)*] => { $($body:tt)* }) => {
macro_rules! __inner_helper {
($needle $needle) => { $($body)* };
($needle $head) => {
test_helper!(@if_item [$needle] in [$($tail),*] => { $($body)* });
}
}
__inner_helper!($needle $head);
};
(@if_item [$head:ident $(, $tail:ident)*] in $extra:tt => $body:tt) => {
test_helper!(@if_item [$head] in $extra => {
test_helper!(@if_item [$($tail),*] in $extra => $body);
});
};
(@is_valid_extra_trait BasicAdj) => {};
(@is_valid_extra_trait FullAdj) => {};
(@is_valid_extra_trait EdgeAdj) => {};
(@is_valid_extra_trait TriMesh) => {};
(@is_valid_extra_trait PolyMesh) => {};
(@is_valid_extra_trait EdgeMesh) => {};
(@is_valid_extra_trait Manifold) => {}; (@is_valid_extra_trait SupportsMultiBlade) => {};
(@is_valid_extra_trait $other:ident) => {
compile_error!(concat!(
"`",
stringify!($other),
"` is not a valid trait to pass to `gen_mesh_tests`",
));
};
}