brep: an intersection curve's point is a function of t alone

Intersection::point started its Newton solve from the last point found (a Cell holding both
surfaces' parameters) when that landed near the leader, as truck took a hinted point. The
answer then depended on what was evaluated before, so CurveEval was not Sync, and the
clearance check, which shares a body's DistanceShape (its edges' CurveEvals) across worker
threads, did not compile; with threads it would also have made the result depend on their
interleaving. Each point is now solved from the leader's own point, the path the hint fell
back to, and the unused trace tolerance goes with the hint.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017itGh5fpMEousnZ91cYcVt
This commit is contained in:
jyviko
2026-10-22 07:48:53 -04:00
parent 4ca7793a3c
commit 8d91c19f17
+7 -21
View File
@@ -14,7 +14,6 @@ use crate::Vec3;
use crate::bspline::MAX_SPLINE_DEGREE;
use crate::tess::uv::{self, Domain};
use crate::tess::{EvalCurve, EvalSurface, MAX_EVAL_KNOTS, MAX_EVAL_POINTS};
use std::cell::Cell;
use std::f64::consts::{FRAC_PI_2, TAU};
/// Newton steps of one intersection point.
@@ -286,9 +285,6 @@ pub enum CurveEval {
pub struct Intersection {
surfaces: [SurfaceEval; 2],
leader: CurveEval,
tolerance: f64,
/// Both surfaces' parameters of the last point found: the next point starts from them.
hint: Cell<Option<[[f64; 2]; 2]>>,
}
impl CurveEval {
@@ -300,7 +296,7 @@ impl CurveEval {
Curve::Polyline { points } => CurveEval::Polyline(points.iter().copied().map(v3).collect()),
Curve::Spline { knots, points } => spline(knots, points.iter().map(|p| [p[0], p[1], p[2], 1.0]).collect())?,
Curve::RationalSpline { knots, homogeneous } => spline(knots, homogeneous.clone())?,
Curve::SurfaceIntersection { surfaces, leader, tolerance } => {
Curve::SurfaceIntersection { surfaces, leader, .. } => {
let leader = CurveEval::new(leader)?;
if !matches!(leader, CurveEval::Polyline(_) | CurveEval::Spline(_)) {
return Err(Error::Invalid("intersection leader".into()));
@@ -308,8 +304,6 @@ impl CurveEval {
CurveEval::Intersection(Box::new(Intersection {
surfaces: [SurfaceEval::new(&surfaces[0])?, SurfaceEval::new(&surfaces[1])?],
leader,
tolerance: *tolerance,
hint: Cell::new(None),
}))
}
})
@@ -380,29 +374,21 @@ fn spline(knots: &[f64], ctrl: Vec<[f64; 4]>) -> Result<CurveEval, Error> {
}
impl Intersection {
/// The point in the leader's normal plane at `t`: from the last point's parameters when that
/// lands within the trace tolerance of the leader (as truck takes a hinted point), else from
/// the leader's point alone.
/// The point in the leader's normal plane at `t`, found from the leader's point: a function
/// of `t` alone, whatever was evaluated before (and so the same on every thread).
fn point(&self, t: f64) -> Option<Vec3> {
let (lead, along) = self.leader.leader(t)?;
if !(along.len2() > 0.0) {
return None;
}
let near = self.tolerance.max(1e-6);
let hinted = self.hint.get().and_then(|h| self.solve(lead, along, Some(h))).filter(|(x, _)| x.dist(lead) <= near);
let (x, uv) = match hinted {
Some(found) => found,
None => self.solve(lead, along, None)?,
};
self.hint.set(Some(uv));
Some(x)
self.solve(lead, along)
}
/// Newton on the planes: each surface's tangent plane at the foot of the current point, and
/// the leader's normal plane, until the point stops moving.
fn solve(&self, lead: Vec3, along: Vec3, hint: Option<[[f64; 2]; 2]>) -> Option<(Vec3, [[f64; 2]; 2])> {
fn solve(&self, lead: Vec3, along: Vec3) -> Option<Vec3> {
let mut x = lead;
let mut uv = hint;
let mut uv: Option<[[f64; 2]; 2]> = None;
for _ in 0..MAX_NEWTON {
let mut planes = [(Vec3::ZERO, 0.0); 2];
let mut feet = [[0.0; 2]; 2];
@@ -419,7 +405,7 @@ impl Intersection {
let step = y.dist(x);
x = y;
if step <= 1e-12 * (1.0 + x.len()) {
return Some((x, feet));
return Some(x);
}
}
None