use glam::Vec3;
use crate::mesh::Mesh;
pub fn smin(a: f32, b: f32, k: f32) -> f32 {
if k <= 0.0 {
return a.min(b);
}
let h = ((k - (a - b).abs()).max(0.0)) / k;
a.min(b) - h * h * k * 0.25
}
pub fn sd_sphere(p: Vec3, c: Vec3, r: f32) -> f32 {
(p - c).length() - r
}
pub fn sd_round_cone(p: Vec3, a: Vec3, b: Vec3, r1: f32, r2: f32) -> f32 {
let ba = b - a;
let l2 = ba.dot(ba).max(1e-12);
let rr = r1 - r2;
let a2 = l2 - rr * rr;
let il2 = 1.0 / l2;
let pa = p - a;
let y = pa.dot(ba);
let z = y - l2;
let x2 = (pa * l2 - ba * y).length_squared();
let y2 = y * y * l2;
let z2 = z * z * l2;
let k = rr.signum() * rr * rr * x2;
if z.signum() * a2 * z2 > k {
(x2 + z2).sqrt() * il2 - r2
} else if y.signum() * a2 * y2 < k {
(x2 + y2).sqrt() * il2 - r1
} else {
((x2 * a2 * il2).sqrt() + y * rr) * il2 - r1
}
}
pub fn sd_ellipsoid(p: Vec3, c: Vec3, r: Vec3) -> f32 {
let q = p - c;
let k0 = (q / r).length();
let k1 = (q / (r * r)).length();
if k1 < 1e-9 {
-r.min_element()
} else {
k0 * (k0 - 1.0) / k1
}
}
pub fn sd_round_cone_scaled(
p: Vec3,
a: Vec3,
b: Vec3,
r1: f32,
r2: f32,
origin: Vec3,
scale: Vec3,
) -> f32 {
let q = origin + (p - origin) / scale;
sd_round_cone(q, a, b, r1, r2) * scale.min_element()
}
pub fn mesh_field(
lo: Vec3,
hi: Vec3,
cell: f32,
field: &dyn Fn(Vec3) -> f32,
color: &dyn Fn(Vec3) -> Vec3,
) -> Mesh {
let n = |a: f32, b: f32| (((b - a) / cell).ceil() as usize).max(2);
let (nx, ny, nz) = (n(lo.x, hi.x), n(lo.y, hi.y), n(lo.z, hi.z));
let corner = |i: usize, j: usize, k: usize| lo + Vec3::new(i as f32, j as f32, k as f32) * cell;
let sx = nx + 1;
let sy = ny + 1;
let sz = nz + 1;
let idx = |i: usize, j: usize, k: usize| (i * sy + j) * sz + k;
let mut samples = vec![0.0f32; sx * sy * sz];
for i in 0..sx {
for j in 0..sy {
for k in 0..sz {
samples[idx(i, j, k)] = field(corner(i, j, k));
}
}
}
let s = |i: usize, j: usize, k: usize| samples[idx(i, j, k)];
let cidx = |i: usize, j: usize, k: usize| (i * ny + j) * nz + k;
let mut cell_vert = vec![u32::MAX; nx * ny * nz];
let mut m = Mesh::new();
#[allow(clippy::type_complexity)]
const EDGES: [((usize, usize, usize), (usize, usize, usize)); 12] = [
((0, 0, 0), (1, 0, 0)),
((0, 1, 0), (1, 1, 0)),
((0, 0, 1), (1, 0, 1)),
((0, 1, 1), (1, 1, 1)),
((0, 0, 0), (0, 1, 0)),
((1, 0, 0), (1, 1, 0)),
((0, 0, 1), (0, 1, 1)),
((1, 0, 1), (1, 1, 1)),
((0, 0, 0), (0, 0, 1)),
((1, 0, 0), (1, 0, 1)),
((0, 1, 0), (0, 1, 1)),
((1, 1, 0), (1, 1, 1)),
];
for i in 0..nx {
for j in 0..ny {
for k in 0..nz {
let mut sum = Vec3::ZERO;
let mut cnt = 0;
for (ea, eb) in EDGES {
let va = s(i + ea.0, j + ea.1, k + ea.2);
let vb = s(i + eb.0, j + eb.1, k + eb.2);
if (va < 0.0) != (vb < 0.0) {
let t = va / (va - vb);
let pa = corner(i + ea.0, j + ea.1, k + ea.2);
let pb = corner(i + eb.0, j + eb.1, k + eb.2);
sum += pa + (pb - pa) * t;
cnt += 1;
}
}
if cnt > 0 {
let p = sum / cnt as f32;
let e = cell * 0.5;
let nrm = Vec3::new(
field(p + Vec3::X * e) - field(p - Vec3::X * e),
field(p + Vec3::Y * e) - field(p - Vec3::Y * e),
field(p + Vec3::Z * e) - field(p - Vec3::Z * e),
)
.normalize_or(Vec3::Y);
cell_vert[cidx(i, j, k)] = m.push_vertex(p, nrm, color(p));
}
}
}
}
let mut quad = |a: u32, b: u32, c: u32, d: u32, flip: bool| {
if a == u32::MAX || b == u32::MAX || c == u32::MAX || d == u32::MAX {
return;
}
if flip {
m.push_tri(a, b, c);
m.push_tri(a, c, d);
} else {
m.push_tri(a, c, b);
m.push_tri(a, d, c);
}
};
for i in 0..nx {
for j in 0..ny {
for k in 0..nz {
if j > 0 && k > 0 {
let (va, vb) = (s(i, j, k), s(i + 1, j, k));
if (va < 0.0) != (vb < 0.0) {
quad(
cell_vert[cidx(i, j - 1, k - 1)],
cell_vert[cidx(i, j, k - 1)],
cell_vert[cidx(i, j, k)],
cell_vert[cidx(i, j - 1, k)],
va < 0.0,
);
}
}
if i > 0 && k > 0 {
let (va, vb) = (s(i, j, k), s(i, j + 1, k));
if (va < 0.0) != (vb < 0.0) {
quad(
cell_vert[cidx(i - 1, j, k - 1)],
cell_vert[cidx(i - 1, j, k)],
cell_vert[cidx(i, j, k)],
cell_vert[cidx(i, j, k - 1)],
va < 0.0,
);
}
}
if i > 0 && j > 0 {
let (va, vb) = (s(i, j, k), s(i, j, k + 1));
if (va < 0.0) != (vb < 0.0) {
quad(
cell_vert[cidx(i - 1, j - 1, k)],
cell_vert[cidx(i, j - 1, k)],
cell_vert[cidx(i, j, k)],
cell_vert[cidx(i - 1, j, k)],
va < 0.0,
);
}
}
}
}
}
m
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn surface_nets_sphere() {
let f = |p: Vec3| sd_sphere(p, Vec3::ZERO, 1.0);
let c = |_: Vec3| Vec3::ONE;
let m = mesh_field(Vec3::splat(-1.4), Vec3::splat(1.4), 0.1, &f, &c);
m.validate().unwrap();
assert!(m.triangle_count() > 500, "{}", m.triangle_count());
for (p, n) in m.positions.iter().zip(&m.normals) {
assert!((p.length() - 1.0).abs() < 0.08, "vertex off surface: {p}");
assert!(n.dot(p.normalize()) > 0.8, "normal not outward");
}
let m2 = mesh_field(Vec3::splat(-1.4), Vec3::splat(1.4), 0.1, &f, &c);
assert_eq!(m.positions, m2.positions);
}
#[test]
fn smin_blends_and_round_cone_tapers() {
let a = 3.0;
let b = 0.5;
assert_eq!(smin(a, b, 0.2), b);
assert!(smin(0.5, 0.5, 0.2) < 0.5);
let a_pt = Vec3::ZERO;
let b_pt = Vec3::new(0.0, 2.0, 0.0);
let d1 = sd_round_cone(Vec3::new(0.5, 0.0, 0.0), a_pt, b_pt, 0.4, 0.1);
let d2 = sd_round_cone(Vec3::new(0.5, 2.0, 0.0), a_pt, b_pt, 0.4, 0.1);
assert!((d1 - 0.1).abs() < 0.02);
assert!((d2 - 0.4).abs() < 0.02);
}
}