use crate::marching_tetrahedra::table::{TET_EDGE_COUNT, TET_EDGES};
pub const TET_FACE_COUNT: usize = 4;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct TetFace {
pub corner: [u8; 3],
pub edge: [u8; 3],
}
pub static TET_FACES: [TetFace; TET_FACE_COUNT] = build_faces();
const fn build_faces() -> [TetFace; TET_FACE_COUNT] {
let mut out = [TetFace {
corner: [0; 3],
edge: [0; 3],
}; TET_FACE_COUNT];
let mut opposite = 0usize;
while opposite < TET_FACE_COUNT {
let mut corner = [0u8; 3];
let mut n = 0usize;
let mut c = 0u8;
while c < 4 {
if c as usize != opposite {
corner[n] = c;
n += 1;
}
c += 1;
}
let mut edge = [0u8; 3];
let mut k = 0usize;
while k < 3 {
edge[k] = edge_between(corner[k], corner[(k + 1) % 3]);
k += 1;
}
out[opposite] = TetFace { corner, edge };
opposite += 1;
}
out
}
pub const fn edge_between(a: u8, b: u8) -> u8 {
let (lo, hi) = if a < b { (a, b) } else { (b, a) };
let mut e = 0usize;
while e < TET_EDGE_COUNT {
if TET_EDGES[e][0] == lo && TET_EDGES[e][1] == hi {
return e as u8;
}
e += 1;
}
panic!("a tetrahedron's corners are pairwise adjacent")
}
#[inline]
#[must_use]
pub const fn complementary(edge: u8) -> u8 {
(TET_EDGE_COUNT as u8 - 1) - edge
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct EdgeCoordinates {
pub count: [u32; TET_EDGE_COUNT],
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum NotNormal {
OddSum {
face: u8,
sum: u32,
},
TriangleInequality {
face: u8,
corner: u8,
},
}
impl EdgeCoordinates {
#[must_use]
pub const fn empty() -> Self {
Self {
count: [0; TET_EDGE_COUNT],
}
}
#[must_use]
pub const fn new(count: [u32; TET_EDGE_COUNT]) -> Self {
Self { count }
}
#[must_use]
pub const fn edge(&self, edge: u8) -> u32 {
self.count[edge as usize]
}
#[must_use]
pub fn total(&self) -> u32 {
self.count.iter().sum()
}
#[must_use]
pub fn is_classic(&self) -> bool {
self.count.iter().all(|c| *c <= 1)
}
#[must_use]
pub fn corner_coordinates(&self, face: u8) -> Option<[u32; 3]> {
let f = TET_FACES[face as usize];
let e = [
i64::from(self.count[f.edge[0] as usize]),
i64::from(self.count[f.edge[1] as usize]),
i64::from(self.count[f.edge[2] as usize]),
];
if (e[0] + e[1] + e[2]) % 2 != 0 {
return None;
}
let mut out = [0u32; 3];
for (k, slot) in out.iter_mut().enumerate() {
let adjacent_a = e[k];
let adjacent_b = e[(k + 2) % 3];
let opposite = e[(k + 1) % 3];
let c = (adjacent_a + adjacent_b - opposite) / 2;
if c < 0 {
return None;
}
*slot = c as u32;
}
Some(out)
}
pub fn validate(&self) -> Result<(), NotNormal> {
for face in 0..TET_FACE_COUNT as u8 {
let f = TET_FACES[face as usize];
let e: [u32; 3] = [
self.count[f.edge[0] as usize],
self.count[f.edge[1] as usize],
self.count[f.edge[2] as usize],
];
let sum = e[0] + e[1] + e[2];
if !sum.is_multiple_of(2) {
return Err(NotNormal::OddSum { face, sum });
}
for k in 0..3 {
if e[k] + e[(k + 2) % 3] < e[(k + 1) % 3] {
return Err(NotNormal::TriangleInequality {
face,
corner: f.corner[k],
});
}
}
}
Ok(())
}
#[must_use]
pub fn is_normal(&self) -> bool {
self.validate().is_ok()
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct NormalSurface {
pub triangle: [u32; 4],
pub quad: [u32; 3],
}
impl NormalSurface {
#[must_use]
pub fn edge_coordinates(&self) -> EdgeCoordinates {
let mut count = [0u32; TET_EDGE_COUNT];
for (edge, slot) in count.iter_mut().enumerate() {
let [a, b] = TET_EDGES[edge];
*slot += self.triangle[a as usize] + self.triangle[b as usize];
}
for (pair, q) in self.quad.iter().enumerate() {
if *q == 0 {
continue;
}
let lo = pair as u8;
let hi = complementary(lo);
for (edge, slot) in count.iter_mut().enumerate() {
if edge as u8 != lo && edge as u8 != hi {
*slot += q;
}
}
}
EdgeCoordinates { count }
}
#[must_use]
pub fn polygon_count(&self) -> u32 {
self.triangle.iter().sum::<u32>() + self.quad.iter().sum::<u32>()
}
}
#[must_use]
pub fn decompose(e: &EdgeCoordinates) -> Option<NormalSurface> {
for pair in [usize::MAX, 0, 1, 2] {
let mut quad = [0u32; 3];
let mut residual = [0i64; TET_EDGE_COUNT];
for (edge, slot) in residual.iter_mut().enumerate() {
*slot = i64::from(e.count[edge]);
}
if pair != usize::MAX {
let lo = pair as u8;
let hi = complementary(lo);
let mut crossed = 0i64;
let mut uncrossed = 0i64;
for edge in 0..TET_EDGE_COUNT as u8 {
let v = i64::from(e.count[edge as usize]);
if edge == lo || edge == hi {
uncrossed += v;
} else {
crossed += v;
}
}
let numerator = crossed - 2 * uncrossed;
if numerator <= 0 || numerator % 4 != 0 {
continue;
}
let q = numerator / 4;
if q > i64::from(u32::MAX) {
continue;
}
quad[pair] = q as u32;
for (edge, slot) in residual.iter_mut().enumerate() {
if edge as u8 != lo && edge as u8 != hi {
*slot -= q;
}
}
}
let Some(triangle) = triangles_from(&residual) else {
continue;
};
let candidate = NormalSurface { triangle, quad };
if candidate.edge_coordinates() == *e {
return Some(candidate);
}
}
None
}
fn triangles_from(residual: &[i64; TET_EDGE_COUNT]) -> Option<[u32; 4]> {
let e01 = residual[edge_between(0, 1) as usize];
let e02 = residual[edge_between(0, 2) as usize];
let e12 = residual[edge_between(1, 2) as usize];
let e03 = residual[edge_between(0, 3) as usize];
let doubled = [e01 + e02 - e12, e01 + e12 - e02, e02 + e12 - e01];
let mut triangle = [0u32; 4];
for (k, slot) in triangle.iter_mut().enumerate().take(3) {
if doubled[k] < 0 || doubled[k] % 2 != 0 {
return None;
}
let t = doubled[k] / 2;
if t > i64::from(u32::MAX) {
return None;
}
*slot = t as u32;
}
let t3 = e03 - i64::from(triangle[0]);
if t3 < 0 || t3 > i64::from(u32::MAX) {
return None;
}
triangle[3] = t3 as u32;
Some(triangle)
}
#[cfg(test)]
mod tests;