// 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 "GuSweepTests.h"
#include "GuSweepMesh.h"
#include "GuInternal.h"
#include "GuConvexUtilsInternal.h"
#include "CmScaling.h"
#include "GuSweepMTD.h"
#include "GuVecBox.h"
#include "GuVecCapsule.h"
#include "GuSweepBoxTriangle_SAT.h"
#include "GuSweepCapsuleTriangle.h"
#include "GuSweepSphereTriangle.h"
#include "GuDistancePointTriangle.h"
#include "GuCapsule.h"
#include "CmMatrix34.h"

using namespace physx;
using namespace Gu;
using namespace Cm;
using namespace aos;

#include "GuSweepConvexTri.h"

///////////////////////////////////////////////////////////////////////////////

static bool sweepSphereTriangle(const PxTriangle& tri,
								const PxVec3& center, PxReal radius,
								const PxVec3& unitDir, PxReal distance,
								PxGeomSweepHit& hit, PxVec3& triNormalOut,
								PxHitFlags hitFlags, bool isDoubleSided)
{
	const bool meshBothSides = hitFlags & PxHitFlag::eMESH_BOTH_SIDES;
	if(!(hitFlags & PxHitFlag::eASSUME_NO_INITIAL_OVERLAP))
	{
		const bool doBackfaceCulling = !isDoubleSided && !meshBothSides;

		// PT: test if shapes initially overlap
		// PT: add culling here for now, but could be made more efficiently...

		// Create triangle normal
		PxVec3 denormalizedNormal;
		tri.denormalizedNormal(denormalizedNormal);

		// Backface culling
		if(doBackfaceCulling && (denormalizedNormal.dot(unitDir) > 0.0f))
			return false;

		float s_unused, t_unused;
		const PxVec3 cp = closestPtPointTriangle(center, tri.verts[0], tri.verts[1], tri.verts[2], s_unused, t_unused);
		const PxReal dist2 = (cp - center).magnitudeSquared();
		if(dist2<=radius*radius)
		{
			triNormalOut = denormalizedNormal.getNormalized();
			return setInitialOverlapResults(hit, unitDir, 0);
		}
	}
	
	return sweepSphereTriangles(1, &tri,
								center, radius,
								unitDir, distance,
								NULL,
								hit, triNormalOut,
								isDoubleSided, meshBothSides, false, false);
}

///////////////////////////////////////////////////////////////////////////////

SweepShapeMeshHitCallback::SweepShapeMeshHitCallback(CallbackMode::Enum inMode, const PxHitFlags& hitFlags, bool flipNormal, float distCoef) :
	MeshHitCallback<PxGeomRaycastHit>	(inMode),
	mHitFlags							(hitFlags),
	mStatus								(false),
	mInitialOverlap						(false),
	mFlipNormal							(flipNormal),
	mDistCoeff							(distCoef)
{		
}

///////////////////////////////////////////////////////////////////////////////

SweepCapsuleMeshHitCallback::SweepCapsuleMeshHitCallback(
	PxGeomSweepHit& sweepHit, const PxMat34& worldMatrix, PxReal distance, bool meshDoubleSided,
	const Capsule& capsule, const PxVec3& unitDir, const PxHitFlags& hitFlags, bool flipNormal, float distCoef) :
	SweepShapeMeshHitCallback	(CallbackMode::eMULTIPLE, hitFlags, flipNormal, distCoef),
	mSweepHit					(sweepHit),
	mVertexToWorldSkew			(worldMatrix),	
	mTrueSweepDistance			(distance),
	mBestAlignmentValue			(2.0f),
	mBestDist					(distance + GU_EPSILON_SAME_DISTANCE),
	mCapsule					(capsule),
	mUnitDir					(unitDir),
	mMeshDoubleSided			(meshDoubleSided),
	mIsSphere					(capsule.p0 == capsule.p1)
{
	mSweepHit.distance = mTrueSweepDistance;
}

