feat(physics): wire physx sdk into build

This commit is contained in:
2026-04-15 12:22:15 +08:00
parent 5bf258df6d
commit 31f40e2cbb
2044 changed files with 752623 additions and 1 deletions

View File

@@ -0,0 +1,371 @@
// 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.
#ifndef GU_COLLISION_SDF_H
#define GU_COLLISION_SDF_H
#include "GuSDF.h"
#include "foundation/PxPreprocessor.h"
namespace physx
{
namespace Gu
{
// SDF wrapper for collision computations
// may be shared by CPU/GPU code in the future
//
// \detailed CollisionSDF wraps an `SDF` object, providing useful methods for SDF collision.
//
// Conventions
// * The coarse or background SDF is always referred to as _coarse_. Its associated coordinates are called `cPos` and
// are in units of `mSpacing*mSubgridSize`. The origin is normally at the first grid point.
// * The fine SDF is always referred to as _fine_. Its associated coordinates are called `fPos` and are in units of
// `mSpacing`. If the sdf is dense, `cPos` is equivalent to `fPos`. The origin is normally at the first grid point.
// * Coordinates in the native space of the SDF are denoted `sPos` and are in native units.
template <int BytesPerSparsePixelT>
PX_FORCE_INLINE PxReal decodeSample(PxReal subgridScalingFactor, PxReal subgridMinSdfValue, const PxU8* data, PxU32 index);
template <>
PX_FORCE_INLINE PxReal decodeSample<1>(PxReal subgridScalingFactor, PxReal subgridMinSdfValue, const PxU8* data, PxU32 index)
{
return static_cast<PxReal>(data[index]) * subgridScalingFactor + subgridMinSdfValue;
}
template <>
PX_FORCE_INLINE PxReal decodeSample<2>(PxReal subgridScalingFactor, PxReal subgridMinSdfValue, const PxU8* data, PxU32 index)
{
const PxU16* ptr = reinterpret_cast<const PxU16*>(data);
return static_cast<PxReal>(ptr[index]) * subgridScalingFactor + subgridMinSdfValue;
}
template <>
PX_FORCE_INLINE PxReal decodeSample<4>(PxReal /*unused*/, PxReal /*unused*/, const PxU8* data, PxU32 index)
{
//If 4 bytes per subgrid pixel are available, then normal floats are used. No need to
//de-normalize integer values since the floats already contain real distance values
const PxReal* ptr = reinterpret_cast<const PxReal*>(data);
return ptr[index];
}
struct CollisionSDF
{
CollisionSDF(const SDF& sdf): mSdf(sdf), mSdfBoxLower(sdf.mMeshLower), mFDims(sdf.mDims), mInvGridDx(1.0f / sdf.mSpacing),
mInvSubgridSize(sdf.mSubgridSize ? 1.0f / sdf.mSubgridSize : 0), mIsDense(sdf.mSubgridSize == 0)
{
// assume that `mMeshLower` is also the location of the lowest grid point
if (mIsDense)
{
mCDims = mFDims;
mCSamples = mCDims;
mSdfBoxUpper = sdf.mMeshLower + sdf.mSpacing * PxVec3(static_cast<PxReal>(mFDims.x-1), static_cast<PxReal>(mFDims.y-1), static_cast<PxReal>(mFDims.z-1));
}
else
{
mCDims = Dim3(mFDims.x / sdf.mSubgridSize, mFDims.y / sdf.mSubgridSize, mFDims.z / sdf.mSubgridSize);
mCSamples = Dim3(mCDims.x + 1, mCDims.y + 1, mCDims.z + 1);
mSdfBoxUpper = sdf.mMeshLower + sdf.mSpacing * PxVec3(mFDims);
}
if (mSdf.mBytesPerSparsePixel == 1)
mSubgridScalingFactor = (1.0f / 255.0f) * (mSdf.mSubgridsMaxSdfValue - mSdf.mSubgridsMinSdfValue);
else if (mSdf.mBytesPerSparsePixel == 2)
mSubgridScalingFactor = (1.0f / 65535.0f) * (mSdf.mSubgridsMaxSdfValue - mSdf.mSubgridsMinSdfValue);
const PxU32 fW = mSdf.mSdfSubgrids3DTexBlockDim.x * (mSdf.mSubgridSize + 1),
fH = mSdf.mSdfSubgrids3DTexBlockDim.y * (mSdf.mSubgridSize + 1);
mFStrideY = fW;
mFStrideZ = fW*fH;
}
// clamp `fPos` to the grid on which `sdf` is defined
PX_INLINE PxVec3 clampToFine(const PxVec3& fPos) const
{
if (!mIsDense)
return fPos.maximum(PxVec3(0.0f)).minimum(PxVec3(mFDims));
return fPos.maximum(PxVec3(0.5f)).minimum(PxVec3(mFDims) + PxVec3(0.5f));
}
// clamp `sPos` to the grid on which `sdf` is defined
PX_INLINE PxVec3 clampToBox(const PxVec3& sPos) const
{
if (mIsDense)
return sPos.maximum(mSdfBoxLower+PxVec3(0.5f*mSdf.mSpacing)).minimum(mSdfBoxUpper+PxVec3(0.5f*mSdf.mSpacing));
return sPos.maximum(mSdfBoxLower).minimum(mSdfBoxUpper);
}
// Utility to convert from x/y/z indices to a linear index given the grid size (only width and height required)
PX_FORCE_INLINE PX_CUDA_CALLABLE static PxU32 idx3D(PxU32 x, PxU32 y, PxU32 z, PxU32 width, PxU32 height)
{
return z * width * height + y * width + x;
}
static PX_INLINE PxReal TriLerpWithGradient(
const PxReal f000,
const PxReal f100,
const PxReal f010,
const PxReal f110,
const PxReal f001,
const PxReal f101,
const PxReal f011,
const PxReal f111,
const PxReal tx,
const PxReal ty,
const PxReal tz,
PxVec3* grad = NULL)
{
if (grad)
{
const PxReal a = f100 - f000;
const PxReal b = f110 - f010;
const PxReal c = f101 - f001;
const PxReal d = f111 - f011;
grad->x = a + (b - (a)) * ty + (c + (d - (c)) * ty - (a + (b - (a)) * ty)) * tz;
grad->y = f010 + tx * (b) - (f000 + tx * (a)) + (f011 + tx * (d) - (f001 + tx * (c)) - (f010 + tx * (b) - (f000 + tx * (a)))) * tz;
grad->z = f001 + tx * (c) + ty * (f011 + tx * (d) - (f001 + tx * (c))) - (f000 + tx * (a) + ty * (f010 + tx * (b) - (f000 + tx * (a))));
}
return PxTriLerp(
f000,
f100,
f010,
f110,
f001,
f101,
f011,
f111,
tx,
ty,
tz);
}
template <int BytesPerSparsePixelT>
PX_INLINE PxReal interpolateSubgrid (const PxU8* subgridBase, PxU32 baseIdx, PxReal x, PxReal y, PxReal z, PxVec3* gradient = NULL) const
{
PX_COMPILE_TIME_ASSERT(
BytesPerSparsePixelT == 1 || BytesPerSparsePixelT == 2 || BytesPerSparsePixelT == 4);
return TriLerpWithGradient(
decodeSample<BytesPerSparsePixelT>(mSubgridScalingFactor, mSdf.mSubgridsMinSdfValue, subgridBase, baseIdx ),
decodeSample<BytesPerSparsePixelT>(mSubgridScalingFactor, mSdf.mSubgridsMinSdfValue, subgridBase, baseIdx+1 ),
decodeSample<BytesPerSparsePixelT>(mSubgridScalingFactor, mSdf.mSubgridsMinSdfValue, subgridBase, baseIdx+mFStrideY ),
decodeSample<BytesPerSparsePixelT>(mSubgridScalingFactor, mSdf.mSubgridsMinSdfValue, subgridBase, baseIdx+mFStrideY+1 ),
decodeSample<BytesPerSparsePixelT>(mSubgridScalingFactor, mSdf.mSubgridsMinSdfValue, subgridBase, baseIdx+mFStrideZ ),
decodeSample<BytesPerSparsePixelT>(mSubgridScalingFactor, mSdf.mSubgridsMinSdfValue, subgridBase, baseIdx+mFStrideZ+1 ),
decodeSample<BytesPerSparsePixelT>(mSubgridScalingFactor, mSdf.mSubgridsMinSdfValue, subgridBase, baseIdx+mFStrideZ+mFStrideY ),
decodeSample<BytesPerSparsePixelT>(mSubgridScalingFactor, mSdf.mSubgridsMinSdfValue, subgridBase, baseIdx+mFStrideZ+mFStrideY+1),
x, y, z, gradient);
}
// interpolate the values of `array` at position `fPos`
// input vector `fPos` is in units of subgrid cells, with 0 corresponding to the subgrid origin
PX_INLINE PxReal evaluateSubgrid(const PxU32 subgridInfo, const PxVec3& fPos, PxVec3* gradient = NULL) const
{
const PxU32 sgSamples = mSdf.mSubgridSize + 1;
PX_ASSERT(fPos.x >= 0 && fPos.y >= 0 && fPos.z >= 0);
PX_ASSERT(fPos.x < sgSamples && fPos.y < sgSamples && fPos.z < sgSamples);
// find the subgrid offset in memory
PxU32 xSubgrid, ySubgrid, zSubgrid;
SDF::decodeTriple(subgridInfo, xSubgrid, ySubgrid, zSubgrid);
xSubgrid *= (mSdf.mSubgridSize + 1);
ySubgrid *= (mSdf.mSubgridSize + 1);
zSubgrid *= (mSdf.mSubgridSize + 1);
// reference subgrid point
const PxU32 x = PxMin(static_cast<PxU32>(fPos.x), sgSamples - 2),
y = PxMin(static_cast<PxU32>(fPos.y), sgSamples - 2),
z = PxMin(static_cast<PxU32>(fPos.z), sgSamples - 2);
// offset values by the subgrid memory offset
const PxU32 xM = xSubgrid + x, yM = ySubgrid + y, zM = zSubgrid + z;
const PxU32 base = mFStrideZ * zM + mFStrideY * yM + xM;
switch (mSdf.mBytesPerSparsePixel)
{
case 1:
return interpolateSubgrid<1>(mSdf.mSubgridSdf, base, fPos.x - x, fPos.y - y, fPos.z - z, gradient);
case 2:
return interpolateSubgrid<2>(mSdf.mSubgridSdf, base, fPos.x - x, fPos.y - y, fPos.z - z, gradient);
case 4:
return interpolateSubgrid<4>(mSdf.mSubgridSdf, base, fPos.x - x, fPos.y - y, fPos.z - z, gradient);
default: // never reached
PX_ASSERT(0);
return 0;
}
}
// interpolate the values of `array` at position `cPos`.
// `cPos` must be >= 0 and < `cDims`
PX_INLINE PxReal evaluateCoarse(const PxVec3& cPos, PxVec3* gradient = NULL) const
{
PX_ASSERT(cPos.x >= 0 && cPos.y >= 0 && cPos.z >= 0);
PX_ASSERT(cPos.x < mCSamples.x && cPos.y < mCSamples.y && cPos.z < mCSamples.z);
// reference grid point
const PxU32 x = PxMin(static_cast<PxU32>(cPos.x), mCSamples.x - 2),
y = PxMin(static_cast<PxU32>(cPos.y), mCSamples.y - 2),
z = PxMin(static_cast<PxU32>(cPos.z), mCSamples.z - 2);
const PxU32 w = mCSamples.x, h = mCSamples.y;
const PxU32 cStrideY = w, cStrideZ = w*h; // Note that this is sample, not cell, stride
const PxU32 base = cStrideZ * z + cStrideY * y + x;
return TriLerpWithGradient(
mSdf.mSdf[base],
mSdf.mSdf[base+1],
mSdf.mSdf[base+cStrideY],
mSdf.mSdf[base+cStrideY+1],
mSdf.mSdf[base+cStrideZ],
mSdf.mSdf[base+cStrideZ+1],
mSdf.mSdf[base+cStrideZ+cStrideY],
mSdf.mSdf[base+cStrideZ+cStrideY+1],
cPos.x - x, cPos.y - y, cPos.z - z, gradient);
}
// sample the SDF at `fPos`
// input vector `fPos` is in units of (sub-) grid cells, with integer values representing nodes
PX_INLINE PxReal sample(PxVec3 fPos, PxVec3* gradient = NULL) const
{
if (mIsDense)
fPos -= PxVec3(0.5);
PX_ASSERT(fPos.x >= 0 && fPos.y >= 0 && fPos.z >= 0);
PX_ASSERT(fPos.x <= mFDims.x && fPos.y <= mFDims.y && fPos.z <= mFDims.z);
if (mIsDense) // fPos = cPos
return evaluateCoarse(fPos, gradient);
// coarse reference gridpoint index
const Dim3 cBase(
PxMin(static_cast<PxU32>(fPos.x * mInvSubgridSize), mCSamples.x - 2),
PxMin(static_cast<PxU32>(fPos.y * mInvSubgridSize), mCSamples.y - 2),
PxMin(static_cast<PxU32>(fPos.z * mInvSubgridSize), mCSamples.z - 2)
);
const PxU32 i = idx3D(cBase.x, cBase.y, cBase.z, mCDims.x, mCDims.y);
const PxU32 subgridInfo = mSdf.mSubgridStartSlots[i];
if (subgridInfo == 0xFFFFFFFF) // Evaluate (coarse) background of sparse SDF
return evaluateCoarse((fPos * mInvSubgridSize).minimum(PxVec3(PxReal(mCSamples.x), PxReal(mCSamples.y), PxReal(mCSamples.z))), gradient);
// offset to subgrid origin
PxVec3 fPosInSubgrid;
fPosInSubgrid.x = PxMax(0.f, fPos.x - cBase.x * mSdf.mSubgridSize);
fPosInSubgrid.y = PxMax(0.f, fPos.y - cBase.y * mSdf.mSubgridSize);
fPosInSubgrid.z = PxMax(0.f, fPos.z - cBase.z * mSdf.mSubgridSize);
return evaluateSubgrid(subgridInfo, fPosInSubgrid, gradient);
}
// evaluate & interpolate `sdf` (in `sdf`'s "vertex" space) at `sPos`
// when outside the sdf grid, this should be considered an upper bound
// TODO(CA): add more clamps or prove they're unnecessary
inline PxReal dist(const PxVec3& sPos, PxVec3* gradient = NULL) const
{
// clamped to SDF support
const PxVec3 boxPos = clampToBox(sPos);
const PxVec3 diff = sPos - boxPos;
const PxReal diffMag = diff.magnitude();
const PxVec3 fPos = (boxPos - mSdfBoxLower) * mInvGridDx;
const PxReal distance = sample(clampToFine(fPos), gradient) + diffMag; // division inaccuracy necessitates clamp
if (gradient && diffMag > 0.0f)
*gradient = diff; //A quite coarse approximation but it's only used if the sample point is outside of the sdf's bounding box
return distance;
}
// evaluate & interpolate `sdf` at `sPos` (in `sdf`'s "vertex" space), and compute its gradient
inline PxVec3 grad(const PxVec3& sPos) const
{
// clamped to SDF support
const PxVec3 boxPos = clampToBox(sPos);
const PxVec3 fPos = (boxPos - mSdfBoxLower) * mInvGridDx;
PxVec3 gradient;
if ( fPos.x >= 1.0f && fPos.x <= mFDims.x - 2.0f &&
fPos.y >= 1.0f && fPos.y <= mFDims.y - 2.0f &&
fPos.z >= 1.0f && fPos.z <= mFDims.z - 2.0f)
{
gradient.x = sample(PxVec3(fPos.x+1, fPos.y, fPos.z)) - sample(PxVec3(fPos.x-1, fPos.y, fPos.z));
gradient.y = sample(PxVec3(fPos.x, fPos.y+1, fPos.z)) - sample(PxVec3(fPos.x, fPos.y -1, fPos.z));
gradient.z = sample(PxVec3(fPos.x, fPos.y, fPos.z+1)) - sample(PxVec3(fPos.x, fPos.y, fPos.z -1));
}
else
{
const PxReal h = mSdf.mSpacing;
gradient.x = dist(PxVec3(sPos.x+h, sPos.y, sPos.z)) - dist(PxVec3(sPos.x-h, sPos.y, sPos.z));
gradient.y = dist(PxVec3(sPos.x, sPos.y+h, sPos.z)) - dist(PxVec3(sPos.x, sPos.y-h, sPos.z));
gradient.z = dist(PxVec3(sPos.x, sPos.y, sPos.z+h)) - dist(PxVec3(sPos.x, sPos.y, sPos.z-h));
}
gradient *= 0.5f / mSdf.mSpacing;
return gradient;
}
// Estimate the value and gradient of `sdf` at `sPos`, using gradient information when `sPos` is
// outside the SDF grid. Return `PX_MAX_F32` when the distance exceeds `cutoffDistance`
PX_INLINE PxReal distUsingGradient(const PxVec3& sPos, PxVec3& gradient, const PxReal& cutoffDistance) const
{
// clamped to SDF support
const PxVec3 boxPos = clampToBox(sPos);
const PxVec3 diff = sPos - boxPos;
const PxVec3 fPos = (boxPos - mSdfBoxLower) * mInvGridDx;
const PxReal dist = sample(clampToFine(fPos));
if (dist > cutoffDistance)
return PX_MAX_F32;
gradient = grad(sPos);
gradient = (gradient.getNormalized() * PxAbs(dist) + diff).getNormalized();
return dist + gradient.dot(diff);
}
// data members
const SDF& mSdf;
PxVec3 mSdfBoxLower, mSdfBoxUpper; // Positions of the first and last grid points
Dim3 mCDims, mFDims; // background and high-resolution SDF dimensions in cells. Equal if dense.
// fDims is equally divisible by subgridSize, which is also in cells, for sparse SDFs
// the coarse grid has cDims+1 (cDims) samples per dimension for sparse (dense) SDFs
// subgrids have subgridSize+1 samples per dimension
Dim3 mCSamples; // Number of samples in each dimension. Equal to cDims for dense, and cDims + 1 for spares SDFs
PxReal mInvGridDx; // invSdfDx
PxReal mInvSubgridSize; // fineToCoarse
bool mIsDense;
PxReal mSubgridScalingFactor; // purely for optimization
PxU32 mFStrideY, mFStrideZ;
};
}
}
#endif

View File

