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, TET_FACES, 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 budget = tet.coordinates().total();
fill_append(tet, out, budget)
}
fn fill_append<R: Real>(
tet: &TetCrossings<'_, R>,
out: &mut TetPatch<R>,
budget: u32,
) -> Result<Unfilled, NotFillable> {
tet.check()?;
let coords = tet.coordinates();
let cycles = cycles(&coords);
let base = out.positions.len() as u32;
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| base + 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));
}
Some(NonNormalKind::Contractible) => {
unfilled = unfilled.worst(fill_boundary_disk(&coords, cycle, &index_of, out));
}
Some(NonNormalKind::Corner) => {
unfilled = unfilled.worst(fill_boundary_disk(&coords, cycle, &index_of, out));
}
None => unfilled = unfilled.worst(Unfilled::Inconsistent),
}
}
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(fill_subdivision(
tet,
&residual,
pattern,
&residual_loops,
out,
budget,
)?);
}
None => {}
}
Ok(unfilled)
}
fn fill_boundary_disk<R: Real>(
coords: &EdgeCoordinates,
cycle: &Cycle,
index_of: &impl Fn(FacePoint) -> u32,
out: &mut TetPatch<R>,
) -> Unfilled {
let inside_corner = match cycle.non_normal_kind() {
Some(NonNormalKind::Contractible) => None,
Some(NonNormalKind::Corner) => match cycle.distinguished_corner() {
Some(v) => Some(v),
None => return Unfilled::Inconsistent,
},
_ => return Unfilled::NonNormalLoop,
};
for face in 0..TET_FACE_COUNT as u8 {
let Some(regions) = face_regions(face, coords, cycle) else {
return Unfilled::Inconsistent;
};
for region in regions.iter().filter(|r| r.is_inside()) {
let mut corner = Vec::with_capacity(region.node.len());
for node in ®ion.node {
match node {
Node::Crossing(p) => corner.push(index_of(*p)),
Node::Corner(c) if Some(*c) == inside_corner => {}
Node::Corner(_) => return Unfilled::Inconsistent,
}
}
for k in 1..corner.len().saturating_sub(1) {
out.triangles.push([corner[0], corner[k], corner[k + 1]]);
}
}
}
if let Some(v) = inside_corner {
let mut cap = [0u32; 3];
for (slot, other) in cap.iter_mut().zip((0..4u8).filter(|c| *c != v)) {
let edge = super::coordinates::edge_between(v, other);
let [lo, _hi] = TET_EDGES[edge as usize];
let mut index: Vec<u32> = cycle
.points
.iter()
.filter(|p| p.edge == edge)
.map(|p| p.index)
.collect();
index.sort_unstable();
let nearest = if v == lo {
index.first().copied()
} else {
index.last().copied()
};
match nearest {
Some(at) => *slot = index_of(FacePoint { edge, index: at }),
None => return Unfilled::Inconsistent,
}
}
out.triangles.push(cap);
}
Unfilled::None
}
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 enum Arc {
Edge {
edge: u8,
piece: usize,
side: Side,
},
Chord,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Node {
Corner(u8),
Crossing(FacePoint),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Region {
pub arc: Vec<Arc>,
pub node: Vec<Node>,
}
impl Region {
#[must_use]
pub fn is_inside(&self) -> bool {
self.arc.iter().all(|a| match a {
Arc::Edge { side, .. } => *side == Side::Inside,
Arc::Chord => true,
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Side {
Inside,
Outside,
}
impl Side {
#[must_use]
pub const fn flipped(self) -> Self {
match self {
Self::Inside => Self::Outside,
Self::Outside => Self::Inside,
}
}
}
impl Cycle {
#[must_use]
pub fn distinguished_corner(&self) -> Option<u8> {
if self.non_normal_kind()? != NonNormalKind::Corner {
return None;
}
let mut own = [0u32; TET_EDGE_COUNT];
for point in &self.points {
own[point.edge as usize] += 1;
}
(0..4u8).find(|corner| {
(0..4u8).filter(|other| other != corner).all(|other| {
own[super::coordinates::edge_between(*corner, other) as usize] % 2 == 1
})
})
}
#[must_use]
pub fn corner_sides(&self) -> Option<[Side; 4]> {
match self.non_normal_kind()? {
NonNormalKind::Diagonal => None,
NonNormalKind::Contractible => Some([Side::Outside; 4]),
NonNormalKind::Corner => {
let distinguished = self.distinguished_corner()?;
let mut side = [Side::Outside; 4];
side[distinguished as usize] = Side::Inside;
Some(side)
}
}
}
#[must_use]
pub fn edge_sides(&self, edge: u8) -> Option<Vec<Side>> {
let sides = self.corner_sides()?;
let [lo, _hi] = TET_EDGES[edge as usize];
let crossings = self.points.iter().filter(|p| p.edge == edge).count();
let mut out = Vec::with_capacity(crossings + 1);
let mut side = sides[lo as usize];
out.push(side);
for _ in 0..crossings {
side = side.flipped();
out.push(side);
}
Some(out)
}
}
#[must_use]
pub fn face_regions(face: u8, coords: &EdgeCoordinates, cycle: &Cycle) -> Option<Vec<Region>> {
let f = TET_FACES[face as usize];
let sides: [Vec<Side>; 3] = [
cycle.edge_sides(f.edge[0])?,
cycle.edge_sides(f.edge[1])?,
cycle.edge_sides(f.edge[2])?,
];
let mut node: Vec<Node> = Vec::new();
let mut arc: Vec<Arc> = Vec::new();
for (k, side_of) in sides.iter().enumerate() {
let edge = f.edge[k];
let [lo, _hi] = TET_EDGES[edge as usize];
let forward = f.corner[k] == lo;
let mut index: Vec<u32> = cycle
.points
.iter()
.filter(|p| p.edge == edge)
.map(|p| p.index)
.collect();
index.sort_unstable();
if !forward {
index.reverse();
}
node.push(Node::Corner(f.corner[k]));
for (step, crossing) in index.iter().enumerate() {
let piece = if forward { step } else { index.len() - step };
arc.push(Arc::Edge {
edge,
piece,
side: *side_of.get(piece)?,
});
node.push(Node::Crossing(FacePoint {
edge,
index: *crossing,
}));
}
let piece = if forward { index.len() } else { 0 };
arc.push(Arc::Edge {
edge,
piece,
side: *side_of.get(piece)?,
});
}
let mut partner: Vec<Option<usize>> = alloc::vec![None; node.len()];
let position = |p: FacePoint| node.iter().position(|n| *n == Node::Crossing(p));
let mine: Vec<(FacePoint, FacePoint)> = {
let mut s: Vec<(FacePoint, FacePoint)> = (0..cycle.points.len())
.map(|k| {
let (a, b) = (cycle.points[k], cycle.points[(k + 1) % cycle.points.len()]);
if a <= b { (a, b) } else { (b, a) }
})
.collect();
s.sort_unstable();
s
};
for segment in super::curves::face_segments(face, coords) {
if mine.binary_search(&(segment.a, segment.b)).is_err() {
continue;
}
if let (Some(i), Some(j)) = (position(segment.a), position(segment.b)) {
partner[i] = Some(j);
partner[j] = Some(i);
}
}
Some(peel(node, arc, partner))
}
fn peel(mut node: Vec<Node>, mut arc: Vec<Arc>, mut partner: Vec<Option<usize>>) -> Vec<Region> {
let mut out = Vec::new();
loop {
let n = node.len();
let innermost = (0..n).find_map(|i| {
let j = partner[i]?;
let span = (j + n - i) % n;
if span == 0 {
return None;
}
let clear = (1..span).all(|s| partner[(i + s) % n].is_none());
clear.then_some((i, j, span))
});
let Some((i, j, span)) = innermost else {
break;
};
let mut region = Region {
arc: Vec::with_capacity(span + 1),
node: Vec::with_capacity(span + 1),
};
for s in 0..span {
region.node.push(node[(i + s) % n]);
region.arc.push(arc[(i + s) % n]);
}
region.node.push(node[j]);
region.arc.push(Arc::Chord);
out.push(region);
let mut kept_node = Vec::with_capacity(n - span + 1);
let mut kept_arc = Vec::with_capacity(n - span + 1);
let mut kept_partner = Vec::with_capacity(n - span + 1);
kept_node.push(node[i]);
kept_arc.push(Arc::Chord);
kept_partner.push(None);
for s in span..n {
let at = (i + s) % n;
kept_node.push(node[at]);
kept_arc.push(arc[at]);
kept_partner.push(partner[at]);
}
let remap: Vec<Option<usize>> = kept_partner
.iter()
.map(|p| {
p.and_then(|old| {
let original = |at: usize| (i + at) % n;
(0..kept_node.len()).find(|k| *k > 0 && original(span + k - 1) == old)
})
})
.collect();
node = kept_node;
arc = kept_arc;
partner = remap;
let _ = j;
}
if !arc.is_empty() {
out.push(Region { arc, node });
}
out
}
#[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_subdivision<R: Real>(
tet: &TetCrossings<'_, R>,
residual: &EdgeCoordinates,
pattern: Pattern,
loops: &[&Cycle],
out: &mut TetPatch<R>,
budget: u32,
) -> Result<Unfilled, NotFillable> {
if budget == 0 {
return Ok(Unfilled::Subdivision);
}
let Some(stencil) = Subdivision::label(residual, pattern) else {
return Ok(Unfilled::NoPattern);
};
let quarter = R::from_f64(0.25);
let mut centre = [R::ZERO; 3];
for corner in &tet.corners {
for (slot, value) in centre.iter_mut().zip(corner.iter()) {
*slot += *value;
}
}
for slot in &mut centre {
*slot *= quarter;
}
let mut kept: [Vec<R>; TET_EDGE_COUNT] = Default::default();
for (e, slot) in kept.iter_mut().enumerate() {
let edge = e as u8;
let mut index: Vec<u32> = loops
.iter()
.flat_map(|c| c.points.iter())
.filter(|p| p.edge == edge)
.map(|p| p.index)
.collect();
index.sort_unstable();
index.dedup();
for at in index {
match tet.along[e].get(at as usize) {
Some(t) => slot.push(*t),
None => return Ok(Unfilled::Inconsistent),
}
}
}
let spoke = stencil.spoke();
let mut unfilled = Unfilled::None;
for which in 0..4usize {
let mut surviving = [0u8; 3];
let mut at = 0;
for (s, corner) in stencil.corner.iter().enumerate() {
if s != which {
surviving[at] = *corner;
at += 1;
}
}
let mut corners = [centre; 4];
for (slot, corner) in corners.iter_mut().skip(1).zip(surviving.iter()) {
*slot = tet.corners[*corner as usize];
}
let mut along: [Vec<R>; TET_EDGE_COUNT] = Default::default();
for (e, slot) in along.iter_mut().enumerate() {
let [lo, hi] = TET_EDGES[e];
if lo == 0 {
let corner = surviving[hi as usize - 1];
let Some(position) = stencil.corner.iter().position(|c| *c == corner) else {
return Ok(Unfilled::Inconsistent);
};
let count = spoke[position];
for i in 0..count {
let t = R::from_f64(f64::from(i) + 1.0) / R::from_f64(f64::from(count) + 1.0);
slot.push(t);
}
} else {
let (a, b) = (surviving[lo as usize - 1], surviving[hi as usize - 1]);
let parent = super::coordinates::edge_between(a, b);
let [plo, _phi] = TET_EDGES[parent as usize];
let source = &kept[parent as usize];
if a == plo {
slot.extend_from_slice(source);
} else {
slot.extend(source.iter().rev().map(|t| R::ONE - *t));
}
}
}
let mut borrowed: [&[R]; TET_EDGE_COUNT] = [&[]; TET_EDGE_COUNT];
for (slot, v) in borrowed.iter_mut().zip(along.iter()) {
*slot = v.as_slice();
}
let child = TetCrossings {
corners,
along: borrowed,
};
let Some(predicted) = stencil.sub_tet(residual, which) else {
return Ok(Unfilled::Inconsistent);
};
if child.coordinates() != predicted {
return Ok(Unfilled::Inconsistent);
}
unfilled = unfilled.worst(fill_append(&child, out, budget - 1)?);
}
Ok(unfilled)
}
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;