PxAgain SweepCapsuleMeshHitCallback::processHit( // all reported coords are in mesh local space including hit.position
												const PxGeomRaycastHit& aHit, const PxVec3& v0, const PxVec3& v1, const PxVec3& v2, PxReal& shrunkMaxT, const PxU32*)
{
	const PxTriangle tmpt(	mVertexToWorldSkew.transform(v0),
							mVertexToWorldSkew.transform(mFlipNormal ? v2 : v1),
							mVertexToWorldSkew.transform(mFlipNormal ? v1 : v2));

	PxGeomSweepHit localHit;	// PT: TODO: ctor!
	PxVec3 triNormal;
	// pick a farther hit within distEpsilon that is more opposing than the previous closest hit
	// make it a relative epsilon to make sure it still works with large distances
	const PxReal distEpsilon = GU_EPSILON_SAME_DISTANCE * PxMax(1.0f, mSweepHit.distance); 
	const float minD = mSweepHit.distance + distEpsilon;
	if(mIsSphere)
	{
		if(!::sweepSphereTriangle(	tmpt,
									mCapsule.p0, mCapsule.radius,
									mUnitDir, minD,
									localHit, triNormal,
									mHitFlags, mMeshDoubleSided))
			return true;
	}
	else
	{
		// PT: this one is safe because cullbox is NULL (no need to allocate one more triangle)		
		if(!sweepCapsuleTriangles_Precise(	1, &tmpt,
											mCapsule,
											mUnitDir, minD,
											NULL,
											localHit, triNormal,
											mHitFlags, mMeshDoubleSided,
											NULL))
			return true;
	}

	const PxReal alignmentValue = computeAlignmentValue(triNormal, mUnitDir);
	if(keepTriangle(localHit.distance, alignmentValue, mBestDist, mBestAlignmentValue, mTrueSweepDistance))
	{
		mBestAlignmentValue = alignmentValue;

		// AP: need to shrink the sweep distance passed into sweepCapsuleTriangles for correctness so that next sweep is closer		
		shrunkMaxT = localHit.distance * mDistCoeff; // shrunkMaxT is scaled

		mBestDist = PxMin(mBestDist, localHit.distance); // exact lower bound
		mSweepHit.flags		= localHit.flags;
		mSweepHit.distance	= localHit.distance;
		mSweepHit.normal	= localHit.normal;
		mSweepHit.position	= localHit.position;
		mSweepHit.faceIndex	= aHit.faceIndex;

		mStatus = true;
		//ML:this is the initial overlap condition
		if(localHit.distance == 0.0f)
		{
			mInitialOverlap = true;
			return false;
		}
		if(mHitFlags & PxHitFlag::eANY_HIT)
			return false; // abort traversal
	}
	///
	else if(keepTriangleBasic(localHit.distance, mBestDist, mTrueSweepDistance))
	{
		mSweepHit.distance = localHit.distance;
		mBestDist = PxMin(mBestDist, localHit.distance); // exact lower bound
	}
	///
	return true;
}

bool SweepCapsuleMeshHitCallback::finalizeHit(	PxGeomSweepHit& sweepHit, const Capsule& lss, const PxTriangleMeshGeometry& triMeshGeom,
												const PxTransform& pose, bool isDoubleSided) const
{
	if(!mStatus)
		return false;

	if(mInitialOverlap)
	{
		// PT: TODO: consider using 'setInitialOverlapResults' here
		bool hasContacts = false;
		if(mHitFlags & PxHitFlag::eMTD)
		{
			const Vec3V p0 = V3LoadU(mCapsule.p0);
			const Vec3V p1 = V3LoadU(mCapsule.p1);
			const FloatV radius = FLoad(lss.radius);
			CapsuleV capsuleV;
			capsuleV.initialize(p0, p1, radius);

			//we need to calculate the MTD
			hasContacts = computeCapsule_TriangleMeshMTD(triMeshGeom, pose, capsuleV, mCapsule.radius, isDoubleSided, sweepHit);
		}
		setupSweepHitForMTD(sweepHit, hasContacts, mUnitDir);
	}
	else
	{
		mSweepHit.distance = mBestDist;
		sweepHit.flags = PxHitFlag::eNORMAL | PxHitFlag::ePOSITION | PxHitFlag::eFACE_INDEX;
	}
	return true;
}

