468 lines
14 KiB
C++
468 lines
14 KiB
C++
/*
|
|
Bullet Continuous Collision Detection and Physics Library
|
|
Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/
|
|
|
|
This software is provided 'as-is', without any express or implied warranty.
|
|
In no event will the authors be held liable for any damages arising from the use of this software.
|
|
Permission is granted to anyone to use this software for any purpose,
|
|
including commercial applications, and to alter it and redistribute it freely,
|
|
subject to the following restrictions:
|
|
|
|
1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
|
|
2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
|
|
3. This notice may not be removed or altered from any source distribution.
|
|
*/
|
|
|
|
#include "btGjkPairDetector.h"
|
|
#include "BulletCollision/CollisionShapes/btConvexShape.h"
|
|
#include "BulletCollision/NarrowPhaseCollision/btSimplexSolverInterface.h"
|
|
#include "BulletCollision/NarrowPhaseCollision/btConvexPenetrationDepthSolver.h"
|
|
|
|
|
|
|
|
#if defined(DEBUG) || defined (_DEBUG)
|
|
//#define TEST_NON_VIRTUAL 1
|
|
#include <stdio.h> //for debug printf
|
|
#ifdef __SPU__
|
|
#include <spu_printf.h>
|
|
#define printf spu_printf
|
|
#endif //__SPU__
|
|
#endif
|
|
|
|
//must be above the machine epsilon
|
|
#ifdef BT_USE_DOUBLE_PRECISION
|
|
#define REL_ERROR2 btScalar(1.0e-12)
|
|
btScalar gGjkEpaPenetrationTolerance = 1e-7;
|
|
#else
|
|
#define REL_ERROR2 btScalar(1.0e-6)
|
|
btScalar gGjkEpaPenetrationTolerance = 0.001;
|
|
#endif
|
|
|
|
//temp globals, to improve GJK/EPA/penetration calculations
|
|
int gNumDeepPenetrationChecks = 0;
|
|
int gNumGjkChecks = 0;
|
|
|
|
|
|
btGjkPairDetector::btGjkPairDetector(const btConvexShape* objectA,const btConvexShape* objectB,btSimplexSolverInterface* simplexSolver,btConvexPenetrationDepthSolver* penetrationDepthSolver)
|
|
:m_cachedSeparatingAxis(btScalar(0.),btScalar(1.),btScalar(0.)),
|
|
m_penetrationDepthSolver(penetrationDepthSolver),
|
|
m_simplexSolver(simplexSolver),
|
|
m_minkowskiA(objectA),
|
|
m_minkowskiB(objectB),
|
|
m_shapeTypeA(objectA->getShapeType()),
|
|
m_shapeTypeB(objectB->getShapeType()),
|
|
m_marginA(objectA->getMargin()),
|
|
m_marginB(objectB->getMargin()),
|
|
m_ignoreMargin(false),
|
|
m_lastUsedMethod(-1),
|
|
m_catchDegeneracies(1),
|
|
m_fixContactNormalDirection(1)
|
|
{
|
|
}
|
|
btGjkPairDetector::btGjkPairDetector(const btConvexShape* objectA,const btConvexShape* objectB,int shapeTypeA,int shapeTypeB,btScalar marginA, btScalar marginB, btSimplexSolverInterface* simplexSolver,btConvexPenetrationDepthSolver* penetrationDepthSolver)
|
|
:m_cachedSeparatingAxis(btScalar(0.),btScalar(1.),btScalar(0.)),
|
|
m_penetrationDepthSolver(penetrationDepthSolver),
|
|
m_simplexSolver(simplexSolver),
|
|
m_minkowskiA(objectA),
|
|
m_minkowskiB(objectB),
|
|
m_shapeTypeA(shapeTypeA),
|
|
m_shapeTypeB(shapeTypeB),
|
|
m_marginA(marginA),
|
|
m_marginB(marginB),
|
|
m_ignoreMargin(false),
|
|
m_lastUsedMethod(-1),
|
|
m_catchDegeneracies(1),
|
|
m_fixContactNormalDirection(1)
|
|
{
|
|
}
|
|
|
|
void btGjkPairDetector::getClosestPoints(const ClosestPointInput& input,Result& output,class btIDebugDraw* debugDraw,bool swapResults)
|
|
{
|
|
(void)swapResults;
|
|
|
|
getClosestPointsNonVirtual(input,output,debugDraw);
|
|
}
|
|
|
|
#ifdef __SPU__
|
|
void btGjkPairDetector::getClosestPointsNonVirtual(const ClosestPointInput& input,Result& output,class btIDebugDraw* debugDraw)
|
|
#else
|
|
void btGjkPairDetector::getClosestPointsNonVirtual(const ClosestPointInput& input, Result& output, class btIDebugDraw* debugDraw)
|
|
#endif
|
|
{
|
|
m_cachedSeparatingDistance = 0.f;
|
|
|
|
btScalar distance=btScalar(0.);
|
|
btVector3 normalInB(btScalar(0.),btScalar(0.),btScalar(0.));
|
|
|
|
btVector3 pointOnA,pointOnB;
|
|
btTransform localTransA = input.m_transformA;
|
|
btTransform localTransB = input.m_transformB;
|
|
btVector3 positionOffset=(localTransA.getOrigin() + localTransB.getOrigin()) * btScalar(0.5);
|
|
localTransA.getOrigin() -= positionOffset;
|
|
localTransB.getOrigin() -= positionOffset;
|
|
|
|
bool check2d = m_minkowskiA->isConvex2d() && m_minkowskiB->isConvex2d();
|
|
|
|
btScalar marginA = m_marginA;
|
|
btScalar marginB = m_marginB;
|
|
|
|
gNumGjkChecks++;
|
|
|
|
//for CCD we don't use margins
|
|
if (m_ignoreMargin)
|
|
{
|
|
marginA = btScalar(0.);
|
|
marginB = btScalar(0.);
|
|
}
|
|
|
|
m_curIter = 0;
|
|
int gGjkMaxIter = 1000;//this is to catch invalid input, perhaps check for #NaN?
|
|
m_cachedSeparatingAxis.setValue(0,1,0);
|
|
|
|
bool isValid = false;
|
|
bool checkSimplex = false;
|
|
bool checkPenetration = true;
|
|
m_degenerateSimplex = 0;
|
|
|
|
m_lastUsedMethod = -1;
|
|
|
|
{
|
|
btScalar squaredDistance = BT_LARGE_FLOAT;
|
|
btScalar delta = btScalar(0.);
|
|
|
|
btScalar margin = marginA + marginB;
|
|
|
|
|
|
|
|
m_simplexSolver->reset();
|
|
|
|
for ( ; ; )
|
|
//while (true)
|
|
{
|
|
|
|
btVector3 seperatingAxisInA = (-m_cachedSeparatingAxis)* input.m_transformA.getBasis();
|
|
btVector3 seperatingAxisInB = m_cachedSeparatingAxis* input.m_transformB.getBasis();
|
|
|
|
|
|
btVector3 pInA = m_minkowskiA->localGetSupportVertexWithoutMarginNonVirtual(seperatingAxisInA);
|
|
btVector3 qInB = m_minkowskiB->localGetSupportVertexWithoutMarginNonVirtual(seperatingAxisInB);
|
|
|
|
btVector3 pWorld = localTransA(pInA);
|
|
btVector3 qWorld = localTransB(qInB);
|
|
|
|
|
|
if (check2d)
|
|
{
|
|
pWorld[2] = 0.f;
|
|
qWorld[2] = 0.f;
|
|
}
|
|
|
|
btVector3 w = pWorld - qWorld;
|
|
delta = m_cachedSeparatingAxis.dot(w);
|
|
|
|
// potential exit, they don't overlap
|
|
if ((delta > btScalar(0.0)) && (delta * delta > squaredDistance * input.m_maximumDistanceSquared))
|
|
{
|
|
m_degenerateSimplex = 10;
|
|
checkSimplex=true;
|
|
//checkPenetration = false;
|
|
break;
|
|
}
|
|
|
|
//exit 0: the new point is already in the simplex, or we didn't come any closer
|
|
if (m_simplexSolver->inSimplex(w))
|
|
{
|
|
m_degenerateSimplex = 1;
|
|
checkSimplex = true;
|
|
break;
|
|
}
|
|
// are we getting any closer ?
|
|
btScalar f0 = squaredDistance - delta;
|
|
btScalar f1 = squaredDistance * REL_ERROR2;
|
|
|
|
if (f0 <= f1)
|
|
{
|
|
if (f0 <= btScalar(0.))
|
|
{
|
|
m_degenerateSimplex = 2;
|
|
} else
|
|
{
|
|
m_degenerateSimplex = 11;
|
|
}
|
|
checkSimplex = true;
|
|
break;
|
|
}
|
|
|
|
//add current vertex to simplex
|
|
m_simplexSolver->addVertex(w, pWorld, qWorld);
|
|
btVector3 newCachedSeparatingAxis;
|
|
|
|
//calculate the closest point to the origin (update vector v)
|
|
if (!m_simplexSolver->closest(newCachedSeparatingAxis))
|
|
{
|
|
m_degenerateSimplex = 3;
|
|
checkSimplex = true;
|
|
break;
|
|
}
|
|
|
|
if(newCachedSeparatingAxis.length2()<REL_ERROR2)
|
|
{
|
|
m_cachedSeparatingAxis = newCachedSeparatingAxis;
|
|
m_degenerateSimplex = 6;
|
|
checkSimplex = true;
|
|
break;
|
|
}
|
|
|
|
btScalar previousSquaredDistance = squaredDistance;
|
|
squaredDistance = newCachedSeparatingAxis.length2();
|
|
#if 0
|
|
///warning: this termination condition leads to some problems in 2d test case see Bullet/Demos/Box2dDemo
|
|
if (squaredDistance>previousSquaredDistance)
|
|
{
|
|
m_degenerateSimplex = 7;
|
|
squaredDistance = previousSquaredDistance;
|
|
checkSimplex = false;
|
|
break;
|
|
}
|
|
#endif //
|
|
|
|
|
|
//redundant m_simplexSolver->compute_points(pointOnA, pointOnB);
|
|
|
|
//are we getting any closer ?
|
|
if (previousSquaredDistance - squaredDistance <= SIMD_EPSILON * previousSquaredDistance)
|
|
{
|
|
// m_simplexSolver->backup_closest(m_cachedSeparatingAxis);
|
|
checkSimplex = true;
|
|
m_degenerateSimplex = 12;
|
|
|
|
break;
|
|
}
|
|
|
|
m_cachedSeparatingAxis = newCachedSeparatingAxis;
|
|
|
|
//degeneracy, this is typically due to invalid/uninitialized worldtransforms for a btCollisionObject
|
|
if (m_curIter++ > gGjkMaxIter)
|
|
{
|
|
#if defined(DEBUG) || defined (_DEBUG)
|
|
|
|
printf("btGjkPairDetector maxIter exceeded:%i\n",m_curIter);
|
|
printf("sepAxis=(%f,%f,%f), squaredDistance = %f, shapeTypeA=%i,shapeTypeB=%i\n",
|
|
m_cachedSeparatingAxis.getX(),
|
|
m_cachedSeparatingAxis.getY(),
|
|
m_cachedSeparatingAxis.getZ(),
|
|
squaredDistance,
|
|
m_minkowskiA->getShapeType(),
|
|
m_minkowskiB->getShapeType());
|
|
|
|
#endif
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
bool check = (!m_simplexSolver->fullSimplex());
|
|
//bool check = (!m_simplexSolver->fullSimplex() && squaredDistance > SIMD_EPSILON * m_simplexSolver->maxVertex());
|
|
|
|
if (!check)
|
|
{
|
|
//do we need this backup_closest here ?
|
|
// m_simplexSolver->backup_closest(m_cachedSeparatingAxis);
|
|
m_degenerateSimplex = 13;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (checkSimplex)
|
|
{
|
|
m_simplexSolver->compute_points(pointOnA, pointOnB);
|
|
normalInB = m_cachedSeparatingAxis;
|
|
|
|
btScalar lenSqr =m_cachedSeparatingAxis.length2();
|
|
|
|
//valid normal
|
|
if (lenSqr < REL_ERROR2)
|
|
{
|
|
m_degenerateSimplex = 5;
|
|
}
|
|
if (lenSqr > SIMD_EPSILON*SIMD_EPSILON)
|
|
{
|
|
btScalar rlen = btScalar(1.) / btSqrt(lenSqr );
|
|
normalInB *= rlen; //normalize
|
|
|
|
btScalar s = btSqrt(squaredDistance);
|
|
|
|
btAssert(s > btScalar(0.0));
|
|
pointOnA -= m_cachedSeparatingAxis * (marginA / s);
|
|
pointOnB += m_cachedSeparatingAxis * (marginB / s);
|
|
distance = ((btScalar(1.)/rlen) - margin);
|
|
isValid = true;
|
|
|
|
m_lastUsedMethod = 1;
|
|
} else
|
|
{
|
|
m_lastUsedMethod = 2;
|
|
}
|
|
}
|
|
|
|
bool catchDegeneratePenetrationCase =
|
|
(m_catchDegeneracies && m_penetrationDepthSolver && m_degenerateSimplex && ((distance+margin) < gGjkEpaPenetrationTolerance));
|
|
|
|
//if (checkPenetration && !isValid)
|
|
if (checkPenetration && (!isValid || catchDegeneratePenetrationCase ))
|
|
{
|
|
//penetration case
|
|
|
|
//if there is no way to handle penetrations, bail out
|
|
if (m_penetrationDepthSolver)
|
|
{
|
|
// Penetration depth case.
|
|
btVector3 tmpPointOnA,tmpPointOnB;
|
|
|
|
gNumDeepPenetrationChecks++;
|
|
m_cachedSeparatingAxis.setZero();
|
|
|
|
bool isValid2 = m_penetrationDepthSolver->calcPenDepth(
|
|
*m_simplexSolver,
|
|
m_minkowskiA,m_minkowskiB,
|
|
localTransA,localTransB,
|
|
m_cachedSeparatingAxis, tmpPointOnA, tmpPointOnB,
|
|
debugDraw
|
|
);
|
|
|
|
|
|
if (isValid2)
|
|
{
|
|
btVector3 tmpNormalInB = tmpPointOnB-tmpPointOnA;
|
|
btScalar lenSqr = tmpNormalInB.length2();
|
|
if (lenSqr <= (SIMD_EPSILON*SIMD_EPSILON))
|
|
{
|
|
tmpNormalInB = m_cachedSeparatingAxis;
|
|
lenSqr = m_cachedSeparatingAxis.length2();
|
|
}
|
|
|
|
if (lenSqr > (SIMD_EPSILON*SIMD_EPSILON))
|
|
{
|
|
tmpNormalInB /= btSqrt(lenSqr);
|
|
btScalar distance2 = -(tmpPointOnA-tmpPointOnB).length();
|
|
m_lastUsedMethod = 3;
|
|
//only replace valid penetrations when the result is deeper (check)
|
|
if (!isValid || (distance2 < distance))
|
|
{
|
|
distance = distance2;
|
|
pointOnA = tmpPointOnA;
|
|
pointOnB = tmpPointOnB;
|
|
normalInB = tmpNormalInB;
|
|
|
|
isValid = true;
|
|
|
|
} else
|
|
{
|
|
m_lastUsedMethod = 8;
|
|
}
|
|
} else
|
|
{
|
|
m_lastUsedMethod = 9;
|
|
}
|
|
} else
|
|
|
|
{
|
|
///this is another degenerate case, where the initial GJK calculation reports a degenerate case
|
|
///EPA reports no penetration, and the second GJK (using the supporting vector without margin)
|
|
///reports a valid positive distance. Use the results of the second GJK instead of failing.
|
|
///thanks to Jacob.Langford for the reproduction case
|
|
///http://code.google.com/p/bullet/issues/detail?id=250
|
|
|
|
|
|
if (m_cachedSeparatingAxis.length2() > btScalar(0.))
|
|
{
|
|
btScalar distance2 = (tmpPointOnA-tmpPointOnB).length()-margin;
|
|
//only replace valid distances when the distance is less
|
|
if (!isValid || (distance2 < distance))
|
|
{
|
|
distance = distance2;
|
|
pointOnA = tmpPointOnA;
|
|
pointOnB = tmpPointOnB;
|
|
pointOnA -= m_cachedSeparatingAxis * marginA ;
|
|
pointOnB += m_cachedSeparatingAxis * marginB ;
|
|
normalInB = m_cachedSeparatingAxis;
|
|
normalInB.normalize();
|
|
|
|
isValid = true;
|
|
m_lastUsedMethod = 6;
|
|
} else
|
|
{
|
|
m_lastUsedMethod = 5;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (isValid && ((distance < 0) || (distance*distance < input.m_maximumDistanceSquared)))
|
|
{
|
|
|
|
m_cachedSeparatingAxis = normalInB;
|
|
m_cachedSeparatingDistance = distance;
|
|
|
|
{
|
|
///todo: need to track down this EPA penetration solver degeneracy
|
|
///the penetration solver reports penetration but the contact normal
|
|
///connecting the contact points is pointing in the opposite direction
|
|
///until then, detect the issue and revert the normal
|
|
|
|
btScalar d1=0;
|
|
{
|
|
btVector3 seperatingAxisInA = (normalInB)* input.m_transformA.getBasis();
|
|
btVector3 seperatingAxisInB = -normalInB* input.m_transformB.getBasis();
|
|
|
|
|
|
btVector3 pInA = m_minkowskiA->localGetSupportVertexWithoutMarginNonVirtual(seperatingAxisInA);
|
|
btVector3 qInB = m_minkowskiB->localGetSupportVertexWithoutMarginNonVirtual(seperatingAxisInB);
|
|
|
|
btVector3 pWorld = localTransA(pInA);
|
|
btVector3 qWorld = localTransB(qInB);
|
|
btVector3 w = pWorld - qWorld;
|
|
d1 = (-normalInB).dot(w);
|
|
}
|
|
btScalar d0 = 0.f;
|
|
{
|
|
btVector3 seperatingAxisInA = (-normalInB)* input.m_transformA.getBasis();
|
|
btVector3 seperatingAxisInB = normalInB* input.m_transformB.getBasis();
|
|
|
|
|
|
btVector3 pInA = m_minkowskiA->localGetSupportVertexWithoutMarginNonVirtual(seperatingAxisInA);
|
|
btVector3 qInB = m_minkowskiB->localGetSupportVertexWithoutMarginNonVirtual(seperatingAxisInB);
|
|
|
|
btVector3 pWorld = localTransA(pInA);
|
|
btVector3 qWorld = localTransB(qInB);
|
|
btVector3 w = pWorld - qWorld;
|
|
d0 = normalInB.dot(w);
|
|
}
|
|
if (d1>d0)
|
|
{
|
|
m_lastUsedMethod = 10;
|
|
normalInB*=-1;
|
|
}
|
|
|
|
}
|
|
output.addContactPoint(
|
|
normalInB,
|
|
pointOnB+positionOffset,
|
|
distance);
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|