diff options
| author | git perforce import user <a@b> | 2016-10-25 12:29:14 -0600 |
|---|---|---|
| committer | Sheikh Dawood Abdul Ajees <Sheikh Dawood Abdul Ajees> | 2016-10-25 18:56:37 -0500 |
| commit | 3dfe2108cfab31ba3ee5527e217d0d8e99a51162 (patch) | |
| tree | fa6485c169e50d7415a651bf838f5bcd0fd3bfbd /PhysX_3.4/Source/GeomUtils/src/contact | |
| download | physx-3.4-3dfe2108cfab31ba3ee5527e217d0d8e99a51162.tar.xz physx-3.4-3dfe2108cfab31ba3ee5527e217d0d8e99a51162.zip | |
Initial commit:
PhysX 3.4.0 Update @ 21294896
APEX 1.4.0 Update @ 21275617
[CL 21300167]
Diffstat (limited to 'PhysX_3.4/Source/GeomUtils/src/contact')
25 files changed, 9266 insertions, 0 deletions
diff --git a/PhysX_3.4/Source/GeomUtils/src/contact/GuContactBoxBox.cpp b/PhysX_3.4/Source/GeomUtils/src/contact/GuContactBoxBox.cpp new file mode 100644 index 00000000..3b12a326 --- /dev/null +++ b/PhysX_3.4/Source/GeomUtils/src/contact/GuContactBoxBox.cpp @@ -0,0 +1,703 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2016 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 "GuContactBuffer.h" +#include "GuContactMethodImpl.h" +#include "GuGeometryUnion.h" +#include "CmMatrix34.h" +#include "PsUtilities.h" + +using namespace physx; +using namespace Gu; + +#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 Cm::Matrix34& transform0, const Cm::Matrix34& transform1, PxReal contactDistance);*/ + +static PxI32 doBoxBoxContactGeneration(ContactBuffer& contactBuffer, + const PxVec3& extents0, const PxVec3& extents1, + PxU32& collisionData, + const Cm::Matrix34& transform0, const Cm::Matrix34& transform1, PxReal contactDistance); + +namespace physx +{ + +namespace Gu +{ +bool contactBoxBox(GU_CONTACT_METHOD_ARGS) +{ + PX_UNUSED(renderOutput); + + // Get actual shape data + const PxBoxGeometry& shapeBox0 = shape0.get<const PxBoxGeometry>(); + const PxBoxGeometry& shapeBox1 = shape1.get<const PxBoxGeometry>(); + + PxU32 pd = PxU32(cache.mPairData); + PxI32 Nb = doBoxBoxContactGeneration(contactBuffer, + shapeBox0.halfExtents, shapeBox1.halfExtents, + pd, + Cm::Matrix34(transform0), Cm::Matrix34(transform1), + params.mContactDistance); + + cache.mPairData = Ps::to8(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; +} +}//Gu +}//physx + +// 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[], + ContactBuffer& contactBuffer, const PxVec3& contactNormal, + PxReal y1, PxReal z1, const PxVec3& box2, + const Cm::Matrix34& transform0, const Cm::Matrix34& transform1, PxReal contactDistance) +{ +// PxI32 NbContacts=0; + contactBuffer.reset(); + y1 += contactDistance; + z1 += contactDistance; + + const Cm::Matrix34 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(ContactBuffer& contactBuffer, + const PxVec3& extents0, const PxVec3& extents1, + PxU32& collisionData, + const Cm::Matrix34& transform0, const Cm::Matrix34& 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 + Cm::Matrix34 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); + } +} diff --git a/PhysX_3.4/Source/GeomUtils/src/contact/GuContactCapsuleBox.cpp b/PhysX_3.4/Source/GeomUtils/src/contact/GuContactCapsuleBox.cpp new file mode 100644 index 00000000..75e737d4 --- /dev/null +++ b/PhysX_3.4/Source/GeomUtils/src/contact/GuContactCapsuleBox.cpp @@ -0,0 +1,457 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2016 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 "GuContactBuffer.h" +#include "GuIntersectionRayBox.h" +#include "GuDistanceSegmentBox.h" +#include "GuInternal.h" +#include "GuContactMethodImpl.h" +#include "PsMathUtils.h" +#include "PsUtilities.h" +#include "GuGeometryUnion.h" +#include "GuBoxConversion.h" + +using namespace physx; +using namespace Gu; + +/*namespace Gu +{ +const PxU8* getBoxEdges(); +}*/ + +///////// + /*#include "CmRenderOutput.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); + + Cm::RenderOutput& 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(); + + Cm::RenderOutput& out = context.mRenderOutput; + out << color << m << Cm::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) Ps::swap(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(!Ps::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( ContactBuffer& contactBuffer, + // + const Segment& segment, + const PxReal radius, + // + const Box& worldBox, + // + const PxVec3& normal, + const 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( ContactBuffer& 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; + Ps::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; + Ps::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++]; +// Ps::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( ContactBuffer& contactBuffer, + // + const Segment& segment, + const PxReal radius, + // + const Box& worldBox, + // + const PxVec3& normal, + const PxReal contactDistance) +{ + const PxU8* PX_RESTRICT Indices = getBoxEdges(); + + PxVec3 Pts[8]; + worldBox.computeBoxPoints(Pts); + + PxVec3 s0 = segment.p0; + PxVec3 s1 = segment.p1; + Ps::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; + Ps::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++]; +// Ps::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; + } + } +} + +namespace physx +{ +namespace Gu +{ +bool contactCapsuleBox(GU_CONTACT_METHOD_ARGS) +{ + PX_UNUSED(renderOutput); + PX_UNUSED(cache); + + // Get actual shape data + const PxCapsuleGeometry& shapeCapsule = shape0.get<const PxCapsuleGeometry>(); + const PxBoxGeometry& shapeBox = shape1.get<const PxBoxGeometry>(); + + // 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; +} +}//Gu +}//physx diff --git a/PhysX_3.4/Source/GeomUtils/src/contact/GuContactCapsuleCapsule.cpp b/PhysX_3.4/Source/GeomUtils/src/contact/GuContactCapsuleCapsule.cpp new file mode 100644 index 00000000..f2de1078 --- /dev/null +++ b/PhysX_3.4/Source/GeomUtils/src/contact/GuContactCapsuleCapsule.cpp @@ -0,0 +1,155 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2016 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 "GuContactBuffer.h" +#include "GuDistanceSegmentSegment.h" +#include "GuContactMethodImpl.h" +#include "GuInternal.h" +#include "GuGeometryUnion.h" + +using namespace physx; + +namespace physx +{ +namespace Gu +{ +bool contactCapsuleCapsule(GU_CONTACT_METHOD_ARGS) +{ + PX_UNUSED(renderOutput); + PX_UNUSED(cache); + + const PxCapsuleGeometry& capsuleGeom0 = shape0.get<const PxCapsuleGeometry>(); + const PxCapsuleGeometry& capsuleGeom1 = shape1.get<const PxCapsuleGeometry>(); + + // 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; +} +}//Gu +}//physx diff --git a/PhysX_3.4/Source/GeomUtils/src/contact/GuContactCapsuleConvex.cpp b/PhysX_3.4/Source/GeomUtils/src/contact/GuContactCapsuleConvex.cpp new file mode 100644 index 00000000..981b76ea --- /dev/null +++ b/PhysX_3.4/Source/GeomUtils/src/contact/GuContactCapsuleConvex.cpp @@ -0,0 +1,587 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2016 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(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 Gu::Segment& segment, PxReal radius, + const PolygonalData& polyData, const Cm::FastVertex2ShapeScaling& scaling, + const Cm::Matrix34& worldTM, + PxReal& depth) +{ + // Project capsule + PxReal min0 = segment.p0.dot(axis); + PxReal max0 = segment.p1.dot(axis); + if(min0>max0) 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<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 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<nbPolys;i++) + { + const Gu::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 Gu::HullPolygonData& poly = polys[i]; + const PxPlane& vertSpacePlane = poly.mPlane; + + const PxVec3 worldNormal = worldTM.rotate(vertSpacePlane.n); + + PxVec3 Cross = CapsuleAxis.cross(worldNormal); + if(!Ps::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 Gu::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 Gu::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(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<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( ContactBuffer& contactBuffer, + // + const Gu::Segment& segment, + const PxReal radius, + const PxReal contactDistance, + // + const PolygonalData& polyData, + const PxTransform& transform, + const Cm::FastVertex2ShapeScaling& scaling, + // + const PxVec3& normal) +{ + PxU32 numPolygons = polyData.mNbPolygons; + const Gu::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; + Ps::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; + Ps::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]); +// Ps::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(ContactBuffer& contactBuffer, + // + const Gu::Segment& segment, + const PxReal radius, + // + const Cm::Matrix34& transform, + const PolygonalData& polyData, + const Cm::FastVertex2ShapeScaling& scaling, + // + const PxVec3& normal, + const 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; + Ps::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; + Ps::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 Gu::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++; + } +} + +namespace physx +{ +namespace Gu +{ +bool contactCapsuleConvex(GU_CONTACT_METHOD_ARGS) +{ + PX_UNUSED(renderOutput); + PX_UNUSED(cache); + + // Get actual shape data + const PxCapsuleGeometry& shapeCapsule = shape0.get<const PxCapsuleGeometry>(); + const PxConvexMeshGeometryLL& shapeConvex = shape1.get<const PxConvexMeshGeometryLL>(); + + PxVec3 onSegment, onConvex; + PxReal distance; + PxVec3 normal_; + { + Gu::ConvexMesh* cm = static_cast<Gu::ConvexMesh*>(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<CapsuleV> convexA(capsule); + 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; + + 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; +} + +} +} diff --git a/PhysX_3.4/Source/GeomUtils/src/contact/GuContactCapsuleMesh.cpp b/PhysX_3.4/Source/GeomUtils/src/contact/GuContactCapsuleMesh.cpp new file mode 100644 index 00000000..da282865 --- /dev/null +++ b/PhysX_3.4/Source/GeomUtils/src/contact/GuContactCapsuleMesh.cpp @@ -0,0 +1,636 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2016 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 "GuIntersectionEdgeEdge.h" +#include "GuDistanceSegmentTriangle.h" +#include "GuIntersectionRayTriangle.h" +#include "GuIntersectionTriangleBox.h" +#include "GuInternal.h" +#include "GuContactMethodImpl.h" +#include "GuFeatureCode.h" +#include "GuContactBuffer.h" +#include "GuMidphaseInterface.h" +#include "GuEntityReport.h" +#include "GuHeightFieldUtil.h" +#include "GuConvexEdgeFlags.h" +#include "GuGeometryUnion.h" +#include "GuSIMDHelpers.h" +#include "GuBox.h" + +using namespace physx; +using namespace Gu; + +#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 "CmRenderOutput.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(); + + Cm::RenderOutput& out = context.mRenderOutput; + out << color << m << Cm::RenderOutput::LINES << a << b; +} +static void gVisualizeTri(const PxVec3& a, const PxVec3& b, const PxVec3& c, PxcNpThreadContext& context, PxU32 color=0xffffff) +{ + PxMat44 m = PxMat44::identity(); + + Cm::RenderOutput& out = context.mRenderOutput; + out << color << m << Cm::RenderOutput::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) Ps::swap(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[Ps::getNextIndex3(i)]; + const PxVec3 edge = e0 - e1; + + PxVec3 cross = capsuleAxis.cross(edge); + if(!Ps::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 Cm::Matrix34& meshAbsPose, ContactBuffer& 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); + Cm::RenderOutput((Cm::RenderBuffer&)s->getRenderBuffer()) << Cm::RenderOutput::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 Cm::Matrix34& meshAbsPose, ContactBuffer& 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; + Ps::makeFatEdge(s0, s1, fatBoxEdgeCoeff); + + for(PxU32 i=0;i<3;i++) + { + PxReal dist; + PxVec3 ip; + if(intersectEdgeEdge(triVerts[i], triVerts[Ps::getNextIndex3(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); + Cm::RenderOutput((Cm::RenderBuffer&)s->getRenderBuffer()) << Cm::RenderOutput::LINES << PxDebugColor::eARGB_BLUE // red + << ip << (ip + wn * 10.0f); + #endif + } + } +} + +static void PxcGenerateEEContacts2( const Cm::Matrix34& meshAbsPose, ContactBuffer& 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; + Ps::makeFatEdge(s0, s1, fatBoxEdgeCoeff); + + for(PxU32 i=0;i<3;i++) + { + PxReal dist; + PxVec3 ip; + if(intersectEdgeEdge(triVerts[i], triVerts[Ps::getNextIndex3(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); + Cm::RenderOutput((Cm::RenderBuffer&)s->getRenderBuffer()) << Cm::RenderOutput::LINES << PxDebugColor::eARGB_BLUE // red + << ip << (ip + wn * 10.0f); + #endif + } + } +} + +namespace +{ +struct CapsuleMeshContactGeneration +{ + ContactBuffer& mContactBuffer; + const Cm::Matrix34 mMeshAbsPose; + const Segment& mMeshCapsule; +#ifdef USE_AABB_TRI_CULLING + Vec3p mBC; + Vec3p mBE; +#endif + PxReal mInflatedRadius; + PxReal mContactDistance; + PxReal mShapeCapsuleRadius; + + CapsuleMeshContactGeneration(ContactBuffer& contactBuffer, const PxTransform& transform1, const Segment& meshCapsule, PxReal inflatedRadius, PxReal contactDistance, PxReal shapeCapsuleRadius) : + mContactBuffer (contactBuffer), + mMeshAbsPose (Cm::Matrix34(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 Vec3p 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 TrianglePadded& tri, PxU8 extraData/*, const PxU32* vertInds*/) + { +#ifdef USE_AABB_TRI_CULLING + #if VISUALIZE_CULLING_BOX + { + Cm::RenderOutput& out = context.mRenderOutput; + PxTransform idt = PxTransform(PxIdentity); + out << idt; + out << 0xffffffff; + out << Cm::DebugBox(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 = Ps::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<PxRaycastHit> +{ + CapsuleMeshContactGeneration mGeneration; + const TriangleMesh* mMeshData; + + CapsuleMeshContactGenerationCallback_NoScale( + ContactBuffer& contactBuffer, + const PxTransform& transform1, const Segment& meshCapsule, + PxReal inflatedRadius, PxReal contactDistance, + PxReal shapeCapsuleRadius, const TriangleMesh* meshData + ) : + MeshHitCallback<PxRaycastHit> (CallbackMode::eMULTIPLE), + mGeneration (contactBuffer, transform1, meshCapsule, inflatedRadius, contactDistance, shapeCapsuleRadius), + mMeshData (meshData) + { + PX_ASSERT(contactBuffer.count==0); + } + + PX_FORCE_INLINE PxAgain processTriangle(const PxRaycastHit& hit, const TrianglePadded& tri) + { + 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; + } + + virtual PxAgain processHit( + const PxRaycastHit& hit, const PxVec3& v0, const PxVec3& v1, const PxVec3& v2, PxReal&, const PxU32* /*vInds*/) + { + TrianglePadded tri; + // PT: TODO: revisit this, avoid the copy + tri.verts[0] = v0; + tri.verts[1] = v1; + tri.verts[2] = v2; + return processTriangle(hit, tri); + } + +private: + CapsuleMeshContactGenerationCallback_NoScale& operator=(const CapsuleMeshContactGenerationCallback_NoScale&); +}; + +struct CapsuleMeshContactGenerationCallback_Scale : CapsuleMeshContactGenerationCallback_NoScale +{ + const Cm::FastVertex2ShapeScaling& mScaling; + + CapsuleMeshContactGenerationCallback_Scale( + ContactBuffer& contactBuffer, + const PxTransform& transform1, const Segment& meshCapsule, + PxReal inflatedRadius, const Cm::FastVertex2ShapeScaling& scaling, PxReal contactDistance, + PxReal shapeCapsuleRadius, const TriangleMesh* meshData + ) : + CapsuleMeshContactGenerationCallback_NoScale(contactBuffer, transform1, meshCapsule, inflatedRadius, contactDistance, shapeCapsuleRadius, meshData), + mScaling (scaling) + { + } + + virtual PxAgain processHit( + const PxRaycastHit& hit, const PxVec3& v0, const PxVec3& v1, const PxVec3& v2, PxReal&, const PxU32* /*vInds*/) + { + TrianglePadded tri; + getScaledVertices(tri.verts, v0, v1, v2, false, mScaling); + return processTriangle(hit, tri); + } + +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 = shape0.get<const PxCapsuleGeometry>(); + const PxTriangleMeshGeometryLL& shapeMesh = shape1.get<const PxTriangleMeshGeometryLL>(); + + const PxReal inflatedRadius = shapeCapsule.radius + params.mContactDistance; //AM: inflate! + const Segment meshCapsule = computeLocalCapsule(transform0, transform1, shapeCapsule); + + const TriangleMesh* meshData = shapeMesh.meshData; + + //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 Cm::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 : EntityReport<PxU32> +{ + CapsuleMeshContactGeneration mGeneration; + HeightFieldUtil& mHfUtil; + const PxTransform& mTransform1; + + CapsuleHeightfieldContactGenerationCallback( + ContactBuffer& contactBuffer, + const PxTransform& transform1, 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 onEvent(PxU32 nb, PxU32* indices) + { + const PxU8 nextInd[] = {2,0,1}; + + while(nb--) + { + const PxU32 triangleIndex = *indices++; + + PxU32 vertIndices[3]; + TrianglePadded 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 = shape0.get<const PxCapsuleGeometry>(); + const PxHeightFieldGeometryLL& shapeMesh = shape1.get<const PxHeightFieldGeometryLL>(); + + 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 HeightField& hf = *static_cast<HeightField*>(shapeMesh.heightField); + HeightFieldUtil hfUtil(shapeMesh, hf); + + 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 OBB: + + PxBounds3 bounds; + bounds.maximum = PxVec3(shapeCapsule.halfHeight + inflatedRadius, inflatedRadius, inflatedRadius); + bounds.minimum = -bounds.maximum; + + bounds = PxBounds3::transformFast(transform1.transformInv(transform0), bounds); + + hfUtil.overlapAABBTriangles(transform1, bounds, 0, &callback); + + return contactBuffer.count > 0; +} diff --git a/PhysX_3.4/Source/GeomUtils/src/contact/GuContactConvexConvex.cpp b/PhysX_3.4/Source/GeomUtils/src/contact/GuContactConvexConvex.cpp new file mode 100644 index 00000000..898152ae --- /dev/null +++ b/PhysX_3.4/Source/GeomUtils/src/contact/GuContactConvexConvex.cpp @@ -0,0 +1,1033 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2016 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 "PsMathUtils.h" +#include "GuContactPolygonPolygon.h" +#include "GuGeometryUnion.h" +#include "GuConvexHelper.h" +#include "GuInternal.h" +#include "GuSeparatingAxes.h" +#include "GuContactMethodImpl.h" +#include "GuContactBuffer.h" +#include "PsFPU.h" + +// csigg: the single reference of gEnableOptims (below) has been +// replaced with the actual value to prevent ios64 compiler crash. +// static const int gEnableOptims = 1; +#define CONVEX_CONVEX_ROUGH_FIRST_PASS +#define TEST_INTERNAL_OBJECTS +#ifdef TEST_INTERNAL_OBJECTS + #define USE_BOX_DATA +#endif + +using namespace physx; +using namespace Gu; +using namespace shdfnd::aos; +using namespace intrinsics; + +#ifdef TEST_INTERNAL_OBJECTS +#ifdef USE_BOX_DATA +PX_FORCE_INLINE void BoxSupport(const float extents[3], const PxVec3& sv, float p[3]) +{ + const PxU32* iextents = reinterpret_cast<const PxU32*>(extents); + const PxU32* isv = reinterpret_cast<const PxU32*>(&sv); + PxU32* ip = reinterpret_cast<PxU32*>(p); + + ip[0] = iextents[0]|(isv[0]&PX_SIGN_BITMASK); + ip[1] = iextents[1]|(isv[1]&PX_SIGN_BITMASK); + ip[2] = iextents[2]|(isv[2]&PX_SIGN_BITMASK); +} +#endif + +#if PX_DEBUG +static const PxReal testInternalObjectsEpsilon = 1.0e-3f; +#endif + +#ifdef DO_NOT_REMOVE + PX_FORCE_INLINE void testInternalObjects_( const PxVec3& delta_c, const PxVec3& axis, + const PolygonalData& polyData0, const PolygonalData& polyData1, + const Cm::Matrix34& tr0, const Cm::Matrix34& tr1, + float& dmin, float contactDistance) + { + { +/* float projected0 = axis.dot(tr0.p); + float projected1 = axis.dot(tr1.p); + float min0 = projected0 - polyData0.mInternal.mRadius; + float max0 = projected0 + polyData0.mInternal.mRadius; + float min1 = projected1 - polyData1.mInternal.mRadius; + float max1 = projected1 + polyData1.mInternal.mRadius; +*/ + float MinMaxRadius = polyData0.mInternal.mRadius + polyData1.mInternal.mRadius; + PxVec3 delta = tr0.p - tr1.p; + const PxReal dp = axis.dot(delta); +// const PxReal dp2 = axis.dot(delta_c); + +// const PxReal d0 = max0 - min1; +// const PxReal d0 = projected0 + polyData0.mInternal.mRadius - projected1 + polyData1.mInternal.mRadius; +// const PxReal d0 = projected0 - projected1 + polyData0.mInternal.mRadius + polyData1.mInternal.mRadius; +// const PxReal d0 = projected0 - projected1 + MinMaxRadius; +// const PxReal d0 = axis.dot(tr0.p) - axis.dot(tr1.p) + MinMaxRadius; +// const PxReal d0 = axis.dot(tr0.p - tr1.p) + MinMaxRadius; +// const PxReal d0 = MinMaxRadius + axis.dot(delta); + const PxReal d0 = MinMaxRadius + dp; + +// const PxReal d1 = max1 - min0; +// const PxReal d1 = projected1 + polyData1.mInternal.mRadius - projected0 + polyData0.mInternal.mRadius; +// const PxReal d1 = projected1 - projected0 + polyData1.mInternal.mRadius + polyData0.mInternal.mRadius; +// const PxReal d1 = projected1 - projected0 + MinMaxRadius; +// const PxReal d1 = axis.dot(tr1.p) - axis.dot(tr0.p) + MinMaxRadius; +// const PxReal d1 = axis.dot(tr1.p - tr0.p) + MinMaxRadius; +// const PxReal d1 = MinMaxRadius - axis.dot(delta); + const PxReal d1 = MinMaxRadius - dp; + + dmin = selectMin(d0, d1); + return; + } + + #ifdef USE_BOX_DATA + const PxVec3 localAxis0 = tr0.rotateTranspose(axis); + const PxVec3 localAxis1 = tr1.rotateTranspose(axis); + + const float dp = delta_c.dot(axis); + + float p0[3]; + BoxSupport(polyData0.mInternal.mExtents, localAxis0, p0); + float p1[3]; + BoxSupport(polyData1.mInternal.mExtents, localAxis1, p1); + + const float Radius0 = p0[0]*localAxis0.x + p0[1]*localAxis0.y + p0[2]*localAxis0.z; + const float Radius1 = p1[0]*localAxis1.x + p1[1]*localAxis1.y + p1[2]*localAxis1.z; + + const float MinRadius = selectMax(Radius0, polyData0.mInternal.mRadius); + const float MaxRadius = selectMax(Radius1, polyData1.mInternal.mRadius); + #else + const float dp = delta_c.dot(axis); + const float MinRadius = polyData0.mInternal.mRadius; + const float MaxRadius = polyData1.mInternal.mRadius; + #endif + const float MinMaxRadius = MaxRadius + MinRadius; + const float d0 = MinMaxRadius + dp; + const float d1 = MinMaxRadius - dp; + + dmin = selectMin(d0, d1); + } +#endif + +static PX_FORCE_INLINE bool testInternalObjects( const PxVec3& delta_c, const PxVec3& axis, + const PolygonalData& polyData0, const PolygonalData& polyData1, + const Cm::Matrix34& tr0, const Cm::Matrix34& tr1, + float dmin) +{ +#ifdef USE_BOX_DATA + const PxVec3 localAxis0 = tr0.rotateTranspose(axis); + const PxVec3 localAxis1 = tr1.rotateTranspose(axis); + + const float dp = delta_c.dot(axis); + + float p0[3]; + BoxSupport(polyData0.mInternal.mExtents, localAxis0, p0); + float p1[3]; + BoxSupport(polyData1.mInternal.mExtents, localAxis1, p1); + + const float Radius0 = p0[0]*localAxis0.x + p0[1]*localAxis0.y + p0[2]*localAxis0.z; + const float Radius1 = p1[0]*localAxis1.x + p1[1]*localAxis1.y + p1[2]*localAxis1.z; + + const float MinRadius = selectMax(Radius0, polyData0.mInternal.mRadius); + const float MaxRadius = selectMax(Radius1, polyData1.mInternal.mRadius); +#else + const float dp = delta_c.dot(axis); + const float MinRadius = polyData0.mInternal.mRadius; + const float MaxRadius = polyData1.mInternal.mRadius; +#endif + const float MinMaxRadius = MaxRadius + MinRadius; + const float d0 = MinMaxRadius + dp; + const float d1 = MinMaxRadius - dp; + + const float depth = selectMin(d0, d1); + if(depth>dmin) + return false; + return true; +} +#endif + +PX_FORCE_INLINE float PxcMultiplyAdd3x4(PxU32 i, const PxVec3& p0, const PxVec3& p1, const Cm::Matrix34& world) +{ + return (p1.x + p0.x) * world.m.column0[i] + (p1.y + p0.y) * world.m.column1[i] + (p1.z + p0.z) * world.m.column2[i] + 2.0f * world.p[i]; +} + +PX_FORCE_INLINE float PxcMultiplySub3x4(PxU32 i, const PxVec3& p0, const PxVec3& p1, const Cm::Matrix34& world) +{ + return (p1.x - p0.x) * world.m.column0[i] + (p1.y - p0.y) * world.m.column1[i] + (p1.z - p0.z) * world.m.column2[i]; +} + +static PX_FORCE_INLINE bool testNormal( const PxVec3& axis, PxReal min0, PxReal max0, + const PolygonalData& polyData1, + const Cm::Matrix34& m1to0, + const Cm::FastVertex2ShapeScaling& scaling1, + PxReal& depth, PxReal contactDistance) +{ + //The separating axis we want to test is a face normal of hull0 + PxReal min1, max1; + (polyData1.mProjectHull)(polyData1, axis, m1to0, scaling1, min1, max1); + + if(max0+contactDistance<min1 || max1+contactDistance<min0) + return false; + + const PxReal d0 = max0 - min1; + const PxReal d1 = max1 - min0; + depth = selectMin(d0, d1); + return true; +} + +static PX_FORCE_INLINE bool testSeparatingAxis( const PolygonalData& polyData0, const PolygonalData& polyData1, + const Cm::Matrix34& world0, const Cm::Matrix34& world1, + const Cm::FastVertex2ShapeScaling& scaling0, const Cm::FastVertex2ShapeScaling& scaling1, + const PxVec3& axis, PxReal& depth, PxReal contactDistance) +{ + PxReal min0, max0; + (polyData0.mProjectHull)(polyData0, axis, world0, scaling0, min0, max0); + + return testNormal(axis, min0, max0, polyData1, world1, scaling1, depth, contactDistance); +} + +static bool PxcSegmentAABBIntersect(const PxVec3& p0, const PxVec3& p1, + const PxVec3& minimum, const PxVec3& maximum, + const Cm::Matrix34& world) +{ + PxVec3 boxExtent, diff, dir; + PxReal fAWdU[3]; + + dir.x = PxcMultiplySub3x4(0, p0, p1, world); + boxExtent.x = maximum.x - minimum.x; + diff.x = (PxcMultiplyAdd3x4(0, p0, p1, world) - (maximum.x+minimum.x)); + fAWdU[0] = PxAbs(dir.x); + if(PxAbs(diff.x)> boxExtent.x + fAWdU[0]) return false; + + dir.y = PxcMultiplySub3x4(1, p0, p1, world); + boxExtent.y = maximum.y - minimum.y; + diff.y = (PxcMultiplyAdd3x4(1, p0, p1, world) - (maximum.y+minimum.y)); + fAWdU[1] = PxAbs(dir.y); + if(PxAbs(diff.y)> boxExtent.y + fAWdU[1]) return false; + + dir.z = PxcMultiplySub3x4(2, p0, p1, world); + boxExtent.z = maximum.z - minimum.z; + diff.z = (PxcMultiplyAdd3x4(2, p0, p1, world) - (maximum.z+minimum.z)); + fAWdU[2] = PxAbs(dir.z); + if(PxAbs(diff.z)> boxExtent.z + fAWdU[2]) return false; + + PxReal f; + f = dir.y * diff.z - dir.z*diff.y; if(PxAbs(f)>boxExtent.y*fAWdU[2] + boxExtent.z*fAWdU[1]) return false; + f = dir.z * diff.x - dir.x*diff.z; if(PxAbs(f)>boxExtent.x*fAWdU[2] + boxExtent.z*fAWdU[0]) return false; + f = dir.x * diff.y - dir.y*diff.x; if(PxAbs(f)>boxExtent.x*fAWdU[1] + boxExtent.y*fAWdU[0]) return false; + return true; +} + +#define EXPERIMENT + +/* +Edge culling can clearly be improved : in ConvexTest02 for example, edges don't even touch the other mesh sometimes. +*/ +static void PxcFindSeparatingAxes( SeparatingAxes& sa, const PxU32* PX_RESTRICT indices, PxU32 numPolygons, + const PolygonalData& polyData, + const Cm::Matrix34& world0, const PxPlane& plane, + const Cm::Matrix34& m0to1, const PxBounds3& aabb, PxReal contactDistance, + const Cm::FastVertex2ShapeScaling& scaling) +{ +// EdgeCache edgeCache; // PT: TODO: check this is actually useful + const PxVec3* PX_RESTRICT vertices = polyData.mVerts; + const Gu::HullPolygonData* PX_RESTRICT polygons = polyData.mPolygons; + const PxU8* PX_RESTRICT vrefsBase = polyData.mPolygonVertexRefs; + + while(numPolygons--) + { + //Get current polygon + const Gu::HullPolygonData& P = polygons[*indices++]; + const PxU8* PX_RESTRICT VData = vrefsBase + P.mVRef8; + + // Loop through polygon vertices == polygon edges + PxU32 numVerts = P.mNbVerts; + +#ifdef EXPERIMENT + PxVec3 p0,p1; + PxU8 VRef0 = VData[0]; + p0 = scaling * vertices[VRef0]; + bool b0 = plane.distance(p0) <= contactDistance; +#endif + + for(PxU32 j = 0; j < numVerts; j++) + { + PxU32 j1 = j+1; + if(j1 >= numVerts) j1 = 0; + +#ifndef EXPERIMENT + PxU8 VRef0 = VData[j]; +#endif + PxU8 VRef1 = VData[j1]; + +// Ps::order(VRef0, VRef1); //make sure edge (a,b) == edge (b,a) +// if (edgeCache.isInCache(VRef0, VRef1)) +// continue; + + //transform points //TODO: once this works we could transform plan instead, that is more efficient!! + +#ifdef EXPERIMENT + p1 = scaling * vertices[VRef1]; +#else + const PxVec3 p0 = scaling * vertices[VRef0]; + const PxVec3 p1 = scaling * vertices[VRef1]; +#endif + + // Cheap but effective culling! +#ifdef EXPERIMENT + bool b1 = plane.distance(p1) <= contactDistance; + if(b0 || b1) +#else + if(plane.signedDistanceHessianNormalForm(p0) <= contactDistance || + plane.signedDistanceHessianNormalForm(p1) <= contactDistance) +#endif + { + if(PxcSegmentAABBIntersect(p0, p1, aabb.minimum, aabb.maximum, m0to1)) + { + // Create current edge. We're only interested in different edge directions so we normalize. + const PxVec3 currentEdge = world0.rotate(p0 - p1).getNormalized(); + sa.addAxis(currentEdge); + } + } +#ifdef EXPERIMENT + VRef0 = VRef1; + p0 = p1; + b0 = b1; +#endif + } + } +} + +static bool PxcTestFacesSepAxesBackface(const PolygonalData& polyData0, const PolygonalData& polyData1, + const Cm::Matrix34& world0, const Cm::Matrix34& world1, + const Cm::FastVertex2ShapeScaling& scaling0, const Cm::FastVertex2ShapeScaling& scaling1, + const Cm::Matrix34& m1to0, const PxVec3& delta, + PxReal& dmin, PxVec3& sep, PxU32& id, PxU32* PX_RESTRICT indices_, PxU32& numIndices, + PxReal contactDistance, float toleranceLength +#ifdef TEST_INTERNAL_OBJECTS + , const PxVec3& worldDelta +#endif + ) +{ + PX_UNUSED(toleranceLength); // Only used in Debug + id = PX_INVALID_U32; + PxU32* indices = indices_; + + const PxU32 num = polyData0.mNbPolygons; + const PxVec3* PX_RESTRICT vertices = polyData0.mVerts; + const Gu::HullPolygonData* PX_RESTRICT polygons = polyData0.mPolygons; + + //transform delta from hull0 shape into vertex space: + const PxVec3 vertSpaceDelta = scaling0 % delta; + + // PT: prefetch polygon data + { + const PxU32 dataSize = num*sizeof(Gu::HullPolygonData); + for(PxU32 offset=0; offset < dataSize; offset+=128) + Ps::prefetchLine(polygons, offset); + } + + for(PxU32 i=0; i < num; i++) + { + const Gu::HullPolygonData& P = polygons[i]; + const PxPlane& PL = P.mPlane; + + // Do backface-culling + if(PL.n.dot(vertSpaceDelta) < 0.0f) + continue; + + //normals transform by inverse transpose: (and transpose(skew) == skew as its symmetric) + PxVec3 shapeSpaceNormal = scaling0 % PL.n; + //renormalize: (Arr!) + const PxReal magnitude = shapeSpaceNormal.normalize(); // PT: We need to find a way to skip this normalize + +#ifdef TEST_INTERNAL_OBJECTS + +/* +const PxVec3 worldNormal_ = world0.rotate(shapeSpaceNormal); +PxReal d0; +bool test0 = PxcTestSeparatingAxis(polyData0, polyData1, world0, world1, scaling0, scaling1, worldNormal_, d0, contactDistance); + +PxReal d1; +const float invMagnitude0 = 1.0f / magnitude; +bool test1 = PxcTestNormal(shapeSpaceNormal, P.getMin(vertices) * invMagnitude0, P.getMax() * invMagnitude0, polyData1, m1to0, scaling1, d1, contactDistance); + +PxReal d2; +testInternalObjects_(worldDelta, worldNormal_, polyData0, polyData1, world0, world1, d2, contactDistance); +*/ + + const PxVec3 worldNormal = world0.rotate(shapeSpaceNormal); + if(!testInternalObjects(worldDelta, worldNormal, polyData0, polyData1, world0, world1, dmin)) + { + #if PX_DEBUG + PxReal d; + const float invMagnitude = 1.0f / magnitude; + if(testNormal(shapeSpaceNormal, P.getMin(vertices) * invMagnitude, P.getMax() * invMagnitude, polyData1, m1to0, scaling1, d, contactDistance)) //note how we scale scalars by skew magnitude change as we do plane d-s. + { + PX_ASSERT(d + testInternalObjectsEpsilon*toleranceLength >= dmin); + } + #endif + continue; + } +#endif + + *indices++ = i; + + //////////////////// + /* + //gotta transform minimum and maximum from vertex to shape space! + //I think they transform like the 'd' of the plane, basically by magnitude division! + //unfortunately I am not certain of that, so let's convert them to a point in vertex space, transform that, and then convert back to a plane d. + + //let's start by transforming the plane's d: + //a point on the plane: + + PxVec3 vertSpaceDPoint = PL.normal * -PL.d; + + //make sure this is on the plane: + PxReal distZero = PL.signedDistanceHessianNormalForm(vertSpaceDPoint); //should be zero + + //transform: + + PxVec3 shapeSpaceDPoint = cache.mVertex2ShapeSkew[skewIndex] * vertSpaceDPoint; + + //make into a d offset again by projecting along the plane: + PxcPlane shapeSpacePlane(shapeSpaceNormal, shapeSpaceDPoint); + + //see what D is!! + + + //NOTE: for boxes scale[0] is always id so this is all redundant. Hopefully for convex convex it will become useful! + */ + + //////////////////// + + PxReal d; + const float invMagnitude = 1.0f / magnitude; + if(!testNormal(shapeSpaceNormal, P.getMin(vertices) * invMagnitude, P.getMax() * invMagnitude, polyData1, m1to0, scaling1, d, contactDistance)) //note how we scale scalars by skew magnitude change as we do plane d-s. + return false; + + if(d < dmin) + { +#ifdef TEST_INTERNAL_OBJECTS + sep = worldNormal; +#else + sep = world0.rotate(shapeSpaceNormal); +#endif + dmin = d; + id = i; + } + } + + numIndices = PxU32(indices - indices_); + + PX_ASSERT(id!=PX_INVALID_U32); //Should never happen with this version + + return true; +} + +// PT: isolating this piece of code allows us to better see what happens in PIX. For some reason it looks +// like isolating it creates *less* LHS than before, but I don't understand why. There's still a lot of +// bad stuff there anyway +//PX_FORCE_INLINE +static void prepareData(PxPlane& witnessPlane0, PxPlane& witnessPlane1, + PxBounds3& aabb0, PxBounds3& aabb1, + const PxBounds3& hullBounds0, const PxBounds3& hullBounds1, + const PxPlane& vertSpacePlane0, const PxPlane& vertSpacePlane1, + const Cm::FastVertex2ShapeScaling& scaling0, const Cm::FastVertex2ShapeScaling& scaling1, + const Cm::Matrix34& m0to1, const Cm::Matrix34& m1to0, + PxReal contactDistance + ) +{ + scaling0.transformPlaneToShapeSpace(vertSpacePlane0.n, vertSpacePlane0.d, witnessPlane0.n, witnessPlane0.d); + scaling1.transformPlaneToShapeSpace(vertSpacePlane1.n, vertSpacePlane1.d, witnessPlane1.n, witnessPlane1.d); + + //witnessPlane0 = witnessPlane0.getTransformed(m0to1); + //witnessPlane1 = witnessPlane1.getTransformed(m1to0); + const PxVec3 newN0 = m0to1.rotate(witnessPlane0.n); + witnessPlane0 = PxPlane(newN0, witnessPlane0.d - m0to1.p.dot(newN0)); + const PxVec3 newN1 = m1to0.rotate(witnessPlane1.n); + witnessPlane1 = PxPlane(newN1, witnessPlane1.d - m1to0.p.dot(newN1)); + + aabb0 = hullBounds0; + aabb1 = hullBounds1; + + //gotta inflate // PT: perfect LHS recipe here.... + const PxVec3 inflate(contactDistance); + aabb0.minimum -= inflate; + aabb1.minimum -= inflate; + aabb0.maximum += inflate; + aabb1.maximum += inflate; +} + +static bool PxcBruteForceOverlapBackface( const PxBounds3& hullBounds0, const PxBounds3& hullBounds1, + const PolygonalData& polyData0, const PolygonalData& polyData1, + const Cm::Matrix34& world0, const Cm::Matrix34& world1, + const Cm::FastVertex2ShapeScaling& scaling0, const Cm::FastVertex2ShapeScaling& scaling1, + const Cm::Matrix34& m0to1, const Cm::Matrix34& m1to0, const PxVec3& delta, + PxU32& id0, PxU32& id1, + PxReal& depth, PxVec3& sep, PxcSepAxisType& code, PxReal contactDistance, float toleranceLength) +{ + const PxVec3 localDelta0 = world0.rotateTranspose(delta); + PxU32* PX_RESTRICT indices0 = reinterpret_cast<PxU32*>(PxAlloca(polyData0.mNbPolygons*sizeof(PxU32))); + + PxU32 numIndices0; + PxReal dmin0 = PX_MAX_REAL; + PxVec3 vec0; + if(!PxcTestFacesSepAxesBackface(polyData0, polyData1, world0, world1, scaling0, scaling1, m1to0, localDelta0, dmin0, vec0, id0, indices0, numIndices0, contactDistance, toleranceLength +#ifdef TEST_INTERNAL_OBJECTS + , -delta +#endif + )) + return false; + + const PxVec3 localDelta1 = world1.rotateTranspose(delta); + + PxU32* PX_RESTRICT indices1 = reinterpret_cast<PxU32*>(PxAlloca(polyData1.mNbPolygons*sizeof(PxU32))); + + PxU32 numIndices1; + PxReal dmin1 = PX_MAX_REAL; + PxVec3 vec1; + if(!PxcTestFacesSepAxesBackface(polyData1, polyData0, world1, world0, scaling1, scaling0, m0to1, -localDelta1, dmin1, vec1, id1, indices1, numIndices1, contactDistance, toleranceLength +#ifdef TEST_INTERNAL_OBJECTS + , delta +#endif + )) + return false; + + PxReal dmin = dmin0; + PxVec3 vec = vec0; + code = SA_NORMAL0; + + if(dmin1 < dmin) + { + dmin = dmin1; + vec = vec1; + code = SA_NORMAL1; + } + + PX_ASSERT(id0!=PX_INVALID_U32); + PX_ASSERT(id1!=PX_INVALID_U32); + + // Brute-force find a separating axis + SeparatingAxes mSA0; + SeparatingAxes mSA1; + mSA0.reset(); + mSA1.reset(); + PxPlane witnessPlane0; + PxPlane witnessPlane1; + PxBounds3 aabb0, aabb1; + + prepareData(witnessPlane0, witnessPlane1, + aabb0, aabb1, + hullBounds0, hullBounds1, + polyData0.mPolygons[id0].mPlane, + polyData1.mPolygons[id1].mPlane, + scaling0, scaling1, + m0to1, m1to0, + contactDistance); + + // Find possibly separating axes + PxcFindSeparatingAxes(mSA0, indices0, numIndices0, polyData0, world0, witnessPlane1, m0to1, aabb1, contactDistance, scaling0); + PxcFindSeparatingAxes(mSA1, indices1, numIndices1, polyData1, world1, witnessPlane0, m1to0, aabb0, contactDistance, scaling1); +// PxcFindSeparatingAxes(context.mSA0, &id0, 1, hull0, world0, witnessPlane1, m0to1, aabbMin1, aabbMax1); +// PxcFindSeparatingAxes(context.mSA1, &id1, 1, hull1, world1, witnessPlane0, m1to0, aabbMin0, aabbMax0); + + const PxU32 numEdges0 = mSA0.getNumAxes(); + const PxVec3* PX_RESTRICT edges0 = mSA0.getAxes(); + + const PxU32 numEdges1 = mSA1.getNumAxes(); + const PxVec3* PX_RESTRICT edges1 = mSA1.getAxes(); + + // Worst case = convex test 02 with big meshes: 23 & 23 edges => 23*23 = 529 tests! + // printf("%d - %d\n", NbEdges0, NbEdges1); // maximum = ~20 in test scenes. + + for(PxU32 i=0; i < numEdges0; i++) + { + const PxVec3& edge0 = edges0[i]; + for(PxU32 j=0; j < numEdges1; j++) + { + const PxVec3& edge1 = edges1[j]; + + PxVec3 sepAxis = edge0.cross(edge1); + if(!Ps::isAlmostZero(sepAxis)) + { + sepAxis = sepAxis.getNormalized(); + +#ifdef TEST_INTERNAL_OBJECTS + if(!testInternalObjects(-delta, sepAxis, polyData0, polyData1, world0, world1, dmin)) + { + #if PX_DEBUG + PxReal d; + if(testSeparatingAxis(polyData0, polyData1, world0, world1, scaling0, scaling1, sepAxis, d, contactDistance)) + { + PX_ASSERT(d + testInternalObjectsEpsilon*toleranceLength >= dmin); + } + #endif + continue; + } +#endif + PxReal d; + if(!testSeparatingAxis(polyData0, polyData1, world0, world1, scaling0, scaling1, sepAxis, d, contactDistance)) + return false; + + if(d<dmin) + { + dmin = d; + vec = sepAxis; + code = SA_EE; + } + } + } + } + + depth = dmin; + sep = vec; + + return true; +} + +static bool GuTestFacesSepAxesBackfaceRoughPass( + const PolygonalData& polyData0, const PolygonalData& polyData1, + const Cm::Matrix34& world0, const Cm::Matrix34& world1, + const Cm::FastVertex2ShapeScaling& scaling0, const Cm::FastVertex2ShapeScaling& scaling1, + const Cm::Matrix34& m1to0, const PxVec3& /*witness*/, const PxVec3& delta, + PxReal& dmin, PxVec3& sep, PxU32& id, + PxReal contactDistance, float toleranceLength +#ifdef TEST_INTERNAL_OBJECTS + , const PxVec3& worldDelta +#endif + ) +{ + PX_UNUSED(toleranceLength); // Only used in Debug + id = PX_INVALID_U32; + + const PxU32 num = polyData0.mNbPolygons; + const Gu::HullPolygonData* PX_RESTRICT polygons = polyData0.mPolygons; + const PxVec3* PX_RESTRICT vertices = polyData0.mVerts; + + //transform delta from hull0 shape into vertex space: + const PxVec3 vertSpaceDelta = scaling0 % delta; + + for(PxU32 i=0; i < num; i++) + { + const Gu::HullPolygonData& P = polygons[i]; + const PxPlane& PL = P.mPlane; + + if(PL.n.dot(vertSpaceDelta) < 0.0f) + continue; //backface-cull + + PxVec3 shapeSpaceNormal = scaling0 % PL.n; //normals transform with inverse transpose + //renormalize: (Arr!) + const PxReal magnitude = shapeSpaceNormal.normalize(); // PT: We need to find a way to skip this normalize + +#ifdef TEST_INTERNAL_OBJECTS + const PxVec3 worldNormal = world0.rotate(shapeSpaceNormal); + if(!testInternalObjects(worldDelta, worldNormal, polyData0, polyData1, world0, world1, dmin)) + { + #if PX_DEBUG + PxReal d; + const float invMagnitude = 1.0f / magnitude; + if(testNormal(shapeSpaceNormal, P.getMin(vertices) * invMagnitude, P.getMax() * invMagnitude, /*hull1,*/ polyData1, m1to0, scaling1, d, contactDistance)) //note how we scale scalars by skew magnitude change as we do plane d-s. + { + PX_ASSERT(d + testInternalObjectsEpsilon*toleranceLength >= dmin); + } + #endif + continue; + } +#endif + + PxReal d; + const float invMagnitude = 1.0f / magnitude; + if(!testNormal(shapeSpaceNormal, P.getMin(vertices) * invMagnitude, P.getMax() * invMagnitude, /*hull1,*/ polyData1, m1to0, scaling1, d, contactDistance)) //note how we scale scalars by skew magnitude change as we do plane d-s. + return false; + + if(d < dmin) + { +#ifdef TEST_INTERNAL_OBJECTS + sep = worldNormal; +#else + sep = world0.rotate(shapeSpaceNormal); +#endif + dmin = d; + id = i; + } + } + + PX_ASSERT(id!=PX_INVALID_U32); //Should never happen with this version + + return true; +} + +static bool GuBruteForceOverlapBackfaceRoughPass( const PolygonalData& polyData0, const PolygonalData& polyData1, + const Cm::Matrix34& world0, const Cm::Matrix34& world1, + const Cm::FastVertex2ShapeScaling& scaling0, const Cm::FastVertex2ShapeScaling& scaling1, + const Cm::Matrix34& m0to1, const Cm::Matrix34& m1to0, const PxVec3& delta, + PxU32& id0, PxU32& id1, + PxReal& depth, PxVec3& sep, PxcSepAxisType& code, PxReal contactDistance, PxReal toleranceLength) +{ + PxReal dmin0 = PX_MAX_REAL; + PxReal dmin1 = PX_MAX_REAL; + PxVec3 vec0, vec1; + + const PxVec3 localDelta0 = world0.rotateTranspose(delta); + const PxVec3 localCenter1in0 = m1to0.transform(polyData1.mCenter); + if(!GuTestFacesSepAxesBackfaceRoughPass(polyData0, polyData1, world0, world1, scaling0, scaling1, m1to0, localCenter1in0, localDelta0, dmin0, vec0, id0, contactDistance, toleranceLength +#ifdef TEST_INTERNAL_OBJECTS + , -delta +#endif + )) + return false; + + const PxVec3 localDelta1 = world1.rotateTranspose(delta); + const PxVec3 localCenter0in1 = m0to1.transform(polyData0.mCenter); + if(!GuTestFacesSepAxesBackfaceRoughPass(polyData1, polyData0, world1, world0, scaling1, scaling0, m0to1, localCenter0in1, -localDelta1, dmin1, vec1, id1, contactDistance, toleranceLength +#ifdef TEST_INTERNAL_OBJECTS + , delta +#endif + )) + return false; + + PxReal dmin = dmin0; + PxVec3 vec = vec0; + code = SA_NORMAL0; + + if(dmin1 < dmin) + { + dmin = dmin1; + vec = vec1; + code = SA_NORMAL1; + } + + PX_ASSERT(id0!=PX_INVALID_U32); + PX_ASSERT(id1!=PX_INVALID_U32); + + depth = dmin; + sep = vec; + + return true; +} + +static bool GuContactHullHull( const PolygonalData& polyData0, const PolygonalData& polyData1, + const PxBounds3& hullBounds0, const PxBounds3& hullBounds1, + const PxTransform& transform0, const PxTransform& transform1, + const NarrowPhaseParams& params, ContactBuffer& contactBuffer, + const Cm::FastVertex2ShapeScaling& scaling0, const Cm::FastVertex2ShapeScaling& scaling1, + bool idtScale0, bool idtScale1); + +namespace physx +{ + +namespace Gu +{ +// Box-convex contact generation +// this can no longer share code with convex-convex because that case needs scaling for both shapes, while this only needs it for one, which may be significant perf wise. +// +// PT: duplicating the full convex-vs-convex codepath is a lot worse. Look at it this way: if scaling is really "significant perf wise" then we made the whole convex-convex +// codepath significantly slower, and this is a lot more important than just box-vs-convex. The proper approach is to share the code and make sure scaling is NOT a perf hit. +// PT: please leave this function in the same translation unit as PxcContactHullHull. + +bool contactBoxConvex(GU_CONTACT_METHOD_ARGS) +{ + PX_UNUSED(renderOutput); + PX_UNUSED(cache); + + const PxBoxGeometry& shapeBox = shape0.get<const PxBoxGeometry>(); + + Cm::FastVertex2ShapeScaling idtScaling; + + const PxBounds3 boxBounds(-shapeBox.halfExtents, shapeBox.halfExtents); + + PolygonalData polyData0; + PolygonalBox polyBox(shapeBox.halfExtents); + polyBox.getPolygonalData(&polyData0); + + /////// + + Cm::FastVertex2ShapeScaling convexScaling; + PxBounds3 convexBounds; + PolygonalData polyData1; + const bool idtScale = getConvexData(shape1, convexScaling, convexBounds, polyData1); + + return GuContactHullHull( polyData0, polyData1, boxBounds, convexBounds, + transform0, transform1, params, contactBuffer, + idtScaling, convexScaling, true, idtScale); +} + +// PT: please leave this function in the same translation unit as PxcContactHullHull. +bool contactConvexConvex(GU_CONTACT_METHOD_ARGS) +{ + PX_UNUSED(cache); + PX_UNUSED(renderOutput); + + Cm::FastVertex2ShapeScaling scaling0, scaling1; + PxBounds3 convexBounds0, convexBounds1; + PolygonalData polyData0, polyData1; + const bool idtScale0 = getConvexData(shape0, scaling0, convexBounds0, polyData0); + const bool idtScale1 = getConvexData(shape1, scaling1, convexBounds1, polyData1); + + return GuContactHullHull( polyData0, polyData1, convexBounds0, convexBounds1, + transform0, transform1, params, contactBuffer, + scaling0, scaling1, idtScale0, idtScale1); +} +}//Gu +}//physx + +static bool GuContactHullHull( const PolygonalData& polyData0, const PolygonalData& polyData1, + const PxBounds3& hullBounds0, const PxBounds3& hullBounds1, + const PxTransform& transform0, const PxTransform& transform1, + const NarrowPhaseParams& params, ContactBuffer& contactBuffer, + const Cm::FastVertex2ShapeScaling& scaling0, const Cm::FastVertex2ShapeScaling& scaling1, + bool idtScale0, bool idtScale1) +{ + // Compute matrices + const Cm::Matrix34 world0(transform0); + const Cm::Matrix34 world1(transform1); + + const PxVec3 worldCenter0 = world0.transform(polyData0.mCenter); + const PxVec3 worldCenter1 = world1.transform(polyData1.mCenter); + + const PxVec3 deltaC = worldCenter1 - worldCenter0; + + PxReal depth; + + /////////////////////////////////////////////////////////////////////////// + + // Early-exit test: quickly discard obviously non-colliding cases. + // PT: we used to skip this when objects were touching, which provided a small speedup (saving 2 full hull projections). + // We may want to add this back at some point in the future, if possible. + if(true) //AM: now we definitely want to test the cached axis to get the depth value for the cached axis, even if we had a contact before. + { + if(!testSeparatingAxis(polyData0, polyData1, world0, world1, scaling0, scaling1, deltaC, depth, params.mContactDistance)) + //there was no contact previously and we reject using the same prev. axis. + return false; + } + + /////////////////////////////////////////////////////////////////////////// + + // Compute relative transforms + PxTransform t0to1 = transform1.transformInv(transform0); + PxTransform t1to0 = transform0.transformInv(transform1); + + PxU32 c0(0x7FFF); + PxU32 c1(0x7FFF); + PxVec3 worldNormal; + + const Cm::Matrix34 m0to1(t0to1); + const Cm::Matrix34 m1to0(t1to0); + +#ifdef CONVEX_CONVEX_ROUGH_FIRST_PASS + // PT: it is a bad idea to skip the rough pass, for two reasons: + // 1) performance. The rough pass is obviously a lot faster. + // 2) stability. The "skipIt" optimization relies on the rough pass being present to catch the cases where it fails. If you disable the rough pass + // "skipIt" can skip the whole work, contacts get lost, and we never "try again" ==> explosions +// bool TryRoughPass = (contactDistance == 0.0f); //Rough first pass doesn't work with dist based for some reason. + for(int TryRoughPass = 1 /*gEnableOptims*/; 0 <= TryRoughPass; --TryRoughPass) + { +#endif + + { + PxcSepAxisType code; + PxU32 id0, id1; + //PxReal depth; +#ifdef CONVEX_CONVEX_ROUGH_FIRST_PASS + bool Status; + if(TryRoughPass) + { + Status = GuBruteForceOverlapBackfaceRoughPass(polyData0, polyData1, world0, world1, scaling0, scaling1, m0to1, m1to0, deltaC, id0, id1, depth, worldNormal, code, params.mContactDistance, params.mToleranceLength); + } + else + { + Status = PxcBruteForceOverlapBackface( +// hull0, hull1, + hullBounds0, hullBounds1, + polyData0, polyData1, world0, world1, scaling0, scaling1, m0to1, m1to0, deltaC, id0, id1, depth, worldNormal, code, params.mContactDistance, params.mToleranceLength); + } + + if(!Status) +#else + if(!PxcBruteForceOverlapBackface( +// hull0, hull1, + hullBounds0, hullBounds1, + polyData0, polyData1, + world0, world1, scaling0, scaling1, m0to1, m1to0, deltaC, id0, id1, depth, worldNormal, code, contactDistance)) +#endif + return false; + + if(deltaC.dot(worldNormal) < 0.0f) + worldNormal = -worldNormal; + +/* +worldNormal = -partialSep; +depth = -partialDepth; +code = SA_EE; +*/ + + if(code==SA_NORMAL0) + { + c0 = id0; + c1 = (polyData1.mSelectClosestEdgeCB)(polyData1, scaling1, world1.rotateTranspose(-worldNormal)); + } + else if(code==SA_NORMAL1) + { + c0 = (polyData0.mSelectClosestEdgeCB)(polyData0, scaling0, world0.rotateTranspose(worldNormal)); + c1 = id1; + } + else if(code==SA_EE) + { + c0 = (polyData0.mSelectClosestEdgeCB)(polyData0, scaling0, world0.rotateTranspose(worldNormal)); + c1 = (polyData1.mSelectClosestEdgeCB)(polyData1, scaling1, world1.rotateTranspose(-worldNormal)); + } + } + + const Gu::HullPolygonData& HP0 = polyData0.mPolygons[c0]; + const Gu::HullPolygonData& HP1 = polyData1.mPolygons[c1]; + // PT: prefetching those guys saves ~600.000 cycles in convex-convex benchmark + Ps::prefetchLine(&HP0.mPlane); + Ps::prefetchLine(&HP1.mPlane); + + //ok, we have a new depth value. convert to real distance. +// PxReal separation = -depth; //depth was either computed in initial cached-axis check, or better, when skipIt was false, in sep axis search. +// if (separation < 0.0f) +// separation = 0.0f; //we don't want to store penetration values. + const PxReal separation = fsel(depth, 0.0f, -depth); + + PxVec3 worldNormal0; + PX_ALIGN(16, PxPlane) shapeSpacePlane0; + if(idtScale0) + { + V4StoreA(V4LoadU(&HP0.mPlane.n.x), &shapeSpacePlane0.n.x); + worldNormal0 = world0.rotate(HP0.mPlane.n); + } + else + { + scaling0.transformPlaneToShapeSpace(HP0.mPlane.n,HP0.mPlane.d,shapeSpacePlane0.n,shapeSpacePlane0.d); + worldNormal0 = world0.rotate(shapeSpacePlane0.n); + } + + PxVec3 worldNormal1; + PX_ALIGN(16, PxPlane) shapeSpacePlane1; + if(idtScale1) + { + V4StoreA(V4LoadU(&HP1.mPlane.n.x), &shapeSpacePlane1.n.x); + worldNormal1 = world1.rotate(HP1.mPlane.n); + } + else + { + scaling1.transformPlaneToShapeSpace(HP1.mPlane.n,HP1.mPlane.d,shapeSpacePlane1.n,shapeSpacePlane1.d); + worldNormal1 = world1.rotate(shapeSpacePlane1.n); + } + + Ps::IntBool flag; + { + const PxReal d0 = PxAbs(worldNormal0.dot(worldNormal)); + const PxReal d1 = PxAbs(worldNormal1.dot(worldNormal)); + bool f = d0>d1; //which face normal is the separating axis closest to. + flag = f; + } + +////////////////////NEW DIST HANDLING////////////////////// + PX_ASSERT(separation >= 0.0f); //be sure this got fetched somewhere in the chaos above. + + const PxReal cCCDEpsilon = params.mMeshContactMargin; + const PxReal contactGenPositionShift = separation + cCCDEpsilon; //if we're at a distance, shift so we're in penetration. + + const PxVec3 contactGenPositionShiftVec = worldNormal * -contactGenPositionShift; //shift one of the bodies this distance toward the other just for Pierre's contact generation. Then the bodies should be penetrating exactly by MIN_SEPARATION_FOR_PENALTY - ideal conditions for this contact generator. + + //note: for some reason this has to change sign! + + //this will make contact gen always generate contacts at about MSP. Shift them back to the true real distance, and then to a solver compliant distance given that + //the solver converges to MSP penetration, while we want it to converge to 0 penetration. + //to real distance: + + //The system: We always shift convex 0 (arbitrary). If the contact is attached to convex 0 then we will need to shift the contact point, otherwise not. + const PxVec3 newp = world0.p - contactGenPositionShiftVec; + const Cm::Matrix34 world0_Tweaked(world0.m.column0, world0.m.column1, world0.m.column2, newp); + PxTransform shifted0(newp, transform0.q); + + t0to1 = transform1.transformInv(shifted0); + t1to0 = shifted0.transformInv(transform1); + Cm::Matrix34 m0to1_Tweaked; + Cm::Matrix34 m1to0_Tweaked; + m0to1_Tweaked.set(t0to1.q); m0to1_Tweaked.p = t0to1.p; + m1to0_Tweaked.set(t1to0.q); m1to0_Tweaked.p = t1to0.p; + +////////////////////////////////////////////////// + //pretransform convex polygon if we have scaling! + PxVec3* scaledVertices0; + PxU8* stackIndices0; + GET_SCALEX_CONVEX(scaledVertices0, stackIndices0, idtScale0, HP0.mNbVerts, scaling0, polyData0.mVerts, polyData0.getPolygonVertexRefs(HP0)) + + //pretransform convex polygon if we have scaling! + PxVec3* scaledVertices1; + PxU8* stackIndices1; + GET_SCALEX_CONVEX(scaledVertices1, stackIndices1, idtScale1, HP1.mNbVerts, scaling1, polyData1.mVerts, polyData1.getPolygonVertexRefs(HP1)) + + // So we need to switch: + // - HP0, HP1 + // - scaledVertices0, scaledVertices1 + // - stackIndices0, stackIndices1 + // - world0, world1 + // - shapeSpacePlane0, shapeSpacePlane1 + // - worldNormal0, worldNormal1 + // - m0to1, m1to0 + // - true, false + const PxMat33 RotT0 = findRotationMatrixFromZ(shapeSpacePlane0.n); + const PxMat33 RotT1 = findRotationMatrixFromZ(shapeSpacePlane1.n); + + if(flag) + { + if(contactPolygonPolygonExt(HP0.mNbVerts, scaledVertices0, stackIndices0, world0_Tweaked, shapeSpacePlane0, RotT0, + HP1.mNbVerts, scaledVertices1, stackIndices1, world1, shapeSpacePlane1, RotT1, + worldNormal0, m0to1_Tweaked, m1to0_Tweaked, + PXC_CONTACT_NO_FACE_INDEX, PXC_CONTACT_NO_FACE_INDEX, + contactBuffer, + true, contactGenPositionShiftVec, contactGenPositionShift)) + return true; + } + else + { + if(contactPolygonPolygonExt(HP1.mNbVerts, scaledVertices1, stackIndices1, world1, shapeSpacePlane1, RotT1, + HP0.mNbVerts, scaledVertices0, stackIndices0, world0_Tweaked, shapeSpacePlane0, RotT0, + worldNormal1, m1to0_Tweaked, m0to1_Tweaked, + PXC_CONTACT_NO_FACE_INDEX, PXC_CONTACT_NO_FACE_INDEX, + contactBuffer, + false, contactGenPositionShiftVec, contactGenPositionShift)) + return true; + } + +#ifdef CONVEX_CONVEX_ROUGH_FIRST_PASS + } +#endif + return false; +} diff --git a/PhysX_3.4/Source/GeomUtils/src/contact/GuContactConvexMesh.cpp b/PhysX_3.4/Source/GeomUtils/src/contact/GuContactConvexMesh.cpp new file mode 100644 index 00000000..58142f46 --- /dev/null +++ b/PhysX_3.4/Source/GeomUtils/src/contact/GuContactConvexMesh.cpp @@ -0,0 +1,1449 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2016 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 "GuConvexUtilsInternal.h" +#include "GuInternal.h" +#include "GuContactPolygonPolygon.h" +#include "GuConvexEdgeFlags.h" +#include "GuSeparatingAxes.h" +#include "GuContactMethodImpl.h" +#include "GuContactBuffer.h" +#include "GuMidphaseInterface.h" +#include "GuConvexHelper.h" +#include "GuTriangleCache.h" +#include "GuHeightFieldUtil.h" +#include "GuEntityReport.h" +#include "GuGeometryUnion.h" +#include "GuIntersectionTriangleBox.h" +#include "CmUtils.h" +#include "PsAllocator.h" +#include "GuBox.h" +#include "PsFPU.h" + +using namespace physx; +using namespace Gu; +using namespace shdfnd::aos; +using namespace intrinsics; + +#define LOCAL_TOUCHED_TRIG_SIZE 192 + +//#define USE_TRIANGLE_NORMAL +#define TEST_INTERNAL_OBJECTS + +static PX_FORCE_INLINE void projectTriangle(const PxVec3& localSpaceDirection, const PxVec3* PX_RESTRICT triangle, PxReal& min1, PxReal& max1) +{ + const PxReal dp0 = triangle[0].dot(localSpaceDirection); + const PxReal dp1 = triangle[1].dot(localSpaceDirection); + min1 = selectMin(dp0, dp1); + max1 = selectMax(dp0, dp1); + + const PxReal dp2 = triangle[2].dot(localSpaceDirection); + min1 = selectMin(min1, dp2); + max1 = selectMax(max1, dp2); +} + +#ifdef TEST_INTERNAL_OBJECTS + +static PX_FORCE_INLINE void boxSupport(const float extents[3], const PxVec3& sv, float p[3]) +{ + const PxU32* iextents = reinterpret_cast<const PxU32*>(extents); + const PxU32* isv = reinterpret_cast<const PxU32*>(&sv); + PxU32* ip = reinterpret_cast<PxU32*>(p); + + ip[0] = iextents[0]|(isv[0]&PX_SIGN_BITMASK); + ip[1] = iextents[1]|(isv[1]&PX_SIGN_BITMASK); + ip[2] = iextents[2]|(isv[2]&PX_SIGN_BITMASK); +} + +#if PX_DEBUG +static const PxReal testInternalObjectsEpsilon = 1.0e-3f; +#endif + +static PX_FORCE_INLINE bool testInternalObjects(const PxVec3& localAxis0, + const PolygonalData& polyData0, + const PxVec3* PX_RESTRICT triangleInHullSpace, + float dmin) +{ + PxReal min1, max1; + projectTriangle(localAxis0, triangleInHullSpace, min1, max1); + + const float dp = polyData0.mCenter.dot(localAxis0); + + float p0[3]; + boxSupport(polyData0.mInternal.mExtents, localAxis0, p0); + const float Radius0 = p0[0]*localAxis0.x + p0[1]*localAxis0.y + p0[2]*localAxis0.z; + const float bestRadius = selectMax(Radius0, polyData0.mInternal.mRadius); + const PxReal min0 = dp - bestRadius; + const PxReal max0 = dp + bestRadius; + + const PxReal d0 = max0 - min1; + const PxReal d1 = max1 - min0; + + const float depth = selectMin(d0, d1); + if(depth>dmin) + return false; + return true; +} +#endif + +static PX_FORCE_INLINE bool testNormal( const PxVec3& sepAxis, PxReal min0, PxReal max0, + const PxVec3* PX_RESTRICT triangle, + PxReal& depth, PxReal contactDistance) +{ + PxReal min1, max1; + projectTriangle(sepAxis, triangle, min1, max1); + + if(max0+contactDistance<min1 || max1+contactDistance<min0) + return false; + + const PxReal d0 = max0 - min1; + const PxReal d1 = max1 - min0; + depth = selectMin(d0, d1); + return true; +} + +static PX_FORCE_INLINE bool testSepAxis(const PxVec3& sepAxis, + const PolygonalData& polyData0, + const PxVec3* PX_RESTRICT triangle, + const Cm::Matrix34& m0to1, + const Cm::FastVertex2ShapeScaling& convexScaling, + PxReal& depth, PxReal contactDistance) +{ + PxReal min0, max0; + (polyData0.mProjectHull)(polyData0, sepAxis, m0to1, convexScaling, min0, max0); + + PxReal min1, max1; + projectTriangle(sepAxis, triangle, min1, max1); + + if(max0+contactDistance < min1 || max1+contactDistance < min0) + return false; + + const PxReal d0 = max0 - min1; + const PxReal d1 = max1 - min0; + depth = selectMin(d0, d1); + return true; +} + +static bool testFacesSepAxesBackface( const PolygonalData& polyData0, + const Cm::Matrix34& /*world0*/, const Cm::Matrix34& /*world1*/, + const Cm::Matrix34& m0to1, const PxVec3& witness, + const PxVec3* PX_RESTRICT triangle, + const Cm::FastVertex2ShapeScaling& convexScaling, + PxU32& numHullIndices, + PxU32* hullIndices_, PxReal& dmin, PxVec3& sep, PxU32& id, PxReal contactDistance, + bool idtConvexScale + ) +{ + id = PX_INVALID_U32; + + const PxU32 numHullPolys = polyData0.mNbPolygons; + const HullPolygonData* PX_RESTRICT polygons = polyData0.mPolygons; + const PxVec3* PX_RESTRICT vertices = polyData0.mVerts; + const PxVec3& trans = m0to1.p; + { + PxU32* hullIndices = hullIndices_; + + // PT: when the center of one object is inside the other object (deep penetrations) this discards everything! + // PT: when this happens, the backup procedure is used to come up with good results anyway. + // PT: it's worth having a special codepath here for identity scales, to skip all the normalizes/division. Lot faster without. + + if(idtConvexScale) + { + for(PxU32 i=0; i<numHullPolys; i++) + { + const HullPolygonData& P = polygons[i]; + const PxPlane& PL = P.mPlane; + +#ifdef USE_TRIANGLE_NORMAL + if(PL.normal.dot(witness) > 0.0f) +#else + // ### this is dubious since the triangle center is likely to be very close to the hull, if not inside. Why not use the triangle normal? + if(PL.distance(witness) < 0.0f) +#endif + continue; //backface culled + + *hullIndices++ = i; + + const PxVec3 sepAxis = m0to1.rotate(PL.n); + const PxReal dp = sepAxis.dot(trans); + + PxReal d; + if(!testNormal(sepAxis, P.getMin(vertices) + dp, P.getMax() + dp, triangle, + d, contactDistance)) + return false; + + if(d < dmin) + { + dmin = d; + sep = sepAxis; + id = i; + } + } + } + else + { +#ifndef USE_TRIANGLE_NORMAL + //transform delta from hull0 shape into vertex space: + const PxVec3 vertSpaceWitness = convexScaling % witness; +#endif + for(PxU32 i=0; i<numHullPolys; i++) + { + const HullPolygonData& P = polygons[i]; + const PxPlane& PL = P.mPlane; + +#ifdef USE_TRIANGLE_NORMAL + if(PL.normal.dot(witness) > 0.0f) +#else + // ### this is dubious since the triangle center is likely to be very close to the hull, if not inside. Why not use the triangle normal? + if(PL.distance(vertSpaceWitness) < 0.0f) +#endif + continue; //backface culled + + //normals transform by inverse transpose: (and transpose(skew) == skew as its symmetric) + PxVec3 shapeSpaceNormal = convexScaling % PL.n; + //renormalize: (Arr!) + const PxReal magnitude = shapeSpaceNormal.normalize(); + + *hullIndices++ = i; + const PxVec3 sepAxis = m0to1.rotate(shapeSpaceNormal); + PxReal d; + const PxReal dp = sepAxis.dot(trans); + + const float oneOverM = 1.0f / magnitude; + if(!testNormal(sepAxis, P.getMin(vertices) * oneOverM + dp, P.getMax() * oneOverM + dp, triangle, + d, contactDistance)) + return false; + + if(d < dmin) + { + dmin = d; + sep = sepAxis; + id = i; + } + } + } + numHullIndices = PxU32(hullIndices - hullIndices_); + } + + // Backup + if(id == PX_INVALID_U32) + { + if(idtConvexScale) + { + for(PxU32 i=0; i<numHullPolys; i++) + { + const HullPolygonData& P = polygons[i]; + const PxPlane& PL = P.mPlane; + + const PxVec3 sepAxis = m0to1.rotate(PL.n); + const PxReal dp = sepAxis.dot(trans); + + PxReal d; + if(!testNormal(sepAxis, P.getMin(vertices) + dp, P.getMax() + dp, triangle, + d, contactDistance)) + return false; + + if(d < dmin) + { + dmin = d; + sep = sepAxis; + id = i; + } + hullIndices_[i] = i; + } + } + else + { + for(PxU32 i=0; i<numHullPolys; i++) + { + const HullPolygonData& P = polygons[i]; + const PxPlane& PL = P.mPlane; + + PxVec3 shapeSpaceNormal = convexScaling % PL.n; + //renormalize: (Arr!) + const PxReal magnitude = shapeSpaceNormal.normalize(); + + const PxVec3 sepAxis = m0to1.rotate(shapeSpaceNormal); + + PxReal d; + const PxReal dp = sepAxis.dot(trans); + + const float oneOverM = 1.0f / magnitude; + if(!testNormal(sepAxis, P.getMin(vertices) * oneOverM + dp, P.getMax() * oneOverM + dp, triangle, + d, contactDistance)) + return false; + + if(d < dmin) + { + dmin = d; + sep = sepAxis; + id = i; + } + hullIndices_[i] = i; + } + } + numHullIndices = numHullPolys; + } + return true; +} + +static PX_FORCE_INLINE bool edgeCulling(const PxPlane& plane, const PxVec3& p0, const PxVec3& p1, PxReal contactDistance) +{ + return plane.distance(p0)<=contactDistance || plane.distance(p1)<=contactDistance; +} + +static bool performEETests( + const PolygonalData& polyData0, + const PxU8 triFlags, + const Cm::Matrix34& m0to1, const Cm::Matrix34& m1to0, + const PxVec3* PX_RESTRICT triangle, + PxU32 numHullIndices, const PxU32* PX_RESTRICT hullIndices, + const PxPlane& localTriPlane, + const Cm::FastVertex2ShapeScaling& convexScaling, + PxVec3& vec, PxReal& dmin, PxReal contactDistance, PxReal toleranceLength, + PxU32 id0, PxU32 /*triangleIndex*/) +{ + PX_UNUSED(toleranceLength); // Only used in Debug + + // Cull candidate triangle edges vs to hull plane + PxU32 nbTriangleAxes = 0; + PxVec3 triangleAxes[3]; + { + const HullPolygonData& P = polyData0.mPolygons[id0]; + const PxPlane& vertSpacePlane = P.mPlane; + + const PxVec3 newN = m1to0.rotate(vertSpacePlane.n); + PxPlane hullWitness(convexScaling * newN, vertSpacePlane.d - m1to0.p.dot(newN)); //technically not a fully xformed plane, just use property of x|My == Mx|y for symmetric M. + + if((triFlags & ETD_CONVEX_EDGE_01) && edgeCulling(hullWitness, triangle[0], triangle[1], contactDistance)) + triangleAxes[nbTriangleAxes++] = (triangle[0] - triangle[1]); + + if((triFlags & ETD_CONVEX_EDGE_12) && edgeCulling(hullWitness, triangle[1], triangle[2], contactDistance)) + triangleAxes[nbTriangleAxes++] = (triangle[1] - triangle[2]); + + if((triFlags & ETD_CONVEX_EDGE_20) && edgeCulling(hullWitness, triangle[2], triangle[0], contactDistance)) + triangleAxes[nbTriangleAxes++] = (triangle[2] - triangle[0]); + } + + //PxcPlane vertexSpacePlane = localTriPlane.getTransformed(m1to0); + //vertexSpacePlane.normal = convexScaling * vertexSpacePlane.normal; //technically not a fully xformed plane, just use property of x|My == Mx|y for symmetric M. + const PxVec3 newN = m1to0.rotate(localTriPlane.n); + PxPlane vertexSpacePlane(convexScaling * newN, localTriPlane.d - m1to0.p.dot(newN)); + + const PxVec3* PX_RESTRICT hullVerts = polyData0.mVerts; + + SeparatingAxes SA; + SA.reset(); + + const PxU8* PX_RESTRICT vrefBase0 = polyData0.mPolygonVertexRefs; + const HullPolygonData* PX_RESTRICT polygons = polyData0.mPolygons; + while(numHullIndices--) + { + const HullPolygonData& P = polygons[*hullIndices++]; + const PxU8* PX_RESTRICT data = vrefBase0 + P.mVRef8; + + PxU32 numEdges = nbTriangleAxes; + const PxVec3* edges = triangleAxes; + + // TODO: cheap edge culling as in convex/convex! + while(numEdges--) + { + const PxVec3& currentPolyEdge = *edges++; + + // Loop through polygon vertices == polygon edges; + PxU32 numVerts = P.mNbVerts; + for(PxU32 j = 0; j < numVerts; j++) + { + PxU32 j1 = j+1; + if(j1>=numVerts) j1 = 0; + + const PxU32 VRef0 = data[j]; + const PxU32 VRef1 = data[j1]; + + if(edgeCulling(vertexSpacePlane, hullVerts[VRef0], hullVerts[VRef1], contactDistance)) + { + const PxVec3 currentHullEdge = m0to1.rotate(convexScaling * (hullVerts[VRef0] - hullVerts[VRef1])); //matrix mult is distributive! + + PxVec3 sepAxis = currentHullEdge.cross(currentPolyEdge); + if(!Ps::isAlmostZero(sepAxis)) + SA.addAxis(sepAxis.getNormalized()); + } + } + } + } + + dmin = PX_MAX_REAL; + PxU32 numAxes = SA.getNumAxes(); + const PxVec3* PX_RESTRICT axes = SA.getAxes(); + +#ifdef TEST_INTERNAL_OBJECTS + PxVec3 triangleInHullSpace[3]; + if(numAxes) + { + triangleInHullSpace[0] = m1to0.transform(triangle[0]); + triangleInHullSpace[1] = m1to0.transform(triangle[1]); + triangleInHullSpace[2] = m1to0.transform(triangle[2]); + } +#endif + + while(numAxes--) + { + const PxVec3& currentAxis = *axes++; + +#ifdef TEST_INTERNAL_OBJECTS + const PxVec3 localAxis0 = m1to0.rotate(currentAxis); + if(!testInternalObjects(localAxis0, polyData0, triangleInHullSpace, dmin)) + { + #if PX_DEBUG + PxReal dtest; + if(testSepAxis(currentAxis, polyData0, triangle, m0to1, convexScaling, dtest, contactDistance)) + { + PX_ASSERT(dtest + testInternalObjectsEpsilon*toleranceLength >= dmin); + } + #endif + continue; + } +#endif + + PxReal d; + if(!testSepAxis(currentAxis, polyData0, triangle, + m0to1, convexScaling, d, contactDistance)) + { + return false; + } + + if(d < dmin) + { + dmin = d; + vec = currentAxis; + } + } + return true; +} + +static bool triangleConvexTest( const PolygonalData& polyData0, + const PxU8 triFlags, + PxU32 index, const PxVec3* PX_RESTRICT localPoints, + const PxPlane& localPlane, + const PxVec3& groupCenterHull, + const Cm::Matrix34& world0, const Cm::Matrix34& world1, const Cm::Matrix34& m0to1, const Cm::Matrix34& m1to0, + const Cm::FastVertex2ShapeScaling& convexScaling, + PxReal contactDistance, PxReal toleranceLength, + PxVec3& groupAxis, PxReal& groupMinDepth, bool& faceContact, + bool idtConvexScale + ) +{ + PxU32 id0 = PX_INVALID_U32; + PxReal dmin0 = PX_MAX_REAL; + PxVec3 vec0; + + PxU32 numHullIndices = 0; + PxU32* PX_RESTRICT const hullIndices = reinterpret_cast<PxU32*>(PxAlloca(polyData0.mNbPolygons*sizeof(PxU32))); + + // PT: we test the hull normals first because they don't need any hull projection. If we can early exit thanks + // to those, we completely avoid all hull projections. + bool status = testFacesSepAxesBackface(polyData0, world0, world1, m0to1, groupCenterHull, localPoints, + convexScaling, numHullIndices, hullIndices, dmin0, vec0, id0, contactDistance, idtConvexScale); + if(!status) + return false; + + groupAxis = PxVec3(0); + groupMinDepth = PX_MAX_REAL; + + const PxReal eps = 0.0001f; // DE7748 + + //Test in mesh-space + PxVec3 sepAxis; + PxReal depth; + + { + // Test triangle normal + PxReal d; + if(!testSepAxis(localPlane.n, polyData0, localPoints, + m0to1, convexScaling, d, contactDistance)) + return false; + + if(d<dmin0+eps) +// if(d<dmin0) + { + depth = d; + sepAxis = localPlane.n; + faceContact = true; + } + else + { + depth = dmin0; + sepAxis = vec0; + faceContact = false; + } + } + + if(depth < groupMinDepth) + { + groupMinDepth = depth; + groupAxis = world1.rotate(sepAxis); + } + + if(!performEETests(polyData0, triFlags, m0to1, m1to0, + localPoints, + numHullIndices, hullIndices, localPlane, + convexScaling, sepAxis, depth, contactDistance, toleranceLength, + id0, index)) + return false; + + if(depth < groupMinDepth) + { + groupMinDepth = depth; + groupAxis = world1.rotate(sepAxis); + faceContact = false; + } + + return true; +} + +namespace +{ + struct ConvexMeshContactGeneration + { + Ps::InlineArray<PxU32,LOCAL_CONTACTS_SIZE>& mDelayedContacts; + Gu::CacheMap<Gu::CachedEdge, 128> mEdgeCache; + Gu::CacheMap<Gu::CachedVertex, 128> mVertCache; + + const Cm::Matrix34 m0to1; + const Cm::Matrix34 m1to0; + + PxVec3 mHullCenterMesh; + PxVec3 mHullCenterWorld; + + const PolygonalData& mPolyData0; + const Cm::Matrix34& mWorld0; + const Cm::Matrix34& mWorld1; + + const Cm::FastVertex2ShapeScaling& mConvexScaling; + + PxReal mContactDistance; + PxReal mToleranceLength; + bool mIdtMeshScale, mIdtConvexScale; + PxReal mCCDEpsilon; + const PxTransform& mTransform0; + const PxTransform& mTransform1; + ContactBuffer& mContactBuffer; + bool mAnyHits; + + ConvexMeshContactGeneration( + Ps::InlineArray<PxU32,LOCAL_CONTACTS_SIZE>& delayedContacts, + const PxTransform& t0to1, const PxTransform& t1to0, + const PolygonalData& polyData0, const Cm::Matrix34& world0, const Cm::Matrix34& world1, + const Cm::FastVertex2ShapeScaling& convexScaling, + PxReal contactDistance, + PxReal toleranceLength, + bool idtConvexScale, + PxReal cCCDEpsilon, + const PxTransform& transform0, const PxTransform& transform1, + ContactBuffer& contactBuffer + ); + + void processTriangle(const PxVec3* verts, PxU32 triangleIndex, PxU8 triFlags, const PxU32* vertInds); + void generateLastContacts(); + + bool generateContacts( + const PxPlane& localPlane, + const PxVec3* PX_RESTRICT localPoints, + const PxVec3& triCenter, PxVec3& groupAxis, + PxReal groupMinDepth, PxU32 index) const; + + private: + ConvexMeshContactGeneration& operator=(const ConvexMeshContactGeneration&); + }; + +// 17 entries. 1088/17 = 64 triangles in the local array +struct SavedContactData +{ + PxU32 mTriangleIndex; + PxVec3 mVerts[3]; + PxU32 mInds[3]; + PxVec3 mGroupAxis; + PxReal mGroupMinDepth; +}; +} + +ConvexMeshContactGeneration::ConvexMeshContactGeneration( + Ps::InlineArray<PxU32,LOCAL_CONTACTS_SIZE>& delayedContacts, + const PxTransform& t0to1, const PxTransform& t1to0, + const PolygonalData& polyData0, const Cm::Matrix34& world0, const Cm::Matrix34& world1, + const Cm::FastVertex2ShapeScaling& convexScaling, + PxReal contactDistance, + PxReal toleranceLength, + bool idtConvexScale, + PxReal cCCDEpsilon, + const PxTransform& transform0, const PxTransform& transform1, + ContactBuffer& contactBuffer +) : + mDelayedContacts(delayedContacts), + m0to1 (t0to1), + m1to0 (t1to0), + mPolyData0 (polyData0), + mWorld0 (world0), + mWorld1 (world1), + mConvexScaling (convexScaling), + mContactDistance(contactDistance), + mToleranceLength(toleranceLength), + mIdtConvexScale (idtConvexScale), + mCCDEpsilon (cCCDEpsilon), + mTransform0 (transform0), + mTransform1 (transform1), + mContactBuffer (contactBuffer) +{ + delayedContacts.forceSize_Unsafe(0); + mAnyHits = false; + + // Hull center in local space + const PxVec3& hullCenterLocal = mPolyData0.mCenter; + // Hull center in mesh space + mHullCenterMesh = m0to1.transform(hullCenterLocal); + // Hull center in world space + mHullCenterWorld = mWorld0.transform(hullCenterLocal); +} + +struct ConvexMeshContactGenerationCallback : MeshHitCallback<PxRaycastHit> +{ + ConvexMeshContactGeneration mGeneration; + const Cm::FastVertex2ShapeScaling& mMeshScaling; + const PxU8* PX_RESTRICT mExtraTrigData; + bool mIdtMeshScale; + const TriangleMesh* mMeshData; + const BoxPadded& mBox; + + ConvexMeshContactGenerationCallback( + Ps::InlineArray<PxU32,LOCAL_CONTACTS_SIZE>& delayedContacts, + const PxTransform& t0to1, const PxTransform& t1to0, + const PolygonalData& polyData0, const Cm::Matrix34& world0, const Cm::Matrix34& world1, + const TriangleMesh* meshData, + const PxU8* PX_RESTRICT extraTrigData, + const Cm::FastVertex2ShapeScaling& meshScaling, + const Cm::FastVertex2ShapeScaling& convexScaling, + PxReal contactDistance, + PxReal toleranceLength, + bool idtMeshScale, bool idtConvexScale, + PxReal cCCDEpsilon, + const PxTransform& transform0, const PxTransform& transform1, + ContactBuffer& contactBuffer, + const BoxPadded& box + ) : + MeshHitCallback<PxRaycastHit> (CallbackMode::eMULTIPLE), + mGeneration (delayedContacts, t0to1, t1to0, polyData0, world0, world1, convexScaling, contactDistance, toleranceLength, idtConvexScale, cCCDEpsilon, transform0, transform1, contactBuffer), + mMeshScaling (meshScaling), + mExtraTrigData (extraTrigData), + mIdtMeshScale (idtMeshScale), + mMeshData (meshData), + mBox (box) + { + } + + virtual PxAgain processHit( // all reported coords are in mesh local space including hit.position + const PxRaycastHit& hit, const PxVec3& v0, const PxVec3& v1, const PxVec3& v2, PxReal&, const PxU32* vinds) + { + // PT: this one is safe because incoming vertices from midphase are always safe to V4Load (by design) + // PT: TODO: is this test really needed? Not done in midphase already? + if(!intersectTriangleBox(mBox, v0, v1, v2)) + return true; + + PxVec3 verts[3]; + getScaledVertices(verts, v0, v1, v2, mIdtMeshScale, mMeshScaling); + + const PxU32 triangleIndex = hit.faceIndex; + + const PxU8 extraData = getConvexEdgeFlags(mExtraTrigData, triangleIndex); + mGeneration.processTriangle(verts, triangleIndex, extraData, vinds); + return true; + } + +protected: + ConvexMeshContactGenerationCallback &operator=(const ConvexMeshContactGenerationCallback &); +}; + +bool ConvexMeshContactGeneration::generateContacts( + const PxPlane& localPlane, + const PxVec3* PX_RESTRICT localPoints, + const PxVec3& triCenter, PxVec3& groupAxis, + PxReal groupMinDepth, PxU32 index) const +{ + const PxVec3 worldGroupCenter = mWorld1.transform(triCenter); + const PxVec3 deltaC = mHullCenterWorld - worldGroupCenter; + if(deltaC.dot(groupAxis) < 0.0f) + groupAxis = -groupAxis; + + const PxU32 id = (mPolyData0.mSelectClosestEdgeCB)(mPolyData0, mConvexScaling, mWorld0.rotateTranspose(-groupAxis)); + + const HullPolygonData& HP = mPolyData0.mPolygons[id]; + PX_ALIGN(16, PxPlane) shapeSpacePlane0; + if(mIdtConvexScale) + V4StoreA(V4LoadU(&HP.mPlane.n.x), &shapeSpacePlane0.n.x); + else + mConvexScaling.transformPlaneToShapeSpace(HP.mPlane.n, HP.mPlane.d, shapeSpacePlane0.n, shapeSpacePlane0.d); + + const PxVec3 hullNormalWorld = mWorld0.rotate(shapeSpacePlane0.n); + + const PxReal d0 = PxAbs(hullNormalWorld.dot(groupAxis)); + + const PxVec3 triNormalWorld = mWorld1.rotate(localPlane.n); + const PxReal d1 = PxAbs(triNormalWorld.dot(groupAxis)); + const bool d0biggerd1 = d0 > d1; + +////////////////////NEW DIST HANDLING////////////////////// + //TODO: skip this if there is no dist involved! + +PxReal separation = - groupMinDepth; //convert to real distance. + +separation = fsel(separation, separation, 0.0f); //don't do anything when penetrating! + +//printf("\nseparation = %f", separation); + +PxReal contactGenPositionShift = separation + mCCDEpsilon; //if we're at a distance, shift so we're within penetration. + +PxVec3 contactGenPositionShiftVec = groupAxis * contactGenPositionShift; //shift one of the bodies this distance toward the other just for Pierre's contact generation. Then the bodies should be penetrating exactly by MIN_SEPARATION_FOR_PENALTY - ideal conditions for this contact generator. + +//note: for some reason this has to change sign! + +//this will make contact gen always generate contacts at about MSP. Shift them back to the true real distance, and then to a solver compliant distance given that +//the solver converges to MSP penetration, while we want it to converge to 0 penetration. +//to real distance: +// PxReal polyPolySeparationShift = separation; //(+ or - depending on which way normal goes) + +//The system: We always shift convex 0 (arbitrary). If the contact is attached to convex 0 then we will need to shift the contact point, otherwise not. + +//TODO: make these overwrite orig location if its safe to do so. + +Cm::Matrix34 world0_(mWorld0); +PxTransform transform0_(mTransform0); + +world0_.p -= contactGenPositionShiftVec; +transform0_.p = world0_.p; //reset this too. + +PxTransform t0to1_ = mTransform1.transformInv(transform0_); +PxTransform t1to0_ = transform0_.transformInv(mTransform1); +Cm::Matrix34 m0to1_(t0to1_); +Cm::Matrix34 m1to0_(t1to0_); + + PxVec3* scaledVertices0; + PxU8* stackIndices0; + GET_SCALEX_CONVEX(scaledVertices0, stackIndices0, mIdtConvexScale, HP.mNbVerts, mConvexScaling, mPolyData0.mVerts, mPolyData0.getPolygonVertexRefs(HP)) + + const PxU8 indices[3] = {0, 1, 2}; + + const PxMat33 RotT0 = findRotationMatrixFromZ(shapeSpacePlane0.n); + const PxMat33 RotT1 = findRotationMatrixFromZ(localPlane.n); + + if(d0biggerd1) + { + if(contactPolygonPolygonExt( + HP.mNbVerts, scaledVertices0, stackIndices0, world0_, shapeSpacePlane0, + RotT0, + 3, localPoints, indices, mWorld1, localPlane, + RotT1, + hullNormalWorld, m0to1_, m1to0_, PXC_CONTACT_NO_FACE_INDEX, index, + mContactBuffer, + true, + contactGenPositionShiftVec, contactGenPositionShift)) + { + return true; + } + } + else + { + if(contactPolygonPolygonExt( + 3, localPoints, indices, mWorld1, localPlane, + RotT1, + HP.mNbVerts, scaledVertices0, stackIndices0, world0_, shapeSpacePlane0, + RotT0, + triNormalWorld, m1to0_, m0to1_, PXC_CONTACT_NO_FACE_INDEX, index, + mContactBuffer, + false, + contactGenPositionShiftVec, contactGenPositionShift)) + { + return true; + } + } + return false; +} + +enum FeatureCode +{ + FC_VERTEX0, + FC_VERTEX1, + FC_VERTEX2, + FC_EDGE01, + FC_EDGE12, + FC_EDGE20, + FC_FACE, + + FC_UNDEFINED +}; + +static FeatureCode computeFeatureCode(const PxVec3& point, const PxVec3* verts) +{ + const PxVec3& triangleOrigin = verts[0]; + const PxVec3 triangleEdge0 = verts[1] - verts[0]; + const PxVec3 triangleEdge1 = verts[2] - verts[0]; + + const PxVec3 kDiff = triangleOrigin - point; + const PxReal fA00 = triangleEdge0.magnitudeSquared(); + const PxReal fA01 = triangleEdge0.dot(triangleEdge1); + const PxReal fA11 = triangleEdge1.magnitudeSquared(); + const PxReal fB0 = kDiff.dot(triangleEdge0); + const PxReal fB1 = kDiff.dot(triangleEdge1); + const PxReal fDet = PxAbs(fA00*fA11 - fA01*fA01); + const PxReal u = fA01*fB1-fA11*fB0; + const PxReal v = fA01*fB0-fA00*fB1; + + FeatureCode fc = FC_UNDEFINED; + + if(u + v <= fDet) + { + if(u < 0.0f) + { + if(v < 0.0f) // region 4 + { + if(fB0 < 0.0f) + { + if(-fB0 >= fA00) + fc = FC_VERTEX1; + else + fc = FC_EDGE01; + } + else + { + if(fB1 >= 0.0f) + fc = FC_VERTEX0; + else if(-fB1 >= fA11) + fc = FC_VERTEX2; + else + fc = FC_EDGE20; + } + } + else // region 3 + { + if(fB1 >= 0.0f) + fc = FC_VERTEX0; + else if(-fB1 >= fA11) + fc = FC_VERTEX2; + else + fc = FC_EDGE20; + } + } + else if(v < 0.0f) // region 5 + { + if(fB0 >= 0.0f) + fc = FC_VERTEX0; + else if(-fB0 >= fA00) + fc = FC_VERTEX1; + else + fc = FC_EDGE01; + } + else // region 0 + { + // minimum at interior PxVec3 + if(fDet==0.0f) + fc = FC_VERTEX0; + else + fc = FC_FACE; + } + } + else + { + PxReal fTmp0, fTmp1, fNumer, fDenom; + + if(u < 0.0f) // region 2 + { + fTmp0 = fA01 + fB0; + fTmp1 = fA11 + fB1; + if(fTmp1 > fTmp0) + { + fNumer = fTmp1 - fTmp0; + fDenom = fA00-2.0f*fA01+fA11; + if(fNumer >= fDenom) + fc = FC_VERTEX1; + else + fc = FC_EDGE12; + } + else + { + if(fTmp1 <= 0.0f) + fc = FC_VERTEX2; + else if(fB1 >= 0.0f) + fc = FC_VERTEX0; + else + fc = FC_EDGE20; + } + } + else if(v < 0.0f) // region 6 + { + fTmp0 = fA01 + fB1; + fTmp1 = fA00 + fB0; + if(fTmp1 > fTmp0) + { + fNumer = fTmp1 - fTmp0; + fDenom = fA00-2.0f*fA01+fA11; + if(fNumer >= fDenom) + fc = FC_VERTEX2; + else + fc = FC_EDGE12; + } + else + { + if(fTmp1 <= 0.0f) + fc = FC_VERTEX1; + else if(fB0 >= 0.0f) + fc = FC_VERTEX0; + else + fc = FC_EDGE01; + } + } + else // region 1 + { + fNumer = fA11 + fB1 - fA01 - fB0; + if(fNumer <= 0.0f) + { + fc = FC_VERTEX2; + } + else + { + fDenom = fA00-2.0f*fA01+fA11; + if(fNumer >= fDenom) + fc = FC_VERTEX1; + else + fc = FC_EDGE12; + } + } + } + return fc; +} + + +//static bool validateVertex(PxU32 vref, const PxU32 count, const ContactPoint* PX_RESTRICT contacts, const TriangleMesh& meshData) +//{ +// PxU32 previous = 0xffffffff; +// for(PxU32 i=0;i<count;i++) +// { +// if(contacts[i].internalFaceIndex1==previous) +// continue; +// previous = contacts[i].internalFaceIndex1; +// +// const TriangleIndices T(meshData, contacts[i].internalFaceIndex1); +// if( T.mVRefs[0]==vref +// || T.mVRefs[1]==vref +// || T.mVRefs[2]==vref) +// return false; +// } +// return true; +//} + + +//static PX_FORCE_INLINE bool testEdge(PxU32 vref0, PxU32 vref1, PxU32 tvref0, PxU32 tvref1) +//{ +// if(tvref0>tvref1) +// Ps::swap(tvref0, tvref1); +// +// if(tvref0==vref0 && tvref1==vref1) +// return false; +// return true; +//} + +//static bool validateEdge(PxU32 vref0, PxU32 vref1, const PxU32 count, const ContactPoint* PX_RESTRICT contacts, const TriangleMesh& meshData) +//{ +// if(vref0>vref1) +// Ps::swap(vref0, vref1); +// +// PxU32 previous = 0xffffffff; +// for(PxU32 i=0;i<count;i++) +// { +// if(contacts[i].internalFaceIndex1==previous) +// continue; +// previous = contacts[i].internalFaceIndex1; +// +// const TriangleIndices T(meshData, contacts[i].internalFaceIndex1); +// +///* if(T.mVRefs[0]==vref0 || T.mVRefs[0]==vref1) +// return false; +// if(T.mVRefs[1]==vref0 || T.mVRefs[1]==vref1) +// return false; +// if(T.mVRefs[2]==vref0 || T.mVRefs[2]==vref1) +// return false;*/ +// // PT: wow, this was wrong??? ###FIX +// if(!testEdge(vref0, vref1, T.mVRefs[0], T.mVRefs[1])) +// return false; +// if(!testEdge(vref0, vref1, T.mVRefs[1], T.mVRefs[2])) +// return false; +// if(!testEdge(vref0, vref1, T.mVRefs[2], T.mVRefs[0])) +// return false; +// } +// return true; +//} + +//static bool validateEdge(PxU32 vref0, PxU32 vref1, const PxU32* vertIndices, const PxU32 nbIndices) +//{ +// if(vref0>vref1) +// Ps::swap(vref0, vref1); +// +// for(PxU32 i=0;i<nbIndices;i+=3) +// { +// if(!testEdge(vref0, vref1, vertIndices[i+0], vertIndices[i+1])) +// return false; +// if(!testEdge(vref0, vref1, vertIndices[i+1], vertIndices[i+2])) +// return false; +// if(!testEdge(vref0, vref1, vertIndices[i+2], vertIndices[i+0])) +// return false; +// } +// return true; +//} + +//#endif + +void ConvexMeshContactGeneration::processTriangle(const PxVec3* verts, PxU32 triangleIndex, PxU8 triFlags, const PxU32* vertInds) +{ + const PxPlane localPlane(verts[0], verts[1], verts[2]); + + // Backface culling + if(localPlane.distance(mHullCenterMesh)<0.0f) +// if(localPlane.normal.dot(mHullCenterMesh - T.mVerts[0]) <= 0.0f) + return; + + ////////////////////////////////////////////////////////////////////////// + + const PxVec3 triCenter = (verts[0] + verts[1] + verts[2])*(1.0f/3.0f); + + // Group center in hull space +#ifdef USE_TRIANGLE_NORMAL + const PxVec3 groupCenterHull = m1to0.rotate(localPlane.normal); +#else + const PxVec3 groupCenterHull = m1to0.transform(triCenter); +#endif + ////////////////////////////////////////////////////////////////////////// + + PxVec3 groupAxis; + PxReal groupMinDepth; + bool faceContact; + if(!triangleConvexTest( mPolyData0, triFlags, + triangleIndex, verts, + localPlane, + groupCenterHull, + mWorld0, mWorld1, m0to1, m1to0, + mConvexScaling, + mContactDistance, mToleranceLength, + groupAxis, groupMinDepth, faceContact, + mIdtConvexScale + )) + return; + + if(faceContact) + { + // PT: generate face contacts immediately to save memory & avoid recomputing triangle data later + if(generateContacts( + localPlane, + verts, + triCenter, groupAxis, + groupMinDepth, triangleIndex)) + { + mAnyHits = true; + mEdgeCache.addData(CachedEdge(vertInds[0], vertInds[1])); + mEdgeCache.addData(CachedEdge(vertInds[0], vertInds[2])); + mEdgeCache.addData(CachedEdge(vertInds[1], vertInds[2])); + mVertCache.addData(CachedVertex(vertInds[0])); + mVertCache.addData(CachedVertex(vertInds[1])); + mVertCache.addData(CachedVertex(vertInds[2])); + } + else + { + int stop=1; + (void)stop; + } + } + else + { + const PxU32 nb = sizeof(SavedContactData)/sizeof(PxU32); + + // PT: no "pushBack" please (useless data copy + LHS) + PxU32 newSize = nb + mDelayedContacts.size(); + mDelayedContacts.reserve(newSize); + SavedContactData* PX_RESTRICT cd = reinterpret_cast<SavedContactData*>(mDelayedContacts.end()); + mDelayedContacts.forceSize_Unsafe(newSize); + + cd->mTriangleIndex = triangleIndex; + cd->mVerts[0] = verts[0]; + cd->mVerts[1] = verts[1]; + cd->mVerts[2] = verts[2]; + cd->mInds[0] = vertInds[0]; + cd->mInds[1] = vertInds[1]; + cd->mInds[2] = vertInds[2]; + cd->mGroupAxis = groupAxis; + cd->mGroupMinDepth = groupMinDepth; + } +} + +void ConvexMeshContactGeneration::generateLastContacts() +{ + // Process delayed contacts + PxU32 nbEntries = mDelayedContacts.size(); + if(nbEntries) + { + nbEntries /= sizeof(SavedContactData)/sizeof(PxU32); + + // PT: TODO: replicate this fix in sphere-vs-mesh ###FIX + //const PxU32 count = mContactBuffer.count; + //const ContactPoint* PX_RESTRICT contacts = mContactBuffer.contacts; + + const SavedContactData* PX_RESTRICT cd = reinterpret_cast<const SavedContactData*>(mDelayedContacts.begin()); + for(PxU32 i=0;i<nbEntries;i++) + { + const SavedContactData& currentContact = cd[i]; + + const PxU32 triangleIndex = currentContact.mTriangleIndex; + + // PT: unfortunately we must recompute this triangle-data here. + // PT: TODO: find a way not to +// const TriangleVertices T(*mMeshData, mMeshScaling, triangleIndex); +// const TriangleIndices T(*mMeshData, triangleIndex); + + const PxU32 ref0 = currentContact.mInds[0]; + const PxU32 ref1 = currentContact.mInds[1]; + const PxU32 ref2 = currentContact.mInds[2]; + + // PT: TODO: why bother with the feature code at all? Use edge cache directly? +// const FeatureCode FC = computeFeatureCode(mHullCenterMesh, T.mVerts); + const FeatureCode FC = computeFeatureCode(mHullCenterMesh, currentContact.mVerts); + + bool generateContact = false; + switch(FC) + { + // PT: trying the same as in sphere-mesh here + case FC_VERTEX0: + generateContact = !mVertCache.contains(CachedVertex(ref0)); + break; + case FC_VERTEX1: + generateContact =!mVertCache.contains(CachedVertex(ref1)); + break; + case FC_VERTEX2: + generateContact = !mVertCache.contains(CachedVertex(ref2)); + break; + case FC_EDGE01: + generateContact = !mEdgeCache.contains(CachedEdge(ref0, ref1)); + break; + case FC_EDGE12: + generateContact = !mEdgeCache.contains(CachedEdge(ref1, ref2)); + break; + case FC_EDGE20: + generateContact = !mEdgeCache.contains(CachedEdge(ref0, ref2)); + break; + case FC_FACE: + generateContact = true; + break; + case FC_UNDEFINED: + break; + }; + + if(!generateContact) + continue; + +// const PxcPlane localPlane(T.mVerts[0], T.mVerts[1], T.mVerts[2]); + const PxPlane localPlane(currentContact.mVerts[0], currentContact.mVerts[1], currentContact.mVerts[2]); +// const PxVec3 triCenter = (T.mVerts[0] + T.mVerts[1] + T.mVerts[2])*(1.0f/3.0f); + const PxVec3 triCenter = (currentContact.mVerts[0] + currentContact.mVerts[1] + currentContact.mVerts[2])*(1.0f/3.0f); + + PxVec3 groupAxis = currentContact.mGroupAxis; + if(generateContacts( + localPlane, +// T.mVerts, + currentContact.mVerts, + triCenter, groupAxis, + currentContact.mGroupMinDepth, triangleIndex)) + { + mAnyHits = true; + + //We don't add the edges to the data - this is important because we don't want to reject triangles + //because we generated an edge contact with an adjacent triangle + /*mEdgeCache.addData(CachedEdge(ref0, ref1)); + mEdgeCache.addData(CachedEdge(ref0, ref2)); + mEdgeCache.addData(CachedEdge(ref1, ref2)); + mVertCache.addData(CachedVertex(ref0)); + mVertCache.addData(CachedVertex(ref1)); + mVertCache.addData(CachedVertex(ref2));*/ + + } + } + } +} + +///////////// + +static bool contactHullMesh2(const PolygonalData& polyData0, const PxBounds3& hullAABB, const PxTriangleMeshGeometryLL& shape1, + const PxTransform& transform0, const PxTransform& transform1, + const NarrowPhaseParams& params, ContactBuffer& contactBuffer, + const Cm::FastVertex2ShapeScaling& convexScaling, const Cm::FastVertex2ShapeScaling& meshScaling, + bool idtConvexScale, bool idtMeshScale) +{ + //Just a sanity-check in debug-mode + PX_ASSERT(shape1.getType() == PxGeometryType::eTRIANGLEMESH); + //////////////////// + + // Compute matrices + const Cm::Matrix34 world0(transform0); + const Cm::Matrix34 world1(transform1); + + // Compute relative transforms + const PxTransform t0to1 = transform1.transformInv(transform0); + const PxTransform t1to0 = transform0.transformInv(transform1); + + BoxPadded hullOBB; + computeHullOBB(hullOBB, hullAABB, params.mContactDistance, world0, world1, meshScaling, idtMeshScale); + + // Setup the collider + const TriangleMesh* PX_RESTRICT meshData = shape1.meshData; + + Ps::InlineArray<PxU32,LOCAL_CONTACTS_SIZE> delayedContacts; + + ConvexMeshContactGenerationCallback blockCallback( + delayedContacts, + t0to1, t1to0, polyData0, world0, world1, meshData, meshData->getExtraTrigData(), meshScaling, + convexScaling, params.mContactDistance, params.mToleranceLength, + idtMeshScale, idtConvexScale, params.mMeshContactMargin, + transform0, transform1, + contactBuffer, hullOBB + ); + + Midphase::intersectOBB(meshData, hullOBB, blockCallback, false); + + blockCallback.mGeneration.generateLastContacts(); + + return blockCallback.mGeneration.mAnyHits; +} + +///////////// + +bool Gu::contactConvexMesh(GU_CONTACT_METHOD_ARGS) +{ + PX_UNUSED(cache); + PX_UNUSED(renderOutput); + + const PxTriangleMeshGeometryLL& shapeMesh = shape1.get<const PxTriangleMeshGeometryLL>(); + + const bool idtScaleMesh = shapeMesh.scale.isIdentity(); + + Cm::FastVertex2ShapeScaling meshScaling; + if(!idtScaleMesh) + meshScaling.init(shapeMesh.scale); + + Cm::FastVertex2ShapeScaling convexScaling; + PxBounds3 hullAABB; + PolygonalData polyData0; + const bool idtScaleConvex = getConvexData(shape0, convexScaling, hullAABB, polyData0); + + return contactHullMesh2(polyData0, hullAABB, shapeMesh, transform0, transform1, params, contactBuffer, convexScaling, meshScaling, idtScaleConvex, idtScaleMesh); +} + +bool Gu::contactBoxMesh(GU_CONTACT_METHOD_ARGS) +{ + PX_UNUSED(cache); + PX_UNUSED(renderOutput); + + const PxBoxGeometry& shapeBox = shape0.get<const PxBoxGeometry>(); + const PxTriangleMeshGeometryLL& shapeMesh = shape1.get<const PxTriangleMeshGeometryLL>(); + + PolygonalData polyData0; + PolygonalBox polyBox(shapeBox.halfExtents); + polyBox.getPolygonalData(&polyData0); + + const PxBounds3 hullAABB(-shapeBox.halfExtents, shapeBox.halfExtents); + + const bool idtScaleMesh = shapeMesh.scale.isIdentity(); + + Cm::FastVertex2ShapeScaling meshScaling; + if(!idtScaleMesh) + meshScaling.init(shapeMesh.scale); + + Cm::FastVertex2ShapeScaling idtScaling; + return contactHullMesh2(polyData0, hullAABB, shapeMesh, transform0, transform1, params, contactBuffer, idtScaling, meshScaling, true, idtScaleMesh); +} + +///////////// + +namespace +{ +struct ConvexVsHeightfieldContactGenerationCallback : EntityReport<PxU32> +{ + ConvexMeshContactGeneration mGeneration; + HeightFieldUtil& mHfUtil; + + ConvexVsHeightfieldContactGenerationCallback( + HeightFieldUtil& hfUtil, + Ps::InlineArray<PxU32,LOCAL_CONTACTS_SIZE>& delayedContacts, + const PxTransform& t0to1, const PxTransform& t1to0, + const PolygonalData& polyData0, const Cm::Matrix34& world0, const Cm::Matrix34& world1, + const Cm::FastVertex2ShapeScaling& convexScaling, + PxReal contactDistance, + PxReal toleranceLength, + bool idtConvexScale, + PxReal cCCDEpsilon, + const PxTransform& transform0, const PxTransform& transform1, + ContactBuffer& contactBuffer + ) : mGeneration(delayedContacts, t0to1, t1to0, polyData0, world0, world1, convexScaling, contactDistance, toleranceLength, idtConvexScale, cCCDEpsilon, transform0, + transform1, contactBuffer), + mHfUtil(hfUtil) + { + } + + // PT: TODO: refactor/unify with similar code in other places + virtual bool onEvent(PxU32 nb, PxU32* indices) + { + const PxU8 nextInd[] = {2,0,1}; + + while(nb--) + { + const PxU32 triangleIndex = *indices++; + + PxU32 vertIndices[3]; + PxTriangle currentTriangle; // in world space + PxU32 adjInds[3]; + mHfUtil.getTriangle(mGeneration.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(mGeneration.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(currentTriangle.verts, triangleIndex, triFlags, vertIndices); + } + return true; + } + +protected: + ConvexVsHeightfieldContactGenerationCallback &operator=(const ConvexVsHeightfieldContactGenerationCallback &); +}; +} + +static bool contactHullHeightfield2(const PolygonalData& polyData0, const PxBounds3& hullAABB, const PxHeightFieldGeometry& shape1, + const PxTransform& transform0, const PxTransform& transform1, + const NarrowPhaseParams& params, ContactBuffer& contactBuffer, + const Cm::FastVertex2ShapeScaling& convexScaling, + bool idtConvexScale) +{ + //We need to create a callback that fills triangles from the HF + + const HeightField& hf = *static_cast<HeightField*>(shape1.heightField); + + HeightFieldUtil hfUtil(shape1, hf); + + const Cm::Matrix34 world0(transform0); + const Cm::Matrix34 world1(transform1); + + //////////////////// + + // Compute relative transforms + const PxTransform t0to1 = transform1.transformInv(transform0); + const PxTransform t1to0 = transform0.transformInv(transform1); + + Ps::InlineArray<PxU32, LOCAL_CONTACTS_SIZE> delayedContacts; + + ConvexVsHeightfieldContactGenerationCallback blockCallback(hfUtil, delayedContacts, t0to1, t1to0, polyData0, world0, world1, convexScaling, params.mContactDistance, params.mToleranceLength, + idtConvexScale, params.mMeshContactMargin, transform0, transform1, contactBuffer); + + hfUtil.overlapAABBTriangles(transform1, PxBounds3::transformFast(t0to1, hullAABB), 0, &blockCallback); + + blockCallback.mGeneration.generateLastContacts(); + + return blockCallback.mGeneration.mAnyHits; +} + +bool Gu::contactConvexHeightfield(GU_CONTACT_METHOD_ARGS) +{ + PX_UNUSED(cache); + PX_UNUSED(renderOutput); + + //Create a triangle cache from the HF triangles and then feed triangles to NP mesh methods + const physx::PxHeightFieldGeometryLL& shapeMesh = shape1.get<const PxHeightFieldGeometryLL>(); + + Cm::FastVertex2ShapeScaling convexScaling; + PxBounds3 hullAABB; + PolygonalData polyData0; + const bool idtScaleConvex = getConvexData(shape0, convexScaling, hullAABB, polyData0); + const PxVec3 inflation(params.mContactDistance); + hullAABB.minimum -= inflation; + hullAABB.maximum += inflation; + + return contactHullHeightfield2(polyData0, hullAABB, shapeMesh, transform0, transform1, params, contactBuffer, convexScaling, idtScaleConvex); +} + +bool Gu::contactBoxHeightfield(GU_CONTACT_METHOD_ARGS) +{ + PX_UNUSED(cache); + PX_UNUSED(renderOutput); + + //Create a triangle cache from the HF triangles and then feed triangles to NP mesh methods + const physx::PxHeightFieldGeometryLL& shapeMesh = shape1.get<const PxHeightFieldGeometryLL>(); + + const PxBoxGeometry& shapeBox = shape0.get<const PxBoxGeometry>(); + + PolygonalData polyData0; + PolygonalBox polyBox(shapeBox.halfExtents); + polyBox.getPolygonalData(&polyData0); + + const PxVec3 inflatedExtents = shapeBox.halfExtents + PxVec3(params.mContactDistance); + + const PxBounds3 hullAABB = PxBounds3(-inflatedExtents, inflatedExtents); + + const Cm::FastVertex2ShapeScaling idtScaling; + + return contactHullHeightfield2(polyData0, hullAABB, shapeMesh, transform0, transform1, params, contactBuffer, idtScaling, true); +} diff --git a/PhysX_3.4/Source/GeomUtils/src/contact/GuContactMethodImpl.h b/PhysX_3.4/Source/GeomUtils/src/contact/GuContactMethodImpl.h new file mode 100644 index 00000000..86101dc3 --- /dev/null +++ b/PhysX_3.4/Source/GeomUtils/src/contact/GuContactMethodImpl.h @@ -0,0 +1,174 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2016 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 "PxPhysXCommonConfig.h" +#include "CmPhysXCommon.h" +#include "collision/PxCollisionDefs.h" + +namespace physx +{ +namespace Cm +{ + class RenderOutput; +} + +namespace Gu +{ + class GeometryUnion; + class ContactBuffer; + struct NarrowPhaseParams; + class PersistentContactManifold; + class MultiplePersistentContactManifold; + + enum ManifoldFlags + { + IS_MANIFOLD = (1<<0), + IS_MULTI_MANIFOLD = (1<<1) + }; + + struct Cache : public PxCache + { + Cache() + { + } + + PX_FORCE_INLINE void setManifold(void* manifold) + { + mCachedData = reinterpret_cast<PxU8*>(manifold); + mManifoldFlags |= IS_MANIFOLD; + } + + PX_FORCE_INLINE void setMultiManifold(void* manifold) + { + 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); + } + }; +} + +#define GU_CONTACT_METHOD_ARGS \ + const Gu::GeometryUnion& shape0, \ + const Gu::GeometryUnion& shape1, \ + const PxTransform& transform0, \ + const PxTransform& transform1, \ + const Gu::NarrowPhaseParams& params, \ + Gu::Cache& cache, \ + Gu::ContactBuffer& contactBuffer, \ + Cm::RenderOutput* renderOutput + +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 contactPlaneConvex(GU_CONTACT_METHOD_ARGS); + + //Legacy heightfield code path + PX_PHYSX_COMMON_API bool legacyContactSphereHeightfield(GU_CONTACT_METHOD_ARGS); + PX_PHYSX_COMMON_API bool legacyContactCapsuleHeightfield(GU_CONTACT_METHOD_ARGS); + PX_PHYSX_COMMON_API bool legacyContactBoxHeightfield(GU_CONTACT_METHOD_ARGS); + PX_PHYSX_COMMON_API bool legacyContactConvexHeightfield(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); +} +} + +#endif diff --git a/PhysX_3.4/Source/GeomUtils/src/contact/GuContactPlaneBox.cpp b/PhysX_3.4/Source/GeomUtils/src/contact/GuContactPlaneBox.cpp new file mode 100644 index 00000000..bd46b296 --- /dev/null +++ b/PhysX_3.4/Source/GeomUtils/src/contact/GuContactPlaneBox.cpp @@ -0,0 +1,128 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2016 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 "GuContactMethodImpl.h" +#include "GuContactBuffer.h" +#include "GuGeometryUnion.h" +#include "CmMatrix34.h" + +#include "PsUtilities.h" +#include "foundation/PxUnionCast.h" + +namespace physx +{ +namespace Gu +{ +bool contactPlaneBox(GU_CONTACT_METHOD_ARGS) +{ + PX_UNUSED(renderOutput); + PX_UNUSED(cache); + PX_UNUSED(shape0); + + // Get actual shape data + //const PxPlaneGeometry& shapePlane = shape.get<const PxPlaneGeometry>(); + const PxBoxGeometry& shapeBox = shape1.get<const PxBoxGeometry>(); + + const PxVec3 negPlaneNormal = -transform0.q.getBasisVector0(); + + //Make sure we have a normalized plane + //PX_ASSERT(PxAbs(shape0.mNormal.magnitudeSquared() - 1.0f) < 0.000001f); + + Cm::Matrix34 boxMatrix(transform1); + Cm::Matrix34 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; +} +}//Gu +}//physx diff --git a/PhysX_3.4/Source/GeomUtils/src/contact/GuContactPlaneCapsule.cpp b/PhysX_3.4/Source/GeomUtils/src/contact/GuContactPlaneCapsule.cpp new file mode 100644 index 00000000..92a917a3 --- /dev/null +++ b/PhysX_3.4/Source/GeomUtils/src/contact/GuContactPlaneCapsule.cpp @@ -0,0 +1,80 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2016 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 "GuContactMethodImpl.h" +#include "GuContactBuffer.h" +#include "GuInternal.h" +#include "GuSegment.h" +#include "GuGeometryUnion.h" + +namespace physx +{ +namespace Gu +{ +bool contactPlaneCapsule(GU_CONTACT_METHOD_ARGS) +{ + PX_UNUSED(renderOutput); + PX_UNUSED(cache); + PX_UNUSED(shape0); + + // Get actual shape data + //const PxPlaneGeometry& shapePlane = shape.get<const PxPlaneGeometry>(); + const PxCapsuleGeometry& shapeCapsule = shape1.get<const PxCapsuleGeometry>(); + + 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; +} +}//Gu +}//physx diff --git a/PhysX_3.4/Source/GeomUtils/src/contact/GuContactPlaneConvex.cpp b/PhysX_3.4/Source/GeomUtils/src/contact/GuContactPlaneConvex.cpp new file mode 100644 index 00000000..682186c3 --- /dev/null +++ b/PhysX_3.4/Source/GeomUtils/src/contact/GuContactPlaneConvex.cpp @@ -0,0 +1,101 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2016 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 "GuConvexMeshData.h" +#include "GuContactMethodImpl.h" +#include "GuContactBuffer.h" +#include "GuGeometryUnion.h" +#include "CmScaling.h" + + +namespace physx +{ +namespace Gu +{ +bool contactPlaneConvex(GU_CONTACT_METHOD_ARGS) +{ + PX_UNUSED(renderOutput); + PX_UNUSED(cache); + PX_UNUSED(shape0); + + // Get actual shape data + //const PxPlaneGeometry& shapePlane = shape.get<const PxPlaneGeometry>(); + const PxConvexMeshGeometryLL& shapeConvex = shape1.get<const PxConvexMeshGeometryLL>(); + + const PxVec3* PX_RESTRICT hullVertices = shapeConvex.hullData->getHullVertices(); + PxU32 numHullVertices = shapeConvex.hullData->mNbHullVertices; +// Ps::prefetch128(hullVertices); + + // Plane is implicitly <1,0,0> 0 in localspace + Cm::Matrix34 convexToPlane (transform0.transformInv(transform1)); + PxMat33 convexToPlane_rot(convexToPlane[0], convexToPlane[1], convexToPlane[2] ); + + bool idtScale = shapeConvex.scale.isIdentity(); + Cm::FastVertex2ShapeScaling convexScaling; // PT: TODO: remove default ctor + if(!idtScale) + convexScaling.init(shapeConvex.scale); + + convexToPlane = Cm::Matrix34( convexToPlane_rot * convexScaling.getVertex2ShapeSkew(), convexToPlane[3] ); + + //convexToPlane = context.mVertex2ShapeSkew[1].getVertex2WorldSkew(convexToPlane); + + const Cm::Matrix34 planeToW (transform0); + + // This is rather brute-force + + bool status = false; + + const PxVec3 contactNormal = -planeToW.m.column0; + + while(numHullVertices--) + { + const PxVec3& vertex = *hullVertices++; +// if(numHullVertices) +// Ps::prefetch128(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; + Gu::ContactPoint* 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; +} +}//Gu +}//physx diff --git a/PhysX_3.4/Source/GeomUtils/src/contact/GuContactPolygonPolygon.cpp b/PhysX_3.4/Source/GeomUtils/src/contact/GuContactPolygonPolygon.cpp new file mode 100644 index 00000000..33190da8 --- /dev/null +++ b/PhysX_3.4/Source/GeomUtils/src/contact/GuContactPolygonPolygon.cpp @@ -0,0 +1,861 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2016 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 "GuContactPolygonPolygon.h" +#include "GuShapeConvex.h" +#include "GuContactBuffer.h" +#include "PsMathUtils.h" +#include "PsAllocator.h" +#include "PsFPU.h" + +using namespace physx; +using namespace Gu; + +#define CONTACT_REDUCTION + +/* +void gVisualizeLocalLine(const PxVec3& a, const PxVec3& b, const Cm::Matrix34& m, PxsContactManager& manager) //temp debug +{ + Cm::RenderOutput out = manager.getContext()->getRenderOutput(); + out << 0xffffff << m << Cm::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( ContactBuffer& 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 + + Gu::ContactPoint* 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 = Ps::degToRad(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 = Ps::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 Cm::Matrix34 transformTranspose(const PxMat33& a, const Cm::Matrix34& b) +{ + return Cm::Matrix34(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 Cm::Matrix34& 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 Cm::Matrix34& 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 Cm::Matrix34& 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 Cm::Matrix34& 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 Cm::Matrix34& transform0to1, const Cm::Matrix34& transform1to0, //transforms between polygons + PxU32 /*polyIndex0*/, PxU32 polyIndex1, //feature indices for contact callback + ContactBuffer& 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)); +// Ps::memZero(flags0, size0); +// Ps::memZero(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)); +// Ps::memZero(flags1, size1); +// Ps::memZero(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[ContactBuffer::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 Cm::Matrix34 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 + + Gu::ContactPoint* 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 Cm::Matrix34 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. + + Gu::ContactPoint* 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; + Ps::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);*/ + + Gu::ContactPoint* 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; +} diff --git a/PhysX_3.4/Source/GeomUtils/src/contact/GuContactPolygonPolygon.h b/PhysX_3.4/Source/GeomUtils/src/contact/GuContactPolygonPolygon.h new file mode 100644 index 00000000..41f5bf6f --- /dev/null +++ b/PhysX_3.4/Source/GeomUtils/src/contact/GuContactPolygonPolygon.h @@ -0,0 +1,69 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2016 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/PxVec3.h" +#include "CmPhysXCommon.h" +#include "PxPhysXCommonConfig.h" + +namespace physx +{ + +namespace Cm +{ + class Matrix34; + class FastVertex2ShapeScaling; +} + +namespace Gu +{ +class ContactBuffer; + +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 Cm::Matrix34& 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 Cm::Matrix34& 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 Cm::Matrix34& transform0to1, const Cm::Matrix34& transform1to0,//transforms between polygons + PxU32 polyIndex0, PxU32 polyIndex1, //face indices for contact callback, + ContactBuffer& contactBuffer, + bool flipNormal, const PxVec3& posShift, float sepShift + ); // shape order, post gen shift. +} +} + +#endif diff --git a/PhysX_3.4/Source/GeomUtils/src/contact/GuContactSphereBox.cpp b/PhysX_3.4/Source/GeomUtils/src/contact/GuContactSphereBox.cpp new file mode 100644 index 00000000..58da6764 --- /dev/null +++ b/PhysX_3.4/Source/GeomUtils/src/contact/GuContactSphereBox.cpp @@ -0,0 +1,181 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2016 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 "GuContactMethodImpl.h" +#include "GuContactBuffer.h" +#include "GuGeometryUnion.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 PxTransform& 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; + } +} + +namespace physx +{ +namespace Gu +{ +bool contactSphereBox(GU_CONTACT_METHOD_ARGS) +{ + PX_UNUSED(renderOutput); + PX_UNUSED(cache); + + const PxSphereGeometry& sphereGeom = shape0.get<const PxSphereGeometry>(); + const PxBoxGeometry& boxGeom = shape1.get<const PxBoxGeometry>(); + + 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; +} +}//Gu +}//physx diff --git a/PhysX_3.4/Source/GeomUtils/src/contact/GuContactSphereCapsule.cpp b/PhysX_3.4/Source/GeomUtils/src/contact/GuContactSphereCapsule.cpp new file mode 100644 index 00000000..24a97d6f --- /dev/null +++ b/PhysX_3.4/Source/GeomUtils/src/contact/GuContactSphereCapsule.cpp @@ -0,0 +1,82 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2016 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 "GuDistancePointSegment.h" +#include "GuContactMethodImpl.h" +#include "GuContactBuffer.h" +#include "GuInternal.h" +#include "GuGeometryUnion.h" + +namespace physx +{ +namespace Gu +{ +bool contactSphereCapsule(GU_CONTACT_METHOD_ARGS) +{ + PX_UNUSED(renderOutput); + PX_UNUSED(cache); + + const PxSphereGeometry& sphereGeom = shape0.get<const PxSphereGeometry>(); + const PxCapsuleGeometry& capsuleGeom = shape1.get<const PxCapsuleGeometry>(); + + // 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; +} +}//Gu +}//physx diff --git a/PhysX_3.4/Source/GeomUtils/src/contact/GuContactSphereMesh.cpp b/PhysX_3.4/Source/GeomUtils/src/contact/GuContactSphereMesh.cpp new file mode 100644 index 00000000..582b2df9 --- /dev/null +++ b/PhysX_3.4/Source/GeomUtils/src/contact/GuContactSphereMesh.cpp @@ -0,0 +1,615 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2016 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 "GuDistancePointTriangle.h" +#include "GuContactMethodImpl.h" +#include "GuContactBuffer.h" +#include "GuGeometryUnion.h" +#include "GuFeatureCode.h" +#include "GuMidphaseInterface.h" +#include "GuEntityReport.h" +#include "GuHeightFieldUtil.h" +#include "GuBox.h" +#include "PsSort.h" + +#include "CmRenderOutput.h" + +#define DEBUG_RENDER_MESHCONTACTS 0 + +using namespace physx; +using namespace Gu; + +static const bool gDrawTouchedTriangles = false; + +static void outputErrorMessage() +{ +#if PX_CHECKED + Ps::getFoundation().error(PxErrorCode::eINTERNAL_ERROR, __FILE__, __LINE__, "Dropping contacts in sphere vs mesh: exceeded limit of 64 "); +#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; + ContactBuffer& mContactBuffer; + const PxVec3& mSphereCenterShape1Space; + PxF32 mInflatedRadius2; + PxU32 mNbDelayed; + TriangleData mSavedData[ContactBuffer::MAX_CONTACTS]; + SortKey mSortKey[ContactBuffer::MAX_CONTACTS]; + PxU32 mNbCachedTris; + CachedTriangleIndices mCachedTris[ContactBuffer::MAX_CONTACTS]; + Cm::RenderOutput* mRenderOutput; + + SphereMeshContactGeneration(const PxSphereGeometry& shapeSphere, const PxTransform& transform0, const PxTransform& transform1, + ContactBuffer& contactBuffer, const PxVec3& sphereCenterShape1Space, PxF32 inflatedRadius, + Cm::RenderOutput* 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<ContactBuffer::MAX_CONTACTS) + cacheTriangle(vertInds[0], vertInds[1], vertInds[2]); + } + else + { + if(mNbDelayed<ContactBuffer::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; + + Ps::sort(mSortKey, count, Ps::Less<SortKey>(), NullAllocator(), ContactBuffer::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<ContactBuffer::MAX_CONTACTS) + cacheTriangle(ref0, ref1, ref2); + else + outputErrorMessage(); + } + } + +private: + SphereMeshContactGeneration& operator=(const SphereMeshContactGeneration&); +}; + +struct SphereMeshContactGenerationCallback_NoScale : MeshHitCallback<PxRaycastHit> +{ + SphereMeshContactGeneration mGeneration; + const TriangleMesh& mMeshData; + + SphereMeshContactGenerationCallback_NoScale(const TriangleMesh& meshData, const PxSphereGeometry& shapeSphere, + const PxTransform& transform0, const PxTransform& transform1, ContactBuffer& contactBuffer, + const PxVec3& sphereCenterShape1Space, PxF32 inflatedRadius, Cm::RenderOutput* renderOutput + ) : MeshHitCallback<PxRaycastHit> (CallbackMode::eMULTIPLE), + mGeneration (shapeSphere, transform0, transform1, contactBuffer, sphereCenterShape1Space, inflatedRadius, renderOutput), + mMeshData (meshData) + { + } + + virtual ~SphereMeshContactGenerationCallback_NoScale() + { + mGeneration.generateLastContacts(); + } + + virtual PxAgain processHit( + const PxRaycastHit& 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, + ContactBuffer& contactBuffer, const PxVec3& sphereCenterShape1Space, PxF32 inflatedRadius, Cm::RenderOutput* renderOutput + ) : SphereMeshContactGenerationCallback_NoScale(meshData, shapeSphere, + transform0, transform1, contactBuffer, sphereCenterShape1Space, inflatedRadius, renderOutput), + mMeshScaling (meshScaling) + { + } + + virtual ~SphereMeshContactGenerationCallback_Scale() {} + + virtual PxAgain processHit(const PxRaycastHit& hit, const PxVec3& v0, const PxVec3& v1, const PxVec3& v2, PxReal&, const PxU32* vinds) + { + PxVec3 verts[3]; + getScaledVertices(verts, v0, v1, v2, false, mMeshScaling); + + 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], vinds); + return true; + } +protected: + SphereMeshContactGenerationCallback_Scale &operator=(const SphereMeshContactGenerationCallback_Scale &); +}; + +} + +/////////////////////////////////////////////////////////////////////////////// + +bool Gu::contactSphereMesh(GU_CONTACT_METHOD_ARGS) +{ + PX_UNUSED(cache); + + const PxSphereGeometry& shapeSphere = shape0.get<const PxSphereGeometry>(); + const PxTriangleMeshGeometryLL& shapeMesh = shape1.get<const PxTriangleMeshGeometryLL>(); + + // 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 = shapeMesh.meshData; + + // 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 : EntityReport<PxU32> +{ + SphereMeshContactGeneration mGeneration; + HeightFieldUtil& mHfUtil; + + SphereHeightfieldContactGenerationCallback( + HeightFieldUtil& hfUtil, + const PxSphereGeometry& shapeSphere, + const PxTransform& transform0, + const PxTransform& transform1, + ContactBuffer& contactBuffer, + const PxVec3& sphereCenterInMeshSpace, + PxF32 inflatedRadius, + Cm::RenderOutput* renderOutput + ) : + mGeneration (shapeSphere, transform0, transform1, contactBuffer, sphereCenterInMeshSpace, inflatedRadius, renderOutput), + mHfUtil (hfUtil) + { + } + + // PT: TODO: refactor/unify with similar code in other places + virtual bool onEvent(PxU32 nb, 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 = shape0.get<const PxSphereGeometry>(); + const PxHeightFieldGeometryLL& shapeMesh = shape1.get<const PxHeightFieldGeometryLL>(); + const HeightField& hf = *static_cast<HeightField*>(shapeMesh.heightField); + + HeightFieldUtil hfUtil(shapeMesh, hf); + + const PxVec3 sphereCenterInMeshSpace = transform1.transformInv(transform0.p); + const PxReal inflatedRadius = shapeSphere.radius + params.mContactDistance; + const PxVec3 inflatedRV3(inflatedRadius); + + const PxBounds3 bounds(sphereCenterInMeshSpace - inflatedRV3, sphereCenterInMeshSpace + inflatedRV3); + + SphereHeightfieldContactGenerationCallback blockCallback(hfUtil, shapeSphere, transform0, transform1, contactBuffer, sphereCenterInMeshSpace, inflatedRadius, renderOutput); + + hfUtil.overlapAABBTriangles(transform1, bounds, 0, &blockCallback); + + blockCallback.mGeneration.generateLastContacts(); + + return contactBuffer.count > 0; +} + +/////////////////////////////////////////////////////////////////////////////// diff --git a/PhysX_3.4/Source/GeomUtils/src/contact/GuContactSpherePlane.cpp b/PhysX_3.4/Source/GeomUtils/src/contact/GuContactSpherePlane.cpp new file mode 100644 index 00000000..38446117 --- /dev/null +++ b/PhysX_3.4/Source/GeomUtils/src/contact/GuContactSpherePlane.cpp @@ -0,0 +1,68 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2016 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 "GuContactBuffer.h" +#include "GuContactMethodImpl.h" +#include "GuGeometryUnion.h" + +namespace physx +{ +namespace Gu +{ +bool contactSpherePlane(GU_CONTACT_METHOD_ARGS) +{ + PX_UNUSED(renderOutput); + PX_UNUSED(cache); + PX_UNUSED(shape1); + + // Get actual shape data + const PxSphereGeometry& shapeSphere = shape0.get<const PxSphereGeometry>(); + //const PxPlaneGeometry& shapePlane = shape1.get<const PxPlaneGeometry>(); + + //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; +} +}//Gu +}//physx diff --git a/PhysX_3.4/Source/GeomUtils/src/contact/GuContactSphereSphere.cpp b/PhysX_3.4/Source/GeomUtils/src/contact/GuContactSphereSphere.cpp new file mode 100644 index 00000000..6146b1d2 --- /dev/null +++ b/PhysX_3.4/Source/GeomUtils/src/contact/GuContactSphereSphere.cpp @@ -0,0 +1,68 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2016 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 "GuContactBuffer.h" +#include "GuContactMethodImpl.h" +#include "GuGeometryUnion.h" + +namespace physx +{ +namespace Gu +{ +bool contactSphereSphere(GU_CONTACT_METHOD_ARGS) +{ + PX_UNUSED(renderOutput); + PX_UNUSED(cache); + + const PxSphereGeometry& sphereGeom0 = shape0.get<const PxSphereGeometry>(); + const PxSphereGeometry& sphereGeom1 = shape1.get<const PxSphereGeometry>(); + + 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; +} +}//Gu +}//physx diff --git a/PhysX_3.4/Source/GeomUtils/src/contact/GuFeatureCode.cpp b/PhysX_3.4/Source/GeomUtils/src/contact/GuFeatureCode.cpp new file mode 100644 index 00000000..f9bbd7e9 --- /dev/null +++ b/PhysX_3.4/Source/GeomUtils/src/contact/GuFeatureCode.cpp @@ -0,0 +1,128 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2016 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; +} + diff --git a/PhysX_3.4/Source/GeomUtils/src/contact/GuFeatureCode.h b/PhysX_3.4/Source/GeomUtils/src/contact/GuFeatureCode.h new file mode 100644 index 00000000..541f2a67 --- /dev/null +++ b/PhysX_3.4/Source/GeomUtils/src/contact/GuFeatureCode.h @@ -0,0 +1,56 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2016 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 + +#include "CmPhysXCommon.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 diff --git a/PhysX_3.4/Source/GeomUtils/src/contact/GuLegacyContactBoxHeightField.cpp b/PhysX_3.4/Source/GeomUtils/src/contact/GuLegacyContactBoxHeightField.cpp new file mode 100644 index 00000000..8429fde3 --- /dev/null +++ b/PhysX_3.4/Source/GeomUtils/src/contact/GuLegacyContactBoxHeightField.cpp @@ -0,0 +1,476 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2016 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 "GuContactMethodImpl.h" +#include "GuGeometryUnion.h" +#include "GuHeightFieldUtil.h" +#include "CmRenderBuffer.h" +#include "GuContactBuffer.h" +#include "GuContactMethodImpl.h" +#include "GuLegacyTraceLineCallback.h" +#include "PsFPU.h" + +#include "CmMatrix34.h" + +using namespace physx; + +#define DISTANCE_BASED_TEST + +///////// +#if 0 + #include "CmRenderOutput.h" + #include "PxsContext.h" + static void gVisualizeBox(const Gu::Box& box, PxcNpThreadContext& context, PxU32 color=0xffffff) + { + PxMat33 rot(box.rot.column0, box.rot.column1, box.rot.column2); + PxMat44 m(rot, box.center); + + Cm::DebugBox db(box.extents); + + Cm::RenderOutput& 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(PxIdentity); + + Cm::RenderOutput& out = context.mRenderOutput; + out << color << m << Cm::RenderOutput::LINES << a << b; + } +#endif +///////// + +// ptchernev TODO: make sure these are ok before shipping +static const bool gCompileBoxVertex = true; +static const bool gCompileEdgeEdge = true; +static const bool gCompileHeightFieldVertex = true; + +static const PxReal signs[24] = +{ + -1,-1,-1, + -1,-1, 1, + -1, 1,-1, + -1, 1, 1, + 1,-1,-1, + 1,-1, 1, + 1, 1,-1, + 1, 1, 1, +}; + +static const PxU8 edges[24] = +{ + 0,1, + 1,3, + 3,2, + 2,0, + 4,5, + 5,7, + 7,6, + 6,4, + 0,4, + 1,5, + 2,6, + 3,7, +}; + +static bool GuDepenetrateBox( const PxVec3& point, + const PxVec3& safeNormal, + const PxVec3& dimensions, + float /*contactDistance*/, + PxVec3& normal, + PxReal& distance) +{ + PxVec3 faceNormal(PxReal(0)); + PxReal distance1 = -PX_MAX_REAL; // cant be more + PxReal distance2 = -PX_MAX_REAL; // cant be more + PxI32 poly1 = -1; + PxI32 poly2 = -2; + + for (PxU32 poly = 0; poly < 6; poly++) + { + PxU32 dim = poly % 3; + + PxReal sign = (poly > 2) ? -PxReal(1) : PxReal(1); + + PxVec3 n(PxVec3(0)); + + n[dim] = sign; + PxReal proj = n[dim] * safeNormal[dim]; + PxReal d = n[dim] * (point[dim] - sign * dimensions[dim]); + +#ifdef DISTANCE_BASED_TEST + // PT: I'm not really sure about contactDistance here + // AP: enabling this causes jitter in DE2740 + //d -= contactDistance; +#endif + + if (d >= 0) + return false; + + if (proj > 0) + { + if (d > distance1) // less penetration + { + distance1 = d; + faceNormal = n; + poly1 = PxI32(poly); + } + + // distance2 / d = 1 / proj + PxReal tmp = d / proj; + if (tmp > distance2) + { + distance2 = tmp; + poly2 = PxI32(poly); + } + } + } + + if (poly1 == poly2) + { + PX_ASSERT(faceNormal.magnitudeSquared() != 0.0f); + normal = faceNormal; + distance = -distance1; + } + else + { + normal = safeNormal; + distance = -distance2; + } + + return true; +} + +//Box-Heightfield and Convex-Heightfield do not support positive values for contactDistance, +//and if in this case we would emit contacts normally, we'd cause things to jitter. +//as a workaround we add contactDistance to the distance values that we emit in contacts. +//this has the effect that the biasing will work exactly as if we had specified a legacy skinWidth of (contactDistance - restDistance) + +namespace physx +{ +namespace Gu +{ +bool legacyContactBoxHeightfield(GU_CONTACT_METHOD_ARGS) +{ + PX_UNUSED(renderOutput); + PX_UNUSED(cache); + //PXC_WARN_ONCE(contactDistance > 0.0f, "PxcContactBoxHeightField: Box-Heightfield does not support distance based contact generation! Ignoring contactOffset > 0!"); + + // Get actual shape data + const PxBoxGeometry& shapeBox = shape0.get<const PxBoxGeometry>(); + const PxHeightFieldGeometryLL& hfGeom = shape1.get<const PxHeightFieldGeometryLL>(); + + const Gu::HeightField& hf = *static_cast<Gu::HeightField*>(hfGeom.heightField); + const Gu::HeightFieldUtil hfUtil(hfGeom, hf); + + PX_ASSERT(contactBuffer.count==0); + + Cm::Matrix34 boxShapeAbsPose(transform0); + Cm::Matrix34 hfShapeAbsPose(transform1); + + const PxMat33& left = hfShapeAbsPose.m; + const PxMat33& right = boxShapeAbsPose.m; + + Cm::Matrix34 boxShape2HfShape(left.getInverse()* right, left.getInverse()*(boxShapeAbsPose.p - hfShapeAbsPose.p)); + + // Get box vertices. + PxVec3 boxVertices[8]; + PxVec3 boxVertexNormals[8]; + for(PxU32 i=0; i<8; i++) + { + boxVertices[i] = PxVec3(shapeBox.halfExtents.x*signs[3*i], shapeBox.halfExtents.y*signs[3*i+1], shapeBox.halfExtents.z*signs[3*i+2]); + + boxVertexNormals[i] = PxVec3(signs[3*i], signs[3*i+1], signs[3*i+2]); + boxVertexNormals[i].normalize(); + } + + // Transform box vertices to HeightFieldShape space. + PxVec3 boxVerticesInHfShape[8]; + PxVec3 boxVertexNormalsInHfShape[8]; + for(PxU32 i=0; i<8; i++) + { + boxVerticesInHfShape[i] = boxShape2HfShape.transform(boxVertices[i]); + boxVertexNormalsInHfShape[i] = boxShape2HfShape.rotate(boxVertexNormals[i]); + } + + // bounds of box on HeightField. + PxVec3 aabbMin(boxVerticesInHfShape[0]); + PxVec3 aabbMax(boxVerticesInHfShape[0]); + for(PxU32 i=1; i<8; i++) + { + for(PxU32 dim = 0; dim < 3; dim++) + { + aabbMin[dim] = PxMin(aabbMin[dim], boxVerticesInHfShape[i][dim]); + aabbMax[dim] = PxMax(aabbMax[dim], boxVerticesInHfShape[i][dim]); + } + } + + const bool thicknessNegOrNull = (hf.getThicknessFast() <= 0.0f); // PT: don't do this each time! FCMPs are slow. + + // Compute the height field extreme over the bounds area. +// PxReal hfExtreme = thicknessNegOrNull ? -PX_MAX_REAL : PX_MAX_REAL; + // PT: we already computed those! +// const PxReal oneOverRowScale = 1.0f / shapeHeightField.rowScale; +// const PxReal oneOverColumnScale = 1.0f / shapeHeightField.columnScale; + const PxReal oneOverRowScale = hfUtil.getOneOverRowScale(); + const PxReal oneOverColumnScale = hfUtil.getOneOverColumnScale(); + + PxU32 minRow; + PxU32 maxRow; + PxU32 minColumn; + PxU32 maxColumn; + + if (hfGeom.rowScale < 0) + { + minRow = hf.getMinRow(aabbMax.x * oneOverRowScale); + maxRow = hf.getMaxRow(aabbMin.x * oneOverRowScale); + } + else + { + minRow = hf.getMinRow(aabbMin.x * oneOverRowScale); + maxRow = hf.getMaxRow(aabbMax.x * oneOverRowScale); + } + + if (hfGeom.columnScale < 0) + { + minColumn = hf.getMinColumn(aabbMax.z * oneOverColumnScale); + maxColumn = hf.getMaxColumn(aabbMin.z * oneOverColumnScale); + } + else + { + minColumn = hf.getMinColumn(aabbMin.z * oneOverColumnScale); + maxColumn = hf.getMaxColumn(aabbMax.z * oneOverColumnScale); + } + + PxReal hfExtreme = hf.computeExtreme(minRow, maxRow, minColumn, maxColumn); + + //hfExtreme *= hfShape.getHeightScale(); + hfExtreme *= hfGeom.heightScale; + + // Return if convex is on the wrong side of the extreme. + if (thicknessNegOrNull) + { + if (aabbMin.y > hfExtreme) return false; + } + else + { + if (aabbMax.y < hfExtreme) return false; + } + + // Test box vertices. + if (gCompileBoxVertex) + { + for(PxU32 i=0; i<8; i++) + { + const int32_t* tmp = reinterpret_cast<const int32_t*>(&boxVertexNormalsInHfShape[i].y); + // PT: orientation culling + if(*tmp>0) +// if(PX_SIR(boxVertexNormalsInHfShape[i].y)>0) + continue; + + const PxVec3& boxVertexInHfShape = boxVerticesInHfShape[i]; +#if 0 + PxVec3 pt = boxShapeAbsPose.transform(boxVertices[i]); + PxVec3 worldNormal = boxShapeAbsPose.rotate(boxVertexNormals[i]); + //gVisualizeLine(pt, pt+PxVec3(1.0f,0.0f,0.0f), context, PxDebugColor::eARGB_RED); + //gVisualizeLine(pt, pt+PxVec3(0.0f,1.0f,0.0f), context, PxDebugColor::eARGB_GREEN); + //gVisualizeLine(pt, pt+PxVec3(0.0f,0.0f,1.0f), context, PxDebugColor::eARGB_BLUE); + gVisualizeLine(pt, pt+worldNormal, context, PxDebugColor::eARGB_MAGENTA); +#endif + +//////// SAME CODE AS IN CONVEX-HF + const bool insideExtreme = + thicknessNegOrNull ? (boxVertexInHfShape.y < hfExtreme + params.mContactDistance) : (boxVertexInHfShape.y > hfExtreme-params.mContactDistance); + + //if (insideExtreme && hfShape.isShapePointOnHeightField(boxVertexInHfShape.x, boxVertexInHfShape.z)) + if (insideExtreme && hfUtil.isShapePointOnHeightField(boxVertexInHfShape.x, boxVertexInHfShape.z)) + { + //PxReal y = hfShape.getHeightAtShapePoint(boxVertexInHfShape.x, boxVertexInHfShape.z); +// const PxReal y = hfUtil.getHeightAtShapePoint(boxVertexInHfShape.x, boxVertexInHfShape.z); + + // PT: compute this once, reuse results (3 times!) + // PT: TODO: also reuse this in EE tests + PxReal fracX, fracZ; + const PxU32 vertexIndex = hfUtil.getHeightField().computeCellCoordinates( + boxVertexInHfShape.x * oneOverRowScale, boxVertexInHfShape.z * oneOverColumnScale, fracX, fracZ); + + const PxReal y = hfUtil.getHeightAtShapePoint2(vertexIndex, fracX, fracZ); + + const PxReal dy = boxVertexInHfShape.y - y; +#ifdef DISTANCE_BASED_TEST + if (hf.isDeltaHeightInsideExtent(dy, params.mContactDistance)) +#else + if (hf.isDeltaHeightInsideExtent(dy)) +#endif + { + const PxU32 faceIndex = hfUtil.getFaceIndexAtShapePointNoTest2(vertexIndex, fracX, fracZ); + if (faceIndex != 0xffffffff) + { +// PxcMaterialIndex material = hfShape.getTriangleMaterial(feature); + PxVec3 n; + //n = hfShape.getNormalAtShapePoint(boxVertexInHfShape.x, boxVertexInHfShape.z); +// n = hfUtil.getNormalAtShapePoint(boxVertexInHfShape.x, boxVertexInHfShape.z); + n = hfUtil.getNormalAtShapePoint2(vertexIndex, fracX, fracZ); + n = n.getNormalized(); + + contactBuffer +#ifdef DISTANCE_BASED_TEST + .contact(boxShapeAbsPose.transform(boxVertices[i]), hfShapeAbsPose.rotate(n), n.y*dy/* + contactDistance*/, faceIndex); +#else + .contact(boxShapeAbsPose.transform(boxVertices[i]), hfShapeAbsPose.rotate(n), n.y*dy + contactDistance, feature);//add contactDistance to compensate for fact that we don't support dist based contacts! See comment at start of funct. +#endif + } + } + } +////////~SAME CODE AS IN CONVEX-HF + } + } + + // Test box edges. + if (gCompileEdgeEdge) + { + // create helper class for the trace segment + GuContactHeightfieldTraceSegmentHelper traceSegmentHelper(hfUtil); + + for(PxU32 i=0; i<12; i++) + { +// PT: orientation culling +//float worldNormalY = (boxVertexNormalsInHfShape[edges[2*i]].y + boxVertexNormalsInHfShape[edges[2*i+1]].y)*0.5f; +float worldNormalY = boxVertexNormalsInHfShape[edges[2*i]].y + boxVertexNormalsInHfShape[edges[2*i+1]].y; +if(worldNormalY>0.0f) + continue; + + + const PxVec3& v0 = boxVerticesInHfShape[edges[2*i]]; + const PxVec3& v1 = boxVerticesInHfShape[edges[2*i+1]]; + +#if 0 + PxVec3 pt0 = boxShapeAbsPose.transform(boxVertices[edges[2*i]]); + PxVec3 pt1 = boxShapeAbsPose.transform(boxVertices[edges[2*i+1]]); + + PxVec3 worldNormal0 = boxShapeAbsPose.rotate(boxVertexNormals[edges[2*i]]); + PxVec3 worldNormal1 = boxShapeAbsPose.rotate(boxVertexNormals[edges[2*i+1]]); + + PxVec3 pt = (pt0 + pt1)*0.5f; + PxVec3 worldNormal = (worldNormal0 + worldNormal1)*0.5f; + + gVisualizeLine(pt, pt+worldNormal, context, PxDebugColor::eARGB_CYAN); +#endif + + + + if (hf.getThicknessFast()) // PT: errr...? not the same test as in the convex code? + { + if ((v0.y > hfExtreme) && (v1.y > hfExtreme)) continue; + } + else + { + if ((v0.y < hfExtreme) && (v1.y < hfExtreme)) continue; + } + GuContactTraceSegmentCallback cb(v1 - v0, + contactBuffer, + hfShapeAbsPose, params.mContactDistance/*, context.mRenderOutput*/); + + //context.mRenderOutput << PxVec3(1,0,0) << Gu::Debug::convertToPxMat44(transform1) + // << Cm::RenderOutput::LINES << v0+PxVec3(0.01f) << v1+PxVec3(0.01f); + //hfShape.traceSegment<PxcContactTraceSegmentCallback>(v0, v1, &cb); + traceSegmentHelper.traceSegment(v0, v1, &cb); + } + } + + // Test HeightField vertices. + if (gCompileHeightFieldVertex) + { + // Iterate over all HeightField vertices inside the bounds. + for(PxU32 row = minRow; row <= maxRow; row++) + { + for(PxU32 column = minColumn; column <= maxColumn; column++) + { + const PxU32 vertexIndex = row * hf.getNbColumnsFast() + column; + + //if (!hfShape.isCollisionVertex(vertexIndex)) continue; + if (!hfUtil.isCollisionVertex(vertexIndex, row, column)) continue; + + // Check if hf vertex is inside the box. + //PxVec3 hfVertex; + //hfVertex.set(hfShape.getRowScale() * row, hfShape.getHeightScale() * hfShape.getHeight(vertexIndex), hfShape.getColumnScale() * column); +// const PxVec3 hfVertex(shapeHeightField.rowScale * row, shapeHeightField.columnScale * hfShape.getHeight(vertexIndex), shapeHeightField.columnScale * column); + const PxVec3 hfVertex(hfGeom.rowScale * row, hfGeom.heightScale * hf.getHeight(vertexIndex), hfGeom.columnScale * column); + + const PxVec3 hfVertexInBoxShape = boxShape2HfShape.transformTranspose(hfVertex); + if ((PxAbs(hfVertexInBoxShape.x) - shapeBox.halfExtents.x - params.mContactDistance < 0) + && (PxAbs(hfVertexInBoxShape.y) - shapeBox.halfExtents.y - params.mContactDistance < 0) + && (PxAbs(hfVertexInBoxShape.z) - shapeBox.halfExtents.z - params.mContactDistance < 0)) + { + + // ptchernev: should have done this in HeightFieldShape + // check if this really is a collision vertex + //PxVec3 hfVertexNormal = thicknessNegOrNull ? hfShape.getVertexNormal(vertexIndex) : -hfShape.getVertexNormal(vertexIndex); +// PxVec3 hfVertexNormal = thicknessNegOrNull ? hfUtil.getVertexNormal(vertexIndex) : -hfUtil.getVertexNormal(vertexIndex); + const PxVec3 nrm = hfUtil.getVertexNormal(vertexIndex, row, column); + PxVec3 hfVertexNormal = thicknessNegOrNull ? nrm : -nrm; + hfVertexNormal = hfVertexNormal.getNormalized(); + const PxVec3 hfVertexNormalInBoxShape = boxShape2HfShape.rotateTranspose(hfVertexNormal); + PxVec3 normal; + PxReal depth; + if (!GuDepenetrateBox(hfVertexInBoxShape, -hfVertexNormalInBoxShape, shapeBox.halfExtents, params.mContactDistance, normal, depth)) + { + continue; + } + +// PxMat33 rot(boxShape2HfShape[0],boxShape2HfShape[1],boxShape2HfShape[2]); +// PxVec3 normalInHfShape = rot * (-normal); + PxVec3 normalInHfShape = boxShape2HfShape.rotate(-normal); + //hfShape.clipShapeNormalToVertexVoronoi(normalInHfShape, vertexIndex); + hfUtil.clipShapeNormalToVertexVoronoi(normalInHfShape, vertexIndex, row, column); + if (normalInHfShape.dot(hfVertexNormal) < PX_EPS_REAL) + { + // hmm, I dont think this can happen + continue; + } + normalInHfShape = normalInHfShape.getNormalized(); + const PxU32 faceIndex = hfUtil.getVertexFaceIndex(vertexIndex, row, column); + contactBuffer + .contact(hfShapeAbsPose.transform(hfVertex), hfShapeAbsPose.rotate(normalInHfShape), +#ifdef DISTANCE_BASED_TEST + -depth, +#else + -depth + contactDistance, //add contactDistance to compensate for fact that we don't support dist based contacts! See comment at start of funct. +#endif + faceIndex); + } + } + } + } + + return contactBuffer.count > 0; +} +}//Gu +}//physx diff --git a/PhysX_3.4/Source/GeomUtils/src/contact/GuLegacyContactCapsuleHeightField.cpp b/PhysX_3.4/Source/GeomUtils/src/contact/GuLegacyContactCapsuleHeightField.cpp new file mode 100644 index 00000000..a21a281d --- /dev/null +++ b/PhysX_3.4/Source/GeomUtils/src/contact/GuLegacyContactCapsuleHeightField.cpp @@ -0,0 +1,279 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2016 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 "GuDistancePointSegment.h" +#include "GuDistanceSegmentSegment.h" +#include "GuGeometryUnion.h" +#include "GuHeightFieldData.h" +#include "GuHeightFieldUtil.h" +#include "GuContactMethodImpl.h" +#include "GuContactBuffer.h" +#include "GuContactMethodImpl.h" +#include "GuInternal.h" + +#define DO_EDGE_EDGE 1 +#define DEBUG_HFNORMAL 0 +#define DEBUG_HFNORMALV 0 +#define DEBUG_RENDER_HFCONTACTS 0 + +#if DEBUG_RENDER_HFCONTACTS +#include "PxPhysics.h" +#include "PxScene.h" +#endif + +using namespace physx; +using namespace Gu; + +bool GuContactSphereHeightFieldShared(GU_CONTACT_METHOD_ARGS, bool isCapsule); + +namespace physx +{ +namespace Gu +{ +bool legacyContactCapsuleHeightfield(GU_CONTACT_METHOD_ARGS) +{ + // Get actual shape data + const PxCapsuleGeometry& shapeCapsule = shape0.get<const PxCapsuleGeometry>(); + const PxHeightFieldGeometryLL& hfGeom = shape1.get<const PxHeightFieldGeometryLL>(); + + const HeightField& hf = *static_cast<HeightField*>(hfGeom.heightField); + const HeightFieldUtil hfUtil(hfGeom, hf); + + const PxReal radius = shapeCapsule.radius; + const PxReal inflatedRadius = shapeCapsule.radius + params.mContactDistance; + const PxReal radiusSquared = inflatedRadius * inflatedRadius; + const PxReal halfHeight = shapeCapsule.halfHeight; + const PxReal eps = PxReal(0.1)*radius; + const PxReal epsSqr = eps*eps; + const PxReal oneOverRowScale = hfUtil.getOneOverRowScale(); + const PxReal oneOverColumnScale = hfUtil.getOneOverColumnScale(); + const PxReal radiusOverRowScale = inflatedRadius * PxAbs(oneOverRowScale); + const PxReal radiusOverColumnScale = inflatedRadius * PxAbs(oneOverColumnScale); + const PxTransform capsuleShapeToHfShape = transform1.transformInv(transform0); + + PxVec3 verticesInHfShape[2]; + verticesInHfShape[0] = capsuleShapeToHfShape.transform(PxVec3(-halfHeight, 0, 0)); + verticesInHfShape[1] = capsuleShapeToHfShape.transform(PxVec3(halfHeight, 0, 0)); + + PX_ASSERT(contactBuffer.count==0); + Gu::GeometryUnion u; + u.set(PxSphereGeometry(radius)); + PxTransform ts0(transform1.transform(verticesInHfShape[0])), ts1(transform1.transform(verticesInHfShape[1])); + GuContactSphereHeightFieldShared(u, shape1, ts0, transform1, params, cache, contactBuffer, renderOutput, true); + GuContactSphereHeightFieldShared(u, shape1, ts1, transform1, params, cache, contactBuffer, renderOutput, true); + + Segment worldCapsule; + worldCapsule.p0 = -getCapsuleHalfHeightVector(transform0, shapeCapsule); + worldCapsule.p1 = -worldCapsule.p0; + worldCapsule.p0 += transform0.p; + worldCapsule.p1 += transform0.p; + + const Segment capsuleSegmentInHfShape(verticesInHfShape[0], verticesInHfShape[1]); + + const PxU32 numCapsuleVertexContacts = contactBuffer.count; // remember how many contacts were stored as capsule vertex vs hf + + // test capsule edges vs HF + PxVec3 v0h = hfUtil.shape2hfp(verticesInHfShape[0]), v1h = hfUtil.shape2hfp(verticesInHfShape[1]); + const PxU32 absMinRow = hf.getMinRow(PxMin(v0h.x - radiusOverRowScale, v1h.x - radiusOverRowScale)); + const PxU32 absMaxRow = hf.getMaxRow(PxMax(v0h.x + radiusOverRowScale, v1h.x + radiusOverRowScale)); + const PxU32 absMinCol = hf.getMinColumn(PxMin(v0h.z - radiusOverColumnScale, v1h.z - radiusOverColumnScale)); + const PxU32 absMaxCol = hf.getMaxColumn(PxMax(v0h.z + radiusOverColumnScale, v1h.z + radiusOverColumnScale)); + if (DO_EDGE_EDGE) + for(PxU32 row = absMinRow; row <= absMaxRow; row++) + { + for(PxU32 column = absMinCol; column <= absMaxCol; column++) + { + //PxU32 vertexIndex = row * hfShape.getNbColumnsFast() + column; + const PxU32 vertexIndex = row * hf.getNbColumnsFast() + column; + const PxU32 firstEdge = 3 * vertexIndex; + + // omg I am sorry about this code but I can't find a simpler way: + // last column will only test edge 2 + // last row will only test edge 0 + // and most importantly last row and column will not go inside the for + const PxU32 minEi = PxU32((column == absMaxCol) ? 2 : 0); + const PxU32 maxEi = PxU32((row == absMaxRow) ? 1 : 3); + // perform capsule edge vs HF edge collision + for (PxU32 ei = minEi; ei < maxEi; ei++) + { + const PxU32 edgeIndex = firstEdge + ei; + + PX_ASSERT(vertexIndex == edgeIndex / 3); + PX_ASSERT(row == vertexIndex / hf.getNbColumnsFast()); + PX_ASSERT(column == vertexIndex % hf.getNbColumnsFast()); + + // look up the face indices adjacent to the current edge + PxU32 adjFaceIndices[2]; + const PxU32 adjFaceCount = hf.getEdgeTriangleIndices(edgeIndex, adjFaceIndices); + bool doCollision = false; + if(adjFaceCount == 2) + { + doCollision = hf.getMaterialIndex0(adjFaceIndices[0] >> 1) != PxHeightFieldMaterial::eHOLE + || hf.getMaterialIndex1(adjFaceIndices[1] >> 1) != PxHeightFieldMaterial::eHOLE; + } + else if(adjFaceCount == 1) + { + doCollision = (hf.getMaterialIndex0(adjFaceIndices[0] >> 1) != PxHeightFieldMaterial::eHOLE); + } + + if(doCollision) + { + PxVec3 origin; + PxVec3 direction; + hfUtil.getEdge(edgeIndex, vertexIndex, row, column, origin, direction); + + PxReal s, t; + const PxReal ll = distanceSegmentSegmentSquared( + capsuleSegmentInHfShape.p0, capsuleSegmentInHfShape.computeDirection(), origin, direction, &s, &t); + if ((ll < radiusSquared) && (t >= 0) && (t <= 1)) + { + + // We only want to test the vertices for either rows or columns. + // In this case we have chosen rows (ei == 0). + if (ei != 0 && (t == 0 || t == 1)) + continue; + + const PxVec3 pointOnCapsuleInHfShape = capsuleSegmentInHfShape.getPointAt(s); + const PxVec3 pointOnEdge = origin + t * direction; + const PxVec3 d = pointOnCapsuleInHfShape - pointOnEdge; + //if (hfShape.isDeltaHeightOppositeExtent(d.y)) + if (hf.isDeltaHeightOppositeExtent(d.y)) + { + + // Check if the current edge's normal is within any of it's 2 adjacent faces' Voronoi regions + // If it is, force the normal to that region's face normal + PxReal l; + PxVec3 n = hfUtil.computePointNormal(hfGeom.heightFieldFlags, d, transform1, ll, pointOnEdge.x, pointOnEdge.z, epsSqr, l); + PxVec3 localN = transform1.rotateInv(n); + for (PxU32 j = 0; j < adjFaceCount; j++) + { + const PxVec3 adjNormal = hfUtil.hf2shapen(hf.getTriangleNormalInternal(adjFaceIndices[j])).getNormalized(); + PxU32 triCell = adjFaceIndices[j] >> 1; + PxU32 triRow = triCell/hf.getNbColumnsFast(); + PxU32 triCol = triCell%hf.getNbColumnsFast(); + PxVec3 tv0, tv1, tv2, tvc; + hf.getTriangleVertices(adjFaceIndices[j], triRow, triCol, tv0, tv1, tv2); + tvc = hfUtil.hf2shapep((tv0+tv1+tv2)/3.0f); // compute adjacent triangle center + PxVec3 perp = adjNormal.cross(direction).getNormalized(); // adj face normal cross edge dir + if (perp.dot(tvc-origin) < 0.0f) // make sure perp is pointing toward the center of the triangle + perp = -perp; + // perp is now a vector sticking out of the edge of the triangle (also the test edge) pointing toward the center + // perpendicular to the normal (in triangle plane) + if (perp.dot(localN) > 0.0f) // if the normal is in perp halfspace, clamp it to Voronoi region + { + n = transform1.rotate(adjNormal); + break; + } + } + + const PxVec3 worldPoint = worldCapsule.getPointAt(s); + const PxVec3 p = worldPoint - n * radius; + PxU32 adjTri = adjFaceIndices[0]; + if(adjFaceCount == 2) + { + const PxU16 m0 = hf.getMaterialIndex0(adjFaceIndices[0] >> 1); + if(m0 == PxHeightFieldMaterial::eHOLE) + adjTri = adjFaceIndices[1]; + } + contactBuffer.contact(p, n, l-radius, adjTri); + #if DEBUG_HFNORMAL + printf("n=%.5f %.5f %.5f; d=%.5f\n", n.x, n.y, n.z, l-radius); + #if DEBUG_RENDER_HFCONTACTS + PxScene *s; PxGetPhysics().getScenes(&s, 1, 0); + Cm::RenderOutput((Cm::RenderBuffer&)s->getRenderBuffer()) << Cm::RenderOutput::LINES << PxDebugColor::eARGB_BLUE // red + << p << (p + n * 10.0f); + #endif + #endif + } + } + } + } + + // also perform capsule edge vs HF vertex collision + if (hfUtil.isCollisionVertex(vertexIndex, row, column)) + { + PxVec3 vertex(row * hfGeom.rowScale, hfGeom.heightScale * hf.getHeight(vertexIndex), column * hfGeom.columnScale); + PxReal s; + const PxReal ll = distancePointSegmentSquared(capsuleSegmentInHfShape, vertex, &s); + if (ll < radiusSquared) + { + const PxVec3 pointOnCapsuleInHfShape = capsuleSegmentInHfShape.getPointAt(s); + const PxVec3 d = pointOnCapsuleInHfShape - vertex; + //if (hfShape.isDeltaHeightOppositeExtent(d.y)) + if (hf.isDeltaHeightOppositeExtent(d.y)) + { + // we look through all prior capsule vertex vs HF face contacts and see + // if any of those share a face with hf_edge for the currently considered capsule_edge/hf_vertex contact + bool normalFromFace = false; + PxVec3 n; + PxReal l = 1.0f; + for (PxU32 iVertexContact = 0; iVertexContact < numCapsuleVertexContacts; iVertexContact++) + { + const ContactPoint& cp = contactBuffer.contacts[iVertexContact]; + PxU32 vi0, vi1, vi2; + hf.getTriangleVertexIndices(cp.internalFaceIndex1, vi0, vi1, vi2); + + const PxU32 vi = vertexIndex; + if ((cp.forInternalUse == 0) // if this is a face contact + && (vi == vi0 || vi == vi1 || vi == vi2)) // with one of the face's vertices matching this one + { + n = cp.normal; // then copy the normal from this contact + l = PxAbs(d.dot(n)); + normalFromFace = true; + break; + } + } + + if (!normalFromFace) + n = hfUtil.computePointNormal(hfGeom.heightFieldFlags, d, transform1, ll, vertex.x, vertex.z, epsSqr, l); + + const PxVec3 worldPoint = worldCapsule.getPointAt(s); + + const PxU32 faceIndex = hfUtil.getVertexFaceIndex(vertexIndex, row, column); + + const PxVec3 p = worldPoint - n * radius; + contactBuffer.contact(p, n, l-radius, faceIndex); + #if DEBUG_HFNORMAL + printf("n=%.5f %.5f %.5f; d=%.5f\n", n.x, n.y, n.z, l-radius); + #if DEBUG_RENDER_HFCONTACTS + PxScene *s; PxGetPhysics().getScenes(&s, 1, 0); + Cm::RenderOutput((Cm::RenderBuffer&)s->getRenderBuffer()) << Cm::RenderOutput::LINES << PxDebugColor::eARGB_BLUE // red + << p << (p + n * 10.0f); + #endif + #endif + } + } // if ll < radiusSquared + } // if isCollisionVertex + } // forEach HF column intersecting with capsule edge AABB + } // forEach HF row intersecting with capsule edge AABB + + return contactBuffer.count>0; +} +}//Gu +}//physx diff --git a/PhysX_3.4/Source/GeomUtils/src/contact/GuLegacyContactConvexHeightField.cpp b/PhysX_3.4/Source/GeomUtils/src/contact/GuLegacyContactConvexHeightField.cpp new file mode 100644 index 00000000..4103714c --- /dev/null +++ b/PhysX_3.4/Source/GeomUtils/src/contact/GuLegacyContactConvexHeightField.cpp @@ -0,0 +1,430 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2016 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 "GuLegacyTraceLineCallback.h" +#include "GuConvexMeshData.h" +#include "GuEdgeCache.h" +#include "GuConvexHelper.h" +#include "GuContactMethodImpl.h" +#include "GuContactBuffer.h" + +using namespace physx; +using namespace Gu; +using namespace shdfnd::aos; + +#define DISTANCE_BASED_TEST + +// ptchernev TODO: make sure these are ok before shipping +static const bool gCompileConvexVertex = true; +static const bool gCompileEdgeEdge = true; +static const bool gCompileHeightFieldVertex = true; + +/** +\param point vertex tested for penetration (in local hull space) +\param safeNormal if none of the faces are good this becomes the normal +\param normal the direction to translate vertex to depenetrate +\param distance the distance along normal to translate vertex to depenetrate +*/ +static bool GuDepenetrateConvex( const PxVec3& point, + const PxVec3& safeNormal, + const Gu::ConvexHullData& hull, + float contactDistance, + PxVec3& normal, + PxReal& distance, + const Cm::FastVertex2ShapeScaling& scaling, + bool isConvexScaleIdentity) +{ + PxVec3 faceNormal(PxReal(0)); + PxReal distance1 = -PX_MAX_REAL; // cant be more + PxReal distance2 = -PX_MAX_REAL; // cant be more + PxI32 poly1 = -1; + PxI32 poly2 = -2; + +// const Cm::FastVertex2ShapeScaling& scaling = context.mVertex2ShapeSkew[0]; + + for (PxU32 poly = 0; poly < hull.mNbPolygons; poly++) + { + PX_ALIGN(16, PxPlane) shapeSpacePlane; + if(isConvexScaleIdentity) + { + V4StoreA(V4LoadU(&hull.mPolygons[poly].mPlane.n.x), &shapeSpacePlane.n.x); + } + else + { + const PxPlane& vertSpacePlane = hull.mPolygons[poly].mPlane; + scaling.transformPlaneToShapeSpace(vertSpacePlane.n, vertSpacePlane.d, shapeSpacePlane.n, shapeSpacePlane.d);//transform plane into shape space + } + +#ifdef DISTANCE_BASED_TEST + // PT: I'm not really sure about contactDistance here + const PxReal d = shapeSpacePlane.distance(point) - contactDistance; +#else + const PxReal d = shapeSpacePlane.distance(point); +#endif + + if (d >= 0) + { + // no penetration at all + return false; + } + + //const PxVec3& n = plane.normal; + const PxReal proj = shapeSpacePlane.n.dot(safeNormal); + if (proj > 0) + { + if (d > distance1) // less penetration + { + distance1 = d; + faceNormal = shapeSpacePlane.n; + poly1 = PxI32(poly); + } + + // distance2 / d = 1 / proj + const PxReal tmp = d / proj; + if (tmp > distance2) + { + distance2 = tmp; + poly2 = PxI32(poly); + } + } + } + + if (poly1 == poly2) + { + PX_ASSERT(faceNormal.magnitudeSquared() != 0.0f); + normal = faceNormal; + distance = -distance1; + } + else + { + normal = safeNormal; + distance = -distance2; + } + + return true; +} + +//Box-Heightfield and Convex-Heightfield do not support positive values for contactDistance, +//and if in this case we would emit contacts normally, we'd cause things to jitter. +//as a workaround we add contactDistance to the distance values that we emit in contacts. +//this has the effect that the biasing will work exactly as if we had specified a legacy skinWidth of (contactDistance - restDistance) + +#include "GuContactMethodImpl.h" + +namespace physx +{ +namespace Gu +{ +bool legacyContactConvexHeightfield(GU_CONTACT_METHOD_ARGS) +{ + PX_UNUSED(cache); + PX_UNUSED(renderOutput); + +#ifndef DISTANCE_BASED_TEST + PX_WARN_ONCE(contactDistance > 0.0f, "PxcContactConvexHeightField: Convex-Heightfield does not support distance based contact generation! Ignoring contactOffset > 0!"); +#endif + + // Get actual shape data + const PxConvexMeshGeometryLL& shapeConvex = shape0.get<const PxConvexMeshGeometryLL>(); + const PxHeightFieldGeometryLL& hfGeom = shape1.get<const PxHeightFieldGeometryLL>(); + + Cm::Matrix34 convexShapeAbsPose(transform0); + Cm::Matrix34 hfShapeAbsPose(transform1); + + PX_ASSERT(contactBuffer.count==0); + + const Gu::HeightField& hf = *static_cast<Gu::HeightField*>(hfGeom.heightField); + const Gu::HeightFieldUtil hfUtil(hfGeom, hf); + + const bool isConvexScaleIdentity = shapeConvex.scale.isIdentity(); + Cm::FastVertex2ShapeScaling convexScaling; // PT: TODO: remove default ctor + if(!isConvexScaleIdentity) + convexScaling.init(shapeConvex.scale); + + const PxMat33& left = hfShapeAbsPose.m; + const PxMat33& right = convexShapeAbsPose.m; + + Cm::Matrix34 convexShape2HfShape(left.getInverse()* right, left.getInverse()*(convexShapeAbsPose.p - hfShapeAbsPose.p)); + Cm::Matrix34 convexVertex2World( right * convexScaling.getVertex2ShapeSkew(),convexShapeAbsPose.p ); + + // Allocate space for transformed vertices. + const Gu::ConvexHullData* PX_RESTRICT hull = shapeConvex.hullData; + PxVec3* PX_RESTRICT convexVerticesInHfShape = reinterpret_cast<PxVec3*>(PxAlloca(hull->mNbHullVertices*sizeof(PxVec3))); + + // Transform vertices to height field shape + PxMat33 convexShape2HfShape_rot(convexShape2HfShape[0],convexShape2HfShape[1],convexShape2HfShape[2]); + Cm::Matrix34 convexVertices2HfShape(convexShape2HfShape_rot*convexScaling.getVertex2ShapeSkew(), convexShape2HfShape[3]); + const PxVec3* const PX_RESTRICT hullVerts = hull->getHullVertices(); + for(PxU32 i = 0; i<hull->mNbHullVertices; i++) + convexVerticesInHfShape[i] = convexVertices2HfShape.transform(hullVerts[i]); + + PxVec3 convexBoundsInHfShapeMin( PX_MAX_REAL, PX_MAX_REAL, PX_MAX_REAL); + PxVec3 convexBoundsInHfShapeMax(-PX_MAX_REAL, -PX_MAX_REAL, -PX_MAX_REAL); + + // Compute bounds of convex in hf space + for(PxU32 i = 0; i<hull->mNbHullVertices; i++) + { + const PxVec3& v = convexVerticesInHfShape[i]; + + convexBoundsInHfShapeMin.x = PxMin(convexBoundsInHfShapeMin.x, v.x); + convexBoundsInHfShapeMin.y = PxMin(convexBoundsInHfShapeMin.y, v.y); + convexBoundsInHfShapeMin.z = PxMin(convexBoundsInHfShapeMin.z, v.z); + + convexBoundsInHfShapeMax.x = PxMax(convexBoundsInHfShapeMax.x, v.x); + convexBoundsInHfShapeMax.y = PxMax(convexBoundsInHfShapeMax.y, v.y); + convexBoundsInHfShapeMax.z = PxMax(convexBoundsInHfShapeMax.z, v.z); + } + + const bool thicknessNegOrNull = (hf.getThicknessFast() <= 0.0f); // PT: don't do this each time! FCMPs are slow. + + // Compute the height field extreme over the bounds area. + const PxReal oneOverRowScale = hfUtil.getOneOverRowScale(); + const PxReal oneOverColumnScale = hfUtil.getOneOverColumnScale(); + + PxU32 minRow; + PxU32 maxRow; + PxU32 minColumn; + PxU32 maxColumn; + + minRow = hf.getMinRow(convexBoundsInHfShapeMin.x * oneOverRowScale); + maxRow = hf.getMaxRow(convexBoundsInHfShapeMax.x * oneOverRowScale); + + minColumn = hf.getMinColumn(convexBoundsInHfShapeMin.z * oneOverColumnScale); + maxColumn = hf.getMaxColumn(convexBoundsInHfShapeMax.z * oneOverColumnScale); + + PxReal hfExtreme = hf.computeExtreme(minRow, maxRow, minColumn, maxColumn); + + hfExtreme *= hfGeom.heightScale; + + + // Return if convex is on the wrong side of the extreme. + if (thicknessNegOrNull) + { + if (convexBoundsInHfShapeMin.y > hfExtreme) return false; + } + else + { + if (convexBoundsInHfShapeMax.y < hfExtreme) return false; + } + + // Test convex vertices + if (gCompileConvexVertex) + { + for(PxU32 i=0; i<hull->mNbHullVertices; i++) + { + const PxVec3& convexVertexInHfShape = convexVerticesInHfShape[i]; + + //////// SAME CODE AS IN BOX-HF + const bool insideExtreme = thicknessNegOrNull ? (convexVertexInHfShape.y < hfExtreme) : (convexVertexInHfShape.y > hfExtreme); + + if (insideExtreme && hfUtil.isShapePointOnHeightField(convexVertexInHfShape.x, convexVertexInHfShape.z)) + { + // PT: compute this once, reuse results (3 times!) + // PT: TODO: also reuse this in EE tests + PxReal fracX, fracZ; + const PxU32 vertexIndex = hfUtil.getHeightField().computeCellCoordinates(convexVertexInHfShape.x * oneOverRowScale, convexVertexInHfShape.z * oneOverColumnScale, fracX, fracZ); + + const PxReal y = hfUtil.getHeightAtShapePoint2(vertexIndex, fracX, fracZ); + + const PxReal dy = convexVertexInHfShape.y - y; + #ifdef DISTANCE_BASED_TEST + if (hf.isDeltaHeightInsideExtent(dy, params.mContactDistance/**2.0f*/)) + #else + if (hf.isDeltaHeightInsideExtent(dy)) + #endif + { + const PxU32 faceIndex = hfUtil.getFaceIndexAtShapePointNoTest2(vertexIndex, fracX, fracZ); + if (faceIndex != 0xffffffff) + { + PxVec3 n; + n = hfUtil.getNormalAtShapePoint2(vertexIndex, fracX, fracZ); + n = n.getNormalized(); + + contactBuffer + #ifdef DISTANCE_BASED_TEST + .contact(convexVertex2World.transform(hullVerts[i]), hfShapeAbsPose.rotate(n), n.y*dy/* - contactDistance*/, faceIndex); + #else + .contact(convexVertex2World.transform(hullVerts[i]), hfShapeAbsPose.rotate(n), n.y*dy + contactDistance, faceIndex);//add contactDistance to compensate for fact that we don't support dist based contacts! See comment at start of funct. + #endif + } + } + } + ////////~SAME CODE AS IN BOX-HF + } + } + + // Test convex edges + if (gCompileEdgeEdge) + { + // create helper class for the trace segment + GuContactHeightfieldTraceSegmentHelper traceSegmentHelper(hfUtil); + + if(1) + { + PxU32 numPolygons = hull->mNbPolygons; + const Gu::HullPolygonData* polygons = hull->mPolygons; + const PxU8* vertexData = hull->getVertexData8(); + + ConvexEdge edges[512]; + PxU32 nbEdges = findUniqueConvexEdges(512, edges, numPolygons, polygons, vertexData); + + for(PxU32 i=0;i<nbEdges;i++) + { + const PxVec3 convexNormalInHfShape = convexVertices2HfShape.rotate(edges[i].normal); + if(convexNormalInHfShape.y>0.0f) + continue; + + const PxU8 vi0 = edges[i].vref0; + const PxU8 vi1 = edges[i].vref1; + + const PxVec3& sv0 = convexVerticesInHfShape[vi0]; + const PxVec3& sv1 = convexVerticesInHfShape[vi1]; + + if (thicknessNegOrNull) + { + if ((sv0.y > hfExtreme) && (sv1.y > hfExtreme)) continue; + } + else + { + if ((sv0.y < hfExtreme) && (sv1.y < hfExtreme)) continue; + } + GuContactTraceSegmentCallback cb(sv1 - sv0, contactBuffer, hfShapeAbsPose, params.mContactDistance); + traceSegmentHelper.traceSegment(sv0, sv1, &cb); + } + } + else + { + Gu::EdgeCache edgeCache; + PxU32 numPolygons = hull->mNbPolygons; + const Gu::HullPolygonData* polygons = hull->mPolygons; + const PxU8* vertexData = hull->getVertexData8(); + while (numPolygons--) + { + const Gu::HullPolygonData& polygon = *polygons++; + const PxU8* vRefBase = vertexData + polygon.mVRef8; + + PxU32 numEdges = polygon.mNbVerts; + + PxU32 a = numEdges - 1; + PxU32 b = 0; + while(numEdges--) + { + PxU8 vi0 = vRefBase[a]; + PxU8 vi1 = vRefBase[b]; + + + if(vi1 < vi0) + { + PxU8 tmp = vi0; + vi0 = vi1; + vi1 = tmp; + } + + a = b; + b++; + + // avoid processing edges 2x if possible (this will typically have cache misses about 5% of the time + // leading to 5% redundant work). + if (edgeCache.isInCache(vi0, vi1)) + continue; + + const PxVec3& sv0 = convexVerticesInHfShape[vi0]; + const PxVec3& sv1 = convexVerticesInHfShape[vi1]; + + if (thicknessNegOrNull) + { + if ((sv0.y > hfExtreme) && (sv1.y > hfExtreme)) + continue; + } + else + { + if ((sv0.y < hfExtreme) && (sv1.y < hfExtreme)) + continue; + } + GuContactTraceSegmentCallback cb(sv1 - sv0, contactBuffer, hfShapeAbsPose, params.mContactDistance); + traceSegmentHelper.traceSegment(sv0, sv1, &cb); + } + } + } + } + + // Test height field vertices + if (gCompileHeightFieldVertex) + { + // Iterate over HeightField vertices inside the projected box bounds. + for(PxU32 row = minRow; row <= maxRow; row++) + { + for(PxU32 column = minColumn; column <= maxColumn; column++) + { + const PxU32 vertexIndex = row * hf.getNbColumnsFast() + column; + + if (!hfUtil.isCollisionVertex(vertexIndex, row, column)) + continue; + + const PxVec3 hfVertex(hfGeom.rowScale * row, hfGeom.heightScale * hf.getHeight(vertexIndex), hfGeom.columnScale * column); + + const PxVec3 nrm = hfUtil.getVertexNormal(vertexIndex, row, column); + PxVec3 hfVertexNormal = thicknessNegOrNull ? nrm : -nrm; + hfVertexNormal = hfVertexNormal.getNormalized(); + const PxVec3 hfVertexNormalInConvexShape = convexShape2HfShape.rotateTranspose(hfVertexNormal); + PxVec3 hfVertexInConvexShape = convexShape2HfShape.transformTranspose(hfVertex); + PxReal depth; + PxVec3 normal; + if (!GuDepenetrateConvex(hfVertexInConvexShape, -hfVertexNormalInConvexShape, *hull, params.mContactDistance, normal, depth, + convexScaling, + isConvexScaleIdentity)) + { + continue; + } + PxVec3 normalInHfShape = convexShape2HfShape.rotate(-normal); + hfUtil.clipShapeNormalToVertexVoronoi(normalInHfShape, vertexIndex, row, column); + if (normalInHfShape.dot(hfVertexNormal) < PX_EPS_REAL) // AP scaffold: verify this is impossible + continue; + + normalInHfShape = normalInHfShape.getNormalized(); + PxU32 faceIndex = hfUtil.getVertexFaceIndex(vertexIndex, row, column); + contactBuffer + .contact(hfShapeAbsPose.transform(hfVertex), hfShapeAbsPose.rotate(normalInHfShape), + #ifdef DISTANCE_BASED_TEST + -depth, + #else + //add contactDistance to compensate for fact that we don't support dist based contacts! + // See comment at start of funct. + -depth + contactDistance, + #endif + faceIndex); + } + } + } + + return contactBuffer.count>0; +} +}//Gu +}//physx diff --git a/PhysX_3.4/Source/GeomUtils/src/contact/GuLegacyContactSphereHeightField.cpp b/PhysX_3.4/Source/GeomUtils/src/contact/GuLegacyContactSphereHeightField.cpp new file mode 100644 index 00000000..5c9ba896 --- /dev/null +++ b/PhysX_3.4/Source/GeomUtils/src/contact/GuLegacyContactSphereHeightField.cpp @@ -0,0 +1,290 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2016 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 "GuGeometryUnion.h" +#include "GuHeightFieldData.h" +#include "GuHeightFieldUtil.h" +#include "GuContactMethodImpl.h" +#include "GuContactBuffer.h" + +#define DEBUG_RENDER_HFCONTACTS 0 +#if DEBUG_RENDER_HFCONTACTS +#include "PxPhysics.h" +#include "PxScene.h" +#endif + +using namespace physx; +using namespace Gu; + +// Sphere-heightfield contact generation + +// this code is shared between capsule vertices and sphere +bool GuContactSphereHeightFieldShared(GU_CONTACT_METHOD_ARGS, bool isCapsule) +{ +#if 1 + PX_UNUSED(cache); + PX_UNUSED(renderOutput); + + // Get actual shape data + const PxSphereGeometry& shapeSphere = shape0.get<const PxSphereGeometry>(); + const PxHeightFieldGeometryLL& hfGeom = shape1.get<const PxHeightFieldGeometryLL>(); + + const Gu::HeightField& hf = *static_cast<Gu::HeightField*>(hfGeom.heightField); + const Gu::HeightFieldUtil hfUtil(hfGeom, hf); + + const PxReal radius = shapeSphere.radius; + const PxReal eps = PxReal(0.1) * radius; + + const PxVec3 sphereInHfShape = transform1.transformInv(transform0.p); + + PX_ASSERT(isCapsule || contactBuffer.count==0); + + const PxReal oneOverRowScale = hfUtil.getOneOverRowScale(); + const PxReal oneOverColumnScale = hfUtil.getOneOverColumnScale(); + + // check if the sphere is below the HF surface + if (hfUtil.isShapePointOnHeightField(sphereInHfShape.x, sphereInHfShape.z)) + { + + PxReal fracX, fracZ; + const PxU32 vertexIndex = hfUtil.getHeightField().computeCellCoordinates(sphereInHfShape.x * oneOverRowScale, sphereInHfShape.z * oneOverColumnScale, fracX, fracZ); + + // The sphere origin projects within the bounds of the heightfield in the X-Z plane +// const PxReal sampleHeight = hfShape.getHeightAtShapePoint(sphereInHfShape.x, sphereInHfShape.z); + const PxReal sampleHeight = hfUtil.getHeightAtShapePoint2(vertexIndex, fracX, fracZ); + + const PxReal deltaHeight = sphereInHfShape.y - sampleHeight; + //if (hfShape.isDeltaHeightInsideExtent(deltaHeight, eps)) + if (hf.isDeltaHeightInsideExtent(deltaHeight, eps)) + { + // The sphere origin is 'below' the heightfield surface + // Actually there is an epsilon involved to make sure the + // 'above' surface calculations can deliver a good normal + const PxU32 faceIndex = hfUtil.getFaceIndexAtShapePointNoTest2(vertexIndex, fracX, fracZ); + if (faceIndex != 0xffffffff) + { + + //hfShape.getAbsPoseFast().M.getColumn(1, hfShapeUp); + const PxVec3 hfShapeUp = transform1.q.getBasisVector1(); + + if (hf.getThicknessFast() <= 0) + contactBuffer.contact(transform0.p, hfShapeUp, deltaHeight-radius, faceIndex); + else + contactBuffer.contact(transform0.p, -hfShapeUp, -deltaHeight-radius, faceIndex); + + return true; + } + + return false; + } + + } + + const PxReal epsSqr = eps * eps; + + const PxReal inflatedRadius = radius + params.mContactDistance; + const PxReal inflatedRadiusSquared = inflatedRadius * inflatedRadius; + + const PxVec3 sphereInHF = hfUtil.shape2hfp(sphereInHfShape); + + const PxReal radiusOverRowScale = inflatedRadius * PxAbs(oneOverRowScale); + const PxReal radiusOverColumnScale = inflatedRadius * PxAbs(oneOverColumnScale); + + const PxU32 minRow = hf.getMinRow(sphereInHF.x - radiusOverRowScale); + const PxU32 maxRow = hf.getMaxRow(sphereInHF.x + radiusOverRowScale); + const PxU32 minColumn = hf.getMinColumn(sphereInHF.z - radiusOverColumnScale); + const PxU32 maxColumn = hf.getMaxColumn(sphereInHF.z + radiusOverColumnScale); + + // this assert is here because the following code depends on it for reasonable performance for high-count situations + PX_COMPILE_TIME_ASSERT(ContactBuffer::MAX_CONTACTS == 64); + + const PxU32 nbColumns = hf.getNbColumnsFast(); + +#define HFU Gu::HeightFieldUtil + PxU32 numFaceContacts = 0; + for (PxU32 i = 0; i<2; i++) + { + const bool facesOnly = (i == 0); + // first we go over faces-only meaning only contacts directly in Voronoi regions of faces + // at second pass we consider edges and vertices and clamp the normals to adjacent feature's normal + // if there was a prior contact. it is equivalent to clipping the normal to it's feature's Voronoi region + + for (PxU32 r = minRow; r < maxRow; r++) + { + for (PxU32 c = minColumn; c < maxColumn; c++) + { + + // x--x--x + // | x | + // x x x + // | x | + // x--x--x + PxVec3 closestPoints[11]; + PxU32 closestFeatures[11]; + PxU32 npcp = hfUtil.findClosestPointsOnCell( + r, c, sphereInHfShape, closestPoints, closestFeatures, facesOnly, !facesOnly, true); + + for(PxU32 pi = 0; pi < npcp; pi++) + { + PX_ASSERT(closestFeatures[pi] != 0xffffffff); + const PxVec3 d = sphereInHfShape - closestPoints[pi]; + + if (hf.isDeltaHeightOppositeExtent(d.y)) // See if we are 'above' the heightfield + { + const PxReal dMagSq = d.magnitudeSquared(); + + if (dMagSq > inflatedRadiusSquared) + // Too far above + continue; + + PxReal dMag = -1.0f; // dMag is sqrt(sMadSq) and comes up as a byproduct of other calculations in computePointNormal + PxVec3 n; // n is in world space, rotated by transform1 + PxU32 featureType = HFU::getFeatureType(closestFeatures[pi]); + if (featureType == HFU::eEDGE) + { + PxU32 edgeIndex = HFU::getFeatureIndex(closestFeatures[pi]); + PxU32 adjFaceIndices[2]; + const PxU32 adjFaceCount = hf.getEdgeTriangleIndices(edgeIndex, adjFaceIndices); + PxVec3 origin; + PxVec3 direction; + const PxU32 vertexIndex = edgeIndex / 3; + const PxU32 row = vertexIndex / nbColumns; + const PxU32 col = vertexIndex % nbColumns; + hfUtil.getEdge(edgeIndex, vertexIndex, row, col, origin, direction); + n = hfUtil.computePointNormal( + hfGeom.heightFieldFlags, d, transform1, dMagSq, + closestPoints[pi].x, closestPoints[pi].z, epsSqr, dMag); + PxVec3 localN = transform1.rotateInv(n); + // clamp the edge's normal to its Voronoi region + for (PxU32 j = 0; j < adjFaceCount; j++) + { + const PxVec3 adjNormal = hfUtil.hf2shapen(hf.getTriangleNormalInternal(adjFaceIndices[j])).getNormalized(); + PxU32 triCell = adjFaceIndices[j] >> 1; + PxU32 triRow = triCell/hf.getNbColumnsFast(); + PxU32 triCol = triCell%hf.getNbColumnsFast(); + PxVec3 tv0, tv1, tv2, tvc; + hf.getTriangleVertices(adjFaceIndices[j], triRow, triCol, tv0, tv1, tv2); + tvc = hfUtil.hf2shapep((tv0+tv1+tv2)/3.0f); // compute adjacent triangle center + PxVec3 perp = adjNormal.cross(direction).getNormalized(); // adj face normal cross edge dir + if (perp.dot(tvc-origin) < 0.0f) // make sure perp is pointing toward the center of the triangle + perp = -perp; + // perp is now a vector sticking out of the edge of the triangle (also the test edge) pointing toward the center + // perpendicular to the normal (in triangle plane) + if (perp.dot(localN) > 0.0f) // if the normal is in perp halfspace, clamp it to Voronoi region + { + n = transform1.rotate(adjNormal); + break; + } + } + } else if(featureType == HFU::eVERTEX) + { + // AP: these contacts are rare so hopefully it's ok + const PxU32 bufferCount = contactBuffer.count; + const PxU32 vertIndex = HFU::getFeatureIndex(closestFeatures[pi]); + EdgeData adjEdges[8]; + const PxU32 row = vertIndex / nbColumns; + const PxU32 col = vertIndex % nbColumns; + const PxU32 numAdjEdges = ::getVertexEdgeIndices(hf, vertIndex, row, col, adjEdges); + for (PxU32 iPrevEdgeContact = numFaceContacts; iPrevEdgeContact < bufferCount; iPrevEdgeContact++) + { + if (contactBuffer.contacts[iPrevEdgeContact].forInternalUse != HFU::eEDGE) + continue; // skip non-edge contacts (can be other vertex contacts) + + for (PxU32 iAdjEdge = 0; iAdjEdge < numAdjEdges; iAdjEdge++) + // does adjacent edge index for this vertex match a previously encountered edge index? + if (adjEdges[iAdjEdge].edgeIndex == contactBuffer.contacts[iPrevEdgeContact].internalFaceIndex1) + { + // if so, clamp the normal for this vertex to that edge's normal + n = contactBuffer.contacts[iPrevEdgeContact].normal; + dMag = PxSqrt(dMagSq); + break; + } + } + } + + if (dMag == -1.0f) + n = hfUtil.computePointNormal(hfGeom.heightFieldFlags, d, transform1, + dMagSq, closestPoints[pi].x, closestPoints[pi].z, epsSqr, dMag); + + PxVec3 p = transform0.p - n * radius; + #if DEBUG_RENDER_HFCONTACTS + printf("n=%.5f %.5f %.5f; ", n.x, n.y, n.z); + if (n.y < 0.8f) + int a = 1; + PxScene *s; PxGetPhysics().getScenes(&s, 1, 0); + Cm::RenderOutput((Cm::RenderBuffer&)s->getRenderBuffer()) << Cm::RenderOutput::LINES << PxDebugColor::eARGB_BLUE // red + << p << (p + n * 10.0f); + #endif + + // temporarily use the internalFaceIndex0 slot in the contact buffer for featureType + contactBuffer.contact( + p, n, dMag - radius, PxU16(featureType), HFU::getFeatureIndex(closestFeatures[pi])); + } + } + } + } + if (facesOnly) + numFaceContacts = contactBuffer.count; + } // for facesOnly = true to false + + if (!isCapsule) // keep the face labels as internalFaceIndex0 if this function is called from inside capsule/HF function + for (PxU32 k = 0; k < numFaceContacts; k++) + contactBuffer.contacts[k].forInternalUse = 0xFFFF; + + for (PxU32 k = numFaceContacts; k < contactBuffer.count; k++) + { + PX_ASSERT(contactBuffer.contacts[k].forInternalUse != HFU::eFACE); + PX_ASSERT(contactBuffer.contacts[k].forInternalUse <= HFU::eVERTEX); + PxU32 featureIndex = contactBuffer.contacts[k].internalFaceIndex1; + if (contactBuffer.contacts[k].forInternalUse == HFU::eEDGE) + contactBuffer.contacts[k].internalFaceIndex1 = hfUtil.getEdgeFaceIndex(featureIndex); + else if (contactBuffer.contacts[k].forInternalUse == HFU::eVERTEX) + { + PxU32 row = featureIndex/hf.getNbColumnsFast(); + PxU32 col = featureIndex%hf.getNbColumnsFast(); + contactBuffer.contacts[k].internalFaceIndex1 = hfUtil.getVertexFaceIndex(featureIndex, row, col); + } + } +#undef HFU + + return contactBuffer.count>0; +#endif +} + +namespace physx +{ +namespace Gu +{ +bool legacyContactSphereHeightfield(GU_CONTACT_METHOD_ARGS) +{ + return GuContactSphereHeightFieldShared(shape0, shape1, transform0, transform1, params, cache, contactBuffer, renderOutput, false); +} + +}//Gu +}//physx diff --git a/PhysX_3.4/Source/GeomUtils/src/contact/GuLegacyTraceLineCallback.h b/PhysX_3.4/Source/GeomUtils/src/contact/GuLegacyTraceLineCallback.h new file mode 100644 index 00000000..d89cfb24 --- /dev/null +++ b/PhysX_3.4/Source/GeomUtils/src/contact/GuLegacyTraceLineCallback.h @@ -0,0 +1,160 @@ +// This code contains NVIDIA Confidential Information and is disclosed to you +// under a form of NVIDIA software license agreement provided separately to you. +// +// Notice +// NVIDIA Corporation and its licensors retain all intellectual property and +// proprietary rights in and to this software and related documentation and +// any modifications thereto. Any use, reproduction, disclosure, or +// distribution of this software and related documentation without an express +// license agreement from NVIDIA Corporation is strictly prohibited. +// +// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES +// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO +// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, +// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. +// +// Information and code furnished is believed to be accurate and reliable. +// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such +// information or for any infringement of patents or other rights of third parties that may +// result from its use. No license is granted by implication or otherwise under any patent +// or patent rights of NVIDIA Corporation. Details are subject to change without notice. +// This code supersedes and replaces all information previously supplied. +// NVIDIA Corporation products are not authorized for use as critical +// components in life support devices or systems without express written approval of +// NVIDIA Corporation. +// +// Copyright (c) 2008-2016 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_CONTACTTRACESEGMENTCALLBACK_H +#define GU_CONTACTTRACESEGMENTCALLBACK_H + +#include "CmMatrix34.h" +#include "GuGeometryUnion.h" + +#include "GuHeightFieldUtil.h" +#include "CmRenderOutput.h" +#include "GuContactBuffer.h" + +namespace physx +{ +namespace Gu +{ +#define DISTANCE_BASED_TEST + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +struct GuContactTraceSegmentCallback +{ + PxVec3 mLine; + ContactBuffer& mContactBuffer; + Cm::Matrix34 mTransform; + PxReal mContactDistance; + PxU32 mPrevTriangleIndex; // currently only used to indicate first callback +// Cm::RenderOutput& mRO; + + PX_INLINE GuContactTraceSegmentCallback( + const PxVec3& line, Gu::ContactBuffer& contactBuffer, + Cm::Matrix34 transform, PxReal contactDistance + /*, Cm::RenderOutput& ro*/) + : mLine(line), mContactBuffer(contactBuffer), mTransform(transform), + mContactDistance(contactDistance), mPrevTriangleIndex(0xFFFFffff)//, mRO(ro) + { + } + + bool onEvent(PxU32 , PxU32* ) + { + return true; + } + + PX_INLINE bool faceHit(const Gu::HeightFieldUtil& /*hfUtil*/, const PxVec3& /*hitPoint*/, PxU32 /*triangleIndex*/, PxReal, PxReal) { return true; } + + // x,z is the point of projected face entry intercept in hf coords, rayHeight is at that same point + PX_INLINE bool underFaceHit( + const Gu::HeightFieldUtil& hfUtil, const PxVec3& triangleNormal, + const PxVec3& crossedEdge, PxF32 x, PxF32 z, PxF32 rayHeight, PxU32 triangleIndex) + { + if (mPrevTriangleIndex == 0xFFFFffff) // we only record under-edge contacts so we need at least 2 face hits to have the edge + { + mPrevTriangleIndex = triangleIndex; + //mPrevTriangleNormal = hfUtil.getTriangleNormal(triangleIndex); + return true; + } + + const Gu::HeightField& hf = hfUtil.getHeightField(); + PxF32 y = hfUtil.getHeightAtShapePoint(x, z); // TODO: optmization opportunity - this can be derived cheaply inside traceSegment + PxF32 dy = rayHeight - y; + + if (!hf.isDeltaHeightInsideExtent(dy, mContactDistance)) + return true; + + // add contact + PxVec3 n = crossedEdge.cross(mLine); + if (n.y < 0) // Make sure cross product is facing correctly before clipping + n = -n; + + if (n.y < 0) // degenerate case + return true; + + const PxReal ll = n.magnitudeSquared(); + if (ll > 0) // normalize + n *= PxRecipSqrt(ll); + else // degenerate case + return true; + + // Scale delta height so it becomes the "penetration" along the normal + dy *= n.y; + if (hf.getThicknessFast() > 0) + { + n = -n; + dy = -dy; + } + + // compute the contact point + const PxVec3 point(x, rayHeight, z); + //mRO << PxVec3(1,0,0) << Gu::Debug::convertToPxMat44(mTransform) + // << Cm::RenderOutput::LINES << point << point + triangleNormal; +#ifdef DISTANCE_BASED_TEST + mContactBuffer.contact( + mTransform.transform(point), mTransform.rotate(triangleNormal), dy, triangleIndex); +#else + // add gContactDistance to compensate for fact that we don't support dist based contacts in box/convex-hf! + // See comment at start of those functs. + mContactBuffer.contact( + mTransform.transform(point), mTransform.rotate(triangleNormal), + dy + mContactDistance, PXC_CONTACT_NO_FACE_INDEX, triangleIndex); +#endif + mPrevTriangleIndex = triangleIndex; + //mPrevTriangleNormal = triangleNormal; + return true; + } + +private: + GuContactTraceSegmentCallback& operator=(const GuContactTraceSegmentCallback&); +}; + +class GuContactHeightfieldTraceSegmentHelper +{ + PX_NOCOPY(GuContactHeightfieldTraceSegmentHelper) +public: + GuContactHeightfieldTraceSegmentHelper(const HeightFieldUtil& hfUtil) + : mHfUtil(hfUtil) + { + mHfUtil.computeLocalBounds(mLocalBounds); + } + + PX_INLINE void traceSegment(const PxVec3& aP0, const PxVec3& aP1, GuContactTraceSegmentCallback* aCallback) const + { + mHfUtil.traceSegment<GuContactTraceSegmentCallback, true, false>(aP0, aP1 - aP0, 1.0f, aCallback, mLocalBounds, true, NULL); + } + +private: + const HeightFieldUtil& mHfUtil; + PxBounds3 mLocalBounds; +}; + +}//Gu +}//physx + +#endif |