///////////////////////////////////////////////////////////////////////////////

bool sweepCapsule_MeshGeom(GU_CAPSULE_SWEEP_FUNC_PARAMS)
{
	PX_UNUSED(threadContext);
	PX_UNUSED(capsuleGeom_);
	PX_UNUSED(capsulePose_);

	PX_ASSERT(geom.getType() == PxGeometryType::eTRIANGLEMESH);
	const PxTriangleMeshGeometry& meshGeom = static_cast<const PxTriangleMeshGeometry&>(geom);

	TriangleMesh* meshData = static_cast<TriangleMesh*>(meshGeom.triangleMesh);

	return Midphase::sweepCapsuleVsMesh(meshData, meshGeom, pose, lss, unitDir, distance, sweepHit, hitFlags, inflation);
}

///////////////////////////////////////////////////////////////////////////////

	// same as 'mat.transform(p)' but using SIMD
	static PX_FORCE_INLINE Vec4V transformV(const Vec4V p, const PxMat34Padded& mat)
	{
		Vec4V ResV = V4Scale(V4LoadU(&mat.m.column0.x), V4GetX(p));
		ResV = V4ScaleAdd(V4LoadU(&mat.m.column1.x), V4GetY(p), ResV);
		ResV = V4ScaleAdd(V4LoadU(&mat.m.column2.x), V4GetZ(p), ResV);
		ResV = V4Add(ResV, V4LoadU(&mat.p.x));	// PT: this load is safe thanks to padding
		return ResV;
	}

///////////////////////////////////////////////////////////////////////////////

SweepBoxMeshHitCallback::SweepBoxMeshHitCallback(	CallbackMode::Enum mode_, const PxMat34Padded& meshToBox, PxReal distance, bool bothTriangleSidesCollide,
													const Box& box, const PxVec3& localMotion, const PxVec3& localDir, const PxVec3& unitDir,
													const PxHitFlags& hitFlags, PxReal inflation, bool flipNormal, float distCoef) :
	SweepShapeMeshHitCallback	(mode_, hitFlags, flipNormal,distCoef),
	mMeshToBox					(meshToBox),
	mDist						(distance),
	mBox						(box),
	mLocalDir					(localDir),
	mWorldUnitDir				(unitDir),
	mInflation					(inflation),
	mBothTriangleSidesCollide	(bothTriangleSidesCollide)
{
	mLocalMotionV = V3LoadU(localMotion);
	mDistV = FLoad(distance);
	mDist0 = distance;
	mOneOverDir = PxVec3(
		mLocalDir.x!=0.0f ? 1.0f/mLocalDir.x : 0.0f,
		mLocalDir.y!=0.0f ? 1.0f/mLocalDir.y : 0.0f,
		mLocalDir.z!=0.0f ? 1.0f/mLocalDir.z : 0.0f);
}

