/*
|
* Java port of Bullet (c) 2008 Martin Dvorak <jezek2@advel.cz>
|
*
|
* Bullet Continuous Collision Detection and Physics Library
|
* Copyright (c) 2003-2008 Erwin Coumans http://www.bulletphysics.com/
|
*
|
* 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.
|
*/
|
|
package com.bulletphysics.collision.narrowphase;
|
|
import com.bulletphysics.BulletGlobals;
|
import com.bulletphysics.BulletStats;
|
import com.bulletphysics.collision.shapes.ConvexShape;
|
import com.bulletphysics.linearmath.IDebugDraw;
|
import com.bulletphysics.linearmath.MatrixUtil;
|
import com.bulletphysics.linearmath.Transform;
|
import cz.advel.stack.Stack;
|
import cz.advel.stack.StaticAlloc;
|
import javax.vecmath.Vector3f;
|
|
/**
|
* GjkPairDetector uses GJK to implement the {@link DiscreteCollisionDetectorInterface}.
|
*
|
* @author jezek2
|
*/
|
public class GjkPairDetector extends DiscreteCollisionDetectorInterface {
|
|
//protected final BulletStack stack = BulletStack.get();
|
|
// must be above the machine epsilon
|
private static final float REL_ERROR2 = 1.0e-6f;
|
|
private final Vector3f cachedSeparatingAxis = new Vector3f();
|
private ConvexPenetrationDepthSolver penetrationDepthSolver;
|
private SimplexSolverInterface simplexSolver;
|
private ConvexShape minkowskiA;
|
private ConvexShape minkowskiB;
|
private boolean ignoreMargin;
|
|
// some debugging to fix degeneracy problems
|
public int lastUsedMethod;
|
public int curIter;
|
public int degenerateSimplex;
|
public int catchDegeneracies;
|
|
public void init(ConvexShape objectA, ConvexShape objectB, SimplexSolverInterface simplexSolver, ConvexPenetrationDepthSolver penetrationDepthSolver) {
|
this.cachedSeparatingAxis.set(0f, 0f, 1f);
|
this.ignoreMargin = false;
|
this.lastUsedMethod = -1;
|
this.catchDegeneracies = 1;
|
|
this.penetrationDepthSolver = penetrationDepthSolver;
|
this.simplexSolver = simplexSolver;
|
this.minkowskiA = objectA;
|
this.minkowskiB = objectB;
|
}
|
|
@StaticAlloc
|
public void getClosestPoints(ClosestPointInput input, Result output, IDebugDraw debugDraw, boolean swapResults) {
|
Vector3f tmp = Stack.alloc(Vector3f.class);
|
|
float distance = 0f;
|
Vector3f normalInB = Stack.alloc(Vector3f.class);
|
normalInB.set(0f, 0f, 0f);
|
Vector3f pointOnA = Stack.alloc(Vector3f.class), pointOnB = Stack.alloc(Vector3f.class);
|
Transform localTransA = (Transform) Stack.alloc(input.transformA);
|
Transform localTransB = (Transform) Stack.alloc(input.transformB);
|
Vector3f positionOffset = Stack.alloc(Vector3f.class);
|
positionOffset.add(localTransA.origin, localTransB.origin);
|
positionOffset.scale(0.5f);
|
localTransA.origin.sub(positionOffset);
|
localTransB.origin.sub(positionOffset);
|
|
float marginA = minkowskiA.getMargin();
|
float marginB = minkowskiB.getMargin();
|
|
BulletStats.gNumGjkChecks++;
|
|
// for CCD we don't use margins
|
if (ignoreMargin) {
|
marginA = 0f;
|
marginB = 0f;
|
}
|
|
curIter = 0;
|
int gGjkMaxIter = 1000; // this is to catch invalid input, perhaps check for #NaN?
|
cachedSeparatingAxis.set(0f, 1f, 0f);
|
|
boolean isValid = false;
|
boolean checkSimplex = false;
|
boolean checkPenetration = true;
|
degenerateSimplex = 0;
|
|
lastUsedMethod = -1;
|
|
{
|
float squaredDistance = BulletGlobals.SIMD_INFINITY;
|
float delta = 0f;
|
|
float margin = marginA + marginB;
|
|
simplexSolver.reset();
|
|
Vector3f seperatingAxisInA = Stack.alloc(Vector3f.class);
|
Vector3f seperatingAxisInB = Stack.alloc(Vector3f.class);
|
|
Vector3f pInA = Stack.alloc(Vector3f.class);
|
Vector3f qInB = Stack.alloc(Vector3f.class);
|
|
Vector3f pWorld = Stack.alloc(Vector3f.class);
|
Vector3f qWorld = Stack.alloc(Vector3f.class);
|
Vector3f w = Stack.alloc(Vector3f.class);
|
|
Vector3f tmpPointOnA = Stack.alloc(Vector3f.class), tmpPointOnB = Stack.alloc(Vector3f.class);
|
Vector3f tmpNormalInB = Stack.alloc(Vector3f.class);
|
|
for (;;) //while (true)
|
{
|
seperatingAxisInA.negate(cachedSeparatingAxis);
|
MatrixUtil.transposeTransform(seperatingAxisInA, seperatingAxisInA, input.transformA.basis);
|
|
seperatingAxisInB.set(cachedSeparatingAxis);
|
MatrixUtil.transposeTransform(seperatingAxisInB, seperatingAxisInB, input.transformB.basis);
|
|
minkowskiA.localGetSupportingVertexWithoutMargin(seperatingAxisInA, pInA);
|
minkowskiB.localGetSupportingVertexWithoutMargin(seperatingAxisInB, qInB);
|
|
pWorld.set(pInA);
|
localTransA.transform(pWorld);
|
|
qWorld.set(qInB);
|
localTransB.transform(qWorld);
|
|
w.sub(pWorld, qWorld);
|
|
delta = cachedSeparatingAxis.dot(w);
|
|
// potential exit, they don't overlap
|
if ((delta > 0f) && (delta * delta > squaredDistance * input.maximumDistanceSquared)) {
|
checkPenetration = false;
|
break;
|
}
|
|
// exit 0: the new point is already in the simplex, or we didn't come any closer
|
if (simplexSolver.inSimplex(w)) {
|
degenerateSimplex = 1;
|
checkSimplex = true;
|
break;
|
}
|
// are we getting any closer ?
|
float f0 = squaredDistance - delta;
|
float f1 = squaredDistance * REL_ERROR2;
|
|
if (f0 <= f1) {
|
if (f0 <= 0f) {
|
degenerateSimplex = 2;
|
}
|
checkSimplex = true;
|
break;
|
}
|
// add current vertex to simplex
|
simplexSolver.addVertex(w, pWorld, qWorld);
|
|
// calculate the closest point to the origin (update vector v)
|
if (!simplexSolver.closest(cachedSeparatingAxis)) {
|
degenerateSimplex = 3;
|
checkSimplex = true;
|
break;
|
}
|
|
if (cachedSeparatingAxis.lengthSquared() < REL_ERROR2) {
|
degenerateSimplex = 6;
|
checkSimplex = true;
|
break;
|
}
|
|
float previousSquaredDistance = squaredDistance;
|
squaredDistance = cachedSeparatingAxis.lengthSquared();
|
|
// redundant m_simplexSolver->compute_points(pointOnA, pointOnB);
|
|
// are we getting any closer ?
|
if (previousSquaredDistance - squaredDistance <= BulletGlobals.FLT_EPSILON * previousSquaredDistance) {
|
simplexSolver.backup_closest(cachedSeparatingAxis);
|
checkSimplex = true;
|
break;
|
}
|
|
// degeneracy, this is typically due to invalid/uninitialized worldtransforms for a CollisionObject
|
if (curIter++ > gGjkMaxIter) {
|
//#if defined(DEBUG) || defined (_DEBUG)
|
if (BulletGlobals.DEBUG) {
|
System.err.printf("btGjkPairDetector maxIter exceeded:%i\n", curIter);
|
System.err.printf("sepAxis=(%f,%f,%f), squaredDistance = %f, shapeTypeA=%i,shapeTypeB=%i\n",
|
cachedSeparatingAxis.x,
|
cachedSeparatingAxis.y,
|
cachedSeparatingAxis.z,
|
squaredDistance,
|
minkowskiA.getShapeType(),
|
minkowskiB.getShapeType());
|
}
|
//#endif
|
break;
|
|
}
|
|
boolean check = (!simplexSolver.fullSimplex());
|
//bool check = (!m_simplexSolver->fullSimplex() && squaredDistance > SIMD_EPSILON * m_simplexSolver->maxVertex());
|
|
if (!check) {
|
// do we need this backup_closest here ?
|
simplexSolver.backup_closest(cachedSeparatingAxis);
|
break;
|
}
|
}
|
|
if (checkSimplex) {
|
simplexSolver.compute_points(pointOnA, pointOnB);
|
normalInB.sub(pointOnA, pointOnB);
|
float lenSqr = cachedSeparatingAxis.lengthSquared();
|
// valid normal
|
if (lenSqr < 0.0001f) {
|
degenerateSimplex = 5;
|
}
|
if (lenSqr > BulletGlobals.FLT_EPSILON * BulletGlobals.FLT_EPSILON) {
|
float rlen = 1f / (float) Math.sqrt(lenSqr);
|
normalInB.scale(rlen); // normalize
|
float s = (float) Math.sqrt(squaredDistance);
|
|
assert (s > 0f);
|
|
tmp.scale((marginA / s), cachedSeparatingAxis);
|
pointOnA.sub(tmp);
|
|
tmp.scale((marginB / s), cachedSeparatingAxis);
|
pointOnB.add(tmp);
|
|
distance = ((1f / rlen) - margin);
|
isValid = true;
|
|
lastUsedMethod = 1;
|
}
|
else {
|
lastUsedMethod = 2;
|
}
|
}
|
|
boolean catchDegeneratePenetrationCase =
|
(catchDegeneracies != 0 && penetrationDepthSolver != null && degenerateSimplex != 0 && ((distance + margin) < 0.01f));
|
|
//if (checkPenetration && !isValid)
|
if (checkPenetration && (!isValid || catchDegeneratePenetrationCase)) {
|
// penetration case
|
|
// if there is no way to handle penetrations, bail out
|
if (penetrationDepthSolver != null) {
|
// Penetration depth case.
|
BulletStats.gNumDeepPenetrationChecks++;
|
|
boolean isValid2 = penetrationDepthSolver.calcPenDepth(
|
simplexSolver,
|
minkowskiA, minkowskiB,
|
localTransA, localTransB,
|
cachedSeparatingAxis, tmpPointOnA, tmpPointOnB,
|
debugDraw/*,input.stackAlloc*/);
|
|
if (isValid2) {
|
tmpNormalInB.sub(tmpPointOnB, tmpPointOnA);
|
|
float lenSqr = tmpNormalInB.lengthSquared();
|
if (lenSqr > (BulletGlobals.FLT_EPSILON * BulletGlobals.FLT_EPSILON)) {
|
tmpNormalInB.scale(1f / (float) Math.sqrt(lenSqr));
|
tmp.sub(tmpPointOnA, tmpPointOnB);
|
float distance2 = -tmp.length();
|
// only replace valid penetrations when the result is deeper (check)
|
if (!isValid || (distance2 < distance)) {
|
distance = distance2;
|
pointOnA.set(tmpPointOnA);
|
pointOnB.set(tmpPointOnB);
|
normalInB.set(tmpNormalInB);
|
isValid = true;
|
lastUsedMethod = 3;
|
}
|
else {
|
|
}
|
}
|
else {
|
//isValid = false;
|
lastUsedMethod = 4;
|
}
|
}
|
else {
|
lastUsedMethod = 5;
|
}
|
|
}
|
}
|
}
|
|
if (isValid) {
|
//#ifdef __SPU__
|
// //spu_printf("distance\n");
|
//#endif //__CELLOS_LV2__
|
|
tmp.add(pointOnB, positionOffset);
|
output.addContactPoint(
|
normalInB,
|
tmp,
|
distance);
|
//printf("gjk add:%f",distance);
|
}
|
}
|
|
public void setMinkowskiA(ConvexShape minkA) {
|
minkowskiA = minkA;
|
}
|
|
public void setMinkowskiB(ConvexShape minkB) {
|
minkowskiB = minkB;
|
}
|
|
public void setCachedSeperatingAxis(Vector3f seperatingAxis) {
|
cachedSeparatingAxis.set(seperatingAxis);
|
}
|
|
public void setPenetrationDepthSolver(ConvexPenetrationDepthSolver penetrationDepthSolver) {
|
this.penetrationDepthSolver = penetrationDepthSolver;
|
}
|
|
/**
|
* Don't use setIgnoreMargin, it's for Bullet's internal use.
|
*/
|
public void setIgnoreMargin(boolean ignoreMargin) {
|
this.ignoreMargin = ignoreMargin;
|
}
|
|
}
|