Skip to main content

fast_mesh_query/
lib.rs

1//! Queries on meshes stored in [`fast_mesh_store`], accelerated by [`fast_mesh_bvh`].
2//!
3//! Every query takes a mesh and a BVH built for it, works in the mesh's local
4//! `f32` coordinates, and fails with [`QueryError::StaleBvh`] when the mesh was
5//! edited after the BVH was last updated. Point, ray, region and pair queries
6//! allocate only into the output vector they are given. [`slice()`],
7//! [`fill()`], [`contour_gap`],
8//! [`triangulate`](triangulate::triangulate) and [`intersection_polylines`]
9//! also build per-call temporaries, all reserved fallibly, so that running
10//! out of memory is [`QueryError::OutOfMemory`].
11//!
12//! - Closest point: [`closest_point`] and [`closest_point_many`], with the
13//!   [`Feature`] of the triangle the nearest point lies on.
14//! - Raycasts: [`raycast`] (nearest hit), [`raycast_any`] (occlusion),
15//!   [`raycast_all`] (every hit, sorted) and [`raycast_many`] (a batch). The
16//!   ray-triangle test is watertight: a ray through a shared edge or vertex
17//!   never slips between the triangles meeting there. Ties go to the smallest
18//!   triangle id.
19//! - Region queries: [`triangles_in`] and [`vertices_in`] find the triangles
20//!   that touch, or the vertices inside, a [`Region`] (a box or a sphere). The
21//!   box is an [`Aabb`], re-exported from `fast_mesh_bvh` so that callers need
22//!   not depend on that crate to build a [`Region::Box`].
23//! - Relative placement: a [`Rigid`] transform (rotation and translation, no
24//!   scale) maps one mesh's local coordinates into another's, for queries
25//!   between two meshes. Whether two triangles intersect or touch is decided
26//!   exactly, by exact orientation predicates on the transformed `f64`
27//!   coordinates.
28//! - Mesh-vs-mesh queries, with mesh B placed in mesh A's space by a
29//!   [`Rigid`]: [`intersects`], [`mesh_distance`] (a [`MeshDistance`] with
30//!   both points in A's space), [`triangles_near`] (the triangles of each
31//!   mesh within a margin of the other), [`intersecting_pairs`] and
32//!   [`intersection_polylines`] (the intersecting pairs clipped and chained
33//!   into [`Polyline`]s). Results are in A's space; passing the same mesh
34//!   twice means two coincident copies.
35//! - Self-intersection: [`self_intersecting_pairs`] finds the triangle pairs
36//!   of one mesh that intersect and share no vertex id, by the same exact
37//!   test.
38//! - Cutting planes: a [`Plane`] (origin and unit normal, in `f64` mesh-local
39//!   coordinates) with an orthonormal 2D frame `(u, v)`. A point is on its
40//!   `+` side when its signed distance is zero or more.
41//! - Sections: [`slice()`] cuts a mesh with a [`Plane`] into a [`Slice`] of
42//!   [`Contour`]s, chained by crossed edge, with flags for open, unoriented
43//!   and non-manifold pieces.
44//! - Polygon fill: [`triangulate::triangulate`] splits a 2D polygon with
45//!   holes into counter-clockwise triangles by ear clipping.
46//! - Section measurements on `(u, v)` polylines: [`contour_length`],
47//!   [`signed_area`], [`fill`] (nesting into outers and holes, then a
48//!   [`Fill`] of triangles) and [`contour_gap`] (the exact in-plane
49//!   [`Gap2`] between two sets of polylines, found through a grid).
50
51mod closest;
52mod error;
53mod pair;
54mod pairs;
55mod plane;
56mod polylines;
57mod ray;
58mod region;
59mod rigid;
60mod section;
61mod selfpairs;
62mod slice;
63#[cfg(test)]
64mod test_util;
65mod tri;
66pub mod triangulate;
67mod tritri;
68
69use fast_mesh_bvh::Bvh;
70use fast_mesh_store::Mesh;
71
72pub use closest::*;
73pub use error::{QueryError, Result};
74/// The axis-aligned box type of [`Region::Box`], re-exported from `fast_mesh_bvh`.
75pub use fast_mesh_bvh::Aabb;
76pub use pairs::*;
77pub use plane::Plane;
78pub use polylines::{PairSegment, Polyline, intersecting_segments, intersection_polylines};
79pub use ray::*;
80pub use region::*;
81pub use rigid::Rigid;
82pub use section::{Fill, Gap2, contour_gap, contour_length, fill, signed_area};
83pub use selfpairs::self_intersecting_pairs;
84pub use selfpairs::self_intersects;
85pub use slice::{Contour, Slice, slice};
86pub use tri::{Cull, Feature};
87
88/// Checks that `bvh` was built for `mesh` and is up to date with it.
89pub(crate) fn check(mesh: &Mesh, bvh: &Bvh) -> Result<()> {
90    if bvh.mesh_uid() != mesh.uid() {
91        Err(QueryError::MeshMismatch)
92    } else if !bvh.is_current(mesh) {
93        Err(QueryError::StaleBvh)
94    } else {
95        Ok(())
96    }
97}
98
99#[cfg(any(test, feature = "fault-injection"))]
100thread_local! {
101    /// Test-only fault injection: number of fallible reservations that still succeed.
102    static ALLOCATIONS_LEFT: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
103}
104
105/// Makes the `n`-th next fallible reservation on this thread (0-based) and every one
106/// after it fail with `OutOfMemory`. `None` turns failures off. While armed,
107/// parallel work runs on this thread so the hook sees every reservation.
108///
109/// Test-only: compiled for this crate's own tests, and, with the
110/// `fault-injection` feature, as a public helper for dependents' tests.
111#[cfg(any(test, feature = "fault-injection"))]
112pub fn fail_allocations_after(n: Option<usize>) {
113    // Only on arming or disarming: the count must see one `true` per arm.
114    let was = ALLOCATIONS_LEFT.with(|c| c.replace(n)).is_some();
115    if was != n.is_some() {
116        fast_mesh_store::par::set_force_sequential(n.is_some());
117    }
118}
119
120fn allocation_allowed() -> Result<()> {
121    #[cfg(any(test, feature = "fault-injection"))]
122    {
123        if let Some(n) = ALLOCATIONS_LEFT.with(|c| c.get()) {
124            if n == 0 {
125                return Err(QueryError::OutOfMemory);
126            }
127            ALLOCATIONS_LEFT.with(|c| c.set(Some(n - 1)));
128        }
129    }
130    Ok(())
131}
132
133/// Appends to a caller's vector; on failure it restores the vector's original length.
134pub(crate) struct Appender<'a, T> {
135    out: &'a mut Vec<T>,
136    start: usize,
137}
138
139impl<'a, T> Appender<'a, T> {
140    pub(crate) fn new(out: &'a mut Vec<T>) -> Self {
141        let start = out.len();
142        Appender { out, start }
143    }
144
145    /// Pushes with a fallible reservation. On failure everything appended so far is
146    /// dropped and `OutOfMemory` is returned.
147    pub(crate) fn push(&mut self, v: T) -> Result<()> {
148        let reserved = allocation_allowed()
149            .and_then(|()| self.out.try_reserve(1).map_err(|_| QueryError::OutOfMemory));
150        if let Err(e) = reserved {
151            self.out.truncate(self.start);
152            return Err(e);
153        }
154        self.out.push(v);
155        Ok(())
156    }
157
158    /// Appends all of `items` with one fallible reservation, as [`Self::push`]
159    /// does for one.
160    pub(crate) fn extend(&mut self, items: &[T]) -> Result<()>
161    where
162        T: Copy,
163    {
164        let reserved = allocation_allowed().and_then(|()| {
165            self.out
166                .try_reserve(items.len())
167                .map_err(|_| QueryError::OutOfMemory)
168        });
169        if let Err(e) = reserved {
170            self.out.truncate(self.start);
171            return Err(e);
172        }
173        self.out.extend_from_slice(items);
174        Ok(())
175    }
176
177    /// The elements appended through this appender.
178    pub(crate) fn appended(&mut self) -> &mut [T] {
179        &mut self.out[self.start..]
180    }
181
182    /// Drops all but the first `n` appended elements.
183    pub(crate) fn truncate_appended(&mut self, n: usize) {
184        self.out.truncate(self.start + n);
185    }
186
187    /// How many elements were appended.
188    pub(crate) fn count(&self) -> usize {
189        self.out.len() - self.start
190    }
191
192    /// Sorts the appended elements and removes duplicates among them, in
193    /// place; elements from before stay as they are. Returns how many remain.
194    pub(crate) fn sort_dedup_appended(&mut self) -> usize
195    where
196        T: Ord + Copy,
197    {
198        let found = self.appended();
199        found.sort_unstable();
200        // Compact the sorted run in place, keeping the first of each value.
201        let mut kept = 0;
202        for i in 0..found.len() {
203            if kept == 0 || found[i] != found[kept - 1] {
204                found[kept] = found[i];
205                kept += 1;
206            }
207        }
208        self.truncate_appended(kept);
209        kept
210    }
211}
212
213#[cfg(test)]
214mod tests {
215    use super::*;
216    use fast_mesh_bvh::{BuildOptions, Bvh};
217    use fast_mesh_store::Mesh;
218
219    fn mesh() -> Mesh {
220        Mesh::from_buffers(&[0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0], &[0, 1, 2]).unwrap()
221    }
222
223    #[test]
224    fn check_reports_mismatch_then_staleness() {
225        let mut a = mesh();
226        let b = mesh();
227        let mut bvh = Bvh::build(&a, &BuildOptions::default()).unwrap();
228        assert_eq!(check(&b, &bvh), Err(QueryError::MeshMismatch));
229        assert_eq!(check(&a, &bvh), Ok(()));
230        let mut tx = a.begin_edit();
231        tx.set_positions(&[0], &[[0.0, 0.0, 1.0]]).unwrap();
232        let p = tx.commit().unwrap();
233        assert_eq!(check(&a, &bvh), Err(QueryError::StaleBvh));
234        bvh.update(&mut a, p.changes()).unwrap();
235        assert_eq!(check(&a, &bvh), Ok(()));
236    }
237
238    #[test]
239    fn appender_restores_length_on_oom() {
240        let mut out = vec![7u32];
241        {
242            let mut ap = Appender::new(&mut out);
243            fail_allocations_after(Some(1));
244            assert_eq!(ap.push(1), Ok(()));
245            assert_eq!(ap.count(), 1);
246            assert_eq!(ap.appended(), &[1]);
247            assert_eq!(ap.push(2), Err(QueryError::OutOfMemory));
248            fail_allocations_after(None);
249        }
250        assert_eq!(out, vec![7]);
251    }
252}