PxAgain SweepBoxMeshHitCallback::processHit( // all reported coords are in mesh local space including hit.position
											const PxGeomRaycastHit& meshHit, const PxVec3& lp0, const PxVec3& lp1, const PxVec3& lp2, PxReal& shrinkMaxT, const PxU32*)
{
	if(mHitFlags & PxHitFlag::ePRECISE_SWEEP)
	{
		const PxTriangle currentTriangle(
				mMeshToBox.transform(lp0),
				mMeshToBox.transform(mFlipNormal ? lp2 : lp1),
				mMeshToBox.transform(mFlipNormal ? lp1 : lp2));

		PxF32 t = PX_MAX_REAL; // PT: could be better!
		if(!triBoxSweepTestBoxSpace(currentTriangle, mBox.extents, mLocalDir, mOneOverDir, mDist, t, !mBothTriangleSidesCollide))
			return true;

		if(t <= mDist)
		{
			// PT: test if shapes initially overlap
			mDist				= t;
			shrinkMaxT			= t * mDistCoeff; // shrunkMaxT is scaled
			mMinClosestA		= V3LoadU(currentTriangle.verts[0]); // PT: this is arbitrary
			mMinNormal			= V3LoadU(-mWorldUnitDir);
			mStatus				= true;
			mMinTriangleIndex	= meshHit.faceIndex;
			mHitTriangle		= currentTriangle;
			if(t == 0.0f)
			{
				mInitialOverlap = true;
				return false; // abort traversal
			}
		}
	}
	else
	{
		const FloatV zero = FZero();

		// PT: SIMD code similar to:
		//	const Vec3V triV0 = V3LoadU(mMeshToBox.transform(lp0));
		//	const Vec3V triV1 = V3LoadU(mMeshToBox.transform(lp1));
		//	const Vec3V triV2 = V3LoadU(mMeshToBox.transform(lp2));
		//
		// SIMD version works but we need to ensure all loads are safe.
		// For incoming vertices they should either come from the vertex array or from a binary deserialized file.
		// For the vertex array we can just allocate one more vertex. For the binary file it should be ok as soon
		// as vertices aren't the last thing serialized in the file.
		// For the matrix only the last column is a problem, and we can easily solve that with some padding in the local class.
		const Vec3V triV0 = Vec3V_From_Vec4V(transformV(V4LoadU(&lp0.x), mMeshToBox));
		const Vec3V triV1 = Vec3V_From_Vec4V(transformV(V4LoadU(mFlipNormal ? &lp2.x : &lp1.x), mMeshToBox));
		const Vec3V triV2 = Vec3V_From_Vec4V(transformV(V4LoadU(mFlipNormal ? &lp1.x : &lp2.x), mMeshToBox));

		if(!mBothTriangleSidesCollide)
		{
			const Vec3V triNormal = V3Cross(V3Sub(triV2, triV1),V3Sub(triV0, triV1)); 
			if(FAllGrtrOrEq(V3Dot(triNormal, mLocalMotionV), zero))
				return true;
		}

		const Vec3V zeroV = V3Zero();
		const Vec3V boxExtents = V3LoadU(mBox.extents);
		const BoxV boxV(zeroV, boxExtents);

		const TriangleV triangleV(triV0, triV1, triV2);

		FloatV lambda;   
		Vec3V closestA, normal;//closestA and normal is in the local space of convex hull
		const LocalConvex<TriangleV> convexA(triangleV);
		const LocalConvex<BoxV> convexB(boxV);
		const Vec3V initialSearchDir = V3Sub(triangleV.getCenter(), boxV.getCenter());
		if(!gjkRaycastPenetration<LocalConvex<TriangleV>, LocalConvex<BoxV> >(convexA, convexB, initialSearchDir, zero, zeroV, mLocalMotionV, lambda, normal, closestA, mInflation, false))
			return true;

		mStatus = true;
		mMinClosestA = closestA;
		mMinTriangleIndex = meshHit.faceIndex;
		if(FAllGrtrOrEq(zero, lambda)) // lambda < 0? => initial overlap
		{
			mInitialOverlap = true;
			shrinkMaxT = 0.0f;
			mDistV = zero;
			mDist = 0.0f;
			mMinNormal = V3LoadU(-mWorldUnitDir);
			return false;
		}

		PxF32 f;
		FStore(lambda, &f);
		mDist = f*mDist; // shrink dist
		mLocalMotionV = V3Scale(mLocalMotionV, lambda); // shrink localMotion
		mDistV = FMul(mDistV, lambda); // shrink distV
		mMinNormal = normal;
		if(mDist * mDistCoeff < shrinkMaxT) // shrink shrinkMaxT
			shrinkMaxT = mDist * mDistCoeff; // shrunkMaxT is scaled

		//mHitTriangle = currentTriangle;
		V3StoreU(triV0, mHitTriangle.verts[0]);
		V3StoreU(triV1, mHitTriangle.verts[1]);
		V3StoreU(triV2, mHitTriangle.verts[2]);
	}
	return true;
}

