use approx;
use crate::*;
use super::*;
pub mod integer;
#[inline]
pub fn discrete_interval <S> (a : Interval <S>, b : Interval <S>) -> bool where
S : OrderedRing
{
let (min_a, max_a) = (a.min(), a.max());
let (min_b, max_b) = (b.min(), b.max());
max_a > min_b && min_a < max_b
}
#[inline]
pub fn continuous_interval <S> (a : Interval <S>, b : Interval <S>)
-> Option <Interval <S>>
where S : OrderedRing {
if discrete_interval (a, b) {
Some (Interval::with_minmax_unchecked (
S::max (a.min(), b.min()), S::min (a.max(), b.max()))
)
} else {
None
}
}
#[inline]
pub fn discrete_aabb2_aabb2 <S : OrderedRing> (a : Aabb2 <S>, b : Aabb2 <S>) -> bool {
let (min_a, max_a) = (a.min(), a.max());
let (min_b, max_b) = (b.min(), b.max());
max_a.0.x > min_b.0.x && min_a.0.x < max_b.0.x &&
max_a.0.y > min_b.0.y && min_a.0.y < max_b.0.y
}
#[inline]
pub fn continuous_aabb2_aabb2 <S> (a : Aabb2 <S>, b : Aabb2 <S>) -> Option <Aabb2 <S>>
where S : OrderedRing + std::fmt::Debug
{
if discrete_aabb2_aabb2 (a, b) {
Some (Aabb2::with_minmax_unchecked (
point2_max (a.min(), b.min()), point2_min (a.max(), b.max()))
)
} else {
None
}
}
#[inline]
pub fn discrete_aabb3_aabb3 <S : OrderedRing> (a : Aabb3 <S>, b : Aabb3 <S>) -> bool {
let (min_a, max_a) = (a.min(), a.max());
let (min_b, max_b) = (b.min(), b.max());
max_a.0.x > min_b.0.x && min_a.0.x < max_b.0.x &&
max_a.0.y > min_b.0.y && min_a.0.y < max_b.0.y &&
max_a.0.z > min_b.0.z && min_a.0.z < max_b.0.z
}
#[inline]
pub fn continuous_aabb3_aabb3 <S> (a : Aabb3 <S>, b : Aabb3 <S>) -> Option <Aabb3 <S>>
where S : OrderedRing + std::fmt::Debug
{
if discrete_aabb3_aabb3 (a, b) {
Some (
Aabb3::with_minmax_unchecked (
point3_max (a.min(), b.min()), point3_min (a.max(), b.max()))
)
} else {
None
}
}
pub fn continuous_line2_aabb2 <S> (line : Line2 <S>, aabb : Aabb2 <S>)
-> Option <(Line2Point <S>, Line2Point <S>)>
where S : OrderedRing + std::fmt::Debug {
let aabb_min = aabb.min();
let aabb_max = aabb.max();
if line.direction.x == S::zero() {
if aabb_min.0.x < line.base.0.x && line.base.0.x < aabb_max.0.x {
let out = if line.direction.y > S::zero() {
let (t0, t1) = (aabb_min.0.y - line.base.0.y, aabb_max.0.y - line.base.0.y);
( (t0, [line.base.0.x, aabb_min.0.y].into()),
(t1, [line.base.0.x, aabb_max.0.y].into())
)
} else {
let (t0, t1) = (line.base.0.y - aabb_max.0.y, line.base.0.y - aabb_min.0.y);
( (t0, [line.base.0.x, aabb_max.0.y].into()),
(t1, [line.base.0.x, aabb_min.0.y].into())
)
};
Some (out)
} else {
None
}
} else if line.direction.y == S::zero() {
if aabb_min.0.y < line.base.0.y && line.base.0.y < aabb_max.0.y {
let out = if line.direction.x > S::zero() {
let (t0, t1) = (aabb_min.0.x - line.base.0.x, aabb_max.0.x - line.base.0.x);
( (t0, [aabb_min.0.x, line.base.0.y].into()),
(t1, [aabb_max.0.x, line.base.0.y].into())
)
} else {
let (t0, t1) = (line.base.0.x - aabb_max.0.x, line.base.0.x - aabb_min.0.x);
( (t0, [aabb_max.0.x, line.base.0.y].into()),
(t1, [aabb_min.0.x, line.base.0.y].into())
)
};
Some (out)
} else {
None
}
} else {
let dir_reciprocal = line.direction.map (|s| S::one() / s);
let (t0_x, t1_x) = {
let (near_x, far_x) = if line.direction.x.is_positive() {
(aabb_min.0.x, aabb_max.0.x)
} else {
(aabb_max.0.x, aabb_min.0.x)
};
( (near_x - line.base.0.x) * dir_reciprocal.x,
(far_x - line.base.0.x) * dir_reciprocal.x
)
};
let (t0_y, t1_y) = {
let (near_y, far_y) = if line.direction.y.is_positive() {
(aabb_min.0.y, aabb_max.0.y)
} else {
(aabb_max.0.y, aabb_min.0.y)
};
( (near_y - line.base.0.y) * dir_reciprocal.y,
(far_y - line.base.0.y) * dir_reciprocal.y
)
};
let interval_x = Interval::with_minmax_unchecked (t0_x, t1_x);
let interval_y = Interval::with_minmax_unchecked (t0_y, t1_y);
continuous_interval (interval_x, interval_y).map (|interval|{
let start = line.point (interval.min());
let end = line.point (interval.max());
( (interval.min(), start), (interval.max(), end) )
})
}
}
pub fn continuous_ray2_aabb2 <S> (_ray : Ray2 <S>, _aabb : Aabb2 <S>)
-> Option <(Ray2Point <S>, Ray2Point <S>)>
{
unimplemented!("TODO: ray2 aabb2 intersect")
}
pub fn continuous_line2_sphere2 <S> (line : Line2 <S>, sphere : Sphere2 <S>)
-> Option <(Line2Point <S>, Line2Point <S>)>
where S : OrderedField + Sqrt {
let two = S::two();
let four = S::four();
let p1 = line.base;
#[expect(clippy::no_effect_underscore_binding)]
let _p2 = line.base + *line.direction;
let p3 = sphere.center;
let r = *sphere.radius;
let p1p2 = line.direction;
let p3p1 = p1 - p3;
let a = S::one();
let b = two * p1p2.dot (p3p1);
let c = *p3p1.norm_squared() - r * r;
let discriminant = b * b - four * a * c;
if discriminant <= S::zero() {
None
} else {
let discriminant_sqrt = discriminant.sqrt();
let frac_2a = S::one() / (two * a);
let t1 = (-b - discriminant_sqrt) * frac_2a;
let t2 = (-b + discriminant_sqrt) * frac_2a;
let first = p1 + (*p1p2) * t1;
let second = p1 + (*p1p2) * t2;
Some (((t1, first), (t2, second)))
}
}
pub fn continuous_ray2_sphere2 <S> (_ray : Ray2 <S>, _sphere : Sphere2 <S>)
-> Option <(Ray2Point <S>, Ray2Point <S>)>
{
unimplemented!("TODO: ray2 sphere2 intersect")
}
pub fn continuous_line3_plane3 <S> (line : Line3 <S>, plane : Plane3 <S>)
-> Option <Line3Point <S>>
where S : OrderedField + approx::RelativeEq {
let normal_dot_direction = plane.normal.dot (*line.direction);
if approx::relative_eq!(normal_dot_direction, S::zero()) {
None
} else {
let plane_to_line = line.base - plane.base;
let t = -plane.normal.dot (plane_to_line) / normal_dot_direction;
let point = line.point (t);
Some ((t, point))
}
}
pub fn continuous_ray3_plane3 <S> (_ray : Ray3 <S>, _plane : Plane3 <S>)
-> Option <Ray3Point <S>>
{
unimplemented!("TODO: ray3 plane3 intersect")
}
pub fn continuous_line3_triangle3 <S> (line : Line3 <S>, triangle : Triangle3 <S>)
-> Option <Line3Point <S>>
where
S : OrderedField + num::real::Real + approx::AbsDiffEq <Epsilon = S>
{
line_triangle (line.affine_line(), triangle)
}
pub fn continuous_ray3_triangle3 <S> (ray : Ray3 <S>, triangle : Triangle3 <S>)
-> Option <Ray3Point <S>>
where
S : Real + num::real::Real + approx::AbsDiffEq <Epsilon = S>
{
line_triangle (ray.affine_line(), triangle)
.and_then (|(s, p)| NonNegative::new (s).map (|s| (s, p)))
}
pub fn continuous_line3_aabb3 <S> (line : Line3 <S>, aabb : Aabb3 <S>)
-> Option <(Line3Point <S>, Line3Point <S>)>
where S : OrderedRing + num::real::Real + approx::RelativeEq <Epsilon=S> {
let aabb_min = aabb.min();
let aabb_max = aabb.max();
if line.direction.x == S::zero() {
if aabb_min.0.x < line.base.0.x && line.base.0.x < aabb_max.0.x {
let line2 = Line2::new (
[line.base.0.y, line.base.0.z].into(),
Unit2::unchecked_approx ([line.direction.y, line.direction.z].into()));
let aabb2 = Aabb2::with_minmax_unchecked (
[aabb_min.0.y, aabb_min.0.z].into(),
[aabb_max.0.y, aabb_max.0.z].into());
continuous_line2_aabb2 (line2, aabb2).map (|((t0, p0), (t1, p1))|
( (t0, [line.base.0.x, p0.0.x, p0.0.y].into()),
(t1, [line.base.0.x, p1.0.x, p1.0.y].into())
)
)
} else {
None
}
} else if line.direction.y == S::zero() {
if aabb_min.0.y < line.base.0.y && line.base.0.y < aabb_max.0.y {
let line2 = Line2::new (
[line.base.0.x, line.base.0.z].into(),
Unit2::unchecked_approx ([line.direction.x, line.direction.z].into()));
let aabb2 = Aabb2::with_minmax_unchecked (
[aabb_min.0.x, aabb_min.0.z].into(),
[aabb_max.0.x, aabb_max.0.z].into());
continuous_line2_aabb2 (line2, aabb2).map (|((t0, p0), (t1, p1))|
( (t0, [p0.0.x, line.base.0.y, p0.0.y].into()),
(t1, [p1.0.x, line.base.0.y, p1.0.y].into())
)
)
} else {
None
}
} else if line.direction.z == S::zero() {
if aabb_min.0.z < line.base.0.z && line.base.0.z < aabb_max.0.z {
let line2 = Line2::new (
[line.base.0.x, line.base.0.y].into(),
Unit2::unchecked_approx ([line.direction.x, line.direction.y].into()));
let aabb2 = Aabb2::with_minmax_unchecked (
[aabb_min.0.x, aabb_min.0.y].into(),
[aabb_max.0.x, aabb_max.0.y].into());
continuous_line2_aabb2 (line2, aabb2).map (|((t0, p0), (t1, p1))|
( (t0, [p0.0.x, p0.0.y, line.base.0.z].into()),
(t1, [p1.0.x, p1.0.y, line.base.0.z].into())
)
)
} else {
None
}
} else {
let dir_reciprocal = line.direction.map (|s| S::one() / s);
let (t0_x, t1_x) = {
let (near_x, far_x) = if line.direction.x.is_positive() {
(aabb_min.0.x, aabb_max.0.x)
} else {
(aabb_max.0.x, aabb_min.0.x)
};
( (near_x - line.base.0.x) * dir_reciprocal.x,
(far_x - line.base.0.x) * dir_reciprocal.x
)
};
let (t0_y, t1_y) = {
let (near_y, far_y) = if line.direction.y.is_positive() {
(aabb_min.0.y, aabb_max.0.y)
} else {
(aabb_max.0.y, aabb_min.0.y)
};
( (near_y - line.base.0.y) * dir_reciprocal.y,
(far_y - line.base.0.y) * dir_reciprocal.y
)
};
let (t0_z, t1_z) = {
let (near_z, far_z) = if line.direction.z.is_positive() {
(aabb_min.0.z, aabb_max.0.z)
} else {
(aabb_max.0.z, aabb_min.0.z)
};
( (near_z - line.base.0.z) * dir_reciprocal.z,
(far_z - line.base.0.z) * dir_reciprocal.z
)
};
let interval_x = Interval::with_minmax_unchecked (t0_x, t1_x);
let interval_y = Interval::with_minmax_unchecked (t0_y, t1_y);
continuous_interval (interval_x, interval_y).and_then (|interval| {
let interval_z = Interval::with_minmax_unchecked (t0_z, t1_z);
continuous_interval (interval, interval_z).map (|interval|{
let start = line.point (interval.min());
let end = line.point (interval.max());
( (interval.min(), start), (interval.max(), end) )
})
})
}
}
pub fn continuous_ray3_aabb3 <S> (_ray : Ray3 <S>, _aabb : Aabb3 <S>)
-> Option <(Ray3Point <S>, Ray3Point <S>)>
{
unimplemented!("TODO: ray3 aabb3 intersect")
}
pub fn continuous_line3_sphere3 <S> (line : Line3 <S>, sphere : Sphere3 <S>)
-> Option <(Line3Point <S>, Line3Point <S>)>
where S : OrderedField + Sqrt {
let two = S::two();
let four = S::four();
let p1 = line.base;
#[expect(clippy::no_effect_underscore_binding)]
let _p2 = line.base + *line.direction;
let p3 = sphere.center;
let r = *sphere.radius;
let p1p2 = &line.direction;
let p3p1 = p1 - p3;
let a = S::one();
let b = two * p1p2.dot (p3p1);
let c = *p3p1.norm_squared() - r * r;
let discriminant = b * b - four * a * c;
if discriminant <= S::zero() {
None
} else {
let discriminant_sqrt = discriminant.sqrt();
let frac_2a = S::one() / (two * a);
let t1 = (-b - discriminant_sqrt) * frac_2a;
let t2 = (-b + discriminant_sqrt) * frac_2a;
let first = p1 + (**p1p2) * t1;
let second = p1 + (**p1p2) * t2;
Some (((t1, first), (t2, second)))
}
}
pub fn continuous_ray3_sphere3 <S> (_ray : Ray3 <S>, _sphere : Sphere3 <S>)
-> Option <(Ray3Point <S>, Ray3Point <S>)>
{
unimplemented!("TODO: ray3 sphere3 intersect")
}
pub fn continuous_segment2_aabb2 <S> (segment : Segment2 <S>, aabb : Aabb2 <S>)
-> Option <(Segment2Point <S>, Segment2Point <S>)>
where S : Real {
let vector = segment.point_b() - segment.point_a();
let length = *vector.norm();
let line = Line2::new (segment.point_a(), Unit2::unchecked (vector / length));
if let Some (((t0, _p0), (t1, _p1))) = continuous_line2_aabb2 (line, aabb) {
let interval = Interval::with_minmax_unchecked (S::zero(), length);
continuous_interval (interval, Interval::with_minmax_unchecked (t0, t1)).map (
|interval|
( (Normalized::unchecked (interval.min() / length), line.point (interval.min())),
(Normalized::unchecked (interval.max() / length), line.point (interval.max()))
)
)
} else {
None
}
}
pub fn continuous_segment2_sphere2 <S> (segment : Segment2 <S>, sphere : Sphere2 <S>)
-> Option <(Segment2Point <S>, Segment2Point <S>)>
where S : Real {
let vector = segment.point_b() - segment.point_a();
let length = *vector.norm();
let line = Line2::new (segment.point_a(), Unit2::unchecked (vector / length));
if let Some (((t0, _p0), (t1, _p1))) = continuous_line2_sphere2 (line, sphere) {
let interval = Interval::with_minmax_unchecked (S::zero(), length);
continuous_interval (interval, Interval::with_minmax_unchecked (t0, t1))
.map (|interval|
( (Normalized::unchecked (interval.min() / length), line.point (interval.min())),
(Normalized::unchecked (interval.max() / length), line.point (interval.max()))
)
)
} else {
None
}
}
pub fn continuous_segment3_aabb3 <S> (segment : Segment3 <S>, aabb : Aabb3 <S>)
-> Option <(Segment3Point <S>, Segment3Point <S>)>
where S : Real + num::Float + approx::RelativeEq <Epsilon=S> {
let vector = segment.point_b() - segment.point_a();
let length = *vector.norm();
let line = Line3::new (segment.point_a(), Unit3::unchecked_approx (vector / length));
if let Some (((t0, _p0), (t1, _p1))) = continuous_line3_aabb3 (line, aabb) {
let interval = Interval::with_minmax_unchecked (S::zero(), length);
continuous_interval (interval, Interval::with_minmax_unchecked (t0, t1))
.map (|interval|
( (Normalized::unchecked (interval.min() / length), line.point (interval.min())),
(Normalized::unchecked (interval.max() / length), line.point (interval.max()))
)
)
} else {
None
}
}
pub fn continuous_segment3_sphere3 <S> (segment : Segment3 <S>, sphere : Sphere3 <S>)
-> Option <(Segment3Point <S>, Segment3Point <S>)>
where S : OrderedField + Sqrt {
let two = S::two();
let four = S::four();
let p1 = segment.point_a();
let p2 = segment.point_b();
let p3 = sphere.center;
let r = *sphere.radius;
let p1p2 = p2 - p1;
let p3p1 = p1 - p3;
let a = *p1p2.norm_squared();
let b = two * p1p2.dot (p3p1);
let c = *p3p1.norm_squared() - r * r;
let discriminant = b * b - four * a * c;
if discriminant <= S::zero() {
None
} else {
let discriminant_sqrt = discriminant.sqrt();
let frac_2a = S::one() / (two * a);
let t1 = S::max ((-b - discriminant_sqrt) * frac_2a, S::zero());
let t2 = S::min ((-b + discriminant_sqrt) * frac_2a, S::one());
if t2 <= S::zero() || S::one() <= t1 {
None
} else {
let first = p1 + p1p2 * t1;
let second = p1 + p1p2 * t2;
Some ((
(Normalized::unchecked (t1), first),
(Normalized::unchecked (t2), second)
))
}
}
}
pub fn continuous_segment3_cylinder3 <S : Real> (
segment : Segment3 <S>, cylinder : Cylinder3 <S>
) -> Option <(Segment3Point <S>, Segment3Point <S>)> {
let segment_aabb = segment.aabb3();
let cylinder_aabb = cylinder.aabb3();
if !discrete_aabb3_aabb3 (segment_aabb, cylinder_aabb) {
None
} else {
let p1 = segment.point_a();
let p2 = segment.point_b();
let p3 = cylinder.center;
let r = cylinder.radius;
let r2 = r * r;
let p1p2 = p2 - p1;
let p1_xy = Point2::from ([p1.0.x, p1.0.y]);
let p2_xy = Point2::from ([p2.0.x, p2.0.y]);
let p3_xy = Point2::from ([p3.0.x, p3.0.y]);
let p3_z_max = cylinder_aabb.max().0.z;
let p3_z_min = cylinder_aabb.min().0.z;
if p1_xy == p2_xy { let d2 = (p1_xy - p3_xy).norm_squared();
if *d2 >= *r2 {
None
} else {
let (t1, begin_z) = if p1.0.z >= p3_z_max {
((p3_z_max - p1.0.z) / p1p2.z, p3_z_max)
} else if p1.0.z <= p3_z_min {
((p3_z_min - p1.0.z) / p1p2.z, p3_z_min)
} else {
(S::zero(), p1.0.z)
};
let (t2, end_z) = if p2.0.z >= p3_z_max {
((p3_z_max - p1.0.z) / p1p2.z, p3_z_max)
} else if p2.0.z <= p3_z_min {
((p3_z_min - p1.0.z) / p1p2.z, p3_z_min)
} else {
(S::one(), p2.0.z)
};
let begin = [p1_xy.0.x, p1_xy.0.y, begin_z].into();
let end = [p1_xy.0.x, p1_xy.0.y, end_z ].into();
Some ((
(Normalized::unchecked (t1), begin),
(Normalized::unchecked (t2), end)
))
}
} else { let two = S::two();
let four = S::four();
let p1p2_xy = p1p2.xy();
let p3p1_xy = p1_xy - p3_xy;
let a = p1p2_xy.norm_squared();
let b = two * p1p2_xy.dot (p3p1_xy);
let c = *p3p1_xy.norm_squared() - *r2;
let discriminant = b * b - four * *a * c;
if discriminant <= S::zero() {
None
} else {
let discriminant_sqrt = discriminant.sqrt();
let frac_2a = S::one() / (two * *a);
let t1_xy = S::max ((-b - discriminant_sqrt) * frac_2a, S::zero());
let t2_xy = S::min ((-b + discriminant_sqrt) * frac_2a, S::one());
if t2_xy <= S::zero() || S::one() <= t1_xy {
None
} else if let Some ((t1, t2)) = if p1.0.z == p2.0.z {
Some ((t1_xy, t2_xy))
} else {
let p1p3_z_max = p3_z_max - p1.0.z;
let p1p3_z_min = p3_z_min - p1.0.z;
let t_z_max = S::max (S::min (p1p3_z_max / p1p2.z, S::one()), S::zero());
let t_z_min = S::max (S::min (p1p3_z_min / p1p2.z, S::one()), S::zero());
let t1_z = S::min (t_z_max, t_z_min);
let t2_z = S::max (t_z_max, t_z_min);
let aabb_xy = Interval::with_minmax_unchecked (t1_xy, t2_xy);
let aabb_z = Interval::with_minmax_unchecked (t1_z, t2_z);
if !aabb_xy.intersects (aabb_z) {
None
} else {
Some ((S::max (t1_xy, t1_z), S::min (t2_xy, t2_z)))
}
} {
debug_assert!(t1 < t2);
debug_assert!(t1 >= S::zero());
debug_assert!(t1 < S::one());
debug_assert!(t2 > S::zero());
debug_assert!(t2 <= S::one());
let first = p1 + p1p2 * t1;
let second = p1 + p1p2 * t2;
Some ((
(Normalized::unchecked (t1), first),
(Normalized::unchecked (t2), second)
))
} else {
None
}
}
}
}
}
pub fn continuous_segment3_capsule3 <S : Real> (
segment : Segment3 <S>, capsule : Capsule3 <S>
) -> Option <(Segment3Point <S>, Segment3Point <S>)> {
let segment_aabb = segment.aabb3();
let capsule_aabb = capsule.aabb3();
if !discrete_aabb3_aabb3 (segment_aabb, capsule_aabb) {
None
} else {
let (upper_sphere, cylinder, lower_sphere) = capsule.decompose();
let cylinder_result = cylinder
.and_then (|cylinder| segment.intersect_cylinder (cylinder));
let upper_result = segment.intersect_sphere (upper_sphere);
let lower_result = segment.intersect_sphere (lower_sphere);
match (upper_result, cylinder_result, lower_result) {
(None, None, None) => None,
(one, None, None) | (None, one, None) | (None, None, one) => one,
(Some (((t1,p1), (t2,p2))), Some (((u1,q1), (u2,q2))), None) |
(Some (((t1,p1), (t2,p2))), None, Some (((u1,q1), (u2,q2)))) |
(None, Some (((t1,p1), (t2,p2))), Some (((u1,q1), (u2,q2)))) => {
let first = if t1 < u1 {
(t1,p1)
} else {
(u1,q1)
};
let second = if t2 > u2 {
(t2,p2)
} else {
(u2,q2)
};
Some ((first, second))
}
( Some (((t1,p1), (t2,p2))), Some (((u1,q1), (u2,q2))), Some (((v1,r1), (v2,r2)))
) => {
let min1 = Normalized::min (Normalized::min (t1, u1), v1);
let max2 = Normalized::max (Normalized::max (t2, u2), v2);
let first = if min1 == t1 {
(t1,p1)
} else if min1 == u1 {
(u1,q1)
} else {
debug_assert_eq!(min1, v1);
(v1,r1)
};
let second = if max2 == t2 {
(t2,p2)
} else if max2 == u2 {
(u2,q2)
} else {
debug_assert_eq!(max2, v2);
(v2,r2)
};
Some ((first, second))
}
}
}
}
pub fn continuous_segment3_hull3 <S> (_segment : Segment3 <S>, _hull : &Hull3 <S>)
-> Option <(Segment3Point <S>, Segment3Point <S>)>
{
unimplemented!("TODO: segment/hull intersection")
}
pub fn continuous_triangle3_segment3 <S> (
triangle : Triangle3 <S>, segment : Segment3 <S>
) -> Option <Segment3Point <S>> where
S : OrderedField + num::real::Real + approx::AbsDiffEq <Epsilon = S>
{
line_triangle (segment.affine_line(), triangle)
.and_then (|(s, p)| Normalized::new (s).map (|s| (s, p)))
}
#[inline]
pub fn discrete_triangle3_segment3 <S> (
triangle : Triangle3 <S>, segment : Segment3 <S>
) -> bool where
S : OrderedField + num::real::Real + approx::AbsDiffEq <Epsilon = S>
{
continuous_triangle3_segment3 (triangle, segment).is_some()
}
pub fn discrete_triangle3 <S> (triangle_a : Triangle3 <S>, triangle_b : Triangle3 <S>)
-> bool
where S : Real + num::real::Real + approx::AbsDiffEq <Epsilon = S> {
for edge in triangle_a.edges() {
if discrete_triangle3_segment3 (triangle_b, edge) {
return true
}
}
for edge in triangle_b.edges() {
if discrete_triangle3_segment3 (triangle_a, edge) {
return true
}
}
false
}
#[inline]
pub fn discrete_sphere2_sphere2 <S : OrderedRing> (a : Sphere2 <S>, b : Sphere2 <S>)
-> bool
{
let r_ab = a.radius + b.radius;
(b.center - a.center).self_dot() < (r_ab * r_ab).into()
}
#[inline]
pub fn discrete_sphere3_sphere3 <S : OrderedRing> (a : Sphere3 <S>, b : Sphere3 <S>)
-> bool
{
let r_ab = a.radius + b.radius;
(b.center - a.center).self_dot() < (r_ab * r_ab).into()
}
pub fn line_triangle <S> (line : frame::Line3 <S>, triangle : Triangle3 <S>)
-> Option <Line3Point <S>>
where S : OrderedField + num::real::Real + approx::AbsDiffEq <Epsilon = S> {
let r = line.basis;
let edge1 = triangle.point_b() - triangle.point_a();
let edge2 = triangle.point_c() - triangle.point_a();
let p = r.cross (edge2);
let a = edge1.dot (p);
if approx::abs_diff_eq!(a, S::zero()) {
return None
}
let f = S::one() / a;
let s = line.origin - triangle.point_a();
let u = f * s.dot (p);
if u < S::zero() || u > S::one() {
return None
}
let q = s.cross (edge1);
let v = f * r.dot (q);
if v < S::zero() || u + v > S::one() {
return None
}
let t = f * edge2.dot (q);
Some ((t, line.origin + *r * t))
}
#[cfg(test)]
mod tests {
extern crate test;
use super::*;
#[expect(clippy::cast_possible_truncation)]
static RAY_TRIANGLE_BENCH_SEED : std::sync::LazyLock <u64> = std::sync::LazyLock::new (
|| std::time::SystemTime::UNIX_EPOCH.elapsed().unwrap().as_nanos() as u64);
#[test]
fn line2_aabb2() {
use std::f64::consts::SQRT_2;
let aabb = Aabb2::with_minmax_unchecked ([-1.0, -1.0].into(), [1.0, 1.0].into());
let line = Line2::new ([0.0, 0.0].into(), Unit2::axis_y());
assert_eq!(
continuous_line2_aabb2 (line, aabb).unwrap(),
((-1.0, [ 0.0, -1.0].into()), (1.0, [ 0.0, 1.0].into())));
let line = Line2::new ([0.0, 0.0].into(), Unit2::axis_x());
assert_eq!(
continuous_line2_aabb2 (line, aabb).unwrap(),
((-1.0, [-1.0, 0.0].into()), (1.0, [ 1.0, 0.0].into())));
let line = Line2::new ([0.0, 0.0].into(), Unit2::normalize ([1.0, 1.0].into()));
assert_eq!(
continuous_line2_aabb2 (line, aabb).unwrap(),
((-SQRT_2, [-1.0, -1.0].into()), (SQRT_2, [ 1.0, 1.0].into())));
let line = Line2::new ([0.0, 0.0].into(), Unit2::normalize ([-1.0, -1.0].into()));
assert_eq!(
continuous_line2_aabb2 (line, aabb).unwrap(),
((-SQRT_2, [1.0, 1.0].into()), (SQRT_2, [-1.0, -1.0].into())));
let line = Line2::new ([0.0, 3.0].into(), Unit2::normalize ([-1.0, -1.0].into()));
assert!(continuous_line2_aabb2 (line, aabb).is_none());
let line = Line2::new ([0.0, -3.0].into(), Unit2::normalize ([1.0, 1.0].into()));
assert!(continuous_line2_aabb2 (line, aabb).is_none());
}
#[test]
fn line3_aabb3() {
use approx::assert_ulps_eq;
let aabb = Aabb3::with_minmax_unchecked (
[-1.0, -1.0, -1.0].into(), [1.0, 1.0, 1.0].into());
let line = Line3::new ([0.0, 0.0, 0.0].into(), Unit3::axis_z());
assert_eq!(
continuous_line3_aabb3 (line, aabb).unwrap(),
((-1.0, [0.0, 0.0, -1.0].into()), (1.0, [0.0, 0.0, 1.0].into())));
let line = Line3::new ([0.0, 0.0, 0.0].into(), Unit3::axis_y());
assert_eq!(
continuous_line3_aabb3 (line, aabb).unwrap(),
((-1.0, [0.0, -1.0, 0.0].into()), (1.0, [0.0, 1.0, 0.0].into())));
{
let line = Line3::new (
[0.0, 0.0, 0.0].into(),
Unit3::normalize ([1.0, 1.0, 1.0].into()));
let result = continuous_line3_aabb3 (line, aabb).unwrap();
assert_ulps_eq!((result.0).0, -f64::sqrt_3());
assert_ulps_eq!((result.1).0, f64::sqrt_3());
assert_eq!((result.0).1, [-1.0, -1.0, -1.0].into());
assert_eq!((result.1).1, [ 1.0, 1.0, 1.0].into());
}
{
let line = Line3::new (
[0.0, 0.0, 0.0].into(),
Unit3::normalize ([-1.0, -1.0, -1.0].into()));
let result = continuous_line3_aabb3 (line, aabb).unwrap();
assert_ulps_eq!((result.0).0, -f64::sqrt_3());
assert_ulps_eq!((result.1).0, f64::sqrt_3());
assert_eq!((result.0).1, [ 1.0, 1.0, 1.0].into());
assert_eq!((result.1).1, [-1.0, -1.0, -1.0].into());
}
let line = Line3::new (
[0.0, 0.0, 3.0].into(),
Unit3::normalize ([-1.0, -1.0, -1.0].into()));
assert!(continuous_line3_aabb3 (line, aabb).is_none());
let line = Line3::new (
[0.0, 0.0, -3.0].into(),
Unit3::normalize ([1.0, 1.0, 1.0].into()));
assert!(continuous_line3_aabb3 (line, aabb).is_none());
}
#[test]
fn segment3_sphere3() {
let sphere = shape::Sphere::unit().sphere3 (Point3::origin());
let segment = Segment3::noisy ([-2.0, 0.0, 0.0].into(), [2.0, 0.0, 0.0].into());
assert_eq!(
continuous_segment3_sphere3 (segment, sphere)
.map (|((s, a), (t, b))| ((*s, a), (*t, b))).unwrap(),
((0.25, [-1.0, 0.0, 0.0].into()), (0.75, [1.0, 0.0, 0.0].into())));
let segment = Segment3::noisy ([2.0, 0.0, 0.0].into(), [-2.0, 0.0, 0.0].into());
assert_eq!(
continuous_segment3_sphere3 (segment, sphere)
.map (|((s, a), (t, b))| ((*s, a), (*t, b))).unwrap(),
((0.25, [1.0, 0.0, 0.0].into()), (0.75, [-1.0, 0.0, 0.0].into())));
let segment = Segment3::noisy ([0.0, 0.0, 0.0].into(), [0.0, 0.0, 2.0].into());
assert_eq!(
continuous_segment3_sphere3 (segment, sphere)
.map (|((s, a), (t, b))| ((*s, a), (*t, b))).unwrap(),
((0.0, [0.0, 0.0, 0.0].into()), (0.5, [0.0, 0.0, 1.0].into())));
let segment = Segment3::noisy ([0.0, 0.0, -2.0].into(), [0.0, 0.0, 0.0].into());
assert_eq!(
continuous_segment3_sphere3 (segment, sphere)
.map (|((s, a), (t, b))| ((*s, a), (*t, b))).unwrap(),
((0.5, [0.0, 0.0, -1.0].into()), (1.0, [0.0, 0.0, 0.0].into())));
}
#[test]
fn segment3_cylinder3() {
let cylinder = shape::Cylinder::unit().cylinder3 (Point3::origin());
let segment = Segment3::noisy ([-2.0, 0.0, 0.0].into(), [2.0, 0.0, 0.0].into());
assert_eq!(
continuous_segment3_cylinder3 (segment, cylinder)
.map (|((s, a), (t, b))| ((*s, a), (*t, b))).unwrap(),
((0.25, [-1.0, 0.0, 0.0].into()), (0.75, [1.0, 0.0, 0.0].into())));
let segment = Segment3::noisy ([2.0, 0.0, 0.0].into(), [-2.0, 0.0, 0.0].into());
assert_eq!(
continuous_segment3_cylinder3 (segment, cylinder)
.map (|((s, a), (t, b))| ((*s, a), (*t, b))).unwrap(),
((0.25, [1.0, 0.0, 0.0].into()), (0.75, [-1.0, 0.0, 0.0].into())));
let segment = Segment3::noisy ([0.0, 0.0, 0.0].into(), [0.0, 0.0, 2.0].into());
assert_eq!(
continuous_segment3_cylinder3 (segment, cylinder)
.map (|((s, a), (t, b))| ((*s, a), (*t, b))).unwrap(),
((0.0, [0.0, 0.0, 0.0].into()), (0.5, [0.0, 0.0, 1.0].into())));
let segment = Segment3::noisy ([0.0, 0.0, -2.0].into(), [0.0, 0.0, 0.0].into());
assert_eq!(
continuous_segment3_cylinder3 (segment, cylinder)
.map (|((s, a), (t, b))| ((*s, a), (*t, b))).unwrap(),
((0.5, [0.0, 0.0, -1.0].into()), (1.0, [0.0, 0.0, 0.0].into())));
}
#[test]
fn segment3_capsule3() {
let capsule = shape::Capsule::noisy (1.0, 1.0).capsule3 (Point3::origin());
let segment = Segment3::noisy ([-2.0, 0.0, 0.0].into(), [2.0, 0.0, 0.0].into());
assert_eq!(
continuous_segment3_capsule3 (segment, capsule)
.map (|((s, a), (t, b))| ((*s, a), (*t, b))).unwrap(),
((0.25, [-1.0, 0.0, 0.0].into()), (0.75, [1.0, 0.0, 0.0].into())));
let segment = Segment3::noisy ([2.0, 0.0, 0.0].into(), [-2.0, 0.0, 0.0].into());
assert_eq!(
continuous_segment3_capsule3 (segment, capsule)
.map (|((s, a), (t, b))| ((*s, a), (*t, b))).unwrap(),
((0.25, [1.0, 0.0, 0.0].into()), (0.75, [-1.0, 0.0, 0.0].into())));
let segment = Segment3::noisy ([0.0, 0.0, 0.0].into(), [0.0, 0.0, 4.0].into());
assert_eq!(
continuous_segment3_capsule3 (segment, capsule)
.map (|((s, a), (t, b))| ((*s, a), (*t, b))).unwrap(),
((0.0, [0.0, 0.0, 0.0].into()), (0.5, [0.0, 0.0, 2.0].into())));
let segment = Segment3::noisy ([0.0, 0.0, -4.0].into(), [0.0, 0.0, 0.0].into());
assert_eq!(
continuous_segment3_capsule3 (segment, capsule)
.map (|((s, a), (t, b))| ((*s, a), (*t, b))).unwrap(),
((0.5, [0.0, 0.0, -2.0].into()), (1.0, [0.0, 0.0, 0.0].into())));
}
#[test]
fn segment3_aabb3() {
let aabb =
Aabb3::with_minmax_unchecked ([1.0, -0.5, 0.0].into(), [2.0, 0.5, 1.0].into());
let segment = Segment3::noisy ([-1.0, 0.0, 0.5].into(), [2.0, 0.0, 0.5].into());
assert_eq!(
continuous_segment3_aabb3 (segment, aabb)
.map (|((s, a), (t, b))| ((*s, a), (*t, b))).unwrap(),
((2.0/3.0, [1.0, 0.0, 0.5].into()), (1.0, [2.0, 0.0, 0.5].into())));
}
#[test]
fn line_triangle_intersect() {
let triangle = Triangle3::noisy (
[0.0, 1.0, 1.0].into(), [-1.0, -1.0, 1.0].into(), [ 1.0, -1.0, 1.0].into());
let line = Segment3::noisy ([0.0, 0.0, -1.0].into(), [0.0, 0.0, 1.0].into());
assert_eq!(
line_triangle (line.into(), triangle).unwrap(),
(1.0, [0.0, 0.0, 1.0].into()));
let line = Segment3::noisy ([0.0, 0.0, 1.0].into(), [0.0, 0.0, 2.0].into());
assert_eq!(
line_triangle (line.into(), triangle).unwrap(),
(0.0, [0.0, 0.0, 1.0].into()));
let line = Segment3::noisy ([0.0, 0.0, 2.0].into(), [0.0, 0.0, 3.0].into());
assert_eq!(
line_triangle (line.into(), triangle).unwrap(),
(-1.0, [0.0, 0.0, 1.0].into()));
let line = Segment3::noisy ([5.0, 5.0, 1.0].into(), [5.0, 5.0, 2.0].into());
assert!(line_triangle (line.into(), triangle).is_none());
}
#[bench]
fn bench_line_triangle (b : &mut test::Bencher) {
use rand::SeedableRng;
let aabb = Aabb3::with_minmax_unchecked (
[-10.0, -10.0, -10.0].into(),
[ 10.0, 10.0, 10.0].into());
let mut rng = rand_xorshift::XorShiftRng::seed_from_u64 (*RAY_TRIANGLE_BENCH_SEED);
let mut iter = 0;
let mut num_intersections = 0;
b.iter(||{
let line = Segment3::noisy (
aabb.rand_point (&mut rng), aabb.rand_point (&mut rng));
let triangle = Triangle3::noisy (
aabb.rand_point (&mut rng), aabb.rand_point (&mut rng),
aabb.rand_point (&mut rng));
if line_triangle (line.into(), triangle).is_some() && iter <= 1_000_000 {
num_intersections += 1;
}
iter += 1;
});
let n = Ord::min (iter, 1_000_000);
println!("{num_intersections}/{n} intersections");
}
}