use alga::general::RealField;
use na::{self, Unit};
use crate::query::algorithms::{special_support_maps::ConstantOrigin, CSOPoint, VoronoiSimplex};
use crate::shape::SupportMap;
use crate::math::{Isometry, Point, Vector, DIM};
use crate::query::{self, Ray};
#[derive(Clone, Debug, PartialEq)]
pub enum GJKResult<N: RealField> {
Intersection,
ClosestPoints(Point<N>, Point<N>, Unit<Vector<N>>),
Proximity(Unit<Vector<N>>),
NoIntersection(Unit<Vector<N>>),
}
pub fn eps_tol<N: RealField>() -> N {
let _eps = N::default_epsilon();
_eps * na::convert(10.0f64)
}
pub fn project_origin<N, G: ?Sized>(
m: &Isometry<N>,
g: &G,
simplex: &mut VoronoiSimplex<N>,
) -> Option<Point<N>>
where
N: RealField,
G: SupportMap<N>,
{
match closest_points(
m,
g,
&Isometry::identity(),
&ConstantOrigin,
N::max_value(),
true,
simplex,
) {
GJKResult::Intersection => None,
GJKResult::ClosestPoints(p, _, _) => Some(p),
_ => unreachable!(),
}
}
pub fn closest_points<N, G1: ?Sized, G2: ?Sized>(
m1: &Isometry<N>,
g1: &G1,
m2: &Isometry<N>,
g2: &G2,
max_dist: N,
exact_dist: bool,
simplex: &mut VoronoiSimplex<N>,
) -> GJKResult<N>
where
N: RealField,
G1: SupportMap<N>,
G2: SupportMap<N>,
{
let _eps = N::default_epsilon();
let _eps_tol: N = eps_tol();
let _eps_rel: N = _eps_tol.sqrt();
let mut proj = simplex.project_origin_and_reduce();
let mut old_dir = -Unit::new_normalize(proj.coords);
let mut max_bound = N::max_value();
let mut dir;
let mut niter = 0;
loop {
let old_max_bound = max_bound;
if let Some((new_dir, dist)) = Unit::try_new_and_get(-proj.coords, _eps_tol) {
dir = new_dir;
max_bound = dist;
} else {
return GJKResult::Intersection;
}
if max_bound >= old_max_bound {
if exact_dist {
let (p1, p2) = result(simplex, true);
return GJKResult::ClosestPoints(p1, p2, old_dir);
} else {
return GJKResult::Proximity(old_dir);
}
}
let cso_point = CSOPoint::from_shapes(m1, g1, m2, g2, &dir);
let min_bound = -dir.dot(&cso_point.point.coords);
assert!(min_bound == min_bound);
if min_bound > max_dist {
return GJKResult::NoIntersection(dir);
} else if !exact_dist && min_bound > na::zero() && max_bound <= max_dist {
return GJKResult::Proximity(old_dir);
} else if max_bound - min_bound <= _eps_rel * max_bound {
if exact_dist {
let (p1, p2) = result(simplex, false);
return GJKResult::ClosestPoints(p1, p2, dir);
} else {
return GJKResult::Proximity(dir);
}
}
if !simplex.add_point(cso_point) {
if exact_dist {
let (p1, p2) = result(simplex, false);
return GJKResult::ClosestPoints(p1, p2, dir);
} else {
return GJKResult::Proximity(dir);
}
}
old_dir = dir;
proj = simplex.project_origin_and_reduce();
if simplex.dimension() == DIM {
if min_bound >= _eps_tol {
if exact_dist {
let (p1, p2) = result(simplex, true);
return GJKResult::ClosestPoints(p1, p2, old_dir);
} else {
return GJKResult::Proximity(old_dir);
}
} else {
return GJKResult::Intersection;
}
}
niter += 1;
if niter == 10000 {
return GJKResult::NoIntersection(Vector::x_axis());
}
}
}
pub fn cast_ray<N, G: ?Sized>(
m: &Isometry<N>,
shape: &G,
simplex: &mut VoronoiSimplex<N>,
ray: &Ray<N>,
max_toi: N,
) -> Option<(N, Vector<N>)>
where
N: RealField,
G: SupportMap<N>,
{
let m2 = Isometry::identity();
let g2 = ConstantOrigin;
minkowski_ray_cast(m, shape, &m2, &g2, ray, max_toi, simplex)
}
pub fn directional_distance<N, G1: ?Sized, G2: ?Sized>(
m1: &Isometry<N>,
g1: &G1,
m2: &Isometry<N>,
g2: &G2,
dir: &Vector<N>,
simplex: &mut VoronoiSimplex<N>,
) -> Option<(N, Vector<N>, Point<N>, Point<N>)>
where
N: RealField,
G1: SupportMap<N>,
G2: SupportMap<N>,
{
let ray = Ray::new(Point::origin(), *dir);
minkowski_ray_cast(m1, g1, m2, g2, &ray, N::max_value(), simplex).map(|(toi, normal)| {
let witnesses = if !toi.is_zero() {
result(simplex, simplex.dimension() == DIM)
} else {
(Point::origin(), Point::origin())
};
(toi, normal, witnesses.0, witnesses.1)
})
}
fn minkowski_ray_cast<N, G1: ?Sized, G2: ?Sized>(
m1: &Isometry<N>,
g1: &G1,
m2: &Isometry<N>,
g2: &G2,
ray: &Ray<N>,
max_toi: N,
simplex: &mut VoronoiSimplex<N>,
) -> Option<(N, Vector<N>)>
where
N: RealField,
G1: SupportMap<N>,
G2: SupportMap<N>,
{
let _eps = N::default_epsilon();
let _eps_tol: N = eps_tol();
let _eps_rel: N = _eps_tol.sqrt();
let ray_length = ray.dir.norm();
if relative_eq!(ray_length, N::zero()) {
return None;
}
let mut ltoi = N::zero();
let mut curr_ray = Ray::new(ray.origin, ray.dir / ray_length);
let max_toi = max_toi * ray_length;
let dir = -curr_ray.dir;
let mut ldir = dir;
let support_point = CSOPoint::from_shapes(m1, g1, m2, g2, &dir);
simplex.reset(support_point.translate(&-curr_ray.origin.coords));
let mut proj = simplex.project_origin_and_reduce();
let mut max_bound = N::max_value();
let mut dir;
let mut niter = 0;
let mut last_chance = false;
loop {
let old_max_bound = max_bound;
if let Some((new_dir, dist)) = Unit::try_new_and_get(-proj.coords, _eps_tol) {
dir = new_dir;
max_bound = dist;
} else {
return Some((ltoi / ray_length, ldir));
}
let support_point = if max_bound >= old_max_bound {
last_chance = true;
CSOPoint::single_point(proj + curr_ray.origin.coords)
} else {
CSOPoint::from_shapes(m1, g1, m2, g2, &dir)
};
if last_chance && ltoi > N::zero() {
return Some((ltoi / ray_length, ldir));
}
match query::ray_toi_with_plane(&support_point.point, &dir, &curr_ray) {
Some(t) => {
if dir.dot(&curr_ray.dir) < na::zero() && t > N::zero() {
ldir = *dir;
ltoi += t;
if ltoi > max_toi {
return None;
}
let shift = curr_ray.dir * t;
curr_ray.origin += shift;
max_bound = N::max_value();
simplex.modify_pnts(&|pt| pt.translate_mut(&-shift));
last_chance = false;
}
}
None => {
if dir.dot(&curr_ray.dir) > N::default_epsilon() {
return None;
}
}
}
if last_chance {
return None;
}
let min_bound = -dir.dot(&(support_point.point.coords - curr_ray.origin.coords));
assert!(min_bound == min_bound);
if max_bound - min_bound <= _eps_rel * max_bound {
return None;
}
let _ = simplex.add_point(support_point.translate(&-curr_ray.origin.coords));
proj = simplex.project_origin_and_reduce();
if simplex.dimension() == DIM {
if min_bound >= _eps_tol {
return None;
} else {
return Some((ltoi / ray_length, ldir));
}
}
niter += 1;
if niter == 10000 {
return None;
}
}
}
fn result<N: RealField>(simplex: &VoronoiSimplex<N>, prev: bool) -> (Point<N>, Point<N>) {
let mut res = (Point::origin(), Point::origin());
if prev {
for i in 0..simplex.prev_dimension() + 1 {
let coord = simplex.prev_proj_coord(i);
let point = simplex.prev_point(i);
res.0 += point.orig1.coords * coord;
res.1 += point.orig2.coords * coord;
}
res
} else {
for i in 0..simplex.dimension() + 1 {
let coord = simplex.proj_coord(i);
let point = simplex.point(i);
res.0 += point.orig1.coords * coord;
res.1 += point.orig2.coords * coord;
}
res
}
}