@@ -0,0 +1,695 @@
// 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 "GuContactMethodImpl.h"
#include "CmMatrix34.h"
#include "foundation/PxUtilities.h"
using namespace physx;
using namespace Gu;
using namespace Cm;
#define MAX_NB_CTCS 8 + 12*5 + 6*4
#define ABS_GREATER(x, y) (PxAbs(x) > (y))
#define ABS_SMALLER_EQUAL(x, y) (PxAbs(x) <= (y))
//#define AIR(x) ((PxU32&)(x)&SIGN_BITMASK)
//#define ABS_GREATER(x, y) (AIR(x) > IR(y))
//#define ABS_SMALLER_EQUAL(x, y) (AIR(x) <= IR(y))
#if PX_X86 && !PX_OSX
// Some float optimizations ported over from novodex.
//returns non zero if the value is negative.
#define PXC_IS_NEGATIVE(x) (((PxU32&)(x)) & 0x80000000)
#else
//On most platforms using the integer rep is worse(produces LHSs) since the CPU has more registers.
//returns non zero if the value is negative.
#define PXC_IS_NEGATIVE(x) ((x) < 0.0f)
#endif
enum
{
AXIS_A0, AXIS_A1, AXIS_A2,
AXIS_B0, AXIS_B1, AXIS_B2
};
struct VertexInfo
{
PxVec3 pos;
bool penetrate;
bool area;
};
/*static PxI32 doBoxBoxContactGeneration(PxVec3 ctcPts[MAX_NB_CTCS], PxReal depths[MAX_NB_CTCS], PxVec3* ctcNrm,
const PxVec3& extents0, const PxVec3& extents1,
PxU32& collisionData,
const PxMat34& transform0, const PxMat34& transform1, PxReal contactDistance);*/
static PxI32 doBoxBoxContactGeneration(PxContactBuffer& contactBuffer,
const PxVec3& extents0, const PxVec3& extents1,
PxU32& collisionData,
const PxMat34& transform0, const PxMat34& transform1, PxReal contactDistance);
bool Gu::contactBoxBox(GU_CONTACT_METHOD_ARGS)
{
PX_UNUSED(renderOutput);
// Get actual shape data
const PxBoxGeometry& shapeBox0 = checkedCast<PxBoxGeometry>(shape0);
const PxBoxGeometry& shapeBox1 = checkedCast<PxBoxGeometry>(shape1);
PxU32 pd = PxU32(cache.mPairData);
PxI32 Nb = doBoxBoxContactGeneration(contactBuffer,
shapeBox0.halfExtents, shapeBox1.halfExtents,
pd,
Matrix34FromTransform(transform0), Matrix34FromTransform(transform1),
params.mContactDistance);
cache.mPairData = PxTo8(pd);
if(!Nb)
{
cache.mPairData = 0; // Mark as separated for temporal coherence
return false; // WARNING: the contact stream code below used to output stuff even for 0 contacts (!). Now we just return here.
}
return true;
}
// face => 4 vertices of a face of the cube (i.e. a quad)
static PX_FORCE_INLINE PxReal IsInYZ(const PxReal y, const PxReal z, const VertexInfo** PX_RESTRICT face)
{
// Warning, indices have been remapped. We're now actually like this:
//
// 3+------+2
// | | |
// | *--|
// | (y,z)|
// 0+------+1
PxReal PreviousY = face[3]->pos.y;
PxReal PreviousZ = face[3]->pos.z;
// Loop through quad vertices
for(PxI32 i=0; i<4; i++)
{
const PxReal CurrentY = face[i]->pos.y;
const PxReal CurrentZ = face[i]->pos.z;
// |CurrentY - PreviousY y - PreviousY|
// |CurrentZ - PreviousZ z - PreviousZ|
// => similar to backface culling, check each one of the 4 triangles are consistent, in which case
// the point is within the parallelogram.
if((CurrentY - PreviousY)*(z - PreviousZ) - (CurrentZ - PreviousZ)*(y - PreviousY) >= 0.0f) return -1.0f;
PreviousY = CurrentY;
PreviousZ = CurrentZ;
}
PxReal x = face[0]->pos.x;
{
const PxReal ay = y - face[0]->pos.y;
const PxReal az = z - face[0]->pos.z;
PxVec3 b = face[1]->pos - face[0]->pos; // ### could be precomputed ?
x += b.x * (ay*b.y + az*b.z) / b.magnitudeSquared(); // ### could be precomputed ?
b = face[3]->pos - face[0]->pos; // ### could be precomputed ?
x += b.x * (ay*b.y + az*b.z) / b.magnitudeSquared(); // ### could be precomputed ?
}
return x;
}
// Test with respect to the quad defined by (0,-y1,-z1) and (0,y1,z1)
// +------+ y1 y
// | | |
// | * | |
// | | |
// +------+ -y1 *-----z
static PxI32 generateContacts(//PxVec3 ctcPts[], PxReal depths[],
PxContactBuffer& contactBuffer, const PxVec3& contactNormal,
PxReal y1, PxReal z1, const PxVec3& box2,
const PxMat34& transform0, const PxMat34& transform1, PxReal contactDistance)
{
// PxI32 NbContacts=0;
contactBuffer.reset();
y1 += contactDistance;
z1 += contactDistance;
const PxMat34 trans1to0 = transform0.getInverseRT() * transform1;
VertexInfo vtx[8]; // The 8 cube vertices
// PxI32 i;
// 6+------+7
// /| /|
// / | / |
// / 4+---/--+5
// 2+------+3 / y z
// | / | / | /
// |/ |/ |/
// 0+------+1 *---x
{
const PxVec3 ex = trans1to0.m.column0 * box2.x;
const PxVec3 ey = trans1to0.m.column1 * box2.y;
const PxVec3 ez = trans1to0.m.column2 * box2.z;
/*
vtx[0].pos = mat.pos - ex - ey - ez;
vtx[1].pos = mat.pos + ex - ey - ez;
vtx[2].pos = mat.pos - ex + ey - ez;
vtx[3].pos = mat.pos + ex + ey - ez;
vtx[4].pos = mat.pos - ex - ey + ez;
vtx[5].pos = mat.pos + ex - ey + ez;
vtx[6].pos = mat.pos - ex + ey + ez;
vtx[7].pos = mat.pos + ex + ey + ez;
*/
// 12 vector ops = 12*3 = 36 FPU ops
vtx[0].pos = vtx[2].pos = vtx[4].pos = vtx[6].pos = trans1to0.p - ex;
vtx[1].pos = vtx[3].pos = vtx[5].pos = vtx[7].pos = trans1to0.p + ex;
PxVec3 e = ey+ez;
vtx[0].pos -= e;
vtx[1].pos -= e;
vtx[6].pos += e;
vtx[7].pos += e;
e = ey-ez;
vtx[2].pos += e;
vtx[3].pos += e;
vtx[4].pos -= e;
vtx[5].pos -= e;
}
// Create vertex info for 8 vertices
for(PxU32 i=0; i<8; i++)
{
// Vertex suivant
VertexInfo& p = vtx[i];
// test the point with respect to the x = 0 plane
// if(p.pos.x < 0)
if(p.pos.x < -contactDistance) //if(PXC_IS_NEGATIVE(p.pos.x))
{
p.area = false;
p.penetrate = false;
continue;
}
{
// we penetrated the quad plane
p.penetrate = true;
// test to see if we are in the quad
// PxAbs => thus we test Y with respect to -Y1 and +Y1 (same for Z)
// if(PxAbs(p->pos.y) <= y1 && PxAbs(p->pos.z) <= z1)
if(ABS_SMALLER_EQUAL(p.pos.y, y1) && ABS_SMALLER_EQUAL(p.pos.z, z1))
{
// the point is inside the quad
p.area=true;
// Since we are testing with respect to x = 0, the penetration is directly the x coordinate.
// depths[NbContacts] = p.pos.x;
// We take the vertex as the impact point
// ctcPts[NbContacts++] = p.pos;
contactBuffer.contact(p.pos, contactNormal, -p.pos.x);
}
else
{
p.area=false;
}
}
}
// Teste 12 edges on the quad
static const PxI32 indices[]={ 0,1, 1,3, 3,2, 2,0, 4,5, 5,7, 7,6, 6,4, 0,4, 1,5, 2,6, 3,7, };
const PxI32* runningLine = indices;
const PxI32* endLine = runningLine+24;
while(runningLine!=endLine)
{
// The two vertices of the current edge
const VertexInfo* p1 = &vtx[*runningLine++];
const VertexInfo* p2 = &vtx[*runningLine++];
// Penetrate|Area|Penetrate|Area => 16 cases
// We only take the edges that at least penetrated the quad's plane into account.
if(p1->penetrate || p2->penetrate)
// if(p1->penetrate + p2->penetrate) // One branch only
{
// If at least one of the two vertices is not in the quad...
if(!p1->area || !p2->area)
// if(!p1->area + !p2->area) // One branch only
{
// Test y
if(p1->pos.y > p2->pos.y) { const VertexInfo* tmp=p1; p1=p2; p2=tmp; }
// Impact on the +Y1 edge of the quad
if(p1->pos.y < +y1 && p2->pos.y >= +y1)
// => a point under Y1, the other above
{
// Case 1
PxReal a = (+y1 - p1->pos.y)/(p2->pos.y - p1->pos.y);
PxReal z = p1->pos.z + (p2->pos.z - p1->pos.z)*a;
if(PxAbs(z) <= z1)
{
PxReal x = p1->pos.x + (p2->pos.x - p1->pos.x)*a;
if(x+contactDistance>=0.0f)
{
// depths[NbContacts] = x;
// ctcPts[NbContacts++] = PxVec3(x, y1, z);
contactBuffer.contact(PxVec3(x, y1, z), contactNormal, -x);
}
}
}
// Impact on the edge -Y1 of the quad
if(p1->pos.y < -y1 && p2->pos.y >= -y1)
{
// Case 2
PxReal a = (-y1 - p1->pos.y)/(p2->pos.y - p1->pos.y);
PxReal z = p1->pos.z + (p2->pos.z - p1->pos.z)*a;
if(PxAbs(z) <= z1)
{
PxReal x = p1->pos.x + (p2->pos.x - p1->pos.x)*a;
if(x+contactDistance>=0.0f)
{
// depths[NbContacts] = x;
// ctcPts[NbContacts++] = PxVec3(x, -y1, z);
contactBuffer.contact(PxVec3(x, -y1, z), contactNormal, -x);
}
}
}
// Test z
if(p1->pos.z > p2->pos.z) { const VertexInfo* tmp=p1; p1=p2; p2=tmp; }
// Impact on the edge +Z1 of the quad
if(p1->pos.z < +z1 && p2->pos.z >= +z1)
{
// Case 3
PxReal a = (+z1 - p1->pos.z)/(p2->pos.z - p1->pos.z);
PxReal y = p1->pos.y + (p2->pos.y - p1->pos.y)*a;
if(PxAbs(y) <= y1)
{
PxReal x = p1->pos.x + (p2->pos.x - p1->pos.x)*a;
if(x+contactDistance>=0.0f)
{
// depths[NbContacts] = x;
// ctcPts[NbContacts++] = PxVec3(x, y, z1);
contactBuffer.contact(PxVec3(x, y, z1), contactNormal, -x);
}
}
}
// Impact on the edge -Z1 of the quad
if(p1->pos.z < -z1 && p2->pos.z >= -z1)
{
// Case 4
PxReal a = (-z1 - p1->pos.z)/(p2->pos.z - p1->pos.z);
PxReal y = p1->pos.y + (p2->pos.y - p1->pos.y)*a;
if(PxAbs(y) <= y1)
{
PxReal x = p1->pos.x + (p2->pos.x - p1->pos.x)*a;
if(x+contactDistance>=0.0f)
{
// depths[NbContacts] = x;
// ctcPts[NbContacts++] = PxVec3(x, y, -z1);
contactBuffer.contact(PxVec3(x, y, -z1), contactNormal, -x);
}
}
}
}
// The case where one point penetrates the plane, and the other is not in the quad.
if((!p1->penetrate && !p2->area) || (!p2->penetrate && !p1->area))
{
// Case 5
PxReal a = (-p1->pos.x)/(p2->pos.x - p1->pos.x);
PxReal y = p1->pos.y + (p2->pos.y - p1->pos.y)*a;
if(PxAbs(y) <= y1)
{
PxReal z = p1->pos.z + (p2->pos.z - p1->pos.z)*a;
if(PxAbs(z) <= z1)
{
// depths[NbContacts] = 0;
// ctcPts[NbContacts++] = PxVec3(0, y, z);
contactBuffer.contact(PxVec3(0, y, z), contactNormal, 0);
}
}
}
}
}
{
// 6 quads => 6 faces of the cube
static const PxI32 face[][4]={ {0,1,3,2}, {1,5,7,3}, {5,4,6,7}, {4,0,2,6}, {2,3,7,6}, {0,4,5,1} };
PxI32 addflg=0;
for(PxU32 i=0; i<6 && addflg!=0x0f; i++)
{
const PxI32* p = face[i];
const VertexInfo* q[4];
if((q[0]=&vtx[p[0]])->penetrate && (q[1]=&vtx[p[1]])->penetrate && (q[2]=&vtx[p[2]])->penetrate && (q[3]=&vtx[p[3]])->penetrate)
{
if(!q[0]->area || !q[1]->area || !q[2]->area || !q[3]->area)
{
if(!(addflg&1)) { PxReal x = IsInYZ(-y1, -z1, q); if(x>=0.0f) { addflg|=1; contactBuffer.contact(PxVec3(x, -y1, -z1), contactNormal, -x); /*depths[NbContacts]=x; ctcPts[NbContacts++] = PxVec3(x, -y1, -z1);*/ } }
if(!(addflg&2)) { PxReal x = IsInYZ(+y1, -z1, q); if(x>=0.0f) { addflg|=2; contactBuffer.contact(PxVec3(x, +y1, -z1), contactNormal, -x); /*depths[NbContacts]=x; ctcPts[NbContacts++] = PxVec3(x, +y1, -z1);*/ } }
if(!(addflg&4)) { PxReal x = IsInYZ(-y1, +z1, q); if(x>=0.0f) { addflg|=4; contactBuffer.contact(PxVec3(x, -y1, +z1), contactNormal, -x); /*depths[NbContacts]=x; ctcPts[NbContacts++] = PxVec3(x, -y1, +z1);*/ } }
if(!(addflg&8)) { PxReal x = IsInYZ(+y1, +z1, q); if(x>=0.0f) { addflg|=8; contactBuffer.contact(PxVec3(x, +y1, +z1), contactNormal, -x); /*depths[NbContacts]=x; ctcPts[NbContacts++] = PxVec3(x, +y1, +z1);*/ } }
}
}
}
}
// for(i=0; i<NbContacts; i++)
for(PxU32 i=0; i<contactBuffer.count; i++)
// ctcPts[i] = transform0.transform(ctcPts[i]); // local to world
contactBuffer.contacts[i].point = transform0.transform(contactBuffer.contacts[i].point); // local to world
//PX_ASSERT(NbContacts); //if this did not make contacts then something went wrong in theory, but even the old code without distances had this flaw!
// return NbContacts;
return PxI32(contactBuffer.count);
}
//static PxI32 doBoxBoxContactGeneration(PxVec3 ctcPts[MAX_NB_CTCS], PxReal depths[MAX_NB_CTCS], PxVec3* ctcNrm,
static PxI32 doBoxBoxContactGeneration(PxContactBuffer& contactBuffer,
const PxVec3& extents0, const PxVec3& extents1,
PxU32& collisionData,
const PxMat34& transform0, const PxMat34& transform1, PxReal contactDistance)
{
PxReal aafC[3][3]; // matrix C = A^T B, c_{ij} = Dot(A_i,B_j)
PxReal aafAbsC[3][3]; // |c_{ij}|
PxReal afAD[3]; // Dot(A_i,D)
PxReal d1[6];
PxReal overlap[6];
PxVec3 kD = transform1.p - transform0.p;
const PxVec3& axis00 = transform0.m.column0;
const PxVec3& axis01 = transform0.m.column1;
const PxVec3& axis02 = transform0.m.column2;
const PxVec3& axis10 = transform1.m.column0;
const PxVec3& axis11 = transform1.m.column1;
const PxVec3& axis12 = transform1.m.column2;
// Perform Class I tests
aafC[0][0] = axis00.dot(axis10);
aafC[0][1] = axis00.dot(axis11);
aafC[0][2] = axis00.dot(axis12);
afAD[0] = axis00.dot(kD);
aafAbsC[0][0] = 1e-6f + PxAbs(aafC[0][0]);
aafAbsC[0][1] = 1e-6f + PxAbs(aafC[0][1]);
aafAbsC[0][2] = 1e-6f + PxAbs(aafC[0][2]);
d1[AXIS_A0] = afAD[0];
PxReal d0 = extents0.x + extents1.x*aafAbsC[0][0] + extents1.y*aafAbsC[0][1] + extents1.z*aafAbsC[0][2];
overlap[AXIS_A0] = d0 - PxAbs(d1[AXIS_A0]) + contactDistance;
if(PXC_IS_NEGATIVE(overlap[AXIS_A0])) return 0;
aafC[1][0] = axis01.dot(axis10);
aafC[1][1] = axis01.dot(axis11);
aafC[1][2] = axis01.dot(axis12);
afAD[1] = axis01.dot(kD);
aafAbsC[1][0] = 1e-6f + PxAbs(aafC[1][0]);
aafAbsC[1][1] = 1e-6f + PxAbs(aafC[1][1]);
aafAbsC[1][2] = 1e-6f + PxAbs(aafC[1][2]);
d1[AXIS_A1] = afAD[1];
d0 = extents0.y + extents1.x*aafAbsC[1][0] + extents1.y*aafAbsC[1][1] + extents1.z*aafAbsC[1][2];
overlap[AXIS_A1] = d0 - PxAbs(d1[AXIS_A1]) + contactDistance;
if(PXC_IS_NEGATIVE(overlap[AXIS_A1])) return 0;
aafC[2][0] = axis02.dot(axis10);
aafC[2][1] = axis02.dot(axis11);
aafC[2][2] = axis02.dot(axis12);
afAD[2] = axis02.dot(kD);
aafAbsC[2][0] = 1e-6f + PxAbs(aafC[2][0]);
aafAbsC[2][1] = 1e-6f + PxAbs(aafC[2][1]);
aafAbsC[2][2] = 1e-6f + PxAbs(aafC[2][2]);
d1[AXIS_A2] = afAD[2];
d0 = extents0.z + extents1.x*aafAbsC[2][0] + extents1.y*aafAbsC[2][1] + extents1.z*aafAbsC[2][2];
overlap[AXIS_A2] = d0 - PxAbs(d1[AXIS_A2]) + contactDistance;
if(PXC_IS_NEGATIVE(overlap[AXIS_A2])) return 0;
// Perform Class II tests
d1[AXIS_B0] = axis10.dot(kD);
d0 = extents1.x + extents0.x*aafAbsC[0][0] + extents0.y*aafAbsC[1][0] + extents0.z*aafAbsC[2][0];
overlap[AXIS_B0] = d0 - PxAbs(d1[AXIS_B0]) + contactDistance;
if(PXC_IS_NEGATIVE(overlap[AXIS_B0])) return 0;
d1[AXIS_B1] = axis11.dot(kD);
d0 = extents1.y + extents0.x*aafAbsC[0][1] + extents0.y*aafAbsC[1][1] + extents0.z*aafAbsC[2][1];
overlap[AXIS_B1] = d0 - PxAbs(d1[AXIS_B1]) + contactDistance;
if(PXC_IS_NEGATIVE(overlap[AXIS_B1])) return 0;
d1[AXIS_B2] = axis12.dot(kD);
d0 = extents1.z + extents0.x*aafAbsC[0][2] + extents0.y*aafAbsC[1][2] + extents0.z*aafAbsC[2][2];
overlap[AXIS_B2] = d0 - PxAbs(d1[AXIS_B2]) + contactDistance;
if(PXC_IS_NEGATIVE(overlap[AXIS_B2])) return 0;
// Perform Class III tests - we don't need to store distances for those ones.
// We only test those axes when objects are likely to be separated, i.e. when they where previously non-colliding. For stacks, we'll have
// to do full contact generation anyway, and those tests are useless - so we skip them. This is similar to what I did in Opcode.
if(!collisionData) // separated or first run
{
PxReal d = afAD[2]*aafC[1][0] - afAD[1]*aafC[2][0];
d0 = contactDistance + extents0.y*aafAbsC[2][0] + extents0.z*aafAbsC[1][0] + extents1.y*aafAbsC[0][2] + extents1.z*aafAbsC[0][1];
if(ABS_GREATER(d, d0)) return 0;
d = afAD[2]*aafC[1][1] - afAD[1]*aafC[2][1];
d0 = contactDistance + extents0.y*aafAbsC[2][1] + extents0.z*aafAbsC[1][1] + extents1.x*aafAbsC[0][2] + extents1.z*aafAbsC[0][0];
if(ABS_GREATER(d, d0)) return 0;
d = afAD[2]*aafC[1][2] - afAD[1]*aafC[2][2];
d0 = contactDistance + extents0.y*aafAbsC[2][2] + extents0.z*aafAbsC[1][2] + extents1.x*aafAbsC[0][1] + extents1.y*aafAbsC[0][0];
if(ABS_GREATER(d, d0)) return 0;
d = afAD[0]*aafC[2][0] - afAD[2]*aafC[0][0];
d0 = contactDistance + extents0.x*aafAbsC[2][0] + extents0.z*aafAbsC[0][0] + extents1.y*aafAbsC[1][2] + extents1.z*aafAbsC[1][1];
if(ABS_GREATER(d, d0)) return 0;
d = afAD[0]*aafC[2][1] - afAD[2]*aafC[0][1];
d0 = contactDistance + extents0.x*aafAbsC[2][1] + extents0.z*aafAbsC[0][1] + extents1.x*aafAbsC[1][2] + extents1.z*aafAbsC[1][0];
if(ABS_GREATER(d, d0)) return 0;
d = afAD[0]*aafC[2][2] - afAD[2]*aafC[0][2];
d0 = contactDistance + extents0.x*aafAbsC[2][2] + extents0.z*aafAbsC[0][2] + extents1.x*aafAbsC[1][1] + extents1.y*aafAbsC[1][0];
if(ABS_GREATER(d, d0)) return 0;
d = afAD[1]*aafC[0][0] - afAD[0]*aafC[1][0];
d0 = contactDistance + extents0.x*aafAbsC[1][0] + extents0.y*aafAbsC[0][0] + extents1.y*aafAbsC[2][2] + extents1.z*aafAbsC[2][1];
if(ABS_GREATER(d, d0)) return 0;
d = afAD[1]*aafC[0][1] - afAD[0]*aafC[1][1];
d0 = contactDistance + extents0.x*aafAbsC[1][1] + extents0.y*aafAbsC[0][1] + extents1.x*aafAbsC[2][2] + extents1.z*aafAbsC[2][0];
if(ABS_GREATER(d, d0)) return 0;
d = afAD[1]*aafC[0][2] - afAD[0]*aafC[1][2];
d0 = contactDistance + extents0.x*aafAbsC[1][2] + extents0.y*aafAbsC[0][2] + extents1.x*aafAbsC[2][1] + extents1.y*aafAbsC[2][0];
if(ABS_GREATER(d, d0)) return 0;
}
/* djs - tempUserData can be zero when it gets here
- maybe if there was no previous axis?
- which causes stack corruption, and thence a crash, in .NET
PT: right! At first tempUserData wasn't ever supposed to be zero, but then I used that
value to mark separation of boxes, and forgot to update the code below. Now I think
the test is redundant with the one performed above, and the line could eventually
be merged in the previous block. I'll do that later when removing all the #defines.
*/
// NB: the "16" here has nothing to do with MAX_NB_CTCS. Don't touch.
if(collisionData) // if initialized & not previously separated
overlap[collisionData-1] *= 0.999f; // Favorise previous axis .999 is too little.
PxReal minimum = PX_MAX_REAL;
PxI32 minIndex = 0;
for(PxU32 i=AXIS_A0; i<6; i++)
{
PxReal d = overlap[i];
if(d>=0.0f && d<minimum) { minimum=d; minIndex=PxI32(i); } // >=0 !! otherwise bug at sep = 0
}
collisionData = PxU32(minIndex + 1); // Leave "0" for separation
#if PX_X86
const PxU32 sign = PXC_IS_NEGATIVE(d1[minIndex]);
#else
const PxU32 sign = PxU32(PXC_IS_NEGATIVE(d1[minIndex]));
#endif
PxMat34 trs;
PxVec3 ctcNrm;
switch(minIndex)
{
default:
return 0;
case AXIS_A0:
// *ctcNrm = axis00;
if(sign)
{
ctcNrm = axis00;
trs.m = transform0.m;
trs.p = transform0.p - extents0.x*axis00;
}
else
{
// *ctcNrm = -*ctcNrm;
ctcNrm = -axis00;
trs.m.column0 = -axis00;
trs.m.column1 = -axis01;
trs.m.column2 = axis02;
trs.p = transform0.p + extents0.x*axis00;
}
// return generateContacts(ctcPts, depths, extents0.y, extents0.z, extents1, trs, transform1, contactDistance);
return generateContacts(contactBuffer, ctcNrm, extents0.y, extents0.z, extents1, trs, transform1, contactDistance);
case AXIS_A1:
// *ctcNrm = axis01;
trs.m.column2 = axis00; // Factored out
if(sign)
{
ctcNrm = axis01;
trs.m.column0 = axis01;
trs.m.column1 = axis02;
trs.p = transform0.p - extents0.y*axis01;
}
else
{
// *ctcNrm = -*ctcNrm;
ctcNrm = -axis01;
trs.m.column0 = -axis01;
trs.m.column1 = -axis02;
trs.p = transform0.p + extents0.y*axis01;
}
// return generateContacts(ctcPts, depths, extents0.z, extents0.x, extents1, trs, transform1, contactDistance);
return generateContacts(contactBuffer, ctcNrm, extents0.z, extents0.x, extents1, trs, transform1, contactDistance);
case AXIS_A2:
// *ctcNrm = axis02;
trs.m.column2 = axis01; // Factored out
if(sign)
{
ctcNrm = axis02;
trs.m.column0 = axis02;
trs.m.column1 = axis00;
trs.p = transform0.p - extents0.z*axis02;
}
else
{
// *ctcNrm = -*ctcNrm;
ctcNrm = -axis02;
trs.m.column0 = -axis02;
trs.m.column1 = -axis00;
trs.p = transform0.p + extents0.z*axis02;
}
// return generateContacts(ctcPts, depths, extents0.x, extents0.y, extents1, trs, transform1, contactDistance);
return generateContacts(contactBuffer, ctcNrm, extents0.x, extents0.y, extents1, trs, transform1, contactDistance);
case AXIS_B0:
// *ctcNrm = axis10;
if(sign)
{
ctcNrm = axis10;
trs.m.column0 = -axis10;
trs.m.column1 = -axis11;
trs.m.column2 = axis12;
trs.p = transform1.p + extents1.x*axis10;
}
else
{
// *ctcNrm = -*ctcNrm;
ctcNrm = -axis10;
trs.m = transform1.m;
trs.p = transform1.p - extents1.x*axis10;
}
// return generateContacts(ctcPts, depths, extents1.y, extents1.z, extents0, trs, transform0, contactDistance);
return generateContacts(contactBuffer, ctcNrm, extents1.y, extents1.z, extents0, trs, transform0, contactDistance);
case AXIS_B1:
// *ctcNrm = axis11;
trs.m.column2 = axis10; // Factored out
if(sign)
{
ctcNrm = axis11;
trs.m.column0 = -axis11;
trs.m.column1 = -axis12;
trs.p = transform1.p + extents1.y*axis11;
}
else
{
// *ctcNrm = -*ctcNrm;
ctcNrm = -axis11;
trs.m.column0 = axis11;
trs.m.column1 = axis12;
trs.m.column2 = axis10;
trs.p = transform1.p - extents1.y*axis11;
}
// return generateContacts(ctcPts, depths, extents1.z, extents1.x, extents0, trs, transform0, contactDistance);
return generateContacts(contactBuffer, ctcNrm, extents1.z, extents1.x, extents0, trs, transform0, contactDistance);
case AXIS_B2:
// *ctcNrm = axis12;
trs.m.column2 = axis11; // Factored out
if(sign)
{
ctcNrm = axis12;
trs.m.column0 = -axis12;
trs.m.column1 = -axis10;
trs.p = transform1.p + extents1.z*axis12;
}
else
{
// *ctcNrm = -*ctcNrm;
ctcNrm = -axis12;
trs.m.column0 = axis12;
trs.m.column1 = axis10;
trs.p = transform1.p - extents1.z*axis12;
}
// return generateContacts(ctcPts, depths, extents1.x, extents1.y, extents0, trs, transform0, contactDistance);
return generateContacts(contactBuffer, ctcNrm, extents1.x, extents1.y, extents0, trs, transform0, contactDistance);
}
}

View File

