Casey's Video
Illustration image
The boolean intersection test passed all of my unit and random real time tests, though they were not exaustive.
I've tried to add closest pt results and I'm having some trouble.
The test ends with a triangle simplex. The separation vector is plausible but the pt's are a little off.
Closest Pt test result image. (closest pt found to be in the center of these two "wrong" triangles)
Intuitively i would have thought he blue triangle would be on the top face, and the red triangle on the bottom face.
I believe this is due to Casey's algorithm quick rejecting when ever possible and not exhaustively searching the support.
Has anyone encountered this and can describe the solution?
Do i just need to check all/more of the voronoi regions?
Source code: I hope it is easy to follow.
#ifndef __tGJK__
#define __tGJK__
#include "tSupportMapping.hpp"
namespace Sig { namespace Physics
{
struct tSimplexVertex
{
Math::tVec3f mA;
Math::tVec3f mB;
Math::tVec3f mMinkowski;
tSimplexVertex( )
{ }
tSimplexVertex( const Math::tVec3f& a, const Math::tVec3f& b, const Math::tVec3f& minkowski )
: mA( a )
, mB( b )
, mMinkowski( minkowski )
{ }
};
struct tSimplex
{
typedef tFixedGrowingArray< tSimplexVertex, 4 > tVertList;
tVertList mVerts;
Math::tVec3f mSearchDir;
void fClear( ) { mVerts.fSetCount( 0 ); }
};
class tGJK
{
public:
tGJK( tSupportMapping* a, tSupportMapping* b );
const tSupportMappingPtr& fA( ) const { return mA; }
const tSupportMappingPtr& fB( ) const { return mB; }
void fReset( ) { fInitialize( ); }
// keep calling this to keep the information up to date as objects move.
void fUpdate( );
// Feedback
b32 fIntersects( ) const { return mIntersects; }
const Math::tVec3f& fClosestPtA( ) const { return mClosestPtA; }
const Math::tVec3f& fClosestPtB( ) const { return mClosestPtB; }
// only for debugging.
static void fTest( );
void fDrawSimplexes( );
private:
tSupportMappingPtr mA;
tSupportMappingPtr mB;
tSimplex mSimplex;
b32 mIntersects;
Math::tVec3f mClosestPtA;
Math::tVec3f mClosestPtB;
void fInitialize( );
tSimplexVertex fSupport( const Math::tVec3f& worldD );
// return false if origin is within simplex.
b32 fUpdateSimplex( const tSimplexVertex& newVert );
// a final step to compute the actual closest pts on the shapes.
void fUpdateClosestPts( );
};
}}
#endif//__tGJK__
#include "BasePch.hpp"
#include "tGJK.hpp"
#include "tPhysicsWorld.hpp" //for debug rendering only
using namespace Sig::Math;
namespace Sig { namespace Physics
{
// -----------------------------------------------------
// See tGJK_Figures.png for illustrations of how this work.
// -----------------------------------------------------
namespace
{
b32 fSameDirection( const tVec3f& a, const tVec3f& b )
{
return a.fDot( b ) > 0.f;
}
// UNNORMALIZED
tVec3f fPlaneNormal( const tVec3f& a, const tVec3f& b, const tVec3f& c )
{
return (b-a).fCross( c-a );
}
tPlanef fUnnormalizedPlane( const tVec3f& a, const tVec3f& b, const tVec3f& c )
{
tVec3f normal = fPlaneNormal( a, b, c );
return tPlanef( normal, a );
}
b32 fOriginOutsidePlane( const tPlanef& plane )
{
return plane.d > 0.f;
}
tVec3f fEdgeToOriginDir( const tVec3f& ab, const tVec3f& ao )
{
return ab.fCross( ao.fCross( ab ) );
}
}
tGJK::tGJK( tSupportMapping* a, tSupportMapping* b )
: mA( a )
, mB( b )
, mIntersects( false )
, mClosestPtA( tVec3f::cZeroVector )
, mClosestPtB( tVec3f::cZeroVector )
{
fInitialize( );
}
void tGJK::fInitialize( )
{
sigassert( mA && mB );
mSimplex.fClear( );
tVec3f firstDir = tVec3f::cYAxis;
mSimplex.mVerts.fPushBack( fSupport( firstDir ) );
// first direction is towards the origin.
mSimplex.mSearchDir = -mSimplex.mVerts[ 0 ].mMinkowski;
}
tSimplexVertex tGJK::fSupport( const Math::tVec3f& worldD )
{
tVec3f a = mA->fWorldSupport( worldD );
tVec3f b = mB->fWorldSupport( -worldD );
return tSimplexVertex( a, b, a - b );
}
void tGJK::fUpdate( )
{
sigassert( mSimplex.mVerts.fCount( ) );
// clamp iterations
for( u32 i = 0; i < 2000; ++i )
//while( 1 )
{
tSimplexVertex newPt = fSupport( mSimplex.mSearchDir );
//if( mSimplex.mVerts.fBack( ).mMinkowski.fLengthSquared( ) > newPt.mMinkowski.fLengthSquared( ) )
if( fSameDirection( newPt.mMinkowski, mSimplex.mSearchDir ) )
{
// new pt is useful. see figure 0
if( !fUpdateSimplex( newPt ) )
{
mIntersects = true;
break; //origin is contained, intersection
}
}
else
{
// no point lies beyond origin., fig 0.
mIntersects = false;
break;
}
}
fUpdateClosestPts( );
}
b32 tGJK::fUpdateSimplex( const tSimplexVertex& newVert )
{
tSimplex::tVertList& verts = mSimplex.mVerts;
sigassert( verts.fCount( ) && "simplex needs to be intialized!" );
verts.fPushBack( newVert );
switch( verts.fCount( ) )
{
case 2:
{
// Line case, a is the newer pt.
const tVec3f& a = verts[ 1 ].mMinkowski;
const tVec3f& b = verts[ 0 ].mMinkowski;
tVec3f ab = b - a;
tVec3f ao = -a; //a to origin.
if( fSameDirection( ab, ao ) )
{
// origin is along segment. fig 1.
mSimplex.mSearchDir = fEdgeToOriginDir( ab, ao );
}
else
{
// origin is outside of segment, fig 1.
verts.fErase( 0 );
mSimplex.mSearchDir = ao; //search to origin.
}
break;
}
case 3:
{
// triangle case, a is the newer pt.
// see fig. 2
const tVec3f& a = verts[ 2 ].mMinkowski;
const tVec3f& b = verts[ 1 ].mMinkowski;
const tVec3f& c = verts[ 0 ].mMinkowski;
tVec3f ab = b - a;
tVec3f ac = c - a;
tVec3f ao = -a;
if( !fSameDirection( ab, ao ) )
{
// origin is outside of ab
if( !fSameDirection( ac, ao ) )
{
// origin is outside of ac
// origin is closest to a.
mSimplex.mSearchDir = ao;
// remove all features except a
verts[ 0 ] = verts[ 2 ];
verts.fSetCount( 1 );
}
else
{
//origin is closest to ac
mSimplex.mSearchDir = fEdgeToOriginDir( ac, ao );
//throw out b
verts.fEraseOrdered( 1 );
}
}
else //origin is along ab
{
if( fSameDirection( ac, ao ) )
{
// origin is inside of ac also
// so origin is closest to the face.
tPlanef facePlane = fUnnormalizedPlane( a, b, c );
if( fOriginOutsidePlane( facePlane ) )
mSimplex.mSearchDir = facePlane.fGetNormal( );
else
{
mSimplex.mSearchDir = -facePlane.fGetNormal( );
//rewind face in other direction
fSwap( verts[ 0 ], verts[ 1 ] );
}
}
else
{
//origin is closest to ab segment
mSimplex.mSearchDir = fEdgeToOriginDir( ab, ao );
//throw out c
verts.fEraseOrdered( 0 );
}
}
break;
}
case 4:
{
// tetrahedron case, a is the newer pt.
// see fig. 3
const tVec3f& a = verts[ 3 ].mMinkowski;
const tVec3f& b = verts[ 2 ].mMinkowski;
const tVec3f& c = verts[ 1 ].mMinkowski;
const tVec3f& d = verts[ 0 ].mMinkowski;
const tVec3f ao = -a;
tPlanef p1 = fUnnormalizedPlane( a, b, c );
tPlanef p2 = fUnnormalizedPlane( a, d, b );
tPlanef p3 = fUnnormalizedPlane( a, c, d );
b32 out1 = fOriginOutsidePlane( p1 );
b32 out2 = fOriginOutsidePlane( p2 );
b32 out3 = fOriginOutsidePlane( p3 );
if( out1 )
{
if( out2 )
{
if( out3 )
{
//origin is closest to a.
mSimplex.mSearchDir = ao;
//throw out all but a
verts[ 0 ] = verts[ 3 ];
verts.fSetCount( 1 );
}
else
{
//origin is along edge ab.
mSimplex.mSearchDir = fEdgeToOriginDir( b-a, ao );
//throw out c and d
verts[ 0 ] = verts[ 2 ];
verts[ 1 ] = verts[ 3 ];
verts.fSetCount( 2 );
}
}
else if( out3 )
{
//origin is along edge ac.
mSimplex.mSearchDir = fEdgeToOriginDir( c-a, ao );
//throw out d and b
verts[ 0 ] = verts[ 1 ];
verts[ 1 ] = verts[ 3 ];
verts.fSetCount( 2 );
}
else
{
//origin is outside p1 only
mSimplex.mSearchDir = p1.fGetNormal( );
//throw out d
verts.fEraseOrdered( 0 );
}
}
else if( out2 )
{
if( out3 )
{
//origin is along ad
mSimplex.mSearchDir = fEdgeToOriginDir( d-a, ao );
//throw out c and b
verts[ 1 ] = verts[ 3 ];
verts.fSetCount( 2 );
}
else
{
//origin is outside p2 only
mSimplex.mSearchDir = p2.fGetNormal( );
//throw out d
verts.fEraseOrdered( 1 );
}
}
else if( out3 )
{
//origin is outside p3 only
mSimplex.mSearchDir = p3.fGetNormal( );
//throw out b
verts.fEraseOrdered( 2 );
}
else
{
//origin is within all 3 planes, contained, intersection.
return false;
}
break;
}
default:
{
sigassert( !"Should not have gotten here." );
}
}
return true;
}
void tGJK::fUpdateClosestPts( )
{
tSimplex::tVertList& verts = mSimplex.mVerts;
sigassert( verts.fCount( ) && "simplex needs to be intialized!" );
switch( verts.fCount( ) )
{
case 1:
{
const tSimplexVertex& a = verts[ 0 ];
mClosestPtA = a.mA;
mClosestPtB = a.mB;
break;
}
case 2:
{
// Line case, a is the newer pt.
const tSimplexVertex& a = verts[ 1 ];
const tSimplexVertex& b = verts[ 0 ];
tVec3f ab = b.mMinkowski - a.mMinkowski;
tVec3f ao = -a.mMinkowski; //a to origin.
f32 abLen;
ab.fNormalize( abLen );
f32 bCoef = ab.fDot( ao ) / abLen;
f32 aCoef = 1.f - bCoef;
sigassert( fInBounds( aCoef, 0.f, 1.f ) );
sigassert( fInBounds( bCoef, 0.f, 1.f ) );
mClosestPtA = a.mA * aCoef + b.mA * bCoef;
mClosestPtB = a.mB * aCoef + b.mB * bCoef;
break;
}
case 3:
{
// triangle case, a is the newer pt.
// see fig. 2
const tSimplexVertex& a = verts[ 2 ];
const tSimplexVertex& b = verts[ 1 ];
const tSimplexVertex& c = verts[ 0 ];
tVec3f ab = b.mMinkowski - a.mMinkowski;
tVec3f ac = c.mMinkowski - a.mMinkowski;
tVec3f ao = -a.mMinkowski;
f32 abLen;
f32 acLen;
ab.fNormalize( abLen );
ac.fNormalize( acLen );
f32 bCoef = ab.fDot( ao ) / abLen;
f32 cCoef = ac.fDot( ao ) / acLen;
f32 aCoef = 1.f - bCoef - cCoef;
sigassert( fInBounds( aCoef, 0.f, 1.f ) );
sigassert( fInBounds( bCoef, 0.f, 1.f ) );
sigassert( fInBounds( cCoef, 0.f, 1.f ) );
mClosestPtA = a.mA * aCoef + b.mA * bCoef + c.mA * cCoef;
mClosestPtB = a.mB * aCoef + b.mB * bCoef + c.mB * cCoef;
break;
}
case 4:
{
// tetrahedron case, a is the newer pt.
// see fig. 3
const tSimplexVertex& a = verts[ 3 ];
/*const tSimplexVertex& b = verts[ 2 ];
const tSimplexVertex& c = verts[ 1 ];
const tSimplexVertex& d = verts[ 0 ];*/
//const tVec3f ao = -a;
// have not attempted this case yet.
mClosestPtA = a.mA;
mClosestPtB = a.mB;
break;
}
default:
{
sigassert( !"Should not have gotten here." );
}
}
}
void tGJK::fDrawSimplexes( )
{
u32 count = mSimplex.mVerts.fCount( );
if( count > 1 )
{
for( u32 i = 0; i < count - 1; ++i )
{
tPhysicsWorld::fDebugGeometry( ).fRenderOnce( mSimplex.mVerts[ i ].mA, mSimplex.mVerts[ i+1
].mA, tVec4f( 1,0,0,1 ) );
tPhysicsWorld::fDebugGeometry( ).fRenderOnce( mSimplex.mVerts[ i ].mB, mSimplex.mVerts[ i+1
].mB, tVec4f( 0,0,1,1 ) );
}
tPhysicsWorld::fDebugGeometry( ).fRenderOnce( mSimplex.mVerts[ 0 ].mA, mSimplex.mVerts[ count-1 ].mA,
tVec4f( 1,0,0,1 ) );
tPhysicsWorld::fDebugGeometry( ).fRenderOnce( mSimplex.mVerts[ 0 ].mB, mSimplex.mVerts[ count-1 ].mB,
tVec4f( 0,0,1,1 ) );
}
}
} }