use crate::cube::{EDGE_CORNERS, EDGE_COUNT, corner_offset, edge_crossing, is_inside};
use crate::equivariant::sum_equivariant;
use crate::vec3;
use crate::{Real, Sdf};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct HermiteCrossing<R: Real> {
pub position: [R; 3],
pub normal: [R; 3],
}
#[derive(Clone, Copy, Debug)]
pub struct HermiteCell<R: Real> {
crossings: [HermiteCrossing<R>; EDGE_COUNT],
mask: u16,
}
impl<R: Real> HermiteCell<R> {
pub fn from_corners<S: Sdf<Scalar = R>>(
sdf: &S,
corner_values: &[R; 8],
cell_origin: [R; 3],
cell_size: R,
) -> Self {
let empty = HermiteCrossing {
position: [R::ZERO; 3],
normal: [R::ZERO; 3],
};
let mut crossings = [empty; EDGE_COUNT];
let mut mask = 0u16;
for (edge, [lo, hi]) in EDGE_CORNERS.iter().copied().enumerate() {
let (a, b) = (corner_values[lo as usize], corner_values[hi as usize]);
if is_inside(a) == is_inside(b) {
continue;
}
let t = edge_crossing(a, b);
let (lo_offset, hi_offset) = (corner_offset(lo), corner_offset(hi));
let mut position = [R::ZERO; 3];
for (axis, slot) in position.iter_mut().enumerate() {
let from = R::from_f64(f64::from(lo_offset[axis]));
let to = R::from_f64(f64::from(hi_offset[axis]));
*slot = cell_origin[axis] + cell_size * (from + (to - from) * t);
}
let gradient = sdf.gradient(position);
let length = vec3::length(gradient);
debug_assert!(length > R::ZERO, "zero gradient at an edge crossing");
let normal = vec3::scale(gradient, length.recip());
crossings[edge] = HermiteCrossing { position, normal };
mask |= 1 << edge;
}
Self { crossings, mask }
}
#[cfg(test)]
pub(crate) fn from_crossings(pairs: &[(u8, HermiteCrossing<R>)]) -> Self {
let mut cell = Self {
crossings: [HermiteCrossing {
position: [R::ZERO; 3],
normal: [R::ZERO; 3],
}; EDGE_COUNT],
mask: 0,
};
for &(edge, crossing) in pairs {
if (edge as usize) < EDGE_COUNT {
cell.crossings[edge as usize] = crossing;
cell.mask |= 1 << edge;
}
}
cell
}
#[must_use]
pub fn restricted(&self, edges: u16) -> Self {
Self {
crossings: self.crossings,
mask: self.mask & edges,
}
}
#[must_use]
pub fn len(&self) -> usize {
self.mask.count_ones() as usize
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.mask == 0
}
#[must_use]
pub fn contains(&self, edge: u8) -> bool {
edge < EDGE_COUNT as u8 && self.mask & (1 << edge) != 0
}
#[must_use]
pub fn get(&self, edge: u8) -> Option<&HermiteCrossing<R>> {
if self.contains(edge) {
Some(&self.crossings[edge as usize])
} else {
None
}
}
pub fn iter(&self) -> impl Iterator<Item = &HermiteCrossing<R>> {
(0..EDGE_COUNT as u8).filter_map(|edge| self.get(edge))
}
#[must_use]
pub fn centroid(&self) -> Option<[R; 3]> {
if self.is_empty() {
return None;
}
let mut axes = [[R::ZERO; EDGE_COUNT]; 3];
for edge in 0..EDGE_COUNT {
let Some(crossing) = self.get(edge as u8) else {
continue;
};
for (slot, value) in axes.iter_mut().zip(crossing.position) {
slot[edge] = value;
}
}
let inverse = R::from_f64(self.len() as f64).recip();
let sum = axes.map(sum_equivariant);
Some([sum[0] * inverse, sum[1] * inverse, sum[2] * inverse])
}
}
#[cfg(test)]
mod tests;