bool SweepBoxMeshHitCallback::finalizeHit(	PxGeomSweepHit& sweepHit, const PxTriangleMeshGeometry& triMeshGeom, const PxTransform& pose,
											const PxTransform& boxTransform, const PxVec3& localDir,
											bool meshBothSides, bool isDoubleSided) const
{
	if(!mStatus)
		return false;

	Vec3V minClosestA = mMinClosestA;
	Vec3V minNormal = mMinNormal;
	sweepHit.faceIndex = mMinTriangleIndex;

	if(mInitialOverlap)
	{
		bool hasContacts = false;
		if(mHitFlags & PxHitFlag::eMTD)
			hasContacts = computeBox_TriangleMeshMTD(triMeshGeom, pose, mBox, boxTransform, mInflation, mBothTriangleSidesCollide, sweepHit);

		setupSweepHitForMTD(sweepHit, hasContacts, mWorldUnitDir);
	}
	else
	{
		sweepHit.distance = mDist;
		sweepHit.flags = PxHitFlag::eFACE_INDEX;

		// PT: we need the "best triangle" normal in order to call 'shouldFlipNormal'. We stored the best
		// triangle in both GJK & precise codepaths (in box space). We use a dedicated 'shouldFlipNormal'
		// function that delays computing the triangle normal.
		// TODO: would still be more efficient to store the best normal directly, it's already computed at least
		// in the GJK codepath.

		const Vec3V p0 = V3LoadU(&boxTransform.p.x);
		const QuatV q0 = QuatVLoadU(&boxTransform.q.x);
		const PxTransformV boxPos(p0, q0);

		if(mHitFlags & PxHitFlag::ePRECISE_SWEEP)
		{
			computeBoxLocalImpact(sweepHit.position, sweepHit.normal, sweepHit.flags, mBox, localDir, mHitTriangle, mHitFlags, isDoubleSided, meshBothSides, mDist);
		}
		else
		{
			sweepHit.flags |= PxHitFlag::eNORMAL|PxHitFlag::ePOSITION;

			// PT: now for the GJK path, we must first always negate the returned normal. Similar to what happens in the precise path,
			// we can't delay this anymore: our normal must be properly oriented in order to call 'shouldFlipNormal'.
			minNormal = V3Neg(minNormal);

			// PT: this one is to ensure the normal respects the mesh-both-sides/double-sided convention
			PxVec3 tmp;
			V3StoreU(minNormal, tmp);

			if(shouldFlipNormal(tmp, meshBothSides, isDoubleSided, mHitTriangle, localDir, NULL))
				minNormal = V3Neg(minNormal);

			// PT: finally, this moves everything back to world space
			V3StoreU(boxPos.rotate(minNormal), sweepHit.normal);
			V3StoreU(boxPos.transform(minClosestA), sweepHit.position);
		}
	}
	return true;
}

///////////////////////////////////////////////////////////////////////////////

bool sweepBox_MeshGeom(GU_BOX_SWEEP_FUNC_PARAMS)
{
	PX_ASSERT(geom.getType() == PxGeometryType::eTRIANGLEMESH);
	PX_UNUSED(threadContext);
	PX_UNUSED(boxPose_);
	PX_UNUSED(boxGeom_);

	const PxTriangleMeshGeometry& meshGeom = static_cast<const PxTriangleMeshGeometry&>(geom);

	TriangleMesh* meshData = static_cast<TriangleMesh*>(meshGeom.triangleMesh);

	return Midphase::sweepBoxVsMesh(meshData, meshGeom, pose, box, unitDir, distance, sweepHit, hitFlags, inflation);
}

///////////////////////////////////////////////////////////////////////////////

