PhysX icon indicating copy to clipboard operation
PhysX copied to clipboard

Found bug in Gu::sweepSphereVSTri

Open bleston opened this issue 3 years ago • 2 comments

I found a bug in function Gu::sweepSphereVSTri. In some case, it will miss collision.

Below code show the bug. The capsule should hit the triangle at point hitPos. But PxGeometryQuery::sweep do not find the hit.

void testSweepSphereTriangle()
{
	const PxVec3 vertices[3] = { {7.33294010f, 79.6109009f, -26.9337997f}, {7.61456013f, 80.2574005f, -27.3008995f }, {7.33294010f, 80.2574005f, -26.9337997f} };
	const PxU32 indices[3] = { 0, 1, 2 };

	static float radius = 0.265350759f;
	static float halfHeight = 0.340350509f;

	const PxVec3 startPoint{ 7.74110079f,80.8543167f,-27.2595482f };
	const PxQuat rot{ 0.f, 0.f,0.707106829f,0.707106829f };
	const PxVec3 unitDir{ -0.929564953f,  -0.0281119142f,  0.367584974f };
	const PxReal distance{ 0.149537891f };
	
	const PxVec3 hitPos{ 7.59219694f, 80.2574005f, -27.2717476f };
	const PxReal hitDistance{ 0.0944127962f };

	const PxVec3 p0 = hitPos - unitDir * hitDistance;

	const physx::PxTransform pose0{ startPoint, rot};
	const physx::PxTransform poseTri{ PxIdentity };

	PxHitFlags hitFlags{ physx::PxHitFlag::eDEFAULT | physx::PxHitFlag::eMESH_BOTH_SIDES | physx::PxHitFlag::eASSUME_NO_INITIAL_OVERLAP };

	PxTriangleMeshDesc meshDesc;
	meshDesc.points.count = 3;
	meshDesc.points.data = vertices;
	meshDesc.points.stride = sizeof(PxVec3);
	meshDesc.triangles.count = 1;
	meshDesc.triangles.data = indices;
	meshDesc.triangles.stride = 3 * sizeof(PxU32);
	PxCookingParams params = gCooking->getParams();

	PxTriangleMesh* triMesh = NULL;
	triMesh = gCooking->createTriangleMesh(meshDesc, gPhysics->getPhysicsInsertionCallback());

	PxTriangleMeshGeometry triGeom{ triMesh };
	PxCapsuleGeometry capsuleGeom{ radius, halfHeight };
	

	bool isInitOverlap = PxGeometryQuery::overlap(capsuleGeom, pose0, triGeom, poseTri);
	printf("isInitOverlap = %d.\n", isInitOverlap);

	physx::PxSweepHit hit;
	PxU32 hitCount = PxGeometryQuery::sweep(unitDir, distance, capsuleGeom, pose0, triGeom, poseTri, hit, hitFlags);
	printf("\nSweep capsule hitCount = %d.\n", hitCount);
	if (hitCount > 0)
	{
		printf("hit position is (%f, %f, %f).\n", hit.position.x, hit.position.y, hit.position.z);
		printf("hit normal is (%f, %f, %f).\n", hit.normal.x, hit.normal.y, hit.normal.z);
		printf("hit distance is %f.\n", hit.distance);
	}

	PxTransform pose1{ startPoint + unitDir * distance, rot };
	bool isResultOverlap = PxGeometryQuery::overlap(capsuleGeom, pose1, triGeom, poseTri);
	printf("\nisResultOverlap = %d.\n", isResultOverlap);

	PxReal p0ToCapsule = PxGeometryQuery::pointDistance(p0, capsuleGeom, pose0);
	printf("\np0(%f, %f, %f) %s capsule\n", p0.x, p0.y, p0.z, (p0ToCapsule <= 0.f) ? "is in" : "is not in");
	physx::PxRaycastHit rayHits[1];
	hitCount = PxGeometryQuery::raycast(p0, unitDir, triGeom, poseTri, distance, hitFlags, 1, rayHits);
	printf("raycast p0 hitCount = %d.\n", hitCount);
	if (hitCount > 0)
	{
		printf("raycast hit position is (%f, %f, %f).\n", rayHits[0].position.x, rayHits[0].position.y, rayHits[0].position.z);
		printf("raycast hit normal is (%f, %f, %f).\n", rayHits[0].normal.x, rayHits[0].normal.y, rayHits[0].normal.z);
		printf("raycast hit distance is %f.\n", rayHits[0].distance);
	}

	triMesh->release();
}

