2 Bullet Continuous Collision Detection and Physics Library
3 Copyright (c) 2003-2014 Erwin Coumans https://bulletphysics.org
5 This software is provided 'as-is', without any express or implied warranty.
6 In no event will the authors be held liable for any damages arising from the
8 Permission is granted to anyone to use this software for any purpose,
9 including commercial applications, and to alter it and redistribute it
11 subject to the following restrictions:
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software in a
15 product, an acknowledgment in the product documentation would be appreciated
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
23 Initial GJK-EPA collision solver by Nathanael Presson, 2008
24 Improvements and refactoring by Erwin Coumans, 2008-2014
29 #include "LinearMath/btTransform.h"
30 #include "btGjkCollisionDescription.h"
32 struct btGjkEpaSolver3
38 Separated, /* Shapes doesnt penetrate */
39 Penetrating, /* Shapes are penetrating */
40 GJK_Failed, /* GJK phase fail, no big issue, shapes are probably just 'touching' */
41 EPA_Failed /* EPA phase fail, bigger problem, need to save parameters, and debug */
43 btVector3 witnesses[2];
49 #if defined(DEBUG) || defined(_DEBUG)
50 #include <stdio.h> //for debug printf
52 #include <spu_printf.h>
53 #define printf spu_printf
60 #define GJK_MAX_ITERATIONS 128
61 #define GJK_ACCURARY ((btScalar)0.0001)
62 #define GJK_MIN_DISTANCE ((btScalar)0.0001)
63 #define GJK_DUPLICATED_EPS ((btScalar)0.0001)
64 #define GJK_SIMPLEX2_EPS ((btScalar)0.0)
65 #define GJK_SIMPLEX3_EPS ((btScalar)0.0)
66 #define GJK_SIMPLEX4_EPS ((btScalar)0.0)
69 #define EPA_MAX_VERTICES 64
70 #define EPA_MAX_FACES (EPA_MAX_VERTICES * 2)
71 #define EPA_MAX_ITERATIONS 255
72 #define EPA_ACCURACY ((btScalar)0.0001)
73 #define EPA_FALLBACK (10 * EPA_ACCURACY)
74 #define EPA_PLANE_EPS ((btScalar)0.00001)
75 #define EPA_INSIDE_EPS ((btScalar)0.01)
78 typedef unsigned int U;
79 typedef unsigned char U1;
82 template <typename btConvexTemplate>
85 const btConvexTemplate* m_convexAPtr;
86 const btConvexTemplate* m_convexBPtr;
88 btMatrix3x3 m_toshape1;
89 btTransform m_toshape0;
93 MinkowskiDiff(const btConvexTemplate& a, const btConvexTemplate& b)
99 void EnableMargin(bool enable)
101 m_enableMargin = enable;
103 inline btVector3 Support0(const btVector3& d) const
105 return m_convexAPtr->getLocalSupportWithMargin(d);
107 inline btVector3 Support1(const btVector3& d) const
109 return m_toshape0 * m_convexBPtr->getLocalSupportWithMargin(m_toshape1 * d);
112 inline btVector3 Support(const btVector3& d) const
114 return (Support0(d) - Support1(-d));
116 btVector3 Support(const btVector3& d, U index) const
119 return (Support1(d));
121 return (Support0(d));
133 template <typename btConvexTemplate>
150 MinkowskiDiff<btConvexTemplate> m_shape;
153 sSimplex m_simplices[2];
162 GJK(const btConvexTemplate& a, const btConvexTemplate& b)
169 m_ray = btVector3(0, 0, 0);
171 m_status = eGjkFailed;
175 eGjkStatus Evaluate(const MinkowskiDiff<btConvexTemplate>& shapearg, const btVector3& guess)
182 /* Initialize solver */
183 m_free[0] = &m_store[0];
184 m_free[1] = &m_store[1];
185 m_free[2] = &m_store[2];
186 m_free[3] = &m_store[3];
189 m_status = eGjkValid;
192 /* Initialize simplex */
193 m_simplices[0].rank = 0;
195 const btScalar sqrl = m_ray.length2();
196 appendvertice(m_simplices[0], sqrl > 0 ? -m_ray : btVector3(1, 0, 0));
197 m_simplices[0].p[0] = 1;
198 m_ray = m_simplices[0].c[0]->w;
207 const U next = 1 - m_current;
208 sSimplex& cs = m_simplices[m_current];
209 sSimplex& ns = m_simplices[next];
211 const btScalar rl = m_ray.length();
212 if (rl < GJK_MIN_DISTANCE)
213 { /* Touching or inside */
214 m_status = eGjkInside;
217 /* Append new vertice in -'v' direction */
218 appendvertice(cs, -m_ray);
219 const btVector3& w = cs.c[cs.rank - 1]->w;
221 for (U i = 0; i < 4; ++i)
223 if ((w - lastw[i]).length2() < GJK_DUPLICATED_EPS)
230 { /* Return old simplex */
231 removevertice(m_simplices[m_current]);
236 lastw[clastw = (clastw + 1) & 3] = w;
238 /* Check for termination */
239 const btScalar omega = btDot(m_ray, w) / rl;
240 alpha = btMax(omega, alpha);
241 if (((rl - alpha) - (GJK_ACCURARY * rl)) <= 0)
242 { /* Return old simplex */
243 removevertice(m_simplices[m_current]);
252 sqdist = projectorigin(cs.c[0]->w,
257 sqdist = projectorigin(cs.c[0]->w,
263 sqdist = projectorigin(cs.c[0]->w,
273 m_ray = btVector3(0, 0, 0);
275 for (U i = 0, ni = cs.rank; i < ni; ++i)
279 ns.c[ns.rank] = cs.c[i];
280 ns.p[ns.rank++] = weights[i];
281 m_ray += cs.c[i]->w * weights[i];
285 m_free[m_nfree++] = cs.c[i];
288 if (mask == 15) m_status = eGjkInside;
291 { /* Return old simplex */
292 removevertice(m_simplices[m_current]);
295 m_status = ((++iterations) < GJK_MAX_ITERATIONS) ? m_status : eGjkFailed;
296 } while (m_status == eGjkValid);
297 m_simplex = &m_simplices[m_current];
301 m_distance = m_ray.length();
314 switch (m_simplex->rank)
318 for (U i = 0; i < 3; ++i)
320 btVector3 axis = btVector3(0, 0, 0);
322 appendvertice(*m_simplex, axis);
323 if (EncloseOrigin()) return (true);
324 removevertice(*m_simplex);
325 appendvertice(*m_simplex, -axis);
326 if (EncloseOrigin()) return (true);
327 removevertice(*m_simplex);
333 const btVector3 d = m_simplex->c[1]->w - m_simplex->c[0]->w;
334 for (U i = 0; i < 3; ++i)
336 btVector3 axis = btVector3(0, 0, 0);
338 const btVector3 p = btCross(d, axis);
341 appendvertice(*m_simplex, p);
342 if (EncloseOrigin()) return (true);
343 removevertice(*m_simplex);
344 appendvertice(*m_simplex, -p);
345 if (EncloseOrigin()) return (true);
346 removevertice(*m_simplex);
353 const btVector3 n = btCross(m_simplex->c[1]->w - m_simplex->c[0]->w,
354 m_simplex->c[2]->w - m_simplex->c[0]->w);
357 appendvertice(*m_simplex, n);
358 if (EncloseOrigin()) return (true);
359 removevertice(*m_simplex);
360 appendvertice(*m_simplex, -n);
361 if (EncloseOrigin()) return (true);
362 removevertice(*m_simplex);
368 if (btFabs(det(m_simplex->c[0]->w - m_simplex->c[3]->w,
369 m_simplex->c[1]->w - m_simplex->c[3]->w,
370 m_simplex->c[2]->w - m_simplex->c[3]->w)) > 0)
378 void getsupport(const btVector3& d, sSV& sv) const
380 sv.d = d / d.length();
381 sv.w = m_shape.Support(sv.d);
383 void removevertice(sSimplex& simplex)
385 m_free[m_nfree++] = simplex.c[--simplex.rank];
387 void appendvertice(sSimplex& simplex, const btVector3& v)
389 simplex.p[simplex.rank] = 0;
390 simplex.c[simplex.rank] = m_free[--m_nfree];
391 getsupport(v, *simplex.c[simplex.rank++]);
393 static btScalar det(const btVector3& a, const btVector3& b, const btVector3& c)
395 return (a.y() * b.z() * c.x() + a.z() * b.x() * c.y() -
396 a.x() * b.z() * c.y() - a.y() * b.x() * c.z() +
397 a.x() * b.y() * c.z() - a.z() * b.y() * c.x());
399 static btScalar projectorigin(const btVector3& a,
403 const btVector3 d = b - a;
404 const btScalar l = d.length2();
405 if (l > GJK_SIMPLEX2_EPS)
407 const btScalar t(l > 0 ? -btDot(a, d) / l : 0);
413 return (b.length2());
420 return (a.length2());
424 w[0] = 1 - (w[1] = t);
426 return ((a + d * t).length2());
431 static btScalar projectorigin(const btVector3& a,
436 static const U imd3[] = {1, 2, 0};
437 const btVector3* vt[] = {&a, &b, &c};
438 const btVector3 dl[] = {a - b, b - c, c - a};
439 const btVector3 n = btCross(dl[0], dl[1]);
440 const btScalar l = n.length2();
441 if (l > GJK_SIMPLEX3_EPS)
443 btScalar mindist = -1;
444 btScalar subw[2] = {0.f, 0.f};
446 for (U i = 0; i < 3; ++i)
448 if (btDot(*vt[i], btCross(dl[i], n)) > 0)
451 const btScalar subd(projectorigin(*vt[i], *vt[j], subw, subm));
452 if ((mindist < 0) || (subd < mindist))
455 m = static_cast<U>(((subm & 1) ? 1 << i : 0) + ((subm & 2) ? 1 << j : 0));
464 const btScalar d = btDot(a, n);
465 const btScalar s = btSqrt(l);
466 const btVector3 p = n * (d / l);
467 mindist = p.length2();
469 w[0] = (btCross(dl[1], b - p)).length() / s;
470 w[1] = (btCross(dl[2], c - p)).length() / s;
471 w[2] = 1 - (w[0] + w[1]);
477 static btScalar projectorigin(const btVector3& a,
483 static const U imd3[] = {1, 2, 0};
484 const btVector3* vt[] = {&a, &b, &c, &d};
485 const btVector3 dl[] = {a - d, b - d, c - d};
486 const btScalar vl = det(dl[0], dl[1], dl[2]);
487 const bool ng = (vl * btDot(a, btCross(b - c, a - b))) <= 0;
488 if (ng && (btFabs(vl) > GJK_SIMPLEX4_EPS))
490 btScalar mindist = -1;
491 btScalar subw[3] = {0.f, 0.f, 0.f};
493 for (U i = 0; i < 3; ++i)
496 const btScalar s = vl * btDot(d, btCross(dl[i], dl[j]));
499 const btScalar subd = projectorigin(*vt[i], *vt[j], d, subw, subm);
500 if ((mindist < 0) || (subd < mindist))
503 m = static_cast<U>((subm & 1 ? 1 << i : 0) +
504 (subm & 2 ? 1 << j : 0) +
517 w[0] = det(c, b, d) / vl;
518 w[1] = det(a, c, d) / vl;
519 w[2] = det(b, a, d) / vl;
520 w[3] = 1 - (w[0] + w[1] + w[2]);
543 template <typename btConvexTemplate>
552 typename GJK<btConvexTemplate>::sSV* c[3];
562 sList() : root(0), count(0) {}
569 sHorizon() : cf(0), ff(0), nf(0) {}
574 typename GJK<btConvexTemplate>::sSimplex m_result;
577 typename GJK<btConvexTemplate>::sSV m_sv_store[EPA_MAX_VERTICES];
578 sFace m_fc_store[EPA_MAX_FACES];
588 static inline void bind(sFace* fa, U ea, sFace* fb, U eb)
595 static inline void append(sList& list, sFace* face)
598 face->l[1] = list.root;
599 if (list.root) list.root->l[0] = face;
603 static inline void remove(sList& list, sFace* face)
605 if (face->l[1]) face->l[1]->l[0] = face->l[0];
606 if (face->l[0]) face->l[0]->l[1] = face->l[1];
607 if (face == list.root) list.root = face->l[1];
613 m_status = eEpaFailed;
614 m_normal = btVector3(0, 0, 0);
617 for (U i = 0; i < EPA_MAX_FACES; ++i)
619 append(m_stock, &m_fc_store[EPA_MAX_FACES - i - 1]);
622 eEpaStatus Evaluate(GJK<btConvexTemplate>& gjk, const btVector3& guess)
624 typename GJK<btConvexTemplate>::sSimplex& simplex = *gjk.m_simplex;
625 if ((simplex.rank > 1) && gjk.EncloseOrigin())
630 sFace* f = m_hull.root;
634 m_status = eEpaValid;
637 if (gjk.det(simplex.c[0]->w - simplex.c[3]->w,
638 simplex.c[1]->w - simplex.c[3]->w,
639 simplex.c[2]->w - simplex.c[3]->w) < 0)
641 btSwap(simplex.c[0], simplex.c[1]);
642 btSwap(simplex.p[0], simplex.p[1]);
644 /* Build initial hull */
645 sFace* tetra[] = {newface(simplex.c[0], simplex.c[1], simplex.c[2], true),
646 newface(simplex.c[1], simplex.c[0], simplex.c[3], true),
647 newface(simplex.c[2], simplex.c[1], simplex.c[3], true),
648 newface(simplex.c[0], simplex.c[2], simplex.c[3], true)};
649 if (m_hull.count == 4)
651 sFace* best = findbest();
655 bind(tetra[0], 0, tetra[1], 0);
656 bind(tetra[0], 1, tetra[2], 0);
657 bind(tetra[0], 2, tetra[3], 0);
658 bind(tetra[1], 1, tetra[3], 2);
659 bind(tetra[1], 2, tetra[2], 1);
660 bind(tetra[2], 2, tetra[3], 1);
661 m_status = eEpaValid;
662 for (; iterations < EPA_MAX_ITERATIONS; ++iterations)
664 if (m_nextsv < EPA_MAX_VERTICES)
667 typename GJK<btConvexTemplate>::sSV* w = &m_sv_store[m_nextsv++];
669 best->pass = (U1)(++pass);
670 gjk.getsupport(best->n, *w);
671 const btScalar wdist = btDot(best->n, w->w) - best->d;
672 if (wdist > EPA_ACCURACY)
674 for (U j = 0; (j < 3) && valid; ++j)
676 valid &= expand(pass, w,
677 best->f[j], best->e[j],
680 if (valid && (horizon.nf >= 3))
682 bind(horizon.cf, 1, horizon.ff, 2);
683 remove(m_hull, best);
684 append(m_stock, best);
690 m_status = eEpaInvalidHull;
696 m_status = eEpaAccuraryReached;
702 m_status = eEpaOutOfVertices;
706 const btVector3 projection = outer.n * outer.d;
710 m_result.c[0] = outer.c[0];
711 m_result.c[1] = outer.c[1];
712 m_result.c[2] = outer.c[2];
713 m_result.p[0] = btCross(outer.c[1]->w - projection,
714 outer.c[2]->w - projection)
716 m_result.p[1] = btCross(outer.c[2]->w - projection,
717 outer.c[0]->w - projection)
719 m_result.p[2] = btCross(outer.c[0]->w - projection,
720 outer.c[1]->w - projection)
722 const btScalar sum = m_result.p[0] + m_result.p[1] + m_result.p[2];
723 m_result.p[0] /= sum;
724 m_result.p[1] /= sum;
725 m_result.p[2] /= sum;
730 m_status = eEpaFallBack;
732 const btScalar nl = m_normal.length();
734 m_normal = m_normal / nl;
736 m_normal = btVector3(1, 0, 0);
739 m_result.c[0] = simplex.c[0];
743 bool getedgedist(sFace* face, typename GJK<btConvexTemplate>::sSV* a, typename GJK<btConvexTemplate>::sSV* b, btScalar& dist)
745 const btVector3 ba = b->w - a->w;
746 const btVector3 n_ab = btCross(ba, face->n); // Outward facing edge normal direction, on triangle plane
747 const btScalar a_dot_nab = btDot(a->w, n_ab); // Only care about the sign to determine inside/outside, so not normalization required
751 // Outside of edge a->b
753 const btScalar ba_l2 = ba.length2();
754 const btScalar a_dot_ba = btDot(a->w, ba);
755 const btScalar b_dot_ba = btDot(b->w, ba);
759 // Pick distance vertex a
760 dist = a->w.length();
762 else if (b_dot_ba < 0)
764 // Pick distance vertex b
765 dist = b->w.length();
769 // Pick distance to edge a->b
770 const btScalar a_dot_b = btDot(a->w, b->w);
771 dist = btSqrt(btMax((a->w.length2() * b->w.length2() - a_dot_b * a_dot_b) / ba_l2, (btScalar)0));
779 sFace* newface(typename GJK<btConvexTemplate>::sSV* a, typename GJK<btConvexTemplate>::sSV* b, typename GJK<btConvexTemplate>::sSV* c, bool forced)
783 sFace* face = m_stock.root;
784 remove(m_stock, face);
785 append(m_hull, face);
790 face->n = btCross(b->w - a->w, c->w - a->w);
791 const btScalar l = face->n.length();
792 const bool v = l > EPA_ACCURACY;
796 if (!(getedgedist(face, a, b, face->d) ||
797 getedgedist(face, b, c, face->d) ||
798 getedgedist(face, c, a, face->d)))
800 // Origin projects to the interior of the triangle
801 // Use distance to triangle plane
802 face->d = btDot(a->w, face->n) / l;
806 if (forced || (face->d >= -EPA_PLANE_EPS))
811 m_status = eEpaNonConvex;
814 m_status = eEpaDegenerated;
816 remove(m_hull, face);
817 append(m_stock, face);
820 m_status = m_stock.root ? eEpaOutOfVertices : eEpaOutOfFaces;
825 sFace* minf = m_hull.root;
826 btScalar mind = minf->d * minf->d;
827 for (sFace* f = minf->l[1]; f; f = f->l[1])
829 const btScalar sqd = f->d * f->d;
838 bool expand(U pass, typename GJK<btConvexTemplate>::sSV* w, sFace* f, U e, sHorizon& horizon)
840 static const U i1m3[] = {1, 2, 0};
841 static const U i2m3[] = {2, 0, 1};
844 const U e1 = i1m3[e];
845 if ((btDot(f->n, w->w) - f->d) < -EPA_PLANE_EPS)
847 sFace* nf = newface(f->c[e1], f->c[e], w, false);
852 bind(horizon.cf, 1, nf, 2);
862 const U e2 = i2m3[e];
864 if (expand(pass, w, f->f[e1], f->e[e1], horizon) &&
865 expand(pass, w, f->f[e2], f->e[e2], horizon))
877 template <typename btConvexTemplate>
878 static void Initialize(const btConvexTemplate& a, const btConvexTemplate& b,
879 btGjkEpaSolver3::sResults& results,
880 MinkowskiDiff<btConvexTemplate>& shape)
883 results.witnesses[0] =
884 results.witnesses[1] = btVector3(0, 0, 0);
885 results.status = btGjkEpaSolver3::sResults::Separated;
888 shape.m_toshape1 = b.getWorldTransform().getBasis().transposeTimes(a.getWorldTransform().getBasis());
889 shape.m_toshape0 = a.getWorldTransform().inverseTimes(b.getWorldTransform());
897 template <typename btConvexTemplate>
898 bool btGjkEpaSolver3_Distance(const btConvexTemplate& a, const btConvexTemplate& b,
899 const btVector3& guess,
900 btGjkEpaSolver3::sResults& results)
902 MinkowskiDiff<btConvexTemplate> shape(a, b);
903 Initialize(a, b, results, shape);
904 GJK<btConvexTemplate> gjk(a, b);
905 eGjkStatus gjk_status = gjk.Evaluate(shape, guess);
906 if (gjk_status == eGjkValid)
908 btVector3 w0 = btVector3(0, 0, 0);
909 btVector3 w1 = btVector3(0, 0, 0);
910 for (U i = 0; i < gjk.m_simplex->rank; ++i)
912 const btScalar p = gjk.m_simplex->p[i];
913 w0 += shape.Support(gjk.m_simplex->c[i]->d, 0) * p;
914 w1 += shape.Support(-gjk.m_simplex->c[i]->d, 1) * p;
916 results.witnesses[0] = a.getWorldTransform() * w0;
917 results.witnesses[1] = a.getWorldTransform() * w1;
918 results.normal = w0 - w1;
919 results.distance = results.normal.length();
920 results.normal /= results.distance > GJK_MIN_DISTANCE ? results.distance : 1;
925 results.status = gjk_status == eGjkInside ? btGjkEpaSolver3::sResults::Penetrating : btGjkEpaSolver3::sResults::GJK_Failed;
930 template <typename btConvexTemplate>
931 bool btGjkEpaSolver3_Penetration(const btConvexTemplate& a,
932 const btConvexTemplate& b,
933 const btVector3& guess,
934 btGjkEpaSolver3::sResults& results)
936 MinkowskiDiff<btConvexTemplate> shape(a, b);
937 Initialize(a, b, results, shape);
938 GJK<btConvexTemplate> gjk(a, b);
939 eGjkStatus gjk_status = gjk.Evaluate(shape, -guess);
944 EPA<btConvexTemplate> epa;
945 eEpaStatus epa_status = epa.Evaluate(gjk, -guess);
946 if (epa_status != eEpaFailed)
948 btVector3 w0 = btVector3(0, 0, 0);
949 for (U i = 0; i < epa.m_result.rank; ++i)
951 w0 += shape.Support(epa.m_result.c[i]->d, 0) * epa.m_result.p[i];
953 results.status = btGjkEpaSolver3::sResults::Penetrating;
954 results.witnesses[0] = a.getWorldTransform() * w0;
955 results.witnesses[1] = a.getWorldTransform() * (w0 - epa.m_normal * epa.m_depth);
956 results.normal = -epa.m_normal;
957 results.distance = -epa.m_depth;
961 results.status = btGjkEpaSolver3::sResults::EPA_Failed;
965 results.status = btGjkEpaSolver3::sResults::GJK_Failed;
975 int btComputeGjkEpaPenetration2(const btCollisionDescription& colDesc, btDistanceInfo* distInfo)
977 btGjkEpaSolver3::sResults results;
978 btVector3 guess = colDesc.m_firstDir;
980 bool res = btGjkEpaSolver3::Penetration(colDesc.m_objA,colDesc.m_objB,
981 colDesc.m_transformA,colDesc.m_transformB,
982 colDesc.m_localSupportFuncA,colDesc.m_localSupportFuncB,
987 if ((results.status==btGjkEpaSolver3::sResults::Penetrating) || results.status==GJK::eStatus::Inside)
989 //normal could be 'swapped'
991 distInfo->m_distance = results.distance;
992 distInfo->m_normalBtoA = results.normal;
993 btVector3 tmpNormalInB = results.witnesses[1]-results.witnesses[0];
994 btScalar lenSqr = tmpNormalInB.length2();
995 if (lenSqr <= (SIMD_EPSILON*SIMD_EPSILON))
997 tmpNormalInB = results.normal;
998 lenSqr = results.normal.length2();
1001 if (lenSqr > (SIMD_EPSILON*SIMD_EPSILON))
1003 tmpNormalInB /= btSqrt(lenSqr);
1004 btScalar distance2 = -(results.witnesses[0]-results.witnesses[1]).length();
1005 //only replace valid penetrations when the result is deeper (check)
1006 //if ((distance2 < results.distance))
1008 distInfo->m_distance = distance2;
1009 distInfo->m_pointOnA= results.witnesses[0];
1010 distInfo->m_pointOnB= results.witnesses[1];
1011 distInfo->m_normalBtoA= tmpNormalInB;
1023 template <typename btConvexTemplate, typename btDistanceInfoTemplate>
1024 int btComputeGjkDistance(const btConvexTemplate& a, const btConvexTemplate& b,
1025 const btGjkCollisionDescription& colDesc, btDistanceInfoTemplate* distInfo)
1027 btGjkEpaSolver3::sResults results;
1028 btVector3 guess = colDesc.m_firstDir;
1030 bool isSeparated = btGjkEpaSolver3_Distance(a, b,
1035 distInfo->m_distance = results.distance;
1036 distInfo->m_pointOnA = results.witnesses[0];
1037 distInfo->m_pointOnB = results.witnesses[1];
1038 distInfo->m_normalBtoA = results.normal;
1045 /* Symbols cleanup */
1047 #undef GJK_MAX_ITERATIONS
1049 #undef GJK_MIN_DISTANCE
1050 #undef GJK_DUPLICATED_EPS
1051 #undef GJK_SIMPLEX2_EPS
1052 #undef GJK_SIMPLEX3_EPS
1053 #undef GJK_SIMPLEX4_EPS
1055 #undef EPA_MAX_VERTICES
1056 #undef EPA_MAX_FACES
1057 #undef EPA_MAX_ITERATIONS
1060 #undef EPA_PLANE_EPS
1061 #undef EPA_INSIDE_EPS
1063 #endif //BT_GJK_EPA3_H