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}