use alloc::vec::Vec;
use crate::marching_tetrahedra::table::{TET_EDGE_COUNT, TET_EDGES};
use crate::real::Real;
use super::coordinates::{EdgeCoordinates, TET_FACE_COUNT, complementary};
use super::curves::{Curve, CurveKind, FacePoint, Segment, curves};
#[derive(Clone, Copy, Debug)]
pub struct TetCrossings<'a, R: Real> {
pub corners: [[R; 3]; 4],
pub along: [&'a [R]; TET_EDGE_COUNT],
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum NotFillable {
Unsorted {
edge: u8,
at: usize,
},
OffEdge {
edge: u8,
at: usize,
},
}
impl<R: Real> TetCrossings<'_, R> {
#[must_use]
pub fn coordinates(&self) -> EdgeCoordinates {
let mut count = [0u32; TET_EDGE_COUNT];
for (slot, along) in count.iter_mut().zip(self.along.iter()) {
*slot = u32::try_from(along.len()).unwrap_or(u32::MAX);
}
EdgeCoordinates { count }
}
pub fn check(&self) -> Result<(), NotFillable> {
for (e, along) in self.along.iter().enumerate() {
let edge = e as u8;
for (i, t) in along.iter().enumerate() {
if !t.is_finite() || *t < R::ZERO || *t > R::ONE {
return Err(NotFillable::OffEdge { edge, at: i });
}
if i > 0 && *t <= along[i - 1] {
return Err(NotFillable::Unsorted { edge, at: i });
}
}
}
Ok(())
}
#[must_use]
pub fn position(&self, point: FacePoint) -> Option<[R; 3]> {
let [lo, hi] = TET_EDGES[point.edge as usize];
let t = *self.along[point.edge as usize].get(point.index as usize)?;
let (a, b) = (self.corners[lo as usize], self.corners[hi as usize]);
Some([
a[0] + (b[0] - a[0]) * t,
a[1] + (b[1] - a[1]) * t,
a[2] + (b[2] - a[2]) * t,
])
}
}
#[derive(Clone, Debug, Default)]
pub struct TetPatch<R: Real> {
pub positions: Vec<[R; 3]>,
pub triangles: Vec<[u32; 3]>,
}
impl<R: Real> TetPatch<R> {
#[must_use]
pub const fn new() -> Self {
Self {
positions: Vec::new(),
triangles: Vec::new(),
}
}
pub fn reset(&mut self) {
self.positions.clear();
self.triangles.clear();
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.triangles.is_empty()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Cycle {
pub kind: CurveKind,
pub points: Vec<FacePoint>,
}
impl Cycle {
#[must_use]
pub fn length(&self) -> usize {
self.points.len()
}
#[must_use]
pub fn is_corner_cut(&self) -> bool {
self.kind == CurveKind::Normal && self.points.len() == 3
}
}
#[must_use]
pub fn cycles(coords: &EdgeCoordinates) -> Vec<Cycle> {
let mut out = Vec::new();
for curve in curves(coords) {
if curve.kind == CurveKind::Open {
continue;
}
if let Some(points) = walk(&curve) {
out.push(Cycle {
kind: curve.kind,
points,
});
}
}
out
}
fn walk(curve: &Curve) -> Option<Vec<FacePoint>> {
let segments: &[Segment] = &curve.segments;
if segments.is_empty() {
return None;
}
let mut used = alloc::vec![false; segments.len()];
let start = segments[0].a;
let mut points = Vec::with_capacity(segments.len());
let mut current = start;
for _ in 0..segments.len() {
points.push(current);
let next = segments.iter().enumerate().find_map(|(i, s)| {
if used[i] {
return None;
}
if s.a == current {
Some((i, s.b))
} else if s.b == current {
Some((i, s.a))
} else {
None
}
})?;
used[next.0] = true;
current = next.1;
}
if current == start && used.iter().all(|u| *u) {
Some(points)
} else {
None
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Pattern {
pub d1: u32,
pub d2: u32,
}
impl Pattern {
#[must_use]
pub fn of(residual: &EdgeCoordinates) -> Option<Self> {
let mut pair = [0u32; 3];
for (e, slot) in pair.iter_mut().enumerate() {
let edge = e as u8;
let (a, b) = (residual.edge(edge), residual.edge(complementary(edge)));
if a != b {
return None;
}
*slot = a;
}
pair.sort_unstable();
if pair[0] + pair[1] != pair[2] {
return None;
}
Some(Self {
d1: pair[1],
d2: pair[0],
})
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.d1 == 0 && self.d2 == 0
}
#[must_use]
pub fn loop_count(&self) -> u32 {
gcd(self.d1, self.d2)
}
#[must_use]
pub fn loop_length(&self) -> Option<u32> {
let g = self.loop_count();
if g == 0 {
return None;
}
Some(4 * (self.d1 + self.d2) / g)
}
}
const fn gcd(a: u32, b: u32) -> u32 {
let (mut a, mut b) = (a, b);
while b != 0 {
let t = b;
b = a % b;
a = t;
}
a
}
#[must_use]
pub fn residual(cycles: &[Cycle]) -> EdgeCoordinates {
let mut count = [0u32; TET_EDGE_COUNT];
for cycle in cycles
.iter()
.filter(|c| c.kind == CurveKind::Normal && !c.is_corner_cut())
{
for point in &cycle.points {
count[point.edge as usize] += 1;
}
}
EdgeCoordinates { count }
}
pub fn fill<R: Real>(
tet: &TetCrossings<'_, R>,
out: &mut TetPatch<R>,
) -> Result<Unfilled, NotFillable> {
tet.check()?;
out.reset();
let coords = tet.coordinates();
let cycles = cycles(&coords);
let mut keys: Vec<FacePoint> = cycles
.iter()
.flat_map(|c| c.points.iter().copied())
.collect();
keys.sort_unstable();
keys.dedup();
for key in &keys {
match tet.position(*key) {
Some(p) => out.positions.push(p),
None => return Ok(Unfilled::Inconsistent),
}
}
let index_of = |p: FacePoint| -> u32 {
keys.binary_search(&p).map_or(u32::MAX, |i| i as u32)
};
let mut unfilled = Unfilled::None;
for cut in cycles.iter().filter(|c| c.is_corner_cut()) {
out.triangles.push([
index_of(cut.points[0]),
index_of(cut.points[1]),
index_of(cut.points[2]),
]);
}
for cycle in cycles.iter().filter(|c| c.kind == CurveKind::NonNormal) {
match cycle.non_normal_kind() {
Some(NonNormalKind::Diagonal) => {
unfilled = unfilled.worst(fill_centroid_fan(cycle, &index_of, out));
}
_ => unfilled = unfilled.worst(Unfilled::NonNormalLoop),
}
}
let residual_loops: Vec<&Cycle> = cycles
.iter()
.filter(|c| c.kind == CurveKind::Normal && !c.is_corner_cut())
.collect();
let residual = residual(&cycles);
let Some(pattern) = Pattern::of(&residual) else {
return Ok(unfilled.worst(Unfilled::NoPattern));
};
if pattern.is_empty() {
return Ok(unfilled);
}
match pattern.loop_length() {
Some(4) => {
for cycle in &residual_loops {
let v: Vec<u32> = cycle.points.iter().map(|p| index_of(*p)).collect();
out.triangles.push([v[0], v[1], v[2]]);
out.triangles.push([v[0], v[2], v[3]]);
}
}
Some(8) => {
unfilled = unfilled.worst(fill_octagons(
tet,
&residual,
pattern,
&residual_loops,
&index_of,
out,
));
}
Some(_) if pattern.loop_count() == 1 => {
unfilled = unfilled.worst(fill_single_loop(&residual_loops, &index_of, out));
}
Some(_) => unfilled = unfilled.worst(Unfilled::Subdivision),
None => {}
}
Ok(unfilled)
}
fn fill_centroid_fan<R: Real>(
cycle: &Cycle,
index_of: &impl Fn(FacePoint) -> u32,
out: &mut TetPatch<R>,
) -> Unfilled {
let n = cycle.points.len();
if n < 3 {
return Unfilled::Inconsistent;
}
let mut sum = [R::ZERO; 3];
for point in &cycle.points {
let Some(index) = usize::try_from(index_of(*point))
.ok()
.filter(|i| *i < out.positions.len())
else {
return Unfilled::Inconsistent;
};
let p = out.positions[index];
for (slot, value) in sum.iter_mut().zip(p.iter()) {
*slot += *value;
}
}
let scale = R::ONE / R::from_f64(n as f64);
let steiner = out.positions.len() as u32;
out.positions
.push([sum[0] * scale, sum[1] * scale, sum[2] * scale]);
fan(cycle, steiner, index_of, out);
Unfilled::None
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum NonNormalKind {
Contractible,
Diagonal,
Corner,
}
impl Cycle {
#[must_use]
pub fn non_normal_kind(&self) -> Option<NonNormalKind> {
if self.kind != CurveKind::NonNormal {
return None;
}
let mut own = [0u32; TET_EDGE_COUNT];
for point in &self.points {
own[point.edge as usize] += 1;
}
let p: u32 = own[0] % 2 + own[1] % 2 + own[2] % 2;
Some(match p {
0 => NonNormalKind::Contractible,
2 => NonNormalKind::Diagonal,
_ => NonNormalKind::Corner,
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Subdivision {
pub corner: [u8; 4],
pub pattern: Pattern,
}
impl Subdivision {
#[must_use]
pub fn label(residual: &EdgeCoordinates, pattern: Pattern) -> Option<Self> {
let (d1, d2) = (pattern.d1, pattern.d2);
let e = |a: u8, b: u8| residual.edge(super::coordinates::edge_between(a, b));
for i in 0..4u8 {
for j in 0..4u8 {
if j == i {
continue;
}
for k in 0..4u8 {
if k == i || k == j {
continue;
}
let l = 6 - i - j - k;
if e(i, j) == d1
&& e(k, l) == d1
&& e(i, k) == d2
&& e(j, l) == d2
&& e(i, l) == d1 + d2
&& e(j, k) == d1 + d2
{
return Some(Self {
corner: [i, j, k, l],
pattern,
});
}
}
}
}
None
}
#[must_use]
pub fn spoke(&self) -> [u32; 4] {
let (d1, d2) = (self.pattern.d1, self.pattern.d2);
[2 * d2, d1, d2, d1 - d2]
}
#[must_use]
pub fn sub_tet(&self, residual: &EdgeCoordinates, which: usize) -> Option<EdgeCoordinates> {
if which >= 4 {
return None;
}
let spoke = self.spoke();
let mut kept = [0u8; 3];
let mut slot = 0;
for (s, corner) in self.corner.iter().enumerate() {
if s != which {
kept[slot] = *corner;
slot += 1;
}
}
let mut spoke_of = [0u32; 3];
for (s, value) in spoke_of.iter_mut().enumerate() {
let stencil = self.corner.iter().position(|c| *c == kept[s])?;
*value = spoke[stencil];
}
let mut count = [0u32; TET_EDGE_COUNT];
for (local_lo, local_hi) in (0..4u8).flat_map(|a| (a + 1..4u8).map(move |b| (a, b))) {
let edge = super::coordinates::edge_between(local_lo, local_hi) as usize;
count[edge] = if local_lo == 0 {
spoke_of[local_hi as usize - 1]
} else {
residual.edge(super::coordinates::edge_between(
kept[local_lo as usize - 1],
kept[local_hi as usize - 1],
))
};
}
Some(EdgeCoordinates { count })
}
}
fn fill_single_loop<R: Real>(
loops: &[&Cycle],
index_of: &impl Fn(FacePoint) -> u32,
out: &mut TetPatch<R>,
) -> Unfilled {
let [cycle] = loops else {
return Unfilled::Inconsistent;
};
fill_centroid_fan(cycle, index_of, out)
}
fn fan<R: Real>(
cycle: &Cycle,
steiner: u32,
index_of: &impl Fn(FacePoint) -> u32,
out: &mut TetPatch<R>,
) {
let n = cycle.points.len();
for k in 0..n {
let a = index_of(cycle.points[k]);
let b = index_of(cycle.points[(k + 1) % n]);
out.triangles.push([a, b, steiner]);
}
}
fn fill_octagons<R: Real>(
tet: &TetCrossings<'_, R>,
residual: &EdgeCoordinates,
pattern: Pattern,
loops: &[&Cycle],
index_of: &impl Fn(FacePoint) -> u32,
out: &mut TetPatch<R>,
) -> Unfilled {
let m = pattern.loop_count();
if m == 0 || pattern.d1 != pattern.d2 || loops.len() as u32 != m {
return Unfilled::Inconsistent;
}
let Some(e) = (0..TET_EDGE_COUNT as u8).find(|e| {
*e < complementary(*e)
&& residual.edge(*e) == 2 * m
&& residual.edge(complementary(*e)) == 2 * m
}) else {
return Unfilled::Inconsistent;
};
let opposite = complementary(e);
let mut p: Vec<u32> = loops
.iter()
.flat_map(|c| c.points.iter())
.filter(|q| q.edge == e)
.map(|q| q.index)
.collect();
p.sort_unstable();
p.dedup();
if p.len() as u32 != 2 * m {
return Unfilled::Inconsistent;
}
let half = R::from_f64(0.5);
let midpoint = |edge: u8| -> [R; 3] {
let [lo, hi] = TET_EDGES[edge as usize];
let (a, b) = (tet.corners[lo as usize], tet.corners[hi as usize]);
[
(a[0] + b[0]) * half,
(a[1] + b[1]) * half,
(a[2] + b[2]) * half,
]
};
let (from, to) = (midpoint(e), midpoint(opposite));
let steiner_base = out.positions.len() as u32;
let step = R::ONE / R::from_f64(f64::from(m) + 1.0);
for i in 0..m {
let t = step * R::from_f64(f64::from(i) + 1.0);
out.positions.push([
from[0] + (to[0] - from[0]) * t,
from[1] + (to[1] - from[1]) * t,
from[2] + (to[2] - from[2]) * t,
]);
}
let mut assigned = Vec::with_capacity(loops.len());
for cycle in loops {
if cycle.length() != 8 {
return Unfilled::Inconsistent;
}
let mut rank: Vec<u32> = cycle
.points
.iter()
.filter(|q| q.edge == e)
.filter_map(|q| p.binary_search(&q.index).ok().map(|r| r as u32))
.collect();
rank.sort_unstable();
let [low, high] = match rank[..] {
[low, high] => [low, high],
_ => return Unfilled::Inconsistent,
};
if low + high != 2 * m - 1 || high < m {
return Unfilled::Inconsistent;
}
assigned.push((cycle, steiner_base + (high - m)));
}
for (cycle, steiner) in assigned {
fan(cycle, steiner, index_of, out);
}
Unfilled::None
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Unfilled {
None,
SingleLoop,
Subdivision,
NonNormalLoop,
NoPattern,
Inconsistent,
}
impl Unfilled {
fn worst(self, other: Self) -> Self {
if other > self { other } else { self }
}
}
const _: () = assert!(TET_FACE_COUNT == 4);
#[cfg(test)]
mod tests;