Clearance report: overlaps, near pairs and nearest bodies (#1900)
Thanks @NOWN-DEV.
This commit is contained in:
1 parent
fd073620ae
commit
186555dbe9
18 files changed
+1428
-17
No files matched your search
@@ -145,6 +145,53 @@ impl Bvh {
|
||||
}
|
||||
best
|
||||
}
|
||||
|
||||
/// Calls `item(i, j)` for pairs of items, `i` from this hierarchy and `j` from `other`, in
|
||||
/// leaves whose boxes, and every pair of boxes above them, `enter` accepts. `enter` is asked
|
||||
/// again for a pair of boxes when it is taken up, so a bound that tightens while items are
|
||||
/// visited (the nearest pair so far) prunes what is still waiting. Of two children, the pair
|
||||
/// whose box centres are nearer is taken up first.
|
||||
pub fn visit_pairs(&self, other: &Bvh, mut enter: impl FnMut(&Aabb3, &Aabb3) -> bool, mut item: impl FnMut(u32, u32)) {
|
||||
if self.nodes.is_empty() || other.nodes.is_empty() {
|
||||
return;
|
||||
}
|
||||
let mut stack = vec![(0usize, 0usize)];
|
||||
while let Some((i, j)) = stack.pop() {
|
||||
let (Some(a), Some(b)) = (self.nodes.get(i), other.nodes.get(j)) else { continue };
|
||||
if !enter(&a.bounds, &b.bounds) {
|
||||
continue;
|
||||
}
|
||||
match (a.count > 0, b.count > 0) {
|
||||
(true, true) => {
|
||||
let (ia, ib) = (a.first as usize, b.first as usize);
|
||||
let left = self.order.get(ia..ia + a.count as usize).unwrap_or(&[]);
|
||||
let right = other.order.get(ib..ib + b.count as usize).unwrap_or(&[]);
|
||||
for &x in left {
|
||||
for &y in right {
|
||||
item(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Split the node that is not a leaf, or the larger one.
|
||||
(leaf_a, leaf_b) => {
|
||||
let split_a = !leaf_a && (leaf_b || a.bounds.diagonal() >= b.bounds.diagonal());
|
||||
let pairs =
|
||||
if split_a { [(a.first as usize, j), (a.first as usize + 1, j)] } else { [(i, b.first as usize), (i, b.first as usize + 1)] };
|
||||
let gap = |(x, y): (usize, usize)| match (self.nodes.get(x), other.nodes.get(y)) {
|
||||
(Some(x), Some(y)) => x.bounds.center().dist(y.bounds.center()),
|
||||
_ => f64::INFINITY,
|
||||
};
|
||||
let [p, q] = pairs;
|
||||
// The nearer pair is popped first.
|
||||
if gap(p) <= gap(q) {
|
||||
stack.extend([q, p]);
|
||||
} else {
|
||||
stack.extend([p, q]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn finite(b: &Aabb3) -> bool {
|
||||
|
||||
@@ -18,6 +18,7 @@ pub mod env;
|
||||
pub mod fill;
|
||||
pub mod fill_general;
|
||||
mod mesh;
|
||||
pub mod mesh_distance;
|
||||
mod plane;
|
||||
mod profile;
|
||||
pub mod tess;
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
//! Distances between two triangle meshes: the nearest pair of points, and every pair of
|
||||
//! triangles within a distance. A [`TriangleSet`] keeps a mesh's triangles under a bounding
|
||||
//! volume hierarchy, so a query costs about the pairs of triangles that come near each other
|
||||
//! rather than the product of the triangle counts.
|
||||
|
||||
use crate::compare::closest_on_triangle;
|
||||
use crate::{Aabb3, Bvh, Mesh, Vec3};
|
||||
|
||||
/// A mesh's triangles with their B-rep faces, indexed for distance queries.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct TriangleSet {
|
||||
tris: Vec<[Vec3; 3]>,
|
||||
face: Vec<u32>,
|
||||
bvh: Bvh,
|
||||
bounds: Aabb3,
|
||||
}
|
||||
|
||||
/// The nearest points of two triangle sets, and the triangles they lie on.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct MeshClosest {
|
||||
pub distance: f64,
|
||||
pub a: Vec3,
|
||||
pub b: Vec3,
|
||||
pub tri_a: u32,
|
||||
pub tri_b: u32,
|
||||
}
|
||||
|
||||
impl TriangleSet {
|
||||
/// The set of `mesh`'s triangles (ones with a vertex that is not finite are left out).
|
||||
pub fn new(mesh: &Mesh) -> TriangleSet {
|
||||
let mut tris = Vec::with_capacity(mesh.triangles.len());
|
||||
let mut face = Vec::with_capacity(mesh.triangles.len());
|
||||
let mut boxes = Vec::with_capacity(mesh.triangles.len());
|
||||
let mut bounds = Aabb3::EMPTY;
|
||||
for (k, t) in mesh.triangles.iter().enumerate() {
|
||||
let Some(p) = mesh.tri(t) else { continue };
|
||||
if !p.iter().all(|v| v.is_finite()) {
|
||||
continue;
|
||||
}
|
||||
let mut b = Aabb3::EMPTY;
|
||||
for v in p {
|
||||
b.add(v);
|
||||
}
|
||||
bounds = bounds.union(&b);
|
||||
boxes.push(b);
|
||||
tris.push(p);
|
||||
face.push(mesh.tri_face.get(k).copied().unwrap_or(0));
|
||||
}
|
||||
TriangleSet { bvh: Bvh::new(&boxes), tris, face, bounds }
|
||||
}
|
||||
|
||||
/// The box around every triangle.
|
||||
pub fn bounds(&self) -> Aabb3 {
|
||||
self.bounds
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.tris.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.tris.is_empty()
|
||||
}
|
||||
|
||||
/// Triangle `t`'s corners.
|
||||
pub fn triangle(&self, t: u32) -> Option<[Vec3; 3]> {
|
||||
self.tris.get(t as usize).copied()
|
||||
}
|
||||
|
||||
/// The B-rep face triangle `t` came from.
|
||||
pub fn face(&self, t: u32) -> Option<u32> {
|
||||
self.face.get(t as usize).copied()
|
||||
}
|
||||
|
||||
/// The nearest points of this set and `other`, if they come within `limit` of each other
|
||||
/// (`f64::INFINITY`: always). Triangles that cross count as distance 0, at a point where
|
||||
/// they meet.
|
||||
pub fn closest(&self, other: &TriangleSet, limit: f64) -> Option<MeshClosest> {
|
||||
let mut best: Option<MeshClosest> = None;
|
||||
let bound = std::cell::Cell::new(if limit.is_nan() { f64::INFINITY } else { limit });
|
||||
let mut found = |i: u32, j: u32| {
|
||||
let (Some(x), Some(y)) = (self.tris.get(i as usize), other.tris.get(j as usize)) else { return };
|
||||
let (d, a, b) = triangle_distance(x, y);
|
||||
if d <= bound.get() && best.is_none_or(|m| d < m.distance) {
|
||||
best = Some(MeshClosest { distance: d, a, b, tri_a: i, tri_b: j });
|
||||
bound.set(d);
|
||||
}
|
||||
};
|
||||
self.bvh.visit_pairs(&other.bvh, |x, y| box_gap(x, y) <= bound.get(), &mut found);
|
||||
best
|
||||
}
|
||||
|
||||
/// Calls `f` with every pair of triangles, one from this set and one from `other`, that come
|
||||
/// within `d` of each other, with their nearest points.
|
||||
pub fn within(&self, other: &TriangleSet, d: f64, mut f: impl FnMut(MeshClosest)) {
|
||||
if !(d >= 0.0) {
|
||||
return;
|
||||
}
|
||||
self.bvh.visit_pairs(
|
||||
&other.bvh,
|
||||
|x, y| box_gap(x, y) <= d,
|
||||
|i, j| {
|
||||
let (Some(x), Some(y)) = (self.tris.get(i as usize), other.tris.get(j as usize)) else { return };
|
||||
let (dist, a, b) = triangle_distance(x, y);
|
||||
if dist <= d {
|
||||
f(MeshClosest { distance: dist, a, b, tri_a: i, tri_b: j });
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// The distance from `p` to the nearest of the triangles `tris` (indices into this set).
|
||||
pub fn distance_to(&self, p: Vec3, tris: &[u32]) -> f64 {
|
||||
tris.iter()
|
||||
.filter_map(|&t| self.tris.get(t as usize))
|
||||
.map(|[a, b, c]| closest_on_triangle(p, *a, *b, *c).dist(p))
|
||||
.fold(f64::INFINITY, f64::min)
|
||||
}
|
||||
}
|
||||
|
||||
/// The gap between two boxes (0 when they meet).
|
||||
pub fn box_gap(a: &Aabb3, b: &Aabb3) -> f64 {
|
||||
if a.is_empty() || b.is_empty() {
|
||||
return f64::INFINITY;
|
||||
}
|
||||
let axis = |lo1: f64, hi1: f64, lo2: f64, hi2: f64| (lo2 - hi1).max(lo1 - hi2).max(0.0);
|
||||
let d = Vec3::new(axis(a.min.x, a.max.x, b.min.x, b.max.x), axis(a.min.y, a.max.y, b.min.y, b.max.y), axis(a.min.z, a.max.z, b.min.z, b.max.z));
|
||||
d.len()
|
||||
}
|
||||
|
||||
/// The nearest points of segments `p0`–`p1` and `q0`–`q1` (Ericson, Real-Time Collision
|
||||
/// Detection, 5.1.9).
|
||||
pub fn closest_on_segments(p0: Vec3, p1: Vec3, q0: Vec3, q1: Vec3) -> (Vec3, Vec3) {
|
||||
let (d1, d2, r) = (p1 - p0, q1 - q0, p0 - q0);
|
||||
let (a, e, f) = (d1.len2(), d2.len2(), d2.dot(r));
|
||||
const TINY: f64 = 1e-300;
|
||||
let (s, t) = if a <= TINY && e <= TINY {
|
||||
(0.0, 0.0)
|
||||
} else if a <= TINY {
|
||||
(0.0, (f / e).clamp(0.0, 1.0))
|
||||
} else {
|
||||
let c = d1.dot(r);
|
||||
if e <= TINY {
|
||||
((-c / a).clamp(0.0, 1.0), 0.0)
|
||||
} else {
|
||||
let b = d1.dot(d2);
|
||||
let den = a * e - b * b;
|
||||
let mut s = if den > TINY * (a * e).max(1.0) { ((b * f - c * e) / den).clamp(0.0, 1.0) } else { 0.0 };
|
||||
let mut t = (b * s + f) / e;
|
||||
if t < 0.0 {
|
||||
t = 0.0;
|
||||
s = (-c / a).clamp(0.0, 1.0);
|
||||
} else if t > 1.0 {
|
||||
t = 1.0;
|
||||
s = ((b - c) / a).clamp(0.0, 1.0);
|
||||
}
|
||||
(s, t)
|
||||
}
|
||||
};
|
||||
(p0 + d1 * s, q0 + d2 * t)
|
||||
}
|
||||
|
||||
/// Where segment `p`–`q` passes through triangle `t` (not when it lies in its plane).
|
||||
fn segment_through(p: Vec3, q: Vec3, t: &[Vec3; 3]) -> Option<Vec3> {
|
||||
let [a, b, c] = *t;
|
||||
let n = (b - a).cross(c - a);
|
||||
let (dp, dq) = (n.dot(p - a), n.dot(q - a));
|
||||
if (dp > 0.0 && dq > 0.0) || (dp < 0.0 && dq < 0.0) || dp == dq {
|
||||
return None;
|
||||
}
|
||||
let x = p + (q - p) * (dp / (dp - dq));
|
||||
let inside = [(a, b), (b, c), (c, a)].iter().all(|(u, v)| (*v - *u).cross(x - *u).dot(n) >= 0.0);
|
||||
inside.then_some(x)
|
||||
}
|
||||
|
||||
/// The distance between two triangles and their nearest points (0 where they cross).
|
||||
pub fn triangle_distance(s: &[Vec3; 3], t: &[Vec3; 3]) -> (f64, Vec3, Vec3) {
|
||||
let edges = |x: &[Vec3; 3]| [(x[0], x[1]), (x[1], x[2]), (x[2], x[0])];
|
||||
for (p, q) in edges(s) {
|
||||
if let Some(x) = segment_through(p, q, t) {
|
||||
return (0.0, x, x);
|
||||
}
|
||||
}
|
||||
for (p, q) in edges(t) {
|
||||
if let Some(x) = segment_through(p, q, s) {
|
||||
return (0.0, x, x);
|
||||
}
|
||||
}
|
||||
let mut best = (f64::INFINITY, s[0], t[0]);
|
||||
let mut keep = |a: Vec3, b: Vec3| {
|
||||
let d = a.dist(b);
|
||||
if d < best.0 {
|
||||
best = (d, a, b);
|
||||
}
|
||||
};
|
||||
for p in s {
|
||||
keep(*p, closest_on_triangle(*p, t[0], t[1], t[2]));
|
||||
}
|
||||
for q in t {
|
||||
keep(closest_on_triangle(*q, s[0], s[1], s[2]), *q);
|
||||
}
|
||||
for (p0, p1) in edges(s) {
|
||||
for (q0, q1) in edges(t) {
|
||||
let (a, b) = closest_on_segments(p0, p1, q0, q1);
|
||||
keep(a, b);
|
||||
}
|
||||
}
|
||||
best
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn tri(a: [f64; 3], b: [f64; 3], c: [f64; 3]) -> [Vec3; 3] {
|
||||
[Vec3::from(a), Vec3::from(b), Vec3::from(c)]
|
||||
}
|
||||
|
||||
/// Distances between triangles worked out by hand: face to vertex, edge to edge, crossing.
|
||||
#[test]
|
||||
fn triangle_distances_by_hand() {
|
||||
let floor = tri([0.0, 0.0, 0.0], [10.0, 0.0, 0.0], [0.0, 10.0, 0.0]);
|
||||
// A vertex 3 above the floor's inside.
|
||||
let (d, a, b) = triangle_distance(&floor, &tri([1.0, 1.0, 3.0], [1.0, 1.0, 9.0], [2.0, 1.0, 9.0]));
|
||||
assert!((d - 3.0).abs() < 1e-12 && a.dist(Vec3::new(1.0, 1.0, 0.0)) < 1e-12 && b.dist(Vec3::new(1.0, 1.0, 3.0)) < 1e-12);
|
||||
// Two skew edges: the floor's x axis edge and a line along y at x = 5, z = 2, off the floor at y < 0.
|
||||
let (d, ..) = triangle_distance(&floor, &tri([5.0, -1.0, 2.0], [5.0, -9.0, 2.0], [5.0, -5.0, 9.0]));
|
||||
assert!((d - 5f64.sqrt()).abs() < 1e-12, "{d}");
|
||||
// A triangle through the floor.
|
||||
let (d, ..) = triangle_distance(&floor, &tri([2.0, 2.0, -1.0], [2.0, 2.0, 1.0], [3.0, 2.0, 1.0]));
|
||||
assert_eq!(d, 0.0);
|
||||
// Parallel segments overlapping along their length.
|
||||
let (p, q) = closest_on_segments(Vec3::ZERO, Vec3::X * 4.0, Vec3::new(2.0, 1.0, 0.0), Vec3::new(6.0, 1.0, 0.0));
|
||||
assert!((p.dist(q) - 1.0).abs() < 1e-12);
|
||||
}
|
||||
|
||||
fn cube(at: Vec3, size: f64) -> Mesh {
|
||||
let mut m = Mesh::default();
|
||||
let c = |x: f64, y: f64, z: f64| at + Vec3::new(x, y, z) * size;
|
||||
m.positions = vec![c(0., 0., 0.), c(1., 0., 0.), c(1., 1., 0.), c(0., 1., 0.), c(0., 0., 1.), c(1., 0., 1.), c(1., 1., 1.), c(0., 1., 1.)];
|
||||
m.triangles =
|
||||
vec![[0, 2, 1], [0, 3, 2], [4, 5, 6], [4, 6, 7], [0, 1, 5], [0, 5, 4], [1, 2, 6], [1, 6, 5], [2, 3, 7], [2, 7, 6], [3, 0, 4], [3, 4, 7]];
|
||||
m.tri_face = vec![0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5];
|
||||
m
|
||||
}
|
||||
|
||||
/// Two cubes apart along a diagonal: the nearest points are their facing corners.
|
||||
#[test]
|
||||
fn nearest_points_of_two_meshes() {
|
||||
let a = TriangleSet::new(&cube(Vec3::ZERO, 10.0));
|
||||
let b = TriangleSet::new(&cube(Vec3::new(13.0, 14.0, 0.0), 10.0));
|
||||
let c = a.closest(&b, f64::INFINITY);
|
||||
assert!(c.is_some_and(|c| (c.distance - 5.0).abs() < 1e-12), "{c:?}");
|
||||
assert!(a.closest(&b, 4.0).is_none(), "nothing within 4");
|
||||
let mut n = 0;
|
||||
a.within(&b, 5.5, |_| n += 1);
|
||||
assert!(n > 0);
|
||||
let far = TriangleSet::new(&cube(Vec3::new(5.0, 5.0, 5.0), 10.0));
|
||||
assert_eq!(a.closest(&far, f64::INFINITY).map(|c| c.distance), Some(0.0), "overlapping cubes cross");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user