// 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 "geometry/PxGjkQuery.h"

#include "GuInternal.h"
#include "GuOverlapTests.h"
#include "GuSweepTests.h"
#include "GuRaycastTests.h"
#include "GuBoxConversion.h"
#include "GuTriangleMesh.h"
#include "GuMTD.h"
#include "GuBounds.h"
#include "GuDistancePointSegment.h"
#include "GuConvexMesh.h"
#include "GuDistancePointBox.h"
#include "GuMidphaseInterface.h"
#include "foundation/PxFPU.h"

using namespace physx;
using namespace Gu;

#include "GuGJK.h"
#include "GuGJKPenetration.h"
#include "GuGJKRaycast.h"
#include "GuEPA.h"
#include "geomutils/PxContactBuffer.h"

using namespace aos;

static PX_SUPPORT_INLINE PxVec3 Vec3V_To_PxVec3(const Vec3V& a)
{
	PxVec3 v;
	V3StoreU(a, v);
	return v;
}

static PX_SUPPORT_INLINE PxReal FloatV_To_PxReal(const FloatV& a)
{
	PxF32 f;
	FStore(a, &f);
	return f;
}

struct CustomConvexV : ConvexV
{
	const PxGjkQuery::Support* s;
	PxReal supportScale;

	CustomConvexV(const PxGjkQuery::Support& _s) : ConvexV(Gu::ConvexType::eCUSTOM), s(&_s), supportScale(1.0f)
	{
		setMinMargin(FLoad(0.001f));
		setSweepMargin(FLoad(0.001f));
	}
	PX_SUPPORT_INLINE Vec3V supportPoint(const PxI32 /*index*/) const
	{
		return supportLocal(V3LoadU(PxVec3(1, 0, 0)));
	}
	PX_SUPPORT_INLINE Vec3V supportLocal(const Vec3V& dir) const
	{
		return V3Scale(V3LoadU(s->supportLocal(Vec3V_To_PxVec3(dir))), FLoad(supportScale));
	}
	PX_SUPPORT_INLINE Vec3V supportLocal(const Vec3V& dir, PxI32& index) const
	{
		index = 0;
		return supportLocal(dir);
	}
	PX_SUPPORT_INLINE Vec3V supportRelative(const Vec3V& dir, const PxMatTransformV& aTob, const PxMatTransformV& aTobT) const
	{
		const Vec3V _dir = aTobT.rotate(dir);
		const Vec3V p = supportLocal(_dir);
		return aTob.transform(p);
	}
	PX_SUPPORT_INLINE Vec3V supportRelative(const Vec3V& dir, const PxMatTransformV& aTob, const PxMatTransformV& aTobT, PxI32& index) const
	{
		index = 0;
		return supportRelative(dir, aTob, aTobT);
	}
};

bool PxGjkQuery::proximityInfo(const Support& a, const Support& b, const PxTransform& poseA, const PxTransform& poseB, PxReal contactDistance, PxReal toleranceLength, PxVec3& pointA, PxVec3& pointB, PxVec3& separatingAxis, PxReal& separation)
{
	const PxTransformV transf0 = loadTransformU(poseA);
	const PxTransformV transf1 = loadTransformU(poseB);
	const PxTransformV curRTrans(transf1.transformInv(transf0));
	const PxMatTransformV aToB(curRTrans);
	const PxReal degenerateScale = 0.001f;

	CustomConvexV supportA(a);
	CustomConvexV supportB(b);
	const RelativeConvex<CustomConvexV> convexA(supportA, aToB);
	const LocalConvex<CustomConvexV> convexB(supportB);

	Vec3V initialSearchDir = aToB.p;
	FloatV contactDist = FLoad((a.getMargin() + b.getMargin()) + contactDistance);

	Vec3V aPoints[4];
	Vec3V bPoints[4];
	PxU8 size = 0;
	GjkOutput output;

	GjkStatus status = gjkPenetration(convexA, convexB, initialSearchDir, contactDist, true, aPoints, bPoints, size, output);

	if (status == GJK_DEGENERATE)
	{
		supportA.supportScale = supportB.supportScale = 1.0f - degenerateScale;
		status = gjkPenetration(convexA, convexB, initialSearchDir, contactDist, true, aPoints, bPoints, size, output);
		supportA.supportScale = supportB.supportScale = 1.0f;
	}

	if (status == GJK_CONTACT || status == GJK_DEGENERATE)
	{
		separatingAxis = poseB.rotate(Vec3V_To_PxVec3(output.normal).getNormalized());
		pointA = poseB.transform(Vec3V_To_PxVec3(output.closestA)) - separatingAxis * a.getMargin();
		pointB = poseB.transform(Vec3V_To_PxVec3(output.closestB)) + separatingAxis * b.getMargin();
		separation = (pointA - pointB).dot(separatingAxis);
		return true;
	}

	if (status == EPA_CONTACT)
	{
		status = epaPenetration(convexA, convexB, aPoints, bPoints, size, true, FLoad(toleranceLength), output);

		if (status == EPA_CONTACT || status == EPA_DEGENERATE)
		{
			separatingAxis = poseB.rotate(Vec3V_To_PxVec3(output.normal).getNormalized());
			pointA = poseB.transform(Vec3V_To_PxVec3(output.closestA)) - separatingAxis * a.getMargin();
			pointB = poseB.transform(Vec3V_To_PxVec3(output.closestB)) + separatingAxis * b.getMargin();
			separation = (pointA - pointB).dot(separatingAxis);
			return true;
		}
	}

	return false;
}