I found the bug is in function Gu::sweepSphereVSTri. It says as below:

	//
	// Let's do some art!
	//
	// The triangle gets divided into the following areas (based on the barycentric coordinates (u,v)):
	//
	//               \   A0    /
	//                 \      /
	//                   \   /
	//                     \/ 0
	//            A02      *      A01
	//   u /              /   \          \ v
	//    *              /      \         *
	//                  /         \						.
	//               2 /            \ 1
	//          ------*--------------*-------
	//               /                 \				.
	//        A2    /        A12         \   A1
	//
	//
	// Based on the area where the computed triangle plane intersection point lies in, a different sweep test will be applied.
	//
	// A) A01, A02, A12  : Test sphere against the corresponding edge
	// B) A0, A1, A2     : Test sphere against the corresponding vertex
	//
	// Unfortunately, B) does not work for long, thin triangles. Hence there is some extra code which does a conservative check and
	// switches to edge tests if necessary.
	//

When test sphere against vertex, this function only test with sphere. This may miss contact. In this case, both edge should be tested.

Below patch can fix this problem.

 .../geomutils/src/sweep/GuSweepSphereTriangle.cpp  | 59 +++++++++++++---------
 1 file changed, 35 insertions(+), 24 deletions(-)

diff --git a/physx/source/geomutils/src/sweep/GuSweepSphereTriangle.cpp b/physx/source/geomutils/src/sweep/GuSweepSphereTriangle.cpp
index a8e8d9b9..3470aaaa 100644
--- a/physx/source/geomutils/src/sweep/GuSweepSphereTriangle.cpp
+++ b/physx/source/geomutils/src/sweep/GuSweepSphereTriangle.cpp
@@ -86,7 +86,7 @@ static PX_FORCE_INLINE PxU32 rayTriSpecial(const PxVec3& orig, const PxVec3& dir
 // Returns true if sphere can be tested against triangle vertex, false if edge test should be performed
 //
 // Uses a conservative approach to work for "sliver triangles" (long & thin) as well.
-static PX_FORCE_INLINE bool edgeOrVertexTest(const PxVec3& planeIntersectPoint, const PxVec3* PX_RESTRICT tri, PxU32 vertIntersectCandidate, PxU32 vert0, PxU32 vert1, PxU32& secondEdgeVert)
+static PX_FORCE_INLINE bool oneOrTwoEdgeTest(const PxVec3& planeIntersectPoint, const PxVec3* PX_RESTRICT tri, PxU32 vertIntersectCandidate, PxU32 vert0, PxU32 vert1, PxU32& secondEdgeVert)
 {
 	{
 		const PxVec3 edge0 = tri[vertIntersectCandidate] - tri[vert0];
@@ -116,15 +116,26 @@ static PX_FORCE_INLINE bool edgeOrVertexTest(const PxVec3& planeIntersectPoint,
 	return true;
 }
 
-static PX_FORCE_INLINE bool testRayVsSphereOrCapsule(PxReal& impactDistance, bool testSphere, const PxVec3& center, PxReal radius, const PxVec3& dir, const PxVec3* PX_RESTRICT verts, PxU32 e0, PxU32 e1)
+static PX_FORCE_INLINE bool testRayVsOneOrTwoCapsule(PxReal& impactDistance, bool testBothEdge, const PxVec3& center, PxReal radius, const PxVec3& dir, const PxVec3* PX_RESTRICT verts, PxU32 e0, PxU32 e1)
 {
-	if(testSphere)
+	if(testBothEdge)
 	{
 		PxReal t;
-		if(intersectRaySphere(center, dir, PX_MAX_F32, verts[e0], radius, t))
+		if (intersectRayCapsule(center, dir, verts[e0], verts[(e0+1) % 3], radius, t))
 		{
-			impactDistance = t;
-			return true;
+			if (t >= 0.0f/* && t<MinDist*/)
+			{
+				impactDistance = t;
+				return true;
+			}
+		}
+		else if (intersectRayCapsule(center, dir, verts[e0], verts[(e0 + 2) % 3], radius, t))
+		{
+			if (t >= 0.0f/* && t<MinDist*/)
+			{
+				impactDistance = t;
+				return true;
+			}
 		}
 	}
 	else
@@ -214,7 +225,7 @@ bool Gu::sweepSphereVSTri(const PxVec3* PX_RESTRICT triVerts, const PxVec3& norm
 	// switches to edge tests if necessary.
 	//
 
-	bool TestSphere;
+	bool TestBothEdge;
 	PxU32 e0,e1;
 	if(u<0.0f)
 	{
@@ -223,19 +234,19 @@ bool Gu::sweepSphereVSTri(const PxVec3* PX_RESTRICT triVerts, const PxVec3& norm
 			// 0 or 0-1 or 0-2
 			e0 = 0;
 			const PxVec3 intersectPoint = INTERSECT_POINT;
-			TestSphere = edgeOrVertexTest(intersectPoint, triVerts, 0, 1, 2, e1);
+			TestBothEdge = oneOrTwoEdgeTest(intersectPoint, triVerts, 0, 1, 2, e1);
 		}
 		else if(u+v>1.0f)
 		{
 			// 2 or 2-0 or 2-1
 			e0 = 2;
 			const PxVec3 intersectPoint = INTERSECT_POINT;
-			TestSphere = edgeOrVertexTest(intersectPoint, triVerts, 2, 0, 1, e1);
+			TestBothEdge = oneOrTwoEdgeTest(intersectPoint, triVerts, 2, 0, 1, e1);
 		}
 		else
 		{
 			// 0-2
-			TestSphere = false;
+			TestBothEdge = false;
 			e0 = 0;
 			e1 = 2;
 		}
@@ -249,12 +260,12 @@ bool Gu::sweepSphereVSTri(const PxVec3* PX_RESTRICT triVerts, const PxVec3& norm
 				// 1 or 1-0 or 1-2
 				e0 = 1;
 				const PxVec3 intersectPoint = INTERSECT_POINT;
-				TestSphere = edgeOrVertexTest(intersectPoint, triVerts, 1, 0, 2, e1);
+				TestBothEdge = oneOrTwoEdgeTest(intersectPoint, triVerts, 1, 0, 2, e1);
 			}
 			else
 			{
 				// 0-1
-				TestSphere = false;
+				TestBothEdge = false;
 				e0 = 0;
 				e1 = 1;
 			}
@@ -263,12 +274,12 @@ bool Gu::sweepSphereVSTri(const PxVec3* PX_RESTRICT triVerts, const PxVec3& norm
 		{
 			PX_ASSERT(u+v>=1.0f);	// Else hit triangle
 			// 1-2
-			TestSphere = false;
+			TestBothEdge = false;
 			e0 = 1;
 			e1 = 2;
 		}
 	}
-	return testRayVsSphereOrCapsule(impactDistance, TestSphere, center, radius, dir, triVerts, e0, e1);
+	return testRayVsOneOrTwoCapsule(impactDistance, TestBothEdge, center, radius, dir, triVerts, e0, e1);
 }
 
 bool Gu::sweepSphereTriangles(	PxU32 nbTris, const PxTriangle* PX_RESTRICT triangles,							// Triangle data
@@ -451,7 +462,7 @@ bool Gu::sweepSphereVSQuad(const PxVec3* PX_RESTRICT quadVerts, const PxVec3& no
 	#define INTERSECT_POINT_Q (quadVerts[1]*u) + (quadVerts[2]*v) + (quadVerts[0] * (1.0f-u-v))
 
 	Ps::swap(u,v);
-	bool TestSphere;
+	bool TestBothEdge;
 	PxU32 e0,e1;
 	if(u<0.0f)
 	{
@@ -460,19 +471,19 @@ bool Gu::sweepSphereVSQuad(const PxVec3* PX_RESTRICT quadVerts, const PxVec3& no
 			// 0 or 0-1 or 0-2
 			e0 = 0;
 			const PxVec3 intersectPoint = INTERSECT_POINT_Q;
-			TestSphere = edgeOrVertexTest(intersectPoint, quadVerts, 0, 1, 2, e1);
+			TestBothEdge = oneOrTwoEdgeTest(intersectPoint, quadVerts, 0, 1, 2, e1);
 		}
 		else if(v>1.0f)
 		{
 			// 1 or 1-0 or 1-3
 			e0 = 1;
 			const PxVec3 intersectPoint = INTERSECT_POINT_Q;
-			TestSphere = edgeOrVertexTest(intersectPoint, quadVerts, 1, 0, 3, e1);
+			TestBothEdge = oneOrTwoEdgeTest(intersectPoint, quadVerts, 1, 0, 3, e1);
 		}
 		else
 		{
 			// 0-1
-			TestSphere = false;
+			TestBothEdge = false;
 			e0 = 0;
 			e1 = 1;
 		}
@@ -484,19 +495,19 @@ bool Gu::sweepSphereVSQuad(const PxVec3* PX_RESTRICT quadVerts, const PxVec3& no
 			// 2 or 2-0 or 2-3
 			e0 = 2;
 			const PxVec3 intersectPoint = INTERSECT_POINT_Q;
-			TestSphere = edgeOrVertexTest(intersectPoint, quadVerts, 2, 0, 3, e1);
+			TestBothEdge = oneOrTwoEdgeTest(intersectPoint, quadVerts, 2, 0, 3, e1);
 		}
 		else if(v>1.0f)
 		{
 			// 3 or 3-1 or 3-2
 			e0 = 3;
 			const PxVec3 intersectPoint = INTERSECT_POINT_Q;
-			TestSphere = edgeOrVertexTest(intersectPoint, quadVerts, 3, 1, 2, e1);
+			TestBothEdge = oneOrTwoEdgeTest(intersectPoint, quadVerts, 3, 1, 2, e1);
 		}
 		else
 		{
 			// 2-3
-			TestSphere = false;
+			TestBothEdge = false;
 			e0 = 2;
 			e1 = 3;
 		}
@@ -506,7 +517,7 @@ bool Gu::sweepSphereVSQuad(const PxVec3* PX_RESTRICT quadVerts, const PxVec3& no
 		if(v<0.0f)
 		{
 			// 0-2
-			TestSphere = false;
+			TestBothEdge = false;
 			e0 = 0;
 			e1 = 2;
 		}
@@ -514,11 +525,11 @@ bool Gu::sweepSphereVSQuad(const PxVec3* PX_RESTRICT quadVerts, const PxVec3& no
 		{
 			PX_ASSERT(v>=1.0f);	// Else hit quad
 			// 1-3
-			TestSphere = false;
+			TestBothEdge = false;
 			e0 = 1;
 			e1 = 3;
 		}
 	}
-	return testRayVsSphereOrCapsule(impactDistance, TestSphere, center, radius, dir, quadVerts, e0, e1);
+	return testRayVsOneOrTwoCapsule(impactDistance, TestBothEdge, center, radius, dir, quadVerts, e0, e1);
 }
 

bleston avatar Aug 24 '22 10:08 bleston

Thank you, yes, I think this is a duplicate of https://github.com/NVIDIAGameWorks/PhysX/issues/501

Pierre-Terdiman avatar Aug 24 '22 12:08 Pierre-Terdiman

Thank you, yes, I think this is a duplicate of #501

Thanks

bleston avatar Aug 25 '22 02:08 bleston