use super::PosNormMesh;
use building_blocks_core::prelude::*;
use building_blocks_storage::{access::GetUncheckedRelease, prelude::*};
pub fn padded_surface_nets_chunk_extent(chunk_extent: &Extent3i) -> Extent3i {
chunk_extent.padded(1)
}
pub trait SignedDistance {
fn distance(&self) -> f32;
}
impl SignedDistance for f32 {
fn distance(&self) -> f32 {
*self
}
}
#[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, output: &mut SurfaceNetsBuffer)
where
A: Array<[i32; 3]> + GetUncheckedRelease<Stride, T>,
T: SignedDistance,
{
output.reset(sdf.extent().num_points());
estimate_surface(sdf, extent, output);
make_all_quads(sdf, &extent, output);
}
fn estimate_surface<A, T>(sdf: &A, extent: &Extent3i, output: &mut SurfaceNetsBuffer)
where
A: Array<[i32; 3]> + GetUncheckedRelease<Stride, T>,
T: SignedDistance,
{
let mut corner_offset_strides = [Stride(0); 8];
let corner_offsets = Local::localize_points(&Point3i::corner_offsets());
sdf.strides_from_local_points(&corner_offsets, &mut corner_offset_strides);
let iter_extent = extent.add_to_shape(PointN([-1; 3]));
sdf.for_each_point_and_stride(&iter_extent, |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_voxel(sdf, &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, usize); 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_voxel<A, T>(
sdf: &A,
point: &Point3i,
corner_strides: &[Stride],
) -> Option<([f32; 3], [f32; 3])>
where
A: GetUncheckedRelease<Stride, T>,
T: SignedDistance,
{
let mut dists = [0.0; 8];
let mut num_negative = 0;
for (i, dist) in dists.iter_mut().enumerate() {
let d = sdf.get_unchecked_release(corner_strides[i]).distance();
*dist = d;
if d < 0.0 {
num_negative += 1;
}
}
if num_negative == 0 || num_negative == 8 {
return None;
}
let mut count = 0;
let mut sum = [0.0, 0.0, 0.0];
for (offset1, offset2) in CUBE_EDGES.iter() {
if let Some(intersection) =
estimate_surface_edge_intersection(*offset1, *offset2, dists[*offset1], dists[*offset2])
{
count += 1;
sum[0] += intersection[0];
sum[1] += intersection[1];
sum[2] += intersection[2];
}
}
let normal_x = (dists[0b001] + dists[0b011] + dists[0b101] + dists[0b111])
- (dists[0b000] + dists[0b010] + dists[0b100] + dists[0b110]);
let normal_y = (dists[0b010] + dists[0b011] + dists[0b110] + dists[0b111])
- (dists[0b000] + dists[0b001] + dists[0b100] + dists[0b101]);
let normal_z = (dists[0b100] + dists[0b101] + dists[0b110] + dists[0b111])
- (dists[0b000] + dists[0b001] + dists[0b010] + dists[0b011]);
Some((
[
sum[0] / count as f32 + point.x() as f32 + 0.5,
sum[1] / count as f32 + point.y() as f32 + 0.5,
sum[2] / count as f32 + point.z() as f32 + 0.5,
],
[normal_x, normal_y, normal_z],
))
}
fn estimate_surface_edge_intersection(
offset1: usize,
offset2: usize,
value1: f32,
value2: f32,
) -> Option<[f32; 3]> {
if (value1 < 0.0) == (value2 < 0.0) {
return None;
}
let interp1 = value1 / (value1 - value2);
let interp2 = 1.0 - interp1;
let position = [
(offset1 & 1) as f32 * interp2 + (offset2 & 1) as f32 * interp1,
((offset1 >> 1) & 1) as f32 * interp2 + ((offset2 >> 1) & 1) as f32 * interp1,
((offset1 >> 2) & 1) as f32 * interp2 + ((offset2 >> 2) & 1) as f32 * interp1,
];
Some(position)
}
fn make_all_quads<A, T>(sdf: &A, extent: &Extent3i, output: &mut SurfaceNetsBuffer)
where
A: Array<[i32; 3]> + GetUncheckedRelease<Stride, 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();
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: GetUncheckedRelease<Stride, T>,
T: SignedDistance,
{
let voxel1 = sdf.get_unchecked_release(p1);
let voxel2 = sdf.get_unchecked_release(p2);
let face_result = is_face(voxel1.distance(), voxel2.distance());
if let FaceResult::NoFace = face_result {
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) {
match face_result {
FaceResult::NoFace => unreachable!(),
FaceResult::FacePositive => [v1, v2, v4, v1, v4, v3],
FaceResult::FaceNegative => [v1, v4, v2, v1, v3, v4],
}
} else {
match face_result {
FaceResult::NoFace => unreachable!(),
FaceResult::FacePositive => [v2, v4, v3, v2, v3, v1],
FaceResult::FaceNegative => [v2, v3, v4, v2, v1, v3],
}
};
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]
}
enum FaceResult {
NoFace,
FacePositive,
FaceNegative,
}
fn is_face(d1: f32, d2: f32) -> FaceResult {
match (d1 < 0.0, d2 < 0.0) {
(true, false) => FaceResult::FacePositive,
(false, true) => FaceResult::FaceNegative,
_ => FaceResult::NoFace,
}
}