Files
XCEngine/engine/third_party/physx/source/physxextensions/src/ExtCustomGeometryExt.cpp

995 lines
37 KiB
C++
Raw Normal View History

// 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 "extensions/PxCustomGeometryExt.h"
#include <geometry/PxGeometryHelpers.h>
#include <geometry/PxGeometryQuery.h>
#include <geometry/PxMeshQuery.h>
#include <geometry/PxTriangle.h>
#include <geometry/PxTriangleMesh.h>
#include <geometry/PxTriangleMeshGeometry.h>
#include <geomutils/PxContactBuffer.h>
#include <common/PxRenderOutput.h>
#include <extensions/PxGjkQueryExt.h>
#include <extensions/PxMassProperties.h>
#include <PxImmediateMode.h>
#include "omnipvd/ExtOmniPvdSetData.h"
using namespace physx;
#if PX_SUPPORT_OMNI_PVD
using namespace Ext;
#endif
static const PxU32 gCollisionShapeColor = PxU32(PxDebugColor::eARGB_MAGENTA);
///////////////////////////////////////////////////////////////////////////////
static const PxU32 MAX_TRIANGLES = 512;
static const PxU32 MAX_TRIANGLE_CONTACTS = 6;
const PxReal FACE_CONTACT_THRESHOLD = 0.99999f;
struct TrimeshContactFilter
{
PxU32 triCount;
PxU32 triIndices[MAX_TRIANGLES];
PxU32 triAdjacencies[MAX_TRIANGLES][3];
PxU32 triContactCounts[MAX_TRIANGLES][2];
PxContactPoint triContacts[MAX_TRIANGLES][MAX_TRIANGLE_CONTACTS];
TrimeshContactFilter() : triCount(0) {}
void addTriangleContacts(const PxContactPoint* points, PxU32 count, const PxU32 triIndex, const PxVec3 triVerts[3], const PxU32 triAdjacency[3])
{
if (triCount == MAX_TRIANGLES)
return;
triIndices[triCount] = triIndex;
PxU32& faceContactCount = triContactCounts[triCount][0];
PxU32& edgeContactCount = triContactCounts[triCount][1];
faceContactCount = edgeContactCount = 0;
for (PxU32 i = 0; i < 3; ++i)
triAdjacencies[triCount][i] = triAdjacency[i];
PxVec3 triNormal = (triVerts[1] - triVerts[0]).cross(triVerts[2] - triVerts[0]).getNormalized();
for (PxU32 i = 0; i < count; ++i)
{
const PxContactPoint& point = points[i];
bool faceContact = fabsf(point.normal.dot(triNormal)) > FACE_CONTACT_THRESHOLD;
if (faceContactCount + edgeContactCount < MAX_TRIANGLE_CONTACTS)
{
if (faceContact) triContacts[triCount][faceContactCount++] = point;
else triContacts[triCount][MAX_TRIANGLE_CONTACTS - 1 - edgeContactCount++] = point;
}
}
++triCount;
}
void writeToBuffer(PxContactBuffer& buffer)
{
for (PxU32 i = 0; i < triCount; ++i)
{
if (triContactCounts[i][1] > 0)
{
for (PxU32 j = 0; j < triCount; ++j)
{
if (triIndices[j] == triAdjacencies[i][0] || triIndices[j] == triAdjacencies[i][1] || triIndices[j] == triAdjacencies[i][2])
{
if (triContactCounts[j][0] > 0)
{
triContactCounts[i][1] = 0;
break;
}
}
}
}
for (PxU32 j = 0; j < triContactCounts[i][0]; ++j)
buffer.contact(triContacts[i][j]);
for (PxU32 j = 0; j < triContactCounts[i][1]; ++j)
buffer.contact(triContacts[i][MAX_TRIANGLE_CONTACTS - 1 - j]);
}
}
PX_NOCOPY(TrimeshContactFilter)
};
///////////////////////////////////////////////////////////////////////////////
struct TriangleSupport : PxGjkQuery::Support
{
PxVec3 v0, v1, v2;
PxReal margin;
TriangleSupport(const PxVec3& _v0, const PxVec3& _v1, const PxVec3& _v2, PxReal _margin)
:
v0(_v0), v1(_v1), v2(_v2), margin(_margin)
{}
virtual PxReal getMargin() const
{
return margin;
}
virtual PxVec3 supportLocal(const PxVec3& dir) const
{
float d0 = dir.dot(v0), d1 = dir.dot(v1), d2 = dir.dot(v2);
return (d0 > d1 && d0 > d2) ? v0 : (d1 > d2) ? v1 : v2;
}
};
///////////////////////////////////////////////////////////////////////////////
static void drawArc(const PxVec3& center, const PxVec3& radius, const PxVec3& axis, PxReal angle, PxReal error, PxRenderOutput& out)
{
int sides = int(ceilf(angle / (2 * acosf(1.0f - error))));
float step = angle / sides;
out << PxRenderOutput::LINESTRIP;
for (int i = 0; i <= sides; ++i)
out << center + PxQuat(step * i, axis).rotate(radius);
}
static void drawCircle(const PxVec3& center, const PxVec3& radius, const PxVec3& axis, PxReal error, PxRenderOutput& out)
{
drawArc(center, radius, axis, PxTwoPi, error, out);
}
static void drawQuarterCircle(const PxVec3& center, const PxVec3& radius, const PxVec3& axis, PxReal error, PxRenderOutput& out)
{
drawArc(center, radius, axis, PxPiDivTwo, error, out);
}
static void drawLine(const PxVec3& s, const PxVec3& e, PxRenderOutput& out)
{
out << PxRenderOutput::LINES << s << e;
}
///////////////////////////////////////////////////////////////////////////////
PxBounds3 PxCustomGeometryExt::BaseConvexCallbacks::getLocalBounds(const PxGeometry&) const
{
const PxVec3 min(supportLocal(PxVec3(-1, 0, 0)).x, supportLocal(PxVec3(0, -1, 0)).y, supportLocal(PxVec3(0, 0, -1)).z);
const PxVec3 max(supportLocal(PxVec3(1, 0, 0)).x, supportLocal(PxVec3(0, 1, 0)).y, supportLocal(PxVec3(0, 0, 1)).z);
PxBounds3 bounds(min, max);
bounds.fattenSafe(getMargin());
return bounds;
}
bool PxCustomGeometryExt::BaseConvexCallbacks::generateContacts(const PxGeometry& geom0, const PxGeometry& geom1,
const PxTransform& pose0, const PxTransform& pose1, const PxReal contactDistance, const PxReal meshContactMargin,
const PxReal toleranceLength, PxContactBuffer& contactBuffer) const
{
struct ContactRecorder : immediate::PxContactRecorder
{
PxContactBuffer* contactBuffer;
ContactRecorder(PxContactBuffer& _contactBuffer) : contactBuffer(&_contactBuffer) {}
virtual bool recordContacts(const PxContactPoint* contactPoints, PxU32 nbContacts, PxU32 /*index*/)
{
for (PxU32 i = 0; i < nbContacts; ++i)
contactBuffer->contact(contactPoints[i]);
return true;
}
}
contactRecorder(contactBuffer);
PxCache contactCache;
struct ContactCacheAllocator : PxCacheAllocator
{
PxU8 buffer[1024];
ContactCacheAllocator() { PxMemSet(buffer, 0, sizeof(buffer)); }
virtual PxU8* allocateCacheData(const PxU32 /*byteSize*/) { return reinterpret_cast<PxU8*>(size_t(buffer + 0xf) & ~0xf); }
}
contactCacheAllocator;
const PxTransform identityPose(PxIdentity);
switch (geom1.getType())
{
case PxGeometryType::eSPHERE:
case PxGeometryType::eCAPSULE:
case PxGeometryType::eBOX:
case PxGeometryType::eCONVEXCORE:
case PxGeometryType::eCONVEXMESH:
{
PxGjkQueryExt::ConvexGeomSupport geomSupport(geom1);
if (PxGjkQueryExt::generateContacts(*this, geomSupport, pose0, pose1, contactDistance, toleranceLength, contactBuffer))
{
PxGeometryHolder substituteGeom; PxTransform preTransform;
if (useSubstituteGeometry(substituteGeom, preTransform, contactBuffer.contacts[contactBuffer.count - 1], pose0))
{
contactBuffer.count--;
const PxGeometry* pGeom0 = &substituteGeom.any();
const PxGeometry* pGeom1 = &geom1;
// PT:: tag: scalar transform*transform
PxTransform pose = pose0.transform(preTransform);
immediate::PxGenerateContacts(&pGeom0, &pGeom1, &pose, &pose1, &contactCache, 1, contactRecorder,
contactDistance, meshContactMargin, toleranceLength, contactCacheAllocator);
}
}
break;
}
case PxGeometryType::ePLANE:
{
const PxPlane plane = PxPlane(1.0f, 0.0f, 0.0f, 0.0f).transform(pose1);
const PxPlane localPlane = plane.inverseTransform(pose0);
const PxVec3 point = supportLocal(-localPlane.n) - localPlane.n * margin;
const float dist = localPlane.distance(point);
if (dist < contactDistance)
{
const PxVec3 n = localPlane.n;
const PxVec3 p = point - n * dist * 0.5f;
PxContactPoint contact;
contact.point = pose0.transform(p);
contact.normal = pose0.rotate(n);
contact.separation = dist;
contactBuffer.contact(contact);
PxGeometryHolder substituteGeom; PxTransform preTransform;
if (useSubstituteGeometry(substituteGeom, preTransform, contactBuffer.contacts[contactBuffer.count - 1], pose0))
{
contactBuffer.count--;
const PxGeometry* pGeom0 = &substituteGeom.any();
const PxGeometry* pGeom1 = &geom1;
// PT:: tag: scalar transform*transform
PxTransform pose = pose0.transform(preTransform);
immediate::PxGenerateContacts(&pGeom0, &pGeom1, &pose, &pose1, &contactCache, 1, contactRecorder,
contactDistance, meshContactMargin, toleranceLength, contactCacheAllocator);
}
}
break;
}
case PxGeometryType::eTRIANGLEMESH:
case PxGeometryType::eHEIGHTFIELD:
{
// As a triangle has no thickness, we need to choose some collision margin - the distance
// considered a touching contact. I set it as 1% of the custom shape minimum dimension.
PxReal meshMargin = getLocalBounds(geom0).getDimensions().minElement() * 0.01f;
TrimeshContactFilter contactFilter;
bool hasAdjacency = (geom1.getType() != PxGeometryType::eTRIANGLEMESH || (static_cast<const PxTriangleMeshGeometry&>(geom1).triangleMesh->getTriangleMeshFlags() & PxTriangleMeshFlag::eADJACENCY_INFO));
PxBoxGeometry boxGeom(getLocalBounds(geom0).getExtents() + PxVec3(contactDistance + meshMargin));
PxU32 triangles[MAX_TRIANGLES];
bool overflow = false;
PxU32 triangleCount = (geom1.getType() == PxGeometryType::eTRIANGLEMESH) ?
PxMeshQuery::findOverlapTriangleMesh(boxGeom, pose0, static_cast<const PxTriangleMeshGeometry&>(geom1), pose1, triangles, MAX_TRIANGLES, 0, overflow) :
PxMeshQuery::findOverlapHeightField(boxGeom, pose0, static_cast<const PxHeightFieldGeometry&>(geom1), pose1, triangles, MAX_TRIANGLES, 0, overflow);
if (overflow)
PxGetFoundation().error(PxErrorCode::eDEBUG_INFO, PX_FL, "PxCustomGeometryExt::BaseConvexCallbacks::generateContacts() Too many triangles.\n");
for (PxU32 i = 0; i < triangleCount; ++i)
{
PxTriangle tri; PxU32 adjacent[3];
if (geom1.getType() == PxGeometryType::eTRIANGLEMESH)
PxMeshQuery::getTriangle(static_cast<const PxTriangleMeshGeometry&>(geom1), pose1, triangles[i], tri, NULL, hasAdjacency ? adjacent : NULL);
else
PxMeshQuery::getTriangle(static_cast<const PxHeightFieldGeometry&>(geom1), pose1, triangles[i], tri, NULL, adjacent);
TriangleSupport triSupport(tri.verts[0], tri.verts[1], tri.verts[2], meshMargin);
if (PxGjkQueryExt::generateContacts(*this, triSupport, pose0, identityPose, contactDistance, toleranceLength, contactBuffer))
{
contactBuffer.contacts[contactBuffer.count - 1].internalFaceIndex1 = triangles[i];
PxGeometryHolder substituteGeom; PxTransform preTransform;
if (useSubstituteGeometry(substituteGeom, preTransform, contactBuffer.contacts[contactBuffer.count - 1], pose0))
{
contactBuffer.count--;
const PxGeometry& geom = substituteGeom.any();
// PT:: tag: scalar transform*transform
PxTransform pose = pose0.transform(preTransform);
PxGeometryQuery::generateTriangleContacts(geom, pose, tri.verts, triangles[i], contactDistance, meshMargin, toleranceLength, contactBuffer);
}
}
if (hasAdjacency)
{
contactFilter.addTriangleContacts(contactBuffer.contacts, contactBuffer.count, triangles[i], tri.verts, adjacent);
contactBuffer.count = 0;
}
}
if (hasAdjacency)
contactFilter.writeToBuffer(contactBuffer);
break;
}
case PxGeometryType::eCUSTOM:
{
const PxCustomGeometry& customGeom1 = static_cast<const PxCustomGeometry&>(geom1);
if (customGeom1.getCustomType() == CylinderCallbacks::TYPE() ||
customGeom1.getCustomType() == ConeCallbacks::TYPE()) // It's a CustomConvex
{
BaseConvexCallbacks* custom1 = static_cast<BaseConvexCallbacks*>(customGeom1.callbacks);
if (PxGjkQueryExt::generateContacts(*this, *custom1, pose0, pose1, contactDistance, toleranceLength, contactBuffer))
{
PxGeometryHolder substituteGeom; PxTransform preTransform;
if (useSubstituteGeometry(substituteGeom, preTransform, contactBuffer.contacts[contactBuffer.count - 1], pose0))
{
contactBuffer.count--;
PxU32 oldCount = contactBuffer.count;
const PxGeometry* pGeom0 = &substituteGeom.any();
const PxGeometry* pGeom1 = &geom1;
// PT:: tag: scalar transform*transform
PxTransform pose = pose0.transform(preTransform);
immediate::PxGenerateContacts(&pGeom1, &pGeom0, &pose1, &pose, &contactCache, 1, contactRecorder,
contactDistance, meshContactMargin, toleranceLength, contactCacheAllocator);
for (int i = oldCount; i < int(contactBuffer.count); ++i)
contactBuffer.contacts[i].normal = -contactBuffer.contacts[i].normal;
}
}
}
else
{
const PxGeometry* pGeom0 = &geom0;
const PxGeometry* pGeom1 = &geom1;
immediate::PxGenerateContacts(&pGeom1, &pGeom0, &pose1, &pose0, &contactCache, 1, contactRecorder,
contactDistance, meshContactMargin, toleranceLength, contactCacheAllocator);
for (int i = 0; i < int(contactBuffer.count); ++i)
contactBuffer.contacts[i].normal = -contactBuffer.contacts[i].normal;
}
break;
}
default:
break;
}
return contactBuffer.count > 0;
}
PxU32 PxCustomGeometryExt::BaseConvexCallbacks::raycast(const PxVec3& origin, const PxVec3& unitDir, const PxGeometry& geom, const PxTransform& pose,
PxReal maxDist, PxHitFlags /*hitFlags*/, PxU32 /*maxHits*/, PxGeomRaycastHit* rayHits, PxU32 /*stride*/, PxRaycastThreadContext*) const
{
// When FLT_MAX is used as maxDist, it works bad with GJK algorithm.
// Here I compute the maximum needed distance (wiseDist) as the diagonal
// of the bounding box of both the geometry and the ray origin.
PxBounds3 bounds = PxBounds3::transformFast(pose, getLocalBounds(geom));
bounds.include(origin);
PxReal wiseDist = PxMin(maxDist, bounds.getDimensions().magnitude());
PxReal t;
PxVec3 n, p;
if (PxGjkQuery::raycast(*this, pose, origin, unitDir, wiseDist, t, n, p))
{
PxGeomRaycastHit& hit = *rayHits;
hit.distance = t;
hit.position = p;
hit.normal = n;
hit.flags |= PxHitFlag::ePOSITION | PxHitFlag::eNORMAL;
return 1;
}
return 0;
}
bool PxCustomGeometryExt::BaseConvexCallbacks::overlap(const PxGeometry& /*geom0*/, const PxTransform& pose0, const PxGeometry& geom1, const PxTransform& pose1, PxOverlapThreadContext*) const
{
switch (geom1.getType())
{
case PxGeometryType::eSPHERE:
case PxGeometryType::eCAPSULE:
case PxGeometryType::eBOX:
case PxGeometryType::eCONVEXMESH:
{
PxGjkQueryExt::ConvexGeomSupport geomSupport(geom1);
if (PxGjkQuery::overlap(*this, geomSupport, pose0, pose1))
return true;
break;
}
default:
break;
}
return false;
}
bool PxCustomGeometryExt::BaseConvexCallbacks::sweep(const PxVec3& unitDir, const PxReal maxDist,
const PxGeometry& geom0, const PxTransform& pose0, const PxGeometry& geom1, const PxTransform& pose1,
PxGeomSweepHit& sweepHit, PxHitFlags /*hitFlags*/, const PxReal inflation, PxSweepThreadContext*) const
{
PxBounds3 bounds0 = PxBounds3::transformFast(pose0, getLocalBounds(geom0));
switch (geom1.getType())
{
case PxGeometryType::eSPHERE:
case PxGeometryType::eCAPSULE:
case PxGeometryType::eBOX:
case PxGeometryType::eCONVEXMESH:
{
// See comment in BaseConvexCallbacks::raycast
PxBounds3 bounds; PxGeometryQuery::computeGeomBounds(bounds, geom1, pose1, 0, inflation);
bounds.include(bounds0);
PxReal wiseDist = PxMin(maxDist, bounds.getDimensions().magnitude());
PxGjkQueryExt::ConvexGeomSupport geomSupport(geom1, inflation);
PxReal t;
PxVec3 n, p;
if (PxGjkQuery::sweep(*this, geomSupport, pose0, pose1, unitDir, wiseDist, t, n, p))
{
sweepHit.distance = t;
sweepHit.position = p;
sweepHit.normal = n;
sweepHit.flags = PxHitFlag::ePOSITION | PxHitFlag::eNORMAL;
return true;
}
break;
}
// sweep against triangle meshes and height fields for CCD support
case PxGeometryType::eTRIANGLEMESH:
case PxGeometryType::eHEIGHTFIELD:
{
PxReal radius = getLocalBounds(geom0).getExtents().magnitude();
PxGeometryHolder sweepGeom = PxSphereGeometry(radius + inflation);
PxTransform sweepGeomPose = pose0;
// for some reason, capsule can't have near zero height
// [assert @ source\geomutils\src\intersection\GuIntersectionCapsuleTriangle.cpp(37)]
// so I use capsule only if height is larger than 0.1% of radius
if (maxDist > radius * 0.001f)
{
sweepGeom = PxCapsuleGeometry(radius + inflation, maxDist * 0.5f);
sweepGeomPose = PxTransform(pose0.p - unitDir * maxDist * 0.5f, PxShortestRotation(PxVec3(1, 0, 0), unitDir));
}
PxU32 triangles[MAX_TRIANGLES];
bool overflow = false;
PxU32 triangleCount = (geom1.getType() == PxGeometryType::eTRIANGLEMESH) ?
PxMeshQuery::findOverlapTriangleMesh(sweepGeom.any(), sweepGeomPose, static_cast<const PxTriangleMeshGeometry&>(geom1), pose1, triangles, MAX_TRIANGLES, 0, overflow) :
PxMeshQuery::findOverlapHeightField(sweepGeom.any(), sweepGeomPose, static_cast<const PxHeightFieldGeometry&>(geom1), pose1, triangles, MAX_TRIANGLES, 0, overflow);
if(overflow)
PxGetFoundation().error(PxErrorCode::eDEBUG_INFO, PX_FL, "PxCustomGeometryExt::BaseConvexCallbacks::sweep() Too many triangles.\n");
sweepHit.distance = PX_MAX_F32;
const PxTransform identityPose(PxIdentity);
for (PxU32 i = 0; i < triangleCount; ++i)
{
PxTriangle tri;
if (geom1.getType() == PxGeometryType::eTRIANGLEMESH)
PxMeshQuery::getTriangle(static_cast<const PxTriangleMeshGeometry&>(geom1), pose1, triangles[i], tri);
else
PxMeshQuery::getTriangle(static_cast<const PxHeightFieldGeometry&>(geom1), pose1, triangles[i], tri);
TriangleSupport triSupport(tri.verts[0], tri.verts[1], tri.verts[2], inflation);
PxBounds3 bounds = bounds0;
for (PxU32 j = 0; j < 3; ++j)
bounds.include(tri.verts[j]);
bounds.fattenFast(inflation);
// See comment in BaseConvexCallbacks::raycast
PxReal wiseDist = PxMin(maxDist, bounds.getDimensions().magnitude());
PxReal t;
PxVec3 n, p;
if (PxGjkQuery::sweep(*this, triSupport, pose0, identityPose, unitDir, wiseDist, t, n, p))
{
if (sweepHit.distance > t)
{
sweepHit.faceIndex = triangles[i];
sweepHit.distance = t;
sweepHit.position = p;
sweepHit.normal = n;
sweepHit.flags = PxHitFlag::ePOSITION | PxHitFlag::eNORMAL | PxHitFlag::eFACE_INDEX;
}
}
}
if (sweepHit.distance < PX_MAX_F32)
return true;
break;
}
default:
break;
}
return false;
}
bool PxCustomGeometryExt::BaseConvexCallbacks::usePersistentContactManifold(const PxGeometry& /*geometry*/, PxReal& breakingThreshold) const
{
// Even if we don't use persistent manifold, we still need to set proper breakingThreshold
// because the other geometry still can force the PCM usage. FLT_EPSILON ensures that
// the contacts will be discarded and recomputed every frame if actor moves.
breakingThreshold = FLT_EPSILON;
return false;
}
void PxCustomGeometryExt::BaseConvexCallbacks::setMargin(float m)
{
margin = m;
#if PX_SUPPORT_OMNI_PVD
OMNI_PVD_SET(OMNI_PVD_CONTEXT_HANDLE, PxCustomGeometryExtBaseConvexCallbacks, margin, *this, margin);
#endif
}
///////////////////////////////////////////////////////////////////////////////
IMPLEMENT_CUSTOM_GEOMETRY_TYPE(PxCustomGeometryExt::CylinderCallbacks)
PxCustomGeometryExt::CylinderCallbacks::CylinderCallbacks(float _height, float _radius, int _axis, float _margin)
:
BaseConvexCallbacks(_margin), height(_height), radius(_radius), axis(_axis)
{
#if PX_SUPPORT_OMNI_PVD
OMNI_PVD_WRITE_SCOPE_BEGIN(pvdWriter, pvdRegData)
OMNI_PVD_CREATE_EXPLICIT(pvdWriter, pvdRegData, OMNI_PVD_CONTEXT_HANDLE, PxCustomGeometryExtCylinderCallbacks, *this);
OMNI_PVD_SET_EXPLICIT(pvdWriter, pvdRegData, OMNI_PVD_CONTEXT_HANDLE, PxCustomGeometryExtBaseConvexCallbacks, margin, *static_cast<BaseConvexCallbacks*>(this), margin);
OMNI_PVD_SET_EXPLICIT(pvdWriter, pvdRegData, OMNI_PVD_CONTEXT_HANDLE, PxCustomGeometryExtCylinderCallbacks, height, *this, height);
OMNI_PVD_SET_EXPLICIT(pvdWriter, pvdRegData, OMNI_PVD_CONTEXT_HANDLE, PxCustomGeometryExtCylinderCallbacks, radius, *this, radius);
OMNI_PVD_SET_EXPLICIT(pvdWriter, pvdRegData, OMNI_PVD_CONTEXT_HANDLE, PxCustomGeometryExtCylinderCallbacks, axis, *this, axis);
OMNI_PVD_WRITE_SCOPE_END
#endif
}
void PxCustomGeometryExt::CylinderCallbacks::setHeight(float h)
{
height = h;
#if PX_SUPPORT_OMNI_PVD
OMNI_PVD_SET(OMNI_PVD_CONTEXT_HANDLE, PxCustomGeometryExtCylinderCallbacks, height, *this, height);
#endif
}
void PxCustomGeometryExt::CylinderCallbacks::setRadius(float r)
{
radius = r;
#if PX_SUPPORT_OMNI_PVD
OMNI_PVD_SET(OMNI_PVD_CONTEXT_HANDLE, PxCustomGeometryExtCylinderCallbacks, radius, *this, radius);
#endif
}
void PxCustomGeometryExt::CylinderCallbacks::setAxis(int a)
{
axis = a;
#if PX_SUPPORT_OMNI_PVD
OMNI_PVD_SET(OMNI_PVD_CONTEXT_HANDLE, PxCustomGeometryExtCylinderCallbacks, axis, *this, axis);
#endif
}
void PxCustomGeometryExt::CylinderCallbacks::visualize(const PxGeometry&, PxRenderOutput& out, const PxTransform& transform, const PxBounds3&) const
{
const float ERR = 0.001f;
out << gCollisionShapeColor;
out << transform;
int axis1 = (axis + 1) % 3;
int axis2 = (axis + 2) % 3;
PxVec3 zr(PxZero), rd(PxZero), ax(PxZero), ax1(PxZero), ax2(PxZero), r0(PxZero), r1(PxZero);
ax[axis] = ax1[axis1] = ax2[axis2] = 1.0f;
r0[axis1] = r1[axis2] = radius;
rd[axis1] = radius;
rd[axis] = -(height * 0.5f + margin);
drawCircle(zr, rd, ax, ERR, out);
rd[axis] = (height * 0.5f + margin);
drawCircle(zr, rd, ax, ERR, out);
rd[axis1] = radius + margin;
rd[axis] = -(height * 0.5f);
drawCircle(zr, rd, ax, ERR, out);
rd[axis] = (height * 0.5f);
drawCircle(zr, rd, ax, ERR, out);
drawLine(-ax * height * 0.5f + ax1 * (radius + margin), ax * height * 0.5f + ax1 * (radius + margin), out);
drawLine(-ax * height * 0.5f - ax1 * (radius + margin), ax * height * 0.5f - ax1 * (radius + margin), out);
drawLine(-ax * height * 0.5f + ax2 * (radius + margin), ax * height * 0.5f + ax2 * (radius + margin), out);
drawLine(-ax * height * 0.5f - ax2 * (radius + margin), ax * height * 0.5f - ax2 * (radius + margin), out);
drawQuarterCircle(-ax * height * 0.5f + ax1 * radius, -ax * margin, -ax2, ERR, out);
drawQuarterCircle(-ax * height * 0.5f - ax1 * radius, -ax * margin, ax2, ERR, out);
drawQuarterCircle(-ax * height * 0.5f + ax2 * radius, -ax * margin, ax1, ERR, out);
drawQuarterCircle(-ax * height * 0.5f - ax2 * radius, -ax * margin, -ax1, ERR, out);
drawQuarterCircle(ax * height * 0.5f + ax1 * radius, ax * margin, ax2, ERR, out);
drawQuarterCircle(ax * height * 0.5f - ax1 * radius, ax * margin, -ax2, ERR, out);
drawQuarterCircle(ax * height * 0.5f + ax2 * radius, ax * margin, -ax1, ERR, out);
drawQuarterCircle(ax * height * 0.5f - ax2 * radius, ax * margin, ax1, ERR, out);
}
PxVec3 PxCustomGeometryExt::CylinderCallbacks::supportLocal(const PxVec3& dir) const
{
float halfHeight = height * 0.5f;
PxVec3 d = dir.getNormalized();
switch (axis)
{
case 0: // X
{
if (PxSign2(d.x) != 0 && PxSign2(d.y) == 0 && PxSign2(d.z) == 0) return PxVec3(PxSign2(d.x) * halfHeight, 0, 0);
return PxVec3(PxSign2(d.x) * halfHeight, 0, 0) + PxVec3(0, d.y, d.z).getNormalized() * radius;
}
case 1: // Y
{
if (PxSign2(d.x) == 0 && PxSign2(d.y) != 0 && PxSign2(d.z) == 0) return PxVec3(0, PxSign2(d.y) * halfHeight, 0);
return PxVec3(0, PxSign2(d.y) * halfHeight, 0) + PxVec3(d.x, 0, d.z).getNormalized() * radius;
}
case 2: // Z
{
if (PxSign2(d.x) == 0 && PxSign2(d.y) == 0 && PxSign2(d.z) != 0) return PxVec3(0, 0, PxSign2(d.z) * halfHeight);
return PxVec3(0, 0, PxSign2(d.z) * halfHeight) + PxVec3(d.x, d.y, 0).getNormalized() * radius;
}
}
return PxVec3(0);
}
void PxCustomGeometryExt::CylinderCallbacks::computeMassProperties(const PxGeometry& /*geometry*/, PxMassProperties& massProperties) const
{
if (margin == 0)
{
PxMassProperties& mass = massProperties;
float R = radius, H = height;
mass.mass = PxPi * R * R * H;
mass.inertiaTensor = PxMat33(PxZero);
mass.centerOfMass = PxVec3(PxZero);
float I0 = mass.mass * R * R / 2.0f;
float I1 = mass.mass * (3 * R * R + H * H) / 12.0f;
mass.inertiaTensor[axis][axis] = I0;
mass.inertiaTensor[(axis + 1) % 3][(axis + 1) % 3] = mass.inertiaTensor[(axis + 2) % 3][(axis + 2) % 3] = I1;
}
else
{
const int SLICE_COUNT = 32;
PxMassProperties sliceMasses[SLICE_COUNT];
PxTransform slicePoses[SLICE_COUNT];
float sliceHeight = height / SLICE_COUNT;
for (int i = 0; i < SLICE_COUNT; ++i)
{
float t = -height * 0.5f + i * sliceHeight + sliceHeight * 0.5f;
float R = getRadiusAtHeight(t), H = sliceHeight;
PxMassProperties mass;
mass.mass = PxPi * R * R * H;
mass.inertiaTensor = PxMat33(PxZero);
mass.centerOfMass = PxVec3(PxZero);
float I0 = mass.mass * R * R / 2.0f;
float I1 = mass.mass * (3 * R * R + H * H) / 12.0f;
mass.inertiaTensor[axis][axis] = I0;
mass.inertiaTensor[(axis + 1) % 3][(axis + 1) % 3] = mass.inertiaTensor[(axis + 2) % 3][(axis + 2) % 3] = I1;
mass.centerOfMass[axis] = t;
sliceMasses[i] = mass;
slicePoses[i] = PxTransform(PxIdentity);
}
massProperties = PxMassProperties::sum(sliceMasses, slicePoses, SLICE_COUNT);
}
}
bool PxCustomGeometryExt::CylinderCallbacks::useSubstituteGeometry(PxGeometryHolder& geom, PxTransform& preTransform, const PxContactPoint& p, const PxTransform& pose0) const
{
// here I check if we contact with the cylender bases or the lateral surface
// where more than 1 contact point can be generated.
PxVec3 locN = pose0.rotateInv(p.normal);
float nAng = acosf(PxClamp(-locN[axis], -1.0f, 1.0f));
float epsAng = PxPi / 36.0f; // 5 degrees
if (nAng < epsAng || nAng > PxPi - epsAng)
{
// if we contact with the bases
// make the substitute geometry a box and rotate it so one of
// the corners matches the contact point
PxVec3 halfSize;
halfSize[axis] = height * 0.5f + margin;
halfSize[(axis + 1) % 3] = halfSize[(axis + 2) % 3] = radius / sqrtf(2.0f);
geom = PxBoxGeometry(halfSize);
PxVec3 axisDir(PxZero); axisDir[axis] = 1.0f;
PxVec3 locP = pose0.transformInv(p.point);
float s1 = locP[(axis + 1) % 3], s2 = locP[(axis + 2) % 3];
float ang = ((s1 * s1) + (s2 * s2) > 1e-3f) ? atan2f(s2, s1) : 0;
preTransform = PxTransform(PxQuat(ang + PxPi * 0.25f, axisDir));
return true;
}
else if (nAng > PxPiDivTwo - epsAng && nAng < PxPiDivTwo + epsAng)
{
// if it's the lateral surface
// make the substitute geometry a capsule and rotate it so it aligns
// with the cylinder surface
geom = PxCapsuleGeometry(radius + margin, height * 0.5f);
switch (axis)
{
case 0: preTransform = PxTransform(PxIdentity); break;
case 1: preTransform = PxTransform(PxQuat(PxPi * 0.5f, PxVec3(0, 0, 1))); break;
case 2: preTransform = PxTransform(PxQuat(PxPi * 0.5f, PxVec3(0, 1, 0))); break;
}
return true;
}
return false;
}
float PxCustomGeometryExt::CylinderCallbacks::getRadiusAtHeight(float h) const
{
if (h >= -height * 0.5f && h <= height * 0.5f)
return radius + margin;
if (h < -height * 0.5f)
{
float a = -h - height * 0.5f;
return radius + sqrtf(margin * margin - a * a);
}
if (h > height * 0.5f)
{
float a = h - height * 0.5f;
return radius + sqrtf(margin * margin - a * a);
}
PX_ASSERT(0);
return 0;
}
///////////////////////////////////////////////////////////////////////////////
IMPLEMENT_CUSTOM_GEOMETRY_TYPE(PxCustomGeometryExt::ConeCallbacks)
PxCustomGeometryExt::ConeCallbacks::ConeCallbacks(float _height, float _radius, int _axis, float _margin)
:
BaseConvexCallbacks(_margin), height(_height), radius(_radius), axis(_axis)
{
#if PX_SUPPORT_OMNI_PVD
OMNI_PVD_WRITE_SCOPE_BEGIN(pvdWriter, pvdRegData)
OMNI_PVD_CREATE_EXPLICIT(pvdWriter, pvdRegData, OMNI_PVD_CONTEXT_HANDLE, PxCustomGeometryExtConeCallbacks, *this);
OMNI_PVD_SET_EXPLICIT(pvdWriter, pvdRegData, OMNI_PVD_CONTEXT_HANDLE, PxCustomGeometryExtBaseConvexCallbacks, margin, *static_cast<BaseConvexCallbacks*>(this), margin);
OMNI_PVD_SET_EXPLICIT(pvdWriter, pvdRegData, OMNI_PVD_CONTEXT_HANDLE, PxCustomGeometryExtConeCallbacks, height, *this, height);
OMNI_PVD_SET_EXPLICIT(pvdWriter, pvdRegData, OMNI_PVD_CONTEXT_HANDLE, PxCustomGeometryExtConeCallbacks, radius, *this, radius);
OMNI_PVD_SET_EXPLICIT(pvdWriter, pvdRegData, OMNI_PVD_CONTEXT_HANDLE, PxCustomGeometryExtConeCallbacks, axis, *this, axis);
OMNI_PVD_WRITE_SCOPE_END
#endif
}
void PxCustomGeometryExt::ConeCallbacks::setHeight(float h)
{
height = h;
#if PX_SUPPORT_OMNI_PVD
OMNI_PVD_SET(OMNI_PVD_CONTEXT_HANDLE, PxCustomGeometryExtConeCallbacks, height, *this, height);
#endif
}
void PxCustomGeometryExt::ConeCallbacks::setRadius(float r)
{
radius = r;
#if PX_SUPPORT_OMNI_PVD
OMNI_PVD_SET(OMNI_PVD_CONTEXT_HANDLE, PxCustomGeometryExtConeCallbacks, radius, *this, radius);
#endif
}
void PxCustomGeometryExt::ConeCallbacks::setAxis(int a)
{
axis = a;
#if PX_SUPPORT_OMNI_PVD
OMNI_PVD_SET(OMNI_PVD_CONTEXT_HANDLE, PxCustomGeometryExtConeCallbacks, axis, *this, axis);
#endif
}
void PxCustomGeometryExt::ConeCallbacks::visualize(const PxGeometry&, PxRenderOutput& out, const PxTransform& transform, const PxBounds3&) const
{
const float ERR = 0.001f;
out << gCollisionShapeColor;
out << transform;
int axis1 = (axis + 1) % 3;
int axis2 = (axis + 2) % 3;
PxVec3 zr(PxZero), rd(PxZero), ax(PxZero), ax1(PxZero), ax2(PxZero), r0(PxZero), r1(PxZero);
ax[axis] = ax1[axis1] = ax2[axis2] = 1.0f;
r0[axis1] = r1[axis2] = radius;
float ang = atan2f(radius, height);
float aSin = sinf(ang);
rd[axis1] = radius;
rd[axis] = -(height * 0.5f + margin);
drawCircle(zr, rd, ax, ERR, out);
rd[axis] = -(height * 0.5f) + margin * aSin;
rd[axis1] = getRadiusAtHeight(rd[axis]);
drawCircle(zr, rd, ax, ERR, out);
rd[axis] = height * 0.5f + margin * aSin;
rd[axis1] = getRadiusAtHeight(rd[axis]);
drawCircle(zr, rd, ax, ERR, out);
float h0 = -height * 0.5f + margin * aSin, h1 = height * 0.5f + margin * aSin;
float s0 = getRadiusAtHeight(h0), s1 = getRadiusAtHeight(h1);
drawLine(ax * h0 + ax1 * s0, ax * h1 + ax1 * s1, out);
drawLine(ax * h0 - ax1 * s0, ax * h1 - ax1 * s1, out);
drawLine(ax * h0 + ax2 * s0, ax * h1 + ax2 * s1, out);
drawLine(ax * h0 - ax2 * s0, ax * h1 - ax2 * s1, out);
drawArc(-ax * height * 0.5f + ax1 * radius, -ax * margin, -ax2, PxPiDivTwo + ang, ERR, out);
drawArc(-ax * height * 0.5f - ax1 * radius, -ax * margin, ax2, PxPiDivTwo + ang, ERR, out);
drawArc(-ax * height * 0.5f + ax2 * radius, -ax * margin, ax1, PxPiDivTwo + ang, ERR, out);
drawArc(-ax * height * 0.5f - ax2 * radius, -ax * margin, -ax1, PxPiDivTwo + ang, ERR, out);
drawArc(ax * height * 0.5f, ax * margin, ax2, PxPiDivTwo - ang, ERR, out);
drawArc(ax * height * 0.5f, ax * margin, -ax2, PxPiDivTwo - ang, ERR, out);
drawArc(ax * height * 0.5f, ax * margin, -ax1, PxPiDivTwo - ang, ERR, out);
drawArc(ax * height * 0.5f, ax * margin, ax1, PxPiDivTwo - ang, ERR, out);
}
PxVec3 PxCustomGeometryExt::ConeCallbacks::supportLocal(const PxVec3& dir) const
{
float halfHeight = height * 0.5f;
float cosAlph = radius / sqrtf(height * height + radius * radius);
PxVec3 d = dir.getNormalized();
switch (axis)
{
case 0: // X
{
if (d.x > cosAlph || (PxSign2(d.x) != 0 && PxSign2(d.y) == 0 && PxSign2(d.z) == 0)) return PxVec3(PxSign2(d.x) * halfHeight, 0, 0);
return PxVec3(-halfHeight, 0, 0) + PxVec3(0, d.y, d.z).getNormalized() * radius;
}
case 1: // Y
{
if (d.y > cosAlph || (PxSign2(d.y) != 0 && PxSign2(d.x) == 0 && PxSign2(d.z) == 0)) return PxVec3(0, PxSign2(d.y) * halfHeight, 0);
return PxVec3(0, -halfHeight, 0) + PxVec3(d.x, 0, d.z).getNormalized() * radius;
}
case 2: // Z
{
if (d.z > cosAlph || (PxSign2(d.z) != 0 && PxSign2(d.x) == 0 && PxSign2(d.y) == 0)) return PxVec3(0, 0, PxSign2(d.z) * halfHeight);
return PxVec3(0, 0, -halfHeight) + PxVec3(d.x, d.y, 0).getNormalized() * radius;
}
}
return PxVec3(0);
}
void PxCustomGeometryExt::ConeCallbacks::computeMassProperties(const PxGeometry& /*geometry*/, PxMassProperties& massProperties) const
{
if (margin == 0)
{
PxMassProperties& mass = massProperties;
float H = height, R = radius;
mass.mass = PxPi * R * R * H / 3.0f;
mass.inertiaTensor = PxMat33(PxZero);
mass.centerOfMass = PxVec3(PxZero);
float I0 = mass.mass * R * R * 3.0f / 10.0f;
float I1 = mass.mass * (R * R * 3.0f / 20.0f + H * H * 3.0f / 80.0f);
mass.inertiaTensor[axis][axis] = I0;
mass.inertiaTensor[(axis + 1) % 3][(axis + 1) % 3] = mass.inertiaTensor[(axis + 2) % 3][(axis + 2) % 3] = I1;
mass.centerOfMass[axis] = -H / 4.0f;
mass.inertiaTensor = PxMassProperties::translateInertia(mass.inertiaTensor, mass.mass, mass.centerOfMass);
}
else
{
const int SLICE_COUNT = 32;
PxMassProperties sliceMasses[SLICE_COUNT];
PxTransform slicePoses[SLICE_COUNT];
float sliceHeight = height / SLICE_COUNT;
for (int i = 0; i < SLICE_COUNT; ++i)
{
float t = -height * 0.5f + i * sliceHeight + sliceHeight * 0.5f;
float R = getRadiusAtHeight(t), H = sliceHeight;
PxMassProperties mass;
mass.mass = PxPi * R * R * H;
mass.inertiaTensor = PxMat33(PxZero);
mass.centerOfMass = PxVec3(PxZero);
float I0 = mass.mass * R * R / 2.0f;
float I1 = mass.mass * (3 * R * R + H * H) / 12.0f;
mass.inertiaTensor[axis][axis] = I0;
mass.inertiaTensor[(axis + 1) % 3][(axis + 1) % 3] = mass.inertiaTensor[(axis + 2) % 3][(axis + 2) % 3] = I1;
mass.centerOfMass[axis] = t;
sliceMasses[i] = mass;
slicePoses[i] = PxTransform(PxIdentity);
}
massProperties = PxMassProperties::sum(sliceMasses, slicePoses, SLICE_COUNT);
massProperties.inertiaTensor = PxMassProperties::translateInertia(massProperties.inertiaTensor, massProperties.mass, massProperties.centerOfMass);
}
}
bool PxCustomGeometryExt::ConeCallbacks::useSubstituteGeometry(PxGeometryHolder& geom, PxTransform& preTransform, const PxContactPoint& p, const PxTransform& pose0) const
{
// here I check if we contact with the cone base or the lateral surface
// where more than 1 contact point can be generated.
PxVec3 locN = pose0.rotateInv(p.normal);
float nAng = acosf(PxClamp(-locN[axis], -1.0f, 1.0f));
float epsAng = PxPi / 36.0f; // 5 degrees
float coneAng = atan2f(radius, height);
if (nAng > PxPi - epsAng)
{
// if we contact with the base
// make the substitute geometry a box and rotate it so one of
// the corners matches the contact point
PxVec3 halfSize;
halfSize[axis] = height * 0.5f + margin;
halfSize[(axis + 1) % 3] = halfSize[(axis + 2) % 3] = radius / sqrtf(2.0f);
geom = PxBoxGeometry(halfSize);
PxVec3 axisDir(PxZero); axisDir[axis] = 1.0f;
PxVec3 locP = pose0.transformInv(p.point);
float s1 = locP[(axis + 1) % 3], s2 = locP[(axis + 2) % 3];
float ang = ((s1 * s1) + (s2 * s2) > 1e-3f) ? atan2f(s2, s1) : 0;
preTransform = PxTransform(PxQuat(ang + PxPi * 0.25f, axisDir));
return true;
}
else if (nAng > PxPiDivTwo - coneAng - epsAng && nAng < PxPiDivTwo - coneAng + epsAng)
{
// if it's the lateral surface
// make the substitute geometry a capsule and rotate it so it aligns
// with the cone surface
geom = PxCapsuleGeometry(radius * 0.25f + margin, sqrtf(height * height + radius * radius) * 0.5f);
switch (axis)
{
case 0:
{
PxVec3 capC = (PxVec3(height * 0.5f, 0, 0) + PxVec3(-height * 0.5f, radius, 0)) * 0.5f - PxVec3(radius, height, 0).getNormalized() * radius * 0.25f;
preTransform = PxTransform(capC, PxQuat(-coneAng, PxVec3(0, 0, 1)));
break;
}
case 1:
{
PxVec3 capC = (PxVec3(0, height * 0.5f, 0) + PxVec3(0, -height * 0.5f, radius)) * 0.5f - PxVec3(0, radius, height).getNormalized() * radius * 0.25f;
preTransform = PxTransform(capC, PxQuat(-coneAng, PxVec3(1, 0, 0)) * PxQuat(PxPiDivTwo, PxVec3(0, 0, 1)));
break;
}
case 2:
{
PxVec3 capC = (PxVec3(0, 0, height * 0.5f) + PxVec3(radius, 0, -height * 0.5f)) * 0.5f - PxVec3(height, 0, radius).getNormalized() * radius * 0.25f;
preTransform = PxTransform(capC, PxQuat(-coneAng, PxVec3(0, 1, 0)) * PxQuat(PxPiDivTwo, PxVec3(0, 1, 0)));
break;
}
}
PxVec3 axisDir(PxZero); axisDir[axis] = 1.0f;
float n1 = -locN[(axis + 1) % 3], n2 = -locN[(axis + 2) % 3];
float ang = atan2f(n2, n1);
// PT:: tag: scalar transform*transform
preTransform = PxTransform(PxQuat(ang, axisDir)) * preTransform;
return true;
}
return false;
}
float PxCustomGeometryExt::ConeCallbacks::getRadiusAtHeight(float h) const
{
float angle = atan2f(radius, height);
float aSin = sinf(angle);
float aCos = cosf(angle);
if (h >= -height * 0.5f + margin * aSin && h <= height * 0.5f + margin * aSin)
return radius * (height * 0.5f - h) / height + margin / aCos;
if (h < -height * 0.5f + margin * aSin)
{
float a = -h - height * 0.5f;
return radius + sqrtf(margin * margin - a * a);
}
if (h > height * 0.5f + margin * aSin)
{
float a = h - height * 0.5f;
return sqrtf(margin * margin - a * a);
}
PX_ASSERT(0);
return 0;
}