// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//  * Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
//  * Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//  * Neither the name of NVIDIA CORPORATION nor the names of its
//    contributors may be used to endorse or promote products derived
//    from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Copyright (c) 2008-2025 NVIDIA Corporation. All rights reserved.
// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.  

#include "geomutils/PxContactBuffer.h"

#include "GuContactPolygonPolygon.h"
#include "GuConvexHelper.h"
#include "GuInternal.h"
#include "GuSeparatingAxes.h"
#include "GuContactMethodImpl.h"

#include "foundation/PxFPU.h"
#include "foundation/PxAlloca.h"
#include "CmMatrix34.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 Cm;
using namespace 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 PxMat34& tr0, const PxMat34& 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 PxMat34& tr0, const PxMat34& 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.mInternalExtents.x, localAxis0, p0);
	float p1[3];
	BoxSupport(&polyData1.mInternal.mInternalExtents.x, 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.mInternalRadius);
	const float MaxRadius = selectMax(Radius1, polyData1.mInternal.mInternalRadius);
#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 PxMat34& 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 PxMat34& 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 PxMat34& m1to0,
										const 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 PxMat34& world0, const PxMat34& world1,
												const FastVertex2ShapeScaling& scaling0, const 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 PxMat34& 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 PxMat34& world0, const PxPlane& plane,
									const PxMat34& m0to1, const PxBounds3& aabb, PxReal contactDistance,
									const FastVertex2ShapeScaling& scaling)
{
//	EdgeCache edgeCache;	// PT: TODO: check this is actually useful
	const PxVec3* PX_RESTRICT vertices = polyData.mVerts;
	const HullPolygonData* PX_RESTRICT polygons = polyData.mPolygons;
	const PxU8* PX_RESTRICT vrefsBase = polyData.mPolygonVertexRefs;

	while(numPolygons--)
	{
		//Get current polygon
		const 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];

//			PxOrder(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 PxMat34& world0, const PxMat34& world1,
										const FastVertex2ShapeScaling& scaling0, const FastVertex2ShapeScaling& scaling1,
										const PxMat34& 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 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(HullPolygonData);
		for(PxU32 offset=0; offset < dataSize; offset+=128)
			PxPrefetchLine(polygons, offset);
	}

	for(PxU32 i=0; i < num; i++)
	{
		const 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 FastVertex2ShapeScaling& scaling0, const FastVertex2ShapeScaling& scaling1,
						const PxMat34& m0to1, const PxMat34& 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 PxMat34& world0, const PxMat34& world1,
											const FastVertex2ShapeScaling& scaling0, const FastVertex2ShapeScaling& scaling1,
											const PxMat34& m0to1, const PxMat34& 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(!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 PxMat34& world0, const PxMat34& world1,
										 const FastVertex2ShapeScaling& scaling0, const FastVertex2ShapeScaling& scaling1,
										 const PxMat34& 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 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 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 PxMat34& world0, const PxMat34& world1,
													const FastVertex2ShapeScaling& scaling0, const FastVertex2ShapeScaling& scaling1,
													const PxMat34& m0to1, const PxMat34& 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, PxContactBuffer& contactBuffer,
								const FastVertex2ShapeScaling& scaling0, const FastVertex2ShapeScaling& scaling1,
								bool idtScale0, bool idtScale1);

// 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 Gu::contactBoxConvex(GU_CONTACT_METHOD_ARGS)
{
	PX_UNUSED(renderOutput);
	PX_UNUSED(cache);

	const PxBoxGeometry& shapeBox = checkedCast<PxBoxGeometry>(shape0);
	const PxConvexMeshGeometry& shapeConvex = checkedCast<PxConvexMeshGeometry>(shape1);

	FastVertex2ShapeScaling idtScaling;

	const PxBounds3 boxBounds(-shapeBox.halfExtents, shapeBox.halfExtents);

	PolygonalData polyData0;
	PolygonalBox polyBox(shapeBox.halfExtents);
	polyBox.getPolygonalData(&polyData0);

	///////

	FastVertex2ShapeScaling convexScaling;
	PxBounds3 convexBounds;
	PolygonalData polyData1;
	const bool idtScale = getConvexData(shapeConvex, 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 Gu::contactConvexConvex(GU_CONTACT_METHOD_ARGS)
{
	PX_UNUSED(cache);
	PX_UNUSED(renderOutput);

	const PxConvexMeshGeometry& shapeConvex0 = checkedCast<PxConvexMeshGeometry>(shape0);
	const PxConvexMeshGeometry& shapeConvex1 = checkedCast<PxConvexMeshGeometry>(shape1);

	FastVertex2ShapeScaling scaling0, scaling1;
	PxBounds3 convexBounds0, convexBounds1;
	PolygonalData polyData0, polyData1;
	const bool idtScale0 = getConvexData(shapeConvex0, scaling0, convexBounds0, polyData0);
	const bool idtScale1 = getConvexData(shapeConvex1, scaling1, convexBounds1, polyData1);

	return GuContactHullHull(	polyData0, polyData1, convexBounds0, convexBounds1,
								transform0, transform1, params, contactBuffer,
								scaling0, scaling1, idtScale0, idtScale1);
}

static bool GuContactHullHull(	const PolygonalData& polyData0, const PolygonalData& polyData1,
								const PxBounds3& hullBounds0, const PxBounds3& hullBounds1,
								const PxTransform& transform0, const PxTransform& transform1,
								const NarrowPhaseParams& params, PxContactBuffer& contactBuffer,
								const FastVertex2ShapeScaling& scaling0, const FastVertex2ShapeScaling& scaling1,
								bool idtScale0, bool idtScale1)
{
	// Compute matrices
	const Matrix34FromTransform world0(transform0);
	const Matrix34FromTransform 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 Matrix34FromTransform m0to1(t0to1);
	const Matrix34FromTransform 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 HullPolygonData& HP0 = polyData0.mPolygons[c0];
	const HullPolygonData& HP1 = polyData1.mPolygons[c1];
	// PT: prefetching those guys saves ~600.000 cycles in convex-convex benchmark
	PxPrefetchLine(&HP0.mPlane);
	PxPrefetchLine(&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);
	}

	PxIntBool 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 PxMat34 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);
		const Matrix34FromTransform m0to1_Tweaked(t0to1);
		const Matrix34FromTransform m1to0_Tweaked(t1to0);

//////////////////////////////////////////////////
	//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;
}
