// 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 #include #include #include #include #include #include #include #include #include #include #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(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(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(geom1), pose1, triangles, MAX_TRIANGLES, 0, overflow) : PxMeshQuery::findOverlapHeightField(boxGeom, pose0, static_cast(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(geom1), pose1, triangles[i], tri, NULL, hasAdjacency ? adjacent : NULL); else PxMeshQuery::getTriangle(static_cast(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(geom1); if (customGeom1.getCustomType() == CylinderCallbacks::TYPE() || customGeom1.getCustomType() == ConeCallbacks::TYPE()) // It's a CustomConvex { BaseConvexCallbacks* custom1 = static_cast(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(geom1), pose1, triangles, MAX_TRIANGLES, 0, overflow) : PxMeshQuery::findOverlapHeightField(sweepGeom.any(), sweepGeomPose, static_cast(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(geom1), pose1, triangles[i], tri); else PxMeshQuery::getTriangle(static_cast(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(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(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; }