use approx::abs_diff_eq;
use decorum::cmp::EmptyOrd;
use decorum::InfinityEncoding;
use num_traits::bounds::Bounded;
use num_traits::identities::Zero;
use num_traits::real::Real;
use num_traits::sign::Signed;
use std::fmt::{self, Debug, Formatter};
use std::ops::Neg;
use typenum::type_operators::Cmp;
use typenum::{Greater, U0, U1, U2};
use crate::adjunct::{Fold, ZipMap};
use crate::ops::Dot;
use crate::space::{
Basis, EuclideanSpace, FiniteDimensional, InnerSpace, Scalar, Vector, VectorSpace,
};
pub trait Intersection<T> {
type Output;
fn intersection(&self, other: &T) -> Option<Self::Output>;
}
macro_rules! impl_symmetrical_intersection {
($a:ident $(,)?) => {
impl<S> Intersection<$a<S>> for S
where
S: EuclideanSpace,
$a<S>: Intersection<S>,
{
type Output = <$a<S> as Intersection<S>>::Output;
fn intersection(&self, other: &$a<S>) -> Option<Self::Output> {
other.intersection(self)
}
}
};
($a:ident, $b:ident $(,)?) => {
impl<S> Intersection<$a<S>> for $b<S>
where
S: EuclideanSpace,
$a<S>: Intersection<$b<S>>,
{
type Output = <$a<S> as Intersection<$b<S>>>::Output;
fn intersection(&self, other: &$a<S>) -> Option<Self::Output> {
other.intersection(self)
}
}
};
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Unit<S>
where
S: InnerSpace,
{
inner: S,
}
impl<S> Unit<S>
where
S: InnerSpace,
{
fn from_inner_unchecked(inner: S) -> Self {
Unit { inner }
}
pub fn try_from_inner(inner: S) -> Option<Self> {
inner.normalize().map(|inner| Unit { inner })
}
pub fn into_inner(self) -> S {
self.inner
}
pub fn x() -> Self
where
S: Basis + FiniteDimensional,
S::N: Cmp<U0, Output = Greater>,
{
Self::from_inner_unchecked(Basis::i())
}
pub fn y() -> Self
where
S: Basis + FiniteDimensional,
S::N: Cmp<U1, Output = Greater>,
{
Self::from_inner_unchecked(Basis::j())
}
pub fn z() -> Self
where
S: Basis + FiniteDimensional,
S::N: Cmp<U2, Output = Greater>,
{
Self::from_inner_unchecked(Basis::k())
}
pub fn get(&self) -> &S {
self.as_ref()
}
#[must_use]
pub fn try_set(&mut self, inner: S) -> Option<&S> {
if let Some(inner) = inner.normalize() {
self.inner = inner;
Some(&self.inner)
}
else {
None
}
}
pub fn reverse(self) -> Self {
let Unit { inner, .. } = self;
Self::from_inner_unchecked(-inner)
}
}
impl<S> AsRef<S> for Unit<S>
where
S: InnerSpace,
{
fn as_ref(&self) -> &S {
&self.inner
}
}
impl<S> Default for Unit<S>
where
S: Basis + InnerSpace,
{
fn default() -> Self {
Unit {
inner: S::canonical_basis_component(0).unwrap(),
}
}
}
impl<S> Neg for Unit<S>
where
S: InnerSpace,
{
type Output = Self;
fn neg(self) -> Self::Output {
self.reverse()
}
}
#[derive(Clone, Copy, PartialEq)]
pub struct Line<S>
where
S: EuclideanSpace,
{
pub origin: S,
pub direction: Unit<Vector<S>>,
}
impl<S> Line<S>
where
S: EuclideanSpace,
{
pub fn x() -> Self
where
S: FiniteDimensional,
S::N: Cmp<U0, Output = Greater>,
{
Line {
origin: S::origin(),
direction: Unit::x(),
}
}
pub fn y() -> Self
where
S: FiniteDimensional,
S::N: Cmp<U1, Output = Greater>,
{
Line {
origin: S::origin(),
direction: Unit::y(),
}
}
pub fn z() -> Self
where
S: FiniteDimensional,
S::N: Cmp<U2, Output = Greater>,
{
Line {
origin: S::origin(),
direction: Unit::z(),
}
}
pub fn into_ray(self) -> Ray<S> {
let Line { origin, direction } = self;
Ray { origin, direction }
}
}
impl<S> Line<S>
where
S: EuclideanSpace + FiniteDimensional<N = U2>,
{
pub fn slope(&self) -> Option<Scalar<S>> {
let (x, y) = self.direction.get().into_xy();
if x.is_zero() {
None
}
else {
Some(y / x)
}
}
pub fn x_intercept(&self) -> Option<Scalar<S>> {
self.intersection(&Line::x())
.and_then(|embedding| match embedding {
LineLine::Point(point) => Some(point.into_xy().0),
_ => None,
})
}
pub fn y_intercept(&self) -> Option<Scalar<S>> {
self.intersection(&Line::y())
.and_then(|embedding| match embedding {
LineLine::Point(point) => Some(point.into_xy().1),
_ => None,
})
}
}
impl<S> Debug for Line<S>
where
S: Debug + EuclideanSpace,
Vector<S>: Debug,
{
fn fmt(&self, formatter: &mut Formatter) -> Result<(), fmt::Error> {
formatter
.debug_struct("Line")
.field("origin", &self.origin)
.field("direction", &self.direction)
.finish()
}
}
impl<S> Default for Line<S>
where
S: EuclideanSpace,
{
fn default() -> Self {
Line {
origin: S::origin(),
direction: Unit::default(),
}
}
}
#[derive(Clone, Copy, PartialEq)]
pub enum LineLine<S>
where
S: EuclideanSpace,
{
Point(S),
Line(Line<S>),
}
impl<S> LineLine<S>
where
S: EuclideanSpace,
{
pub fn into_point(self) -> Option<S> {
match self {
LineLine::Point(point) => Some(point),
_ => None,
}
}
pub fn into_line(self) -> Option<Line<S>> {
match self {
LineLine::Line(line) => Some(line),
_ => None,
}
}
}
impl<S> Debug for LineLine<S>
where
S: Debug + EuclideanSpace,
Vector<S>: Debug,
{
fn fmt(&self, formatter: &mut Formatter) -> Result<(), fmt::Error> {
match *self {
LineLine::Point(point) => write!(formatter, "Point({:?})", point),
LineLine::Line(line) => write!(formatter, "Line({:?})", line),
}
}
}
impl<S> Intersection<Line<S>> for Line<S>
where
S: EuclideanSpace + FiniteDimensional<N = U2>,
{
type Output = LineLine<S>;
fn intersection(&self, other: &Line<S>) -> Option<Self::Output> {
let (x1, y1) = if (self.origin - other.origin).is_zero() {
(self.origin + *self.direction.get()).into_xy()
}
else {
self.origin.into_xy()
};
let (u1, v1) = self.direction.get().into_xy();
let (x2, y2) = other.origin.into_xy();
let (u2, v2) = other.direction.get().into_xy();
let numerator = (u2 * (y1 - y2)) - (v2 * (x1 - x2));
let denominator = (v2 * u1) - (u2 * v1);
match (numerator.is_zero(), denominator.is_zero()) {
(true, true) => Some(LineLine::Line(*self)),
(false, true) => None,
_ => {
let quotient = numerator / denominator;
Some(LineLine::Point(S::from_xy(
x1 + (quotient * u1),
y1 + (quotient * v1),
)))
}
}
}
}
#[derive(Clone, Copy, PartialEq)]
pub enum LinePlane<S>
where
S: EuclideanSpace,
{
TimeOfImpact(Scalar<S>),
Line(Line<S>),
}
impl<S> LinePlane<S>
where
S: EuclideanSpace,
{
pub fn into_time_of_impact(self) -> Option<Scalar<S>> {
match self {
LinePlane::TimeOfImpact(time) => Some(time),
_ => None,
}
}
pub fn into_line(self) -> Option<Line<S>> {
match self {
LinePlane::Line(line) => Some(line),
_ => None,
}
}
}
impl<S> Debug for LinePlane<S>
where
S: Debug + EuclideanSpace,
Scalar<S>: Debug,
Vector<S>: Debug,
{
fn fmt(&self, formatter: &mut Formatter) -> Result<(), fmt::Error> {
match *self {
LinePlane::TimeOfImpact(x) => write!(formatter, "TimeOfImpact({:?})", x),
LinePlane::Line(line) => write!(formatter, "Line({:?})", line),
}
}
}
impl<S> Intersection<Plane<S>> for Line<S>
where
S: EuclideanSpace + FiniteDimensional,
<S as FiniteDimensional>::N: Cmp<U2, Output = Greater>,
{
type Output = LinePlane<S>;
fn intersection(&self, plane: &Plane<S>) -> Option<Self::Output> {
let line = self;
let direction = *line.direction.get();
let normal = *plane.normal.get();
let orientation = direction.dot(normal);
if abs_diff_eq!(orientation, Zero::zero()) {
if abs_diff_eq!((plane.origin - line.origin).dot(normal), Zero::zero()) {
Some(LinePlane::Line(*line))
}
else {
None
}
}
else {
Some(LinePlane::TimeOfImpact(
(plane.origin - line.origin).dot(normal) / orientation,
))
}
}
}
impl_symmetrical_intersection!(Line, Plane);
#[derive(Clone, Copy, PartialEq)]
pub struct Ray<S>
where
S: EuclideanSpace,
{
pub origin: S,
pub direction: Unit<Vector<S>>,
}
impl<S> Ray<S>
where
S: EuclideanSpace,
{
pub fn into_line(self) -> Line<S> {
let Ray { origin, direction } = self;
Line { origin, direction }
}
pub fn reverse(self) -> Self {
let Ray { origin, direction } = self;
Ray {
origin,
direction: Unit::from_inner_unchecked(-direction.into_inner()),
}
}
}
impl<S> Debug for Ray<S>
where
S: Debug + EuclideanSpace,
Vector<S>: Debug,
{
fn fmt(&self, formatter: &mut Formatter) -> Result<(), fmt::Error> {
formatter
.debug_struct("Ray")
.field("origin", &self.origin)
.field("direction", &self.direction)
.finish()
}
}
impl<S> Default for Ray<S>
where
S: EuclideanSpace,
{
fn default() -> Self {
Ray {
origin: S::origin(),
direction: Unit::default(),
}
}
}
impl<S> Neg for Ray<S>
where
S: EuclideanSpace,
{
type Output = Self;
fn neg(self) -> Self::Output {
self.reverse()
}
}
#[derive(Clone, Copy, PartialEq)]
pub struct Aabb<S>
where
S: EuclideanSpace,
{
pub origin: S,
pub extent: Vector<S>,
}
impl<S> Aabb<S>
where
S: EuclideanSpace,
{
pub fn from_points<I>(points: I) -> Self
where
I: IntoIterator<Item = S>,
Scalar<S>: EmptyOrd,
{
let mut min = S::origin();
let mut max = S::origin();
for point in points {
min = min.per_item_min_or_empty(point);
max = max.per_item_max_or_empty(point);
}
Aabb {
origin: min,
extent: max - min,
}
}
pub fn endpoint(&self) -> S {
self.origin + self.extent
}
pub fn upper_bound(&self) -> S
where
Scalar<S>: EmptyOrd,
{
self.origin.per_item_max_or_empty(self.endpoint())
}
pub fn lower_bound(&self) -> S
where
Scalar<S>: EmptyOrd,
{
self.origin.per_item_min_or_empty(self.endpoint())
}
pub fn volume(&self) -> Scalar<S> {
self.origin
.zip_map(self.endpoint(), |a, b| (a - b).abs())
.product()
}
pub fn union(&self, aabb: &Self) -> Self
where
Scalar<S>: EmptyOrd,
{
let origin = self.lower_bound().per_item_min_or_empty(aabb.lower_bound());
let extent = self.upper_bound().per_item_max_or_empty(aabb.upper_bound()) - origin;
Aabb { origin, extent }
}
}
impl<S> Debug for Aabb<S>
where
S: Debug + EuclideanSpace,
Vector<S>: Debug,
{
fn fmt(&self, formatter: &mut Formatter) -> Result<(), fmt::Error> {
formatter
.debug_struct("Aabb")
.field("origin", &self.origin)
.field("extent", &self.extent)
.finish()
}
}
impl<S> Default for Aabb<S>
where
S: EuclideanSpace,
{
fn default() -> Self {
Aabb {
origin: S::origin(),
extent: Vector::<S>::zero(),
}
}
}
impl<S> Intersection<S> for Aabb<S>
where
S: EuclideanSpace,
Scalar<S>: EmptyOrd + Signed,
{
type Output = Vector<S>;
fn intersection(&self, point: &S) -> Option<Self::Output> {
let aabb = self;
let lower = aabb.lower_bound().per_item_max_or_empty(*point);
let upper = aabb.upper_bound().per_item_min_or_empty(*point);
if lower == upper {
Some(*point - aabb.lower_bound())
}
else {
None
}
}
}
impl_symmetrical_intersection!(Aabb);
impl<S> Intersection<Aabb<S>> for Aabb<S>
where
S: EuclideanSpace,
Scalar<S>: EmptyOrd + Signed,
{
type Output = Self;
fn intersection(&self, other: &Aabb<S>) -> Option<Self::Output> {
let max_lower_bound = self
.lower_bound()
.per_item_max_or_empty(other.lower_bound());
let min_upper_bound = self
.upper_bound()
.per_item_min_or_empty(other.upper_bound());
let difference = min_upper_bound - max_lower_bound;
if difference.all(|x| (!x.is_empty()) && x.is_positive()) {
Some(Aabb {
origin: max_lower_bound,
extent: difference,
})
}
else {
None
}
}
}
impl<S> Intersection<Ray<S>> for Aabb<S>
where
S: EuclideanSpace,
Scalar<S>: Bounded + EmptyOrd + InfinityEncoding + Signed,
{
type Output = (Scalar<S>, Scalar<S>);
fn intersection(&self, ray: &Ray<S>) -> Option<Self::Output> {
let pdiv = |a: Scalar<S>, b: Scalar<S>| {
if abs_diff_eq!(a, Zero::zero()) {
a
}
else {
a / b
}
};
let aabb = self;
let direction = *ray.direction.get();
let origin = (aabb.origin - ray.origin).zip_map(direction, pdiv);
let endpoint = ((aabb.endpoint()) - ray.origin).zip_map(direction, pdiv);
let min = origin.per_item_min_or_empty(endpoint).max_or_empty();
let max = origin.per_item_max_or_empty(endpoint).min_or_empty();
if max.is_negative() || min > max || min.is_empty() || max.is_empty() {
None
}
else {
Some((min, max))
}
}
}
impl_symmetrical_intersection!(Aabb, Ray);
#[derive(Clone)]
pub struct Plane<S>
where
S: EuclideanSpace,
{
pub origin: S,
pub normal: Unit<Vector<S>>,
}
impl<S> Copy for Plane<S>
where
S: EuclideanSpace,
Vector<S>: Copy,
{
}
impl<S> Debug for Plane<S>
where
S: Debug + EuclideanSpace,
Vector<S>: Debug,
{
fn fmt(&self, formatter: &mut Formatter) -> Result<(), fmt::Error> {
formatter
.debug_struct("Plane")
.field("origin", &self.origin)
.field("normal", &self.normal)
.finish()
}
}
#[derive(Clone, Copy, PartialEq)]
pub enum PlaneRay<S>
where
S: EuclideanSpace,
{
TimeOfImpact(Scalar<S>),
Ray(Ray<S>),
}
impl<S> PlaneRay<S>
where
S: EuclideanSpace,
{
pub fn into_time_of_impact(self) -> Option<Scalar<S>> {
match self {
PlaneRay::TimeOfImpact(time) => Some(time),
_ => None,
}
}
pub fn into_ray(self) -> Option<Ray<S>> {
match self {
PlaneRay::Ray(ray) => Some(ray),
_ => None,
}
}
}
impl<S> Debug for PlaneRay<S>
where
S: Debug + EuclideanSpace,
Scalar<S>: Debug,
Vector<S>: Debug,
{
fn fmt(&self, formatter: &mut Formatter) -> Result<(), fmt::Error> {
match *self {
PlaneRay::TimeOfImpact(x) => write!(formatter, "TimeOfImpact({:?})", x),
PlaneRay::Ray(ray) => write!(formatter, "Ray({:?})", ray),
}
}
}
impl<S> Intersection<Ray<S>> for Plane<S>
where
S: EuclideanSpace + FiniteDimensional,
<S as FiniteDimensional>::N: Cmp<U2, Output = Greater>,
Scalar<S>: Signed,
{
type Output = PlaneRay<S>;
fn intersection(&self, ray: &Ray<S>) -> Option<Self::Output> {
let plane = self;
ray.into_line()
.intersection(plane)
.and_then(|embedding| match embedding {
LinePlane::TimeOfImpact(t) => {
if t.is_positive() {
Some(PlaneRay::TimeOfImpact(t))
}
else {
None
}
}
LinePlane::Line(_) => Some(PlaneRay::Ray(*ray)),
})
}
}
impl_symmetrical_intersection!(Plane, Ray);
#[cfg(test)]
mod tests {
use decorum::real::UnaryRealFunction;
use decorum::ExtendedReal;
use nalgebra::{Point2, Point3};
use crate::adjunct::Converged;
use crate::query::{Aabb, Intersection, Line, LineLine, Plane, PlaneRay, Ray, Unit};
use crate::space::{EuclideanSpace, Vector, VectorSpace};
type X64 = ExtendedReal<f64>;
type E2 = Point2<f64>;
type E3 = Point3<f64>;
#[test]
fn aabb_aabb_intersection_e2() {
let aabb1 = Aabb::<E2> {
origin: EuclideanSpace::origin(),
extent: Converged::converged(2.0),
};
let aabb2 = Aabb::<E2> {
origin: Converged::converged(1.0),
extent: Converged::converged(2.0),
};
assert_eq!(Some(aabb1), aabb1.intersection(&aabb1));
assert_eq!(
Some(Aabb::<E2> {
origin: Converged::converged(1.0),
extent: Converged::converged(1.0),
}),
aabb1.intersection(&aabb2),
);
let aabb2 = Aabb::<E2> {
origin: Converged::converged(-3.0),
extent: Converged::converged(2.0),
};
assert_eq!(None, aabb1.intersection(&aabb2));
}
#[test]
fn aabb_point_intersection_e2() {
let aabb = Aabb::<E2> {
origin: EuclideanSpace::origin(),
extent: Converged::converged(2.0),
};
let point = E2::converged(1.0);
assert_eq!(
Some(Vector::<E2>::converged(1.0)),
aabb.intersection(&point),
);
let point = E2::converged(3.0);
assert_eq!(None, aabb.intersection(&point));
}
#[test]
fn aabb_ray_intersection_e2() {
let aabb = Aabb::<E2> {
origin: EuclideanSpace::origin(),
extent: Converged::converged(1.0),
};
let ray = Ray::<E2> {
origin: EuclideanSpace::from_xy(-1.0, 0.5),
direction: Unit::x(),
};
assert_eq!(Some((1.0, 2.0)), ray.intersection(&aabb));
assert_eq!(None, ray.reverse().intersection(&aabb));
}
#[test]
fn aabb_ray_intersection_e3() {
let aabb = Aabb::<E3> {
origin: EuclideanSpace::origin(),
extent: Converged::converged(1.0),
};
let ray = Ray::<E3> {
origin: EuclideanSpace::from_xyz(-1.0, 0.5, 0.5),
direction: Unit::x(),
};
assert_eq!(Some((1.0, 2.0)), ray.intersection(&aabb));
assert_eq!(None, ray.reverse().intersection(&aabb));
}
#[test]
fn aabb_ray_intersection_nan() {
let aabb = Aabb::<Point2<X64>> {
origin: EuclideanSpace::origin(),
extent: Converged::converged(X64::ONE),
};
let ray = Ray::<Point2<X64>> {
origin: EuclideanSpace::origin(),
direction: Unit::x(),
};
assert_eq!(Some((X64::ZERO, X64::ONE)), ray.intersection(&aabb));
}
#[test]
fn line_line_intersection_e2() {
let line = Line::<E2>::x();
assert_eq!(
Some(LineLine::Point(E2::origin())),
line.intersection(&Line::y()),
);
assert_eq!(Some(LineLine::Line(line)), line.intersection(&Line::x()));
let line1 = Line::<E2> {
origin: E2::origin(),
direction: Unit::try_from_inner(Converged::converged(1.0)).unwrap(),
};
let line2 = Line::<E2> {
origin: E2::from_xy(2.0, 0.0),
direction: Unit::try_from_inner(Vector::<E2>::from_xy(-1.0, 1.0)).unwrap(),
};
assert_eq!(
Some(LineLine::Point(Converged::converged(1.0))),
line1.intersection(&line2),
);
let line1 = Line::<E2>::x();
let line2 = Line::<E2> {
origin: E2::from_xy(0.0, 1.0),
direction: Unit::x(),
};
assert_eq!(None, line1.intersection(&line2));
}
#[test]
fn plane_ray_intersection_e3() {
let plane = Plane::<E3> {
origin: EuclideanSpace::from_xyz(0.0, 0.0, 1.0),
normal: Unit::z(),
};
let ray = Ray::<E3> {
origin: EuclideanSpace::origin(),
direction: Unit::z(),
};
assert_eq!(Some(PlaneRay::TimeOfImpact(1.0)), ray.intersection(&plane));
assert_eq!(None, ray.reverse().intersection(&plane));
}
}