fix: axis or tangent plane on a drill tip no longer fails with "no face at NaN" (#567) (#655)

The nearest point on a triangle with a zero-length edge (a cone's tip) is no longer NaN, so an axis or tangent plane on a drill tip works again (#567).
This commit is contained in:
Lasse Lian authored and GitHub committed 2026-10-13 10:17:49 -04:00
1 parent 0e500cacd3
commit cb071bb93b
3 files changed
+75 -7

No files matched your search

+3 -2
View File
@@ -104,7 +104,8 @@ fn nearest_on_face(m: &Mesh, face: usize, p: Vec3) -> Option<(f64, Vec3)> {
let Some([a, b, c]) = m.tri(t) else { continue };
let q = closest(p, a, b, c);
let d = q.dist(p);
if best.is_none_or(|x| d < x.0) {
// A non-finite candidate must never become the best: nothing compares less than NaN.
if d.is_finite() && best.is_none_or(|x| d < x.0) {
best = Some((d, q));
}
}
@@ -317,7 +318,7 @@ fn legacy_locate<'a>(bodies: impl Iterator<Item = (usize, &'a ModelBody)>, name:
let Some([a, bb, c]) = m.tri(t) else { continue };
let q = closest(p, a, bb, c);
let d = q.dist(p) - if exact { 1e-9 } else { 0.0 };
if best.is_none_or(|x| d < x.0) {
if d.is_finite() && best.is_none_or(|x| d < x.0) {
best = Some((d, bi, *f as usize, q, !exact, tol));
}
}
+19 -3
View File
@@ -114,6 +114,8 @@ fn samples(m: &Mesh, nf: usize) -> Vec<Vec<Vec3>> {
.collect()
}
/// The point of triangle `abc` nearest `p`. A zero-length edge (a cone's tip repeats its apex
/// vertex) has no interior to project onto, so its branch is skipped rather than dividing 0 by 0.
pub(crate) fn closest(p: Vec3, a: Vec3, b: Vec3, c: Vec3) -> Vec3 {
let (ab, ac, ap) = (b - a, c - a, p - a);
let (d1, d2) = (ab.dot(ap), ac.dot(ap));
@@ -126,7 +128,7 @@ pub(crate) fn closest(p: Vec3, a: Vec3, b: Vec3, c: Vec3) -> Vec3 {
return b;
}
let vc = d1 * d4 - d3 * d2;
if vc <= 0.0 && d1 >= 0.0 && d3 <= 0.0 {
if vc <= 0.0 && d1 >= 0.0 && d3 <= 0.0 && d1 - d3 > 0.0 {
return a + ab * (d1 / (d1 - d3));
}
let cp = p - c;
@@ -135,11 +137,11 @@ pub(crate) fn closest(p: Vec3, a: Vec3, b: Vec3, c: Vec3) -> Vec3 {
return c;
}
let vb = d5 * d2 - d1 * d6;
if vb <= 0.0 && d2 >= 0.0 && d6 <= 0.0 {
if vb <= 0.0 && d2 >= 0.0 && d6 <= 0.0 && d2 - d6 > 0.0 {
return a + ac * (d2 / (d2 - d6));
}
let va = d3 * d6 - d5 * d4;
if va <= 0.0 && (d4 - d3) >= 0.0 && (d5 - d6) >= 0.0 {
if va <= 0.0 && (d4 - d3) >= 0.0 && (d5 - d6) >= 0.0 && (d4 - d3) + (d5 - d6) > 0.0 {
return b + (c - b) * ((d4 - d3) / ((d4 - d3) + (d5 - d6)));
}
let den = va + vb + vc;
@@ -753,6 +755,20 @@ pub fn face_on_body(body: &ModelBody, name: &str, p: Vec3) -> Option<(usize, Vec
mod tests {
use super::*;
/// A cone's tip repeats its apex vertex, so a triangle can have a zero-length edge. The nearest
/// point on it must still be a real point (it used to be NaN, which broke picking the tip).
#[test]
fn closest_point_on_a_degenerate_triangle_is_finite() {
let (a, c) = (Vec3::new(0., 0., 0.), Vec3::new(2., 0., 0.));
for (b, c) in [(a, c), (c, c), (a, a)] {
for p in [Vec3::new(1., 1., 0.), Vec3::new(-1., 0.5, 0.), Vec3::new(3., -2., 1.), a] {
let q = closest(p, a, b, c);
assert!(q.x.is_finite() && q.y.is_finite() && q.z.is_finite(), "{a:?} {b:?} {c:?} {p:?} -> {q:?}");
}
}
assert_eq!(closest(Vec3::new(1., 1., 0.), a, a, c), Vec3::new(1., 0., 0.));
}
#[test]
fn merged_face_votes_collectively_for_pieces_of_one_source() {
let body = ModelBody::new("Input".into(), solidcraft_kernel::box_solid(Vec3::ZERO, Vec3::new(4., 1., 1.)).unwrap(), 1);