use crate::Real;
pub const CORNER_COUNT: usize = 8;
pub const EDGE_COUNT: usize = 12;
pub const EDGE_CORNERS: [[u8; 2]; EDGE_COUNT] = build_edge_corners();
const fn build_edge_corners() -> [[u8; 2]; EDGE_COUNT] {
let mut out = [[0u8; 2]; EDGE_COUNT];
let mut n = 0;
let mut axis = 0u8;
while axis < 3 {
let mut c = 0u8;
while c < 8 {
if c & (1 << axis) == 0 {
out[n] = [c, c | (1 << axis)];
n += 1;
}
c += 1;
}
axis += 1;
}
out
}
pub const EDGE_AXIS: [u8; EDGE_COUNT] = build_edge_axis();
const fn build_edge_axis() -> [u8; EDGE_COUNT] {
let mut out = [0u8; EDGE_COUNT];
let mut e = 0;
while e < EDGE_COUNT {
let d = EDGE_CORNERS[e][0] ^ EDGE_CORNERS[e][1];
out[e] = match d {
1 => 0,
2 => 1,
_ => 2,
};
e += 1;
}
out
}
pub const fn edge_index(a: u8, b: u8) -> u8 {
let (lo, hi) = if a < b { (a, b) } else { (b, a) };
let mut e = 0;
while e < EDGE_COUNT {
if EDGE_CORNERS[e][0] == lo && EDGE_CORNERS[e][1] == hi {
return e as u8;
}
e += 1;
}
panic!("corners are not joined by a cube edge")
}
pub const fn face_corners(axis: usize, side: u8) -> [u8; 4] {
let u = (axis + 1) % 3;
let v = (axis + 2) % 3;
let base = side << axis;
let c00 = base;
let c10 = base | (1 << u);
let c11 = base | (1 << u) | (1 << v);
let c01 = base | (1 << v);
if side == 1 {
[c00, c10, c11, c01]
} else {
[c00, c01, c11, c10]
}
}
#[inline]
#[must_use]
pub const fn edge_on_face(edge: u8, axis: usize, side: u8) -> bool {
EDGE_AXIS[edge as usize] as usize != axis
&& (EDGE_CORNERS[edge as usize][0] >> axis) & 1 == side
}
#[inline]
#[must_use]
pub const fn edges_share_a_face(a: u8, b: u8) -> bool {
let mut axis = 0usize;
while axis < 3 {
let mut side = 0u8;
while side < 2 {
if edge_on_face(a, axis, side) && edge_on_face(b, axis, side) {
return true;
}
side += 1;
}
axis += 1;
}
false
}
#[inline]
pub const fn corner_inside(case: u8, corner: u8) -> bool {
case & (1 << corner) != 0
}
#[inline]
#[must_use]
pub(crate) const fn corner_offset(corner: u8) -> [u32; 3] {
[
(corner & 1) as u32,
((corner >> 1) & 1) as u32,
((corner >> 2) & 1) as u32,
]
}
#[inline]
pub fn is_inside<R: Real>(value: R) -> bool {
value < R::ZERO
}
#[inline]
pub(crate) fn edge_crossing<R: Real>(a: R, b: R) -> R {
debug_assert!(is_inside(a) != is_inside(b));
a / (a - b)
}
#[cfg(test)]
mod tests {
#![allow(clippy::float_cmp)]
use super::*;
use alloc::vec::Vec;
#[test]
fn every_edge_joins_corners_differing_in_one_bit() {
for (e, [a, b]) in EDGE_CORNERS
.iter()
.map(|c| (c, *c))
.enumerate()
.map(|(i, (_, c))| (i, c))
{
let diff = a ^ b;
assert!(diff.is_power_of_two(), "edge {e}: {a} to {b}");
assert_eq!(diff.trailing_zeros(), u32::from(EDGE_AXIS[e]));
assert!(a < b, "edge {e} is not lower-corner-first");
}
}
#[test]
fn the_twelve_edges_are_distinct() {
let mut seen: Vec<[u8; 2]> = EDGE_CORNERS.to_vec();
seen.sort_unstable();
seen.dedup();
assert_eq!(seen.len(), EDGE_COUNT);
}
#[test]
fn edge_index_round_trips() {
for (e, [a, b]) in EDGE_CORNERS.iter().copied().enumerate() {
assert_eq!(edge_index(a, b), e as u8);
assert_eq!(edge_index(b, a), e as u8, "order must not matter");
}
}
#[test]
fn faces_partition_the_corners() {
let mut appearances = [0u32; CORNER_COUNT];
for axis in 0..3 {
for side in 0..2u8 {
let corners = face_corners(axis, side);
let mut sorted: Vec<u8> = corners.to_vec();
sorted.sort_unstable();
sorted.dedup();
assert_eq!(sorted.len(), 4, "axis {axis} side {side}");
for c in corners {
appearances[c as usize] += 1;
assert_eq!((c >> axis) & 1, side);
}
}
}
assert_eq!(appearances, [3; CORNER_COUNT]);
}
#[test]
fn face_corners_are_a_cycle() {
for axis in 0..3 {
for side in 0..2u8 {
let c = face_corners(axis, side);
for k in 0..4 {
let diff = c[k] ^ c[(k + 1) % 4];
assert!(diff.is_power_of_two(), "axis {axis} side {side} at {k}");
}
}
}
}
#[test]
fn corner_offset_matches_the_bit_layout() {
assert_eq!(corner_offset(0), [0, 0, 0]);
assert_eq!(corner_offset(1), [1, 0, 0]);
assert_eq!(corner_offset(2), [0, 1, 0]);
assert_eq!(corner_offset(4), [0, 0, 1]);
assert_eq!(corner_offset(7), [1, 1, 1]);
}
#[test]
fn zero_is_outside() {
assert!(is_inside(-1.0f64));
assert!(!is_inside(0.0f64));
assert!(!is_inside(1.0f64));
assert!(!is_inside(-0.0f64));
}
#[test]
fn edge_crossing_lands_between_the_endpoints() {
for (a, b) in [(-1.0f64, 1.0), (-0.25, 3.0), (-2.0, 0.0), (5.0, -0.5)] {
let t = edge_crossing(a, b);
assert!((0.0..=1.0).contains(&t), "a={a} b={b} t={t}");
}
assert_eq!(edge_crossing(-2.0f64, 0.0), 1.0);
}
}