#[cfg(test)]
mod tests;
use alloc::vec::Vec;
use crate::cube::is_inside;
use crate::{MeshSink, Real, Sdf, Shape3};
#[derive(Debug)]
pub struct GreedyQuads<R: Real> {
solid: Vec<bool>,
mask: Vec<bool>,
merge: Merge,
_scalar: core::marker::PhantomData<R>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Merge {
#[default]
Greedy,
Off,
}
impl<R: Real> GreedyQuads<R> {
#[must_use]
pub const fn new() -> Self {
Self {
solid: Vec::new(),
mask: Vec::new(),
merge: Merge::Greedy,
_scalar: core::marker::PhantomData,
}
}
pub fn set_merge(&mut self, merge: Merge) {
self.merge = merge;
}
pub fn extract<S, M>(
&mut self,
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>,
{
let size = shape.size();
if size[0] < 2 || size[1] < 2 || size[2] < 2 {
return Err(crate::Error::GridTooSmall { size });
}
let cells = [size[0] - 1, size[1] - 1, size[2] - 1];
let cell_count = cells[0] as u64 * cells[1] as u64 * cells[2] as u64;
let bound = cell_count.saturating_mul(24);
if bound > u64::from(u32::MAX) {
return Err(crate::Error::IndexSpaceExhausted { needed: bound });
}
let half = cell_size * R::HALF;
self.solid.clear();
self.solid.reserve(cell_count as usize);
for z in 0..cells[2] {
for y in 0..cells[1] {
for x in 0..cells[0] {
let p = [
origin[0] + cell_size * R::from_f64(f64::from(x)) + half,
origin[1] + cell_size * R::from_f64(f64::from(y)) + half,
origin[2] + cell_size * R::from_f64(f64::from(z)) + half,
];
self.solid.push(is_inside(sdf.sample(p)));
}
}
}
let at = |c: [i64; 3]| -> bool {
for axis in 0..3 {
if c[axis] < 0 || c[axis] >= i64::from(cells[axis]) {
return false;
}
}
let i = c[0] as usize
+ cells[0] as usize * (c[1] as usize + cells[1] as usize * c[2] as usize);
self.solid[i]
};
for axis in 0..3usize {
let u = (axis + 1) % 3;
let v = (axis + 2) % 3;
let du = cells[u] as usize;
let dv = cells[v] as usize;
for step in [1i64, -1] {
for slice in 0..cells[axis] as i64 {
self.mask.clear();
self.mask.resize(du * dv, false);
for b in 0..dv {
for a in 0..du {
let mut c = [0i64; 3];
c[axis] = slice;
c[u] = a as i64;
c[v] = b as i64;
let mut n = c;
n[axis] = slice + step;
self.mask[a + du * b] = at(c) && !at(n);
}
}
for b in 0..dv {
let mut a = 0usize;
while a < du {
if !self.mask[a + du * b] {
a += 1;
continue;
}
let mut width = 1usize;
let mut height = 1usize;
if self.merge == Merge::Greedy {
while a + width < du && self.mask[a + width + du * b] {
width += 1;
}
'grow: while b + height < dv {
for k in 0..width {
if !self.mask[a + k + du * (b + height)] {
break 'grow;
}
}
height += 1;
}
}
for row in 0..height {
for k in 0..width {
self.mask[a + k + du * (b + row)] = false;
}
}
emit_quad(
out, origin, cell_size, axis, u, v, slice, step, a, b, width,
height,
);
a += width;
}
}
}
}
}
Ok(())
}
}
impl<R: Real> Default for GreedyQuads<R> {
fn default() -> Self {
Self::new()
}
}
#[allow(clippy::too_many_arguments)]
fn emit_quad<R: Real, M: MeshSink<Scalar = R>>(
out: &mut M,
origin: [R; 3],
cell_size: R,
axis: usize,
u: usize,
v: usize,
slice: i64,
step: i64,
a: usize,
b: usize,
width: usize,
height: usize,
) {
let plane = if step > 0 { slice + 1 } else { slice };
let world = |along_u: usize, along_v: usize| -> [R; 3] {
let mut p = [R::ZERO; 3];
let mut grid = [0i64; 3];
grid[axis] = plane;
grid[u] = (a + along_u) as i64;
grid[v] = (b + along_v) as i64;
for k in 0..3 {
p[k] = origin[k] + cell_size * R::from_f64(grid[k] as f64);
}
p
};
let mut normal = [R::ZERO; 3];
normal[axis] = if step > 0 { R::ONE } else { -R::ONE };
let corners = [
world(0, 0),
world(width, 0),
world(width, height),
world(0, height),
];
let order = if step > 0 { [0, 1, 2, 3] } else { [0, 3, 2, 1] };
let mut index = [0u32; 4];
for (slot, &corner) in index.iter_mut().zip(order.iter()) {
*slot = out.vertex(corners[corner], normal);
}
out.triangle(index[0], index[1], index[2]);
out.triangle(index[0], index[2], index[3]);
}