pub use self::simplex::SimplexProcessor;
use std::cmp::Ordering;
use std::ops::{Neg, Range};
use cgmath::BaseFloat;
use cgmath::prelude::*;
use cgmath::num_traits::NumCast;
use self::simplex::{Simplex, SimplexProcessor2, SimplexProcessor3};
use crate::{CollisionStrategy, Contact};
use crate::algorithm::minkowski::{EPA2, EPA3, SupportPoint, EPA};
use crate::prelude::*;
use approx::ulps_eq;
mod simplex;
const MAX_ITERATIONS: u32 = 100;
const GJK_DISTANCE_TOLERANCE: f32 = 0.000001;
const GJK_CONTINUOUS_TOLERANCE: f32 = 0.000001;
pub type GJK2<S> = GJK<SimplexProcessor2<S>, EPA2<S>, S>;
pub type GJK3<S> = GJK<SimplexProcessor3<S>, EPA3<S>, S>;
#[derive(Debug)]
pub struct GJK<SP, E, S> {
simplex_processor: SP,
epa: E,
distance_tolerance: S,
continuous_tolerance: S,
max_iterations: u32,
}
impl<SP, E, S> GJK<SP, E, S>
where
SP: SimplexProcessor,
SP::Point: EuclideanSpace<Scalar = S>,
S: BaseFloat,
E: EPA<Point = SP::Point>,
{
pub fn new() -> Self {
Self {
simplex_processor: SP::new(),
epa: E::new(),
distance_tolerance: NumCast::from(GJK_DISTANCE_TOLERANCE).unwrap(),
continuous_tolerance: NumCast::from(GJK_CONTINUOUS_TOLERANCE).unwrap(),
max_iterations: MAX_ITERATIONS,
}
}
pub fn new_with_settings(
distance_tolerance: S,
continuous_tolerance: S,
epa_tolerance: S,
max_iterations: u32,
) -> Self {
Self {
simplex_processor: SP::new(),
epa: E::new_with_tolerance(epa_tolerance, max_iterations),
distance_tolerance,
continuous_tolerance,
max_iterations,
}
}
pub fn intersect<P, PL, PR, TL, TR>(
&self,
left: &PL,
left_transform: &TL,
right: &PR,
right_transform: &TR,
) -> Option<Simplex<P>>
where
P: EuclideanSpace<Scalar = S>,
PL: Primitive<Point = P>,
PR: Primitive<Point = P>,
SP: SimplexProcessor<Point = P>,
P::Diff: Neg<Output = P::Diff> + InnerSpace + Zero + Array<Element = S>,
TL: Transform<P>,
TR: Transform<P>,
{
let left_pos = left_transform.transform_point(P::origin());
let right_pos = right_transform.transform_point(P::origin());
let mut d = right_pos - left_pos;
if ulps_eq!(d, P::Diff::zero()) {
d = P::Diff::from_value(S::one());
}
let a = SupportPoint::from_minkowski(left, left_transform, right, right_transform, &d);
if a.v.dot(d) <= S::zero() {
return None;
}
let mut simplex = Simplex::new();
simplex.push(a);
d = d.neg();
for _ in 0..self.max_iterations {
let a = SupportPoint::from_minkowski(left, left_transform, right, right_transform, &d);
if a.v.dot(d) <= S::zero() {
return None;
} else {
simplex.push(a);
if self.simplex_processor
.reduce_to_closest_feature(&mut simplex, &mut d)
{
return Some(simplex);
}
}
}
None
}
#[allow(unused_variables)]
pub fn intersection_time_of_impact<P, PL, PR, TL, TR>(
&self,
left: &PL,
left_transform: Range<&TL>,
right: &PR,
right_transform: Range<&TR>,
) -> Option<Contact<P>>
where
P: EuclideanSpace<Scalar = S>,
PL: Primitive<Point = P>,
PR: Primitive<Point = P>,
SP: SimplexProcessor<Point = P>,
P::Diff: Neg<Output = P::Diff> + InnerSpace + Zero + Array<Element = S>,
TL: Transform<P> + TranslationInterpolate<S>,
TR: Transform<P> + TranslationInterpolate<S>,
{
let left_lin_vel = left_transform.end.transform_point(P::origin())
- left_transform.start.transform_point(P::origin());
let right_lin_vel = right_transform.end.transform_point(P::origin())
- right_transform.start.transform_point(P::origin());
let ray = right_lin_vel - left_lin_vel;
let mut lambda = S::zero();
let mut normal = P::Diff::zero();
let mut ray_origin = P::origin();
let mut simplex = Simplex::new();
let p = SupportPoint::from_minkowski(
left,
left_transform.start,
right,
right_transform.start,
&-ray,
);
let mut v = p.v;
while v.magnitude2() > self.continuous_tolerance {
let p = SupportPoint::from_minkowski(
left,
left_transform.start,
right,
right_transform.start,
&-v,
);
let vp = v.dot(p.v);
let vr = v.dot(ray);
if vp > lambda * vr {
if vr > S::zero() {
lambda = vp / vr;
if lambda > S::one() {
return None;
}
ray_origin = P::from_vec(ray * lambda);
simplex.clear();
normal = -v;
} else {
return None;
}
}
simplex.push(p - ray_origin);
v = self.simplex_processor
.get_closest_point_to_origin(&mut simplex);
}
if v.magnitude2() <= self.continuous_tolerance {
let transform = right_transform
.start
.translation_interpolate(right_transform.end, lambda);
let mut contact = Contact::new_with_point(
CollisionStrategy::FullResolution,
-normal.normalize(),
v.magnitude(),
transform.transform_point(ray_origin),
);
contact.time_of_impact = lambda;
Some(contact)
} else {
None
}
}
pub fn distance<P, PL, PR, TL, TR>(
&self,
left: &PL,
left_transform: &TL,
right: &PR,
right_transform: &TR,
) -> Option<S>
where
P: EuclideanSpace<Scalar = S>,
PL: Primitive<Point = P>,
PR: Primitive<Point = P>,
SP: SimplexProcessor<Point = P>,
P::Diff: Neg<Output = P::Diff> + InnerSpace + Zero + Array<Element = S>,
TL: Transform<P>,
TR: Transform<P>,
{
let zero = P::Diff::zero();
let right_pos = right_transform.transform_point(P::origin());
let left_pos = left_transform.transform_point(P::origin());
let mut simplex = Simplex::new();
let mut d = right_pos - left_pos;
if ulps_eq!(d, P::Diff::zero()) {
d = P::Diff::from_value(S::one());
}
for d in &[d, d.neg()] {
simplex.push(SupportPoint::from_minkowski(
left,
left_transform,
right,
right_transform,
d,
));
}
for _ in 0..self.max_iterations {
let d = self.simplex_processor
.get_closest_point_to_origin(&mut simplex);
if ulps_eq!(d, zero) {
return None;
}
let d = d.neg();
let p = SupportPoint::from_minkowski(left, left_transform, right, right_transform, &d);
let dp = p.v.dot(d);
let d0 = simplex[0].v.dot(d);
if dp - d0 < self.distance_tolerance {
return Some(d.magnitude());
}
simplex.push(p);
}
None
}
pub fn get_contact_manifold<P, PL, PR, TL, TR>(
&self,
mut simplex: &mut Vec<SupportPoint<P>>,
left: &PL,
left_transform: &TL,
right: &PR,
right_transform: &TR,
) -> Option<Contact<P>>
where
P: EuclideanSpace<Scalar = S>,
PL: Primitive<Point = P>,
PR: Primitive<Point = P>,
TL: Transform<P>,
TR: Transform<P>,
SP: SimplexProcessor<Point = P>,
{
self.epa
.process(&mut simplex, left, left_transform, right, right_transform)
}
pub fn intersection<P, PL, PR, TL, TR>(
&self,
strategy: &CollisionStrategy,
left: &PL,
left_transform: &TL,
right: &PR,
right_transform: &TR,
) -> Option<Contact<P>>
where
P: EuclideanSpace<Scalar = S>,
P::Diff: Neg<Output = P::Diff> + InnerSpace + Zero + Array<Element = S>,
PL: Primitive<Point = P>,
PR: Primitive<Point = P>,
TL: Transform<P>,
TR: Transform<P>,
SP: SimplexProcessor<Point = P>,
{
use CollisionStrategy::*;
self.intersect(left, left_transform, right, right_transform)
.and_then(|simplex| match *strategy {
CollisionOnly => Some(Contact::new(CollisionOnly)),
FullResolution => self.get_contact_manifold(
&mut simplex.into_vec(),
left,
left_transform,
right,
right_transform,
),
})
}
pub fn intersection_complex<P, PL, PR, TL, TR>(
&self,
strategy: &CollisionStrategy,
left: &[(PL, TL)],
left_transform: &TL,
right: &[(PR, TR)],
right_transform: &TR,
) -> Option<Contact<P>>
where
P: EuclideanSpace<Scalar = S>,
P::Diff: Neg<Output = P::Diff> + InnerSpace + Zero + Array<Element = S>,
PL: Primitive<Point = P>,
PR: Primitive<Point = P>,
TL: Transform<P>,
TR: Transform<P>,
SP: SimplexProcessor<Point = P>,
{
use CollisionStrategy::*;
let mut contacts = Vec::default();
for &(ref left_primitive, ref left_local_transform) in left.iter() {
let left_transform = left_transform.concat(left_local_transform);
for &(ref right_primitive, ref right_local_transform) in right.iter() {
let right_transform = right_transform.concat(right_local_transform);
if let Some(contact) = self.intersection(
strategy,
left_primitive,
&left_transform,
right_primitive,
&right_transform,
) {
match *strategy {
CollisionOnly => {
return Some(contact);
}
FullResolution => contacts.push(contact),
}
}
}
}
contacts.into_iter().max_by(|l, r| {
l.penetration_depth
.partial_cmp(&r.penetration_depth)
.unwrap()
})
}
pub fn distance_complex<P, PL, PR, TL, TR>(
&self,
left: &[(PL, TL)],
left_transform: &TL,
right: &[(PR, TR)],
right_transform: &TR,
) -> Option<S>
where
P: EuclideanSpace<Scalar = S>,
P::Diff: Neg<Output = P::Diff> + InnerSpace + Zero + Array<Element = S>,
PL: Primitive<Point = P>,
PR: Primitive<Point = P>,
TL: Transform<P>,
TR: Transform<P>,
SP: SimplexProcessor<Point = P>,
{
let mut min_distance = None;
for &(ref left_primitive, ref left_local_transform) in left.iter() {
let left_transform = left_transform.concat(left_local_transform);
for &(ref right_primitive, ref right_local_transform) in right.iter() {
let right_transform = right_transform.concat(right_local_transform);
match self.distance(
left_primitive,
&left_transform,
right_primitive,
&right_transform,
) {
None => return None,
Some(distance) => {
min_distance = Some(
min_distance
.map_or(distance, |min_distance| distance.min(min_distance)),
)
}
}
}
}
min_distance
}
pub fn intersection_complex_time_of_impact<P, PL, PR, TL, TR>(
&self,
strategy: &CollisionStrategy,
left: &[(PL, TL)],
left_transform: Range<&TL>,
right: &[(PR, TR)],
right_transform: Range<&TR>,
) -> Option<Contact<P>>
where
P: EuclideanSpace<Scalar = S>,
P::Diff: Neg<Output = P::Diff> + InnerSpace + Zero + Array<Element = S>,
PL: Primitive<Point = P>,
PR: Primitive<Point = P>,
TL: Transform<P> + TranslationInterpolate<S>,
TR: Transform<P> + TranslationInterpolate<S>,
SP: SimplexProcessor<Point = P>,
{
use CollisionStrategy::*;
let mut contacts = Vec::default();
for &(ref left_primitive, ref left_local_transform) in left.iter() {
let left_start_transform = left_transform.start.concat(left_local_transform);
let left_end_transform = left_transform.end.concat(left_local_transform);
for &(ref right_primitive, ref right_local_transform) in right.iter() {
let right_start_transform = right_transform.start.concat(right_local_transform);
let right_end_transform = right_transform.end.concat(right_local_transform);
if let Some(mut contact) = self.intersection_time_of_impact(
left_primitive,
&left_start_transform..&left_end_transform,
right_primitive,
&right_start_transform..&right_end_transform,
) {
match *strategy {
CollisionOnly => {
contact.strategy = CollisionOnly;
return Some(contact);
}
FullResolution => contacts.push(contact),
}
}
}
}
contacts.into_iter().min_by(|l, r| {
l.time_of_impact
.partial_cmp(&r.time_of_impact)
.unwrap_or(Ordering::Equal)
})
}
}
#[cfg(test)]
mod tests {
use cgmath::{Basis2, Decomposed, Point2, Point3, Quaternion, Rad, Rotation2, Rotation3,
Vector2, Vector3};
use approx::assert_ulps_eq;
use super::*;
use crate::primitive::*;
fn transform(x: f32, y: f32, angle: f32) -> Decomposed<Vector2<f32>, Basis2<f32>> {
Decomposed {
disp: Vector2::new(x, y),
rot: Rotation2::from_angle(Rad(angle)),
scale: 1.,
}
}
fn transform_3d(
x: f32,
y: f32,
z: f32,
angle_z: f32,
) -> Decomposed<Vector3<f32>, Quaternion<f32>> {
Decomposed {
disp: Vector3::new(x, y, z),
rot: Quaternion::from_angle_z(Rad(angle_z)),
scale: 1.,
}
}
#[test]
fn test_gjk_exact() {
let shape = Rectangle::new(1., 1.);
let t = transform(0., 0., 0.);
let gjk = GJK2::new();
let p = gjk.intersection(&CollisionStrategy::FullResolution, &shape, &t, &shape, &t);
assert!(p.is_some());
let d = gjk.distance(&shape, &t, &shape, &t);
assert!(d.is_none());
}
#[test]
fn test_gjk_exact_3d() {
let shape = Cuboid::new(1., 1., 1.);
let t = transform_3d(0., 0., 0., 0.);
let gjk = GJK3::new();
let p = gjk.intersection(&CollisionStrategy::FullResolution, &shape, &t, &shape, &t);
assert!(p.is_some());
let d = gjk.distance(&shape, &t, &shape, &t);
assert!(d.is_none());
}
#[test]
fn test_gjk_sphere() {
let shape = Sphere::new(1.);
let t = transform_3d(0., 0., 0., 0.);
let gjk = GJK3::new();
let p = gjk.intersection(&CollisionStrategy::FullResolution, &shape, &t, &shape, &t);
assert!(p.is_some());
let d = gjk.distance(&shape, &t, &shape, &t);
assert!(d.is_none());
}
#[test]
fn test_gjk_miss() {
let left = Rectangle::new(10., 10.);
let left_transform = transform(15., 0., 0.);
let right = Rectangle::new(10., 10.);
let right_transform = transform(-15., 0., 0.);
let gjk = GJK2::new();
assert!(
gjk.intersect(&left, &left_transform, &right, &right_transform)
.is_none()
);
assert!(gjk.intersection(
&CollisionStrategy::FullResolution,
&left,
&left_transform,
&right,
&right_transform
).is_none())
}
#[test]
fn test_gjk_hit() {
let left = Rectangle::new(10., 10.);
let left_transform = transform(15., 0., 0.);
let right = Rectangle::new(10., 10.);
let right_transform = transform(7., 2., 0.);
let gjk = GJK2::new();
let simplex = gjk.intersect(&left, &left_transform, &right, &right_transform);
assert!(simplex.is_some());
let contact = gjk.intersection(
&CollisionStrategy::FullResolution,
&left,
&left_transform,
&right,
&right_transform,
);
assert!(contact.is_some());
let contact = contact.unwrap();
assert_eq!(Vector2::new(-1., 0.), contact.normal);
assert_eq!(2., contact.penetration_depth);
assert_eq!(Point2::new(10., 1.), contact.contact_point);
}
#[test]
fn test_gjk_3d_hit() {
let left = Cuboid::new(10., 10., 10.);
let left_transform = transform_3d(15., 0., 0., 0.);
let right = Cuboid::new(10., 10., 10.);
let right_transform = transform_3d(7., 2., 0., 0.);
let gjk = GJK3::new();
let simplex = gjk.intersect(&left, &left_transform, &right, &right_transform);
assert!(simplex.is_some());
let contact = gjk.intersection(
&CollisionStrategy::FullResolution,
&left,
&left_transform,
&right,
&right_transform,
);
assert!(contact.is_some());
let contact = contact.unwrap();
assert_eq!(Vector3::new(-1., 0., 0.), contact.normal);
assert_eq!(2., contact.penetration_depth);
assert_ulps_eq!(Point3::new(10., 1., 5.), contact.contact_point);
}
#[test]
fn test_gjk_distance_2d() {
let left = Rectangle::new(10., 10.);
let left_transform = transform(15., 0., 0.);
let right = Rectangle::new(10., 10.);
let right_transform = transform(0., 0., 0.);
let gjk = GJK2::new();
assert_eq!(
Some(5.),
gjk.distance(&left, &left_transform, &right, &right_transform)
);
let right_transform = transform(7., 2., 0.);
assert_eq!(
None,
gjk.distance(&left, &left_transform, &right, &right_transform)
);
}
#[test]
fn test_gjk_distance_3d() {
let left = Cuboid::new(10., 10., 10.);
let left_transform = transform_3d(15., 0., 0., 0.);
let right = Cuboid::new(10., 10., 10.);
let right_transform = transform_3d(7., 2., 0., 0.);
let gjk = GJK3::new();
assert_eq!(
None,
gjk.distance(&left, &left_transform, &right, &right_transform)
);
let right_transform = transform_3d(1., 0., 0., 0.);
assert_eq!(
Some(4.),
gjk.distance(&left, &left_transform, &right, &right_transform)
);
}
#[test]
fn test_gjk_time_of_impact_2d() {
let left = Rectangle::new(10., 20.);
let left_start_transform = transform(0., 0., 0.);
let left_end_transform = transform(30., 0., 0.);
let right = Rectangle::new(10., 11.);
let right_transform = transform(15., 0., 0.);
let gjk = GJK2::new();
let contact = gjk.intersection_time_of_impact(
&left,
&left_start_transform..&left_end_transform,
&right,
&right_transform..&right_transform,
).unwrap();
assert_ulps_eq!(0.1666667, contact.time_of_impact);
assert_eq!(Vector2::new(-1., 0.), contact.normal);
assert_eq!(0., contact.penetration_depth);
assert_eq!(Point2::new(10., 0.), contact.contact_point);
assert!(gjk.intersection_time_of_impact(
&left,
&left_start_transform..&left_start_transform,
&right,
&right_transform..&right_transform
).is_none());
}
#[test]
fn test_gjk_time_of_impact_3d() {
let left = Cuboid::new(10., 11., 10.);
let left_start_transform = transform_3d(0., 0., 0., 0.);
let left_end_transform = transform_3d(30., 0., 0., 0.);
let right = Cuboid::new(10., 15., 10.);
let right_transform = transform_3d(15., 0., 0., 0.);
let gjk = GJK3::new();
let contact = gjk.intersection_time_of_impact(
&left,
&left_start_transform..&left_end_transform,
&right,
&right_transform..&right_transform,
).unwrap();
assert_ulps_eq!(0.1666667, contact.time_of_impact);
assert_eq!(Vector3::new(-1., 0., 0.), contact.normal);
assert_eq!(0., contact.penetration_depth);
assert_eq!(Point3::new(10., 0., 0.), contact.contact_point);
assert!(gjk.intersection_time_of_impact(
&left,
&left_start_transform..&left_start_transform,
&right,
&right_transform..&right_transform
).is_none());
}
}