use alloc::vec::Vec;
use crate::marching_tetrahedra::table::TET_EDGES;
use super::coordinates::{EdgeCoordinates, TET_FACES};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct FacePoint {
pub edge: u8,
pub index: u32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Segment {
pub a: FacePoint,
pub b: FacePoint,
}
impl Segment {
fn new(a: FacePoint, b: FacePoint) -> Self {
if a <= b {
Self { a, b }
} else {
Self { a: b, b: a }
}
}
#[must_use]
pub fn is_scoop(&self) -> bool {
self.a.edge == self.b.edge
}
}
fn from_corner(edge: u8, from: u8, t: u32, count: u32) -> FacePoint {
let [lo, _hi] = TET_EDGES[edge as usize];
let index = if from == lo { t } else { count - 1 - t };
FacePoint { edge, index }
}
#[must_use]
pub fn face_segments(face: u8, coords: &EdgeCoordinates) -> Vec<Segment> {
let f = TET_FACES[face as usize];
let count: [u32; 3] = [
coords.edge(f.edge[0]),
coords.edge(f.edge[1]),
coords.edge(f.edge[2]),
];
let mut out = Vec::new();
let total = count[0] + count[1] + count[2];
let violated = (0..3).find(|k| count[*k] > count[(*k + 1) % 3] + count[(*k + 2) % 3]);
let (reduced, residual_edge) = match violated {
Some(k) => {
let mut r = count;
r[k] = count[(k + 1) % 3] + count[(k + 2) % 3];
(r, Some(k))
}
None if !total.is_multiple_of(2) => ([count[0] - 1, count[1] - 1, count[2] - 1], None),
None => (count, None),
};
let mut corner = [0u32; 3];
for (k, slot) in corner.iter_mut().enumerate() {
let adjacent = reduced[k] + reduced[(k + 2) % 3];
let opposite = reduced[(k + 1) % 3];
debug_assert!(adjacent >= opposite && (adjacent - opposite) % 2 == 0);
*slot = (adjacent - opposite) / 2;
}
for k in 0..3usize {
let at = f.corner[k];
let (leaving, arriving) = (f.edge[k], f.edge[(k + 2) % 3]);
for t in 0..corner[k] {
out.push(Segment::new(
from_corner(leaving, at, t, count[k]),
from_corner(arriving, at, t, count[(k + 2) % 3]),
));
}
}
if let Some(k) = residual_edge {
let edge = f.edge[k];
let n = count[k];
let low = from_corner(edge, f.corner[k], corner[k], n).index;
let high = from_corner(edge, f.corner[(k + 1) % 3], corner[(k + 1) % 3], n).index;
let (first, last) = if low <= high {
(low, high)
} else {
(high, low)
};
let mut point = first;
let r = last + 1 - first;
if r % 2 != 0 {
point += 1;
}
while point < last {
out.push(Segment::new(
FacePoint { edge, index: point },
FacePoint {
edge,
index: point + 1,
},
));
point += 2;
}
}
out.sort_unstable();
out
}
#[must_use]
pub fn boundary_segments(coords: &EdgeCoordinates) -> Vec<Segment> {
let mut out = Vec::new();
for face in 0..TET_FACES.len() as u8 {
out.extend(face_segments(face, coords));
}
out.sort_unstable();
out
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CurveKind {
Open,
Normal,
NonNormal,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Curve {
pub kind: CurveKind,
pub segments: Vec<Segment>,
}
#[must_use]
pub fn curves(coords: &EdgeCoordinates) -> Vec<Curve> {
let segments = boundary_segments(coords);
let mut points: Vec<FacePoint> = segments.iter().flat_map(|s| [s.a, s.b]).collect();
points.sort_unstable();
points.dedup();
let degree = |p: FacePoint| -> usize {
segments
.iter()
.filter(|s| s.a == p || s.b == p)
.map(|s| usize::from(s.a == p) + usize::from(s.b == p))
.sum()
};
let mut unassigned: Vec<usize> = (0..segments.len()).collect();
let mut out = Vec::new();
while let Some(seed) = unassigned.pop() {
let mut group = alloc::vec![seed];
let mut frontier = alloc::vec![seed];
while let Some(current) = frontier.pop() {
let s = segments[current];
let mut still = Vec::new();
for other in unassigned.drain(..) {
let t = segments[other];
if t.a == s.a || t.a == s.b || t.b == s.a || t.b == s.b {
group.push(other);
frontier.push(other);
} else {
still.push(other);
}
}
unassigned = still;
}
let mut mine: Vec<Segment> = group.into_iter().map(|i| segments[i]).collect();
mine.sort_unstable();
let ends: Vec<FacePoint> = {
let mut p: Vec<FacePoint> = mine.iter().flat_map(|s| [s.a, s.b]).collect();
p.sort_unstable();
p.dedup();
p
};
let kind = if ends.iter().any(|p| degree(*p) < 2) {
CurveKind::Open
} else if mine.iter().any(Segment::is_scoop) {
CurveKind::NonNormal
} else {
CurveKind::Normal
};
out.push(Curve {
kind,
segments: mine,
});
}
out.sort_by(|a, b| a.segments.cmp(&b.segments));
out
}
#[cfg(test)]
mod tests;