@@ -0,0 +1,442 @@
// 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 "GuIntersectionRayBox.h"
#include "GuDistanceSegmentBox.h"
#include "GuInternal.h"
#include "GuContactMethodImpl.h"
#include "GuBoxConversion.h"
#include "foundation/PxUtilities.h"
using namespace physx;
using namespace Gu;
/*namespace Gu
{
const PxU8* getBoxEdges();
}*/
/////////
/*#include "common/PxRenderOutput.h"
#include "PxsContext.h"
static void gVisualizeBox(const Box& box, PxcNpThreadContext& context, PxU32 color=0xffffff)
{
PxMat33 rot(box.base.column0, box.base.column1, box.base.column2);
PxMat44 m(rot, box.origin);
DebugBox db(box.extent);
PxRenderOutput& out = context.mRenderOutput;
out << color << m;
out << db;
}
static void gVisualizeLine(const PxVec3& a, const PxVec3& b, PxcNpThreadContext& context, PxU32 color=0xffffff)
{
PxMat44 m = PxMat44::identity();
RenderOutput& out = context.mRenderOutput;
out << color << m << RenderOutput::LINES << a << b;
}*/
/////////
static const PxReal fatBoxEdgeCoeff = 0.01f;
static bool intersectEdgeEdgePreca(const PxVec3& p1, const PxVec3& p2, const PxVec3& v1, const PxPlane& plane, PxU32 i, PxU32 j, float coeff, const PxVec3& dir, const PxVec3& p3, const PxVec3& p4, PxReal& dist, PxVec3& ip)
{
// if colliding edge (p3,p4) does not cross plane return no collision
// same as if p3 and p4 on same side of plane return 0
//
// Derivation:
// d3 = d(p3, P) = (p3 | plane.n) - plane.d; Reversed sign compared to Plane::Distance() because plane.d is negated.
// d4 = d(p4, P) = (p4 | plane.n) - plane.d; Reversed sign compared to Plane::Distance() because plane.d is negated.
// if d3 and d4 have the same sign, they're on the same side of the plane => no collision
// We test both sides at the same time by only testing Sign(d3 * d4).
// ### put that in the Plane class
// ### also check that code in the triangle class that might be similar
const PxReal d3 = plane.distance(p3);
PxReal temp = d3 * plane.distance(p4);
if(temp>0.0f) return false;
// if colliding edge (p3,p4) and plane are parallel return no collision
PxVec3 v2 = p4 - p3;
temp = plane.n.dot(v2);
if(temp==0.0f) return false; // ### epsilon would be better
// compute intersection point of plane and colliding edge (p3,p4)
ip = p3-v2*(d3/temp);
// compute distance of intersection from line (ip, -dir) to line (p1,p2)
dist = (v1[i]*(ip[j]-p1[j])-v1[j]*(ip[i]-p1[i]))*coeff;
if(dist<0.0f) return false;
// compute intersection point on edge (p1,p2) line
ip -= dist*dir;
// check if intersection point (ip) is between edge (p1,p2) vertices
temp = (p1.x-ip.x)*(p2.x-ip.x)+(p1.y-ip.y)*(p2.y-ip.y)+(p1.z-ip.z)*(p2.z-ip.z);
if(temp<0.0f) return true; // collision found
return false; // no collision
}
static bool GuTestAxis(const PxVec3& axis, const Segment& segment, PxReal radius, const Box& box, 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 box
PxReal Min1, Max1;
{
const PxReal BoxCen = box.center.dot(axis);
const PxReal BoxExt =
PxAbs(box.rot.column0.dot(axis)) * box.extents.x
+ PxAbs(box.rot.column1.dot(axis)) * box.extents.y
+ PxAbs(box.rot.column2.dot(axis)) * box.extents.z;
Min1 = BoxCen - BoxExt;
Max1 = BoxCen + BoxExt;
}
// 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;
}
static bool GuCapsuleOBBOverlap3(const Segment& segment, PxReal radius, const Box& box, PxReal* t=NULL, PxVec3* pp=NULL)
{
PxVec3 Sep(PxReal(0));
PxReal PenDepth = PX_MAX_REAL;
// Test normals
for(PxU32 i=0;i<3;i++)
{
PxReal d;
if(!GuTestAxis(box.rot[i], segment, radius, box, d))
return false;
if(d<PenDepth)
{
PenDepth = d;
Sep = box.rot[i];
}
}
// Test edges
PxVec3 CapsuleAxis(segment.p1 - segment.p0);
CapsuleAxis = CapsuleAxis.getNormalized();
for(PxU32 i=0;i<3;i++)
{
PxVec3 Cross = CapsuleAxis.cross(box.rot[i]);
if(!isAlmostZero(Cross))
{
Cross = Cross.getNormalized();
PxReal d;
if(!GuTestAxis(Cross, segment, radius, box, d))
return false;
if(d<PenDepth)
{
PenDepth = d;
Sep = Cross;
}
}
}
const PxVec3 Witness = segment.computeCenter() - box.center;
if(Sep.dot(Witness) < 0.0f)
Sep = -Sep;
if(t)
*t = PenDepth;
if(pp)
*pp = Sep;
return true;
}
static void GuGenerateVFContacts( PxContactBuffer& contactBuffer,
//
const Segment& segment,
PxReal radius,
//
const Box& worldBox,
//
const PxVec3& normal,
PxReal contactDistance)
{
const PxVec3 Max = worldBox.extents;
const PxVec3 Min = -worldBox.extents;
const PxVec3 tmp2 = - worldBox.rot.transformTranspose(normal);
const PxVec3* PX_RESTRICT Ptr = &segment.p0;
for(PxU32 i=0;i<2;i++)
{
const PxVec3& Pos = Ptr[i];
const PxVec3 tmp = worldBox.rot.transformTranspose(Pos - worldBox.center);
PxReal tnear, tfar;
int Res = intersectRayAABB(Min, Max, tmp, tmp2, tnear, tfar);
if(Res!=-1 && tnear < radius + contactDistance)
{
contactBuffer.contact(Pos - tnear * normal, normal, tnear - radius);
}
}
}
// PT: this looks similar to PxcGenerateEEContacts2 but it is mandatory to properly handle thin capsules.
static void GuGenerateEEContacts( PxContactBuffer& contactBuffer,
//
const Segment& segment,
const PxReal radius,
//
const Box& worldBox,
//
const PxVec3& normal)
{
const PxU8* PX_RESTRICT Indices = getBoxEdges();
PxVec3 Pts[8];
worldBox.computeBoxPoints(Pts);
PxVec3 s0 = segment.p0;
PxVec3 s1 = segment.p1;
makeFatEdge(s0, s1, fatBoxEdgeCoeff);
// PT: precomputed part of edge-edge intersection test
// const PxVec3 v1 = segment.p1 - segment.p0;
const PxVec3 v1 = s1 - s0;
PxPlane plane;
plane.n = v1.cross(normal);
// plane.d = -(plane.normal|segment.p0);
plane.d = -(plane.n.dot(s0));
PxU32 ii,jj;
closestAxis(plane.n, ii, jj);
const float coeff = 1.0f /(v1[ii]*normal[jj]-v1[jj]*normal[ii]);
for(PxU32 i=0;i<12;i++)
{
// PxVec3 p1 = Pts[*Indices++];
// PxVec3 p2 = Pts[*Indices++];
// makeFatEdge(p1, p2, fatBoxEdgeCoeff); // PT: TODO: make fat segment instead
const PxVec3& p1 = Pts[*Indices++];
const PxVec3& p2 = Pts[*Indices++];
// PT: keep original code in case something goes wrong
// PxReal dist;
// PxVec3 ip;
// if(intersectEdgeEdge(p1, p2, -normal, segment.p0, segment.p1, dist, ip))
// contactBuffer.contact(ip, normal, - (radius + dist));
PxReal dist;
PxVec3 ip;
if(intersectEdgeEdgePreca(s0, s1, v1, plane, ii, jj, coeff, normal, p1, p2, dist, ip))
// if(intersectEdgeEdgePreca(segment.p0, segment.p1, v1, plane, ii, jj, coeff, normal, p1, p2, dist, ip))
{
contactBuffer.contact(ip-normal*dist, normal, - (radius + dist));
// if(contactBuffer.count==2) // PT: we only need 2 contacts to be stable
// return;
}
}
}
static void GuGenerateEEContacts2( PxContactBuffer& contactBuffer,
//
const Segment& segment,
PxReal radius,
//
const Box& worldBox,
//
const PxVec3& normal,
PxReal contactDistance)
{
const PxU8* PX_RESTRICT Indices = getBoxEdges();
PxVec3 Pts[8];
worldBox.computeBoxPoints(Pts);
PxVec3 s0 = segment.p0;
PxVec3 s1 = segment.p1;
makeFatEdge(s0, s1, fatBoxEdgeCoeff);
// PT: precomputed part of edge-edge intersection test
// const PxVec3 v1 = segment.p1 - segment.p0;
const PxVec3 v1 = s1 - s0;
PxPlane plane;
plane.n = -(v1.cross(normal));
// plane.d = -(plane.normal|segment.p0);
plane.d = -(plane.n.dot(s0));
PxU32 ii,jj;
closestAxis(plane.n, ii, jj);
const float coeff = 1.0f /(v1[jj]*normal[ii]-v1[ii]*normal[jj]);
for(PxU32 i=0;i<12;i++)
{
// PxVec3 p1 = Pts[*Indices++];
// PxVec3 p2 = Pts[*Indices++];
// makeFatEdge(p1, p2, fatBoxEdgeCoeff); // PT: TODO: make fat segment instead
const PxVec3& p1 = Pts[*Indices++];
const PxVec3& p2 = Pts[*Indices++];
// PT: keep original code in case something goes wrong
// PxReal dist;
// PxVec3 ip;
// bool contact = intersectEdgeEdge(p1, p2, normal, segment.p0, segment.p1, dist, ip);
// if(contact && dist < radius + contactDistance)
// contactBuffer.contact(ip, normal, dist - radius);
PxReal dist;
PxVec3 ip;
// bool contact = intersectEdgeEdgePreca(segment.p0, segment.p1, v1, plane, ii, jj, coeff, -normal, p1, p2, dist, ip);
bool contact = intersectEdgeEdgePreca(s0, s1, v1, plane, ii, jj, coeff, -normal, p1, p2, dist, ip);
if(contact && dist < radius + contactDistance)
{
contactBuffer.contact(ip-normal*dist, normal, dist - radius);
// if(contactBuffer.count==2) // PT: we only need 2 contacts to be stable
// return;
}
}
}
bool Gu::contactCapsuleBox(GU_CONTACT_METHOD_ARGS)
{
PX_UNUSED(renderOutput);
PX_UNUSED(cache);
// Get actual shape data
const PxCapsuleGeometry& shapeCapsule = checkedCast<PxCapsuleGeometry>(shape0);
const PxBoxGeometry& shapeBox = checkedCast<PxBoxGeometry>(shape1);
// PT: TODO: move computations to local space
// Capsule data
Segment worldSegment;
getCapsuleSegment(transform0, shapeCapsule, worldSegment);
const PxReal inflatedRadius = shapeCapsule.radius + params.mContactDistance;
// Box data
Box worldBox;
buildFrom(worldBox, transform1.p, shapeBox.halfExtents, transform1.q);
// Collision detection
PxReal t;
PxVec3 onBox;
const PxReal squareDist = distanceSegmentBoxSquared(worldSegment.p0, worldSegment.p1, worldBox.center, worldBox.extents, worldBox.rot, &t, &onBox);
if(squareDist >= inflatedRadius*inflatedRadius)
return false;
PX_ASSERT(contactBuffer.count==0);
if(squareDist != 0.0f)
{
// PT: the capsule segment doesn't intersect the box => distance-based version
const PxVec3 onSegment = worldSegment.getPointAt(t);
onBox = worldBox.center + worldBox.rot.transform(onBox);
PxVec3 normal = onSegment - onBox;
PxReal normalLen = normal.magnitude();
if(normalLen > 0.0f)
{
normal *= 1.0f/normalLen;
// PT: generate VF contacts for segment's vertices vs box
GuGenerateVFContacts(contactBuffer, worldSegment, shapeCapsule.radius, worldBox, normal, params.mContactDistance);
// PT: early exit if we already have 2 stable contacts
if(contactBuffer.count==2)
return true;
// PT: else generate slower EE contacts
GuGenerateEEContacts2(contactBuffer, worldSegment, shapeCapsule.radius, worldBox, normal, params.mContactDistance);
// PT: run VF case for box-vertex-vs-capsule only if we don't have any contact yet
if(!contactBuffer.count)
contactBuffer.contact(onBox, normal, sqrtf(squareDist) - shapeCapsule.radius);
}
else
{
// On linux we encountered the following:
// For a case where a segment endpoint lies on the surface of a box, the squared distance between segment and box was tiny but still larger than 0.
// However, the computation of the normal length was exactly 0. In that case we should have switched to the penetration based version so we do it now
// instead.
goto PenetrationBasedCode;
}
}
else
{
PenetrationBasedCode:
// PT: the capsule segment intersects the box => penetration-based version
// PT: compute penetration vector (MTD)
PxVec3 sepAxis;
PxReal depth;
if(!GuCapsuleOBBOverlap3(worldSegment, shapeCapsule.radius, worldBox, &depth, &sepAxis)) return false;
// PT: generate VF contacts for segment's vertices vs box
GuGenerateVFContacts(contactBuffer, worldSegment, shapeCapsule.radius, worldBox, sepAxis, params.mContactDistance);
// PT: early exit if we already have 2 stable contacts
if(contactBuffer.count==2)
return true;
// PT: else generate slower EE contacts
GuGenerateEEContacts(contactBuffer, worldSegment, shapeCapsule.radius, worldBox, sepAxis);
if(!contactBuffer.count)
{
contactBuffer.contact(worldSegment.computeCenter(), sepAxis, -(shapeCapsule.radius + depth));
return true;
}
}
return true;
}

View File

@@ -0,0 +1,148 @@
// 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 "GuDistanceSegmentSegment.h"
#include "GuContactMethodImpl.h"
#include "GuInternal.h"
using namespace physx;
bool Gu::contactCapsuleCapsule(GU_CONTACT_METHOD_ARGS)
{
PX_UNUSED(renderOutput);
PX_UNUSED(cache);
const PxCapsuleGeometry& capsuleGeom0 = checkedCast<PxCapsuleGeometry>(shape0);
const PxCapsuleGeometry& capsuleGeom1 = checkedCast<PxCapsuleGeometry>(shape1);
// PT: get capsules in local space
PxVec3 dir[2];
Segment segment[2];
{
const PxVec3 capsuleLocalSegment0 = getCapsuleHalfHeightVector(transform0, capsuleGeom0);
const PxVec3 capsuleLocalSegment1 = getCapsuleHalfHeightVector(transform1, capsuleGeom1);
const PxVec3 delta = transform1.p - transform0.p;
segment[0].p0 = capsuleLocalSegment0;
segment[0].p1 = -capsuleLocalSegment0;
dir[0] = -capsuleLocalSegment0*2.0f;
segment[1].p0 = capsuleLocalSegment1 + delta;
segment[1].p1 = -capsuleLocalSegment1 + delta;
dir[1] = -capsuleLocalSegment1*2.0f;
}
// PT: compute distance between capsules' segments
PxReal s,t;
const PxReal squareDist = distanceSegmentSegmentSquared(segment[0], segment[1], &s, &t);
const PxReal radiusSum = capsuleGeom0.radius + capsuleGeom1.radius;
const PxReal inflatedSum = radiusSum + params.mContactDistance;
const PxReal inflatedSumSquared = inflatedSum*inflatedSum;
if(squareDist >= inflatedSumSquared)
return false;
// PT: TODO: optimize this away
PxReal segLen[2];
segLen[0] = dir[0].magnitude();
segLen[1] = dir[1].magnitude();
if (segLen[0]) dir[0] *= 1.0f / segLen[0];
if (segLen[1]) dir[1] *= 1.0f / segLen[1];
if (PxAbs(dir[0].dot(dir[1])) > 0.9998f) //almost parallel, ca. 1 degree difference --> generate two contact points at ends
{
PxU32 numCons = 0;
PxReal segLenEps[2];
segLenEps[0] = segLen[0] * 0.001f;//0.1% error is ok.
segLenEps[1] = segLen[1] * 0.001f;
//project the two end points of each onto the axis of the other and take those 4 points.
//we could also generate a single normal at the single closest point, but this would be 'unstable'.
for (PxU32 destShapeIndex = 0; destShapeIndex < 2; destShapeIndex ++)
{
for (PxU32 startEnd = 0; startEnd < 2; startEnd ++)
{
const PxU32 srcShapeIndex = 1-destShapeIndex;
//project start/end of srcShapeIndex onto destShapeIndex.
PxVec3 pos[2];
pos[destShapeIndex] = startEnd ? segment[srcShapeIndex].p1 : segment[srcShapeIndex].p0;
const PxReal p = dir[destShapeIndex].dot(pos[destShapeIndex] - segment[destShapeIndex].p0);
if (p >= -segLenEps[destShapeIndex] && p <= (segLen[destShapeIndex] + segLenEps[destShapeIndex]))
{
pos[srcShapeIndex] = p * dir[destShapeIndex] + segment[destShapeIndex].p0;
PxVec3 normal = pos[1] - pos[0];
const PxReal normalLenSq = normal.magnitudeSquared();
if (normalLenSq > 1e-6f && normalLenSq < inflatedSumSquared)
{
const PxReal distance = PxSqrt(normalLenSq);
normal *= 1.0f/distance;
PxVec3 point = pos[1] - normal * (srcShapeIndex ? capsuleGeom1 : capsuleGeom0).radius;
point += transform0.p;
contactBuffer.contact(point, normal, distance - radiusSum);
numCons++;
}
}
}
}
if (numCons) //if we did not have contacts, then we may have the case where they are parallel, but are stacked end to end, in which case the old code will generate good contacts.
return true;
}
// Collision response
PxVec3 pos1 = segment[0].getPointAt(s);
PxVec3 pos2 = segment[1].getPointAt(t);
PxVec3 normal = pos1 - pos2;
const PxReal normalLenSq = normal.magnitudeSquared();
if (normalLenSq < 1e-6f)
{
// PT: TODO: revisit this. "FW" sounds old.
// Zero normal -> pick the direction of segment 0.
// Not always accurate but consistent with FW.
if (segLen[0] > 1e-6f)
normal = dir[0];
else
normal = PxVec3(1.0f, 0.0f, 0.0f);
}
else
{
normal *= PxRecipSqrt(normalLenSq);
}
pos1 += transform0.p;
contactBuffer.contact(pos1 - normal * capsuleGeom0.radius, normal, PxSqrt(squareDist) - radiusSum);
return true;
}

View File

@@ -0,0 +1,577 @@
// 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 "GuConvexMesh.h"
#include "GuConvexHelper.h"
#include "GuContactMethodImpl.h"
#include "GuVecConvexHull.h"
#include "GuVecCapsule.h"
#include "GuInternal.h"
#include "GuGJK.h"
#include "CmMatrix34.h"
using namespace physx;
using namespace Gu;
using namespace Cm;
///////////
// #include "PxRenderOutput.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 << RenderOutput::LINES << a << b;
// }
///////////
static const PxReal fatConvexEdgeCoeff = 0.01f;
static bool intersectEdgeEdgePreca(const PxVec3& p1, const PxVec3& p2, const PxVec3& v1, const PxPlane& plane, PxU32 i, PxU32 j, float coeff, const PxVec3& dir, const PxVec3& p3, const PxVec3& p4, PxReal& dist, PxVec3& ip, float limit)
{
// if colliding edge (p3,p4) does not cross plane return no collision
// same as if p3 and p4 on same side of plane return 0
//
// Derivation:
// d3 = d(p3, P) = (p3 | plane.n) - plane.d; Reversed sign compared to Plane::Distance() because plane.d is negated.
// d4 = d(p4, P) = (p4 | plane.n) - plane.d; Reversed sign compared to Plane::Distance() because plane.d is negated.
// if d3 and d4 have the same sign, they're on the same side of the plane => no collision
// We test both sides at the same time by only testing Sign(d3 * d4).
// ### put that in the Plane class
// ### also check that code in the triangle class that might be similar
const PxReal d3 = plane.distance(p3);
PxReal temp = d3 * plane.distance(p4);
if(temp>0.0f)
return false;
// if colliding edge (p3,p4) and plane are parallel return no collision
PxVec3 v2 = p4 - p3;
temp = plane.n.dot(v2);
if(temp==0.0f)
return false; // ### epsilon would be better
// compute intersection point of plane and colliding edge (p3,p4)
ip = p3-v2*(d3/temp);
// compute distance of intersection from line (ip, -dir) to line (p1,p2)
dist = (v1[i]*(ip[j]-p1[j])-v1[j]*(ip[i]-p1[i]))*coeff;
if(dist<limit)
return false;
// compute intersection point on edge (p1,p2) line
ip -= dist*dir;
// check if intersection point (ip) is between edge (p1,p2) vertices
temp = (p1.x-ip.x)*(p2.x-ip.x)+(p1.y-ip.y)*(p2.y-ip.y)+(p1.z-ip.z)*(p2.z-ip.z);
if(temp<0.0f)
return true; // collision found
return false; // no collision
}
static bool GuTestAxis(const PxVec3& axis, const Segment& segment, PxReal radius,
const PolygonalData& polyData, const FastVertex2ShapeScaling& scaling,
const PxMat34& worldTM,
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 convex
PxReal Min1, Max1;
(polyData.mProjectHull)(polyData, axis, worldTM, scaling, Min1, Max1);
// 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;
}
static bool GuCapsuleConvexOverlap(const Segment& segment, PxReal radius,
const PolygonalData& polyData,
const FastVertex2ShapeScaling& scaling,
const PxTransform& transform,
PxReal* t, PxVec3* pp, bool isSphere)
{
// TODO:
// - test normal & edge in same loop
// - local space
// - use precomputed face value
// - optimize projection
PxVec3 Sep(0,0,0);
PxReal PenDepth = PX_MAX_REAL;
PxU32 nbPolys = polyData.mNbPolygons;
const HullPolygonData* polys = polyData.mPolygons;
const Matrix34FromTransform worldTM(transform);
// Test normals
for(PxU32 i=0;i<nbPolys;i++)
{
const HullPolygonData& poly = polys[i];
const PxPlane& vertSpacePlane = poly.mPlane;
const PxVec3 worldNormal = worldTM.rotate(vertSpacePlane.n);
PxReal d;
if(!GuTestAxis(worldNormal, segment, radius, polyData, scaling, worldTM, d))
return false;
if(d<PenDepth)
{
PenDepth = d;
Sep = worldNormal;
}
}
// Test edges
if(!isSphere)
{
PxVec3 CapsuleAxis(segment.p1 - segment.p0);
CapsuleAxis = CapsuleAxis.getNormalized();
for(PxU32 i=0;i<nbPolys;i++)
{
const HullPolygonData& poly = polys[i];
const PxPlane& vertSpacePlane = poly.mPlane;
const PxVec3 worldNormal = worldTM.rotate(vertSpacePlane.n);
PxVec3 Cross = CapsuleAxis.cross(worldNormal);
if(!isAlmostZero(Cross))
{
Cross = Cross.getNormalized();
PxReal d;
if(!GuTestAxis(Cross, segment, radius, polyData, scaling, worldTM, d))
return false;
if(d<PenDepth)
{
PenDepth = d;
Sep = Cross;
}
}
}
}
const PxVec3 Witness = segment.computeCenter() - transform.transform(polyData.mCenter);
if(Sep.dot(Witness) < 0.0f)
Sep = -Sep;
if(t) *t = PenDepth;
if(pp) *pp = Sep;
return true;
}
static bool raycast_convexMesh2( const PolygonalData& polyData,
const PxVec3& vrayOrig, const PxVec3& vrayDir,
PxReal maxDist, PxF32& t)
{
PxU32 nPolys = polyData.mNbPolygons;
const HullPolygonData* PX_RESTRICT polys = polyData.mPolygons;
/*
Purely convex planes based algorithm
Iterate all planes of convex, with following rules:
* determine of ray origin is inside them all or not.
* planes parallel to ray direction are immediate early out if we're on the outside side (plane normal is sep axis)
* else
- for all planes the ray direction "enters" from the front side, track the one furthest along the ray direction (A)
- for all planes the ray direction "exits" from the back side, track the one furthest along the negative ray direction (B)
if the ray origin is outside the convex and if along the ray, A comes before B, the directed line stabs the convex at A
*/
PxReal latestEntry = -FLT_MAX;
PxReal earliestExit = FLT_MAX;
while(nPolys--)
{
const HullPolygonData& poly = *polys++;
const PxPlane& vertSpacePlane = poly.mPlane;
const PxReal distToPlane = vertSpacePlane.distance(vrayOrig);
const PxReal dn = vertSpacePlane.n.dot(vrayDir);
const PxReal distAlongRay = -distToPlane/dn;
if (dn > 1E-7f) //the ray direction "exits" from the back side
{
earliestExit = physx::intrinsics::selectMin(earliestExit, distAlongRay);
}
else if (dn < -1E-7f) //the ray direction "enters" from the front side
{
/* if (distAlongRay > latestEntry)
{
latestEntry = distAlongRay;
}*/
latestEntry = physx::intrinsics::selectMax(latestEntry, distAlongRay);
}
else
{
//plane normal and ray dir are orthogonal
if(distToPlane > 0.0f)
return false; //a plane is parallel with ray -- and we're outside the ray -- we definitely miss the entire convex!
}
}
if(latestEntry < earliestExit && latestEntry != -FLT_MAX && latestEntry < maxDist-1e-5f)
{
t = latestEntry;
return true;
}
return false;
}
// PT: version based on Gu::raycast_convexMesh to handle scaling, but modified to make sure it works when ray starts inside the convex
static void GuGenerateVFContacts2(PxContactBuffer& contactBuffer,
//
const PxTransform& convexPose,
const PolygonalData& polyData, // Convex data
const PxMeshScale& scale,
//
PxU32 nbPts,
const PxVec3* PX_RESTRICT points,
PxReal radius, // Capsule's radius
//
const PxVec3& normal,
PxReal contactDistance)
{
PX_ASSERT(PxAbs(normal.magnitudeSquared()-1)<1e-4f);
//scaling: transform the ray to vertex space
const PxMat34 world2vertexSkew = scale.getInverse() * convexPose.getInverse();
const PxVec3 vrayDir = world2vertexSkew.rotate( -normal );
const PxReal maxDist = contactDistance + radius;
for(PxU32 i=0;i<nbPts;i++)
{
const PxVec3& rayOrigin = points[i];
const PxVec3 vrayOrig = world2vertexSkew.transform(rayOrigin);
PxF32 t;
if(raycast_convexMesh2(polyData, vrayOrig, vrayDir, maxDist, t))
{
contactBuffer.contact(rayOrigin - t * normal, normal, t - radius);
}
}
}
static void GuGenerateEEContacts( PxContactBuffer& contactBuffer,
//
const Segment& segment,
PxReal radius,
PxReal contactDistance,
//
const PolygonalData& polyData,
const PxTransform& transform,
const FastVertex2ShapeScaling& scaling,
//
const PxVec3& normal)
{
PxU32 numPolygons = polyData.mNbPolygons;
const HullPolygonData* PX_RESTRICT polygons = polyData.mPolygons;
const PxU8* PX_RESTRICT vertexData = polyData.mPolygonVertexRefs;
ConvexEdge edges[512];
PxU32 nbEdges = findUniqueConvexEdges(512, edges, numPolygons, polygons, vertexData);
//
PxVec3 s0 = segment.p0;
PxVec3 s1 = segment.p1;
makeFatEdge(s0, s1, fatConvexEdgeCoeff);
// PT: precomputed part of edge-edge intersection test
// const PxVec3 v1 = segment.p1 - segment.p0;
const PxVec3 v1 = s1 - s0;
PxPlane plane;
plane.n = v1.cross(normal);
// plane.d = -(plane.normal|segment.p0);
plane.d = -(plane.n.dot(s0));
PxU32 ii,jj;
closestAxis(plane.n, ii, jj);
const float coeff = 1.0f /(v1[ii]*normal[jj]-v1[jj]*normal[ii]);
//
const PxVec3* PX_RESTRICT verts = polyData.mVerts;
for(PxU32 i=0;i<nbEdges;i++)
{
const PxU8 vi0 = edges[i].vref0;
const PxU8 vi1 = edges[i].vref1;
// PxVec3 p1 = transform.transform(verts[vi0]);
// PxVec3 p2 = transform.transform(verts[vi1]);
// makeFatEdge(p1, p2, fatConvexEdgeCoeff); // PT: TODO: make fat segment instead
const PxVec3 p1 = transform.transform(scaling * verts[vi0]);
const PxVec3 p2 = transform.transform(scaling * verts[vi1]);
PxReal dist;
PxVec3 ip;
// if(intersectEdgeEdgePreca(segment.p0, segment.p1, v1, plane, ii, jj, coeff, normal, p1, p2, dist, ip))
// if(intersectEdgeEdgePreca(s0, s1, v1, plane, ii, jj, coeff, normal, p1, p2, dist, ip, -FLT_MAX))
if(intersectEdgeEdgePreca(s0, s1, v1, plane, ii, jj, coeff, normal, p1, p2, dist, ip, -radius-contactDistance))
// if(intersectEdgeEdgePreca(s0, s1, v1, plane, ii, jj, coeff, normal, p1, p2, dist, ip, 0))
{
contactBuffer.contact(ip-normal*dist, normal, - (radius + dist));
// if(contactBuffer.count==2) // PT: we only need 2 contacts to be stable
// return;
}
}
}
static void GuGenerateEEContacts2b(PxContactBuffer& contactBuffer,
//
const Segment& segment,
PxReal radius,
//
const PxMat34& transform,
const PolygonalData& polyData,
const FastVertex2ShapeScaling& scaling,
//
const PxVec3& normal,
PxReal contactDistance)
{
// TODO:
// - local space
const PxVec3 localDir = transform.rotateTranspose(normal);
PxU32 polyIndex = (polyData.mSelectClosestEdgeCB)(polyData, scaling, localDir);
PxVec3 s0 = segment.p0;
PxVec3 s1 = segment.p1;
makeFatEdge(s0, s1, fatConvexEdgeCoeff);
// PT: precomputed part of edge-edge intersection test
// const PxVec3 v1 = segment.p1 - segment.p0;
const PxVec3 v1 = s1 - s0;
PxPlane plane;
plane.n = -(v1.cross(normal));
// plane.d = -(plane.normal|segment.p0);
plane.d = -(plane.n.dot(s0));
PxU32 ii,jj;
closestAxis(plane.n, ii, jj);
const float coeff = 1.0f /(v1[jj]*normal[ii]-v1[ii]*normal[jj]);
//
const PxVec3* PX_RESTRICT verts = polyData.mVerts;
const HullPolygonData& polygon = polyData.mPolygons[polyIndex];
const PxU8* PX_RESTRICT vRefBase = polyData.mPolygonVertexRefs + polygon.mVRef8;
PxU32 numEdges = polygon.mNbVerts;
PxU32 a = numEdges - 1;
PxU32 b = 0;
while(numEdges--)
{
// const PxVec3 p1 = transform.transform(verts[vRefBase[a]]);
// const PxVec3 p2 = transform.transform(verts[vRefBase[b]]);
const PxVec3 p1 = transform.transform(scaling * verts[vRefBase[a]]);
const PxVec3 p2 = transform.transform(scaling * verts[vRefBase[b]]);
PxReal dist;
PxVec3 ip;
// bool contact = intersectEdgeEdgePreca(segment.p0, segment.p1, v1, plane, ii, jj, coeff, -normal, p1, p2, dist, ip);
bool contact = intersectEdgeEdgePreca(s0, s1, v1, plane, ii, jj, coeff, -normal, p1, p2, dist, ip, 0.0f);
if(contact && dist < radius + contactDistance)
{
contactBuffer.contact(ip-normal*dist, normal, dist - radius);
// if(contactBuffer.count==2) // PT: we only need 2 contacts to be stable
// return;
}
a = b;
b++;
}
}
bool Gu::contactCapsuleConvex(GU_CONTACT_METHOD_ARGS)
{
PX_UNUSED(renderOutput);
PX_UNUSED(cache);
// Get actual shape data
// PT: the capsule can be a sphere in this case so we do this special piece of code:
PxCapsuleGeometry shapeCapsule = static_cast<const PxCapsuleGeometry&>(shape0);
if(shape0.getType()==PxGeometryType::eSPHERE)
shapeCapsule.halfHeight = 0.0f;
const PxConvexMeshGeometry& shapeConvex = checkedCast<PxConvexMeshGeometry>(shape1);
PxVec3 onSegment, onConvex;
PxReal distance;
PxVec3 normal_;
{
const ConvexMesh* cm = static_cast<const ConvexMesh*>(shapeConvex.convexMesh);
using namespace aos;
Vec3V closA, closB, normalV;
GjkStatus status;
FloatV dist;
{
const Vec3V zeroV = V3Zero();
const ConvexHullData* hullData = &cm->getHull();
const FloatV capsuleHalfHeight = FLoad(shapeCapsule.halfHeight);
const Vec3V vScale = V3LoadU_SafeReadW(shapeConvex.scale.scale); // PT: safe because 'rotation' follows 'scale' in PxMeshScale
const QuatV vQuat = QuatVLoadU(&shapeConvex.scale.rotation.x);
const PxMatTransformV aToB(transform1.transformInv(transform0));
const ConvexHullV convexHull(hullData, zeroV, vScale, vQuat, shapeConvex.scale.isIdentity());
//transform capsule(a) into the local space of convexHull(b), treat capsule as segment
const CapsuleV capsule(aToB.p, aToB.rotate(V3Scale(V3UnitX(), capsuleHalfHeight)), FZero());
const LocalConvex<CapsuleV> convexA(capsule);
const LocalConvex<ConvexHullV> convexB(convexHull);
const Vec3V initialSearchDir = V3Sub(convexA.getCenter(), convexB.getCenter());
status = gjk<LocalConvex<CapsuleV>, LocalConvex<ConvexHullV> >(convexA, convexB, initialSearchDir, FMax(),closA, closB, normalV, dist);
}
if(status == GJK_CONTACT)
distance = 0.f;
else
{
//const FloatV sqDist = FMul(dist, dist);
V3StoreU(closB, onConvex);
FStore(dist, &distance);
V3StoreU(normalV, normal_);
onConvex = transform1.transform(onConvex);
normal_ = transform1.rotate(normal_);
}
}
const PxReal inflatedRadius = shapeCapsule.radius + params.mContactDistance;
if(distance >= inflatedRadius)
return false;
Segment worldSegment;
getCapsuleSegment(transform0, shapeCapsule, worldSegment);
const bool isSphere = worldSegment.p0 == worldSegment.p1;
const PxU32 nbPts = PxU32(isSphere ? 1 : 2);
PX_ASSERT(contactBuffer.count==0);
FastVertex2ShapeScaling convexScaling;
const bool idtConvexScale = shapeConvex.scale.isIdentity();
if(!idtConvexScale)
convexScaling.init(shapeConvex.scale);
PolygonalData polyData;
getPolygonalData_Convex(&polyData, _getHullData(shapeConvex), convexScaling);
// if(0)
if(distance > 0.f)
{
// PT: the capsule segment doesn't intersect the convex => distance-based version
PxVec3 normal = -normal_;
// PT: generate VF contacts for segment's vertices vs convex
GuGenerateVFContacts2( contactBuffer,
transform1, polyData, shapeConvex.scale,
nbPts, &worldSegment.p0, shapeCapsule.radius,
normal, params.mContactDistance);
// PT: early exit if we already have 2 stable contacts
if(contactBuffer.count==2)
return true;
// PT: else generate slower EE contacts
if(!isSphere)
{
const Matrix34FromTransform worldTM(transform1);
GuGenerateEEContacts2b(contactBuffer, worldSegment, shapeCapsule.radius,
worldTM, polyData, convexScaling,
normal, params.mContactDistance);
}
// PT: run VF case for convex-vertex-vs-capsule only if we don't have any contact yet
if(!contactBuffer.count)
{
// gVisualizeLine(onConvex, onConvex + normal, context, PxDebugColor::eARGB_RED);
//PxReal distance = PxSqrt(sqDistance);
contactBuffer.contact(onConvex, normal, distance - shapeCapsule.radius);
}
}
else
{
// PT: the capsule segment intersects the convex => penetration-based version
//printf("Penetration-based:\n");
// PT: compute penetration vector (MTD)
PxVec3 SepAxis;
if(!GuCapsuleConvexOverlap(worldSegment, shapeCapsule.radius, polyData, convexScaling, transform1, NULL, &SepAxis, isSphere))
{
//printf("- no overlap\n");
return false;
}
// PT: generate VF contacts for segment's vertices vs convex
GuGenerateVFContacts2( contactBuffer,
transform1, polyData, shapeConvex.scale,
nbPts, &worldSegment.p0, shapeCapsule.radius,
SepAxis, params.mContactDistance);
// PT: early exit if we already have 2 stable contacts
//printf("- %d VF contacts\n", contactBuffer.count);
if(contactBuffer.count==2)
return true;
// PT: else generate slower EE contacts
if(!isSphere)
{
GuGenerateEEContacts(contactBuffer, worldSegment, shapeCapsule.radius, params.mContactDistance, polyData, transform1, convexScaling, SepAxis);
//printf("- %d total contacts\n", contactBuffer.count);
}
}
return true;
}

