use crate::MathError;
use crate::nurbs::curve::NurbsCurve;
use crate::nurbs::decompose::{curve_to_bezier_segments, surface_to_bezier_patches};
use crate::nurbs::surface::NurbsSurface;
use crate::vec::{Point3, Vec3};
use super::MAX_NEWTON_ITER;
#[derive(Debug, Clone, Copy)]
pub struct CurveSurfaceHit {
pub point: Point3,
pub t: f64,
pub uv: (f64, f64),
}
#[allow(clippy::too_many_lines)]
pub fn intersect_curve_surface(
curve: &NurbsCurve,
surface: &NurbsSurface,
tolerance: f64,
) -> Result<Vec<CurveSurfaceHit>, MathError> {
let segments = curve_to_bezier_segments(curve)?;
let patches = surface_to_bezier_patches(surface)?;
let mut hits = Vec::new();
let dedup_dist = tolerance.max(1e-6) * 10.0;
let dedup_sq = dedup_dist * dedup_dist;
for seg in &segments {
let seg_aabb = seg.aabb();
let (t_lo, t_hi) = seg.domain();
for patch in &patches {
let patch_aabb = patch.aabb();
if !seg_aabb.intersects(patch_aabb) {
continue;
}
let n_samples = 8;
let (u0, u1) = patch.u_range;
let (v0, v1) = patch.v_range;
for i in 0..=n_samples {
let frac = i as f64 / n_samples as f64;
let t = t_lo + (t_hi - t_lo) * frac;
let pt = seg.evaluate(t);
let mut best_u = (u0 + u1) * 0.5;
let mut best_v = (v0 + v1) * 0.5;
let mut best_dist = f64::MAX;
for iu in 0..=2_usize {
let uf = u0 + (u1 - u0) * (iu as f64 / 2.0);
for iv in 0..=2_usize {
let vf = v0 + (v1 - v0) * (iv as f64 / 2.0);
let sp = surface.evaluate(uf, vf);
let d = (sp - pt).length();
if d < best_dist {
best_dist = d;
best_u = uf;
best_v = vf;
}
}
}
let u_guess = best_u;
let v_guess = best_v;
if let Some(hit) =
refine_curve_surface_point(curve, surface, t, u_guess, v_guess, tolerance)
{
let dup = hits.iter().any(|h: &CurveSurfaceHit| {
let d = h.point - hit.point;
d.x().mul_add(d.x(), d.y().mul_add(d.y(), d.z() * d.z())) < dedup_sq
});
if !dup {
hits.push(hit);
}
}
}
}
}
hits.sort_by(|a, b| a.t.partial_cmp(&b.t).unwrap_or(std::cmp::Ordering::Equal));
Ok(hits)
}
fn refine_curve_surface_point(
curve: &NurbsCurve,
surface: &NurbsSurface,
t_guess: f64,
u_guess: f64,
v_guess: f64,
tolerance: f64,
) -> Option<CurveSurfaceHit> {
let mut t = t_guess;
let mut u = u_guess;
let mut v = v_guess;
let (t_min, t_max) = curve.domain();
let (u_min, u_max) = surface.domain_u();
let (v_min, v_max) = surface.domain_v();
for _ in 0..MAX_NEWTON_ITER {
let c_pt = curve.evaluate(t);
let s_pt = surface.evaluate(u, v);
let residual = c_pt - s_pt;
let dist_sq = residual.x().mul_add(
residual.x(),
residual
.y()
.mul_add(residual.y(), residual.z() * residual.z()),
);
if dist_sq < tolerance * tolerance {
return Some(CurveSurfaceHit {
point: c_pt,
t,
uv: (u, v),
});
}
let c_derivs = curve.derivatives(t, 1);
let ct = if c_derivs.len() > 1 {
c_derivs[1]
} else {
return None;
};
let s_derivs = surface.derivatives(u, v, 1);
let su = s_derivs[1][0];
let sv = s_derivs[0][1];
let r = Vec3::new(residual.x(), residual.y(), residual.z());
let col0 = ct;
let col1 = Vec3::new(-su.x(), -su.y(), -su.z());
let col2 = Vec3::new(-sv.x(), -sv.y(), -sv.z());
let det = col0.x() * (col1.y() * col2.z() - col1.z() * col2.y())
- col1.x() * (col0.y() * col2.z() - col0.z() * col2.y())
+ col2.x() * (col0.y() * col1.z() - col0.z() * col1.y());
if det.abs() < 1e-30 {
let lambda = 1e-6;
let jtj = [
[col0.dot(col0) + lambda, col0.dot(col1), col0.dot(col2)],
[col1.dot(col0), col1.dot(col1) + lambda, col1.dot(col2)],
[col2.dot(col0), col2.dot(col1), col2.dot(col2) + lambda],
];
let jtr = [col0.dot(r), col1.dot(r), col2.dot(r)];
if let Some((dt, du, dv)) = solve_3x3_cramer(&jtj, &jtr) {
t -= dt;
u -= du;
v -= dv;
} else {
break;
}
} else {
let inv_det = 1.0 / det;
let neg_r = Vec3::new(-r.x(), -r.y(), -r.z());
let dt = inv_det
* (neg_r.x() * (col1.y() * col2.z() - col1.z() * col2.y())
- col1.x() * (neg_r.y() * col2.z() - neg_r.z() * col2.y())
+ col2.x() * (neg_r.y() * col1.z() - neg_r.z() * col1.y()));
let du = inv_det
* (col0.x() * (neg_r.y() * col2.z() - neg_r.z() * col2.y())
- neg_r.x() * (col0.y() * col2.z() - col0.z() * col2.y())
+ col2.x() * (col0.y() * neg_r.z() - col0.z() * neg_r.y()));
let dv = inv_det
* (col0.x() * (col1.y() * neg_r.z() - col1.z() * neg_r.y())
- col1.x() * (col0.y() * neg_r.z() - col0.z() * neg_r.y())
+ neg_r.x() * (col0.y() * col1.z() - col0.z() * col1.y()));
t += dt;
u += du;
v += dv;
}
t = t.clamp(t_min, t_max);
u = u.clamp(u_min, u_max);
v = v.clamp(v_min, v_max);
}
let c_pt = curve.evaluate(t);
let s_pt = surface.evaluate(u, v);
let residual = c_pt - s_pt;
let dist_sq = residual.x().mul_add(
residual.x(),
residual
.y()
.mul_add(residual.y(), residual.z() * residual.z()),
);
if dist_sq < (tolerance * 10.0).powi(2) {
Some(CurveSurfaceHit {
point: c_pt,
t,
uv: (u, v),
})
} else {
None
}
}
fn solve_3x3_cramer(a: &[[f64; 3]; 3], b: &[f64; 3]) -> Option<(f64, f64, f64)> {
let det = a[0][0] * (a[1][1] * a[2][2] - a[1][2] * a[2][1])
- a[0][1] * (a[1][0] * a[2][2] - a[1][2] * a[2][0])
+ a[0][2] * (a[1][0] * a[2][1] - a[1][1] * a[2][0]);
if det.abs() < 1e-30 {
return None;
}
let inv = 1.0 / det;
let x0 = inv
* (b[0] * (a[1][1] * a[2][2] - a[1][2] * a[2][1])
- a[0][1] * (b[1] * a[2][2] - a[1][2] * b[2])
+ a[0][2] * (b[1] * a[2][1] - a[1][1] * b[2]));
let x1 = inv
* (a[0][0] * (b[1] * a[2][2] - a[1][2] * b[2])
- b[0] * (a[1][0] * a[2][2] - a[1][2] * a[2][0])
+ a[0][2] * (a[1][0] * b[2] - b[1] * a[2][0]));
let x2 = inv
* (a[0][0] * (a[1][1] * b[2] - b[1] * a[2][1])
- a[0][1] * (a[1][0] * b[2] - b[1] * a[2][0])
+ b[0] * (a[1][0] * a[2][1] - a[1][1] * a[2][0]));
Some((x0, x1, x2))
}