// // 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-2018 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 "GuConvexMesh.h" #include "GuConvexHelper.h" #include "GuContactBuffer.h" #include "GuContactMethodImpl.h" #include "GuVecConvexHull.h" #include "GuVecCapsule.h" #include "GuInternal.h" #include "GuGJK.h" #include "GuGeometryUnion.h" using namespace physx; using namespace Gu; /////////// // #include "CmRenderOutput.h" // #include "PxsContext.h" // static void gVisualizeLine(const PxVec3& a, const PxVec3& b, PxcNpThreadContext& context, PxU32 color=0xffffff) // { // PxMat44 m = PxMat44::identity(); // // Cm::RenderOutput& out = context.mRenderOutput; // out << color << m << Cm::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(distmax0) Ps::swap(min0, max0); min0 -= radius; max0 += radius; // Project convex PxReal Min1, Max1; (polyData.mProjectHull)(polyData, axis, worldTM, scaling, Min1, Max1); // Test projections if(max0=0.0f); const PxReal d1 = Max1 - min0; PX_ASSERT(d1>=0.0f); depth = physx::intrinsics::selectMin(d0, d1); return true; } static bool GuCapsuleConvexOverlap(const Gu::Segment& segment, PxReal radius, const PolygonalData& polyData, const Cm::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 Gu::HullPolygonData* polys = polyData.mPolygons; const Cm::Matrix34 worldTM(transform); // Test normals for(PxU32 i=0;i 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(ContactBuffer& contactBuffer, // const PxTransform& convexPose, const PolygonalData& polyData, // Convex data const PxMeshScale& scale, // PxU32 nbPts, const PxVec3* PX_RESTRICT points, const PxReal radius, // Capsule's radius // const PxVec3& normal, const PxReal contactDistance) { PX_ASSERT(PxAbs(normal.magnitudeSquared()-1)<1e-4f); //scaling: transform the ray to vertex space const Cm::Matrix34 world2vertexSkew = scale.getInverse() * convexPose.getInverse(); const PxVec3 vrayDir = world2vertexSkew.rotate( -normal ); const PxReal maxDist = contactDistance + radius; for(PxU32 i=0;i(); const PxConvexMeshGeometryLL& shapeConvex = shape1.get(); PxVec3 onSegment, onConvex; PxReal distance; PxVec3 normal_; { Gu::ConvexMesh* cm = static_cast(shapeConvex.convexMesh); using namespace Ps::aos; Vec3V closA, closB, normalV; GjkStatus status; FloatV dist; { const Vec3V zeroV = V3Zero(); const Gu::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 PsMatTransformV aToB(transform1.transformInv(transform0)); Gu::ConvexHullV convexHull(hullData, zeroV, vScale, vQuat, shapeConvex.scale.isIdentity()); //transform capsule(a) into the local space of convexHull(b), treat capsule as segment Gu::CapsuleV capsule(aToB.p, aToB.rotate(V3Scale(V3UnitX(), capsuleHalfHeight)), FZero()); LocalConvex convexA(capsule); LocalConvex convexB(convexHull); const Vec3V initialSearchDir = V3Sub(convexA.getCenter(), convexB.getCenter()); status = gjk, LocalConvex >(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; Gu::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); Cm::FastVertex2ShapeScaling convexScaling; const bool idtConvexScale = shapeConvex.scale.isIdentity(); if(!idtConvexScale) convexScaling.init(shapeConvex.scale); PolygonalData polyData; getPolygonalData_Convex(&polyData, shapeConvex.hullData, 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 Cm::Matrix34 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; } } }