View File

@@ -0,0 +1,637 @@
// 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 "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;
PxTransform idt = PxTransform(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*/)
{
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*/)
{
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)
{
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;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,66 @@
// 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 "GuGJKPenetration.h"
#include "GuEPA.h"
#include "GuVecConvexHull.h"
#include "GuVecConvexHullNoScale.h"
#include "GuContactMethodImpl.h"
#include "GuPCMShapeConvex.h"
#include "GuPCMContactGen.h"
#include "GuConvexGeometry.h"
#include "GuConvexSupport.h"
#include "GuRefGjkEpa.h"
using namespace physx;
using namespace Gu;
using namespace aos;
bool Gu::contactConvexCoreConvex(GU_CONTACT_METHOD_ARGS)
{
PX_UNUSED(cache);
PX_UNUSED(renderOutput);
const PxVec3 shift = (transform0.p + transform1.p) * 0.5f;
const PxTransform pose0(transform0.p - shift, transform0.q);
const PxTransform pose1(transform1.p - shift, transform1.q);
const PxReal contactDist = params.mContactDistance;
ConvexShape convex0; Gu::makeConvexShape(shape0, pose0, convex0);
ConvexShape convex1; Gu::makeConvexShape(shape1, pose1, convex1);
PX_ASSERT(convex0.isValid() && convex1.isValid());
PxVec3 normal, points[Gu::MAX_CONVEX_CONTACTS];
PxReal dists[Gu::MAX_CONVEX_CONTACTS];
if (PxU32 count = Gu::generateContacts(convex0, convex1, contactDist, normal, points, dists))
for (PxU32 i = 0; i < count; ++i)
contactBuffer.contact(points[i] + shift, normal, dists[i]);
return contactBuffer.count > 0;
}

View File

@@ -0,0 +1,435 @@
// 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 "GuContactPolygonPolygon.h"
#include "GuContactMethodImpl.h"
#include "GuMidphaseInterface.h"
#include "GuHeightFieldUtil.h"
#include "GuEntityReport.h"
#include "GuBounds.h"
#include "GuConvexGeometry.h"
#include "GuConvexSupport.h"
#include "GuContactReduction.h"
#include <GuTriangleMesh.h>
using namespace physx;
using namespace Gu;
using namespace Cm;
using namespace aos;
using namespace intrinsics;
namespace
{
struct TriangleMeshTriangles
{
const TriangleMesh* data;
const PxMeshScale& scale;
TriangleMeshTriangles(const TriangleMesh* _data, const PxMeshScale& _scale)
:
data(_data), scale(_scale)
{}
void getVertexIndices(PxU32 triIndex, PxU32& i0, PxU32& i1, PxU32& i2) const
{
const void* tris = data->getTriangles();
const bool ints16bit = data->has16BitIndices();
getVertexRefs(triIndex, i0, i1, i2, tris, ints16bit);
}
PxVec3 getVertex(PxU32 vertIndex) const
{
const PxVec3* verts = data->getVertices();
return scale.transform(verts[vertIndex]);
}
bool hasAdjacency() const
{
return data->getAdjacencies() != NULL;
}
PxU32 getAdjacentTriIndex(PxU32 triIndex, PxU32 edgIndex) const
{
const PxU32* adjucent = data->getAdjacencies();
return adjucent[triIndex * 3 + edgIndex];
}
};
struct HeightFieldTriangles
{
const HeightFieldUtil& hfUtil;
HeightFieldTriangles(const HeightFieldUtil& _hfUtil)
:
hfUtil(_hfUtil)
{}
void getVertexIndices(PxU32 triIndex, PxU32& i0, PxU32& i1, PxU32& i2) const
{
hfUtil.mHeightField->getTriangleVertexIndices(triIndex, i0, i1, i2);
}
PxVec3 getVertex(PxU32 vertIndex) const
{
PxVec3 v = hfUtil.mHeightField->getVertex(vertIndex);
PxVec3 s(hfUtil.mHfGeom->rowScale, hfUtil.mHfGeom->heightScale, hfUtil.mHfGeom->columnScale);
return PxVec3(v.x * s.x, v.y * s.y, v.z * s.z);
}
bool hasAdjacency() const
{
return true;
}
PxU32 getAdjacentTriIndex(PxU32 triIndex, PxU32 edgIndex) const
{
PxU32 adjucent[3];
hfUtil.mHeightField->getTriangleAdjacencyIndices(triIndex, 0, 0, 0, adjucent[0], adjucent[1], adjucent[2]);
return adjucent[edgIndex];
}
};
PxVec3 computeBarycentric(const PxVec3& a, const PxVec3& b, const PxVec3& c, const PxVec3& p)
{
PxVec4 bary;
PxComputeBarycentric(a, b, c, p, bary);
//PxReal u = bary.x, v = bary.y, w = bary.z;
//PX_ASSERT((a * u + b * v + c * w - p).magnitude() < 1e-3f); // VR: find out why this asserts sometimes
return bary.getXYZ();
}
template <typename TriangleSource>
bool validateContact(const PxVec3& normal, const PxVec3& pointB, PxU32 triIndex, const TriangleSource& tris)
{
const PxReal eps = 1e-5f;
PxU32 i0, i1, i2;
tris.getVertexIndices(triIndex, i0, i1, i2);
const PxVec3 v0 = tris.getVertex(i0),
v1 = tris.getVertex(i1),
v2 = tris.getVertex(i2);
const PxVec3 tn = (v1 - v0).cross(v2 - v0).getNormalized();
// close enough to a face contact
if (tn.dot(normal) > 0.99f)
// better to accept
return true;
const PxVec3 bc = computeBarycentric(v0, v1, v2, pointB);
// face contact
if (bc.x > eps && bc.x < 1.0f - eps &&
bc.y > eps && bc.y < 1.0f - eps &&
bc.z > eps && bc.z < 1.0f - eps)
// always accept
return true;
// vertex contact
if (bc.x > 1.0f - eps ||
bc.y > 1.0f - eps ||
bc.z > 1.0f - eps)
{
PxU32 vrtIndex = 0xffffffff;
if (tris.hasAdjacency())
{
if (bc.x > 1.0f - eps)
vrtIndex = 0;
else if (bc.y > 1.0f - eps)
vrtIndex = 1;
else if (bc.z > 1.0f - eps)
vrtIndex = 2;
}
if (vrtIndex != 0xffffffff)
{
PxU32 ai[] = { i0, i1, i2 };
PxU32 ai0 = ai[vrtIndex];
PxU32 adjIndex = tris.getAdjacentTriIndex(triIndex, (vrtIndex + 2) % 3);
while (adjIndex != triIndex && adjIndex != 0xffffffff)
{
// walking through the adjucent triangles surrounding the vertex and checking
// if any other end of the edges sharing the vertex projects onto the contact
// normal higher than the vertex itself.it'd meand that the contact normal is
// out of the vertex's voronoi region.
PxU32 bi[3]; tris.getVertexIndices(adjIndex, bi[0], bi[1], bi[2]);
for (PxU32 i = 0; i < 3; ++i)
{
PxU32 bi0 = bi[i], bi1 = bi[(i + 1) % 3], bi2 = bi[(i + 2) % 3];
if (bi1 == ai0)
{
const PxVec3 bv0 = tris.getVertex(bi0),
bv1 = tris.getVertex(bi1),
bv2 = tris.getVertex(bi2);
const PxReal bd10 = normal.dot((bv0 - bv1).getNormalized()),
bd12 = normal.dot((bv2 - bv1).getNormalized());
if (bd10 > eps || bd12 > eps)
// the vertex is hidden by one of the adjacent
// edges we can't collide with this vertex
return false;
// next triangle to check
adjIndex = tris.getAdjacentTriIndex(adjIndex, i);
break;
}
}
}
}
return true;
}
// edge contact
PxU32 edgIndex = 0xffffffff;
if (tris.hasAdjacency())
{
if (bc.x < eps)
edgIndex = 1;
else if (bc.y < eps)
edgIndex = 2;
else if (bc.z < eps)
edgIndex = 0;
}
if (edgIndex != 0xffffffff)
{
PxU32 ai[] = { i0, i1, i2 };
PxU32 ai0 = ai[edgIndex], ai1 = ai[(edgIndex + 1) % 3];
PxU32 adjIndex = tris.getAdjacentTriIndex(triIndex, edgIndex);
if (adjIndex != 0xffffffff)
{
// testing if the adjacent triangle's vertex opposite to this edge
// projects onto the contact normal higher than the edge itself. it'd
// mean that the normal is out of the edge's voronoi region.
PxU32 bi[3]; tris.getVertexIndices(adjIndex, bi[0], bi[1], bi[2]);
for (PxU32 i = 0; i < 3; ++i)
{
PxU32 bi0 = bi[i], bi1 = bi[(i + 1) % 3], bi2 = bi[(i + 2) % 3];
if (bi0 == ai1 && bi1 == ai0)
{
const PxVec3 bv1 = tris.getVertex(bi1),
bv2 = tris.getVertex(bi2);
const PxReal bd12 = normal.dot((bv2 - bv1).getNormalized());
if (bd12 > eps)
// the edge is hidden by the adjacent triangle
// we can't collide with this edge
return false;
}
}
}
}
return true;
}
}
bool Gu::contactConvexCoreTrimesh(GU_CONTACT_METHOD_ARGS)
{
PX_UNUSED(cache);
PX_UNUSED(renderOutput);
struct Callback : MeshHitCallback<PxGeomRaycastHit>
{
const Gu::ConvexShape& mConvex;
const PxMeshScale& mScale;
const TriangleMesh* mData;
const PxReal mContactDist;
const PxReal mTriMargin;
const PxTransform& mTransform;
Gu::Contact& mContact;
PxRenderOutput* mRenderOutput;
Callback(const Gu::ConvexShape& convex, const PxMeshScale& scale, const TriangleMesh* data, PxReal contactDist,
PxReal triMargin, const PxTransform& transform, Gu::Contact& contact, PxRenderOutput* renderOutput)
:
MeshHitCallback<PxGeomRaycastHit>(CallbackMode::eMULTIPLE),
mConvex(convex), mScale(scale), mData(data), mContactDist(contactDist),
mTriMargin(triMargin), mTransform(transform), mContact(contact), mRenderOutput(renderOutput)
{}
virtual PxAgain processHit(const PxGeomRaycastHit& hit, const PxVec3& v0, const PxVec3& v1, const PxVec3& v2,
PxReal&, const PxU32*)
{
const PxVec3 verts[] = { v0, v1, v2 };
Gu::ConvexShape tri;
tri.coreType = Gu::ConvexCore::Type::ePOINTS;
tri.pose = PxTransform(PxIdentity);
Gu::ConvexCore::PointsCore& core = *reinterpret_cast<Gu::ConvexCore::PointsCore*>(tri.coreData);
core.points = verts;
core.numPoints = 3;
core.stride = sizeof(PxVec3);
core.S = mScale.scale;
core.R = mScale.rotation;
tri.margin = mTriMargin;
const PxVec3 triNormal = (v1 - v0).cross(v2 - v0).getNormalized();
TriangleMeshTriangles triSource(mData, mScale);
PxVec3 normal, points[Gu::MAX_CONVEX_CONTACTS];
PxReal dists[Gu::MAX_CONVEX_CONTACTS];
if (PxU32 count = Gu::generateContacts(mConvex, tri, mContactDist, triNormal, normal, points, dists))
{
const PxVec3 worldNormal = mTransform.rotate(normal);
for (PxU32 i = 0; i < count; ++i)
{
PxVec3 pointB = points[i] - normal * dists[i];
if (validateContact(normal, pointB, hit.faceIndex, triSource))
{
const PxVec3 worldPoint = mTransform.transform(points[i]);
mContact.addPoint(worldPoint, worldNormal, dists[i]);
}
}
}
return true;
}
};
const PxConvexCoreGeometry& shapeConvex = checkedCast<PxConvexCoreGeometry>(shape0);
const PxTriangleMeshGeometry& shapeMesh = checkedCast<PxTriangleMeshGeometry>(shape1);
const TriangleMesh* meshData = _getMeshData(shapeMesh);
const PxTransform transform0in1 = transform1.transformInv(transform0);
const PxBounds3 bounds = Gu::computeBounds(shapeConvex, PxTransform(PxIdentity));
Box queryBox;
queryBox.extents = bounds.getExtents() + PxVec3(params.mContactDistance);
queryBox.center = transform0in1.transform(bounds.getCenter());
queryBox.rot = PxMat33(transform0in1.q);
PxReal triMargin = queryBox.extents.minElement() * 0.0001f;
const FastVertex2ShapeScaling meshScaling(shapeMesh.scale);
meshScaling.transformQueryBounds(queryBox.center, queryBox.extents, queryBox.rot);
Gu::Contact contact;
Gu::ConvexShape convex; Gu::makeConvexShape(shapeConvex, transform0in1, convex);
Callback callback(convex, shapeMesh.scale, meshData, params.mContactDistance, triMargin, transform1, contact, renderOutput);
Midphase::intersectOBB(meshData, queryBox, callback, false);
for (PxU32 i = 0; i < contact.numPatches(); ++i)
for (PxU32 j = 0; j < contact.numPatchPoints(i); ++j)
contactBuffer.contact(contact.patchPoint(i, j).p, contact.patchNormal(i), contact.patchPoint(i, j).d);
return contactBuffer.count > 0;
}
bool Gu::contactConvexCoreHeightfield(GU_CONTACT_METHOD_ARGS)
{
PX_UNUSED(cache);
PX_UNUSED(renderOutput);
struct Callback : Gu::OverlapReport
{
const Gu::ConvexShape& mConvex;
const HeightFieldUtil& mHfUtil;
const PxReal mContactDist;
const PxTransform& mTransform;
Gu::Contact& mContact;
Callback(const Gu::ConvexShape& convex, const HeightFieldUtil& hfUtil, const PxReal contactDist,
const PxTransform& transform, Gu::Contact& contact)
:
mConvex(convex), mHfUtil(hfUtil), mContactDist(contactDist), mTransform(transform), mContact(contact)
{}
virtual bool reportTouchedTris(PxU32 numTris, const PxU32* triInds)
{
HeightFieldTriangles triSource(mHfUtil);
for (PxU32 t = 0; t < numTris; ++t)
{
PxU32 triIndex = triInds[t];
PxU32 vertInds[3];
triSource.getVertexIndices(triIndex, vertInds[0], vertInds[1], vertInds[2]);
PxVec3 verts[] = { triSource.getVertex(vertInds[0]),
triSource.getVertex(vertInds[1]),
triSource.getVertex(vertInds[2]) };
Gu::ConvexShape tri;
tri.coreType = Gu::ConvexCore::Type::ePOINTS;
tri.pose = PxTransform(PxIdentity);
Gu::ConvexCore::PointsCore& core = *reinterpret_cast<Gu::ConvexCore::PointsCore*>(tri.coreData);
core.points = verts;
core.numPoints = 3;
core.stride = sizeof(PxVec3);
core.S = PxVec3(1);
core.R = PxQuat(PxIdentity);
tri.margin = 0.0f;
PxVec3 normal, points[Gu::MAX_CONVEX_CONTACTS];
PxReal dists[Gu::MAX_CONVEX_CONTACTS];
if (PxU32 count = Gu::generateContacts(mConvex, tri, mContactDist, normal, points, dists))
{
const PxVec3 worldNormal = mTransform.rotate(normal);
for (PxU32 i = 0; i < count; ++i)
{
// VR: disabled for now - find out why it skips the tris it shouldn't
//PxVec3 pointB = points[i] - normal * (dists[i] * 0.5f);
//if (validateContact(normal, pointB, triIndex, triSource))
{
const PxVec3 worldPoint = mTransform.transform(points[i]);
mContact.addPoint(worldPoint, worldNormal, dists[i]);
}
}
}
}
return true;
}
};
const PxConvexCoreGeometry& shapeConvex = checkedCast<PxConvexCoreGeometry>(shape0);
const PxHeightFieldGeometry& shapeHeightfield = checkedCast<PxHeightFieldGeometry>(shape1);
const HeightFieldUtil hfUtil(shapeHeightfield);
const PxTransform transform0in1 = transform1.transformInv(transform0);
PxBounds3 bounds = Gu::computeBounds(shapeConvex, PxTransform(PxIdentity));
bounds.fattenFast(params.mContactDistance);
Gu::Contact contact;
Gu::ConvexShape convex; Gu::makeConvexShape(shapeConvex, transform0in1, convex);
Callback callback(convex, hfUtil, params.mContactDistance, transform1, contact);
hfUtil.overlapAABBTriangles0to1(transform0in1, bounds, callback);
for (PxU32 i = 0; i < contact.numPatches(); ++i)
for (PxU32 j = 0; j < contact.numPatchPoints(i); ++j)
contactBuffer.contact(contact.patchPoint(i, j).p, contact.patchNormal(i), contact.patchPoint(i, j).d);
return contactBuffer.count > 0;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,56 @@
// 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 "GuContactMethodImpl.h"
using namespace physx;
bool Gu::contactCustomGeometryGeometry(GU_CONTACT_METHOD_ARGS)
{
PX_UNUSED(renderOutput);
PX_UNUSED(cache);
const PxCustomGeometry& customGeom = checkedCast<PxCustomGeometry>(shape0);
const PxGeometry& otherGeom = shape1;
customGeom.callbacks->generateContacts(customGeom, otherGeom, transform0, transform1,
params.mContactDistance, params.mMeshContactMargin, params.mToleranceLength,
contactBuffer);
return true;
}
bool Gu::contactGeometryCustomGeometry(GU_CONTACT_METHOD_ARGS)
{
bool res = contactCustomGeometryGeometry(shape1, shape0, transform1, transform0, params, cache, contactBuffer, renderOutput);
for (PxU32 i = 0; i < contactBuffer.count; ++i)
contactBuffer.contacts[i].normal = -contactBuffer.contacts[i].normal;
return res;
}

View File

@@ -0,0 +1,285 @@
// 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 "foundation/PxArray.h"
#include "foundation/PxAssert.h"
#include "foundation/PxBounds3.h"
#include "foundation/PxMat33.h"
#include "foundation/PxMath.h"
#include "foundation/PxPreprocessor.h"
#include "foundation/PxSimpleTypes.h"
#include "foundation/PxTransform.h"
#include "foundation/PxVec3.h"
#include "geometry/PxBoxGeometry.h"
#include "geometry/PxMeshQuery.h"
#include "geometry/PxTriangleMesh.h"
#include "GuCollisionSDF.h"
#include "GuTriangleMesh.h"
#include "GuContactMeshMesh.h"
#include "GuContactMethodImpl.h"
#include "GuContactReduction.h"
#include "GuMidphaseInterface.h"
#include "GuTriangle.h"
#include "GuTriangleRefinement.h"
using namespace physx;
using namespace Gu;
using ContactReduction = SDFContactReduction<5, 10000, 32>;
const PxI32 maxRefinementLevel = 8;
struct TransformedTriangle
{
PxVec3 v0, v1, v2;
PxI16 refinementLevel;
PxU8 boundary; // information about boundaries; currently unused
};
PX_FORCE_INLINE bool needsRefinement(const PxReal triRefThreshold, const TransformedTriangle& tri)
{
return (
(tri.v0-tri.v1).magnitudeSquared() > triRefThreshold ||
(tri.v1-tri.v2).magnitudeSquared() > triRefThreshold ||
(tri.v2-tri.v0).magnitudeSquared() > triRefThreshold
) && tri.refinementLevel < maxRefinementLevel;
}
// Find contacts between an SDF and a triangle mesh and return the number of contacts generated
PxU32 sdfMeshCollision (
const PxTransform32& PX_RESTRICT tfSdf, const PxTriangleMeshGeometry& PX_RESTRICT sdfGeom,
const PxTransform32& PX_RESTRICT tfMesh, const PxTriangleMeshGeometry& PX_RESTRICT meshGeom,
ContactReduction& contactReducer, const PxReal totalContactDistance, bool flipContactNormals
)
{
float min_separation = PX_MAX_REAL;
const TriangleMesh& mesh = static_cast<const TriangleMesh&>(*meshGeom.triangleMesh);
const TriangleMesh& sdfMesh = static_cast<const TriangleMesh&>(*sdfGeom.triangleMesh);
const PxMeshScale& sdfScale = sdfGeom.scale, & meshScale = meshGeom.scale;
const CollisionSDF& PX_RESTRICT sdf(sdfMesh.mSdfData);
const PxTransform meshToSdf = tfSdf.transformInv(tfMesh);
const PxMat33 sdfScaleMat = sdfScale.toMat33();
PxBounds3 sdfBoundsAtWorldScale(sdfScaleMat.transform(sdf.mSdfBoxLower), sdfScaleMat.transform(sdf.mSdfBoxUpper));
sdfBoundsAtWorldScale.fattenSafe(totalContactDistance);
const PxTransform poseT(sdfBoundsAtWorldScale.getCenter());
const PxBoxGeometry boxGeom(sdfBoundsAtWorldScale.getExtents());
const PxReal sdfDiagSq = (sdf.mSdfBoxUpper - sdf.mSdfBoxLower).magnitudeSquared();
const PxReal div = 1.0f / 256.0f;
const PxReal triRefThreshold = sdfDiagSq * div;
const bool singleSdf = meshGeom.triangleMesh->getSDF() == NULL; // triangle subdivision if single SDF
const PxU32 MAX_INTERSECTIONS = 1024 * 32;
PxArray<PxU32> overlappingTriangles;
overlappingTriangles.resize(MAX_INTERSECTIONS); //TODO: Not ideal, dynamic allocation for every function call
//PxU32 overlappingTriangles[MAX_INTERSECTIONS]; //TODO: Is this too much memory to allocate on the stack?
bool overflow = false;
const PxU32 overlapCount = PxMeshQuery::findOverlapTriangleMesh(boxGeom, poseT, meshGeom, meshToSdf, overlappingTriangles.begin(), MAX_INTERSECTIONS, 0, overflow);
PX_ASSERT(!overflow);
// we use cullScale to account for SDF scaling whenever distances are
const PxReal cullScale = totalContactDistance / sdfScale.scale.minElement();
const PxVec3* PX_RESTRICT vertices = mesh.getVertices();
const void* PX_RESTRICT tris = mesh.getTriangles();
const bool has16BitIndices = mesh.getTriangleMeshFlags() & physx::PxTriangleMeshFlag::e16_BIT_INDICES;
const PxU32 nbTris = overlapCount; // mesh.getNbTriangles();
/* Transforms fused; unoptimized version:
v0 = shape2Vertex(
meshToSdf.transform(vertex2Shape(vertices[triIndices.mRef[0]], meshScale.scale, meshScale.rotation)),
sdfScale.scale, sdfScale.rotation); */
const PxMat33 sdfScaleIMat = sdfScale.getInverse().toMat33();
const PxMat33 fusedRotScale = sdfScaleIMat * PxMat33Padded(meshToSdf.q) * meshScale.toMat33();
const PxVec3 fusedTranslate = sdfScaleIMat * meshToSdf.p;
const PxMat33Padded tfSdfRotationMatrix(tfSdf.q);
const PxMat33 pointToWorldR = tfSdfRotationMatrix * sdfScale.toMat33();
const PxMat33 normalToWorld = tfSdfRotationMatrix * sdfScaleIMat;
const PxU32 COLLISION_BUF_SIZE = 512;
const PxU32 sudivBufSize = singleSdf ? maxRefinementLevel * 3 : 0; // Overhead for subdivision (pop one, push four)
PX_ASSERT(sudivBufSize < COLLISION_BUF_SIZE/4); // ensure reasonable buffer size
TransformedTriangle goodTriangles[COLLISION_BUF_SIZE];
PxU32 nbContacts = 0;
for (PxU32 i = 0, allTrisProcessed = 0; !allTrisProcessed;)
{
// try to find `COLLISION_BUF_SIZE` triangles that cannot be culled immediately
PxU32 nbGoodTris = 0;
// Every triangle that overlaps with the sdf's axis aligned bounding box is checked against the sdf to see if an intersection
// can be ruled out. If an intersection can be ruled out, the triangle is not further processed. Since SDF data is accessed,
// the check is more accurate (but still very fast) than a simple bounding box overlap test.
// Performance measurements confirm that this pre-pruning loop actually increases performance significantly on some scenes
for ( ; nbGoodTris < COLLISION_BUF_SIZE - sudivBufSize; ++i)
{
if (i == nbTris)
{
allTrisProcessed = true;
break;
}
const PxU32 triIdx = overlappingTriangles[i];
TransformedTriangle niceTri;
const Gu::IndexedTriangle32 triIndices = has16BitIndices ?
getTriangleVertexIndices<PxU16>(tris, triIdx) :
getTriangleVertexIndices<PxU32>(tris, triIdx);
niceTri.v0 = fusedTranslate + fusedRotScale * vertices[triIndices.mRef[0]];
niceTri.v1 = fusedTranslate + fusedRotScale * vertices[triIndices.mRef[1]];
niceTri.v2 = fusedTranslate + fusedRotScale * vertices[triIndices.mRef[2]];
if (singleSdf)
niceTri.refinementLevel = 0;
// - triangles that are not culled are added to goodTriangles
if (sdfTriangleSphericalCull(sdf, niceTri.v0, niceTri.v1, niceTri.v2, cullScale))
goodTriangles[nbGoodTris++] = niceTri;
}
// in promising triangles
// - triangles are popped from goodTriangles and a contact generated or,
// if subdivision is indicated, their children are pushed on top
for (PxU32 goodTriEnd = nbGoodTris; goodTriEnd > 0;)
{
const TransformedTriangle tri = goodTriangles[--goodTriEnd]; // pop
// decide on need for subdivision
if (singleSdf && needsRefinement(triRefThreshold, tri))
{
for (int childIdx = 0; childIdx < 4; ++childIdx)
{
TransformedTriangle child = tri;
Gu::getSubTriangle4(childIdx, child.v0, child.v1, child.v2);
++child.refinementLevel;
if (sdfTriangleSphericalCull(sdf, child.v0, child.v1, child.v2, cullScale))
goodTriangles[goodTriEnd++] = child;
}
continue;
}
// generate contacts
PxVec3 sdfPoint, contactDir;
PxReal separation = sdfTriangleCollision(sdf, tri.v0, tri.v1, tri.v2, sdfPoint, contactDir, cullScale);
min_separation = PxMin(min_separation, separation);
if (separation < cullScale)
{
const PxVec3 worldPoint = tfSdf.p + pointToWorldR * sdfPoint;
contactDir = normalToWorld * contactDir;
const PxReal magSq = contactDir.magnitudeSquared();
// TODO(CA): handle case where only one mesh has an SDF and update heuristic once this is done
if (magSq < 1e-6f) // ignore contacts with a bad/missing normal
continue;
const PxReal mag = PxRecipSqrt(magSq);
if (singleSdf && tri.refinementLevel)
{
const PxVec3 n = (tri.v1 - tri.v0).getNormalized().cross(tri.v2 - tri.v0).getNormalized();
const PxVec3 sdfBoxCenter = 0.5f * (sdf.mSdfBoxUpper + sdf.mSdfBoxLower);
const PxReal triangleNormalSign = -PxSign((sdfBoxCenter - tri.v0).dot(n));
contactDir = normalToWorld * triangleNormalSign * n;
contactDir.normalize();
contactDir /= mag ;
}
separation *= mag;
contactDir *= mag;
const TinyContact contact{flipContactNormals ? -contactDir : contactDir, separation, worldPoint};
contactReducer.addContact(contact);
++nbContacts;
}
}
}
return nbContacts;
};
bool Gu::contactMeshMesh(GU_CONTACT_METHOD_ARGS)
{
PX_UNUSED(renderOutput);
PX_UNUSED(cache);
// Get meshes
const PxTriangleMeshGeometry& geom0 = checkedCast<PxTriangleMeshGeometry>(shape0),
& geom1 = checkedCast<PxTriangleMeshGeometry>(shape1);
PX_ASSERT(geom0.triangleMesh != NULL && geom1.triangleMesh != NULL);
PxU32 nbContacts = 0;
const PxReal contactDistance = params.mContactDistance; // computed in `checkContactsMustBeGenerated`
const bool mesh0PreferProj = static_cast<const TriangleMesh&>(*geom0.triangleMesh).getPreferSDFProjection(),
mesh1PreferProj = static_cast<const TriangleMesh&>(*geom1.triangleMesh).getPreferSDFProjection();
const PxU32 n0 = geom0.triangleMesh->getNbVertices(), n1 = geom1.triangleMesh->getNbVertices();
// sdf0first: in first pass, treat mesh0 as an sdf and mesh1 as a mesh
const bool sdf0first = ((!mesh0PreferProj && mesh1PreferProj) || (mesh0PreferProj == mesh1PreferProj && n1 < n0));
ContactReduction contactReducer;
const bool geom0HasSdf = geom0.triangleMesh->getSDF() != NULL,
geom1HasSdf = geom1.triangleMesh->getSDF() != NULL;
PX_ASSERT(geom0HasSdf || geom1HasSdf); // require at least one SDF
if (!geom0HasSdf && !geom1HasSdf)
{
return false;
}
if (!(geom0HasSdf && geom1HasSdf))
{
if (geom0HasSdf)
nbContacts += sdfMeshCollision(transform0, geom0, transform1, geom1, contactReducer, contactDistance, true);
else
nbContacts += sdfMeshCollision(transform1, geom1, transform0, geom0, contactReducer, contactDistance, false);
}
else if (sdf0first)
{
nbContacts += sdfMeshCollision(transform0, geom0, transform1, geom1, contactReducer, contactDistance, true);
nbContacts += sdfMeshCollision(transform1, geom1, transform0, geom0, contactReducer, contactDistance, false);
}
else
{
nbContacts += sdfMeshCollision(transform1, geom1, transform0, geom0, contactReducer, contactDistance, false);
nbContacts += sdfMeshCollision(transform0, geom0, transform1, geom1, contactReducer, contactDistance, true);
}
contactReducer.flushToContactBuffer(contactBuffer);
return nbContacts != 0;
}

View File

@@ -0,0 +1,196 @@
// 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.
#ifndef GU_MESH_MESH_COLLISION_H
#define GU_MESH_MESH_COLLISION_H
#include "foundation/PxAssert.h"
#include "foundation/PxMath.h"
#include "foundation/PxPreprocessor.h"
#include "foundation/PxSimpleTypes.h"
#include "foundation/PxVec3.h"
#include "GuCollisionSDF.h"
#include "GuDistancePointTriangle.h"
#include "GuTriangle.h"
namespace physx
{
namespace Gu
{
template <typename T>
int argmin(const T& v0, const T& v1)
{
if (v0 < v1)
return 0;
return 1;
}
template <typename T>
int argmin(const T& v0, const T& v1, const T& v2, const T& v3)
{
const int ma = argmin(v0, v1), mb = argmin(v2, v3);
if (!argmin((!ma ? v0 : v1),(!mb ? v2: v3)))
return ma;
return mb+2;
}
// Based on one SDF evaluation at the centroid, can the circumsphere of the triangle
// `v0`, `v1`, `v2` get closer to the surface given by `sdf` than `cutoffDistance`?
static PX_INLINE bool sdfTriangleSphericalCull(
const CollisionSDF& PX_RESTRICT sdf,
const PxVec3& PX_RESTRICT v0, const PxVec3& PX_RESTRICT v1, const PxVec3& PX_RESTRICT v2,
PxReal cutoffDistance)
{
const PxReal third = 1.0f / 3.0f;
const PxVec3 centroid = (v0 + v1 + v2) * third;
const PxReal sphereRadiusSq = PxMax(
(v0 - centroid).magnitudeSquared(),
PxMax((v1 - centroid).magnitudeSquared(), (v2 - centroid).magnitudeSquared()));
const PxVec3 boxPos = sdf.clampToBox(centroid);
const PxReal centroidToBoxSq = (centroid - boxPos).magnitudeSquared();
// TODO(CA): consider minimum of SDF on box boundary, making this check tighter
if (PxSqrt(centroidToBoxSq) > PxSqrt(sphereRadiusSq) + cutoffDistance)
return false; //Early out without touching SDF data
const PxReal centroidSdf = sdf.dist(centroid);
return centroidSdf < PxSqrt(sphereRadiusSq) + cutoffDistance;
}
// Find maximum separation of an sdf and a triangle and find the contact point and normal separation is below `cutoffDistance`
// Return the separation, or`PX\_MAX\_F32` if it exceeds `cutoffDistance`
template <PxU32 TMaxLineSearchIters = 0, PxU32 TMaxPGDIterations = 32, bool TFastGrad = true>
PX_INLINE PxReal sdfTriangleCollision(
const CollisionSDF& PX_RESTRICT sdf,
const PxVec3& PX_RESTRICT v0, const PxVec3& PX_RESTRICT v1, const PxVec3& PX_RESTRICT v2,
PxVec3& point, PxVec3& dir, PxReal cutoffDistance)
{
const PxReal third = 1.0f / 3.0f;
const PxVec3 centroid = (v0 + v1 + v2) * third;
// barycentric coordinates, corresponding to v0, v1, v2
PxVec3 c(0.f);
// choose starting iterate
const int start = argmin(sdf.dist(v0), sdf.dist(v1), sdf.dist(v2), sdf.dist(centroid));
switch (start)
{
case 0:
c.x = 1.f; break;
case 1:
c.y = 1.f; break;
case 2:
c.z = 1.f; break;
case 3:
c = PxVec3(third); break;
default:
PX_ASSERT(false);
}
PxReal stepSize = 0.25;
// we could also compute the gradient's lipschitz constant when baking!
PxVec3 dfdp; // gradient w.r.t. p
const PxReal toleranceSq = 1e-10f;
for (PxU32 i = 0; i < TMaxPGDIterations; ++i)
{
const PxVec3 p = c.x * v0 + c.y * v1 + c.z * v2;
PxReal dist_old = 0;
if (TFastGrad)
dist_old = sdf.dist(p, &dfdp);
else
dfdp = sdf.grad(p);
const PxReal dfdpMagSq = dfdp.magnitudeSquared();
if (dfdpMagSq == 0.0f)
{
// TODO(CA): consider expanding this into a stopping criterion
// At a critical point. Take a small step away into an arbitrary direction
dfdp = PxVec3(0.5718465865353257f, 0.7055450997557186f, 0.41856611625714474f);
}
else
dfdp *= PxRecipSqrt(dfdpMagSq);
// Simple projected gradient descent
const PxVec3 dfdc = PxVec3(dfdp.dot(v0-p), dfdp.dot(v1-p), dfdp.dot(v2-p));
const PxVec3 c_old = c;
if (TMaxLineSearchIters) //Line Search is quite expensive since it increases the number of expensive calls to sdf.dist by approximately a factor of MAX_LINE_SEARCH_ITERATIONS
{
PxReal s = 1;
if (!TFastGrad)
dist_old = sdf.dist(p);
c = closestPtPointBaryTriangle(c_old - s * dfdc);
for (PxU32 ls_it = 0; ls_it < TMaxLineSearchIters; ++ls_it)
{
// restore barycentric coordinates
const PxVec3 p_new = c.x * v0 + c.y * v1 + c.z * v2;
if (sdf.dist(p_new) <= dist_old)
{
#if 0
if (ls_it > 0)
printf("%d: %d ls iterations\n", i, ls_it+1);
#endif
break;
}
s *= 0.5f;
c = closestPtPointBaryTriangle(c_old - s * dfdc);
}
}
else
{
// take step and restore barycentric coordinates
c = closestPtPointBaryTriangle(c - stepSize * dfdc);
}
// this detects a minimum found on the boundary
if ((c - c_old).magnitudeSquared() < toleranceSq)
break;
stepSize *= 0.8f; // line search will get rid of this
}
const PxVec3 p = c.x * v0 + c.y * v1 + c.z * v2;
point = p;
return sdf.distUsingGradient(p, dir, cutoffDistance);
}
// Get the indices of the triangle at position `triIdx`.
// `T` should be `PxU16` if the mesh has 16 bit indices, and `PxU32` otherwise
template <typename T>
PX_INLINE Gu::IndexedTriangle32 getTriangleVertexIndices(const void* triangles, PxU32 triIdx)
{
const T* trisCast = reinterpret_cast<const T*>(triangles);
return {trisCast[triIdx*3], trisCast[triIdx*3+1], trisCast[triIdx*3+2]};
}
} // namespace Gu
} // namespace physx
#endif

View File

@@ -0,0 +1,208 @@
// 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.
#ifndef GU_CONTACTMETHODIMPL_H
#define GU_CONTACTMETHODIMPL_H
#include "foundation/PxAssert.h"
#include "common/PxPhysXCommonConfig.h"
#include "collision/PxCollisionDefs.h"
#include "GuGeometryChecks.h"
namespace physx
{
class PxGeometry;
class PxRenderOutput;
class PxContactBuffer;
namespace Gu
{
class PersistentContactManifold;
class MultiplePersistentContactManifold;
struct NarrowPhaseParams
{
PX_FORCE_INLINE NarrowPhaseParams(PxReal contactDistance, PxReal meshContactMargin, PxReal toleranceLength) :
mContactDistance(contactDistance),
mMeshContactMargin(meshContactMargin),
mToleranceLength(toleranceLength) {}
PxReal mContactDistance;
const PxReal mMeshContactMargin; // PT: Margin used to generate mesh contacts. Temp & unclear, should be removed once GJK is default path.
const PxReal mToleranceLength; // PT: copy of PxTolerancesScale::length
};
enum ManifoldFlags
{
IS_MANIFOLD = (1<<0),
IS_MULTI_MANIFOLD = (1<<1)
};
struct Cache : public PxCache
{
Cache()
{
}
PX_FORCE_INLINE void setManifold(void* manifold)
{
PX_ASSERT((size_t(manifold) & 0xF) == 0);
mCachedData = reinterpret_cast<PxU8*>(manifold);
mManifoldFlags |= IS_MANIFOLD;
}
PX_FORCE_INLINE void setMultiManifold(void* manifold)
{
PX_ASSERT((size_t(manifold) & 0xF) == 0);
mCachedData = reinterpret_cast<PxU8*>(manifold);
mManifoldFlags |= IS_MANIFOLD|IS_MULTI_MANIFOLD;
}
PX_FORCE_INLINE PxU8 isManifold() const
{
return PxU8(mManifoldFlags & IS_MANIFOLD);
}
PX_FORCE_INLINE PxU8 isMultiManifold() const
{
return PxU8(mManifoldFlags & IS_MULTI_MANIFOLD);
}
PX_FORCE_INLINE PersistentContactManifold& getManifold()
{
PX_ASSERT(isManifold());
PX_ASSERT(!isMultiManifold());
PX_ASSERT((uintptr_t(mCachedData) & 0xf) == 0);
return *reinterpret_cast<PersistentContactManifold*>(mCachedData);
}
PX_FORCE_INLINE MultiplePersistentContactManifold& getMultipleManifold()
{
PX_ASSERT(isManifold());
PX_ASSERT(isMultiManifold());
PX_ASSERT((uintptr_t(mCachedData) & 0xf) == 0);
return *reinterpret_cast<MultiplePersistentContactManifold*>(mCachedData);
}
};
}
template<class Geom> PX_CUDA_CALLABLE PX_FORCE_INLINE const Geom& checkedCast(const PxGeometry& geom)
{
checkType<Geom>(geom);
return static_cast<const Geom&>(geom);
}
#define GU_CONTACT_METHOD_ARGS \
const PxGeometry& shape0, \
const PxGeometry& shape1, \
const PxTransform32& transform0, \
const PxTransform32& transform1, \
const Gu::NarrowPhaseParams& params, \
Gu::Cache& cache, \
PxContactBuffer& contactBuffer, \
PxRenderOutput* renderOutput
#define GU_CONTACT_METHOD_ARGS_UNUSED \
const PxGeometry&, \
const PxGeometry&, \
const PxTransform32&, \
const PxTransform32&, \
const Gu::NarrowPhaseParams&, \
Gu::Cache&, \
PxContactBuffer&, \
PxRenderOutput*
namespace Gu
{
PX_PHYSX_COMMON_API bool contactSphereSphere(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool contactSphereCapsule(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool contactSphereBox(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool contactCapsuleCapsule(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool contactCapsuleBox(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool contactCapsuleConvex(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool contactBoxBox(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool contactBoxConvex(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool contactConvexConvex(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool contactSphereMesh(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool contactCapsuleMesh(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool contactBoxMesh(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool contactConvexMesh(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool contactSphereHeightfield(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool contactCapsuleHeightfield(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool contactBoxHeightfield(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool contactConvexHeightfield(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool contactSpherePlane(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool contactPlaneBox(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool contactPlaneCapsule(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool contactPlaneConvexCore(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool contactPlaneConvex(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool contactPlaneMesh(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool contactMeshMesh(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool contactConvexCoreConvex(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool contactConvexCoreTrimesh(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool contactConvexCoreHeightfield(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool contactCustomGeometryGeometry(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool contactGeometryCustomGeometry(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool pcmContactSphereMesh(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool pcmContactCapsuleMesh(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool pcmContactBoxMesh(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool pcmContactConvexMesh(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool pcmContactSphereHeightField(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool pcmContactCapsuleHeightField(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool pcmContactBoxHeightField(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool pcmContactConvexHeightField(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool pcmContactPlaneCapsule(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool pcmContactPlaneBox(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool pcmContactPlaneConvex(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool pcmContactSphereSphere(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool pcmContactSpherePlane(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool pcmContactSphereCapsule(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool pcmContactSphereBox(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool pcmContactSphereConvex(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool pcmContactCapsuleCapsule(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool pcmContactCapsuleBox(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool pcmContactCapsuleConvex(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool pcmContactBoxBox(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool pcmContactBoxConvex(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool pcmContactConvexConvex(GU_CONTACT_METHOD_ARGS);
PX_PHYSX_COMMON_API bool pcmContactGeometryCustomGeometry(GU_CONTACT_METHOD_ARGS);
}
}
#endif

View File

@@ -0,0 +1,122 @@
// 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 "foundation/PxUnionCast.h"
#include "geomutils/PxContactBuffer.h"
#include "GuContactMethodImpl.h"
#include "CmMatrix34.h"
#include "foundation/PxUtilities.h"
using namespace physx;
using namespace Cm;
bool Gu::contactPlaneBox(GU_CONTACT_METHOD_ARGS)
{
PX_UNUSED(renderOutput);
PX_UNUSED(cache);
PX_UNUSED(shape0);
// Get actual shape data
//const PxPlaneGeometry& shapePlane = checkedCast<PxPlaneGeometry>(shape0);
const PxBoxGeometry& shapeBox = checkedCast<PxBoxGeometry>(shape1);
const PxVec3 negPlaneNormal = -transform0.q.getBasisVector0();
//Make sure we have a normalized plane
//PX_ASSERT(PxAbs(shape0.mNormal.magnitudeSquared() - 1.0f) < 0.000001f);
const Matrix34FromTransform boxMatrix(transform1);
const Matrix34FromTransform boxToPlane(transform0.transformInv(transform1));
PxVec3 point;
PX_ASSERT(contactBuffer.count==0);
/* for(int vx=-1; vx<=1; vx+=2)
for(int vy=-1; vy<=1; vy+=2)
for(int vz=-1; vz<=1; vz+=2)
{
//point = boxToPlane.transform(PxVec3(shapeBox.halfExtents.x*vx, shapeBox.halfExtents.y*vy, shapeBox.halfExtents.z*vz));
//PxReal planeEq = point.x;
//Optimized a bit
point.set(shapeBox.halfExtents.x*vx, shapeBox.halfExtents.y*vy, shapeBox.halfExtents.z*vz);
const PxReal planeEq = boxToPlane.m.column0.x*point.x + boxToPlane.m.column1.x*point.y + boxToPlane.m.column2.x*point.z + boxToPlane.p.x;
if(planeEq <= contactDistance)
{
contactBuffer.contact(boxMatrix.transform(point), negPlaneNormal, planeEq);
//no point in making more than 4 contacts.
if (contactBuffer.count >= 6) //was: 4) actually, with strong interpenetration more than just the bottom surface goes through,
//and we want to find the *deepest* 4 vertices, really.
return true;
}
}*/
// PT: the above code is shock full of LHS/FCMPs. And there's no point in limiting the number of contacts to 6 when the max possible is 8.
const PxReal limit = params.mContactDistance - boxToPlane.p.x;
const PxReal dx = shapeBox.halfExtents.x;
const PxReal dy = shapeBox.halfExtents.y;
const PxReal dz = shapeBox.halfExtents.z;
const PxReal bxdx = boxToPlane.m.column0.x * dx;
const PxReal bxdy = boxToPlane.m.column1.x * dy;
const PxReal bxdz = boxToPlane.m.column2.x * dz;
PxReal depths[8];
depths[0] = bxdx + bxdy + bxdz - limit;
depths[1] = bxdx + bxdy - bxdz - limit;
depths[2] = bxdx - bxdy + bxdz - limit;
depths[3] = bxdx - bxdy - bxdz - limit;
depths[4] = - bxdx + bxdy + bxdz - limit;
depths[5] = - bxdx + bxdy - bxdz - limit;
depths[6] = - bxdx - bxdy + bxdz - limit;
depths[7] = - bxdx - bxdy - bxdz - limit;
//const PxU32* binary = reinterpret_cast<const PxU32*>(depths);
const PxU32* binary = PxUnionCast<PxU32*, PxF32*>(depths);
if(binary[0] & PX_SIGN_BITMASK)
contactBuffer.contact(boxMatrix.transform(PxVec3(dx, dy, dz)), negPlaneNormal, depths[0] + params.mContactDistance);
if(binary[1] & PX_SIGN_BITMASK)
contactBuffer.contact(boxMatrix.transform(PxVec3(dx, dy, -dz)), negPlaneNormal, depths[1] + params.mContactDistance);
if(binary[2] & PX_SIGN_BITMASK)
contactBuffer.contact(boxMatrix.transform(PxVec3(dx, -dy, dz)), negPlaneNormal, depths[2] + params.mContactDistance);
if(binary[3] & PX_SIGN_BITMASK)
contactBuffer.contact(boxMatrix.transform(PxVec3(dx, -dy, -dz)), negPlaneNormal, depths[3] + params.mContactDistance);
if(binary[4] & PX_SIGN_BITMASK)
contactBuffer.contact(boxMatrix.transform(PxVec3(-dx, dy, dz)), negPlaneNormal, depths[4] + params.mContactDistance);
if(binary[5] & PX_SIGN_BITMASK)
contactBuffer.contact(boxMatrix.transform(PxVec3(-dx, dy, -dz)), negPlaneNormal, depths[5] + params.mContactDistance);
if(binary[6] & PX_SIGN_BITMASK)
contactBuffer.contact(boxMatrix.transform(PxVec3(-dx, -dy, dz)), negPlaneNormal, depths[6] + params.mContactDistance);
if(binary[7] & PX_SIGN_BITMASK)
contactBuffer.contact(boxMatrix.transform(PxVec3(-dx, -dy, -dz)), negPlaneNormal, depths[7] + params.mContactDistance);
return contactBuffer.count > 0;
}

View File

@@ -0,0 +1,74 @@
// 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 "GuContactMethodImpl.h"
#include "GuInternal.h"
#include "GuSegment.h"
using namespace physx;
bool Gu::contactPlaneCapsule(GU_CONTACT_METHOD_ARGS)
{
PX_UNUSED(renderOutput);
PX_UNUSED(cache);
PX_UNUSED(shape0);
// Get actual shape data
//const PxPlaneGeometry& shapePlane = checkedCast<PxPlaneGeometry>(shape0);
const PxCapsuleGeometry& shapeCapsule = checkedCast<PxCapsuleGeometry>(shape1);
const PxTransform capsuleToPlane = transform0.transformInv(transform1);
//Capsule in plane space
Segment segment;
getCapsuleSegment(capsuleToPlane, shapeCapsule, segment);
const PxVec3 negPlaneNormal = transform0.q.getBasisVector0();
bool contact = false;
const PxReal separation0 = segment.p0.x - shapeCapsule.radius;
const PxReal separation1 = segment.p1.x - shapeCapsule.radius;
if(separation0 <= params.mContactDistance)
{
const PxVec3 temp(segment.p0.x - shapeCapsule.radius, segment.p0.y, segment.p0.z);
const PxVec3 point = transform0.transform(temp);
contactBuffer.contact(point, -negPlaneNormal, separation0);
contact = true;
}
if(separation1 <= params.mContactDistance)
{
const PxVec3 temp(segment.p1.x - shapeCapsule.radius, segment.p1.y, segment.p1.z);
const PxVec3 point = transform0.transform(temp);
contactBuffer.contact(point, -negPlaneNormal, separation1);
contact = true;
}
return contact;
}

View File

@@ -0,0 +1,98 @@
// 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 "GuConvexMeshData.h"
#include "GuContactMethodImpl.h"
#include "GuConvexMesh.h"
#include "CmScaling.h"
#include "CmMatrix34.h"
using namespace physx;
using namespace Cm;
bool Gu::contactPlaneConvex(GU_CONTACT_METHOD_ARGS)
{
PX_UNUSED(renderOutput);
PX_UNUSED(cache);
PX_UNUSED(shape0);
// Get actual shape data
//const PxPlaneGeometry& shapePlane = checkedCast<PxPlaneGeometry>(shape0);
const PxConvexMeshGeometry& shapeConvex = checkedCast<PxConvexMeshGeometry>(shape1);
const ConvexHullData* hullData = _getHullData(shapeConvex);
const PxVec3* PX_RESTRICT hullVertices = hullData->getHullVertices();
PxU32 numHullVertices = hullData->mNbHullVertices;
// PxPrefetch128(hullVertices);
// Plane is implicitly <1,0,0> 0 in localspace
const Matrix34FromTransform convexToPlane0 (transform0.transformInv(transform1));
const PxMat33 convexToPlane_rot(convexToPlane0[0], convexToPlane0[1], convexToPlane0[2] );
bool idtScale = shapeConvex.scale.isIdentity();
FastVertex2ShapeScaling convexScaling; // PT: TODO: remove default ctor
if(!idtScale)
convexScaling.init(shapeConvex.scale);
const PxMat34 convexToPlane(convexToPlane_rot * convexScaling.getVertex2ShapeSkew(), convexToPlane0[3]);
//convexToPlane = context.mVertex2ShapeSkew[1].getVertex2WorldSkew(convexToPlane);
const Matrix34FromTransform planeToW(transform0);
// This is rather brute-force
bool status = false;
const PxVec3 contactNormal = -planeToW.m.column0;
while(numHullVertices--)
{
const PxVec3& vertex = *hullVertices++;
// if(numHullVertices)
// PxPrefetch128(hullVertices);
const PxVec3 pointInPlane = convexToPlane.transform(vertex); //TODO: this multiply could be factored out!
if(pointInPlane.x <= params.mContactDistance)
{
// const PxVec3 pointInW = planeToW.transform(pointInPlane);
// contactBuffer.contact(pointInW, -planeToW.m.column0, pointInPlane.x);
status = true;
PxContactPoint* PX_RESTRICT pt = contactBuffer.contact();
if(pt)
{
pt->normal = contactNormal;
pt->point = planeToW.transform(pointInPlane);
pt->separation = pointInPlane.x;
pt->internalFaceIndex1 = PXC_CONTACT_NO_FACE_INDEX;
}
}
}
return status;
}

View File

@@ -0,0 +1,54 @@
// 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 "GuContactMethodImpl.h"
#include "geometry/PxConvexCoreGeometry.h"
#include "GuConvexGeometry.h"
#include "GuConvexSupport.h"
using namespace physx;
bool Gu::contactPlaneConvexCore(GU_CONTACT_METHOD_ARGS)
{
PX_UNUSED(shape0);
PX_UNUSED(renderOutput);
PX_UNUSED(cache);
PxPlane plane0(transform0.p, transform0.q.getBasisVector0());
Gu::ConvexShape convex1; Gu::makeConvexShape(shape1, transform1, convex1);
PX_ASSERT(convex1.isValid());
PxVec3 normal, points[Gu::MAX_CONVEX_CONTACTS];
PxReal dists[Gu::MAX_CONVEX_CONTACTS];
if (PxU32 count = Gu::generateContacts(plane0, convex1, params.mContactDistance, normal, points, dists))
for (PxU32 i = 0; i < count; ++i)
contactBuffer.contact(points[i], normal, dists[i]);
return contactBuffer.count > 0;
}

View File

@@ -0,0 +1,159 @@
// 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 "CmMatrix34.h"
#include "foundation/PxVec3.h"
#include "CmScaling.h"
#include "foundation/PxMat34.h"
#include "foundation/PxArray.h"
#include "foundation/PxAssert.h"
#include "foundation/PxBitMap.h"
#include "foundation/PxBounds3.h"
#include "foundation/PxMat33.h"
#include "foundation/PxSimpleTypes.h"
#include "geometry/PxBoxGeometry.h"
#include "geometry/PxMeshQuery.h"
#include "geometry/PxTriangleMesh.h"
#include "geomutils/PxContactBuffer.h"
#include "GuTriangle.h"
#include "geomutils/PxContactPoint.h"
#include "foundation/PxPreprocessor.h"
#include "foundation/PxTransform.h"
#include "GuContactMethodImpl.h"
#include "GuContactReduction.h"
#include "GuMidphaseInterface.h"
#include "PxContact.h"
using namespace physx;
using namespace Cm;
using BufferedContactPatch = Gu::BufferedPatch<6, 64>;
using ReducedContactPatch = Gu::TinyContactPatch<6>;
bool Gu::contactPlaneMesh(GU_CONTACT_METHOD_ARGS)
{
PX_UNUSED(renderOutput);
PX_UNUSED(cache);
PX_UNUSED(shape0);
// Get actual shape data
const PxTriangleMeshGeometry& shapeMesh = checkedCast<PxTriangleMeshGeometry>(shape1);
// Plane is implicitly <1,0,0> 0 in localspace
const PxVec3* PX_RESTRICT vertices = shapeMesh.triangleMesh->getVertices();
const PxTransform meshToPlane0Trafo = transform0.transformInv(transform1);
const Matrix34FromTransform meshToPlane0 (meshToPlane0Trafo);
const PxMat33 meshToPlane_rot(meshToPlane0[0], meshToPlane0[1], meshToPlane0[2] );
const bool idtScale = shapeMesh.scale.isIdentity();
FastVertex2ShapeScaling meshScaling;
if(!idtScale)
meshScaling.init(shapeMesh.scale);
const PxMat34 meshToPlane(meshToPlane_rot * meshScaling.getVertex2ShapeSkew(), meshToPlane0[3]);
//convexToPlane = context.mVertex2ShapeSkew[1].getVertex2WorldSkew(convexToPlane);
const Matrix34FromTransform planeToW(transform0);
const PxVec3 contactNormal = -planeToW.m.column0;
BufferedContactPatch patch;
patch.mRootNormal = contactNormal;
const PxMeshScale& sdfScale = shapeMesh.scale;
const PxMat33 sdfScaleMat = sdfScale.toMat33();
PxBounds3 meshBoundsAtWorldScale = shapeMesh.triangleMesh->getLocalBounds();
meshBoundsAtWorldScale.minimum = sdfScaleMat.transform(meshBoundsAtWorldScale.minimum);
meshBoundsAtWorldScale.maximum = sdfScaleMat.transform(meshBoundsAtWorldScale.maximum);
const PxReal radius = 0.5f*(meshBoundsAtWorldScale.maximum - meshBoundsAtWorldScale.minimum).magnitude();
PxVec3 transformedBoundsCenter = meshToPlane0Trafo.transform(meshBoundsAtWorldScale.getCenter());
transformedBoundsCenter.x = 0.0f; //In local plane space, this corresponds to a projection of a point onto the plane
const PxBoxGeometry planeBoxMesh(params.mContactDistance, radius, radius);
const PxU32 MAX_INTERSECTIONS = 1024 * 32;
PxArray<PxU32> overlappingTriangles;
overlappingTriangles.resize(MAX_INTERSECTIONS); //TODO: Not ideal, dynamic allocation for every function call
//PxU32 overlappingTriangles[MAX_INTERSECTIONS]; //TODO: Is this too much memory to allocate on the stack?
bool overflow = false;
const PxU32 overlapCount = PxMeshQuery::findOverlapTriangleMesh(planeBoxMesh, PxTransform(transformedBoundsCenter), shapeMesh, meshToPlane0Trafo, overlappingTriangles.begin(), MAX_INTERSECTIONS, 0, overflow);
PX_ASSERT(!overflow);
const bool has16BitIndices = shapeMesh.triangleMesh->getTriangleMeshFlags() & physx::PxTriangleMeshFlag::e16_BIT_INDICES;
const void* PX_RESTRICT tris = shapeMesh.triangleMesh->getTriangles();
PxBitMap bitmap;
bitmap.resize(shapeMesh.triangleMesh->getNbVertices(), false); //TODO: Not ideal, dynamic allocation for every function call
// FIXME: Make use of bvh to reduce number of checks here
bool contact = false;
for (PxU32 i = 0; i < overlapCount; ++i)
{
const PxU32 triIdx = overlappingTriangles[i];
Gu::IndexedTriangle32 triIndices;
getVertexRefs(triIdx, triIndices.mRef[0], triIndices.mRef[1], triIndices.mRef[2], tris, has16BitIndices);
for (PxU32 j = 0; j < 3; ++j)
{
const PxU32 vertexIndex = triIndices[j];
if (bitmap.test(vertexIndex))
continue;
bitmap.set(vertexIndex);
const PxVec3& vertex = vertices[vertexIndex];
const PxVec3 pointInPlane = meshToPlane.transform(vertex); //TODO: this multiply could be factored out!
if (pointInPlane.x <= params.mContactDistance)
{
contact = true;
patch.addContact(TinyContact(contactNormal, pointInPlane.x, planeToW.transform(pointInPlane)));
}
}
}
if (contact)
{
ReducedContactPatch reduced;
patch.asTinyContactPatch(reduced);
for (const TinyContact& tiny: reduced)
{
PxContactPoint* PX_RESTRICT pt = contactBuffer.contact();
if(pt != NULL)
{
pt->normal = contactNormal;
pt->point = tiny.mPoint;
pt->separation = tiny.mSeparation;
pt->internalFaceIndex1 = PXC_CONTACT_NO_FACE_INDEX;
}
}
}
return contact;
}

View File

@@ -0,0 +1,861 @@
// 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 "foundation/PxMemory.h"
#include "geomutils/PxContactBuffer.h"
#include "GuContactPolygonPolygon.h"
#include "GuShapeConvex.h"
#include "GuInternal.h"
#include "foundation/PxAlloca.h"
#include "foundation/PxFPU.h"
using namespace physx;
using namespace Gu;
#define CONTACT_REDUCTION
/*
void gVisualizeLocalLine(const PxVec3& a, const PxVec3& b, const PxMat34& m, PxsContactManager& manager) //temp debug
{
RenderOutput out = manager.getContext()->getRenderOutput();
out << 0xffffff << m << RenderOutput::LINES << a << b;
}
*/
#ifdef CONTACT_REDUCTION
static PX_FORCE_INLINE PxReal dot2D(const PxVec3& v0, const PxVec3& v1)
{
return v0.x * v1.x + v0.y * v1.y;
}
static void ContactReductionAllIn( PxContactBuffer& contactBuffer, PxU32 nbExistingContacts, PxU32 numIn,
const PxMat33& rotT,
const PxVec3* PX_RESTRICT vertices, const PxU8* PX_RESTRICT indices)
{
// Number of contacts created by current call
const PxU32 nbNewContacts = contactBuffer.count - nbExistingContacts;
if(nbNewContacts<=4)
return; // no reduction for less than 4 verts
// We have 3 different numbers here:
// - numVerts = number of vertices in the convex polygon we're dealing with
// - numIn = number of those that were "inside" the other convex polygon (should be <= numVerts)
// - contactBuffer.count = total number of contacts *from both polygons* (that's the catch here)
// The fast path can only be chosen when the contact buffer contains all the verts from current polygon,
// i.e. when contactBuffer.count == numIn == numVerts
PxContactPoint* PX_RESTRICT ctcs = contactBuffer.contacts + nbExistingContacts;
if(numIn == nbNewContacts)
{
// Codepath 1: all vertices generated a contact
PxReal deepestSeparation = ctcs[0].separation;
PxU32 deepestIndex = 0;
for(PxU32 i=1; i<nbNewContacts; ++i)
{
if(deepestSeparation > ctcs[i].separation)
{
deepestSeparation = ctcs[i].separation;
deepestIndex = i;
}
}
PxU32 index = 0;
const PxU32 step = (numIn<<16)>>2; // Fixed point math, don't use floats here please
bool needsExtraPoint = true;
for(PxU32 i=0;i<4;i++)
{
const PxU32 contactIndex = index>>16;
ctcs[i] = ctcs[contactIndex];
if(contactIndex==deepestIndex)
needsExtraPoint = false;
index += step;
}
if(needsExtraPoint)
{
ctcs[4] = ctcs[deepestIndex];
contactBuffer.count = nbExistingContacts + 5;
}
else
{
contactBuffer.count = nbExistingContacts + 4;
}
/* PT: TODO: investigate why this one does not work
PxU32 index = deepestIndex<<16;
const PxU32 step = (numIn<<16)>>2; // Fixed point math, don't use floats here please
for(PxU32 i=0;i<4;i++)
{
PxU32 contactIndex = index>>16;
if(contactIndex>=numIn)
contactIndex -= numIn;
ctcs[i] = ctcs[contactIndex];
index += step;
}
contactBuffer.count = nbExistingContacts + 4;*/
}
else
{
// Codepath 2: all vertices are "in" but only some of them generated a contact
// WARNING: this path doesn't work when the buffer contains vertices from both polys.
// TODO: precompute those axes
const PxU32 nbAxes = 8;
PxVec3 dirs[nbAxes];
float angle = 0.0f;
const float angleStep = PxDegToRad(180.0f/float(nbAxes));
for(PxU32 i=0;i<nbAxes;i++)
{
dirs[i] = PxVec3(cosf(angle), sinf(angle), 0.0f);
angle += angleStep;
}
float dpmin[nbAxes];
float dpmax[nbAxes];
for(PxU32 i=0;i<nbAxes;i++)
{
dpmin[i] = PX_MAX_F32;
dpmax[i] = -PX_MAX_F32;
}
for(PxU32 i=0;i<nbNewContacts;i++)
{
const PxVec3& p = vertices[indices[i]];
// Transform to 2D
const PxVec3 p2d = rotT.transform(p);
for(PxU32 j=0;j<nbAxes;j++)
{
const float dp = dot2D(dirs[j], p2d);
dpmin[j] = physx::intrinsics::selectMin(dpmin[j], dp);
dpmax[j] = physx::intrinsics::selectMax(dpmax[j], dp);
}
}
PxU32 bestAxis = 0;
float maxVariance = dpmax[0] - dpmin[0];
for(PxU32 i=1;i<nbAxes;i++)
{
const float variance = dpmax[i] - dpmin[i];
if(variance>maxVariance)
{
maxVariance = variance;
bestAxis = i;
}
}
const PxVec3 u = dirs[bestAxis];
const PxVec3 v = PxVec3(-u.y, u.x, 0.0f);
// PxVec3(1.0f, 0.0f, 0.0f) => PxVec3(0.0f, 1.0f, 0.0f)
// PxVec3(0.0f, 1.0f, 0.0f) => PxVec3(-1.0f, 0.0f, 0.0f)
// PxVec3(-1.0f, 1.0f, 0.0f) => PxVec3(-1.0f, -1.0f, 0.0f)
// PxVec3(1.0f, 1.0f, 0.0f) => PxVec3(-1.0f, 1.0f, 0.0f)
float dpminu = PX_MAX_F32;
float dpmaxu = -PX_MAX_F32;
float dpminv = PX_MAX_F32;
float dpmaxv = -PX_MAX_F32;
PxU32 indexMinU = 0;
PxU32 indexMaxU = 0;
PxU32 indexMinV = 0;
PxU32 indexMaxV = 0;
for(PxU32 i=0;i<nbNewContacts;i++)
{
const PxVec3& p = vertices[indices[i]];
// Transform to 2D
const PxVec3 p2d = rotT.transform(p);
const float dpu = dot2D(u, p2d);
const float dpv = dot2D(v, p2d);
if(dpu<dpminu)
{
dpminu=dpu;
indexMinU = i;
}
if(dpu>dpmaxu)
{
dpmaxu=dpu;
indexMaxU = i;
}
if(dpv<dpminv)
{
dpminv=dpv;
indexMinV = i;
}
if(dpv>dpmaxv)
{
dpmaxv=dpv;
indexMaxV = i;
}
}
if(indexMaxU == indexMinU)
indexMaxU = 0xffffffff;
if(indexMinV == indexMinU || indexMinV == indexMaxU)
indexMinV = 0xffffffff;
if(indexMaxV == indexMinU || indexMaxV == indexMaxU || indexMaxV == indexMinV)
indexMaxV = 0xffffffff;
PxU32 newCount = 0;
for(PxU32 i=0;i<nbNewContacts;i++)
{
if( i==indexMinU
|| i==indexMaxU
|| i==indexMinV
|| i==indexMaxV)
{
ctcs[newCount++] = ctcs[i];
}
}
contactBuffer.count = nbExistingContacts + newCount;
}
}
#endif
// PT: please leave that function in the same translation unit as the calling code
/*static*/ PxMat33 Gu::findRotationMatrixFromZ(const PxVec3& to)
{
PxMat33 result;
const PxReal e = to.z;
const PxReal f = PxAbs(e);
if(f <= 0.9999f)
{
// PT: please keep the normal case first for PS3 branch prediction
// Normal case, to and from are not parallel or anti-parallel
const PxVec3 v = cross001(to);
const PxReal h = 1.0f/(1.0f + e); /* optimization by Gottfried Chen */
const PxReal hvx = h * v.x;
const PxReal hvz = h * v.z;
const PxReal hvxy = hvx * v.y;
const PxReal hvxz = hvx * v.z;
const PxReal hvyz = hvz * v.y;
result(0,0) = e + hvx*v.x;
result(0,1) = hvxy - v.z;
result(0,2) = hvxz + v.y;
result(1,0) = hvxy + v.z;
result(1,1) = e + h*v.y*v.y;
result(1,2) = hvyz - v.x;
result(2,0) = hvxz - v.y;
result(2,1) = hvyz + v.x;
result(2,2) = e + hvz*v.z;
}
else
{
//Vectors almost parallel
// PT: TODO: simplify code below
PxVec3 from(0.0f, 0.0f, 1.0f);
PxVec3 absFrom(0.0f, 0.0f, 1.0f);
if(absFrom.x < absFrom.y)
{
if(absFrom.x < absFrom.z)
absFrom = PxVec3(1.0f, 0.0f, 0.0f);
else
absFrom = PxVec3(0.0f, 0.0f, 1.0f);
}
else
{
if(absFrom.y < absFrom.z)
absFrom = PxVec3(0.0f, 1.0f, 0.0f);
else
absFrom = PxVec3(0.0f, 0.0f, 1.0f);
}
PxVec3 u, v;
u.x = absFrom.x - from.x; u.y = absFrom.y - from.y; u.z = absFrom.z - from.z;
v.x = absFrom.x - to.x; v.y = absFrom.y - to.y; v.z = absFrom.z - to.z;
const PxReal c1 = 2.0f / u.dot(u);
const PxReal c2 = 2.0f / v.dot(v);
const PxReal c3 = c1 * c2 * u.dot(v);
for(unsigned int i = 0; i < 3; i++)
{
for(unsigned int j = 0; j < 3; j++)
{
result(i,j) = - c1*u[i]*u[j] - c2*v[i]*v[j] + c3*v[i]*u[j];
}
result(i,i) += 1.0f;
}
}
return result;
}
// PT: using this specialized version avoids doing an explicit transpose, which reduces LHS
PX_FORCE_INLINE PxMat34 transformTranspose(const PxMat33& a, const PxMat34& b)
{
return PxMat34(a.transformTranspose(b.m.column0), a.transformTranspose(b.m.column1), a.transformTranspose(b.m.column2), a.transformTranspose(b.p));
}
// Helper function to transform x/y coordinate of point.
PX_FORCE_INLINE void transform2D(float& x, float& y, const PxVec3& src, const PxMat34& mat)
{
x = src.x * mat.m.column0.x + src.y * mat.m.column1.x + src.z * mat.m.column2.x + mat.p.x;
y = src.x * mat.m.column0.y + src.y * mat.m.column1.y + src.z * mat.m.column2.y + mat.p.y;
}
// Helper function to transform x/y coordinate of point. Use transposed matrix
PX_FORCE_INLINE void transform2DT(float& x, float& y, const PxVec3& src, const PxMat33& mat)
{
x = mat.column0.dot(src);
y = mat.column1.dot(src);
}
// Helper function to transform z coordinate of point.
PX_FORCE_INLINE PxReal transformZ(const PxVec3& src, const PxMat34& mat)
{
return src.x * mat.m.column0.z + src.y * mat.m.column1.z + src.z * mat.m.column2.z + mat.p.z;
}
static void transformVertices( float& minX, float& minY,
float& maxX, float& maxY,
float* PX_RESTRICT verts2D,
PxU32 nb, const PxVec3* PX_RESTRICT vertices, const PxU8* PX_RESTRICT indices, const PxMat33& RotT)
{
// PT: using local variables is important to reduce LHS.
float lminX = FLT_MAX;
float lminY = FLT_MAX;
float lmaxX = -FLT_MAX;
float lmaxY = -FLT_MAX;
// PT: project points, compute min & max at the same time
for(PxU32 i=0; i<nb; i++)
{
float x,y;
transform2DT(x, y, vertices[indices[i]], RotT);
lminX = physx::intrinsics::selectMin(lminX, x);
lminY = physx::intrinsics::selectMin(lminY, y);
lmaxX = physx::intrinsics::selectMax(lmaxX, x);
lmaxY = physx::intrinsics::selectMax(lmaxY, y);
verts2D[i*2+0] = x;
verts2D[i*2+1] = y;
}
// DE702
// Compute center of polygon
const float cx = (lminX + lmaxX)*0.5f;
const float cy = (lminY + lmaxY)*0.5f;
// We'll scale the polygon by epsilon
const float epsilon = 1.e-6f;
// Adjust bounds to take care of scaling
lminX -= epsilon;
lminY -= epsilon;
lmaxX += epsilon;
lmaxY += epsilon;
//~DE702
// PT: relocate polygon to positive quadrant
for(PxU32 i=0; i<nb; i++)
{
const float x = verts2D[i*2+0];
const float y = verts2D[i*2+1];
// PT: original code suffering from DE702 (relocation)
// verts2D[i*2+0] = x - lminX;
// verts2D[i*2+1] = y - lminY;
// PT: theoretically proper DE702 fix (relocation + scaling)
const float dx = x - cx;
const float dy = y - cy;
// const float coeff = epsilon * physx::intrinsics::recipSqrt(dx*dx+dy*dy);
// verts2D[i*2+0] = x - lminX + dx * coeff;
// verts2D[i*2+1] = y - lminY + dy * coeff;
// PT: approximate but faster DE702 fix. We multiply by epsilon so this is good enough.
verts2D[i*2+0] = x - lminX + physx::intrinsics::fsel(dx, epsilon, -epsilon);
verts2D[i*2+1] = y - lminY + physx::intrinsics::fsel(dy, epsilon, -epsilon);
}
lmaxX -= lminX;
lmaxY -= lminY;
minX = lminX;
minY = lminY;
maxX = lmaxX;
maxY = lmaxY;
}
//! Dedicated triangle version
PX_FORCE_INLINE bool pointInTriangle2D( float px, float pz,
float p0x, float p0z,
float e10x, float e10z,
float e20x, float e20z)
{
const float a = e10x*e10x + e10z*e10z;
const float b = e10x*e20x + e10z*e20z;
const float c = e20x*e20x + e20z*e20z;
const float ac_bb = (a*c)-(b*b);
const float vpx = px - p0x;
const float vpz = pz - p0z;
const float d = vpx*e10x + vpz*e10z;
const float e = vpx*e20x + vpz*e20z;
const float x = (d*c) - (e*b);
const float y = (e*a) - (d*b);
const float z = x + y - ac_bb;
// Same as: if(x>0.0f && y>0.0f && z<0.0f) return TRUE;
// else return FALSE;
// return (( IR(z) & ~(IR(x)|IR(y)) ) & SIGN_BITMASK) != 0;
if(x>0.0f && y>0.0f && z<0.0f) return true;
else return false;
}
enum OutCode
{
OUT_XP = (1<<0),
OUT_XN = (1<<1),
OUT_YP = (1<<2),
OUT_YN = (1<<3)
};
static
//PX_FORCE_INLINE
bool PointInConvexPolygon2D_OutCodes(const float* PX_RESTRICT pgon2D, PxU32 numVerts, const PxReal tx, const PxReal ty, const PxReal maxX, const PxReal maxY, PxU8& outCodes)
{
PxU32 out = 0;
if(tx<0.0f) out |= OUT_XN;
if(ty<0.0f) out |= OUT_YN;
if(tx>maxX) out |= OUT_XP;
if(ty>maxY) out |= OUT_YP;
outCodes = PxU8(out);
if(out)
return false;
if(numVerts==3)
return pointInTriangle2D( tx, ty,
pgon2D[0], pgon2D[1],
pgon2D[2] - pgon2D[0],
pgon2D[3] - pgon2D[1],
pgon2D[4] - pgon2D[0],
pgon2D[5] - pgon2D[1]);
#define X 0
#define Y 1
const PxReal* PX_RESTRICT vtx0_ = pgon2D + (numVerts-1)*2;
const PxReal* PX_RESTRICT vtx1_ = pgon2D;
const int* PX_RESTRICT ivtx0 = reinterpret_cast<const int*>(vtx0_);
const int* PX_RESTRICT ivtx1 = reinterpret_cast<const int*>(vtx1_);
//const int itx = (int&)tx;
//const int ity = (int&)ty;
// const int ity = PX_SIR(ty);
const int* tmp = reinterpret_cast<const int*>(&ty);
const int ity = *tmp;
// get test bit for above/below X axis
int yflag0 = ivtx0[Y] >= ity;
int InsideFlag = 0;
while(numVerts--)
{
const int yflag1 = ivtx1[Y] >= ity;
if(yflag0 != yflag1)
{
const PxReal* PX_RESTRICT vtx0 = reinterpret_cast<const PxReal*>(ivtx0);
const PxReal* PX_RESTRICT vtx1 = reinterpret_cast<const PxReal*>(ivtx1);
if( ((vtx1[Y]-ty) * (vtx0[X]-vtx1[X]) > (vtx1[X]-tx) * (vtx0[Y]-vtx1[Y])) == yflag1 )
{
if(InsideFlag == 1) return false;
InsideFlag++;
}
}
yflag0 = yflag1;
ivtx0 = ivtx1;
ivtx1 += 2;
}
#undef X
#undef Y
return InsideFlag & 1;
}
// Helper function to detect contact between two edges
PX_FORCE_INLINE bool EdgeEdgeContactSpecial(const PxVec3& v1, const PxPlane& plane,
const PxVec3& p1, const PxVec3& p2, const PxVec3& dir, const PxVec3& p3, const PxVec3& p4,
PxReal& dist, PxVec3& ip, unsigned int i, unsigned int j, float coeff)
{
const PxReal d3 = plane.distance(p3);
PxReal temp = d3 * plane.distance(p4);
if(temp > 0.0f)
return false;
// if colliding edge (p3,p4) and plane are parallel return no collision
const PxVec3 v2 = (p4-p3);
temp = plane.n.dot(v2);
if(temp == 0.0f) // ### epsilon would be better
return false;
// compute intersection point of plane and colliding edge (p3,p4)
ip = p3-v2*(d3/temp);
// compute distance of intersection from line (ip, -dir) to line (p1,p2)
dist = (v1[i]*(ip[j]-p1[j])-v1[j]*(ip[i]-p1[i])) * coeff;
if(dist < 0.0f)
return false;
// compute intersection point on edge (p1,p2) line
ip -= dist*dir;
// check if intersection point (ip) is between edge (p1,p2) vertices
temp = (p1.x-ip.x)*(p2.x-ip.x)+(p1.y-ip.y)*(p2.y-ip.y)+(p1.z-ip.z)*(p2.z-ip.z);
if(temp<0.0f)
return true; // collision found
return false; //no collision
}
//This one can also handle 2 vertex 'polygons' (useful for capsule surface segments) and can shift the results before contact generation.
bool Gu::contactPolygonPolygonExt( PxU32 numVerts0, const PxVec3* vertices0, const PxU8* indices0, //polygon 0
const PxMat34& world0, const PxPlane& localPlane0, //xform of polygon 0, plane of polygon
const PxMat33& rotT0,
//
PxU32 numVerts1, const PxVec3* PX_RESTRICT vertices1, const PxU8* PX_RESTRICT indices1, //polygon 1
const PxMat34& world1, const PxPlane& localPlane1, //xform of polygon 1, plane of polygon
const PxMat33& rotT1,
//
const PxVec3& worldSepAxis, //world normal of separating plane - this is the world space normal of polygon0!!
const PxMat34& transform0to1, const PxMat34& transform1to0, //transforms between polygons
PxU32 /*polyIndex0*/, PxU32 polyIndex1, //feature indices for contact callback
PxContactBuffer& contactBuffer,
bool flipNormal, const PxVec3& posShift, PxReal sepShift) // shape order, result shift
{
const PxVec3 n = flipNormal ? -worldSepAxis : worldSepAxis;
PX_ASSERT(indices0 != NULL && indices1 != NULL);
// - optimize "from to" computation
// - do the raycast case && EE tests in same space as 2D case...
// - project all edges at the same time ?
PxU32 NumIn = 0;
bool status = false;
void* PX_RESTRICT stackMemory;
{
const PxU32 maxNumVert = PxMax(numVerts0, numVerts1);
stackMemory = PxAlloca(maxNumVert * sizeof(PxVec3));
}
const PxU32 size0 = numVerts0 * sizeof(bool);
bool* PX_RESTRICT flags0 = reinterpret_cast<bool*>(PxAlloca(size0));
PxU8* PX_RESTRICT outCodes0 = reinterpret_cast<PxU8*>(PxAlloca(size0));
// PxMemZero(flags0, size0);
// PxMemZero(outCodes0, size0);
const PxU32 size1 = numVerts1 * sizeof(bool);
bool* PX_RESTRICT flags1 = reinterpret_cast<bool*>(PxAlloca(size1));
PxU8* PX_RESTRICT outCodes1 = reinterpret_cast<PxU8*>(PxAlloca(size1));
// PxMemZero(flags1, size1);
// PxMemZero(outCodes1, size1);
#ifdef CONTACT_REDUCTION
// We want to do contact reduction on newly created contacts, not on all the already existing ones...
PxU32 nbExistingContacts = contactBuffer.count;
PxU32 nbCurrentContacts=0;
PxU8 indices[PxContactBuffer::MAX_CONTACTS];
#endif
{
//polygon 1
float* PX_RESTRICT verts2D = NULL;
float minX=0, minY=0;
float maxX=0, maxY=0;
const PxVec3 localDir = -world1.rotateTranspose(worldSepAxis); //contactNormal in hull1 space
//that's redundant, its equal to -localPlane1.d
const PxMat34 t0to2D = transformTranspose(rotT1, transform0to1); //transform from hull0 to RotT
PxReal dn = localDir.dot(localPlane1.n); //if the contactNormal == +-(normal of poly0) is NOT orthogonal to poly1 ...this is just to protect the division below.
// PT: TODO: if "numVerts1>2" we may skip more
if (numVerts1 > 2 //no need to test whether we're 'inside' ignore capsule segments and points
// if(!(-1E-7 < dn && dn < 1E-7))
&& dn >= 1E-7f) // PT: it should never be negative so this unique test is enough
{
dn = 1.0f / dn;
const float ld1 = -localPlane1.d; // PT: unavoidable "int-to-float" LHS here, so we only want to read it once!
// Lazy-transform vertices
if(!verts2D)
{
verts2D = reinterpret_cast<float*>(stackMemory);
//Project points
transformVertices(
minX, minY,
maxX, maxY,
verts2D, numVerts1, vertices1, indices1, rotT1);
}
for(PxU32 i=0; i < numVerts0; i++) //for all vertices of poly0
{
const PxVec3& p = vertices0[indices0[i]];
const float p0_z = transformZ(p, t0to2D); //transform ith vertex of poly0 to RotT
const PxVec3 pIn1 = transform0to1.transform(p); //transform vertex to hull1 space, in which we have the poly1 vertices.
const PxReal dd = (p0_z - ld1) * dn; //(p0_z + localPlane1.d) is the depth of the vertex behind the triangle measured along the triangle's normal.
//we convert this to being measured along the 'contact normal' using the division.
// if(dd < 0.0f) //if the penetrating vertex will have a penetration along the contact normal:
// PX_ASSERT(dd <= 0.0f); // PT: dn is always positive, so dd is always negative
{
float px, py;
transform2DT(px, py, pIn1 - dd*localDir, rotT1); //project vertex into poly1 plane along CONTACT NORMAL - not the polygon's normal.
const bool res = PointInConvexPolygon2D_OutCodes(verts2D, numVerts1, px-minX, py-minY, maxX, maxY, outCodes0[i]);
flags0[i] = res;
if(res)
{
NumIn++;
if(p0_z < ld1)
{
status = true; // PT: keep this first to avoid an LHS when leaving the function
PxContactPoint* PX_RESTRICT ctc = contactBuffer.contact();
if(ctc)
{
#ifdef CONTACT_REDUCTION
indices[nbCurrentContacts++] = indices0[i];
#endif
ctc->normal = n;
ctc->point = world0.transform(p) + (flipNormal ? posShift : PxVec3(0.0f));
ctc->separation = dd + sepShift;
ctc->internalFaceIndex1 = polyIndex1;
}
}
}
}
}
}
else
{
PxMemZero(flags0, size0);
PxMemZero(outCodes0, size0);
}
if(NumIn == numVerts0)
{
//All vertices0 are inside polygon 1
#ifdef CONTACT_REDUCTION
ContactReductionAllIn(contactBuffer, nbExistingContacts, NumIn, rotT0, vertices0, indices);
#endif
return status;
}
#ifdef CONTACT_REDUCTION
ContactReductionAllIn(contactBuffer, nbExistingContacts, NumIn, rotT0, vertices0, indices);
#endif
#ifdef CONTACT_REDUCTION
nbExistingContacts = contactBuffer.count;
nbCurrentContacts = 0;
#endif
NumIn = 0;
verts2D = NULL;
//Polygon 0
const PxMat34 t1to2D = transformTranspose(rotT0, transform1to0);
if (numVerts0 > 2) //no need to test whether we're 'inside' ignore capsule segments and points
{
const float ld0 = -localPlane0.d; // PT: unavoidable "int-to-float" LHS here, so we only want to read it once!
// Lazy-transform vertices
if(!verts2D)
{
verts2D = reinterpret_cast<float*>(stackMemory);
//Project vertices
transformVertices(
minX, minY,
maxX, maxY,
verts2D, numVerts0, vertices0, indices0, rotT0);
}
for(PxU32 i=0; i < numVerts1; i++)
{
const PxVec3& p = vertices1[indices1[i]];
float px, py;
transform2D(px, py, p, t1to2D);
const bool res = PointInConvexPolygon2D_OutCodes(verts2D, numVerts0, px-minX, py-minY, maxX, maxY, outCodes1[i]);
flags1[i] = res;
if(res)
{
NumIn++;
const float pz = transformZ(p, t1to2D);
if(pz < ld0)
{
status = true; // PT: keep this first to avoid an LHS when leaving the function
// PT: in theory, with this contact point we should use "worldSepAxis" as a contact normal.
// However we want to output the same normal for all contact points not to break friction
// patches!!! In theory again, it should be exactly the same since the contact point at
// time of impact is supposed to be the same on both bodies. In practice however, and with
// a depth-based engine, this is not the case. So the contact point here is not exactly
// right, but preserving the friction patch seems more important.
PxContactPoint* PX_RESTRICT ctc = contactBuffer.contact();
if(ctc)
{
#ifdef CONTACT_REDUCTION
indices[nbCurrentContacts++] = indices1[i];
#endif
ctc->normal = n;
ctc->point = world1.transform(p) + (flipNormal ? PxVec3(0.0f) : posShift);
ctc->separation = (pz - ld0) + sepShift;
ctc->internalFaceIndex1 = polyIndex1;
}
}
}
}
if(NumIn == numVerts1)
{
//all vertices 1 are inside polygon 0
#ifdef CONTACT_REDUCTION
ContactReductionAllIn(contactBuffer, nbExistingContacts, NumIn, rotT1, vertices1, indices);
#endif
return status;
}
#ifdef CONTACT_REDUCTION
ContactReductionAllIn(contactBuffer, nbExistingContacts, NumIn, rotT1, vertices1, indices);
#endif
}
else
{
PxMemZero(flags1, size1);
PxMemZero(outCodes1, size1);
}
}
//Edge/edge case
//Calculation done in space 0
PxVec3* PX_RESTRICT verts1in0 = reinterpret_cast<PxVec3*>(stackMemory);
for(PxU32 i=0; i<numVerts1; i++)
{
verts1in0[i] = transform1to0.transform(vertices1[indices1[i]]);
}
if (numVerts0 >= 2 && numVerts1 >= 2)//useless if one of them is degenerate.
for(PxU32 j=0; j<numVerts1; j++)
{
PxU32 j1 = j+1;
if(j1 >= numVerts1) j1 = 0;
// if(!(flags1[j] ^ flags1[j1]))
// continue;
if(flags1[j] && flags1[j1])
continue;
if(outCodes1[j]&outCodes1[j1])
continue;
const PxVec3& p0 = verts1in0[j];
const PxVec3& p1 = verts1in0[j1];
// gVisualizeLocalLine(vertices1[indices1[j]], vertices1[indices1[j1]], world1, callback.getManager());
const PxVec3 v1 = p1-p0;
const PxVec3 planeNormal = v1.cross(localPlane0.n);
const PxPlane plane(planeNormal, -(planeNormal.dot(p0)));
// find largest 2D plane projection
PxU32 _i, _j;
closestAxis(planeNormal, _i, _j);
const PxReal coeff = 1.0f / (v1[_i]*localPlane0.n[_j]-v1[_j]*localPlane0.n[_i]);
for(PxU32 i=0; i<numVerts0; i++)
{
PxU32 i1 = i+1;
if(i1 >= numVerts0) i1 = 0;
// if(!(flags0[i] ^ flags0[i1]))
// continue;
if(flags0[i] && flags0[i1])
continue;
if(outCodes0[i]&outCodes0[i1])
continue;
const PxVec3& p0b = vertices0[indices0[i]];
const PxVec3& p1b = vertices0[indices0[i1]];
// gVisualizeLocalLine(p0b, p1b, world0, callback.getManager());
PxReal dist;
PxVec3 p;
if(EdgeEdgeContactSpecial(v1, plane, p0, p1, localPlane0.n, p0b, p1b, dist, p, _i, _j, coeff))
{
status = true; // PT: keep this first to avoid an LHS when leaving the function
/* p = world0.transform(p);
//contacts are generated on the edges of polygon 1
//we only have to shift the position of polygon 1 if flipNormal is false, because
//in this case convex 0 gets passed as polygon 1, and it is convex 0 that was shifted.
if (!flipNormal)
p += posShift;
contactBuffer.contact(p, n, -dist + sepShift, polyIndex0, polyIndex1, convexID);*/
PxContactPoint* PX_RESTRICT ctc = contactBuffer.contact();
if(ctc)
{
ctc->normal = n;
ctc->point = world0.transform(p) + (flipNormal ? PxVec3(0.0f) : posShift);
ctc->separation = -dist + sepShift;
ctc->internalFaceIndex1 = polyIndex1;
}
}
}
}
return status;
}

View File

@@ -0,0 +1,69 @@
// 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.
#ifndef GU_CONTACTPOLYGONPOLYGON_H
#define GU_CONTACTPOLYGONPOLYGON_H
#include "foundation/PxMat33.h"
#include "foundation/PxMat34.h"
#include "common/PxPhysXCommonConfig.h"
namespace physx
{
class PxContactBuffer;
class PxPlane;
namespace Cm
{
class FastVertex2ShapeScaling;
}
namespace Gu
{
PX_PHYSX_COMMON_API PxMat33 findRotationMatrixFromZ(const PxVec3& to);
PX_PHYSX_COMMON_API bool contactPolygonPolygonExt( PxU32 numVerts0, const PxVec3* vertices0, const PxU8* indices0,//polygon 0
const PxMat34& world0, const PxPlane& localPlane0, //xform of polygon 0, plane of polygon
const PxMat33& RotT0,
PxU32 numVerts1, const PxVec3* vertices1, const PxU8* indices1,//polygon 1
const PxMat34& world1, const PxPlane& localPlane1, //xform of polygon 1, plane of polygon
const PxMat33& RotT1,
const PxVec3& worldSepAxis, //world normal of separating plane - this is the world space normal of polygon0!!
const PxMat34& transform0to1, const PxMat34& transform1to0,//transforms between polygons
PxU32 polyIndex0, PxU32 polyIndex1, //face indices for contact callback,
PxContactBuffer& contactBuffer,
bool flipNormal, const PxVec3& posShift, float sepShift
); // shape order, post gen shift.
}
}
#endif

View File

@@ -0,0 +1,494 @@
// 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.
#ifndef GU_SDF_CONTACT_REDUCTION_H
#define GU_SDF_CONTACT_REDUCTION_H
#include "foundation/PxArray.h"
#include "foundation/PxAssert.h"
#include "foundation/PxErrors.h"
#include "foundation/PxFoundation.h"
#include "foundation/PxPreprocessor.h"
#include "foundation/PxSort.h"
#include "foundation/PxSimpleTypes.h"
#include "foundation/PxVec3.h"
#include "geomutils/PxContactBuffer.h"
#include "geomutils/PxContactPoint.h"
#include "PxContact.h"
namespace physx
{
namespace Gu
{
const float PATCH_ACCEPT_DOTP = 0.9995f;
const float PATCH_REJECT_DOTP = 0.85f;
const float PXS_SEPARATION_TOLERANCE = 0.001f;
// SDF contact reduction on the CPU takes place in three steps:
// (1) assign a contact to an existing patch or create a new one
// (2) once the patches grow too large, reduce them
// (3) order patches by penetration depth and select a diverse set
struct TinyContact
{
TinyContact() : mNormal(0.0f), mSeparation(0.0f), mPoint(0.0f) { }
TinyContact(const PxVec3& normal, PxReal separation, const PxVec3& point) : mNormal(normal), mSeparation(separation), mPoint(point) {}
PxVec3 mNormal;
PxReal mSeparation;
PxVec3 mPoint;
};
template <PxU8 TMaxContactsPerPatch>
struct TinyContactPatch
{
TinyContact mContacts[TMaxContactsPerPatch];
PxU8 mNbContacts;
const TinyContact* begin() const { return mContacts; }
const TinyContact* end() const { return mContacts + mNbContacts; }
TinyContactPatch() : mNbContacts(0) { }
};
PX_FORCE_INLINE static float square(float x) { return x * x; }
// proto-patch that may need to be reduced
template <PxU8 TMaxContactsPerPatch, PxU32 TContactBufSz, bool planarize = true>
class BufferedPatch
{
public:
BufferedPatch() {}
explicit BufferedPatch(const TinyContact& contact) : mRootNormal(contact.mNormal), mMinSeparation(contact.mSeparation)
{
addContact(contact);
}
// copy, skipping unused elements
BufferedPatch(const BufferedPatch& other) : mRootNormal(other.mRootNormal), mMinSeparation(other.mMinSeparation), mNbContacts(other.mNbContacts)
{
for (PxU32 i = 0; i < mNbContacts; ++i)
{
mPointX[i] = other.mPointX[i];
mPointY[i] = other.mPointY[i];
mPointZ[i] = other.mPointZ[i];
mNormalX[i] = other.mNormalX[i];
mNormalY[i] = other.mNormalY[i];
mNormalZ[i] = other.mNormalZ[i];
mSeparation[i] = other.mSeparation[i];
}
}
// add a contact
PX_INLINE void addContact(const TinyContact& contact)
{
PX_ASSERT(mNbContacts < TContactBufSz);
mPointX[mNbContacts] = contact.mPoint.x;
mPointY[mNbContacts] = contact.mPoint.y;
mPointZ[mNbContacts] = contact.mPoint.z;
mNormalX[mNbContacts] = contact.mNormal.x;
mNormalY[mNbContacts] = contact.mNormal.y;
mNormalZ[mNbContacts] = contact.mNormal.z;
mSeparation[mNbContacts] = contact.mSeparation;
// in case of <= 4 contacts per patch, the deepest one will not usually be conserved
if (contact.mSeparation < mMinSeparation)
mMinSeparation = contact.mSeparation;
++mNbContacts;
if (mNbContacts == TContactBufSz)
reduce();
}
PX_INLINE void asTinyContactPatch(TinyContactPatch<TMaxContactsPerPatch>& patch)
{
reduce();
patch.mNbContacts = static_cast<PxU8>(mNbContacts);
PX_ASSERT(mNbContacts <= TMaxContactsPerPatch);
TinyContact* contacts = patch.mContacts;
for (PxU32 contactIdx = 0; contactIdx < mNbContacts; ++contactIdx, ++contacts)
{
contacts->mPoint = PxVec3(mPointX[contactIdx], mPointY[contactIdx], mPointZ[contactIdx]);
contacts->mNormal = PxVec3(mNormalX[contactIdx], mNormalY[contactIdx], mNormalZ[contactIdx]);
contacts->mSeparation = mSeparation[contactIdx];
}
}
// store reduced contacts into `contacts`, returning the number of contacts
PX_INLINE PxU32 getContacts(TinyContact* contacts)
{
reduce();
PX_ASSERT(mNbContacts <= TMaxContactsPerPatch);
for (PxU32 contactIdx = 0; contactIdx < mNbContacts; ++contactIdx, ++contacts)
{
contacts->mPoint = PxVec3(mPointX[contactIdx], mPointY[contactIdx], mPointZ[contactIdx]);
contacts->mNormal = PxVec3(mNormalX[contactIdx], mNormalY[contactIdx], mNormalZ[contactIdx]);
contacts->mSeparation = mSeparation[contactIdx];
}
return mNbContacts;
}
PX_INLINE PxVec3 getPoint(PxU32 index) const
{
return PxVec3(mPointX[index], mPointY[index], mPointZ[index]);
}
// reduce the contacts to `TMaxContactsPerPatch`.
PX_INLINE void reduce()
{
if (mNbContacts <= TMaxContactsPerPatch)
return;
// P0: most extreme point
float maxDistOrigSq = -1;
PxU32 P0Idx = 0;
PxVec3 P0;
for (PxU32 i = 0; i < mNbContacts; ++i)
{
const PxVec3 p = getPoint(i);
float distOrigSq = p.magnitudeSquared();
if (planarize)
distOrigSq -= square(p.dot(mRootNormal));
if (distOrigSq > maxDistOrigSq)
{
maxDistOrigSq = distOrigSq;
P0Idx = i;
P0 = p;
}
}
// P1: most distant point from P0
float maxDistP0Sq = -1;
PxU32 P1Idx = 0;
PxVec3 P1;
for (PxU32 i = 0; i < mNbContacts; ++i)
{
const PxVec3 p = getPoint(i);
const PxVec3 v = p - P0;
float distP0Sq = v.magnitudeSquared();
if (planarize)
distP0Sq -= square(v.dot(mRootNormal));
if (distP0Sq > maxDistP0Sq)
{
maxDistP0Sq = distP0Sq;
P1Idx = i;
P1 = p;
}
}
if (P0Idx == P1Idx) // P0 == P1 => all points equal. keep only the first
{
// TODO(CA): account for differences in penetration? should not occur
mNbContacts = 1;
return;
}
PxVec3 P2, P3;
// P2 & P3: most distant from P0-P1 segment in both directions
const PxVec3 segNormal = (P0 - P1).cross(mRootNormal);
float maxDistPos = -PX_MAX_REAL, maxDistNeg = PX_MAX_REAL;
PxU32 P2Idx = PX_MAX_U32, P3Idx = PX_MAX_U32;
for (PxU32 i = 0; i < mNbContacts; ++i)
{
if (i == P0Idx || i == P1Idx) // ensure that we have contacts distinct from P0/P1
continue;
const PxVec3 p = getPoint(i);
const PxReal dist = (p - P0).dot(segNormal);
if (dist > maxDistPos)
{
maxDistPos = dist;
P2Idx = i;
P2 = p;
}
if (dist <= maxDistNeg)
{
maxDistNeg = dist;
P3Idx = i;
P3 = p;
}
}
// cluster the points
PxReal anchorSeparation[TMaxContactsPerPatch];
PxU32 anchorDeepestIdx[TMaxContactsPerPatch];
const PxU32 anchorIndices[4] = {P0Idx, P1Idx, P2Idx, P3Idx};
const PxVec3 anchorPoints[4] = {P0, P1, P2, P3};
for(PxU32 i = 0; i < 4; ++i)
{
const PxU32 index = anchorIndices[i];
anchorDeepestIdx[i] = index;
anchorSeparation[i] = mSeparation[index] - PXS_SEPARATION_TOLERANCE;
}
for (PxU32 i = 0; i < mNbContacts; ++i)
{
const PxVec3 p = getPoint(i);
PxReal dMin = PX_MAX_REAL;
PxU32 anchorIdx = 0;
for(PxU32 c = 0; c < 4; ++c) // assign to anchors
{
const PxReal dist = (anchorPoints[c] - p).magnitudeSquared();
if(dist < dMin)
{
dMin = dist;
anchorIdx = c;
}
}
if(mSeparation[i] < anchorSeparation[anchorIdx]) // pick deepest
{
anchorDeepestIdx[anchorIdx] = i;
anchorSeparation[anchorIdx] = mSeparation[i];
}
}
PxU32 chosenPoints[TMaxContactsPerPatch] =
{
anchorDeepestIdx[0], anchorDeepestIdx[1], anchorDeepestIdx[2], anchorDeepestIdx[3]
};
PxReal chosenSeparations[TMaxContactsPerPatch-4]; // only relevant for extra points
for (PxU32 i = 0; i < TMaxContactsPerPatch-4; ++i)
chosenSeparations[i] = PX_MAX_REAL;
for (PxU32 i = 0; i < mNbContacts; ++i)
{
bool alreadyChosen = false;
for (PxU32 j = 0; j < 4; ++j)
{
if (i == chosenPoints[j])
{
alreadyChosen = true;
break;
}
}
if(alreadyChosen)
continue;
PxReal sep = mSeparation[i];
for (PxU32 slotIdx = 4; slotIdx < TMaxContactsPerPatch; ++slotIdx)
{
if (sep < chosenSeparations[slotIdx-4])
{
// drop out largest contact
for (PxU32 k = TMaxContactsPerPatch-1; k > slotIdx; --k)
{
chosenSeparations[k-4] = chosenSeparations[k-1-4];
chosenPoints[k] = chosenPoints[k-1];
}
// assign to this slot
chosenSeparations[slotIdx-4] = sep;
chosenPoints[slotIdx] = i;
break;
}
}
}
float pointXNew[TMaxContactsPerPatch],
pointYNew[TMaxContactsPerPatch],
pointZNew[TMaxContactsPerPatch],
normalXNew[TMaxContactsPerPatch],
normalYNew[TMaxContactsPerPatch],
normalZNew[TMaxContactsPerPatch],
separationNew[TMaxContactsPerPatch];
for (PxU32 dst = 0; dst < TMaxContactsPerPatch; ++dst)
{
const PxU32 src = chosenPoints[dst];
pointXNew[dst] = mPointX[src];
pointYNew[dst] = mPointY[src];
pointZNew[dst] = mPointZ[src];
normalXNew[dst] = mNormalX[src];
normalYNew[dst] = mNormalY[src];
normalZNew[dst] = mNormalZ[src];
separationNew[dst] = mSeparation[src];
}
for (PxU32 i = 0; i < TMaxContactsPerPatch; ++i)
{
mPointX[i] = pointXNew[i];
mPointY[i] = pointYNew[i];
mPointZ[i] = pointZNew[i];
mNormalX[i] = normalXNew[i];
mNormalY[i] = normalYNew[i];
mNormalZ[i] = normalZNew[i];
mSeparation[i] = separationNew[i];
}
mNbContacts = TMaxContactsPerPatch;
}
protected:
float mNormalX[TContactBufSz];
float mNormalY[TContactBufSz];
float mNormalZ[TContactBufSz];
float mSeparation[TContactBufSz];
float mPointX[TContactBufSz];
float mPointY[TContactBufSz];
float mPointZ[TContactBufSz];
public:
PxVec3 mRootNormal;
PxReal mMinSeparation = PX_MAX_REAL;
protected:
PxU32 mNbContacts = 0;
PxU32 pad0;
PxU32 pad1;
PxU32 pad2;
PX_COMPILE_TIME_ASSERT((TContactBufSz * sizeof(float)) % 32 == 0);
PX_COMPILE_TIME_ASSERT(TContactBufSz > TMaxContactsPerPatch);
};
template <PxU8 TMaxContactsPerPatch, PxU32 TMaxPatches, PxU32 TPatchBufSz>
class SDFContactReduction
{
public:
using Patch = BufferedPatch<TMaxContactsPerPatch, TPatchBufSz>;
using TinyPatch = TinyContactPatch<TMaxContactsPerPatch>;
// attempt to add a contact to one of the patches, adding a new one if necessary
// return false if the contact was dropped
PX_INLINE bool addContact(const TinyContact& contact)
{
++mNbContactsConsumed;
for (Patch& patch: mPatchesBuffer)
{
const PxReal dot = patch.mRootNormal.dot(contact.mNormal);
if (dot >= PATCH_ACCEPT_DOTP)
return patch.addContact(contact), true;
}
mPatchesBuffer.pushBack(Patch(contact));
// TODO(CA): make this more robust, taking into account the max number of surviving patches
if (mPatchesBuffer.size() > mPatchCountLimit)
cullPatches(1-(1-PATCH_REJECT_DOTP)/1);
return true;
}
struct PatchPenetrationPredicate
{
bool operator()(const Patch* a, const Patch* b) const { return a->mMinSeparation < b->mMinSeparation; }
};
// cull the existing patches
PX_INLINE void cullPatches(float rejectDotP)
{
PxArray<Patch*> patchesSorted;
patchesSorted.reserve(mPatchesBuffer.size());
for (Patch& patch: mPatchesBuffer)
patchesSorted.pushBack(&patch);
PxSort(patchesSorted.begin(), mPatchesBuffer.size(), PatchPenetrationPredicate());
// drop patches that have > nbAllowedNeighbors neighbors that were selected
// allowed neighbors = 0 seems to work best, still, 3 is worst, and >> 10 deactivates
// isotropy
const PxU32 nbAllowedNeighbors = 0;
PxArray<PxVec3> patchNormals;
// Geometrical upper bound for the number of patches based on the area "reserved" for each patch
const PxU32 nbPatchesBound = static_cast<PxU32>(PxFloor(1.0f/square(PxSin(0.25f * PxAcos(rejectDotP)))));
patchNormals.reserve(PxMin(mPatchesBuffer.size(), nbPatchesBound));
mPatchesBufferTmp.clear();
for (Patch* patch: patchesSorted)
{
PxU32 neighborPatches = 0;
for (const PxVec3& rootNormal: patchNormals)
if (rootNormal.dot(patch->mRootNormal) > rejectDotP)
if (++neighborPatches > nbAllowedNeighbors)
break;
if (neighborPatches > nbAllowedNeighbors)
continue;
// patch->reduce();
patchNormals.pushBack(patch->mRootNormal);
mPatchesBufferTmp.pushBack(*patch);
}
PxSwap(mPatchesBuffer, mPatchesBufferTmp);
mPatchesBufferTmp.clear();
}
// reduce each buffered patch, sort them, and create contacts
PX_INLINE void flushContacts()
{
cullPatches(PATCH_REJECT_DOTP);
for (Patch& patch: mPatchesBuffer)
{
TinyContactPatch<TMaxContactsPerPatch>& finalPatch = mPatchesFinal.pushBack({});
patch.getContacts(finalPatch.mContacts);
finalPatch.mNbContacts = patch.mNbContacts;
}
}
PX_INLINE PxU32 flushToContactBuffer(PxContactBuffer& contactBuffer)
{
cullPatches(PATCH_REJECT_DOTP);
PxU32 nbContactsKept = 0;
for (Patch& patch: mPatchesBuffer)
{
TinyPatch finalPatch;
patch.asTinyContactPatch(finalPatch);
for (PxU32 i = 0; i < finalPatch.mNbContacts; ++i)
{
const TinyContact& contact = finalPatch.mContacts[i];
PxContactPoint* c = contactBuffer.contact();
if (c == NULL)
{
#if PX_CHECKED
PxGetFoundation().error(
physx::PxErrorCode::eDEBUG_WARNING,
PX_FL,
"Dropping contacts in contact reduction due to full contact buffer.");
#endif
break;
}
++nbContactsKept;
c->normal = contact.mNormal;
c->point = contact.mPoint;
c->separation = contact.mSeparation;
c->internalFaceIndex1 = PXC_CONTACT_NO_FACE_INDEX;
}
}
return nbContactsKept;
}
protected:
static const PxU32 mPatchCountLimit = 4000000/sizeof(Patch); // 4 MB limit on patch size
PxArray<Patch> mPatchesBuffer; // store patches up to MaxPatches
PxArray<Patch> mPatchesBufferTmp;
PxArray<TinyPatch> mPatchesFinal; // store culled patches
PxU32 mNbContactsConsumed = 0;
};
}
}
#endif

View File

@@ -0,0 +1,173 @@
// 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 "GuContactMethodImpl.h"
using namespace physx;
//This version is ported 1:1 from novodex
static PX_FORCE_INLINE bool ContactSphereBox(const PxVec3& sphereOrigin,
PxReal sphereRadius,
const PxVec3& boxExtents,
// const PxcCachedTransforms& boxCacheTransform,
const PxTransform32& boxTransform,
PxVec3& point,
PxVec3& normal,
PxReal& separation,
PxReal contactDistance)
{
// const PxTransform& boxTransform = boxCacheTransform.getShapeToWorld();
//returns true on contact
const PxVec3 delta = sphereOrigin - boxTransform.p; // s1.center - s2.center;
PxVec3 dRot = boxTransform.rotateInv(delta); //transform delta into OBB body coords.
//check if delta is outside ABB - and clip the vector to the ABB.
bool outside = false;
if (dRot.x < -boxExtents.x)
{
outside = true;
dRot.x = -boxExtents.x;
}
else if (dRot.x > boxExtents.x)
{
outside = true;
dRot.x = boxExtents.x;
}
if (dRot.y < -boxExtents.y)
{
outside = true;
dRot.y = -boxExtents.y;
}
else if (dRot.y > boxExtents.y)
{
outside = true;
dRot.y = boxExtents.y;
}
if (dRot.z < -boxExtents.z)
{
outside = true;
dRot.z =-boxExtents.z;
}
else if (dRot.z > boxExtents.z)
{
outside = true;
dRot.z = boxExtents.z;
}
if (outside) //if clipping was done, sphere center is outside of box.
{
point = boxTransform.rotate(dRot); //get clipped delta back in world coords.
normal = delta - point; //what we clipped away.
const PxReal lenSquared = normal.magnitudeSquared();
const PxReal inflatedDist = sphereRadius + contactDistance;
if (lenSquared > inflatedDist * inflatedDist)
return false; //disjoint
//normalize to make it into the normal:
separation = PxRecipSqrt(lenSquared);
normal *= separation;
separation *= lenSquared;
//any plane that touches the sphere is tangential, so a vector from contact point to sphere center defines normal.
//we could also use point here, which has same direction.
//this is either a faceFace or a vertexFace contact depending on whether the box's face or vertex collides, but we did not distinguish.
//We'll just use vertex face for now, this info isn't really being used anyway.
//contact point is point on surface of cube closest to sphere center.
point += boxTransform.p;
separation -= sphereRadius;
return true;
}
else
{
//center is in box, we definitely have a contact.
PxVec3 locNorm; //local coords contact normal
/*const*/ PxVec3 absdRot;
absdRot = PxVec3(PxAbs(dRot.x), PxAbs(dRot.y), PxAbs(dRot.z));
/*const*/ PxVec3 distToSurface = boxExtents - absdRot; //dist from embedded center to box surface along 3 dimensions.
//find smallest element of distToSurface
if (distToSurface.y < distToSurface.x)
{
if (distToSurface.y < distToSurface.z)
{
//y
locNorm = PxVec3(0.0f, dRot.y > 0.0f ? 1.0f : -1.0f, 0.0f);
separation = -distToSurface.y;
}
else
{
//z
locNorm = PxVec3(0.0f,0.0f, dRot.z > 0.0f ? 1.0f : -1.0f);
separation = -distToSurface.z;
}
}
else
{
if (distToSurface.x < distToSurface.z)
{
//x
locNorm = PxVec3(dRot.x > 0.0f ? 1.0f : -1.0f, 0.0f, 0.0f);
separation = -distToSurface.x;
}
else
{
//z
locNorm = PxVec3(0.0f,0.0f, dRot.z > 0.0f ? 1.0f : -1.0f);
separation = -distToSurface.z;
}
}
//separation so far is just the embedding of the center point; we still have to push out all of the radius.
point = sphereOrigin;
normal = boxTransform.rotate(locNorm);
separation -= sphereRadius;
return true;
}
}
bool Gu::contactSphereBox(GU_CONTACT_METHOD_ARGS)
{
PX_UNUSED(renderOutput);
PX_UNUSED(cache);
const PxSphereGeometry& sphereGeom = checkedCast<PxSphereGeometry>(shape0);
const PxBoxGeometry& boxGeom = checkedCast<PxBoxGeometry>(shape1);
PxVec3 normal;
PxVec3 point;
PxReal separation;
if(!ContactSphereBox(transform0.p, sphereGeom.radius, boxGeom.halfExtents, transform1, point, normal, separation, params.mContactDistance))
return false;
contactBuffer.contact(point, normal, separation);
return true;
}

View File

@@ -0,0 +1,76 @@
// 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 "GuDistancePointSegment.h"
#include "GuContactMethodImpl.h"
#include "GuInternal.h"
using namespace physx;
bool Gu::contactSphereCapsule(GU_CONTACT_METHOD_ARGS)
{
PX_UNUSED(renderOutput);
PX_UNUSED(cache);
const PxSphereGeometry& sphereGeom = checkedCast<PxSphereGeometry>(shape0);
const PxCapsuleGeometry& capsuleGeom = checkedCast<PxCapsuleGeometry>(shape1);
// PT: get capsule in local space
const PxVec3 capsuleLocalSegment = getCapsuleHalfHeightVector(transform1, capsuleGeom);
const Segment localSegment(capsuleLocalSegment, -capsuleLocalSegment);
// PT: get sphere in capsule space
const PxVec3 sphereCenterInCapsuleSpace = transform0.p - transform1.p;
const PxReal radiusSum = sphereGeom.radius + capsuleGeom.radius;
const PxReal inflatedSum = radiusSum + params.mContactDistance;
// PT: compute distance between sphere center & capsule's segment
PxReal u;
const PxReal squareDist = distancePointSegmentSquared(localSegment, sphereCenterInCapsuleSpace, &u);
if(squareDist >= inflatedSum*inflatedSum)
return false;
// PT: compute contact normal
PxVec3 normal = sphereCenterInCapsuleSpace - localSegment.getPointAt(u);
// We do a *manual* normalization to check for singularity condition
const PxReal lenSq = normal.magnitudeSquared();
if(lenSq==0.0f)
normal = PxVec3(1.0f, 0.0f, 0.0f); // PT: zero normal => pick up random one
else
normal *= PxRecipSqrt(lenSq);
// PT: compute contact point
const PxVec3 point = sphereCenterInCapsuleSpace + transform1.p - normal * sphereGeom.radius;
// PT: output unique contact
contactBuffer.contact(point, normal, PxSqrt(squareDist) - radiusSum);
return true;
}

View File

@@ -0,0 +1,621 @@
// 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;
}
///////////////////////////////////////////////////////////////////////////////

View File

@@ -0,0 +1,62 @@
// 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 "GuContactMethodImpl.h"
using namespace physx;
bool Gu::contactSpherePlane(GU_CONTACT_METHOD_ARGS)
{
PX_UNUSED(renderOutput);
PX_UNUSED(cache);
PX_UNUSED(shape1);
// Get actual shape data
const PxSphereGeometry& shapeSphere = checkedCast<PxSphereGeometry>(shape0);
//const PxPlaneGeometry& shapePlane = checkedCast<PxPlaneGeometry>(shape1);
//Sphere in plane space
const PxVec3 sphere = transform1.transformInv(transform0.p);
//Make sure we have a normalized plane
//The plane is implicitly n=<1,0,0> d=0 (in plane-space)
//PX_ASSERT(PxAbs(shape1.mNormal.magnitudeSquared() - 1.0f) < 0.000001f);
//Separation
const PxReal separation = sphere.x - shapeSphere.radius;
if(separation<=params.mContactDistance)
{
const PxVec3 normal = transform1.q.getBasisVector0();
const PxVec3 point = transform0.p - normal * shapeSphere.radius;
contactBuffer.contact(point, normal, separation);
return true;
}
return false;
}

View File

@@ -0,0 +1,62 @@
// 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 "GuContactMethodImpl.h"
using namespace physx;
bool Gu::contactSphereSphere(GU_CONTACT_METHOD_ARGS)
{
PX_UNUSED(renderOutput);
PX_UNUSED(cache);
const PxSphereGeometry& sphereGeom0 = checkedCast<PxSphereGeometry>(shape0);
const PxSphereGeometry& sphereGeom1 = checkedCast<PxSphereGeometry>(shape1);
PxVec3 delta = transform0.p - transform1.p;
const PxReal distanceSq = delta.magnitudeSquared();
const PxReal radiusSum = sphereGeom0.radius + sphereGeom1.radius;
const PxReal inflatedSum = radiusSum + params.mContactDistance;
if(distanceSq >= inflatedSum*inflatedSum)
return false;
// We do a *manual* normalization to check for singularity condition
const PxReal magn = PxSqrt(distanceSq);
if(magn<=0.00001f)
delta = PxVec3(1.0f, 0.0f, 0.0f); // PT: spheres are exactly overlapping => can't create normal => pick up random one
else
delta *= 1.0f/magn;
// PT: TODO: why is this formula different from the original code?
const PxVec3 contact = delta * ((sphereGeom0.radius + magn - sphereGeom1.radius)*-0.5f) + transform0.p;
contactBuffer.contact(contact, delta, magn - radiusSum);
return true;
}

View File

@@ -0,0 +1,127 @@
// 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 "GuConvexEdgeFlags.h"
#include "GuFeatureCode.h"
using namespace physx;
using namespace Gu;
static FeatureCode computeFeatureCode(PxReal u, PxReal v)
{
// Analysis
if(u==0.0f)
{
if(v==0.0f)
{
// Vertex 0
return FC_VERTEX0;
}
else if(v==1.0f)
{
// Vertex 2
return FC_VERTEX2;
}
else
{
// Edge 0-2
return FC_EDGE20;
}
}
else if(u==1.0f)
{
if(v==0.0f)
{
// Vertex 1
return FC_VERTEX1;
}
}
else
{
if(v==0.0f)
{
// Edge 0-1
return FC_EDGE01;
}
else
{
if((u+v)>=0.9999f)
{
// Edge 1-2
return FC_EDGE12;
}
else
{
// Face
return FC_FACE;
}
}
}
return FC_UNDEFINED;
}
bool Gu::selectNormal(PxU8 data, PxReal u, PxReal v)
{
bool useFaceNormal = false;
const FeatureCode FC = computeFeatureCode(u, v);
switch(FC)
{
case FC_VERTEX0:
if(!(data & (Gu::ETD_CONVEX_EDGE_01|Gu::ETD_CONVEX_EDGE_20)))
useFaceNormal = true;
break;
case FC_VERTEX1:
if(!(data & (Gu::ETD_CONVEX_EDGE_01|Gu::ETD_CONVEX_EDGE_12)))
useFaceNormal = true;
break;
case FC_VERTEX2:
if(!(data & (Gu::ETD_CONVEX_EDGE_12|Gu::ETD_CONVEX_EDGE_20)))
useFaceNormal = true;
break;
case FC_EDGE01:
if(!(data & Gu::ETD_CONVEX_EDGE_01))
useFaceNormal = true;
break;
case FC_EDGE12:
if(!(data & Gu::ETD_CONVEX_EDGE_12))
useFaceNormal = true;
break;
case FC_EDGE20:
if(!(data & Gu::ETD_CONVEX_EDGE_20))
useFaceNormal = true;
break;
case FC_FACE:
useFaceNormal = true;
break;
case FC_UNDEFINED:
break;
};
return useFaceNormal;
}

View File

@@ -0,0 +1,53 @@
// 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.
#ifndef GU_FEATURE_CODE_H
#define GU_FEATURE_CODE_H
namespace physx
{
namespace Gu
{
enum FeatureCode
{
FC_VERTEX0,
FC_VERTEX1,
FC_VERTEX2,
FC_EDGE01,
FC_EDGE12,
FC_EDGE20,
FC_FACE,
FC_UNDEFINED
};
bool selectNormal(PxU8 data, PxReal u, PxReal v);
}
}
#endif