SweepConvexMeshHitCallback::SweepConvexMeshHitCallback(	const ConvexHullData& hull, const PxMeshScale& convexScale, const FastVertex2ShapeScaling& meshScale,
														const PxTransform& convexPose, const PxTransform& meshPose,
														const PxVec3& unitDir, PxReal distance, PxHitFlags hitFlags, bool bothTriangleSidesCollide, PxReal inflation,
														bool anyHit, float distCoef) :
	SweepShapeMeshHitCallback	(CallbackMode::eMULTIPLE, hitFlags, meshScale.flipsNormal(), distCoef),
	mMeshScale					(meshScale),
	mUnitDir					(unitDir),
	mInflation					(inflation),
	mAnyHit						(anyHit),
	mBothTriangleSidesCollide	(bothTriangleSidesCollide)
{
	mSweepHit.distance = distance; // this will be shrinking progressively as we sweep and clip the sweep length
	mSweepHit.faceIndex = 0xFFFFFFFF;

	mMeshSpaceUnitDir = meshPose.rotateInv(unitDir);
	
	const Vec3V worldDir = V3LoadU(unitDir);
	const FloatV dist = FLoad(distance);
	const QuatV q0 = QuatVLoadU(&meshPose.q.x);
	const Vec3V p0 = V3LoadU(&meshPose.p.x);

	const QuatV q1 = QuatVLoadU(&convexPose.q.x);
	const Vec3V p1 = V3LoadU(&convexPose.p.x);

	const PxTransformV meshPoseV(p0, q0);
	const PxTransformV convexPoseV(p1, q1);

	mMeshToConvex = convexPoseV.transformInv(meshPoseV);
	mConvexPoseV = convexPoseV;
	mConvexSpaceDir = convexPoseV.rotateInv(V3Neg(V3Scale(worldDir, dist)));
	mInitialDistance = dist;

	const Vec3V vScale = V3LoadU_SafeReadW(convexScale.scale);	// PT: safe because 'rotation' follows 'scale' in PxMeshScale
	const QuatV vQuat = QuatVLoadU(&convexScale.rotation.x);
	mConvexHull.initialize(&hull, V3Zero(), vScale, vQuat, convexScale.isIdentity());
}

PxAgain SweepConvexMeshHitCallback::processHit( // all reported coords are in mesh local space including hit.position
												const PxGeomRaycastHit& hit, const PxVec3& av0, const PxVec3& av1, const PxVec3& av2, PxReal& shrunkMaxT, const PxU32*)
{
	const PxVec3 v0 = mMeshScale * av0;
	const PxVec3 v1 = mMeshScale * (mFlipNormal ?  av2 : av1);
	const PxVec3 v2 = mMeshScale * (mFlipNormal ?  av1 : av2);

	// mSweepHit will be updated if sweep distance is < input mSweepHit.distance
	const PxReal oldDist = mSweepHit.distance;
	if(sweepConvexVsTriangle(
		v0, v1, v2, mConvexHull, mMeshToConvex, mConvexPoseV, mConvexSpaceDir,
		mUnitDir, mMeshSpaceUnitDir, mInitialDistance, oldDist, mSweepHit, mBothTriangleSidesCollide,
		mInflation, mInitialOverlap, hit.faceIndex))
	{
		mStatus = true;
		shrunkMaxT = mSweepHit.distance * mDistCoeff; // shrunkMaxT is scaled

		// PT: added for 'shouldFlipNormal'
		mHitTriangle.verts[0] = v0;
		mHitTriangle.verts[1] = v1;
		mHitTriangle.verts[2] = v2;

		if(mAnyHit)
			return false; // abort traversal
			
		if(mSweepHit.distance == 0.0f)
			return false;
	}
	return true; // continue traversal
}

