use super::PosNormMesh;
use building_blocks_core::prelude::*;
use building_blocks_storage::{prelude::*, ArrayForEach};
pub fn padded_surface_nets_chunk_extent(chunk_extent: &Extent3i) -> Extent3i {
chunk_extent.padded(1)
}
#[derive(Default)]
pub struct SurfaceNetsBuffer {
pub mesh: PosNormMesh,
pub surface_points: Vec<Point3i>,
pub surface_strides: Vec<Stride>,
stride_to_index: Vec<u32>,
}
impl SurfaceNetsBuffer {
pub fn reset(&mut self, array_size: usize) {
self.mesh.clear();
self.surface_points.clear();
self.surface_strides.clear();
self.stride_to_index.resize(array_size, 0);
}
}
pub fn surface_nets<A, T>(
sdf: &A,
extent: &Extent3i,
voxel_size: f32,
output: &mut SurfaceNetsBuffer,
) where
A: IndexedArray<[i32; 3]> + Get<Stride, Item = T>,
T: SignedDistance,
{
output.reset(sdf.extent().num_points());
estimate_surface(sdf, extent, voxel_size, output);
make_all_quads(sdf, extent, output);
}
fn estimate_surface<A, T>(
sdf: &A,
extent: &Extent3i,
voxel_size: f32,
output: &mut SurfaceNetsBuffer,
) where
A: IndexedArray<[i32; 3]> + Get<Stride, Item = T>,
T: SignedDistance,
{
let mut corner_offset_strides = [Stride(0); 8];
let corner_offsets = Local::localize_points_array(&Point3i::CUBE_CORNER_OFFSETS);
sdf.strides_from_local_points(&corner_offsets, &mut corner_offset_strides);
let iter_extent = extent.add_to_shape(Point3i::fill(-1));
let visitor = ArrayForEach::new_global(*sdf.extent(), iter_extent);
visitor.for_each(|p, p_stride| {
let mut corner_strides = [Stride(0); 8];
for i in 0..8 {
corner_strides[i] = p_stride + corner_offset_strides[i];
}
if let Some((position, normal)) =
estimate_surface_in_cube(sdf, voxel_size, &p, &corner_strides)
{
output.stride_to_index[p_stride.0] = output.mesh.positions.len() as u32;
output.surface_points.push(p);
output.surface_strides.push(p_stride);
output.mesh.positions.push(position);
output.mesh.normals.push(normal);
}
});
}
const CUBE_EDGES: [[usize; 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],
];
fn estimate_surface_in_cube<A, T>(
sdf: &A,
voxel_size: f32,
cube_min_corner: &Point3i,
corner_strides: &[Stride],
) -> Option<([f32; 3], [f32; 3])>
where
A: Get<Stride, Item = T>,
T: SignedDistance,
{
let mut corner_dists = [0.0; 8];
let mut num_negative = 0;
for (i, dist) in corner_dists.iter_mut().enumerate() {
let d = sdf.get(corner_strides[i]).into();
*dist = d;
if d < 0.0 {
num_negative += 1;
}
}
if num_negative == 0 || num_negative == 8 {
return None;
}
let centroid = centroid_of_edge_intersections(&corner_dists);
let position = voxel_size * (Point3f::from(*cube_min_corner) + centroid + Point3f::fill(0.5));
let normal = sdf_gradient(&corner_dists, ¢roid);
Some((position.0, normal))
}
fn centroid_of_edge_intersections(dists: &[f32; 8]) -> Point3f {
let mut count = 0;
let mut sum = Point3f::ZERO;
for [corner1, corner2] in CUBE_EDGES.iter() {
let d1 = dists[*corner1];
let d2 = dists[*corner2];
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: usize,
corner2: usize,
value1: f32,
value2: f32,
) -> Point3f {
let interp1 = value1 / (value1 - value2);
let interp2 = 1.0 - interp1;
PointN([
(corner1 & 1) as f32 * interp2 + (corner2 & 1) as f32 * interp1,
((corner1 >> 1) & 1) as f32 * interp2 + ((corner2 >> 1) & 1) as f32 * interp1,
((corner1 >> 2) & 1) as f32 * interp2 + ((corner2 >> 2) & 1) as f32 * interp1,
])
}
fn sdf_gradient(dists: &[f32; 8], s: &Point3f) -> [f32; 3] {
let nx = 1.0 - s.x();
let ny = 1.0 - s.y();
let nz = 1.0 - s.z();
let dx_z0 = ny * (dists[0b001] - dists[0b000]) + s.y() * (dists[0b011] - dists[0b010]);
let dx_z1 = ny * (dists[0b101] - dists[0b100]) + s.y() * (dists[0b111] - dists[0b110]);
let dx = nz * dx_z0 + s.z() * dx_z1;
let dy_x0 = nz * (dists[0b010] - dists[0b000]) + s.z() * (dists[0b110] - dists[0b100]);
let dy_x1 = nz * (dists[0b011] - dists[0b001]) + s.z() * (dists[0b111] - dists[0b101]);
let dy = nx * dy_x0 + s.x() * dy_x1;
let dz_y0 = nx * (dists[0b100] - dists[0b000]) + s.x() * (dists[0b101] - dists[0b001]);
let dz_y1 = nx * (dists[0b110] - dists[0b010]) + s.x() * (dists[0b111] - dists[0b011]);
let dz = ny * dz_y0 + s.y() * dz_y1;
[dx, dy, dz]
}
fn make_all_quads<A, T>(sdf: &A, extent: &Extent3i, output: &mut SurfaceNetsBuffer)
where
A: IndexedArray<[i32; 3]> + Get<Stride, Item = T>,
T: SignedDistance,
{
let mut xyz_strides = [Stride(0); 3];
let xyz = [
Local(PointN([1, 0, 0])),
Local(PointN([0, 1, 0])),
Local(PointN([0, 0, 1])),
];
sdf.strides_from_local_points(&xyz, &mut xyz_strides);
let min = extent.minimum;
let max = extent.max() - Point3i::ONES;
for (&p, p_stride) in output
.surface_points
.iter()
.zip(output.surface_strides.iter())
{
if p.y() != min.y() && p.z() != min.z() && p.x() != max.x() {
maybe_make_quad(
sdf,
&output.stride_to_index,
&output.mesh.positions,
*p_stride,
*p_stride + xyz_strides[0],
xyz_strides[1],
xyz_strides[2],
&mut output.mesh.indices,
);
}
if p.x() != min.x() && p.z() != min.z() && p.y() != max.y() {
maybe_make_quad(
sdf,
&output.stride_to_index,
&output.mesh.positions,
*p_stride,
*p_stride + xyz_strides[1],
xyz_strides[2],
xyz_strides[0],
&mut output.mesh.indices,
);
}
if p.x() != min.x() && p.y() != min.y() && p.z() != max.z() {
maybe_make_quad(
sdf,
&output.stride_to_index,
&output.mesh.positions,
*p_stride,
*p_stride + xyz_strides[2],
xyz_strides[0],
xyz_strides[1],
&mut output.mesh.indices,
);
}
}
}
fn maybe_make_quad<A, T>(
sdf: &A,
stride_to_index: &[u32],
positions: &[[f32; 3]],
p1: Stride,
p2: Stride,
axis_b_stride: Stride,
axis_c_stride: Stride,
indices: &mut Vec<u32>,
) where
A: Get<Stride, Item = T>,
T: SignedDistance,
{
let d1 = sdf.get(p1);
let d2 = sdf.get(p2);
let negative_face = match (d1.is_negative(), d2.is_negative()) {
(true, false) => false,
(false, true) => true,
_ => return, };
let v1 = stride_to_index[p1.0];
let v2 = stride_to_index[(p1 - axis_b_stride).0];
let v3 = stride_to_index[(p1 - axis_c_stride).0];
let v4 = stride_to_index[(p1 - axis_b_stride - axis_c_stride).0];
let (pos1, pos2, pos3, pos4) = (
positions[v1 as usize],
positions[v2 as usize],
positions[v3 as usize],
positions[v4 as usize],
);
let quad = if sq_dist(pos1, pos4) < sq_dist(pos2, 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);
}
fn sq_dist(a: [f32; 3], b: [f32; 3]) -> f32 {
let d = [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
d[0] * d[0] + d[1] * d[1] + d[2] * d[2]
}