#[cfg(test)]
mod tests;
use crate::cube::{EDGE_AXIS, EDGE_CORNERS, EDGE_COUNT, corner_offset, edge_offset, face_corners};
use crate::real::Real;
use super::table::{NO_EDGE, face_bit, segment_links};
pub const SADDLE_COUNT: usize = 2;
const fn coordinate_bit(axis: usize, solution: usize) -> u8 {
1 << (2 * axis + solution)
}
pub const ALL_INSIDE: u8 = 0b0011_1111;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct BodySaddles<R> {
coordinate: [[R; SADDLE_COUNT]; 3],
inside: u8,
}
impl<R: Real> BodySaddles<R> {
#[must_use]
pub fn of(corner: &[R; 8]) -> Self {
let [a, b, c] = Self::coefficients(corner);
let (u, roots) = Self::roots(a, b, c);
let mut v = [R::ZERO; SADDLE_COUNT];
let mut w = [R::ZERO; SADDLE_COUNT];
for ((vk, wk), &u) in v.iter_mut().zip(w.iter_mut()).zip(u.iter()).take(roots) {
*vk = Self::level_crossing(corner[0], corner[1], corner[2], corner[3], u);
*wk = Self::level_crossing(corner[0], corner[1], corner[4], corner[5], u);
}
let coordinate = [u, v, w];
let mut inside = 0u8;
for (axis, values) in coordinate.iter().enumerate() {
for (k, &value) in values.iter().enumerate().take(roots) {
if value > R::ZERO && value < R::ONE {
inside |= coordinate_bit(axis, k);
}
}
}
Self { coordinate, inside }
}
#[must_use]
pub fn coefficients(corner: &[R; 8]) -> [R; 3] {
let twist_lo = (corner[0] + corner[3]) - (corner[1] + corner[2]);
let twist_hi = (corner[4] + corner[7]) - (corner[5] + corner[6]);
let du_lo = corner[1] - corner[0];
let du_hi = corner[5] - corner[4];
let dv_lo = corner[2] - corner[0];
let dv_hi = corner[6] - corner[4];
let a = du_hi * twist_lo - du_lo * twist_hi;
let b = (corner[4] * twist_lo - corner[0] * twist_hi) + (du_hi * dv_lo - du_lo * dv_hi);
let c = corner[2] * corner[4] - corner[0] * corner[6];
[a, b, c]
}
fn roots(a: R, b: R, c: R) -> ([R; SADDLE_COUNT], usize) {
let mut roots = [R::ZERO; SADDLE_COUNT];
if a == R::ZERO {
if b == R::ZERO {
return (roots, 0);
}
roots[0] = -c / b;
return (roots, 1);
}
let discriminant = b * b - R::TWO * R::TWO * a * c;
if discriminant < R::ZERO {
return (roots, 0);
}
if discriminant == R::ZERO {
roots[0] = -b / (R::TWO * a);
return (roots, 1);
}
let q = -(b + b.signum() * discriminant.sqrt()) * R::HALF;
roots[0] = q / a;
roots[1] = c / q;
(roots, SADDLE_COUNT)
}
fn level_crossing(lo0: R, lo1: R, hi0: R, hi1: R, u: R) -> R {
let s = R::ONE - u;
let lo = lo0 * s + lo1 * u;
let hi = hi0 * s + hi1 * u;
-lo / (hi - lo)
}
#[must_use]
pub const fn inside_mask(&self) -> u8 {
self.inside
}
#[must_use]
pub const fn inside_count(&self) -> u32 {
self.inside.count_ones()
}
#[must_use]
pub const fn has_inner_hexagon(&self) -> bool {
self.inside == ALL_INSIDE
}
#[must_use]
pub fn axis(&self, axis: usize) -> [R; SADDLE_COUNT] {
self.coordinate[axis]
}
#[must_use]
pub fn inner_hexagon(&self) -> Option<[[R; 3]; 6]> {
if !self.has_inner_hexagon() {
return None;
}
let [u, v, w] = self.coordinate;
Some([
[u[0], v[0], w[0]],
[u[0], v[0], w[1]],
[u[1], v[0], w[1]],
[u[1], v[1], w[1]],
[u[1], v[1], w[0]],
[u[0], v[1], w[0]],
])
}
}
#[must_use]
pub fn singular_face_mask<R: Real>(corner_value: &[R; 8]) -> u8 {
let mut mask = 0u8;
for axis in 0..3usize {
for side in 0..2u8 {
let c = face_corners(axis, side);
let v = [
corner_value[c[0] as usize],
corner_value[c[1] as usize],
corner_value[c[2] as usize],
corner_value[c[3] as usize],
];
if v[0] * v[2] == v[1] * v[3] {
mask |= face_bit(axis, side);
}
}
}
mask
}
pub const MAX_TUNNEL_CONTOUR: usize = 6;
pub const MAX_CONTOURS: usize = 4;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Contours {
edges: [u8; EDGE_COUNT],
start: [u8; MAX_CONTOURS + 1],
count: u8,
}
impl Contours {
#[must_use]
pub fn of(case: u8, joined: u8) -> Self {
let next = segment_links(case, joined);
let mut edges = [NO_EDGE; EDGE_COUNT];
let mut start = [0u8; MAX_CONTOURS + 1];
let mut count = 0usize;
let mut filled = 0usize;
let mut visited = 0u16;
for first in 0..EDGE_COUNT as u8 {
if next[first as usize] == NO_EDGE || visited & (1 << first) != 0 {
continue;
}
debug_assert!(count < MAX_CONTOURS, "more rings than a cell can hold");
start[count] = filled as u8;
count += 1;
let mut current = first;
while visited & (1 << current) == 0 {
visited |= 1 << current;
edges[filled] = current;
filled += 1;
current = next[current as usize];
}
}
start[count] = filled as u8;
Self {
edges,
start,
count: count as u8,
}
}
#[must_use]
pub const fn count(&self) -> usize {
self.count as usize
}
#[must_use]
pub fn ring(&self, index: usize) -> &[u8] {
assert!(index < self.count(), "contour {index} does not exist");
let (lo, hi) = (self.start[index] as usize, self.start[index + 1] as usize);
&self.edges[lo..hi]
}
#[must_use]
pub fn longest(&self) -> usize {
(0..self.count())
.map(|i| self.ring(i).len())
.max()
.unwrap_or(0)
}
#[must_use]
pub fn topology<R: Real>(&self, saddles: &BodySaddles<R>) -> Topology {
if !saddles.has_inner_hexagon() {
return Topology::Disks;
}
match self.count() {
0 | 1 => Topology::TwelveVertexContour,
_ if self.longest() <= MAX_TUNNEL_CONTOUR => Topology::Tunnel,
_ => Topology::SeparateDisks,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Topology {
Disks,
Tunnel,
TwelveVertexContour,
SeparateDisks,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Pair {
U = 0,
V = 1,
W = 2,
}
impl Pair {
pub const ALL: [Pair; 3] = [Pair::U, Pair::V, Pair::W];
}
impl<R: Real> BodySaddles<R> {
#[must_use]
pub fn lines(&self, pair: Pair) -> u8 {
let inside = self.inside;
let needed: [[(usize, usize); 2]; 2] = match pair {
Pair::U => [[(1, 1), (2, 0)], [(1, 0), (2, 1)]],
Pair::V => [[(0, 0), (2, 0)], [(0, 1), (2, 1)]],
Pair::W => [[(0, 0), (1, 0)], [(0, 1), (1, 1)]],
};
let mut mask = 0u8;
for (line, coordinates) in needed.iter().enumerate() {
if coordinates
.iter()
.all(|&(axis, k)| inside & coordinate_bit(axis, k) != 0)
{
mask |= 1 << line;
}
}
mask
}
#[must_use]
pub fn line_count(&self, pair: Pair) -> u32 {
self.lines(pair).count_ones()
}
#[must_use]
pub fn same_asymptote_side(&self, corner: &[R; 8]) -> bool {
if !self.has_inner_hexagon() {
return false;
}
let [u, v, _] = self.coordinate;
const FACE: [([usize; 4], usize); 6] = [
([0, 1, 2, 3], 0), ([4, 5, 6, 7], 0), ([0, 1, 4, 5], 0), ([2, 3, 6, 7], 0), ([0, 2, 4, 6], 1), ([1, 3, 5, 7], 1), ];
for ([a, b, c, d], axis) in FACE {
let twist = (corner[a] + corner[d]) - (corner[b] + corner[c]);
let offset = corner[a] - corner[c];
let s = if axis == 0 { u } else { v };
let lo = twist * s[0] - offset;
let hi = twist * s[1] - offset;
if lo * hi < R::ZERO {
return false;
}
}
true
}
#[cfg(test)]
pub(super) fn point_on_line_for_test(&self, pair: Pair, line: usize, along: R) -> [R; 3] {
let [u, v, w] = self.coordinate;
match pair {
Pair::U if line == 0 => [along, v[1], w[0]],
Pair::U => [along, v[0], w[1]],
Pair::V if line == 0 => [u[0], along, w[0]],
Pair::V => [u[1], along, w[1]],
Pair::W if line == 0 => [u[0], v[0], along],
Pair::W => [u[1], v[1], along],
}
}
}
impl<R: Real> BodySaddles<R> {
#[must_use]
pub fn interior_vertex(&self) -> Option<[R; 3]> {
let set = |axis: usize, k: usize| self.inside & coordinate_bit(axis, k) != 0;
let f = |axis: usize, k: usize, x: R| if set(axis, k) { x } else { R::ZERO };
let one = |axis: usize, k: usize| if set(axis, k) { R::ONE } else { R::ZERO };
let count = |a: (usize, usize), b: (usize, usize)| u8::from(set(a.0, a.1) && set(b.0, b.1));
let fc_w = count((0, 0), (1, 0)) + count((0, 1), (1, 1));
let fc_v = count((0, 0), (2, 0)) + count((0, 1), (2, 1));
let fc_u = count((1, 0), (2, 1)) + count((1, 1), (2, 0));
let total = fc_w + fc_v + fc_u;
if total == fc_w || total == fc_v || total == fc_u {
return None;
}
let [u, v, w] = self.coordinate;
let (uc, vc, wc) = match total {
2 if fc_w == 0 => (
f(0, 0, u[0]) + f(0, 1, u[1]),
f(1, 0, v[0]) + f(1, 1, v[1]),
f(1, 0, w[1]) + f(1, 1, w[0]),
),
2 if fc_v == 0 => (
f(0, 0, u[0]) + f(0, 1, u[1]),
f(0, 0, v[0]) + f(0, 1, v[1]),
f(0, 0, w[1]) + f(0, 1, w[0]),
),
2 => (
f(1, 0, u[0]) + f(1, 1, u[1]),
f(1, 0, v[0]) + f(1, 1, v[1]),
f(1, 0, w[0]) + f(1, 1, w[1]),
),
3 => (
(f(0, 0, u[0]) + f(0, 1, u[1])) / (one(0, 0) + one(0, 1)),
(f(1, 0, v[0]) + f(1, 1, v[1])) / (one(1, 0) + one(1, 1)),
(f(2, 0, w[0]) + f(2, 1, w[1])) / (one(2, 0) + one(2, 1)),
),
4 => {
let on = |axis: usize| one(axis, 0) + one(axis, 1);
if on(2) == R::ONE {
(
f(2, 0, u[0]) + f(2, 1, u[1]),
f(2, 1, v[0]) + f(2, 0, v[1]),
f(2, 0, w[0]) + f(2, 1, w[1]),
)
} else if on(1) == R::ONE {
(
f(1, 0, u[0]) + f(1, 1, u[1]),
f(1, 0, v[0]) + f(1, 1, v[1]),
f(1, 1, w[0]) + f(1, 0, w[1]),
)
} else {
(
f(0, 0, u[0]) + f(0, 1, u[1]),
f(0, 0, v[0]) + f(0, 1, v[1]),
f(0, 0, w[0]) + f(0, 1, w[1]),
)
}
}
_ => return None,
};
Some([uc, vc, wc])
}
}
pub const INTERIOR: u8 = EDGE_COUNT as u8;
impl Contours {
pub fn fan(&self, interior: bool, mut emit: impl FnMut([u8; 3])) {
for r in 0..self.count() {
let ring = self.ring(r);
if ring.len() == 3 {
emit([ring[0], ring[1], ring[2]]);
} else if interior {
let apex = INTERIOR + r as u8;
for k in 0..ring.len() {
emit([ring[k], ring[(k + 1) % ring.len()], apex]);
}
} else {
for k in 1..ring.len() - 1 {
emit([ring[0], ring[k], ring[k + 1]]);
}
}
}
}
#[must_use]
pub fn triangle_count(&self, interior: bool) -> usize {
(0..self.count())
.map(|r| {
let k = self.ring(r).len();
if k == 3 {
1
} else if interior {
k
} else {
k - 2
}
})
.sum()
}
}
pub const MAX_INTERIOR_VERTICES: usize = 6;
#[must_use]
pub fn local_crossing<R: Real>(edge: u8, corner: &[R; 8]) -> [R; 3] {
let [lo, hi] = EDGE_CORNERS[edge as usize];
let axis = EDGE_AXIS[edge as usize] as usize;
let o = corner_offset(lo);
let mut p = [
R::from_f64(f64::from(o[0])),
R::from_f64(f64::from(o[1])),
R::from_f64(f64::from(o[2])),
];
p[axis] = crate::cube::place(
R::ZERO,
R::ONE,
edge_offset(corner[lo as usize], corner[hi as usize]),
);
p
}
fn distance_squared<R: Real>(a: [R; 3], b: [R; 3]) -> R {
let d = [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
d[0] * d[0] + d[1] * d[1] + d[2] * d[2]
}
const fn ring_distance(a: usize, b: usize) -> usize {
let r = a.abs_diff(b);
if r > 2 { 6 - r } else { r }
}
const fn ring_midpoint(a: usize, b: usize) -> usize {
let (lo, hi) = if a < b { (a, b) } else { (b, a) };
if (hi + 2) % 6 == lo {
(hi + 1) % 6
} else {
(hi + lo) / 2
}
}
impl Contours {
#[must_use]
pub fn detached_ring<R: Real>(
&self,
saddles: &BodySaddles<R>,
corner: &[R; 8],
) -> Option<usize> {
if self.count() != 3 {
return None;
}
let u = saddles.axis(0);
let (u_lo, u_hi) = if u[0] < u[1] {
(u[0], u[1])
} else {
(u[1], u[0])
};
for r in 0..self.count() {
let ring = self.ring(r);
if ring.len() != 3 {
continue;
}
let mut lo = R::INFINITY;
let mut hi = R::NEG_INFINITY;
for &e in ring {
let x = local_crossing(e, corner)[0];
lo = lo.min(x);
hi = hi.max(x);
}
if u_lo > hi || u_hi < lo {
return Some(r);
}
}
None
}
pub fn fan_tunnel<R: Real>(
&self,
saddles: &BodySaddles<R>,
corner: &[R; 8],
mut emit: impl FnMut([u8; 3]),
) -> usize {
let Some(hexagon) = saddles.inner_hexagon() else {
return 0;
};
let mut buffer = [[0u8; 3]; MAX_PATCH_TRIANGLES];
let mut n = 0usize;
let detached = self.detached_ring(saddles, corner);
let mut unresolved = 0usize;
for r in 0..self.count() {
let ring = self.ring(r);
if Some(r) == detached {
buffer[n] = [ring[0], ring[1], ring[2]];
n += 1;
continue;
}
let nearest = |edge: u8| -> usize {
let p = local_crossing(edge, corner);
let mut best = 0usize;
let mut best_d = R::INFINITY;
for (k, &h) in hexagon.iter().enumerate() {
let d = distance_squared(p, h);
if d < best_d {
best_d = d;
best = k;
}
}
best
};
for k in 0..ring.len() {
let (a, b) = (ring[k], ring[(k + 1) % ring.len()]);
let (ha, hb) = (nearest(a), nearest(b));
match ring_distance(ha, hb) {
0 => {
buffer[n] = [a, b, INTERIOR + ha as u8];
n += 1;
}
1 => {
let pa = local_crossing(a, corner);
let pb = local_crossing(b, corner);
if distance_squared(pa, hexagon[hb]) < distance_squared(pb, hexagon[ha]) {
{
buffer[n] = [a, b, INTERIOR + hb as u8];
n += 1;
}
{
buffer[n] = [a, INTERIOR + hb as u8, INTERIOR + ha as u8];
n += 1;
}
} else {
{
buffer[n] = [a, b, INTERIOR + ha as u8];
n += 1;
}
{
buffer[n] = [b, INTERIOR + hb as u8, INTERIOR + ha as u8];
n += 1;
}
}
}
2 => {
let hm = ring_midpoint(ha, hb);
{
buffer[n] = [a, b, INTERIOR + hm as u8];
n += 1;
}
{
buffer[n] = [a, INTERIOR + hm as u8, INTERIOR + ha as u8];
n += 1;
}
{
buffer[n] = [b, INTERIOR + hb as u8, INTERIOR + hm as u8];
n += 1;
}
}
_ => unresolved += 1,
}
}
}
if self.count() == 1 {
let mut spin = 0i32;
for t in &buffer[..n] {
for k in 0..3 {
let (a, b) = (t[k], t[(k + 1) % 3]);
if a >= INTERIOR && b >= INTERIOR {
let (x, y) = ((a - INTERIOR) as usize, (b - INTERIOR) as usize);
spin += if y == (x + 1) % 6 { 1 } else { -1 };
}
}
}
for k in 1..5u8 {
if spin > 0 {
{
buffer[n] = [INTERIOR, INTERIOR + k + 1, INTERIOR + k];
n += 1;
}
} else {
{
buffer[n] = [INTERIOR, INTERIOR + k, INTERIOR + k + 1];
n += 1;
}
}
}
}
for t in &buffer[..n] {
emit(*t);
}
unresolved
}
}
pub const MAX_PATCH_TRIANGLES: usize = 40;