use crate::curves::ParametricCurve3D;
use crate::math::newton_3d;
use crate::surfaces::ParametricSurface;
use crate::{Circle3D, Ellipse3D, Hyperbola3D, Line3D, Parabola3D, Point2D, Point3D, Tolerance};
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum PlanePlaneIntersection {
Line(Line3D),
Parallel,
Coincident,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum PlaneSphereIntersection {
Circle(Circle3D),
TangentPoint(Point3D),
Empty,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum SphereSphereIntersection {
Circle(Circle3D),
TangentPoint(Point3D),
Empty,
Coincident,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum PlaneCylinderIntersection {
Circle(Circle3D),
Ellipse(Ellipse3D),
TwoLines(Line3D, Line3D),
TangentLine(Line3D),
Empty,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum PlaneConeIntersection {
Circle(Circle3D),
Ellipse(Ellipse3D),
Parabola(Parabola3D),
Hyperbola(Hyperbola3D),
TwoLines(Line3D, Line3D),
TangentLine(Line3D),
ApexPoint(Point3D),
Empty,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LinePlaneIntersection {
Point(f64, Point3D),
Parallel,
Coincident,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LineQuadricIntersection {
TwoPoints((f64, Point3D), (f64, Point3D)),
OnePoint(f64, Point3D),
Tangent(f64, Point3D),
Empty,
Coincident,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum SphereCylinderIntersection {
Circle(Circle3D),
TwoCircles(Circle3D, Circle3D),
Empty,
NotAnalytic,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum SphereConeIntersection {
Circle(Circle3D),
TwoCircles(Circle3D, Circle3D),
TangentCircle(Circle3D),
Empty,
NotAnalytic,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum CylinderCylinderIntersection {
TwoLines(Line3D, Line3D),
TangentLine(Line3D),
Empty,
Coincident,
NotAnalytic,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ConeCylinderIntersection {
Circle(Circle3D),
ApexPoint(Point3D),
NotAnalytic,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum TorusPlaneIntersection {
TwoCircles(Circle3D, Circle3D),
TangentCircle(Circle3D),
Circle(Circle3D),
Empty,
NotAnalytic,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum TorusSphereIntersection {
Circle(Circle3D),
TwoCircles(Circle3D, Circle3D),
TangentCircle(Circle3D),
Empty,
NotAnalytic,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum TorusCylinderIntersection {
TwoCircles(Circle3D, Circle3D),
TangentCircle(Circle3D),
Empty,
NotAnalytic,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum TorusConeIntersection {
Circle(Circle3D),
TwoCircles(Circle3D, Circle3D),
TangentCircle(Circle3D),
Empty,
NotAnalytic,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum TorusTorusIntersection {
Circle(Circle3D),
TwoCircles(Circle3D, Circle3D),
TangentCircle(Circle3D),
Empty,
Coincident,
NotAnalytic,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ConeConeIntersection {
Circle(Circle3D),
ApexPoint(Point3D),
Empty,
Coincident,
NotAnalytic,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LineLine2DIntersection {
Point(f64, Point2D, f64),
Parallel,
Coincident,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LineCircle2DIntersection {
Points((f64, Point2D), (f64, Point2D)),
Tangent(f64, Point2D),
Empty,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum CircleCircle2DIntersection {
Points(Point2D, Point2D),
Tangent(Point2D),
Empty,
Coincident,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LineLine3DIntersection {
Point(f64, Point3D, f64),
Parallel(f64),
Coincident,
Skew {
s: f64,
t: f64,
distance: f64,
},
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LineCircle3DIntersection {
Points((f64, Point3D), (f64, Point3D)),
Tangent(f64, Point3D),
Empty,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum CircleCircle3DIntersection {
Points(Point3D, Point3D),
Tangent(Point3D),
Empty,
Coincident,
NotAnalytic,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) enum QuadraticSolution {
Two(f64, f64),
One(f64),
Linear(f64),
Degenerate,
Empty,
}
pub(crate) fn solve_quadratic(a: f64, b: f64, c: f64, tol: Tolerance) -> QuadraticSolution {
if a.abs() <= tol.angular {
if b.abs() <= tol.confusion {
return QuadraticSolution::Degenerate;
}
return QuadraticSolution::Linear(-c / b);
}
let disc = b * b - 4.0 * a * c;
let mid = -b / (2.0 * a);
let sep = disc.abs().sqrt() / (2.0 * a.abs());
if sep <= tol.confusion * (1.0 + mid.abs()) {
QuadraticSolution::One(mid)
} else if disc < 0.0 {
QuadraticSolution::Empty
} else {
let (t1, t2) = (mid - sep, mid + sep);
QuadraticSolution::Two(t1, t2)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CurveSurfaceHit {
pub curve_param: f64,
pub surface_params: (f64, f64),
pub point: Point3D,
}
pub fn intersect_curve_surface<C, S>(curve: &C, surface: &S, tol: Tolerance) -> Vec<CurveSurfaceHit>
where
C: ParametricCurve3D,
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 mut samples: Vec<(f64, f64)> = (0..SEEDS)
.map(|i| {
let t = lo + (hi - lo) * i as f64 / (SEEDS - 1) as f64;
(t, surface.project_point(curve.eval_point(t), tol).distance)
})
.collect();
samples.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
let mut hits: Vec<CurveSurfaceHit> = Vec::new();
for (t, _) in samples.into_iter().take(KEEP) {
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_derivative(x[0], 1);
let su = surface.eval_derivative(x[1], x[2], 1, 0);
let sv = surface.eval_derivative(x[1], x[2], 0, 1);
[
[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);
let q = surface.eval_point(ru, rv);
if p.distance(q) <= tol.confusion
&& !hits
.iter()
.any(|h: &CurveSurfaceHit| h.point.distance(p) <= tol.confusion)
{
hits.push(CurveSurfaceHit {
curve_param: rt,
surface_params: (ru, rv),
point: p,
});
}
}
}
hits.sort_by(|a, b| a.curve_param.partial_cmp(&b.curve_param).unwrap());
hits
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Tolerance;
use crate::{Line3D, Point3D, Sphere, Vector3D};
#[test]
fn test_solve_quadratic_two_roots_ordered() {
assert_eq!(
solve_quadratic(1.0, -5.0, 6.0, Tolerance::DEFAULT),
QuadraticSolution::Two(2.0, 3.0)
);
}
#[test]
fn test_solve_quadratic_tangent_band() {
assert_eq!(
solve_quadratic(1.0, -4.0, 4.0, Tolerance::DEFAULT),
QuadraticSolution::One(2.0)
);
assert_eq!(
solve_quadratic(1.0, 0.0, 1e-14, Tolerance::DEFAULT),
QuadraticSolution::One(0.0)
);
assert_eq!(
solve_quadratic(1.0, 0.0, 1e-9, Tolerance::DEFAULT),
QuadraticSolution::Empty
);
assert_eq!(
solve_quadratic(1.0, 0.0, 1.0, Tolerance::DEFAULT),
QuadraticSolution::Empty
);
}
#[test]
fn test_solve_quadratic_degenerate_arms() {
assert_eq!(
solve_quadratic(0.0, 2.0, 4.0, Tolerance::DEFAULT),
QuadraticSolution::Linear(-2.0)
);
assert_eq!(
solve_quadratic(0.0, 0.0, 1.0, Tolerance::DEFAULT),
QuadraticSolution::Degenerate
);
}
#[test]
fn test_generic_line_sphere_matches_analytic() {
let tol = Tolerance::DEFAULT;
let line = Line3D::new(Point3D::ORIGIN, Vector3D::X).unwrap();
let sphere = Sphere::new(Point3D::ORIGIN, 2.0).unwrap();
let hits = intersect_curve_surface(&line, &sphere, tol);
assert_eq!(hits.len(), 2);
assert!((hits[0].curve_param + 2.0).abs() < 1e-9);
assert!((hits[1].curve_param - 2.0).abs() < 1e-9);
match line.intersect_sphere(&sphere, tol) {
LineQuadricIntersection::TwoPoints((t1, _), (t2, _)) => {
assert!((hits[0].curve_param - t1).abs() < 1e-9);
assert!((hits[1].curve_param - t2).abs() < 1e-9);
}
_ => panic!("analytic expected two points"),
}
}
#[test]
fn test_generic_line_sphere_miss() {
let tol = Tolerance::DEFAULT;
let line = Line3D::new(Point3D::new(0.0, 0.0, 3.0), Vector3D::X).unwrap();
let sphere = Sphere::new(Point3D::ORIGIN, 2.0).unwrap();
assert!(intersect_curve_surface(&line, &sphere, tol).is_empty());
}
#[test]
fn test_generic_segment_plane_hit() {
use crate::{BSplineCurve3D, Plane};
let tol = Tolerance::DEFAULT;
let poles = vec![Point3D::new(0.0, 0.0, 0.0), Point3D::new(2.0, 0.0, 0.0)];
let curve = BSplineCurve3D::new(1, poles, vec![0.0, 1.0], vec![2, 2], false).unwrap();
let plane = Plane::new(Point3D::new(1.0, 0.0, 0.0), Vector3D::X).unwrap();
let hits = intersect_curve_surface(&curve, &plane, tol);
assert_eq!(hits.len(), 1);
assert!((hits[0].curve_param - 0.5).abs() < 1e-9);
assert_eq!(hits[0].point, Point3D::new(1.0, 0.0, 0.0));
}
}