ncollide3d/query/algorithms/
gjk.rs
1use na::{self, Unit};
4use simba::scalar::RealField;
5
6use crate::query::algorithms::{special_support_maps::ConstantOrigin, CSOPoint, VoronoiSimplex};
7use crate::shape::SupportMap;
8use crate::math::{Isometry, Point, Vector, DIM};
10use crate::query::{self, Ray};
11
12#[derive(Clone, Debug, PartialEq)]
14pub enum GJKResult<N: RealField + Copy> {
15 Intersection,
17 ClosestPoints(Point<N>, Point<N>, Unit<Vector<N>>),
19 Proximity(Unit<Vector<N>>),
21 NoIntersection(Unit<Vector<N>>),
23}
24
25pub fn eps_tol<N: RealField + Copy>() -> N {
27 let _eps = N::default_epsilon();
28 _eps * na::convert(10.0f64)
29}
30
31pub fn project_origin<N, G: ?Sized>(
38 m: &Isometry<N>,
39 g: &G,
40 simplex: &mut VoronoiSimplex<N>,
41) -> Option<Point<N>>
42where
43 N: RealField + Copy,
44 G: SupportMap<N>,
45{
46 match closest_points(
47 m,
48 g,
49 &Isometry::identity(),
50 &ConstantOrigin,
51 N::max_value().unwrap(),
52 true,
53 simplex,
54 ) {
55 GJKResult::Intersection => None,
56 GJKResult::ClosestPoints(p, _, _) => Some(p),
57 _ => unreachable!(),
58 }
59}
60
61pub fn closest_points<N, G1: ?Sized, G2: ?Sized>(
77 m1: &Isometry<N>,
78 g1: &G1,
79 m2: &Isometry<N>,
80 g2: &G2,
81 max_dist: N,
82 exact_dist: bool,
83 simplex: &mut VoronoiSimplex<N>,
84) -> GJKResult<N>
85where
86 N: RealField + Copy,
87 G1: SupportMap<N>,
88 G2: SupportMap<N>,
89{
90 let _eps = N::default_epsilon();
91 let _eps_tol: N = eps_tol();
92 let _eps_rel: N = _eps_tol.sqrt();
93
94 let mut proj = simplex.project_origin_and_reduce();
96
97 let mut old_dir;
98
99 if let Some(proj_dir) = Unit::try_new(proj.coords, N::zero()) {
100 old_dir = -proj_dir;
101 } else {
102 return GJKResult::Intersection;
103 }
104
105 let mut max_bound = N::max_value().unwrap();
106 let mut dir;
107 let mut niter = 0;
108
109 loop {
110 let old_max_bound = max_bound;
111
112 if let Some((new_dir, dist)) = Unit::try_new_and_get(-proj.coords, _eps_tol) {
113 dir = new_dir;
114 max_bound = dist;
115 } else {
116 return GJKResult::Intersection;
118 }
119
120 if max_bound >= old_max_bound {
121 if exact_dist {
122 let (p1, p2) = result(simplex, true);
123 return GJKResult::ClosestPoints(p1, p2, old_dir); } else {
125 return GJKResult::Proximity(old_dir);
126 }
127 }
128
129 let cso_point = CSOPoint::from_shapes(m1, g1, m2, g2, &dir);
130 let min_bound = -dir.dot(&cso_point.point.coords);
131
132 assert!(min_bound == min_bound);
133
134 if min_bound > max_dist {
135 return GJKResult::NoIntersection(dir);
136 } else if !exact_dist && min_bound > na::zero() && max_bound <= max_dist {
137 return GJKResult::Proximity(old_dir);
138 } else if max_bound - min_bound <= _eps_rel * max_bound {
139 if exact_dist {
140 let (p1, p2) = result(simplex, false);
141 return GJKResult::ClosestPoints(p1, p2, dir); } else {
143 return GJKResult::Proximity(dir);
144 }
145 }
146
147 if !simplex.add_point(cso_point) {
148 if exact_dist {
149 let (p1, p2) = result(simplex, false);
150 return GJKResult::ClosestPoints(p1, p2, dir);
151 } else {
152 return GJKResult::Proximity(dir);
153 }
154 }
155
156 old_dir = dir;
157 proj = simplex.project_origin_and_reduce();
158
159 if simplex.dimension() == DIM {
160 if min_bound >= _eps_tol {
161 if exact_dist {
162 let (p1, p2) = result(simplex, true);
163 return GJKResult::ClosestPoints(p1, p2, old_dir);
164 } else {
165 return GJKResult::Proximity(old_dir);
167 }
168 } else {
169 return GJKResult::Intersection; }
171 }
172 niter += 1;
173 if niter == 10000 {
174 return GJKResult::NoIntersection(Vector::x_axis());
175 }
176 }
177}
178
179pub fn cast_ray<N, G: ?Sized>(
181 m: &Isometry<N>,
182 shape: &G,
183 simplex: &mut VoronoiSimplex<N>,
184 ray: &Ray<N>,
185 max_toi: N,
186) -> Option<(N, Vector<N>)>
187where
188 N: RealField + Copy,
189 G: SupportMap<N>,
190{
191 let m2 = Isometry::identity();
192 let g2 = ConstantOrigin;
193 minkowski_ray_cast(m, shape, &m2, &g2, ray, max_toi, simplex)
194}
195
196pub fn directional_distance<N, G1: ?Sized, G2: ?Sized>(
199 m1: &Isometry<N>,
200 g1: &G1,
201 m2: &Isometry<N>,
202 g2: &G2,
203 dir: &Vector<N>,
204 simplex: &mut VoronoiSimplex<N>,
205) -> Option<(N, Vector<N>, Point<N>, Point<N>)>
206where
207 N: RealField + Copy,
208 G1: SupportMap<N>,
209 G2: SupportMap<N>,
210{
211 let ray = Ray::new(Point::origin(), *dir);
212 minkowski_ray_cast(m1, g1, m2, g2, &ray, N::max_value().unwrap(), simplex).map(
213 |(toi, normal)| {
214 let witnesses = if !toi.is_zero() {
215 result(simplex, simplex.dimension() == DIM)
216 } else {
217 (Point::origin(), Point::origin())
220 };
221
222 (toi, normal, witnesses.0, witnesses.1)
223 },
224 )
225}
226
227fn minkowski_ray_cast<N, G1: ?Sized, G2: ?Sized>(
229 m1: &Isometry<N>,
230 g1: &G1,
231 m2: &Isometry<N>,
232 g2: &G2,
233 ray: &Ray<N>,
234 max_toi: N,
235 simplex: &mut VoronoiSimplex<N>,
236) -> Option<(N, Vector<N>)>
237where
238 N: RealField + Copy,
239 G1: SupportMap<N>,
240 G2: SupportMap<N>,
241{
242 let _eps = N::default_epsilon();
243 let _eps_tol: N = eps_tol();
244 let _eps_rel: N = _eps_tol.sqrt();
245
246 let ray_length = ray.dir.norm();
247
248 if relative_eq!(ray_length, N::zero()) {
249 return None;
250 }
251
252 let mut ltoi = N::zero();
253 let mut curr_ray = Ray::new(ray.origin, ray.dir / ray_length);
254 let dir = -curr_ray.dir;
255 let mut ldir = dir;
256
257 let support_point = CSOPoint::from_shapes(m1, g1, m2, g2, &dir);
259 simplex.reset(support_point.translate(&-curr_ray.origin.coords));
260
261 let mut proj = simplex.project_origin_and_reduce();
263 let mut max_bound = N::max_value().unwrap();
264 let mut dir;
265 let mut niter = 0;
266 let mut last_chance = false;
267
268 loop {
269 let old_max_bound = max_bound;
270
271 if let Some((new_dir, dist)) = Unit::try_new_and_get(-proj.coords, _eps_tol) {
272 dir = new_dir;
273 max_bound = dist;
274 } else {
275 return Some((ltoi / ray_length, ldir));
276 }
277
278 let support_point = if max_bound >= old_max_bound {
279 last_chance = true;
281 CSOPoint::single_point(proj + curr_ray.origin.coords)
282 } else {
283 CSOPoint::from_shapes(m1, g1, m2, g2, &dir)
284 };
285
286 if last_chance && ltoi > N::zero() {
287 return Some((ltoi / ray_length, ldir));
289 }
290
291 match query::ray_toi_with_plane(&support_point.point, &dir, &curr_ray) {
300 Some(t) => {
301 if dir.dot(&curr_ray.dir) < na::zero() && t > N::zero() {
302 ldir = *dir;
304 ltoi += t;
305
306 if ltoi / ray_length > max_toi {
310 return None;
311 }
312
313 let shift = curr_ray.dir * t;
314 curr_ray.origin += shift;
315 max_bound = N::max_value().unwrap();
316 simplex.modify_pnts(&|pt| pt.translate_mut(&-shift));
317 last_chance = false;
318 }
319 }
320 None => {
321 if dir.dot(&curr_ray.dir) > _eps_tol {
322 return None;
324 }
325 }
326 }
327
328 if last_chance {
329 return None;
330 }
331
332 let min_bound = -dir.dot(&(support_point.point.coords - curr_ray.origin.coords));
333
334 assert!(min_bound == min_bound);
335
336 if max_bound - min_bound <= _eps_rel * max_bound {
337 if cfg!(feature = "improved_fixed_point_support") {
343 return Some((ltoi / ray_length, ldir));
344 } else {
345 return None;
346 }
347 }
348
349 let _ = simplex.add_point(support_point.translate(&-curr_ray.origin.coords));
350 proj = simplex.project_origin_and_reduce();
351
352 if simplex.dimension() == DIM {
353 if min_bound >= _eps_tol {
354 return None;
355 } else {
356 return Some((ltoi / ray_length, ldir)); }
358 }
359
360 niter += 1;
361 if niter == 10000 {
362 return None;
363 }
364 }
365}
366
367fn result<N: RealField + Copy>(simplex: &VoronoiSimplex<N>, prev: bool) -> (Point<N>, Point<N>) {
368 let mut res = (Point::origin(), Point::origin());
369 if prev {
370 for i in 0..simplex.prev_dimension() + 1 {
371 let coord = simplex.prev_proj_coord(i);
372 let point = simplex.prev_point(i);
373 res.0 += point.orig1.coords * coord;
374 res.1 += point.orig2.coords * coord;
375 }
376
377 res
378 } else {
379 for i in 0..simplex.dimension() + 1 {
380 let coord = simplex.proj_coord(i);
381 let point = simplex.point(i);
382 res.0 += point.orig1.coords * coord;
383 res.1 += point.orig2.coords * coord;
384 }
385
386 res
387 }
388}