use super::curve_surface_hull::CurveSurfaceHits;
use super::results::sort_by_known_parameter;
use super::{
CurveSurfaceHit, CurveSurfaceIntersection, curve_surface_multiplicity, mixed, overlap_runs,
};
use crate::curves::ParametricCurve;
use crate::math::{brent_minimum, newton_3d};
use crate::surfaces::ParametricSurface;
use crate::{Point, Tolerance};
pub fn intersect_curve_surface<C, S>(
curve: &C,
surface: &S,
tol: Tolerance,
) -> CurveSurfaceIntersection
where
C: ParametricCurve,
S: ParametricSurface,
{
let hits = search_curve_surface(curve, surface, tol);
let completeness = hits.completeness().clone();
let (hits, overlaps, completeness) =
overlap_runs::curve_surface_overlaps(curve, surface, hits.into_hits(), completeness, tol);
CurveSurfaceIntersection::numeric(hits, overlaps, completeness)
}
pub(crate) fn search_curve_surface<C, S>(curve: &C, surface: &S, tol: Tolerance) -> CurveSurfaceHits
where
C: ParametricCurve,
S: ParametricSurface,
{
if let Some(mut hits) = mixed::curve_surface(curve, surface, tol) {
if !hits.is_complete() {
hits.add_sampled(sampled_curve_surface(curve, surface, tol), tol);
}
return hits;
}
CurveSurfaceHits::sampled(sampled_curve_surface(curve, surface, tol))
}
pub(super) fn sampled_curve_surface<C, S>(
curve: &C,
surface: &S,
tol: Tolerance,
) -> Vec<CurveSurfaceHit>
where
C: ParametricCurve,
S: ParametricSurface,
{
let (t0, t1) = curve.bounds();
const WINDOW: f64 = 10.0;
let (lo, hi) = (
if t0.is_finite() { t0 } else { -WINDOW },
if t1.is_finite() { t1 } else { WINDOW },
);
const SEEDS: usize = 128;
const KEEP: usize = 24;
let step = (hi - lo) / (SEEDS - 1) as f64;
let samples: Vec<(f64, f64)> = (0..SEEDS)
.map(|i| {
let t = lo + step * i as f64;
(t, surface.project_point(curve.eval_point(t), tol).distance)
})
.collect();
let local_min = |i: usize| {
(i == 0 || samples[i].1 <= samples[i - 1].1)
&& (i + 1 == SEEDS || samples[i].1 <= samples[i + 1].1)
};
let mut order: Vec<usize> = (0..SEEDS).collect();
order.sort_by(|&a, &b| samples[a].1.total_cmp(&samples[b].1));
let mut hits: Vec<CurveSurfaceHit> = Vec::new();
let merge_gap = tol.confusion.sqrt();
let push = |hits: &mut Vec<CurveSurfaceHit>, hit: CurveSurfaceHit| {
if !hits
.iter()
.any(|h| h.point.distance(hit.point) <= merge_gap)
{
hits.push(hit);
}
};
let multiplicity_at = |t: f64, u: f64, v: f64, point: Point| {
let (_, tangent) = curve.eval_d1(t);
let (_, [su, sv]) = surface.eval_d1(u, v);
curve_surface_multiplicity(tangent, su.cross(sv), point, tol)
};
for i in order.into_iter().take(KEEP) {
let t = samples[i].0;
let proj = surface.project_point(curve.eval_point(t), tol);
let f = |x: [f64; 3]| {
let d = curve.eval_point(x[0]) - surface.eval_point(x[1], x[2]);
[d.x, d.y, d.z]
};
let jac = |x: [f64; 3]| {
let (_, ct) = curve.eval_d1(x[0]);
let (_, [su, sv]) = surface.eval_d1(x[1], x[2]);
[
[ct.x, -su.x, -sv.x],
[ct.y, -su.y, -sv.y],
[ct.z, -su.z, -sv.z],
]
};
if let Some([rt, ru, rv]) = newton_3d(f, jac, [t, proj.u, proj.v], tol.confusion, 50) {
let p = curve.eval_point(rt);
if p.distance(surface.eval_point(ru, rv)) <= tol.confusion {
push(
&mut hits,
CurveSurfaceHit {
curve_parameter: Some(rt),
surface_parameters: Some((ru, rv)),
point: p,
multiplicity: multiplicity_at(rt, ru, rv, p),
},
);
continue;
}
}
if local_min(i) {
let distance = |t: f64| {
let p = curve.eval_point(t);
let proj = surface.project_point(p, tol);
p.distance(surface.eval_point(proj.u, proj.v))
};
let (a, b) = ((t - step).max(lo), (t + step).min(hi));
let rt = brent_minimum(distance, a, b, 1e-13);
let p = curve.eval_point(rt);
let proj = surface.project_point(p, tol);
if proj.distance <= tol.confusion {
push(
&mut hits,
CurveSurfaceHit {
curve_parameter: Some(rt),
surface_parameters: Some((proj.u, proj.v)),
point: p,
multiplicity: multiplicity_at(rt, proj.u, proj.v, p),
},
);
}
}
}
sort_by_known_parameter(&mut hits, |hit| hit.curve_parameter);
hits
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{IntersectExact, Line, LineQuadricIntersection, Point, Sphere, Vector};
#[test]
fn test_generic_line_sphere_matches_exact() {
let tol = Tolerance::DEFAULT;
let line = Line::new(Point::ORIGIN, Vector::X).unwrap();
let sphere = Sphere::new(Point::ORIGIN, 2.0).unwrap();
let hits = search_curve_surface(&line, &sphere, tol);
assert_eq!(hits.len(), 2);
assert!((hits[0].curve_parameter.unwrap() + 2.0).abs() < 1e-9);
assert!((hits[1].curve_parameter.unwrap() - 2.0).abs() < 1e-9);
match line.intersect_exact(&sphere, tol) {
LineQuadricIntersection::TwoPoints((t1, _), (t2, _)) => {
assert!((hits[0].curve_parameter.unwrap() - t1).abs() < 1e-9);
assert!((hits[1].curve_parameter.unwrap() - t2).abs() < 1e-9);
}
_ => panic!("exact intersection expected two points"),
}
}
#[test]
fn test_generic_line_sphere_miss() {
let tol = Tolerance::DEFAULT;
let line = Line::new(Point::new(0.0, 0.0, 3.0), Vector::X).unwrap();
let sphere = Sphere::new(Point::ORIGIN, 2.0).unwrap();
assert!(search_curve_surface(&line, &sphere, tol).is_empty());
}
#[test]
fn test_generic_curve_surface_hits_at_singular_points() {
use crate::{BSplineCurve, Cone};
let tol = Tolerance::DEFAULT;
let on_both = |hit: &CurveSurfaceHit, surface: &dyn ParametricSurface| {
surface.contains(hit.point, tol)
&& surface
.eval_point(
hit.surface_parameters.unwrap().0,
hit.surface_parameters.unwrap().1,
)
.distance(hit.point)
<= 1e-6
};
let sphere = Sphere::new(Point::ORIGIN, 1.0).unwrap();
let line = Line::new(Point::new(0.0, 0.0, -3.0), Vector::Z).unwrap();
let hits = search_curve_surface(&line, &sphere, tol);
assert_eq!(hits.len(), 2, "{hits:?}");
assert!(
(hits[0].curve_parameter.unwrap() - 2.0).abs() < 1e-9,
"{hits:?}"
);
assert!(
(hits[1].curve_parameter.unwrap() - 4.0).abs() < 1e-9,
"{hits:?}"
);
assert!(hits.iter().all(|h| on_both(h, &sphere)));
let cone = Cone::new(Point::new(0.0, 0.0, -1.0), Vector::Z, 0.5, 0.5).unwrap();
let apex = cone.apex();
let line = Line::new(apex - Vector::X * 3.0, Vector::X).unwrap();
let hits = search_curve_surface(&line, &cone, tol);
assert_eq!(hits.len(), 1, "{hits:?}");
assert!(hits[0].point.distance(apex) < 1e-6, "{hits:?}");
assert!(on_both(&hits[0], &cone));
let poles = vec![
Point::new(-1.0, 0.0, 3.0),
Point::new(0.0, 0.0, 0.0),
Point::new(1.0, 0.0, 1.0),
];
let curve = BSplineCurve::new(2, poles, vec![0.0, 1.0], vec![3, 3], false).unwrap();
assert_eq!(curve.eval_point(0.5), Point::new(0.0, 0.0, 1.0));
let hits = search_curve_surface(&curve, &sphere, tol);
assert_eq!(hits.len(), 2, "{hits:?}");
assert!(
(hits[0].curve_parameter.unwrap() - 0.5).abs() < 1e-9,
"{hits:?}"
);
assert!(hits.iter().all(|h| on_both(h, &sphere)));
}
#[test]
fn test_generic_segment_plane_hit() {
use crate::{BSplineCurve, Plane};
let tol = Tolerance::DEFAULT;
let poles = vec![Point::new(0.0, 0.0, 0.0), Point::new(2.0, 0.0, 0.0)];
let curve = BSplineCurve::new(1, poles, vec![0.0, 1.0], vec![2, 2], false).unwrap();
let plane = Plane::new(Point::new(1.0, 0.0, 0.0), Vector::X).unwrap();
let hits = search_curve_surface(&curve, &plane, tol);
assert_eq!(hits.len(), 1);
assert!((hits[0].curve_parameter.unwrap() - 0.5).abs() < 1e-9);
assert_eq!(hits[0].point, Point::new(1.0, 0.0, 0.0));
}
}