use std::f32::consts::{PI, TAU};
use super::{Mesh, MeshError, Vec3};
const CORNERS: [[usize; 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 EDGES: [[usize; 2]; 12] = [
[0, 1],
[2, 3],
[4, 5],
[6, 7],
[0, 2],
[1, 3],
[4, 6],
[5, 7],
[0, 4],
[1, 5],
[2, 6],
[3, 7],
];
const AROUND_EDGES: [([usize; 3], [[usize; 3]; 4]); 3] = [
([1, 0, 0], [[0, 1, 1], [0, 0, 1], [0, 0, 0], [0, 1, 0]]),
([0, 1, 0], [[1, 0, 1], [1, 0, 0], [0, 0, 0], [0, 0, 1]]),
([0, 0, 1], [[1, 1, 0], [0, 1, 0], [0, 0, 0], [1, 0, 0]]),
];
struct Samples {
values: Vec<f32>,
cells: usize,
low: Vec3,
step: Vec3,
}
impl Samples {
fn of(field: &impl Fn(Vec3) -> f32, (low, high): (Vec3, Vec3), cells: usize) -> Self {
let side = cells as f32;
let step = Vec3::new(
(high.x - low.x) / side,
(high.y - low.y) / side,
(high.z - low.z) / side,
);
let points = cells + 1;
let mut values = Vec::with_capacity(points * points * points);
for k in 0..points {
for j in 0..points {
for i in 0..points {
values.push(field(Self::place(low, step, [i, j, k])));
}
}
}
Self {
values,
cells,
low,
step,
}
}
fn place(low: Vec3, step: Vec3, [i, j, k]: [usize; 3]) -> Vec3 {
Vec3::new(
low.x + step.x * i as f32,
low.y + step.y * j as f32,
low.z + step.z * k as f32,
)
}
fn point(&self, at: [usize; 3]) -> Vec3 {
Self::place(self.low, self.step, at)
}
fn value(&self, [i, j, k]: [usize; 3]) -> f32 {
let points = self.cells + 1;
self.values[(k * points + j) * points + i]
}
fn cell_point(&self, [i, j, k]: [usize; 3]) -> Option<Vec3> {
let corner = |index: usize| {
let [di, dj, dk] = CORNERS[index];
[i + di, j + dj, k + dk]
};
let (mut sum, mut count) = (Vec3::ZERO, 0.0);
for [a, b] in EDGES {
let (va, vb) = (self.value(corner(a)), self.value(corner(b)));
if (va < 0.0) == (vb < 0.0) {
continue;
}
let (pa, pb) = (self.point(corner(a)), self.point(corner(b)));
sum = sum + pa + (pb - pa) * (va / (va - vb));
count += 1.0;
}
(count > 0.0).then(|| sum * (1.0 / count))
}
}
impl Mesh {
pub fn from_sdf(
field: impl Fn(Vec3) -> f32,
bounds: (Vec3, Vec3),
resolution: u32,
) -> Result<Self, MeshError> {
let cells = resolution.max(2) as usize;
let samples = Samples::of(&field, bounds, cells);
let middle = (bounds.0 + bounds.1) * 0.5;
let reach = samples.step.x.min(samples.step.y).min(samples.step.z) * 0.5;
let mut net = Net::default();
let mut cell_vertices = vec![u32::MAX; cells * cells * cells];
for k in 0..cells {
for j in 0..cells {
for i in 0..cells {
if let Some(point) = samples.cell_point([i, j, k]) {
cell_vertices[(k * cells + j) * cells + i] =
net.vertex(point, gradient(&field, point, reach), middle);
}
}
}
}
let cell = |[i, j, k]: [usize; 3]| cell_vertices[(k * cells + j) * cells + i];
for (step, around) in AROUND_EDGES {
for k in 0..cells {
for j in 0..cells {
for i in 0..cells {
let start = [i, j, k];
let back = |axis: usize| around.iter().any(|offset| offset[axis] > 0);
if (0..3).any(|axis| back(axis) && start[axis] == 0) {
continue;
}
let end = [i + step[0], j + step[1], k + step[2]];
let cells_around =
around.map(|[di, dj, dk]| cell([i - di, j - dj, k - dk]));
net.join(&samples, (start, end), cells_around);
}
}
}
}
Self::new(net.positions, net.uvs, Some(net.normals), net.indices)
}
}
#[derive(Default)]
struct Net {
positions: Vec<[f32; 3]>,
normals: Vec<[f32; 3]>,
uvs: Vec<[f32; 2]>,
indices: Vec<u32>,
turned: Vec<u32>,
}
impl Net {
fn vertex(&mut self, point: Vec3, normal: Vec3, middle: Vec3) -> u32 {
let index = self.positions.len() as u32;
self.positions.push([point.x, point.y, point.z]);
self.normals.push([normal.x, normal.y, normal.z]);
self.uvs.push(wrapped_uv(point - middle));
self.turned.push(u32::MAX);
index
}
fn join(&mut self, samples: &Samples, edge: ([usize; 3], [usize; 3]), around: [u32; 4]) {
let (from, to) = (samples.value(edge.0), samples.value(edge.1));
if (from < 0.0) == (to < 0.0) || around.contains(&u32::MAX) {
return;
}
let [a, b, c, d] = if from < 0.0 {
around
} else {
[around[0], around[3], around[2], around[1]]
};
self.triangle([a, b, c]);
self.triangle([a, c, d]);
}
fn triangle(&mut self, corners: [u32; 3]) {
let us = corners.map(|corner| self.uvs[corner as usize][0]);
let crosses = us.iter().copied().fold(f32::NEG_INFINITY, f32::max)
- us.iter().copied().fold(f32::INFINITY, f32::min)
> 0.5;
for (corner, u) in corners.into_iter().zip(us) {
let index = if crosses && u < 0.5 {
self.turned_copy(corner)
} else {
corner
};
self.indices.push(index);
}
}
fn turned_copy(&mut self, corner: u32) -> u32 {
let at = corner as usize;
if self.turned[at] == u32::MAX {
self.turned[at] = self.positions.len() as u32;
self.positions.push(self.positions[at]);
self.normals.push(self.normals[at]);
let [u, v] = self.uvs[at];
self.uvs.push([u + 1.0, v]);
self.turned.push(u32::MAX);
}
self.turned[at]
}
}
fn wrapped_uv(direction: Vec3) -> [f32; 2] {
let Some(direction) = direction.normalized() else {
return [0.5, 0.5];
};
[
direction.x.atan2(-direction.z) / TAU + 0.5,
(-direction.y).clamp(-1.0, 1.0).acos() / PI,
]
}
fn gradient(field: &impl Fn(Vec3) -> f32, point: Vec3, reach: f32) -> Vec3 {
let along = |axis: Vec3| field(point + axis * reach) - field(point - axis * reach);
Vec3::new(
along(Vec3::new(1.0, 0.0, 0.0)),
along(Vec3::new(0.0, 1.0, 0.0)),
along(Vec3::new(0.0, 0.0, 1.0)),
)
.normalized()
.unwrap_or(Vec3::new(0.0, 0.0, -1.0))
}