[ODE] RE: Terrain<->Box collision without tri-collider
McEvoy, Nick
nick.mcevoy at dsto.defence.gov.au
Mon Jan 27 18:09:01 2003
Stephan Heigl wrote:
>Hi,
>due to data duplication i'd like to do the collision tests against the terrain
>without the tricollider and OPCODE. So i created a custom dTerrainGeom. I
>found some info on how to do terrain<->sphere intersection but nothing on box
>intersection.
>Any idea how to handle that?
>Thank you very much in advance.
I've managed to get tri-box collision working without using tricollider or OPCODE.
My solution was to use Tomas Akenine-Möller Fast 3D Triangle-Box Overlap Testing - tribox.cpp at http://www.acm.org/jgt/papers/AkenineMoller01/tribox.html.
Read how it works: http://www.ce.chalmers.se/staff/tomasm/pubs/tribox.pdf
Steps involved:
1. do a simple bounding sphere test to determine potentially 'hit' triangles near your object
2. check each potentially 'hit' triangle using Tomas Akenine-Möller tribox test
3. if a tribox test returns true then use ODE box-plane collide method (dCollideBP) to generate the contact geom
4. generate contacts as usual (ie. using ODE's method dJointCreateContact)
Sorry, my code below is a little bit application specific (I use PLIB as my scene graph) ... but it shows my words above as real code. My code below starts at step 2 ... PLIB has already determined potentially 'hit' triangles (pHitList) for me (ie. step 1).
bool
reTriangleBoxContact(int iNumHits,
ssgHit* pHitList,
dBodyID BoxBody,
dGeomID BoxGeom)
{
reList<dContact> ContactList;
// Check for hits
if (iNumHits > 0 && pHitList)
{
//printf("** hits <%d>\n", iNumHits);
for (int i = 0; i < iNumHits; i++)
{
ssgHit* h = &pHitList[i];
sgVec3 vTriangle[3];
sgVec3 vTriangleNormal;
// Get 'hit' triangle in leaf
short v1, v2, v3;
h->leaf->getTriangle(h->triangle, &v1, &v2, &v3);
sgCopyVec3(vTriangle[0], h->leaf->getVertex(v1));
sgCopyVec3(vTriangle[1], h->leaf->getVertex(v2));
sgCopyVec3(vTriangle[2], h->leaf->getVertex(v3));
sgSetVec3(vTriangleNormal, h->plane[0], h->plane[1], h->plane[2]);
// Apply current matrix to 'hit' triangle
sgXformPnt3(vTriangle[0], vTriangle[0], h->matrix);
sgXformPnt3(vTriangle[1], vTriangle[1], h->matrix);
sgXformPnt3(vTriangle[2], vTriangle[2], h->matrix);
// Get axis aligned bounding box (AABB) info
dVector3 BoxSides;
sgVec3 BoxExtents;
sgVec3 BoxCenter = {0,0,0};
dGeomBoxGetLengths(BoxGeom, BoxSides);
sgSetVec3(BoxExtents, BoxSides[0]*0.5f, BoxSides[1]*0.5f, BoxSides[2]*0.5f);
// To test against oriented bounding box (OBB) need to
// translate triangle verticies by inverse box transform
sgMat4 Xform;
const dReal* pPos = dBodyGetPosition(BoxBody);
const dReal* pRot = dBodyGetRotation(BoxBody);
sgSetVec4(Xform[0], pRot[0], pRot[4], pRot[8], 0);
sgSetVec4(Xform[1], pRot[1], pRot[5], pRot[9], 0);
sgSetVec4(Xform[2], pRot[2], pRot[6], pRot[10], 0);
sgSetVec4(Xform[3], pPos[0], pPos[1], pPos[2], 1);
sgTransposeNegateMat4(Xform);
sgXformPnt3(vTriangle[0], Xform);
sgXformPnt3(vTriangle[1], Xform);
sgXformPnt3(vTriangle[2], Xform);
// Triangle-box collision test
if (triBoxOverlap(BoxCenter, BoxExtents, vTriangle))
{
// Use ODE box-plane collide to generate contact geom
dContactGeom BoxContacts[3];
dGeomID TriPlaneGeom = dCreatePlane(reGetODESpaceId(),
h->plane[0], h->plane[1], h->plane[2], -h->plane[3]);
int n = dCollideBP(BoxGeom, TriPlaneGeom, 3, BoxContacts, sizeof(dContactGeom));
for (int j = 0; j < n; j++)
{
// Get contact surface properties depending on material hit
ssgState* pState = h->leaf->getState();
if (pState->isA(ssgTypeSimpleState()))
{
dSurfaceParameters Surface;
if (reGetMaterialManager()->GetSurfaceParams(
pState->getExternalPropertyIndex(),
Surface))
{
dContact* pContact = new dContact;
pContact->surface = Surface;
pContact->geom = BoxContacts[j];
pContact->geom.g1 = BoxGeom;
pContact->geom.g2 = NULL;
ContactList.AddTail(pContact);
}
}
}
dGeomDestroy(TriPlaneGeom);
}
}
}
// If no hits then do a height over terrain check
// to stop objects falling thru terrain
if (ContactList.GetCount() == 0)
{
// Quick hack: just approximate box as a sphere !? :)
sgVec3 vCenter;
dVector3 BoxSides;
reGetBodyPosition(vCenter, BoxBody);
dGeomBoxGetLengths(BoxGeom, BoxSides);
float fRadius = BoxSides[0];
if (fRadius < BoxSides[1])
fRadius = BoxSides[1];
if (fRadius < BoxSides[2])
fRadius = BoxSides[2];
fRadius *= 0.5f;
ssgHit* h;
sgVec3 vTriangleNormal;
float fHOT = reGetHeightAndNormal(reGetWorld(), vCenter, vTriangleNormal, &h);
float fDepth = fHOT-vCenter[SG_Z];
if (fDepth > fRadius)
{
//printf("** depth <%.2f>\n", fDepth);
// todo: improve reGetHeightAndNormal() to return position
// same as reTriangleSphereIntersectionPoint() above.
sgVec3 vOffset;
sgVec3 vPosition;
sgScaleVec3(vOffset, vTriangleNormal, -fDepth);
sgSubVec3(vPosition, vCenter, vOffset);
ssgState* pState = h->leaf->getState();
if (pState->isA(ssgTypeSimpleState()))
{
dSurfaceParameters Surface;
if (reGetMaterialManager()->GetSurfaceParams(
pState->getExternalPropertyIndex(),
Surface))
{
dContact* pContact = new dContact;
pContact->surface = Surface;
pContact->geom.pos[0] = vPosition[0];
pContact->geom.pos[1] = vPosition[1];
pContact->geom.pos[2] = vPosition[2];
pContact->geom.normal[0] = vTriangleNormal[0];
pContact->geom.normal[1] = vTriangleNormal[1];
pContact->geom.normal[2] = vTriangleNormal[2];
pContact->geom.depth = fDepth;
pContact->geom.g1 = BoxGeom;
pContact->geom.g2 = NULL;
ContactList.AddTail(pContact);
}
}
}
}
return reCreateContacts(ContactList, BoxBody);
}
bool
reCreateContacts(reList<dContact>& ContactList, dBodyID Body)
{
bool bContact = false;
reList<dContact> OptimisedList;
// Go thru contact list and filter out duplicate contacts
LISTPOSITION pos = ContactList.GetHeadPosition();
while (pos)
{
dContact* pContact = ContactList.GetNext(pos);
// Check if contact is already in optimised list
bool bContactExists = false;
LISTPOSITION pos_opt = OptimisedList.GetHeadPosition();
while (pos_opt)
{
dContact* pContactOpt = OptimisedList.GetNext(pos_opt);
if (sgCompareVec3(pContact->geom.pos, pContactOpt->geom.pos, 0.001f) &&
sgCompareVec3(pContact->geom.normal, pContactOpt->geom.normal, 0.001f))
{
bContactExists = true;
break;
}
}
// If not then add it to optimised list
if (!bContactExists)
{
dContact* pContactOpt = new dContact;
*pContactOpt = *pContact;
OptimisedList.AddTail(pContactOpt);
}
}
// Go thru optimised list and genarate contacts
pos = OptimisedList.GetHeadPosition();
while (pos)
{
dContact* pContact = OptimisedList.GetNext(pos);
/*printf("pos <%.1f:%.1f:%.1f> nrm <%.1f:%.1f:%.1f>\n",
pContact->geom.pos[0],
pContact->geom.pos[1],
pContact->geom.pos[2],
pContact->geom.normal[0],
pContact->geom.normal[1],
pContact->geom.normal[2]);*/
bContact = true;
dJointID c = dJointCreateContact(
reGetODEWorldId(),
reGetODEJointGroupId(),
pContact);
dJointAttach(c, Body, NULL);
}
return bContact;
}
Nick