Revision control

- // 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-2026 NVIDIA Corporation. All rights reserved.
+ // Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
- // Copyright (c) 2001-2004 NovodeX AG. All rights reserved.
+ // SPDX-FileCopyrightText: Copyright (c) 2008-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ // SPDX-License-Identifier: Apache-2.0
#include "geomutils/PxContactBuffer.h"
#include "GuIntersectionEdgeEdge.h"
#include "GuDistanceSegmentTriangle.h"
#include "GuIntersectionRayTriangle.h"
#include "GuIntersectionTriangleBox.h"
#include "GuInternal.h"
#include "GuContactMethodImpl.h"
#include "GuFeatureCode.h"
#include "GuMidphaseInterface.h"
#include "GuEntityReport.h"
#include "GuHeightFieldUtil.h"
#include "GuConvexEdgeFlags.h"
#include "GuBox.h"
#include "CmMatrix34.h"
using namespace physx;
using namespace Gu;
using namespace Cm;
#define DEBUG_RENDER_MESHCONTACTS 0
#if DEBUG_RENDER_MESHCONTACTS
#include "PxPhysics.h"
#include "PxScene.h"
#endif
#define USE_AABB_TRI_CULLING
//#define USE_CAPSULE_TRI_PROJ_CULLING
//#define USE_CAPSULE_TRI_SAT_CULLING
#define VISUALIZE_TOUCHED_TRIS 0
#define VISUALIZE_CULLING_BOX 0
#if VISUALIZE_TOUCHED_TRIS
#include "PxRenderOutput.h"
#include "PxsContactManager.h"
#include "PxsContext.h"
static void gVisualizeLine(const PxVec3& a, const PxVec3& b, PxcNpThreadContext& context, PxU32 color=0xffffff)
{
PxMat44 m = PxMat44::identity();
PxRenderOutput& out = context.mRenderOutput;
out << color << m << PxRenderOutput::LINES << a << b;
}
static void gVisualizeTri(const PxVec3& a, const PxVec3& b, const PxVec3& c, PxcNpThreadContext& context, PxU32 color=0xffffff)
{
PxMat44 m = PxMat44::identity();
PxRenderOutput& out = context.mRenderOutput;
out << color << m << PxRenderOutput::TRIANGLES << a << b << c;
}
static PxU32 gColors[8] = { 0xff0000ff, 0xff00ff00, 0xffff0000,
0xff00ffff, 0xffff00ff, 0xffffff00,
0xff000080, 0xff008000};
#endif
static const float fatBoxEdgeCoeff = 0.01f;
static bool PxcTestAxis(const PxVec3& axis, const Segment& segment, PxReal radius,
const PxVec3* PX_RESTRICT triVerts, PxReal& depth)
{
// Project capsule
PxReal min0 = segment.p0.dot(axis);
PxReal max0 = segment.p1.dot(axis);
if(min0>max0) PxSwap(min0, max0);
min0 -= radius;
max0 += radius;
// Project triangle
float Min1, Max1;
{
Min1 = Max1 = triVerts[0].dot(axis);
const PxReal dp1 = triVerts[1].dot(axis);
Min1 = physx::intrinsics::selectMin(Min1, dp1);
Max1 = physx::intrinsics::selectMax(Max1, dp1);
const PxReal dp2 = triVerts[2].dot(axis);
Min1 = physx::intrinsics::selectMin(Min1, dp2);
Max1 = physx::intrinsics::selectMax(Max1, dp2);
}
// Test projections
if(max0<Min1 || Max1<min0)
return false;
const PxReal d0 = max0 - Min1;
PX_ASSERT(d0>=0.0f);
const PxReal d1 = Max1 - min0;
PX_ASSERT(d1>=0.0f);
depth = physx::intrinsics::selectMin(d0, d1);
return true;
}
PX_FORCE_INLINE static PxVec3 PxcComputeTriangleNormal(const PxVec3* PX_RESTRICT triVerts)
{
return ((triVerts[0]-triVerts[1]).cross(triVerts[0]-triVerts[2])).getNormalized();
}
PX_FORCE_INLINE static PxVec3 PxcComputeTriangleCenter(const PxVec3* PX_RESTRICT triVerts)
{
static const PxReal inv3 = 1.0f / 3.0f;
return (triVerts[0] + triVerts[1] + triVerts[2]) * inv3;
}
static bool PxcCapsuleTriOverlap3(PxU8 edgeFlags, const Segment& segment, PxReal radius, const PxVec3* PX_RESTRICT triVerts,
PxReal* PX_RESTRICT t=NULL, PxVec3* PX_RESTRICT pp=NULL)
{
PxReal penDepth = PX_MAX_REAL;
// Test normal
PxVec3 sep = PxcComputeTriangleNormal(triVerts);
if(!PxcTestAxis(sep, segment, radius, triVerts, penDepth))
return false;
// Test edges
// ML:: use the active edge flag instead of the concave flag
const PxU32 activeEdgeFlag[] = {ETD_CONVEX_EDGE_01, ETD_CONVEX_EDGE_12, ETD_CONVEX_EDGE_20};
const PxVec3 capsuleAxis = (segment.p1 - segment.p0).getNormalized();
for(PxU32 i=0;i<3;i++)
{
//bool active =((edgeFlags & ignoreEdgeFlag[i]) == 0);
if(edgeFlags & activeEdgeFlag[i])
{
const PxVec3 e0 = triVerts[i];
// const PxVec3 e1 = triVerts[(i+1)%3];
const PxVec3 e1 = triVerts[PxGetNextIndex3(i)];
const PxVec3 edge = e0 - e1;
PxVec3 cross = capsuleAxis.cross(edge);
if(!isAlmostZero(cross))
{
cross = cross.getNormalized();
PxReal d;
if(!PxcTestAxis(cross, segment, radius, triVerts, d))
return false;
if(d<penDepth)
{
penDepth = d;
sep = cross;
}
}
}
}
const PxVec3 capsuleCenter = segment.computeCenter();
const PxVec3 triCenter = PxcComputeTriangleCenter(triVerts);
const PxVec3 witness = capsuleCenter - triCenter;
if(sep.dot(witness) < 0.0f)
sep = -sep;
if(t) *t = penDepth;
if(pp) *pp = sep;
return true;
}
static void PxcGenerateVFContacts( const PxMat34& meshAbsPose, PxContactBuffer& contactBuffer, const Segment& segment,
const PxReal radius, const PxVec3* PX_RESTRICT triVerts, const PxVec3& normal,
PxU32 triangleIndex, PxReal contactDistance)
{
const PxVec3* PX_RESTRICT Ptr = &segment.p0;
for(PxU32 i=0;i<2;i++)
{
const PxVec3& Pos = Ptr[i];
PxReal t,u,v;
if(intersectRayTriangleCulling(Pos, -normal, triVerts[0], triVerts[1], triVerts[2], t, u, v, 1e-3f) && t < radius + contactDistance)
{
const PxVec3 Hit = meshAbsPose.transform(Pos - t * normal);
const PxVec3 wn = meshAbsPose.rotate(normal);
contactBuffer.contact(Hit, wn, t-radius, triangleIndex);
#if DEBUG_RENDER_MESHCONTACTS
PxScene *s; PxGetPhysics().getScenes(&s, 1, 0);
PxRenderOutput((PxRenderBufferImpl&)s->getRenderBuffer()) << PxRenderOutput::LINES << PxDebugColor::eARGB_BLUE // red
<< Hit << (Hit + wn * 10.0f);
#endif
}
}
}
// PT: PxcGenerateEEContacts2 uses a segment-triangle distance function, which breaks when the segment
// intersects the triangle, in which case you need to switch to a penetration-depth computation.
// If you don't do this thin capsules don't work.
static void PxcGenerateEEContacts( const PxMat34& meshAbsPose, PxContactBuffer& contactBuffer, const Segment& segment, const PxReal radius,
const PxVec3* PX_RESTRICT triVerts, const PxVec3& normal, PxU32 triangleIndex)
{
PxVec3 s0 = segment.p0;
PxVec3 s1 = segment.p1;
makeFatEdge(s0, s1, fatBoxEdgeCoeff);
for(PxU32 i=0;i<3;i++)
{
PxReal dist;
PxVec3 ip;
if(intersectEdgeEdge(triVerts[i], triVerts[PxGetNextIndex3(i)], -normal, s0, s1, dist, ip))
{
ip = meshAbsPose.transform(ip);
const PxVec3 wn = meshAbsPose.rotate(normal);
contactBuffer.contact(ip, wn, - (radius + dist), triangleIndex);
#if DEBUG_RENDER_MESHCONTACTS
PxScene *s; PxGetPhysics().getScenes(&s, 1, 0);
PxRenderOutput((PxRenderBufferImpl&)s->getRenderBuffer()) << PxRenderOutput::LINES << PxDebugColor::eARGB_BLUE // red
<< ip << (ip + wn * 10.0f);
#endif
}
}
}
static void PxcGenerateEEContacts2( const PxMat34& meshAbsPose, PxContactBuffer& contactBuffer, const Segment& segment, const PxReal radius,
const PxVec3* PX_RESTRICT triVerts, const PxVec3& normal, PxU32 triangleIndex, PxReal contactDistance)
{
PxVec3 s0 = segment.p0;
PxVec3 s1 = segment.p1;
makeFatEdge(s0, s1, fatBoxEdgeCoeff);
for(PxU32 i=0;i<3;i++)
{
PxReal dist;
PxVec3 ip;
if(intersectEdgeEdge(triVerts[i], triVerts[PxGetNextIndex3(i)], normal, s0, s1, dist, ip) && dist < radius+contactDistance)
{
ip = meshAbsPose.transform(ip);
const PxVec3 wn = meshAbsPose.rotate(normal);
contactBuffer.contact(ip, wn, dist - radius, triangleIndex);
#if DEBUG_RENDER_MESHCONTACTS
PxScene *s; PxGetPhysics().getScenes(&s, 1, 0);
PxRenderOutput((PxRenderBufferImpl&)s->getRenderBuffer()) << PxRenderOutput::LINES << PxDebugColor::eARGB_BLUE // red
<< ip << (ip + wn * 10.0f);
#endif
}
}
}
namespace
{
struct CapsuleMeshContactGeneration
{
PxContactBuffer& mContactBuffer;
const PxMat34 mMeshAbsPose;
const Segment& mMeshCapsule;
#ifdef USE_AABB_TRI_CULLING
PxVec3p mBC;
PxVec3p mBE;
#endif
PxReal mInflatedRadius;
PxReal mContactDistance;
PxReal mShapeCapsuleRadius;
CapsuleMeshContactGeneration(PxContactBuffer& contactBuffer, const PxTransform& transform1, const Segment& meshCapsule, PxReal inflatedRadius, PxReal contactDistance, PxReal shapeCapsuleRadius) :
mContactBuffer (contactBuffer),
mMeshAbsPose (Matrix34FromTransform(transform1)),
mMeshCapsule (meshCapsule),
mInflatedRadius (inflatedRadius),
mContactDistance (contactDistance),
mShapeCapsuleRadius (shapeCapsuleRadius)
{
PX_ASSERT(contactBuffer.count==0);
#ifdef USE_AABB_TRI_CULLING
mBC = (meshCapsule.p0 + meshCapsule.p1)*0.5f;
const PxVec3p be = (meshCapsule.p0 - meshCapsule.p1)*0.5f;
mBE.x = fabsf(be.x) + inflatedRadius;
mBE.y = fabsf(be.y) + inflatedRadius;
mBE.z = fabsf(be.z) + inflatedRadius;
#endif
}
void processTriangle(PxU32 triangleIndex, const PxTrianglePadded& tri, PxU8 extraData/*, const PxU32* vertInds*/)
{
#ifdef USE_AABB_TRI_CULLING
#if VISUALIZE_CULLING_BOX
{
PxRenderOutput& out = context.mRenderOutput;
const PxTransform idt(PxIdentity);
out << idt;
out << 0xffffffff;
out << PxDebugBox(mBC, mBE, true);
}
#endif
#endif
const PxVec3& p0 = tri.verts[0];
const PxVec3& p1 = tri.verts[1];
const PxVec3& p2 = tri.verts[2];
#ifdef USE_AABB_TRI_CULLING
// PT: this one is safe because triangle class is padded
// PT: TODO: is this test really needed? Not done in midphase already?
if(!intersectTriangleBox_Unsafe(mBC, mBE, p0, p1, p2))
return;
#endif
#ifdef USE_CAPSULE_TRI_PROJ_CULLING
PxVec3 triCenter = (p0 + p1 + p2)*0.33333333f;
PxVec3 delta = mBC - triCenter;
PxReal depth;
if(!PxcTestAxis(delta, mMeshCapsule, mInflatedRadius, tri.verts, depth))
return;
#endif
#if VISUALIZE_TOUCHED_TRIS
gVisualizeTri(p0, p1, p2, context, PxDebugColor::eARGB_RED);
#endif
#ifdef USE_CAPSULE_TRI_SAT_CULLING
PxVec3 SepAxis;
if(!PxcCapsuleTriOverlap3(extraData, mMeshCapsule, mInflatedRadius, tri.verts, NULL, &SepAxis))
return;
#endif
PxReal t,u,v;
const PxVec3 p1_p0 = p1 - p0;
const PxVec3 p2_p0 = p2 - p0;
const PxReal squareDist = distanceSegmentTriangleSquared(mMeshCapsule, p0, p1_p0, p2_p0, &t, &u, &v);
// PT: do cheaper test first!
if(squareDist >= mInflatedRadius*mInflatedRadius)
return;
// PT: backface culling without the normalize
// PT: TODO: consider doing before the segment-triangle distance test if it's cheaper
const PxVec3 planeNormal = p1_p0.cross(p2_p0);
const PxF32 planeD = planeNormal.dot(p0); // PT: actually -d compared to PxcPlane
if(planeNormal.dot(mBC) < planeD)
return;
if(squareDist > 0.001f*0.001f)
{
// Contact information
PxVec3 normal;
if(selectNormal(extraData, u, v))
{
normal = planeNormal.getNormalized();
}
else
{
const PxVec3 pointOnTriangle = computeBarycentricPoint(p0, p1, p2, u, v);
const PxVec3 pointOnSegment = mMeshCapsule.getPointAt(t);
normal = pointOnSegment - pointOnTriangle;
const PxReal l = normal.magnitude();
if(l == 0.0f)
return;
normal = normal / l;
}
PxcGenerateEEContacts2(mMeshAbsPose, mContactBuffer, mMeshCapsule, mShapeCapsuleRadius, tri.verts, normal, triangleIndex, mContactDistance);
PxcGenerateVFContacts(mMeshAbsPose, mContactBuffer, mMeshCapsule, mShapeCapsuleRadius, tri.verts, normal, triangleIndex, mContactDistance);
}
else
{
PxVec3 SepAxis;
if(!PxcCapsuleTriOverlap3(extraData, mMeshCapsule, mInflatedRadius, tri.verts, NULL, &SepAxis))
return;
PxcGenerateEEContacts(mMeshAbsPose, mContactBuffer, mMeshCapsule, mShapeCapsuleRadius, tri.verts, SepAxis, triangleIndex);
PxcGenerateVFContacts(mMeshAbsPose, mContactBuffer, mMeshCapsule, mShapeCapsuleRadius, tri.verts, SepAxis, triangleIndex, mContactDistance);
}
}
private:
CapsuleMeshContactGeneration& operator=(const CapsuleMeshContactGeneration&);
};
struct CapsuleMeshContactGenerationCallback_NoScale : MeshHitCallback<PxGeomRaycastHit>
{
CapsuleMeshContactGeneration mGeneration;
const TriangleMesh* mMeshData;
CapsuleMeshContactGenerationCallback_NoScale(
PxContactBuffer& contactBuffer,
const PxTransform& transform1, const Segment& meshCapsule,
PxReal inflatedRadius, PxReal contactDistance,
PxReal shapeCapsuleRadius, const TriangleMesh* meshData
) :
MeshHitCallback<PxGeomRaycastHit> (CallbackMode::eMULTIPLE),
mGeneration (contactBuffer, transform1, meshCapsule, inflatedRadius, contactDistance, shapeCapsuleRadius),
mMeshData (meshData)
{
PX_ASSERT(contactBuffer.count==0);
}
virtual PxAgain processHit(
const PxGeomRaycastHit& hit, const PxVec3& v0, const PxVec3& v1, const PxVec3& v2, PxReal&, const PxU32* /*vInds*/) PX_OVERRIDE
{
PxTrianglePadded tri;
// PT: TODO: revisit this, avoid the copy
tri.verts[0] = v0;
tri.verts[1] = v1;
tri.verts[2] = v2;
const PxU32 triangleIndex = hit.faceIndex;
//ML::set all the edges to be active, if the mExtraTrigData exist, we overwrite this flag
const PxU8 extraData = getConvexEdgeFlags(mMeshData->getExtraTrigData(), triangleIndex);
mGeneration.processTriangle(triangleIndex, tri, extraData);
return true;
}
private:
CapsuleMeshContactGenerationCallback_NoScale& operator=(const CapsuleMeshContactGenerationCallback_NoScale&);
};
struct CapsuleMeshContactGenerationCallback_Scale : CapsuleMeshContactGenerationCallback_NoScale
{
const FastVertex2ShapeScaling& mScaling;
CapsuleMeshContactGenerationCallback_Scale(
PxContactBuffer& contactBuffer,
const PxTransform& transform1, const Segment& meshCapsule,
PxReal inflatedRadius, const FastVertex2ShapeScaling& scaling, PxReal contactDistance,
PxReal shapeCapsuleRadius, const TriangleMesh* meshData
) :
CapsuleMeshContactGenerationCallback_NoScale(contactBuffer, transform1, meshCapsule, inflatedRadius, contactDistance, shapeCapsuleRadius, meshData),
mScaling (scaling)
{
}
virtual PxAgain processHit(
const PxGeomRaycastHit& hit, const PxVec3& v0, const PxVec3& v1, const PxVec3& v2, PxReal&, const PxU32* /*vInds*/) PX_OVERRIDE
{
PxTrianglePadded tri;
getScaledVertices(tri.verts, v0, v1, v2, false, mScaling);
const PxU32 triangleIndex = hit.faceIndex;
//ML::set all the edges to be active, if the mExtraTrigData exist, we overwrite this flag
PxU8 extraData = getConvexEdgeFlags(mMeshData->getExtraTrigData(), triangleIndex);
if(mScaling.flipsNormal())
flipConvexEdgeFlags(extraData);
mGeneration.processTriangle(triangleIndex, tri, extraData);
return true;
}
private:
CapsuleMeshContactGenerationCallback_Scale& operator=(const CapsuleMeshContactGenerationCallback_Scale&);
};
}
// PT: computes local capsule without going to world-space
static PX_FORCE_INLINE Segment computeLocalCapsule(const PxTransform& transform0, const PxTransform& transform1, const PxCapsuleGeometry& shapeCapsule)
{
const PxVec3 halfHeight = getCapsuleHalfHeightVector(transform0, shapeCapsule);
const PxVec3 delta = transform1.p - transform0.p;
return Segment(
transform1.rotateInv(halfHeight - delta),
transform1.rotateInv(-halfHeight - delta));
}
bool Gu::contactCapsuleMesh(GU_CONTACT_METHOD_ARGS)
{
PX_UNUSED(cache);
PX_UNUSED(renderOutput);
const PxCapsuleGeometry& shapeCapsule = checkedCast<PxCapsuleGeometry>(shape0);
const PxTriangleMeshGeometry& shapeMesh = checkedCast<PxTriangleMeshGeometry>(shape1);
const PxReal inflatedRadius = shapeCapsule.radius + params.mContactDistance; //AM: inflate!
const Segment meshCapsule = computeLocalCapsule(transform0, transform1, shapeCapsule);
const TriangleMesh* meshData = _getMeshData(shapeMesh);
//bound the capsule in shape space by an OBB:
Box queryBox;
{
const Capsule queryCapsule(meshCapsule, inflatedRadius);
queryBox.create(queryCapsule);
}
if(shapeMesh.scale.isIdentity())
{
CapsuleMeshContactGenerationCallback_NoScale callback(contactBuffer, transform1, meshCapsule,
inflatedRadius, params.mContactDistance, shapeCapsule.radius, meshData);
// PT: TODO: switch to capsule query here
Midphase::intersectOBB(meshData, queryBox, callback, true);
}
else
{
const FastVertex2ShapeScaling meshScaling(shapeMesh.scale);
CapsuleMeshContactGenerationCallback_Scale callback(contactBuffer, transform1, meshCapsule,
inflatedRadius, meshScaling, params.mContactDistance, shapeCapsule.radius, meshData);
//switched from capsuleCollider to boxCollider so we can support nonuniformly scaled meshes by scaling the query region:
//apply the skew transform to the box:
meshScaling.transformQueryBounds(queryBox.center, queryBox.extents, queryBox.rot);
Midphase::intersectOBB(meshData, queryBox, callback, true);
}
return contactBuffer.count > 0;
}
namespace
{
struct CapsuleHeightfieldContactGenerationCallback : OverlapReport
{
CapsuleMeshContactGeneration mGeneration;
const HeightFieldUtil& mHfUtil;
const PxTransform& mTransform1;
CapsuleHeightfieldContactGenerationCallback(
PxContactBuffer& contactBuffer,
const PxTransform& transform1, const HeightFieldUtil& hfUtil, const Segment& meshCapsule,
PxReal inflatedRadius, PxReal contactDistance, PxReal shapeCapsuleRadius
) :
mGeneration (contactBuffer, transform1, meshCapsule, inflatedRadius, contactDistance, shapeCapsuleRadius),
mHfUtil (hfUtil),
mTransform1 (transform1)
{
PX_ASSERT(contactBuffer.count==0);
}
// PT: TODO: refactor/unify with similar code in other places
virtual bool reportTouchedTris(PxU32 nb, const PxU32* indices) PX_OVERRIDE
{
const PxU8 nextInd[] = {2,0,1};
while(nb--)
{
const PxU32 triangleIndex = *indices++;
PxU32 vertIndices[3];
PxTrianglePadded currentTriangle; // in world space
PxU32 adjInds[3];
mHfUtil.getTriangle(mTransform1, currentTriangle, vertIndices, adjInds, triangleIndex, false, false);
PxVec3 normal;
currentTriangle.normal(normal);
PxU8 triFlags = 0; //KS - temporary until we can calculate triFlags for HF
for(PxU32 a = 0; a < 3; ++a)
{
if(adjInds[a] != 0xFFFFFFFF)
{
PxTriangle adjTri;
mHfUtil.getTriangle(mTransform1, adjTri, NULL, NULL, adjInds[a], false, false);
//We now compare the triangles to see if this edge is active
PxVec3 adjNormal;
adjTri.denormalizedNormal(adjNormal);
PxU32 otherIndex = nextInd[a];
PxF32 projD = adjNormal.dot(currentTriangle.verts[otherIndex] - adjTri.verts[0]);
if(projD < 0.f)
{
adjNormal.normalize();
PxF32 proj = adjNormal.dot(normal);
if(proj < 0.999f)
{
triFlags |= 1 << (a+3);
}
}
}
else
{
triFlags |= 1 << (a+3);
}
}
mGeneration.processTriangle(triangleIndex, currentTriangle, triFlags);
}
return true;
}
private:
CapsuleHeightfieldContactGenerationCallback& operator=(const CapsuleHeightfieldContactGenerationCallback&);
};
}
bool Gu::contactCapsuleHeightfield(GU_CONTACT_METHOD_ARGS)
{
PX_UNUSED(cache);
PX_UNUSED(renderOutput);
const PxCapsuleGeometry& shapeCapsule = checkedCast<PxCapsuleGeometry>(shape0);
const PxHeightFieldGeometry& shapeMesh = checkedCast<PxHeightFieldGeometry>(shape1);
const PxReal inflatedRadius = shapeCapsule.radius + params.mContactDistance; //AM: inflate!
const Segment meshCapsule = computeLocalCapsule(transform0, transform1, shapeCapsule);
// We must be in local space to use the cache
const HeightFieldUtil hfUtil(shapeMesh);
CapsuleHeightfieldContactGenerationCallback callback(
contactBuffer, transform1, hfUtil, meshCapsule, inflatedRadius, params.mContactDistance, shapeCapsule.radius);
//switched from capsuleCollider to boxCollider so we can support nonuniformly scaled meshes by scaling the query region:
//bound the capsule in shape space by an AABB:
// PT: TODO: improve these bounds (see computeCapsuleBounds)
hfUtil.overlapAABBTriangles(transform0, transform1, getLocalCapsuleBounds(inflatedRadius, shapeCapsule.halfHeight), callback);
return contactBuffer.count > 0;
}