bool SweepConvexMeshHitCallback::finalizeHit(	PxGeomSweepHit& sweepHit, const PxTriangleMeshGeometry& meshGeom, const PxTransform& pose,
												const PxConvexMeshGeometry& convexGeom, const PxTransform& convexPose,
												const PxVec3& unitDir, PxReal inflation,
												bool isMtd, bool meshBothSides, bool isDoubleSided, bool bothTriangleSidesCollide)
{
	if(!mStatus)
		return false;

	if(mInitialOverlap)
	{
		bool hasContacts = false;
		if(isMtd)
			hasContacts = computeConvex_TriangleMeshMTD(meshGeom, pose, convexGeom, convexPose, inflation, bothTriangleSidesCollide, sweepHit);

		setupSweepHitForMTD(sweepHit, hasContacts, unitDir);

		sweepHit.faceIndex = mSweepHit.faceIndex;
	}
	else
	{
		sweepHit = mSweepHit;
		//sweepHit.position += unitDir * sweepHit.distance;
		sweepHit.normal = -sweepHit.normal;
		sweepHit.normal.normalize();

		// PT: this one is to ensure the normal respects the mesh-both-sides/double-sided convention
		// PT: beware, the best triangle is in mesh-space, but the impact data is in world-space already
		if(shouldFlipNormal(sweepHit.normal, meshBothSides, isDoubleSided, mHitTriangle, unitDir, &pose))
			sweepHit.normal = -sweepHit.normal;
	}
	return true;
}

///////////////////////////////////////////////////////////////////////////////

bool sweepConvex_MeshGeom(GU_CONVEX_SWEEP_FUNC_PARAMS)
{
	PX_ASSERT(geom.getType() == PxGeometryType::eTRIANGLEMESH);
	PX_UNUSED(threadContext);
	const PxTriangleMeshGeometry& meshGeom = static_cast<const PxTriangleMeshGeometry&>(geom);

	ConvexMesh* convexMesh = static_cast<ConvexMesh*>(convexGeom.convexMesh);
	TriangleMesh* meshData = static_cast<TriangleMesh*>(meshGeom.triangleMesh);

	const bool idtScaleConvex = convexGeom.scale.isIdentity();
	const bool idtScaleMesh = meshGeom.scale.isIdentity();

	FastVertex2ShapeScaling convexScaling;
	if(!idtScaleConvex)
		convexScaling.init(convexGeom.scale);

	FastVertex2ShapeScaling meshScaling;
	if(!idtScaleMesh)
		meshScaling.init(meshGeom.scale);

	PX_ASSERT(!convexMesh->getLocalBoundsFast().isEmpty());
	const PxBounds3 hullAABB = convexMesh->getLocalBoundsFast().transformFast(convexScaling.getVertex2ShapeSkew());

	Box hullOBB;
	computeHullOBB(hullOBB, hullAABB, 0.0f, Matrix34FromTransform(convexPose), Matrix34FromTransform(pose), meshScaling, idtScaleMesh);

	hullOBB.extents.x += inflation;
	hullOBB.extents.y += inflation;
	hullOBB.extents.z += inflation;

	const PxVec3 localDir = pose.rotateInv(unitDir);

	// inverse transform the sweep direction and distance to mesh space	
	PxVec3 meshSpaceSweepVector = meshScaling.getShape2VertexSkew().transform(localDir*distance);
	const PxReal meshSpaceSweepDist = meshSpaceSweepVector.normalize();

	PxReal distCoeff = 1.0f;
	if (!idtScaleMesh)
		distCoeff = meshSpaceSweepDist / distance;

	const bool meshBothSides = hitFlags & PxHitFlag::eMESH_BOTH_SIDES;
	const bool isDoubleSided = meshGeom.meshFlags & PxMeshGeometryFlag::eDOUBLE_SIDED;
	const bool bothTriangleSidesCollide = isDoubleSided || meshBothSides;
	const bool anyHit = hitFlags & PxHitFlag::eANY_HIT;
	SweepConvexMeshHitCallback callback(
		convexMesh->getHull(), convexGeom.scale, meshScaling, convexPose, pose, -unitDir, distance, hitFlags,
		bothTriangleSidesCollide, inflation, anyHit, distCoeff);
	
	Midphase::sweepConvexVsMesh(meshData, hullOBB, meshSpaceSweepVector, meshSpaceSweepDist, callback, anyHit);

	const bool isMtd = hitFlags & PxHitFlag::eMTD;
	return callback.finalizeHit(sweepHit, meshGeom, pose, convexGeom, convexPose, unitDir, inflation, isMtd, meshBothSides, isDoubleSided, bothTriangleSidesCollide);
}

///////////////////////////////////////////////////////////////////////////////

