mesh_to_sdf/
lib.rs

1//! ⚠️ This crate is still in its early stages. Expect the API to change.
2//!
3//! ---
4//!
5//! This crate provides two entry points:
6//!
7//! - [`generate_sdf`]: computes the signed distance field for the mesh defined by `vertices` and `indices` at the points `query_points`.
8//! - [`generate_grid_sdf`]: computes the signed distance field for the mesh defined by `vertices` and `indices` on a [Grid].
9//!
10//! ```
11//! use mesh_to_sdf::{generate_sdf, generate_grid_sdf, SignMethod, AccelerationMethod, Topology, Grid};
12//! // vertices are [f32; 3], but can be cgmath::Vector3<f32>, glam::Vec3, etc.
13//! let vertices: Vec<[f32; 3]> = vec![[0.5, 1.5, 0.5], [1., 2., 3.], [1., 3., 7.]];
14//! let indices: Vec<u32> = vec![0, 1, 2];
15//!
16//! // query points must be of the same type as vertices
17//! let query_points: Vec<[f32; 3]> = vec![[0.5, 0.5, 0.5]];
18//!
19//! // Query points are expected to be in the same space as the mesh.
20//! let sdf: Vec<f32> = generate_sdf(
21//!     &vertices,
22//!     Topology::TriangleList(Some(&indices)), // TriangleList as opposed to TriangleStrip
23//!     &query_points,
24//!     AccelerationMethod::RtreeBvh, // Use an r-tree and a bvh to accelerate queries.
25//! );
26//!
27//! for point in query_points.iter().zip(sdf.iter()) {
28//!     // distance is positive outside the mesh and negative inside.
29//!     println!("Distance to {:?}: {}", point.0, point.1);
30//! }
31//! # assert_eq!(sdf, vec![1.0]);
32//!
33//! // if you can, use generate_grid_sdf instead of generate_sdf as it's optimized and much faster.
34//! let bounding_box_min = [0., 0., 0.];
35//! let bounding_box_max = [10., 10., 10.];
36//! let cell_count = [10, 10, 10];
37//!
38//! let grid = Grid::from_bounding_box(&bounding_box_min, &bounding_box_max, cell_count);
39//!
40//! let sdf: Vec<f32> = generate_grid_sdf(
41//!     &vertices,
42//!     Topology::TriangleList(Some(&indices)),
43//!     &grid,
44//!     SignMethod::Raycast, // How the sign is computed.
45//!                          // Raycast is robust but requires the mesh to be watertight.
46//!                          // and is more expensive.
47//!                          // Normal might leak negative distances outside the mesh
48//! );                       // but works for all meshes, even surfaces.
49//!
50//! for x in 0..cell_count[0] {
51//!     for y in 0..cell_count[1] {
52//!         for z in 0..cell_count[2] {
53//!             let index = grid.get_cell_idx(&[x, y, z]);
54//!             log::info!("Distance to cell [{}, {}, {}]: {}", x, y, z, sdf[index as usize]);
55//!         }
56//!     }
57//! }
58//! # assert_eq!(sdf[0], 1.0);
59//! ```
60//!
61//! ---
62//!
63//! #### Mesh Topology
64//!
65//! Indices can be of any type that implements `Into<u32>`, e.g. `u16` and `u32`. Topology can be list or strip.
66//! If the indices are not provided, they are supposed to be `0..vertices.len()`.
67//!
68//! For vertices, this library aims to be as generic as possible by providing a trait `Point` that can be implemented for any type.
69//! Implementations for most common math libraries are gated behind feature flags. By default, `[f32; 3]` and `nalgebra::[Point3, Vector3]` are provided.
70//! If you do not find your favorite library, feel free to implement the trait for it and submit a PR or open an issue.
71//!
72//! ---
73//!
74//! #### Computing sign
75//!
76//! This crate provides two methods to compute the sign of the distance:
77//! - [`SignMethod::Raycast`] (default): a robust method to compute the sign of the distance. It counts the number of intersections between a ray starting from the query point and the triangles of the mesh.
78//!     It only works for watertight meshes, but guarantees the sign is correct.
79//! - [`SignMethod::Normal`]: uses the normals of the triangles to estimate the sign by doing a dot product with the direction of the query point.
80//!     It works for non-watertight meshes but might leak negative distances outside the mesh.
81//!
82//! Using `Raycast` is slower than `Normal` but gives better results. Performances depends on the triangle count and method used.
83//! On big dataset, `Raycast` is 5-10% slower for grid generation and rtree based methods. On smaller dataset, the difference can be worse
84//! depending on whether the query is triangle intensive or query point intensive.
85//! For bvh the difference is negligible between the two methods.
86//!
87//! ---
88//!
89//! #### Acceleration structures
90//!
91//! For generic queries, you can use acceleration structures to speed up the computation.
92//! - [`AccelerationMethod::None`]: no acceleration structure. This is the slowest method but requires no extra memory. Scales really poorly.
93//! - [`AccelerationMethod::Bvh`]: Bounding Volume Hierarchy. Accepts a `SignMethod`.
94//! - [`AccelerationMethod::Rtree`]: R-tree. Uses `SignMethod::Normal`. The fastest method assuming you have more than a couple thousands of queries.
95//! - [`AccelerationMethod::RtreeBvh`] (default): Uses R-tree for nearest neighbor search and Bvh for `SignMethod::Raycast`. 5-10% slower than `Rtree` on big datasets.
96//!
97//! If your mesh is watertight and you have more than a thousand queries/triangles, you should use `AccelerationMethod::RtreeBvh` for best performances.
98//! If it's not watertight, you can use `AccelerationMethod::Rtree` instead.
99//!
100//! `Rtree` methods are 3-4x faster than `Bvh` methods for big enough data. On small meshes, the difference is negligible.
101//! `AccelerationMethod::None` scales really poorly and should be avoided unless for small datasets or if you're really tight on memory.
102//!
103//! ---
104//!
105//! #### Using your favorite library
106//!
107//! To use your favorite math library with `mesh_to_sdf`, you need to add it to `mesh_to_sdf` dependency. For example, to use `glam`:
108//! ```toml
109//! [dependencies]
110//! mesh_to_sdf = { version = "0.2.1", features = ["glam"] }
111//! ```
112//!
113//! Currently, the following libraries are supported:
114//! - [cgmath] ([`cgmath::Vector3<f32>`])
115//! - [glam] ([`glam::Vec3`])
116//! - [mint] ([`mint::Vector3<f32>`] and [`mint::Point3<f32>`])
117//! - [nalgebra] ([`nalgebra::Vector3<f32>`] and [`nalgebra::Point3<f32>`])
118//! - `[f32; 3]`
119//!
120//! [nalgebra] is always available as it's used internally in the bvh tree.
121//!
122//! ---
123//!
124//! #### Serialization
125//!
126//! If you want to serialize and deserialize signed distance fields, you need to enable the `serde` feature.
127//! This features also provides helpers to save and load signed distance fields to and from files via `save_to_file` and `read_from_file`.
128use std::boxed::Box;
129
130use itertools::Itertools;
131
132use generate::generic::{
133    bvh::generate_sdf_bvh, default::generate_sdf_default, rtree::generate_sdf_rtree,
134    rtree_bvh::generate_sdf_rtree_bvh,
135};
136
137mod bvh_ext;
138mod generate;
139mod geo;
140mod grid;
141mod point;
142
143#[cfg(feature = "serde")]
144pub mod serde;
145
146pub use generate::grid::generate_grid_sdf;
147pub use grid::{Grid, SnapResult};
148pub use point::Point;
149
150/// Mesh Topology: how indices are stored.
151#[derive(Copy, Clone)]
152pub enum Topology<'a, I>
153where
154    // I should be a u32 or u16
155    I: Into<u32>,
156{
157    /// Vertex data is a list of triangles. Each set of 3 vertices composes a new triangle.
158    ///
159    /// Vertices `0 1 2 3 4 5` create two triangles `0 1 2` and `3 4 5`
160    /// If no indices are provided, they are supposed to be `0..vertices.len()`
161    TriangleList(Option<&'a [I]>),
162    /// Vertex data is a triangle strip. Each set of three adjacent vertices form a triangle.
163    ///
164    /// Vertices `0 1 2 3 4 5` create four triangles `0 1 2`, `1 2 3`, `2 3 4`, and `3 4 5`
165    /// If no indices are provided, they are supposed to be `0..vertices.len()`
166    TriangleStrip(Option<&'a [I]>),
167}
168
169impl<'a, I> Topology<'a, I>
170where
171    I: Into<u32>,
172{
173    /// Compute the triangles list
174    /// Returns an iterator of tuples of 3 indices representing a triangle.
175    fn get_triangles<V>(
176        vertices: &'a [V],
177        indices: Self,
178    ) -> Box<dyn Iterator<Item = (usize, usize, usize)> + Send + 'a>
179    where
180        V: Point,
181        I: Copy + Into<u32> + Sync + Send,
182    {
183        match indices {
184            Topology::TriangleList(Some(indices)) => {
185                Box::new(indices.iter().map(|x| (*x).into() as usize).tuples())
186            }
187            Topology::TriangleList(None) => Box::new((0..vertices.len()).tuples()),
188            Topology::TriangleStrip(Some(indices)) => {
189                Box::new(indices.iter().map(|x| (*x).into() as usize).tuple_windows())
190            }
191            Topology::TriangleStrip(None) => Box::new((0..vertices.len()).tuple_windows()),
192        }
193    }
194}
195
196/// Method to compute the sign of the distance.
197///
198/// Raycast is the default method. It is robust but requires the mesh to be watertight.
199///
200/// Normal is not robust and might leak negative distances outside the mesh.
201///
202/// For grid generation, Raycast is ~1% slower.
203/// For query points, Raycast is ~10% slower.
204#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
205pub enum SignMethod {
206    /// A robust method to compute the sign of the distance.
207    /// It counts the number of intersection between a ray starting from the query point and the mesh.
208    /// If the number of intersections is odd, the point is inside the mesh.
209    /// This requires the mesh to be watertight.
210    #[default]
211    Raycast,
212    /// A faster but not robust method to compute the sign of the distance.
213    /// It uses the normals of the triangles to estimate the sign.
214    /// It might leak negative distances outside the mesh.
215    Normal,
216}
217
218/// Acceleration structure to speed up the computation.
219///
220/// `RtreeBvh` is the fastest method but also the most memory intensive.
221/// If your mesh is not watertight, you can use `Rtree` instead.
222/// `Bvh` is about 4x slower than `Rtree`.
223/// `None` is the slowest method and scales really poorly but requires no extra memory.
224#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
225pub enum AccelerationMethod {
226    /// No acceleration structure.
227    None(SignMethod),
228    /// Bounding Volume Hierarchy.
229    /// Recommended unless you have very few queries and triangles (less than a couple thousands)
230    /// or if you're really tight on memory as it requires more memory than the default method.
231    Bvh(SignMethod),
232    /// R-tree
233    /// Only compatible with `SignMethod::Normal`
234    Rtree,
235    /// R-tree and Bvh
236    /// Uses R-tree for nearest neighbor search and Bvh for ray intersection.
237    #[default]
238    RtreeBvh,
239}
240
241/// Compare two signed distances, taking into account floating point errors and signs.
242fn compare_distances(a: f32, b: f32) -> core::cmp::Ordering {
243    // for a point to be inside, it has to be inside all normals of nearest triangles.
244    // if one distance is positive, then the point is outside.
245    // this check is sensible to floating point errors though
246    // so it's not perfect, but it reduces the number of false positives considerably.
247    // TODO: expose ulps and epsilon?
248    if float_cmp::approx_eq!(f32, a.abs(), b.abs(), ulps = 2, epsilon = 1e-6) {
249        // they are equals: return the one with the smallest distance, privileging positive distances.
250        match (a.is_sign_negative(), b.is_sign_negative()) {
251            (true, false) => core::cmp::Ordering::Greater,
252            (false, true) => core::cmp::Ordering::Less,
253            _ => a.abs().partial_cmp(&b.abs()).unwrap(),
254        }
255    } else {
256        // return the closest to 0.
257        a.abs().partial_cmp(&b.abs()).expect("NaN distance")
258    }
259}
260
261/// Generate a signed distance field from a mesh.
262/// Query points are expected to be in the same space as the mesh.
263///
264/// Returns a vector of signed distances.
265/// Queries outside the mesh will have a positive distance, and queries inside the mesh will have a negative distance.
266/// ```
267/// use mesh_to_sdf::{generate_sdf, SignMethod, Topology, AccelerationMethod};
268///
269/// let vertices: Vec<[f32; 3]> = vec![[0., 1., 0.], [1., 2., 3.], [1., 3., 4.]];
270/// let indices: Vec<u32> = vec![0, 1, 2];
271///
272/// let query_points: Vec<[f32; 3]> = vec![[0., 0., 0.]];
273///
274/// // Query points are expected to be in the same space as the mesh.
275/// let sdf: Vec<f32> = generate_sdf(
276///     &vertices,
277///     Topology::TriangleList(Some(&indices)),
278///     &query_points,
279///     AccelerationMethod::RtreeBvh,   // Use an rtree and a bvh to accelerate queries.
280///                                     // Recommended unless you have very few queries and triangles (less than a couple thousands)
281///                                     // or if you're really tight on memory as it requires more memory than other methods.
282///                                     // This uses raycasting to compute sign. This is robust but requires the mesh to be watertight.
283/// );                                  // If your mesh isn't watertight, you can use AccelerationMethod::Rtree instead.
284///
285/// for point in query_points.iter().zip(sdf.iter()) {
286///     println!("Distance to {:?}: {}", point.0, point.1);
287/// }
288///
289/// # assert_eq!(sdf, vec![1.0]);
290/// ```
291pub fn generate_sdf<V, I>(
292    vertices: &[V],
293    indices: Topology<I>,
294    query_points: &[V],
295    acceleration_method: AccelerationMethod,
296) -> Vec<f32>
297where
298    V: Point + 'static,
299    I: Copy + Into<u32> + Sync + Send,
300{
301    match acceleration_method {
302        AccelerationMethod::None(sign_method) => {
303            generate_sdf_default(vertices, indices, query_points, sign_method)
304        }
305        AccelerationMethod::Bvh(sign_method) => {
306            generate_sdf_bvh(vertices, indices, query_points, sign_method)
307        }
308        AccelerationMethod::Rtree => generate_sdf_rtree(vertices, indices, query_points),
309        AccelerationMethod::RtreeBvh => generate_sdf_rtree_bvh(vertices, indices, query_points),
310    }
311}