engine/collision/convex.cc
2024-01-07 04:36:33 +00:00

937 lines
25 KiB
C++

//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "dgl/dgl.h"
#include "Core/dataChunker.h"
#include "Collision/collision.h"
#include "sceneGraph/sceneGraph.h"
#include "Sim/sceneObject.h"
#include "terrain/terrData.h"
#include "Collision/convex.h"
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
static DataChunker sChunker;
CollisionStateList CollisionStateList::sFreeList;
CollisionWorkingList CollisionWorkingList::sFreeList;
F32 sqrDistanceEdges(const Point3F& start0,
const Point3F& end0,
const Point3F& start1,
const Point3F& end1,
Point3F* is,
Point3F* it);
//----------------------------------------------------------------------------
// Feature Collision
//----------------------------------------------------------------------------
bool ConvexFeature::collide(ConvexFeature& cf,CollisionList* cList, F32 tol)
{
// Our vertices vs. CF faces
const Point3F* vert = mVertexList.begin();
const Point3F* vend = mVertexList.end();
while (vert != vend)
{
cf.testVertex(*vert,cList,false, tol);
vert++;
}
// CF vertices vs. our faces
vert = cf.mVertexList.begin();
vend = cf.mVertexList.end();
while (vert != vend)
{
U32 storeCount = cList->count;
testVertex(*vert,cList,true, tol);
// Fix up last reference. material and object are copied from this rather
// than the object we're colliding against.
if (storeCount != cList->count)
{
cList->collision[cList->count - 1].material = cf.material;
cList->collision[cList->count - 1].object = cf.object;
}
vert++;
}
// Edge vs. Edge
const Edge* edge = mEdgeList.begin();
const Edge* eend = mEdgeList.end();
while (edge != eend)
{
cf.testEdge(mVertexList[edge->vertex[0]],
mVertexList[edge->vertex[1]],cList, tol);
edge++;
}
return true;
}
inline bool isInside(const Point3F& p, const Point3F& a, const Point3F& b, const VectorF& n)
{
VectorF v;
mCross(n,b - a,&v);
F32 result = mDot(v,p - a);
return result <= 0.0f;
}
void ConvexFeature::testVertex(const Point3F& v,CollisionList* cList,bool flip, F32 tol)
{
// Test vertex against all faces
const Face* face = mFaceList.begin();
const Face* end = mFaceList.end();
for (; face != end; face++) {
if (cList->count >= CollisionList::MaxCollisions)
return;
const Point3F& p0 = mVertexList[face->vertex[0]];
const Point3F& p1 = mVertexList[face->vertex[1]];
const Point3F& p2 = mVertexList[face->vertex[2]];
F32 distance = mFabs(mDot(face->normal,v - p0));
if (distance > tol)
continue;
if (isInside(v,p0,p1,face->normal) &&
isInside(v,p1,p2,face->normal) &&
isInside(v,p2,p0,face->normal)) {
// Add collision to this face
Collision& info = cList->collision[cList->count++];
AssertFatal(cList->count <= CollisionList::MaxCollisions, "Error, exceeded max collisions!");
info.point = v;
info.normal = face->normal;
if (flip)
info.normal.neg();
info.material = material;
info.object = object;
info.distance = distance;
}
}
}
void ConvexFeature::testEdge(const Point3F& s1, const Point3F& e1, CollisionList* cList, F32 tol)
{
F32 tolSquared = tol*tol;
// Test edges against edges
VectorF v1 = e1 - s1;
const Edge* edge = mEdgeList.begin();
const Edge* end = mEdgeList.end();
for (; edge != end; edge++) {
if (cList->count >= CollisionList::MaxCollisions)
return;
const Point3F& s2 = mVertexList[edge->vertex[0]];
const Point3F& e2 = mVertexList[edge->vertex[1]];
VectorF n,v2 = e2 - s2;
// Normal to both lines
mCross(v1,v2,&n);
if (n.lenSquared() < 1e-9) {
continue;
}
Point3F is;
Point3F it;
F32 distance = sqrDistanceEdges(s1, e1,
s2, e2,
&is, &it);
if (distance > tolSquared)
continue;
distance = mSqrt(distance);
// Return a collision
Collision& info = cList->collision[cList->count++];
AssertFatal(cList->count <= CollisionList::MaxCollisions, "Error, exceeded max collisions!");
info.point = is;
info.normal = is - it;
if (info.normal.isZero())
info.normal.set(0, 0, 1);
else
info.normal.normalize();
info.distance = distance;
info.material = material;
info.object = object;
}
}
//----------------------------------------------------------------------------
// Collision State management
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
CollisionStateList::CollisionStateList()
{
mPrev = mNext = this;
mState = NULL;
}
void CollisionStateList::linkAfter(CollisionStateList* ptr)
{
mPrev = ptr;
mNext = ptr->mNext;
ptr->mNext = this;
mNext->mPrev = this;
}
void CollisionStateList::unlink()
{
mPrev->mNext = mNext;
mNext->mPrev = mPrev;
mPrev = mNext = this;
}
CollisionStateList* CollisionStateList::alloc()
{
if (!sFreeList.isEmpty()) {
CollisionStateList* nxt = sFreeList.mNext;
nxt->unlink();
nxt->mState = NULL;
return nxt;
}
return constructInPlace((CollisionStateList*)sChunker.alloc(sizeof(CollisionStateList)));
}
void CollisionStateList::free()
{
unlink();
linkAfter(&sFreeList);
}
//----------------------------------------------------------------------------
CollisionWorkingList::CollisionWorkingList()
{
wLink.mPrev = wLink.mNext = this;
rLink.mPrev = rLink.mNext = this;
}
void CollisionWorkingList::wLinkAfter(CollisionWorkingList* ptr)
{
wLink.mPrev = ptr;
wLink.mNext = ptr->wLink.mNext;
ptr->wLink.mNext = this;
wLink.mNext->wLink.mPrev = this;
}
void CollisionWorkingList::rLinkAfter(CollisionWorkingList* ptr)
{
rLink.mPrev = ptr;
rLink.mNext = ptr->rLink.mNext;
ptr->rLink.mNext = this;
rLink.mNext->rLink.mPrev = this;
}
void CollisionWorkingList::unlink()
{
wLink.mPrev->wLink.mNext = wLink.mNext;
wLink.mNext->wLink.mPrev = wLink.mPrev;
wLink.mPrev = wLink.mNext = this;
rLink.mPrev->rLink.mNext = rLink.mNext;
rLink.mNext->rLink.mPrev = rLink.mPrev;
rLink.mPrev = rLink.mNext = this;
}
CollisionWorkingList* CollisionWorkingList::alloc()
{
if (sFreeList.wLink.mNext != &sFreeList) {
CollisionWorkingList* nxt = sFreeList.wLink.mNext;
nxt->unlink();
return nxt;
}
return constructInPlace((CollisionWorkingList*)sChunker.alloc(sizeof(CollisionWorkingList)));
}
void CollisionWorkingList::free()
{
unlink();
wLinkAfter(&sFreeList);
}
//----------------------------------------------------------------------------
// Convex Base Class
//----------------------------------------------------------------------------
U32 Convex::sTag = -1;
//----------------------------------------------------------------------------
Convex::Convex()
{
mNext = mPrev = this;
mTag = 0;
}
Convex::~Convex()
{
// Unlink from Convex Database
unlink();
// Delete collision states
while (mList.mNext != &mList)
delete mList.mNext->mState;
// Free up working list
while (mWorking.wLink.mNext != &mWorking)
mWorking.wLink.mNext->free();
// Free up references
while (mReference.rLink.mNext != &mReference)
mReference.rLink.mNext->free();
}
//----------------------------------------------------------------------------
void Convex::collectGarbage()
{
// Delete unreferenced Convex Objects
for (Convex* itr = mNext; itr != this; itr = itr->mNext) {
if (itr->mReference.rLink.mNext == &itr->mReference) {
Convex* ptr = itr;
itr = itr->mPrev;
delete ptr;
}
}
}
void Convex::nukeList()
{
// Delete all Convex Objects
for (Convex* itr = mNext; itr != this; itr = itr->mNext) {
Convex* ptr = itr;
itr = itr->mPrev;
delete ptr;
}
}
void Convex::registerObject(Convex *convex)
{
convex->linkAfter(this);
}
//----------------------------------------------------------------------------
void Convex::linkAfter(Convex* ptr)
{
mPrev = ptr;
mNext = ptr->mNext;
ptr->mNext = this;
mNext->mPrev = this;
}
void Convex::unlink()
{
mPrev->mNext = mNext;
mNext->mPrev = mPrev;
mPrev = mNext = this;
}
//----------------------------------------------------------------------------
Point3F Convex::support(const VectorF&) const
{
return Point3F(0,0,0);
}
void Convex::getFeatures(const MatrixF&,const VectorF&,ConvexFeature* f)
{
f->object = NULL;
}
const MatrixF& Convex::getTransform() const
{
return mObject->getTransform();
}
const Point3F& Convex::getScale() const
{
return mObject->getScale();
}
Box3F Convex::getBoundingBox() const
{
return mObject->getWorldBox();
}
Box3F Convex::getBoundingBox(const MatrixF& mat, const Point3F& scale) const
{
Box3F wBox = mObject->getObjBox();
wBox.min.convolve(scale);
wBox.max.convolve(scale);
mat.mul(wBox);
return wBox;
}
//----------------------------------------------------------------------------
void Convex::addToWorkingList(Convex* ptr)
{
CollisionWorkingList* cl = CollisionWorkingList::alloc();
cl->wLinkAfter(&mWorking);
cl->rLinkAfter(&ptr->mReference);
cl->mConvex = ptr;
};
//----------------------------------------------------------------------------
void Convex::updateWorkingList(const Box3F& box, const U32 colMask)
{
sTag++;
// Clear objects off the working list that are no longer intersecting
for (CollisionWorkingList* itr = mWorking.wLink.mNext; itr != &mWorking; itr = itr->wLink.mNext) {
itr->mConvex->mTag = sTag;
if (!box.isOverlapped(itr->mConvex->getBoundingBox())) {
CollisionWorkingList* cl = itr;
itr = itr->wLink.mPrev;
cl->free();
}
}
// Special processing for the terrain and interiors...
AssertFatal(mObject->getContainer(), "Must be in a container!");
SimpleQueryList sql;
mObject->getContainer()->findObjects(box, colMask,
SimpleQueryList::insertionCallback, U32(&sql));
for (U32 i = 0; i < sql.mList.size(); i++)
sql.mList[i]->buildConvex(box, this);
}
// ---------------------------------------------------------------------------
void Convex::updateStateList(const MatrixF& mat, const Point3F& scale, const Point3F* displacement)
{
Box3F box1 = getBoundingBox(mat, scale);
box1.min -= Point3F(1, 1, 1);
box1.max += Point3F(1, 1, 1);
if (displacement) {
Point3F oldMin = box1.min;
Point3F oldMax = box1.max;
box1.min.setMin(oldMin + *displacement);
box1.min.setMin(oldMax + *displacement);
box1.max.setMax(oldMin + *displacement);
box1.max.setMax(oldMax + *displacement);
}
sTag++;
// Destroy states which are no longer intersecting
for (CollisionStateList* itr = mList.mNext; itr != &mList; itr = itr->mNext) {
Convex* cv = (itr->mState->a == this)? itr->mState->b: itr->mState->a;
cv->mTag = sTag;
if (!box1.isOverlapped(cv->getBoundingBox())) {
CollisionState* cs = itr->mState;
itr = itr->mPrev;
delete cs;
}
}
// Add collision states for new overlapping objects
for (CollisionWorkingList* itr0 = mWorking.wLink.mNext; itr0 != &mWorking; itr0 = itr0->wLink.mNext) {
register Convex* cv = itr0->mConvex;
if (cv->mTag != sTag && box1.isOverlapped(cv->getBoundingBox())) {
CollisionState* state = new CollisionState;
state->set(this,cv,mat,cv->getTransform());
state->mLista->linkAfter(&mList);
state->mListb->linkAfter(&cv->mList);
}
}
}
//----------------------------------------------------------------------------
CollisionState* Convex::findClosestState(const MatrixF& mat, const Point3F& scale)
{
updateStateList(mat, scale);
F32 dist = +1E30;
CollisionState *st = 0;
MatrixF axform = mat;
axform.scale(scale);
for (CollisionStateList* itr = mList.mNext; itr != &mList; itr = itr->mNext) {
CollisionState* state = itr->mState;
if (state->mLista != itr)
state->swap();
MatrixF bxform = state->b->getTransform();
bxform.scale(state->b->getScale());
F32 dd = state->distance(axform, bxform);
if (dd < dist) {
dist = dd;
st = state;
}
}
return st;
}
//----------------------------------------------------------------------------
CollisionState* Convex::findClosestStateBounded(const MatrixF& mat, const Point3F& scale, const F32 dontCareDist)
{
updateStateList(mat, scale);
F32 dist = +1E30;
CollisionState *st = 0;
MatrixF axform = mat;
axform.scale(scale);
MatrixF axforminv(true);
MatrixF temp(mat);
axforminv.scale(Point3F(1.0f/scale.x,
1.0f/scale.y,
1.0f/scale.z));
temp.affineInverse();
axforminv.mul(temp);
for (CollisionStateList* itr = mList.mNext; itr != &mList; itr = itr->mNext) {
CollisionState* state = itr->mState;
if (state->mLista != itr)
state->swap();
MatrixF bxform = state->b->getTransform();
temp = bxform;
Point3F bscale = state->b->getScale();
bxform.scale(bscale);
MatrixF bxforminv(true);
bxforminv.scale(Point3F(1.0f/bscale.x,
1.0f/bscale.y,
1.0f/bscale.z));
temp.affineInverse();
bxforminv.mul(temp);
F32 dd = state->distanceBounded(axform, bxform, dontCareDist, &axforminv, &bxforminv);
if (dd < dist) {
dist = dd;
st = state;
}
}
if (dist < dontCareDist)
return st;
else
return NULL;
}
//----------------------------------------------------------------------------
bool Convex::getCollisionInfo(const MatrixF& mat, const Point3F& scale, CollisionList* cList,F32 tol)
{
for (CollisionStateList* itr = mList.mNext; itr != &mList; itr = itr->mNext) {
CollisionState* state = itr->mState;
if (state->mLista != itr)
state->swap();
if (state->dist < tol) {
ConvexFeature fa,fb;
VectorF v;
MatrixF imat = mat;
imat.scale(scale);
imat.inverse();
imat.mulV(-state->v,&v);
getFeatures(mat,v,&fa);
imat = state->b->getTransform();
imat.scale(state->b->getScale());
MatrixF bxform = imat;
imat.inverse();
imat.mulV(state->v,&v);
state->b->getFeatures(bxform,v,&fb);
fa.collide(fb,cList,tol);
}
}
return (cList->count != 0);
}
void Convex::getPolyList(AbstractPolyList*)
{
}
// This function taken from:
// 3D Game Engine Design, David H. Eberly
// Ref page 43. Code from included cd.
//
F32 sqrDistanceEdges(const Point3F& start0,
const Point3F& end0,
const Point3F& start1,
const Point3F& end1,
Point3F* is,
Point3F* it)
{
Point3F direction0 = end0 - start0;
Point3F direction1 = end1 - start1;
Point3F kDiff = start0 - start1;
F32 fA00 = direction0.lenSquared();
F32 fA01 = -mDot(direction0, direction1);
F32 fA11 = direction1.lenSquared();
F32 fB0 = mDot(kDiff, direction0);
F32 fC = kDiff.lenSquared();
F32 fDet = mAbs(fA00*fA11 - fA01*fA01);
F32 fB1, fS, fT, fSqrDist, fTmp;
if ( fDet >= 0.00001 )
{
// line segments are not parallel
fB1 = -mDot(kDiff, direction1);
fS = fA01*fB1-fA11*fB0;
fT = fA01*fB0-fA00*fB1;
if ( fS >= 0.0 )
{
if ( fS <= fDet )
{
if ( fT >= 0.0 )
{
if ( fT <= fDet ) // region 0 (interior)
{
// minimum at two interior points of 3D lines
F32 fInvDet = 1.0/fDet;
fS *= fInvDet;
fT *= fInvDet;
fSqrDist = (fS*(fA00*fS + fA01*fT + 2.0*fB0) +
fT*(fA01*fS + fA11*fT + 2.0*fB1) + fC);
}
else // region 3 (side)
{
fT = 1.0;
fTmp = fA01 + fB0;
if ( fTmp >= 0.0 )
{
fS = 0.0;
fSqrDist = fA11 + 2.0*fB1 + fC;
}
else if ( -fTmp >= fA00 )
{
fS = 1.0;
fSqrDist = fA00 + fA11 + fC + 2.0*(fB1 + fTmp);
}
else
{
fS = -fTmp/fA00;
fSqrDist = fTmp*fS+fA11+2.0*fB1+fC;
}
}
}
else // region 7 (side)
{
fT = 0.0;
if ( fB0 >= 0.0 )
{
fS = 0.0;
fSqrDist = fC;
}
else if ( -fB0 >= fA00 )
{
fS = 1.0;
fSqrDist = fA00+2.0*fB0+fC;
}
else
{
fS = -fB0/fA00;
fSqrDist = fB0*fS+fC;
}
}
}
else
{
if ( fT >= 0.0 )
{
if ( fT <= fDet ) // region 1 (side)
{
fS = 1.0;
fTmp = fA01+fB1;
if ( fTmp >= 0.0 )
{
fT = 0.0;
fSqrDist = fA00+2.0*fB0+fC;
}
else if ( -fTmp >= fA11 )
{
fT = 1.0;
fSqrDist = fA00+fA11+fC+2.0*(fB0+fTmp);
}
else
{
fT = -fTmp/fA11;
fSqrDist = fTmp*fT+fA00+2.0*fB0+fC;
}
}
else // region 2 (corner)
{
fTmp = fA01+fB0;
if ( -fTmp <= fA00 )
{
fT = 1.0;
if ( fTmp >= 0.0 )
{
fS = 0.0;
fSqrDist = fA11+2.0*fB1+fC;
}
else
{
fS = -fTmp/fA00;
fSqrDist = fTmp*fS+fA11+2.0*fB1+fC;
}
}
else
{
fS = 1.0;
fTmp = fA01+fB1;
if ( fTmp >= 0.0 )
{
fT = 0.0;
fSqrDist = fA00+2.0*fB0+fC;
}
else if ( -fTmp >= fA11 )
{
fT = 1.0;
fSqrDist = fA00+fA11+fC+2.0*(fB0+fTmp);
}
else
{
fT = -fTmp/fA11;
fSqrDist = fTmp*fT+fA00+2.0*fB0+fC;
}
}
}
}
else // region 8 (corner)
{
if ( -fB0 < fA00 )
{
fT = 0.0;
if ( fB0 >= 0.0 )
{
fS = 0.0;
fSqrDist = fC;
}
else
{
fS = -fB0/fA00;
fSqrDist = fB0*fS+fC;
}
}
else
{
fS = 1.0;
fTmp = fA01+fB1;
if ( fTmp >= 0.0 )
{
fT = 0.0;
fSqrDist = fA00+2.0*fB0+fC;
}
else if ( -fTmp >= fA11 )
{
fT = 1.0;
fSqrDist = fA00+fA11+fC+2.0*(fB0+fTmp);
}
else
{
fT = -fTmp/fA11;
fSqrDist = fTmp*fT+fA00+2.0*fB0+fC;
}
}
}
}
}
else
{
if ( fT >= 0.0 )
{
if ( fT <= fDet ) // region 5 (side)
{
fS = 0.0;
if ( fB1 >= 0.0 )
{
fT = 0.0;
fSqrDist = fC;
}
else if ( -fB1 >= fA11 )
{
fT = 1.0;
fSqrDist = fA11+2.0*fB1+fC;
}
else
{
fT = -fB1/fA11;
fSqrDist = fB1*fT+fC;
}
}
else // region 4 (corner)
{
fTmp = fA01+fB0;
if ( fTmp < 0.0 )
{
fT = 1.0;
if ( -fTmp >= fA00 )
{
fS = 1.0;
fSqrDist = fA00+fA11+fC+2.0*(fB1+fTmp);
}
else
{
fS = -fTmp/fA00;
fSqrDist = fTmp*fS+fA11+2.0*fB1+fC;
}
}
else
{
fS = 0.0;
if ( fB1 >= 0.0 )
{
fT = 0.0;
fSqrDist = fC;
}
else if ( -fB1 >= fA11 )
{
fT = 1.0;
fSqrDist = fA11+2.0*fB1+fC;
}
else
{
fT = -fB1/fA11;
fSqrDist = fB1*fT+fC;
}
}
}
}
else // region 6 (corner)
{
if ( fB0 < 0.0 )
{
fT = 0.0;
if ( -fB0 >= fA00 )
{
fS = 1.0;
fSqrDist = fA00+2.0*fB0+fC;
}
else
{
fS = -fB0/fA00;
fSqrDist = fB0*fS+fC;
}
}
else
{
fS = 0.0;
if ( fB1 >= 0.0 )
{
fT = 0.0;
fSqrDist = fC;
}
else if ( -fB1 >= fA11 )
{
fT = 1.0;
fSqrDist = fA11+2.0*fB1+fC;
}
else
{
fT = -fB1/fA11;
fSqrDist = fB1*fT+fC;
}
}
}
}
}
else
{
// line segments are parallel
if ( fA01 > 0.0 )
{
// direction vectors form an obtuse angle
if ( fB0 >= 0.0 )
{
fS = 0.0;
fT = 0.0;
fSqrDist = fC;
}
else if ( -fB0 <= fA00 )
{
fS = -fB0/fA00;
fT = 0.0;
fSqrDist = fB0*fS+fC;
}
else
{
fB1 = -mDot(kDiff, direction1);
fS = 1.0;
fTmp = fA00+fB0;
if ( -fTmp >= fA01 )
{
fT = 1.0;
fSqrDist = fA00+fA11+fC+2.0*(fA01+fB0+fB1);
}
else
{
fT = -fTmp/fA01;
fSqrDist = fA00+2.0*fB0+fC+fT*(fA11*fT+2.0*(fA01+fB1));
}
}
}
else
{
// direction vectors form an acute angle
if ( -fB0 >= fA00 )
{
fS = 1.0;
fT = 0.0;
fSqrDist = fA00+2.0*fB0+fC;
}
else if ( fB0 <= 0.0 )
{
fS = -fB0/fA00;
fT = 0.0;
fSqrDist = fB0*fS+fC;
}
else
{
fB1 = -mDot(kDiff, direction1);
fS = 0.0;
if ( fB0 >= -fA01 )
{
fT = 1.0;
fSqrDist = fA11+2.0*fB1+fC;
}
else
{
fT = -fB0/fA01;
fSqrDist = fC+fT*(2.0*fB1+fA11*fT);
}
}
}
}
*is = start0 + direction0 * fS;
*it = start1 + direction1 * fT;
return mFabs(fSqrDist);
}