struct PointConvexV : ConvexV
{
	Vec3V zero;
	PointConvexV() : ConvexV(Gu::ConvexType::eCUSTOM)
	{
		zero = V3Zero();
		setMinMargin(FLoad(0.001f));
		setSweepMargin(FLoad(0.001f));
	}
	PX_SUPPORT_INLINE Vec3V supportPoint(const PxI32 /*index*/) const
	{
		return zero;
	}
	PX_SUPPORT_INLINE Vec3V supportLocal(const Vec3V& /*dir*/) const
	{
		return zero;
	}
	PX_SUPPORT_INLINE Vec3V supportLocal(const Vec3V& dir, PxI32& index) const
	{
		index = 0;
		return supportLocal(dir);
	}
	PX_SUPPORT_INLINE Vec3V supportRelative(const Vec3V& dir, const PxMatTransformV& aTob, const PxMatTransformV& aTobT) const
	{
		const Vec3V _dir = aTobT.rotate(dir);
		const Vec3V p = supportLocal(_dir);
		return aTob.transform(p);
	}
	PX_SUPPORT_INLINE Vec3V supportRelative(const Vec3V& dir, const PxMatTransformV& aTob, const PxMatTransformV& aTobT, PxI32& index) const
	{
		index = 0;
		return supportRelative(dir, aTob, aTobT);
	}
};

bool PxGjkQuery::raycast(const Support& shape, const PxTransform& pose, const PxVec3& rayStart, const PxVec3& unitDir, PxReal maxDist, PxReal& t, PxVec3& n, PxVec3& p)
{
	const PxTransformV transf0 = loadTransformU(pose);
	const PxTransformV transf1 = PxTransformV(V3LoadU(rayStart));
	const PxTransformV curRTrans(transf1.transformInv(transf0));
	const PxMatTransformV aToB(curRTrans);

	CustomConvexV supportA(shape);
	PointConvexV supportB;
	const RelativeConvex<CustomConvexV> convexA(supportA, aToB);
	const LocalConvex<PointConvexV> convexB(supportB);

	Vec3V initialDir = aToB.p;
	FloatV initialLambda = FLoad(0);
	Vec3V s = V3Zero();
	Vec3V r = V3LoadU(unitDir * maxDist);
	FloatV lambda;
	Vec3V normal, closestA;

	if (gjkRaycast(convexA, convexB, initialDir, initialLambda, s, r, lambda, normal, closestA, shape.getMargin()))
	{
		t = FloatV_To_PxReal(lambda) * maxDist;
		n = -Vec3V_To_PxVec3(normal).getNormalized();
		p = Vec3V_To_PxVec3(closestA) + n * shape.getMargin() + rayStart;
		return true;
	}

	return false;
}

bool PxGjkQuery::overlap(const Support& a, const Support& b, const PxTransform& poseA, const PxTransform& poseB)
{
	const PxTransformV transf0 = loadTransformU(poseA);
	const PxTransformV transf1 = loadTransformU(poseB);
	const PxTransformV curRTrans(transf1.transformInv(transf0));
	const PxMatTransformV aToB(curRTrans);

	CustomConvexV supportA(a);
	CustomConvexV supportB(b);
	const RelativeConvex<CustomConvexV> convexA(supportA, aToB);
	const LocalConvex<CustomConvexV> convexB(supportB);

	Vec3V initialSearchDir = aToB.p;
	FloatV contactDist = FLoad(a.getMargin() + b.getMargin());
	Vec3V closestA, closestB, normal;
	FloatV distance;
	GjkStatus status = gjk(convexA, convexB, initialSearchDir, contactDist, closestA, closestB, normal, distance);

	return status == GJK_CLOSE || status == GJK_CONTACT;
}

bool PxGjkQuery::sweep(const Support& a, const Support& b, const PxTransform& poseA, const PxTransform& poseB, const PxVec3& unitDir, PxReal maxDist, PxReal& t, PxVec3& n, PxVec3& p)
{
	const PxTransformV transf0 = loadTransformU(poseA);
	const PxTransformV transf1 = loadTransformU(poseB);
	const PxTransformV curRTrans(transf1.transformInv(transf0));
	const PxMatTransformV aToB(curRTrans);

	CustomConvexV supportA(a);
	CustomConvexV supportB(b);
	const RelativeConvex<CustomConvexV> convexA(supportA, aToB);
	const LocalConvex<CustomConvexV> convexB(supportB);

	Vec3V initialDir = aToB.p;
	FloatV initialLambda = FLoad(0);
	Vec3V s = V3Zero();
	Vec3V r = V3LoadU(poseB.rotateInv(unitDir * maxDist));
	FloatV lambda;
	Vec3V normal, closestA;

	if (gjkRaycast(convexA, convexB, initialDir, initialLambda, s, r, lambda, normal, closestA, a.getMargin() + b.getMargin()))
	{
		t = FloatV_To_PxReal(lambda) * maxDist;
		n = poseB.rotate(-(Vec3V_To_PxVec3(normal)).getNormalized());
		p = poseB.transform(Vec3V_To_PxVec3(closestA)) + n * a.getMargin();
		return true;
	}

	return false;
}

