use std::boxed::Box;
use itertools::Itertools;
use ordered_float::NotNan;
use rayon::prelude::*;
mod geo;
mod grid;
mod point;
pub use grid::{Grid, SnapResult};
pub use point::Point;
pub enum Topology<'a, I>
where
I: Into<u32>,
{
TriangleList(Option<&'a [I]>),
TriangleStrip(Option<&'a [I]>),
}
impl<'a, I> Topology<'a, I>
where
I: Into<u32>,
{
fn get_triangles<V>(
vertices: &[V],
indices: &'a Topology<I>,
) -> Box<dyn Iterator<Item = (usize, usize, usize)> + 'a>
where
V: Point,
I: Copy + Into<u32> + Sync + Send,
{
match indices {
Topology::TriangleList(Some(indices)) => {
Box::new(indices.iter().map(|x| (*x).into() as usize).tuples())
}
Topology::TriangleList(None) => Box::new((0..vertices.len()).tuples()),
Topology::TriangleStrip(Some(indices)) => {
Box::new(indices.iter().map(|x| (*x).into() as usize).tuple_windows())
}
Topology::TriangleStrip(None) => Box::new((0..vertices.len()).tuple_windows()),
}
}
}
fn compare_distances(a: f32, b: f32) -> std::cmp::Ordering {
if float_cmp::approx_eq!(f32, a.abs(), b.abs(), ulps = 2, epsilon = 1e-6) {
match (a.is_sign_negative(), b.is_sign_negative()) {
(true, false) => std::cmp::Ordering::Greater,
(false, true) => std::cmp::Ordering::Less,
_ => a.abs().partial_cmp(&b.abs()).unwrap(),
}
} else {
a.abs().partial_cmp(&b.abs()).expect("NaN distance")
}
}
pub fn generate_sdf<V, I>(vertices: &[V], indices: Topology<I>, query_points: &[V]) -> Vec<f32>
where
V: Point,
I: Copy + Into<u32> + Sync + Send,
{
query_points
.par_iter()
.map(|query| {
Topology::get_triangles(vertices, &indices)
.map(|(i, j, k)| (&vertices[i], &vertices[j], &vertices[k]))
.map(|(a, b, c)| geo::point_triangle_signed_distance(query, a, b, c))
.min_by(|a, b| compare_distances(*a, *b))
.expect("no triangle found") })
.collect()
}
#[derive(Copy, Clone, Eq, PartialEq)]
struct State {
distance: NotNan<f32>,
cell: [usize; 3],
triangle: (usize, usize, usize),
}
impl Ord for State {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
compare_distances(other.distance.into_inner(), self.distance.into_inner())
.then_with(|| self.cell.cmp(&other.cell))
.then_with(|| self.triangle.cmp(&other.triangle))
}
}
impl PartialOrd for State {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
pub fn generate_grid_sdf<V, I>(vertices: &[V], indices: Topology<I>, grid: &Grid<V>) -> Vec<f32>
where
V: Point,
I: Copy + Into<u32> + Sync + Send,
{
let mut distances = vec![f32::MAX; grid.get_total_cell_count()];
let mut heap = std::collections::BinaryHeap::new();
let mut steps = 0;
Topology::get_triangles(vertices, &indices).for_each(|triangle| {
let a = &vertices[triangle.0];
let b = &vertices[triangle.1];
let c = &vertices[triangle.2];
let bounding_box = geo::triangle_bounding_box(a, b, c);
let min_cell = match grid.snap_point_to_grid(&bounding_box.0) {
SnapResult::Inside(cell) | SnapResult::Outside(cell) => cell,
};
let max_cell = match grid.snap_point_to_grid(&bounding_box.1) {
SnapResult::Inside(cell) | SnapResult::Outside(cell) => cell,
};
let min_cell = [
if min_cell[0] == 0 { 0 } else { min_cell[0] - 1 },
if min_cell[1] == 0 { 0 } else { min_cell[1] - 1 },
if min_cell[2] == 0 { 0 } else { min_cell[2] - 1 },
];
let max_cell = [
(max_cell[0] + 1).min(grid.get_cell_count()[0] - 1),
(max_cell[1] + 1).min(grid.get_cell_count()[1] - 1),
(max_cell[2] + 1).min(grid.get_cell_count()[2] - 1),
];
for cell in itertools::iproduct!(
min_cell[0]..=max_cell[0],
min_cell[1]..=max_cell[1],
min_cell[2]..=max_cell[2]
) {
let cell = [cell.0, cell.1, cell.2];
let cell_idx = grid.get_cell_idx(&cell);
if cell_idx >= grid.get_total_cell_count() {
continue;
}
let cell_pos = grid.get_cell_center(&cell);
let distance = geo::point_triangle_signed_distance(&cell_pos, a, b, c);
if compare_distances(distance, distances[cell_idx]).is_lt() {
steps += 1;
distances[cell_idx] = distance;
let state = State {
distance: NotNan::new(distance).unwrap(), triangle,
cell,
};
heap.push(state);
}
}
});
log::info!("[generate_grid_sdf] init steps: {}", steps);
steps = 0;
while let Some(State { triangle, cell, .. }) = heap.pop() {
let a = &vertices[triangle.0];
let b = &vertices[triangle.1];
let c = &vertices[triangle.2];
let neighbours = itertools::iproduct!(-1..=1, -1..=1, -1..=1)
.map(|v| {
(
cell[0] as isize + v.0,
cell[1] as isize + v.1,
cell[2] as isize + v.2,
)
})
.filter(|&(x, y, z)| {
x >= 0
&& y >= 0
&& z >= 0
&& x < grid.get_cell_count()[0] as isize
&& y < grid.get_cell_count()[1] as isize
&& z < grid.get_cell_count()[2] as isize
})
.map(|(x, y, z)| [x as usize, y as usize, z as usize]);
for neighbour_cell in neighbours {
let neighbour_cell_pos = grid.get_cell_center(&neighbour_cell);
let neighbour_cell_idx = grid.get_cell_idx(&neighbour_cell);
let distance = geo::point_triangle_signed_distance(&neighbour_cell_pos, a, b, c);
if compare_distances(distance, distances[neighbour_cell_idx]).is_lt() {
steps += 1;
distances[neighbour_cell_idx] = distance;
let state = State {
distance: NotNan::new(distance).unwrap(), triangle,
cell: neighbour_cell,
};
heap.push(state);
}
}
}
log::info!("[generate_grid_sdf] propagation steps: {}", steps);
distances
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generate() {
let model = &easy_gltf::load("assets/suzanne.glb").unwrap()[0].models[0];
let vertices = model.vertices().iter().map(|v| v.position).collect_vec();
let indices = model.indices().unwrap();
let query_points = [
cgmath::Vector3::new(0.0, 0.0, 0.0),
cgmath::Vector3::new(1.0, 1.0, 1.0),
cgmath::Vector3::new(0.1, 0.2, 0.2),
];
let sdf = generate_sdf(
&vertices,
crate::Topology::TriangleList(Some(indices)),
&query_points,
);
let baseline = [-0.42, 0.69, -0.46];
for (sdf, baseline) in sdf.iter().zip(baseline.iter()) {
assert!((sdf - baseline).abs() < 0.1);
}
}
#[test]
fn test_generate_grid() {
let vertices: Vec<[f32; 3]> = vec![[0., 1., 0.], [1., 2., 3.], [1., 3., 4.], [2., 0., 0.]];
let indices: Vec<u32> = vec![0, 1, 2, 1, 2, 3];
let grid = Grid::from_bounding_box(&[0., 0., 0.], &[5., 5., 5.], &[5, 5, 5]);
let mut query_points = Vec::new();
for x in 0..grid.get_cell_count()[0] {
for y in 0..grid.get_cell_count()[1] {
for z in 0..grid.get_cell_count()[2] {
query_points.push(grid.get_cell_center(&[x, y, z]));
}
}
}
let sdf = generate_sdf(
&vertices,
crate::Topology::TriangleList(Some(&indices)),
&query_points,
);
let grid_sdf = generate_grid_sdf(
&vertices,
crate::Topology::TriangleList(Some(&indices)),
&grid,
);
for (i, (sdf, grid_sdf)) in sdf.iter().zip(grid_sdf.iter()).enumerate() {
assert_eq!(sdf, grid_sdf, "i: {}", i);
}
}
#[test]
fn test_topology() {
let grid = Grid::from_bounding_box(&[0., 0., 0.], &[5., 5., 5.], &[5, 5, 5]);
let v0 = [0., 1., 0.];
let v1 = [1., 2., 3.];
let v2 = [1., 3., 4.];
let v3 = [2., 0., 0.];
let triangle_list_indices = {
let vertices: Vec<[f32; 3]> = vec![v0, v1, v2, v3];
let indices: Vec<u32> = vec![0, 1, 2, 1, 2, 3, 2, 3, 0];
generate_grid_sdf(
&vertices,
crate::Topology::TriangleList(Some(&indices)),
&grid,
)
};
let triangle_list_none = {
let vertices: Vec<[f32; 3]> = vec![v0, v1, v2, v1, v2, v3, v2, v3, v0];
generate_grid_sdf(&vertices, Topology::TriangleList::<u32>(None), &grid)
};
let triangle_strip_indices = {
let vertices: Vec<[f32; 3]> = vec![v0, v1, v2, v3];
let indices: Vec<u32> = vec![0, 1, 2, 3, 0];
generate_grid_sdf(&vertices, Topology::TriangleStrip(Some(&indices)), &grid)
};
let triangle_strip_none = {
let vertices: Vec<[f32; 3]> = vec![v0, v1, v2, v3, v0];
generate_grid_sdf(&vertices, Topology::TriangleStrip::<u32>(None), &grid)
};
let cell_count = grid.get_total_cell_count();
for i in 0..cell_count {
assert_eq!(triangle_list_indices[i], triangle_list_none[i]);
assert_eq!(triangle_list_indices[i], triangle_strip_indices[i]);
assert_eq!(triangle_list_indices[i], triangle_strip_none[i]);
}
}
}