use alloc::vec::Vec;
use crate::cube::{EDGE_COUNT, edge_index, is_inside};
use crate::vec3;
use crate::{MeshSink, Real, Sdf, Shape3};
pub const MAX_CELL_VERTICES: usize = 4;
pub(crate) const NO_SLOT: u8 = u8::MAX;
#[derive(Debug)]
pub struct CellVertices<R: Real> {
position: [[R; 3]; MAX_CELL_VERTICES],
slot_of_edge: [u8; EDGE_COUNT],
count: u8,
}
impl<R: Real> CellVertices<R> {
pub(crate) const fn new() -> Self {
Self {
position: [[R::ZERO; 3]; MAX_CELL_VERTICES],
slot_of_edge: [NO_SLOT; EDGE_COUNT],
count: 0,
}
}
pub(crate) fn clear(&mut self) {
self.slot_of_edge = [NO_SLOT; EDGE_COUNT];
self.count = 0;
}
pub fn push_whole_cell(&mut self, position: [R; 3]) {
debug_assert_eq!(self.count, 0, "a whole-cell vertex must be the only one");
self.position[0] = position;
self.slot_of_edge = [0; EDGE_COUNT];
self.count = 1;
}
pub fn push_component(&mut self, position: [R; 3], edges: u16) {
debug_assert!((self.count as usize) < MAX_CELL_VERTICES, "slot budget");
let slot = self.count;
self.position[slot as usize] = position;
for (edge, owner) in self.slot_of_edge.iter_mut().enumerate() {
if edges & (1 << edge) != 0 {
debug_assert_eq!(*owner, NO_SLOT, "edge {edge} claimed twice");
*owner = slot;
}
}
self.count += 1;
}
fn count(&self) -> usize {
self.count as usize
}
}
pub trait VertexRule<R: Real> {
fn place<S: Sdf<Scalar = R>>(
&self,
sdf: &S,
corner: &[R; 8],
base: [u32; 3],
origin: [R; 3],
cell_size: R,
out: &mut CellVertices<R>,
);
}
const QUAD_EDGE: [[[u8; 2]; 2]; 3] = build_quad_edge();
const fn build_quad_edge() -> [[[u8; 2]; 2]; 3] {
let mut out = [[[0u8; 2]; 2]; 3];
let mut axis = 0usize;
while axis < 3 {
let u = (axis + 1) % 3;
let v = (axis + 2) % 3;
let mut du = 0usize;
while du < 2 {
let mut dv = 0usize;
while dv < 2 {
let lo = ((du as u8) << u) | ((dv as u8) << v);
out[axis][du][dv] = edge_index(lo, lo | (1 << axis));
dv += 1;
}
du += 1;
}
axis += 1;
}
out
}
#[derive(Debug)]
pub(crate) struct DualMesher<R: Real> {
values: Vec<R>,
row: usize,
cell_first: Vec<u32>,
cell_edge_slot: Vec<[u8; EDGE_COUNT]>,
slot_position: Vec<[R; 3]>,
smoothed: Vec<[R; 3]>,
slot_vertex: Vec<u32>,
scratch: CellVertices<R>,
pub(crate) smoothing_passes: u32,
}
impl<R: Real> DualMesher<R> {
pub(crate) const fn new() -> Self {
Self {
values: Vec::new(),
row: 0,
cell_first: Vec::new(),
cell_edge_slot: Vec::new(),
slot_position: Vec::new(),
smoothed: Vec::new(),
slot_vertex: Vec::new(),
scratch: CellVertices::new(),
smoothing_passes: 0,
}
}
pub(crate) fn extract<S, M, V>(
&mut self,
rule: &V,
sdf: &S,
shape: &impl Shape3,
origin: [R; 3],
cell_size: R,
out: &mut M,
) -> crate::Result<()>
where
S: Sdf<Scalar = R>,
M: MeshSink<Scalar = R>,
V: VertexRule<R>,
{
let size = shape.size();
if size[0] < 2 || size[1] < 2 || size[2] < 2 {
return Err(crate::Error::GridTooSmall { size });
}
self.sample(sdf, shape, origin, cell_size);
let cells = [size[0] - 1, size[1] - 1, size[2] - 1];
let cell_count = cells[0] as usize * cells[1] as usize * cells[2] as usize;
self.cell_first.clear();
self.cell_first.resize(cell_count, u32::MAX);
self.cell_edge_slot.clear();
self.cell_edge_slot
.resize(cell_count, [NO_SLOT; EDGE_COUNT]);
self.slot_position.clear();
self.slot_vertex.clear();
self.place_vertices(rule, sdf, shape, cells, origin, cell_size);
self.smooth(cells, origin, cell_size);
self.emit_vertices(sdf, out);
self.emit_quads(shape, cells, out);
Ok(())
}
#[inline]
fn index(&self, p: [u32; 3], size: [u32; 3]) -> usize {
p[0] as usize + self.row * (p[1] as usize + size[1] as usize * p[2] as usize)
}
#[inline]
fn row_stride(size: [u32; 3]) -> usize {
size[0] as usize | 1
}
fn sample<S: Sdf<Scalar = R>>(
&mut self,
sdf: &S,
shape: &impl Shape3,
origin: [R; 3],
cell_size: R,
) {
let size = shape.size();
self.row = Self::row_stride(size);
let pad = self.row - size[0] as usize;
self.values.clear();
self.values
.reserve(self.row * size[1] as usize * size[2] as usize);
for z in 0..size[2] {
for y in 0..size[1] {
for x in 0..size[0] {
self.values.push(sdf.sample([
origin[0] + cell_size * R::from_f64(f64::from(x)),
origin[1] + cell_size * R::from_f64(f64::from(y)),
origin[2] + cell_size * R::from_f64(f64::from(z)),
]));
}
for _ in 0..pad {
self.values.push(R::ZERO);
}
}
}
}
fn place_vertices<S, V>(
&mut self,
rule: &V,
sdf: &S,
shape: &impl Shape3,
cells: [u32; 3],
origin: [R; 3],
cell_size: R,
) where
S: Sdf<Scalar = R>,
V: VertexRule<R>,
{
for z in 0..cells[2] {
for y in 0..cells[1] {
for x in 0..cells[0] {
let base = [x, y, z];
let mut corner = [R::ZERO; 8];
let mut inside_count = 0u32;
for (c, slot) in corner.iter_mut().enumerate() {
let o = crate::cube::corner_offset(c as u8);
let s = self.index(
[base[0] + o[0], base[1] + o[1], base[2] + o[2]],
shape.size(),
);
*slot = self.values[s];
if is_inside(*slot) {
inside_count += 1;
}
}
if inside_count == 0 || inside_count == 8 {
continue;
}
self.scratch.clear();
rule.place(sdf, &corner, base, origin, cell_size, &mut self.scratch);
if self.scratch.count() == 0 {
continue;
}
#[cfg(debug_assertions)]
{
for (edge, [lo, hi]) in crate::cube::EDGE_CORNERS.into_iter().enumerate() {
if is_inside(corner[lo as usize]) != is_inside(corner[hi as usize]) {
assert_ne!(
self.scratch.slot_of_edge[edge], NO_SLOT,
"cut edge {edge} has no owning vertex"
);
}
}
}
let index = cell_index(cells, base);
self.cell_first[index] = self.slot_position.len() as u32;
self.cell_edge_slot[index] = self.scratch.slot_of_edge;
self.slot_position
.extend_from_slice(&self.scratch.position[..self.scratch.count()]);
}
}
}
}
fn smooth(&mut self, cells: [u32; 3], origin: [R; 3], cell_size: R) {
if self.smoothing_passes == 0 {
return;
}
debug_assert_eq!(
self.slot_position.len(),
self.cell_first.iter().filter(|f| **f != u32::MAX).count(),
"smoothing requires one vertex per active cell"
);
for _ in 0..self.smoothing_passes {
self.smoothed.clear();
self.smoothed.extend_from_slice(&self.slot_position);
for z in 0..cells[2] {
for y in 0..cells[1] {
for x in 0..cells[0] {
let index = cell_index(cells, [x, y, z]);
let first = self.cell_first[index];
if first == u32::MAX {
continue;
}
let mut sum = self.slot_position[first as usize];
let mut count = 1u32;
for axis in 0..3usize {
for step in [-1i64, 1] {
let mut neighbour = [i64::from(x), i64::from(y), i64::from(z)];
neighbour[axis] += step;
if neighbour[axis] < 0 || neighbour[axis] >= i64::from(cells[axis])
{
continue;
}
let n = cell_index(
cells,
[
neighbour[0] as u32,
neighbour[1] as u32,
neighbour[2] as u32,
],
);
let n_first = self.cell_first[n];
if n_first == u32::MAX {
continue;
}
for (k, slot) in sum.iter_mut().enumerate() {
*slot += self.slot_position[n_first as usize][k];
}
count += 1;
}
}
let inv = R::from_f64(f64::from(count)).recip();
let cell_origin = [
origin[0] + cell_size * R::from_f64(f64::from(x)),
origin[1] + cell_size * R::from_f64(f64::from(y)),
origin[2] + cell_size * R::from_f64(f64::from(z)),
];
self.smoothed[first as usize] = crate::dual_contouring::apply_clamp(
crate::dual_contouring::Clamp::ToCell,
[sum[0] * inv, sum[1] * inv, sum[2] * inv],
cell_origin,
cell_size,
);
}
}
}
core::mem::swap(&mut self.slot_position, &mut self.smoothed);
}
}
fn emit_vertices<S, M>(&mut self, sdf: &S, out: &mut M)
where
S: Sdf<Scalar = R>,
M: MeshSink<Scalar = R>,
{
self.slot_vertex.reserve(self.slot_position.len());
for &position in &self.slot_position {
let g = sdf.gradient(position);
let len = vec3::length(g);
debug_assert!(len > R::ZERO, "zero gradient at a surface vertex");
let normal = vec3::scale(g, len.recip());
self.slot_vertex.push(out.vertex(position, normal));
}
}
fn emit_quads<M: MeshSink<Scalar = R>>(
&self,
shape: &impl Shape3,
cells: [u32; 3],
out: &mut M,
) {
self.emit_quad_axis::<0, M>(shape, cells, out);
self.emit_quad_axis::<1, M>(shape, cells, out);
self.emit_quad_axis::<2, M>(shape, cells, out);
}
fn emit_quad_axis<const AXIS: usize, M: MeshSink<Scalar = R>>(
&self,
shape: &impl Shape3,
cells: [u32; 3],
out: &mut M,
) {
let size = shape.size();
{
let axis = AXIS;
let u = (AXIS + 1) % 3;
let v = (AXIS + 2) % 3;
let mut p = [0u32; 3];
for a in 0..size[axis] - 1 {
for b in 1..cells[u] {
for c in 1..cells[v] {
p[axis] = a;
p[u] = b;
p[v] = c;
let s0 = self.index(p, size);
let mut q = p;
q[axis] += 1;
let s1 = self.index(q, size);
let inside0 = is_inside(self.values[s0]);
let inside1 = is_inside(self.values[s1]);
if inside0 == inside1 {
continue;
}
let mut quad = [0u32; 4];
for (slot, (du, dv)) in
quad.iter_mut().zip([(0u32, 0u32), (1, 0), (1, 1), (0, 1)])
{
let mut cell = p;
cell[u] -= du;
cell[v] -= dv;
let index = cell_index(cells, cell);
let local = QUAD_EDGE[axis][du as usize][dv as usize];
let first = self.cell_first[index];
let owner = self.cell_edge_slot[index][local as usize];
debug_assert!(first != u32::MAX);
debug_assert!(owner != NO_SLOT);
*slot = if first == u32::MAX || owner == NO_SLOT {
u32::MAX
} else {
self.slot_vertex
.get(first as usize + owner as usize)
.copied()
.unwrap_or(u32::MAX)
};
}
if inside0 {
out.triangle(quad[0], quad[1], quad[2]);
out.triangle(quad[0], quad[2], quad[3]);
} else {
out.triangle(quad[0], quad[2], quad[1]);
out.triangle(quad[0], quad[3], quad[2]);
}
}
}
}
}
}
}
#[inline]
fn cell_index(cells: [u32; 3], p: [u32; 3]) -> usize {
p[0] as usize + cells[0] as usize * (p[1] as usize + cells[1] as usize * p[2] as usize)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cube::{EDGE_AXIS, EDGE_CORNERS};
#[test]
fn quad_edge_names_the_shared_edge_from_all_four_cells() {
for (axis, per_axis) in QUAD_EDGE.iter().enumerate() {
let u = (axis + 1) % 3;
let v = (axis + 2) % 3;
for (du, per_du) in per_axis.iter().enumerate() {
for (dv, &edge) in per_du.iter().enumerate() {
assert_eq!(
EDGE_AXIS[edge as usize] as usize, axis,
"axis {axis} du {du} dv {dv}"
);
let lo = EDGE_CORNERS[edge as usize][0];
assert_eq!(usize::from((lo >> u) & 1), du);
assert_eq!(usize::from((lo >> v) & 1), dv);
assert_eq!((lo >> axis) & 1, 0, "the low corner starts the edge");
}
}
}
}
#[test]
fn a_whole_cell_vertex_owns_every_edge() {
let mut cell = CellVertices::<f64>::new();
cell.clear();
cell.push_whole_cell([1.0, 2.0, 3.0]);
assert_eq!(cell.count(), 1);
assert!(cell.slot_of_edge.iter().all(|s| *s == 0));
}
#[test]
fn components_own_disjoint_edges() {
let mut cell = CellVertices::<f64>::new();
cell.clear();
cell.push_component([0.0; 3], 0b0000_0000_0111);
cell.push_component([1.0; 3], 0b0000_0111_0000);
assert_eq!(cell.count(), 2);
assert_eq!(cell.slot_of_edge[0], 0);
assert_eq!(cell.slot_of_edge[2], 0);
assert_eq!(cell.slot_of_edge[4], 1);
assert_eq!(cell.slot_of_edge[6], 1);
assert_eq!(cell.slot_of_edge[7], NO_SLOT);
}
}