Files
XCEngine/engine/third_party/physx/source/geomutils/src/contact/GuContactSphereMesh.cpp

622 lines
20 KiB
C++

// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of NVIDIA CORPORATION nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Copyright (c) 2008-2025 NVIDIA Corporation. All rights reserved.
// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
#include "geomutils/PxContactBuffer.h"
#include "common/PxRenderOutput.h"
#include "GuDistancePointTriangle.h"
#include "GuContactMethodImpl.h"
#include "GuFeatureCode.h"
#include "GuMidphaseInterface.h"
#include "GuEntityReport.h"
#include "GuHeightFieldUtil.h"
#include "GuBox.h"
#include "foundation/PxSort.h"
#define DEBUG_RENDER_MESHCONTACTS 0
using namespace physx;
using namespace Gu;
static const bool gDrawTouchedTriangles = false;
static void outputErrorMessage()
{
#if PX_CHECKED
PxGetFoundation().error(PxErrorCode::eINTERNAL_ERROR, PX_FL, "Dropping contacts in sphere vs mesh: exceeded limit of 256 ");
#endif
}
///////////////////////////////////////////////////////////////////////////////
// PT: a customized version that also returns the feature code
static PxVec3 closestPtPointTriangle(const PxVec3& p, const PxVec3& a, const PxVec3& b, const PxVec3& c, float& s, float& t, FeatureCode& fc)
{
// Check if P in vertex region outside A
const PxVec3 ab = b - a;
const PxVec3 ac = c - a;
const PxVec3 ap = p - a;
const float d1 = ab.dot(ap);
const float d2 = ac.dot(ap);
if(d1<=0.0f && d2<=0.0f)
{
s = 0.0f;
t = 0.0f;
fc = FC_VERTEX0;
return a; // Barycentric coords 1,0,0
}
// Check if P in vertex region outside B
const PxVec3 bp = p - b;
const float d3 = ab.dot(bp);
const float d4 = ac.dot(bp);
if(d3>=0.0f && d4<=d3)
{
s = 1.0f;
t = 0.0f;
fc = FC_VERTEX1;
return b; // Barycentric coords 0,1,0
}
// Check if P in edge region of AB, if so return projection of P onto AB
const float vc = d1*d4 - d3*d2;
if(vc<=0.0f && d1>=0.0f && d3<=0.0f)
{
const float v = d1 / (d1 - d3);
s = v;
t = 0.0f;
fc = FC_EDGE01;
return a + v * ab; // barycentric coords (1-v, v, 0)
}
// Check if P in vertex region outside C
const PxVec3 cp = p - c;
const float d5 = ab.dot(cp);
const float d6 = ac.dot(cp);
if(d6>=0.0f && d5<=d6)
{
s = 0.0f;
t = 1.0f;
fc = FC_VERTEX2;
return c; // Barycentric coords 0,0,1
}
// Check if P in edge region of AC, if so return projection of P onto AC
const float vb = d5*d2 - d1*d6;
if(vb<=0.0f && d2>=0.0f && d6<=0.0f)
{
const float w = d2 / (d2 - d6);
s = 0.0f;
t = w;
fc = FC_EDGE20;
return a + w * ac; // barycentric coords (1-w, 0, w)
}
// Check if P in edge region of BC, if so return projection of P onto BC
const float va = d3*d6 - d5*d4;
if(va<=0.0f && (d4-d3)>=0.0f && (d5-d6)>=0.0f)
{
const float w = (d4-d3) / ((d4 - d3) + (d5-d6));
s = 1.0f-w;
t = w;
fc = FC_EDGE12;
return b + w * (c-b); // barycentric coords (0, 1-w, w)
}
// P inside face region. Compute Q through its barycentric coords (u,v,w)
const float denom = 1.0f / (va + vb + vc);
const float v = vb * denom;
const float w = vc * denom;
s = v;
t = w;
fc = FC_FACE;
return a + ab*v + ac*w;
}
///////////////////////////////////////////////////////////////////////////////
// PT: we use a separate structure to make sorting faster
struct SortKey
{
float mSquareDist;
PxU32 mIndex;
PX_FORCE_INLINE bool operator < (const SortKey& data) const
{
return mSquareDist < data.mSquareDist;
}
};
struct TriangleData
{
PxVec3 mDelta;
FeatureCode mFC;
PxU32 mTriangleIndex;
PxU32 mVRef[3];
};
struct CachedTriangleIndices
{
PxU32 mVRef[3];
};
static PX_FORCE_INLINE bool validateSquareDist(PxReal squareDist)
{
return squareDist>0.0001f;
}
static bool validateEdge(PxU32 vref0, PxU32 vref1, const CachedTriangleIndices* cachedTris, PxU32 nbCachedTris)
{
while(nbCachedTris--)
{
const CachedTriangleIndices& inds = *cachedTris++;
const PxU32 vi0 = inds.mVRef[0];
const PxU32 vi1 = inds.mVRef[1];
const PxU32 vi2 = inds.mVRef[2];
if(vi0==vref0)
{
if(vi1==vref1 || vi2==vref1)
return false;
}
else if(vi1==vref0)
{
if(vi0==vref1 || vi2==vref1)
return false;
}
else if(vi2==vref0)
{
if(vi1==vref1 || vi0==vref1)
return false;
}
}
return true;
}
static bool validateVertex(PxU32 vref, const CachedTriangleIndices* cachedTris, PxU32 nbCachedTris)
{
while(nbCachedTris--)
{
const CachedTriangleIndices& inds = *cachedTris++;
if(inds.mVRef[0]==vref || inds.mVRef[1]==vref || inds.mVRef[2]==vref)
return false;
}
return true;
}
namespace
{
class NullAllocator
{
public:
PX_FORCE_INLINE NullAllocator() { }
PX_FORCE_INLINE void* allocate(size_t, const char*, int) { return NULL; }
PX_FORCE_INLINE void deallocate(void*) { }
};
struct SphereMeshContactGeneration
{
const PxSphereGeometry& mShapeSphere;
const PxTransform& mTransform0;
const PxTransform& mTransform1;
PxContactBuffer& mContactBuffer;
const PxVec3& mSphereCenterShape1Space;
PxF32 mInflatedRadius2;
PxU32 mNbDelayed;
TriangleData mSavedData[PxContactBuffer::MAX_CONTACTS];
SortKey mSortKey[PxContactBuffer::MAX_CONTACTS];
PxU32 mNbCachedTris;
CachedTriangleIndices mCachedTris[PxContactBuffer::MAX_CONTACTS];
PxRenderOutput* mRenderOutput;
SphereMeshContactGeneration(const PxSphereGeometry& shapeSphere, const PxTransform& transform0, const PxTransform& transform1,
PxContactBuffer& contactBuffer, const PxVec3& sphereCenterShape1Space, PxF32 inflatedRadius,
PxRenderOutput* renderOutput) :
mShapeSphere (shapeSphere),
mTransform0 (transform0),
mTransform1 (transform1),
mContactBuffer (contactBuffer),
mSphereCenterShape1Space (sphereCenterShape1Space),
mInflatedRadius2 (inflatedRadius*inflatedRadius),
mNbDelayed (0),
mNbCachedTris (0),
mRenderOutput (renderOutput)
{
}
PX_FORCE_INLINE void cacheTriangle(PxU32 ref0, PxU32 ref1, PxU32 ref2)
{
const PxU32 nb = mNbCachedTris++;
mCachedTris[nb].mVRef[0] = ref0;
mCachedTris[nb].mVRef[1] = ref1;
mCachedTris[nb].mVRef[2] = ref2;
}
PX_FORCE_INLINE void addContact(const PxVec3& d, PxReal squareDist, PxU32 triangleIndex)
{
float dist;
PxVec3 delta;
if(validateSquareDist(squareDist))
{
// PT: regular contact. Normalize 'delta'.
dist = PxSqrt(squareDist);
delta = d / dist;
}
else
{
// PT: singular contact: 'd' is the non-unit triangle's normal in this case.
dist = 0.0f;
delta = -d.getNormalized();
}
const PxVec3 worldNormal = -mTransform1.rotate(delta);
const PxVec3 localHit = mSphereCenterShape1Space + mShapeSphere.radius*delta;
const PxVec3 hit = mTransform1.transform(localHit);
if(!mContactBuffer.contact(hit, worldNormal, dist - mShapeSphere.radius, triangleIndex))
outputErrorMessage();
}
void processTriangle(PxU32 triangleIndex, const PxVec3& v0, const PxVec3& v1, const PxVec3& v2, const PxU32* vertInds)
{
// PT: compute closest point between sphere center and triangle
PxReal u, v;
FeatureCode fc;
const PxVec3 cp = closestPtPointTriangle(mSphereCenterShape1Space, v0, v1, v2, u, v, fc);
// PT: compute 'delta' vector between closest point and sphere center
const PxVec3 delta = cp - mSphereCenterShape1Space;
const PxReal squareDist = delta.magnitudeSquared();
if(squareDist >= mInflatedRadius2)
return;
// PT: backface culling without the normalize
// PT: TODO: consider doing before the pt-triangle distance test if it's cheaper
// PT: TODO: e0/e1 already computed in closestPtPointTriangle
const PxVec3 e0 = v1 - v0;
const PxVec3 e1 = v2 - v0;
const PxVec3 planeNormal = e0.cross(e1);
const PxF32 planeD = planeNormal.dot(v0); // PT: actually -d compared to PxcPlane
if(planeNormal.dot(mSphereCenterShape1Space) < planeD)
return;
// PT: for a regular contact, 'delta' is non-zero (and so is 'squareDist'). However when the sphere's center exactly touches
// the triangle, then both 'delta' and 'squareDist' become zero. This needs to be handled as a special case to avoid dividing
// by zero. We will use the triangle's normal as a contact normal in this special case.
//
// 'validateSquareDist' is called twice because there are conflicting goals here. We could call it once now and already
// compute the proper data for generating the contact. But this would mean doing a square-root and a division right here,
// even when the contact is not actually needed in the end. We could also call it only once in "addContact', but the plane's
// normal would not always be available (in case of delayed contacts), and thus it would need to be either recomputed (slower)
// or stored within 'TriangleData' (using more memory). Calling 'validateSquareDist' twice is a better option overall.
PxVec3 d;
if(validateSquareDist(squareDist))
d = delta;
else
d = planeNormal;
if(fc==FC_FACE)
{
addContact(d, squareDist, triangleIndex);
if(mNbCachedTris<PxContactBuffer::MAX_CONTACTS)
cacheTriangle(vertInds[0], vertInds[1], vertInds[2]);
}
else
{
if(mNbDelayed<PxContactBuffer::MAX_CONTACTS)
{
const PxU32 index = mNbDelayed++;
mSortKey[index].mSquareDist = squareDist;
mSortKey[index].mIndex = index;
TriangleData* saved = mSavedData + index;
saved->mDelta = d;
saved->mVRef[0] = vertInds[0];
saved->mVRef[1] = vertInds[1];
saved->mVRef[2] = vertInds[2];
saved->mFC = fc;
saved->mTriangleIndex = triangleIndex;
}
else outputErrorMessage();
}
}
void generateLastContacts()
{
const PxU32 count = mNbDelayed;
if(!count)
return;
PxSort(mSortKey, count, PxLess<SortKey>(), NullAllocator(), PxContactBuffer::MAX_CONTACTS);
TriangleData* touchedTris = mSavedData;
for(PxU32 i=0;i<count;i++)
{
const TriangleData& data = touchedTris[mSortKey[i].mIndex];
const PxU32 ref0 = data.mVRef[0];
const PxU32 ref1 = data.mVRef[1];
const PxU32 ref2 = data.mVRef[2];
bool generateContact = false;
switch(data.mFC)
{
case FC_VERTEX0:
generateContact = ::validateVertex(ref0, mCachedTris, mNbCachedTris);
break;
case FC_VERTEX1:
generateContact = ::validateVertex(ref1, mCachedTris, mNbCachedTris);
break;
case FC_VERTEX2:
generateContact = ::validateVertex(ref2, mCachedTris, mNbCachedTris);
break;
case FC_EDGE01:
generateContact = ::validateEdge(ref0, ref1, mCachedTris, mNbCachedTris);
break;
case FC_EDGE12:
generateContact = ::validateEdge(ref1, ref2, mCachedTris, mNbCachedTris);
break;
case FC_EDGE20:
generateContact = ::validateEdge(ref0, ref2, mCachedTris, mNbCachedTris);
break;
case FC_FACE:
case FC_UNDEFINED:
PX_ASSERT(0); // PT: should not be possible
break;
};
if(generateContact)
addContact(data.mDelta, mSortKey[i].mSquareDist, data.mTriangleIndex);
if(mNbCachedTris<PxContactBuffer::MAX_CONTACTS)
cacheTriangle(ref0, ref1, ref2);
else
outputErrorMessage();
}
}
private:
SphereMeshContactGeneration& operator=(const SphereMeshContactGeneration&);
};
struct SphereMeshContactGenerationCallback_NoScale : MeshHitCallback<PxGeomRaycastHit>
{
SphereMeshContactGeneration mGeneration;
const TriangleMesh& mMeshData;
SphereMeshContactGenerationCallback_NoScale(const TriangleMesh& meshData, const PxSphereGeometry& shapeSphere,
const PxTransform& transform0, const PxTransform& transform1, PxContactBuffer& contactBuffer,
const PxVec3& sphereCenterShape1Space, PxF32 inflatedRadius, PxRenderOutput* renderOutput
) : MeshHitCallback<PxGeomRaycastHit> (CallbackMode::eMULTIPLE),
mGeneration (shapeSphere, transform0, transform1, contactBuffer, sphereCenterShape1Space, inflatedRadius, renderOutput),
mMeshData (meshData)
{
}
virtual ~SphereMeshContactGenerationCallback_NoScale()
{
mGeneration.generateLastContacts();
}
virtual PxAgain processHit(
const PxGeomRaycastHit& hit, const PxVec3& v0, const PxVec3& v1, const PxVec3& v2, PxReal&, const PxU32* vinds)
{
if(gDrawTouchedTriangles)
{
(*mGeneration.mRenderOutput) << 0xffffffff;
(*mGeneration.mRenderOutput) << PxMat44(PxIdentity);
const PxVec3 wp0 = mGeneration.mTransform1.transform(v0);
const PxVec3 wp1 = mGeneration.mTransform1.transform(v1);
const PxVec3 wp2 = mGeneration.mTransform1.transform(v2);
mGeneration.mRenderOutput->outputSegment(wp0, wp1);
mGeneration.mRenderOutput->outputSegment(wp1, wp2);
mGeneration.mRenderOutput->outputSegment(wp2, wp0);
}
mGeneration.processTriangle(hit.faceIndex, v0, v1, v2, vinds);
return true;
}
protected:
SphereMeshContactGenerationCallback_NoScale &operator=(const SphereMeshContactGenerationCallback_NoScale &);
};
struct SphereMeshContactGenerationCallback_Scale : SphereMeshContactGenerationCallback_NoScale
{
const Cm::FastVertex2ShapeScaling& mMeshScaling;
SphereMeshContactGenerationCallback_Scale(const TriangleMesh& meshData, const PxSphereGeometry& shapeSphere,
const PxTransform& transform0, const PxTransform& transform1, const Cm::FastVertex2ShapeScaling& meshScaling,
PxContactBuffer& contactBuffer, const PxVec3& sphereCenterShape1Space, PxF32 inflatedRadius, PxRenderOutput* renderOutput
) : SphereMeshContactGenerationCallback_NoScale(meshData, shapeSphere,
transform0, transform1, contactBuffer, sphereCenterShape1Space, inflatedRadius, renderOutput),
mMeshScaling (meshScaling)
{
}
virtual ~SphereMeshContactGenerationCallback_Scale() {}
virtual PxAgain processHit(const PxGeomRaycastHit& hit, const PxVec3& v0, const PxVec3& v1, const PxVec3& v2, PxReal&, const PxU32* vinds)
{
PxVec3 verts[3];
getScaledVertices(verts, v0, v1, v2, false, mMeshScaling);
const PxU32* vertexIndices = vinds;
PxU32 localStorage[3];
if(mMeshScaling.flipsNormal())
{
localStorage[0] = vinds[0];
localStorage[1] = vinds[2];
localStorage[2] = vinds[1];
vertexIndices = localStorage;
}
if(gDrawTouchedTriangles)
{
(*mGeneration.mRenderOutput) << 0xffffffff;
(*mGeneration.mRenderOutput) << PxMat44(PxIdentity);
const PxVec3 wp0 = mGeneration.mTransform1.transform(verts[0]);
const PxVec3 wp1 = mGeneration.mTransform1.transform(verts[1]);
const PxVec3 wp2 = mGeneration.mTransform1.transform(verts[2]);
mGeneration.mRenderOutput->outputSegment(wp0, wp1);
mGeneration.mRenderOutput->outputSegment(wp1, wp2);
mGeneration.mRenderOutput->outputSegment(wp2, wp0);
}
mGeneration.processTriangle(hit.faceIndex, verts[0], verts[1], verts[2], vertexIndices);
return true;
}
protected:
SphereMeshContactGenerationCallback_Scale &operator=(const SphereMeshContactGenerationCallback_Scale &);
};
}
///////////////////////////////////////////////////////////////////////////////
bool Gu::contactSphereMesh(GU_CONTACT_METHOD_ARGS)
{
PX_UNUSED(cache);
const PxSphereGeometry& shapeSphere = checkedCast<PxSphereGeometry>(shape0);
const PxTriangleMeshGeometry& shapeMesh = checkedCast<PxTriangleMeshGeometry>(shape1);
// We must be in local space to use the cache
const PxVec3 sphereCenterInMeshSpace = transform1.transformInv(transform0.p);
const PxReal inflatedRadius = shapeSphere.radius + params.mContactDistance;
const TriangleMesh* meshData = _getMeshData(shapeMesh);
// mesh scale is not baked into cached verts
if(shapeMesh.scale.isIdentity())
{
SphereMeshContactGenerationCallback_NoScale callback(
*meshData, shapeSphere, transform0, transform1,
contactBuffer, sphereCenterInMeshSpace, inflatedRadius, renderOutput);
// PT: TODO: switch to sphere query here
const Box obb(sphereCenterInMeshSpace, PxVec3(inflatedRadius), PxMat33(PxIdentity));
Midphase::intersectOBB(meshData, obb, callback, true);
}
else
{
const Cm::FastVertex2ShapeScaling meshScaling(shapeMesh.scale);
SphereMeshContactGenerationCallback_Scale callback(
*meshData, shapeSphere, transform0, transform1,
meshScaling, contactBuffer, sphereCenterInMeshSpace, inflatedRadius, renderOutput);
PxVec3 obbCenter = sphereCenterInMeshSpace;
PxVec3 obbExtents = PxVec3(inflatedRadius);
PxMat33 obbRot(PxIdentity);
meshScaling.transformQueryBounds(obbCenter, obbExtents, obbRot);
const Box obb(obbCenter, obbExtents, obbRot);
Midphase::intersectOBB(meshData, obb, callback, true);
}
return contactBuffer.count > 0;
}
///////////////////////////////////////////////////////////////////////////////
namespace
{
struct SphereHeightfieldContactGenerationCallback : OverlapReport
{
SphereMeshContactGeneration mGeneration;
HeightFieldUtil& mHfUtil;
SphereHeightfieldContactGenerationCallback(
HeightFieldUtil& hfUtil,
const PxSphereGeometry& shapeSphere,
const PxTransform& transform0,
const PxTransform& transform1,
PxContactBuffer& contactBuffer,
const PxVec3& sphereCenterInMeshSpace,
PxF32 inflatedRadius,
PxRenderOutput* renderOutput
) :
mGeneration (shapeSphere, transform0, transform1, contactBuffer, sphereCenterInMeshSpace, inflatedRadius, renderOutput),
mHfUtil (hfUtil)
{
}
// PT: TODO: refactor/unify with similar code in other places
virtual bool reportTouchedTris(PxU32 nb, const PxU32* indices)
{
while(nb--)
{
const PxU32 triangleIndex = *indices++;
PxU32 vertIndices[3];
PxTriangle currentTriangle;
mHfUtil.getTriangle(mGeneration.mTransform1, currentTriangle, vertIndices, NULL, triangleIndex, false, false);
mGeneration.processTriangle(triangleIndex, currentTriangle.verts[0], currentTriangle.verts[1], currentTriangle.verts[2], vertIndices);
}
return true;
}
protected:
SphereHeightfieldContactGenerationCallback &operator=(const SphereHeightfieldContactGenerationCallback &);
};
}
bool Gu::contactSphereHeightfield(GU_CONTACT_METHOD_ARGS)
{
PX_UNUSED(cache);
PX_UNUSED(renderOutput);
const PxSphereGeometry& shapeSphere = checkedCast<PxSphereGeometry>(shape0);
const PxHeightFieldGeometry& shapeMesh = checkedCast<PxHeightFieldGeometry>(shape1);
HeightFieldUtil hfUtil(shapeMesh);
const PxReal inflatedRadius = shapeSphere.radius + params.mContactDistance;
PxBounds3 localBounds;
const PxVec3 localSphereCenter = getLocalSphereData(localBounds, transform0, transform1, inflatedRadius);
SphereHeightfieldContactGenerationCallback blockCallback(hfUtil, shapeSphere, transform0, transform1, contactBuffer, localSphereCenter, inflatedRadius, renderOutput);
hfUtil.overlapAABBTriangles(localBounds, blockCallback);
blockCallback.mGeneration.generateLastContacts();
return contactBuffer.count > 0;
}
///////////////////////////////////////////////////////////////////////////////