pub const NO_CORNER: u32 = u32::MAX;
#[derive(Debug, Clone)]
pub struct CornerTable {
opposite: Vec<u32>,
vertex_corner: Vec<u32>,
}
impl CornerTable {
pub(crate) fn build(n_nodes: usize, triangles: &[[u32; 3]]) -> Result<CornerTable, String> {
let n_corners = triangles.len() * 3;
let mut opposite = vec![NO_CORNER; n_corners];
let mut vertex_corner = vec![NO_CORNER; n_nodes];
let mut by_edge: std::collections::HashMap<(u32, u32), Vec<u32>> =
std::collections::HashMap::new();
for (t, tri) in triangles.iter().enumerate() {
for k in 0..3 {
let corner = (3 * t + k) as u32;
let v = tri[k] as usize;
if v >= n_nodes {
return Err(format!(
"triangle {t} references node {v} outside the shell's {n_nodes} nodes"
));
}
if vertex_corner[v] == NO_CORNER {
vertex_corner[v] = corner;
}
let (a, b) = (tri[(k + 1) % 3], tri[(k + 2) % 3]);
let key = if a <= b { (a, b) } else { (b, a) };
by_edge.entry(key).or_default().push(corner);
}
}
for ((a, b), corners) in &by_edge {
match corners.as_slice() {
[_] => {}
[c1, c2] => {
opposite[*c1 as usize] = *c2;
opposite[*c2 as usize] = *c1;
}
more => {
return Err(format!(
"edge ({a}, {b}) is carried by {} triangles; a shell must be \
edge-manifold (at most two)",
more.len()
));
}
}
}
Ok(CornerTable {
opposite,
vertex_corner,
})
}
pub fn opposite(&self, corner: u32) -> u32 {
self.opposite[corner as usize]
}
pub fn vertex_corner(&self, vertex: u32) -> u32 {
self.vertex_corner[vertex as usize]
}
pub fn n_corners(&self) -> usize {
self.opposite.len()
}
pub fn triangle_of(corner: u32) -> usize {
corner as usize / 3
}
}
#[cfg(test)]
mod tests {
use super::*;
fn quad() -> Vec<[u32; 3]> {
vec![[0, 1, 2], [2, 1, 3]]
}
#[test]
fn opposites_pair_across_the_shared_edge() {
let ct = CornerTable::build(4, &quad()).unwrap();
assert_eq!(ct.opposite(0), 5);
assert_eq!(ct.opposite(5), 0);
for c in [1u32, 2, 3, 4] {
assert_eq!(ct.opposite(c), NO_CORNER, "corner {c} is on the boundary");
}
assert_eq!(ct.n_corners(), 6);
}
#[test]
fn vertex_corners_are_incident() {
let tris = quad();
let ct = CornerTable::build(4, &tris).unwrap();
for v in 0..4u32 {
let c = ct.vertex_corner(v);
assert_ne!(c, NO_CORNER);
let t = CornerTable::triangle_of(c);
assert_eq!(tris[t][c as usize % 3], v);
}
}
#[test]
fn rejects_a_non_manifold_edge() {
let tris = vec![[0, 1, 2], [1, 0, 3], [0, 1, 4]];
assert!(CornerTable::build(5, &tris).is_err());
}
#[test]
fn rejects_an_out_of_range_node() {
assert!(CornerTable::build(2, &quad()).is_err());
}
}