pub use glam;
pub use ndshape;
use glam::{Vec3A, Vec3Swizzles};
use ndshape::Shape;
pub trait SignedDistance: Into<f32> + Copy {
fn is_negative(self) -> bool;
}
impl SignedDistance for f32 {
fn is_negative(self) -> bool {
self < 0.0
}
}
#[derive(Default, Clone)]
pub struct SurfaceNetsBuffer {
pub positions: Vec<[f32; 3]>,
pub normals: Vec<[f32; 3]>,
pub indices: Vec<u32>,
pub surface_points: Vec<[u32; 3]>,
pub surface_strides: Vec<u32>,
pub stride_to_index: Vec<u32>,
}
impl SurfaceNetsBuffer {
fn reset(&mut self, array_size: usize) {
self.positions.clear();
self.normals.clear();
self.indices.clear();
self.surface_points.clear();
self.surface_strides.clear();
self.stride_to_index.resize(array_size, NULL_VERTEX);
}
}
pub const NULL_VERTEX: u32 = u32::MAX;
pub fn surface_nets<T, S>(
sdf: &[T],
shape: &S,
min: [u32; 3],
max: [u32; 3],
output: &mut SurfaceNetsBuffer,
) where
T: SignedDistance,
S: Shape<3, Coord = u32>,
{
assert!(shape.linearize(min) <= shape.linearize(max));
assert!((shape.linearize(max) as usize) < sdf.len());
output.reset(sdf.len());
estimate_surface(sdf, shape, min, max, output);
make_all_quads(sdf, shape, min, max, output);
}
fn estimate_surface<T, S>(
sdf: &[T],
shape: &S,
[minx, miny, minz]: [u32; 3],
[maxx, maxy, maxz]: [u32; 3],
output: &mut SurfaceNetsBuffer,
) where
T: SignedDistance,
S: Shape<3, Coord = u32>,
{
for z in minz..maxz {
for y in miny..maxy {
for x in minx..maxx {
let stride = shape.linearize([x, y, z]);
let p = Vec3A::from([x as f32, y as f32, z as f32]);
if estimate_surface_in_cube(sdf, shape, p, stride, output) {
output.stride_to_index[stride as usize] = output.positions.len() as u32 - 1;
output.surface_points.push([x, y, z]);
output.surface_strides.push(stride);
} else {
output.stride_to_index[stride as usize] = NULL_VERTEX;
}
}
}
}
}
fn estimate_surface_in_cube<T, S>(
sdf: &[T],
shape: &S,
p: Vec3A,
min_corner_stride: u32,
output: &mut SurfaceNetsBuffer,
) -> bool
where
T: SignedDistance,
S: Shape<3, Coord = u32>,
{
let mut corner_dists = [0f32; 8];
let mut num_negative = 0;
for (i, dist) in corner_dists.iter_mut().enumerate() {
let corner_stride = min_corner_stride + shape.linearize(CUBE_CORNERS[i]);
let d = *unsafe { sdf.get_unchecked(corner_stride as usize) };
*dist = d.into();
if d.is_negative() {
num_negative += 1;
}
}
if num_negative == 0 || num_negative == 8 {
return false;
}
let c = centroid_of_edge_intersections(&corner_dists);
output.positions.push((p + c).into());
output.normals.push(sdf_gradient(&corner_dists, c).into());
true
}
fn centroid_of_edge_intersections(dists: &[f32; 8]) -> Vec3A {
let mut count = 0;
let mut sum = Vec3A::ZERO;
for &[corner1, corner2] in CUBE_EDGES.iter() {
let d1 = dists[corner1 as usize];
let d2 = dists[corner2 as usize];
if (d1 < 0.0) != (d2 < 0.0) {
count += 1;
sum += estimate_surface_edge_intersection(corner1, corner2, d1, d2);
}
}
sum / count as f32
}
fn estimate_surface_edge_intersection(
corner1: u32,
corner2: u32,
value1: f32,
value2: f32,
) -> Vec3A {
let interp1 = value1 / (value1 - value2);
let interp2 = 1.0 - interp1;
interp2 * CUBE_CORNER_VECTORS[corner1 as usize]
+ interp1 * CUBE_CORNER_VECTORS[corner2 as usize]
}
fn sdf_gradient(dists: &[f32; 8], s: Vec3A) -> Vec3A {
let p00 = Vec3A::from([dists[0b001], dists[0b010], dists[0b100]]);
let n00 = Vec3A::from([dists[0b000], dists[0b000], dists[0b000]]);
let p10 = Vec3A::from([dists[0b101], dists[0b011], dists[0b110]]);
let n10 = Vec3A::from([dists[0b100], dists[0b001], dists[0b010]]);
let p01 = Vec3A::from([dists[0b011], dists[0b110], dists[0b101]]);
let n01 = Vec3A::from([dists[0b010], dists[0b100], dists[0b001]]);
let p11 = Vec3A::from([dists[0b111], dists[0b111], dists[0b111]]);
let n11 = Vec3A::from([dists[0b110], dists[0b101], dists[0b011]]);
let d00 = p00 - n00; let d10 = p10 - n10; let d01 = p01 - n01; let d11 = p11 - n11;
let neg = Vec3A::ONE - s;
neg.yzx() * neg.zxy() * d00
+ neg.yzx() * s.zxy() * d10
+ s.yzx() * neg.zxy() * d01
+ s.yzx() * s.zxy() * d11
}
fn make_all_quads<T, S>(
sdf: &[T],
shape: &S,
[minx, miny, minz]: [u32; 3],
[maxx, maxy, maxz]: [u32; 3],
output: &mut SurfaceNetsBuffer,
) where
T: SignedDistance,
S: Shape<3, Coord = u32>,
{
let xyz_strides = [
shape.linearize([1, 0, 0]) as usize,
shape.linearize([0, 1, 0]) as usize,
shape.linearize([0, 0, 1]) as usize,
];
for (&[x, y, z], &p_stride) in output
.surface_points
.iter()
.zip(output.surface_strides.iter())
{
let p_stride = p_stride as usize;
let eval_max_plane = cfg!(feature = "eval-max-plane");
if y != miny && z != minz && (eval_max_plane || x != maxx - 1) {
maybe_make_quad(
sdf,
&output.stride_to_index,
&output.positions,
p_stride,
p_stride + xyz_strides[0],
xyz_strides[1],
xyz_strides[2],
&mut output.indices,
);
}
if x != minx && z != minz && (eval_max_plane || y != maxy - 1) {
maybe_make_quad(
sdf,
&output.stride_to_index,
&output.positions,
p_stride,
p_stride + xyz_strides[1],
xyz_strides[2],
xyz_strides[0],
&mut output.indices,
);
}
if x != minx && y != miny && (eval_max_plane || z != maxz - 1) {
maybe_make_quad(
sdf,
&output.stride_to_index,
&output.positions,
p_stride,
p_stride + xyz_strides[2],
xyz_strides[0],
xyz_strides[1],
&mut output.indices,
);
}
}
}
#[allow(clippy::too_many_arguments)]
fn maybe_make_quad<T>(
sdf: &[T],
stride_to_index: &[u32],
positions: &[[f32; 3]],
p1: usize,
p2: usize,
axis_b_stride: usize,
axis_c_stride: usize,
indices: &mut Vec<u32>,
) where
T: SignedDistance,
{
let d1 = unsafe { sdf.get_unchecked(p1) };
let d2 = unsafe { sdf.get_unchecked(p2) };
let negative_face = match (d1.is_negative(), d2.is_negative()) {
(true, false) => false,
(false, true) => true,
_ => return, };
let v1 = stride_to_index[p1];
let v2 = stride_to_index[p1 - axis_b_stride];
let v3 = stride_to_index[p1 - axis_c_stride];
let v4 = stride_to_index[p1 - axis_b_stride - axis_c_stride];
let (pos1, pos2, pos3, pos4) = (
Vec3A::from(positions[v1 as usize]),
Vec3A::from(positions[v2 as usize]),
Vec3A::from(positions[v3 as usize]),
Vec3A::from(positions[v4 as usize]),
);
let quad = if pos1.distance_squared(pos4) < pos2.distance_squared(pos3) {
if negative_face {
[v1, v4, v2, v1, v3, v4]
} else {
[v1, v2, v4, v1, v4, v3]
}
} else if negative_face {
[v2, v3, v4, v2, v1, v3]
} else {
[v2, v4, v3, v2, v3, v1]
};
indices.extend_from_slice(&quad);
}
const CUBE_CORNERS: [[u32; 3]; 8] = [
[0, 0, 0],
[1, 0, 0],
[0, 1, 0],
[1, 1, 0],
[0, 0, 1],
[1, 0, 1],
[0, 1, 1],
[1, 1, 1],
];
const CUBE_CORNER_VECTORS: [Vec3A; 8] = [
Vec3A::from_array([0.0, 0.0, 0.0]),
Vec3A::from_array([1.0, 0.0, 0.0]),
Vec3A::from_array([0.0, 1.0, 0.0]),
Vec3A::from_array([1.0, 1.0, 0.0]),
Vec3A::from_array([0.0, 0.0, 1.0]),
Vec3A::from_array([1.0, 0.0, 1.0]),
Vec3A::from_array([0.0, 1.0, 1.0]),
Vec3A::from_array([1.0, 1.0, 1.0]),
];
const CUBE_EDGES: [[u32; 2]; 12] = [
[0b000, 0b001],
[0b000, 0b010],
[0b000, 0b100],
[0b001, 0b011],
[0b001, 0b101],
[0b010, 0b011],
[0b010, 0b110],
[0b011, 0b111],
[0b100, 0b101],
[0b100, 0b110],
[0b101, 0b111],
[0b110, 0b111],
];