use crate::mesh::bvh::MeshBvh;
use crate::mesh::mesh_sign::{ExteriorField, MeshSignMode};
use crate::mesh::MeshInputError;
use crate::types::SdfNode;
use glam::Vec3;
use rayon::prelude::*;
use std::collections::HashSet;
use std::sync::Arc;
pub const DEFAULT_SIGN_FLOOD_FILL_RESOLUTION: u32 = 64;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum MeshToSdfStrategy {
#[default]
Capsule,
BvhExact,
Hybrid,
}
#[derive(Debug, Clone)]
pub struct MeshToSdfConfig {
pub strategy: MeshToSdfStrategy,
pub use_bvh: bool,
pub max_triangles_per_leaf: usize,
pub capsule_radius_factor: f32,
pub compute_normals: bool,
pub sign_mode: MeshSignMode,
pub sign_flood_fill_resolution: u32,
}
impl Default for MeshToSdfConfig {
fn default() -> Self {
Self {
strategy: MeshToSdfStrategy::Capsule,
use_bvh: true,
max_triangles_per_leaf: 4,
capsule_radius_factor: 0.05,
compute_normals: true,
sign_mode: MeshSignMode::NearestFaceNormal,
sign_flood_fill_resolution: DEFAULT_SIGN_FLOOD_FILL_RESOLUTION,
}
}
}
impl MeshToSdfConfig {
pub fn fast() -> Self {
Self {
strategy: MeshToSdfStrategy::Capsule,
use_bvh: false,
..Default::default()
}
}
pub fn accurate() -> Self {
Self {
strategy: MeshToSdfStrategy::BvhExact,
use_bvh: true,
max_triangles_per_leaf: 4,
sign_mode: MeshSignMode::ExteriorFloodFill,
..Default::default()
}
}
pub fn hybrid() -> Self {
Self {
strategy: MeshToSdfStrategy::Hybrid,
use_bvh: true,
max_triangles_per_leaf: 4,
..Default::default()
}
}
pub fn topology_robust() -> Self {
Self::accurate()
}
}
pub struct MeshSdf {
bvh: Arc<MeshBvh>,
bounds: crate::types::Aabb,
sign_mode: MeshSignMode,
exterior: Option<Arc<ExteriorField>>,
}
impl MeshSdf {
pub fn new(vertices: &[Vec3], indices: &[u32], config: &MeshToSdfConfig) -> Option<Self> {
Self::try_new(vertices, indices, config).ok()
}
pub fn try_new(
vertices: &[Vec3],
indices: &[u32],
config: &MeshToSdfConfig,
) -> Result<Self, MeshInputError> {
if vertices.is_empty() || indices.is_empty() {
return Err(MeshInputError {
reason: "mesh_to_sdf needs at least one triangle",
});
}
let bvh = MeshBvh::build(vertices, indices, config.max_triangles_per_leaf);
let bounds = bvh.bounds().ok_or(MeshInputError {
reason: "mesh has no bounding box",
})?;
let exterior = match config.sign_mode {
MeshSignMode::ExteriorFloodFill => Some(Arc::new(ExteriorField::build(
&bvh,
config.sign_flood_fill_resolution,
)?)),
_ => None,
};
Ok(Self {
bvh: Arc::new(bvh),
bounds,
sign_mode: config.sign_mode,
exterior,
})
}
#[inline]
pub const fn sign_mode(&self) -> MeshSignMode {
self.sign_mode
}
#[inline]
pub fn eval(&self, point: Vec3) -> f32 {
self.exterior.as_ref().map_or_else(
|| self.bvh.signed_distance(point),
|field| field.signed_distance(&self.bvh, point),
)
}
#[inline]
pub fn eval_unsigned(&self, point: Vec3) -> f32 {
self.bvh.unsigned_distance(point)
}
pub fn eval_batch(&self, points: &[Vec3]) -> Vec<f32> {
self.exterior.as_ref().map_or_else(
|| self.bvh.signed_distance_batch(points),
|field| {
points
.par_iter()
.map(|&p| field.signed_distance(&self.bvh, p))
.collect()
},
)
}
pub fn eval_unsigned_batch(&self, points: &[Vec3]) -> Vec<f32> {
self.bvh.unsigned_distance_batch(points)
}
pub fn gradient(&self, point: Vec3, epsilon: f32) -> Vec3 {
let dx = self.eval(point + Vec3::X * epsilon) - self.eval(point - Vec3::X * epsilon);
let dy = self.eval(point + Vec3::Y * epsilon) - self.eval(point - Vec3::Y * epsilon);
let dz = self.eval(point + Vec3::Z * epsilon) - self.eval(point - Vec3::Z * epsilon);
Vec3::new(dx, dy, dz).normalize_or_zero()
}
pub const fn bounds(&self) -> (Vec3, Vec3) {
(self.bounds.min, self.bounds.max)
}
pub fn triangle_count(&self) -> usize {
self.bvh.triangle_count()
}
pub fn bvh(&self) -> &MeshBvh {
&self.bvh
}
pub fn to_sdf_node(&self, radius_factor: f32) -> SdfNode {
let mut vertices: Vec<Vec3> = Vec::new();
let mut vertex_map: std::collections::HashMap<[u32; 3], usize> =
std::collections::HashMap::new();
let mut unique_edges: HashSet<(usize, usize)> = HashSet::new();
for tri in &self.bvh.triangles {
let mut get_or_add = |v: Vec3| -> usize {
let key = [v.x.to_bits(), v.y.to_bits(), v.z.to_bits()];
if let Some(&idx) = vertex_map.get(&key) {
idx
} else {
let idx = vertices.len();
vertices.push(v);
vertex_map.insert(key, idx);
idx
}
};
let i0 = get_or_add(tri.v0);
let i1 = get_or_add(tri.v1);
let i2 = get_or_add(tri.v2);
unique_edges.insert(edge_key_usize(i0, i1));
unique_edges.insert(edge_key_usize(i1, i2));
unique_edges.insert(edge_key_usize(i2, i0));
}
let avg_edge_len: f32 = if unique_edges.is_empty() {
0.1
} else {
let total: f32 = unique_edges
.iter()
.map(|&(i0, i1)| (vertices[i1] - vertices[i0]).length())
.sum();
total / unique_edges.len() as f32
};
let radius = avg_edge_len * radius_factor;
let nodes: Vec<SdfNode> = unique_edges
.into_iter()
.map(|(i0, i1)| SdfNode::capsule(vertices[i0], vertices[i1], radius))
.collect();
reduce_to_union(nodes)
}
}
pub fn mesh_to_sdf(vertices: &[Vec3], indices: &[u32], config: &MeshToSdfConfig) -> SdfNode {
if vertices.is_empty() || indices.is_empty() {
return SdfNode::sphere(0.001); }
let mut total_edge_len = 0.0;
let mut edge_count = 0;
for chunk in indices.chunks(3) {
if chunk.len() == 3 {
let v0 = vertices[chunk[0] as usize];
let v1 = vertices[chunk[1] as usize];
let v2 = vertices[chunk[2] as usize];
total_edge_len += (v1 - v0).length();
total_edge_len += (v2 - v1).length();
total_edge_len += (v0 - v2).length();
edge_count += 3;
}
}
let avg_edge_len = if edge_count > 0 {
total_edge_len / edge_count as f32
} else {
0.1
};
let radius = avg_edge_len * config.capsule_radius_factor;
let mut unique_edges: HashSet<(u32, u32)> = HashSet::new();
for chunk in indices.chunks(3) {
if chunk.len() == 3 {
let i0 = chunk[0];
let i1 = chunk[1];
let i2 = chunk[2];
unique_edges.insert(edge_key(i0, i1));
unique_edges.insert(edge_key(i1, i2));
unique_edges.insert(edge_key(i2, i0));
}
}
let nodes: Vec<SdfNode> = unique_edges
.into_iter()
.map(|(i0, i1)| {
let v0 = vertices[i0 as usize];
let v1 = vertices[i1 as usize];
SdfNode::capsule(v0, v1, radius)
})
.collect();
reduce_to_union(nodes)
}
pub fn mesh_to_sdf_exact(
vertices: &[Vec3],
indices: &[u32],
config: &MeshToSdfConfig,
) -> Option<MeshSdf> {
MeshSdf::new(vertices, indices, config)
}
#[inline(always)]
const fn edge_key(a: u32, b: u32) -> (u32, u32) {
if a < b {
(a, b)
} else {
(b, a)
}
}
#[inline(always)]
const fn edge_key_usize(a: usize, b: usize) -> (usize, usize) {
if a < b {
(a, b)
} else {
(b, a)
}
}
#[inline(always)]
fn reduce_to_union(mut nodes: Vec<SdfNode>) -> SdfNode {
if nodes.is_empty() {
return SdfNode::sphere(0.001);
}
while nodes.len() > 1 {
let mut next = Vec::with_capacity(nodes.len().div_ceil(2));
for chunk in nodes.chunks(2) {
if chunk.len() == 2 {
next.push(chunk[0].clone().union(chunk[1].clone()));
} else {
next.push(chunk[0].clone());
}
}
nodes = next;
}
nodes.pop().unwrap()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mesh_to_sdf_single_triangle() {
let vertices = vec![
Vec3::new(0.0, 0.0, 0.0),
Vec3::new(1.0, 0.0, 0.0),
Vec3::new(0.5, 1.0, 0.0),
];
let indices = vec![0, 1, 2];
let sdf = mesh_to_sdf(&vertices, &indices, &MeshToSdfConfig::default());
assert!(sdf.node_count() > 0);
}
#[test]
fn test_mesh_to_sdf_cube() {
let vertices = vec![
Vec3::new(-1.0, -1.0, -1.0),
Vec3::new(1.0, -1.0, -1.0),
Vec3::new(1.0, 1.0, -1.0),
Vec3::new(-1.0, 1.0, -1.0),
Vec3::new(-1.0, -1.0, 1.0),
Vec3::new(1.0, -1.0, 1.0),
Vec3::new(1.0, 1.0, 1.0),
Vec3::new(-1.0, 1.0, 1.0),
];
let indices = vec![
0, 1, 2, 0, 2, 3, 4, 6, 5, 4, 7, 6, ];
let sdf = mesh_to_sdf(&vertices, &indices, &MeshToSdfConfig::default());
assert!(sdf.node_count() > 0);
}
#[test]
fn test_mesh_to_sdf_empty() {
let vertices: Vec<Vec3> = vec![];
let indices: Vec<u32> = vec![];
let sdf = mesh_to_sdf(&vertices, &indices, &MeshToSdfConfig::default());
assert_eq!(sdf.node_count(), 1);
}
#[test]
fn test_reduce_to_union() {
let nodes = vec![
SdfNode::sphere(1.0),
SdfNode::sphere(1.0),
SdfNode::sphere(1.0),
SdfNode::sphere(1.0),
];
let union = reduce_to_union(nodes);
assert_eq!(union.node_count(), 7);
}
#[test]
fn test_mesh_sdf_exact() {
let vertices = vec![
Vec3::new(-1.0, -1.0, 0.0),
Vec3::new(1.0, -1.0, 0.0),
Vec3::new(1.0, 1.0, 0.0),
Vec3::new(-1.0, 1.0, 0.0),
];
let indices = vec![0, 1, 2, 0, 2, 3];
let mesh_sdf = mesh_to_sdf_exact(&vertices, &indices, &MeshToSdfConfig::accurate())
.expect("Should create MeshSdf");
let d_surface = mesh_sdf.eval(Vec3::ZERO);
assert!(d_surface.abs() < 0.01, "Expected ~0, got {}", d_surface);
let d_above = mesh_sdf.eval(Vec3::new(0.0, 0.0, 1.0));
assert!(
(d_above - 1.0).abs() < 0.01,
"Expected ~+1 above an open sheet, got {}",
d_above
);
let d_below = mesh_sdf.eval(Vec3::new(0.0, 0.0, -1.0));
assert!(
(d_below - 1.0).abs() < 0.01,
"Expected ~+1 below an open sheet too, got {}",
d_below
);
let points = vec![
Vec3::ZERO,
Vec3::new(0.0, 0.0, 1.0),
Vec3::new(0.0, 0.0, -1.0),
];
let distances = mesh_sdf.eval_batch(&points);
assert_eq!(distances.len(), 3);
}
#[test]
fn test_mesh_sdf_to_node() {
let vertices = vec![
Vec3::new(0.0, 0.0, 0.0),
Vec3::new(1.0, 0.0, 0.0),
Vec3::new(0.5, 1.0, 0.0),
];
let indices = vec![0, 1, 2];
let mesh_sdf = mesh_to_sdf_exact(&vertices, &indices, &MeshToSdfConfig::accurate())
.expect("Should create MeshSdf");
let node = mesh_sdf.to_sdf_node(0.05);
assert!(node.node_count() > 0);
}
#[test]
fn test_config_presets() {
let fast = MeshToSdfConfig::fast();
assert_eq!(fast.strategy, MeshToSdfStrategy::Capsule);
assert!(!fast.use_bvh);
let accurate = MeshToSdfConfig::accurate();
assert_eq!(accurate.strategy, MeshToSdfStrategy::BvhExact);
assert!(accurate.use_bvh);
assert_eq!(accurate.sign_mode, MeshSignMode::ExteriorFloodFill);
assert_eq!(
MeshToSdfConfig::topology_robust().sign_mode,
accurate.sign_mode
);
assert_eq!(
MeshToSdfConfig::topology_robust().strategy,
accurate.strategy
);
assert_eq!(
MeshToSdfConfig::default().sign_mode,
MeshSignMode::NearestFaceNormal
);
let hybrid = MeshToSdfConfig::hybrid();
assert_eq!(hybrid.strategy, MeshToSdfStrategy::Hybrid);
assert!(hybrid.use_bvh);
}
}