// 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 "extensions/PxRemeshingExt.h"
#include "foundation/PxHashMap.h"

using namespace physx;

static void assignTriangle(PxArray<PxU32>& triangles, PxU32 triIndex, PxU32 a, PxU32 b, PxU32 c)
{
	triangles[3 * triIndex] = a;
	triangles[3 * triIndex + 1] = b;
	triangles[3 * triIndex + 2] = c;
}

static void addTriangle(PxArray<PxU32>& triangles, PxU32 a, PxU32 b, PxU32 c)
{
	triangles.pushBack(a);
	triangles.pushBack(b);
	triangles.pushBack(c);
}

static void subdivideTriangle(int i, int ab, int bc, int ac, PxArray<PxU32>& triangles, PxArray<PxVec3>& points)
{
	PxU32 tri[3] = { triangles[3 * i],triangles[3 * i + 1],triangles[3 * i + 2] };
	if (ab >= 0 && bc >= 0 && ac >= 0)
	{
		addTriangle(triangles, tri[0], ab, ac);
		addTriangle(triangles, tri[1], bc, ab);
		addTriangle(triangles, tri[2], ac, bc);
		assignTriangle(triangles, i, ab, bc, ac);
	}
	else if (ac >= 0 && ab >= 0)
	{
		float dB = (points[ac] - points[tri[1]]).magnitudeSquared();
		float dC = (points[ab] - points[tri[2]]).magnitudeSquared();
		if (dB < dC)
		{
			addTriangle(triangles, tri[1], tri[2], ac);
			addTriangle(triangles, tri[1], ac, ab);
		}
		else
		{
			addTriangle(triangles, tri[1], tri[2], ab);
			addTriangle(triangles, tri[2], ac, ab);
		}
		assignTriangle(triangles, i, tri[0], ab, ac);
	}
	else if (ab >= 0 && bc >= 0)
	{
		float dA = (points[bc] - points[tri[0]]).magnitudeSquared();
		float dC = (points[ab] - points[tri[2]]).magnitudeSquared();
		if (dC < dA)
		{
			addTriangle(triangles, tri[2], tri[0], ab);
			addTriangle(triangles, tri[2], ab, bc);
		}
		else
		{
			addTriangle(triangles, tri[2], tri[0], bc);
			addTriangle(triangles, tri[0], ab, bc);
		}
		assignTriangle(triangles, i, tri[1], bc, ab);
	}
	else if (bc >= 0 && ac >= 0)
	{
		float dA = (points[bc] - points[tri[0]]).magnitudeSquared();
		float dB = (points[ac] - points[tri[1]]).magnitudeSquared();
		if (dA < dB)
		{
			addTriangle(triangles, tri[0], tri[1], bc);
			addTriangle(triangles, tri[0], bc, ac);
		}
		else
		{
			addTriangle(triangles, tri[0], tri[1], ac);
			addTriangle(triangles, tri[1], bc, ac);
		}
		assignTriangle(triangles, i, tri[2], ac, bc);
	}
	else if (ab >= 0)
	{
		addTriangle(triangles, tri[1], tri[2], ab);
		assignTriangle(triangles, i, tri[2], tri[0], ab);
	}
	else if (bc >= 0)
	{
		addTriangle(triangles, tri[2], tri[0], bc);
		assignTriangle(triangles, i, tri[0], tri[1], bc);
	}
	else if (ac >= 0)
	{
		addTriangle(triangles, tri[1], tri[2], ac);
		assignTriangle(triangles, i, tri[0], tri[1], ac);
	}
}

static PX_FORCE_INLINE PxU64 key(PxU32 a, PxU32 b)
{
	if (a < b)
		return ((PxU64(a)) << 32) | (PxU64(b));
	else
		return ((PxU64(b)) << 32) | (PxU64(a));
}

static void checkEdge(PxU32 a, PxU32 b, PxHashMap<PxU64, PxI32>& edges, PxArray<PxVec3>& points, PxReal maxEdgeLength)
{
	if ((points[a] - points[b]).magnitudeSquared() < maxEdgeLength * maxEdgeLength)
		return;

	PxU64 k = key(a, b);
	if (edges.find(k))
		return;

	edges.insert(k, points.size());
	points.pushBack(0.5f * (points[a] + points[b]));
}

static PX_FORCE_INLINE PxI32 getEdge(PxU32 a, PxU32 b, PxHashMap<PxU64, PxI32>& edges)
{
	if (const PxPair<const PxU64, PxI32>* ptr = edges.find(key(a, b)))
		return ptr->second;
	return -1;
}

namespace
{
	struct Info
	{
		PxI32 StartIndex;
		PxI32 Count;

		Info(PxI32 startIndex, PxI32 count)
		{
			StartIndex = startIndex;
			Count = count;
		}
	};
}

static void checkEdge(PxU32 a, PxU32 b, PxHashMap<PxU64, Info>& edges, PxArray<PxVec3>& points, PxReal maxEdgeLength)
{
	if (a > b)
		PxSwap(a, b);

	PxReal l = (points[a] - points[b]).magnitudeSquared();
	if (l < maxEdgeLength * maxEdgeLength)
		return;

	l = PxSqrt(l);

	PxU32 numSubdivisions = (PxU32)(l / maxEdgeLength);
	if (numSubdivisions <= 1)
		return;

	PxU64 k = key(a, b);
	if (edges.find(k))
		return;

	edges.insert(k, Info(points.size(), numSubdivisions - 1));
	for (PxU32 i = 1; i < numSubdivisions; ++i)
	{
		PxReal p = (PxReal)i / numSubdivisions;
		points.pushBack((1 - p) * points[a] + p * points[b]);
	}
}
	
static PX_FORCE_INLINE Info getEdge(PxU32 a, PxU32 b, PxHashMap<PxU64, Info>& edges)
{
	const PxPair<const PxU64, Info>* value = edges.find(key(a, b));
	if (value)
		return value->second;
	return Info(-1, -1);
}

static PX_FORCE_INLINE void addPoints(PxArray<PxU32>& polygon, const Info& ab, bool reverse)
{
	if (reverse)
	{
		for (PxI32 i = ab.Count - 1; i >= 0; --i)
			polygon.pushBack(ab.StartIndex + i);
	}
	else
	{
		for (PxI32 i = 0; i < ab.Count; ++i)
			polygon.pushBack(ab.StartIndex + i);
	}
}

static PX_FORCE_INLINE PxReal angle(const PxVec3& l, const PxVec3& r)
{
	PxReal d = l.dot(r) / PxSqrt(l.magnitudeSquared() * r.magnitudeSquared());
	if (d <= -1) return PxPi;
	if (d >= 1) return 0.0f;
	return PxAcos(d);
}

static PX_FORCE_INLINE PxReal evaluateCost(const PxArray<PxVec3>& vertices, PxU32 a, PxU32 b, PxU32 c)
{
	const PxVec3& aa = vertices[a];
	const PxVec3& bb = vertices[b];
	const PxVec3& cc = vertices[c];

	PxReal a1 = angle(bb - aa, cc - aa);
	PxReal a2 = angle(aa - bb, cc - bb);
	PxReal a3 = angle(aa - cc, bb - cc);

	return PxMax(a1, PxMax(a2, a3));

	//return (aa - bb).magnitude() + (aa - cc).magnitude() + (bb - cc).magnitude();
}

static void triangulateConvex(PxU32 originalTriangleIndexTimes3, PxArray<PxU32>& polygon, const PxArray<PxVec3>& vertices, PxArray<PxU32>& triangles, PxArray<PxI32>& offsets)
{
	offsets.forceSize_Unsafe(0);
	offsets.reserve(polygon.size());
	for (PxU32 i = 0; i < polygon.size(); ++i)
		offsets.pushBack(1);

	PxI32 start = 0;
	PxU32 count = polygon.size();
	PxU32 triCounter = 0;
	while (count > 2)
	{
		PxReal minCost = FLT_MAX;
		PxI32 best = -1;
		PxI32 i = start;
		for (PxU32 j = 0; j < count; ++j)
		{
			PxU32 a = polygon[i];

			PX_ASSERT(offsets[i] >= 0);

			PxI32 n = (i + offsets[i]) % polygon.size();

			PX_ASSERT(offsets[n] >= 0);

			PxU32 b = polygon[n];
			PxU32 nn = (n + offsets[n]) % polygon.size();

			PX_ASSERT(offsets[nn] >= 0);

			PxU32 c = polygon[nn];

			PxReal cost = evaluateCost(vertices, a, b, c);
			if (cost < minCost)
			{
				minCost = cost;
				best = i;
			}
			i = n;
		}
		{
			PxU32 a = polygon[best];
			PxI32 n = (best + offsets[best]) % polygon.size();
			PxU32 b = polygon[n];
			PxU32 nn = (n + offsets[n]) % polygon.size();
			PxU32 c = polygon[nn];

			if (n == start)
				start += offsets[n];

			offsets[best] += offsets[n];
			offsets[n] = -1;

			PX_ASSERT(offsets[(best + offsets[best]) % polygon.size()] >= 0);

			if (triCounter == 0)
			{
				triangles[originalTriangleIndexTimes3 + 0] = a;
				triangles[originalTriangleIndexTimes3 + 1] = b;
				triangles[originalTriangleIndexTimes3 + 2] = c;
			}
			else
			{
				triangles.pushBack(a);
				triangles.pushBack(b);
				triangles.pushBack(c);
			}
			++triCounter;
		}

		--count;
	}
}

static bool limitMaxEdgeLengthAdaptive(PxArray<PxU32>& triangles, PxArray<PxVec3>& points, PxReal maxEdgeLength, PxArray<PxU32>* triangleMap = NULL)
{
	PxHashMap<PxU64, Info> edges;
	bool split = false;

	//Analyze edges
	for (PxU32 i = 0; i < triangles.size(); i += 3)
	{
		const PxU32* t = &triangles[i];
		checkEdge(t[0], t[1], edges, points, maxEdgeLength);
		checkEdge(t[1], t[2], edges, points, maxEdgeLength);
		checkEdge(t[0], t[2], edges, points, maxEdgeLength);
	}

	PxArray<PxI32> offsets;
	PxArray<PxU32> polygon;

	//Subdivide triangles if required
	PxU32 size = triangles.size();
	for (PxU32 i = 0; i < size; i += 3)
	{
		const PxU32* t = &triangles[i];
		Info ab = getEdge(t[0], t[1], edges);
		Info bc = getEdge(t[1], t[2], edges);
		Info ac = getEdge(t[0], t[2], edges);
		if (ab.StartIndex >= 0 || bc.StartIndex >= 0 || ac.StartIndex >= 0)
		{
			polygon.forceSize_Unsafe(0);
			polygon.pushBack(t[0]);
			addPoints(polygon, ab, t[0] > t[1]);
			polygon.pushBack(t[1]);
			addPoints(polygon, bc, t[1] > t[2]);
			polygon.pushBack(t[2]);
			addPoints(polygon, ac, t[2] > t[0]);

			PxU32 s = triangles.size();
			triangulateConvex(i, polygon, points, triangles, offsets);
			split = true;

			if (triangleMap != NULL)
			{
				for (PxU32 j = s; j < triangles.size(); ++j)
					triangleMap->pushBack((*triangleMap)[i]);
			}
		}
		/*else
		{
			result.pushBack(t[0]);
			result.pushBack(t[1]);
			result.pushBack(t[2]);
			if (triangleMap != NULL)
				triangleMap->pushBack((*triangleMap)[i]);
		}*/
	}
	return split;
}

bool PxRemeshingExt::reduceSliverTriangles(PxArray<PxU32>& triangles, PxArray<PxVec3>& points, PxReal maxEdgeLength, PxU32 maxIterations, PxArray<PxU32>* triangleMap, PxU32 triangleCountThreshold)
{
	bool split = limitMaxEdgeLengthAdaptive(triangles, points, maxEdgeLength, triangleMap);
	if (!split)
		return false;

	for (PxU32 i = 1; i < maxIterations; ++i)
	{
		split = limitMaxEdgeLengthAdaptive(triangles, points, maxEdgeLength, triangleMap);
			
		if (!split)
			break;

		if (triangles.size() >= triangleCountThreshold)
			break;
	}
		
	return true;
}

bool PxRemeshingExt::limitMaxEdgeLength(PxArray<PxU32>& triangles, PxArray<PxVec3>& points, PxReal maxEdgeLength, PxU32 maxIterations, PxArray<PxU32>* triangleMap, PxU32 triangleCountThreshold)
{
	if (triangleMap)
	{
		triangleMap->clear();
		triangleMap->reserve(triangles.size() / 3);
		for (PxU32 i = 0; i < triangles.size() / 3; ++i)
			triangleMap->pushBack(i);
	}

	PxU32 numIndices = triangles.size();
	PxHashMap<PxU64, PxI32> edges;
	bool success = true;
	for (PxU32 k = 0; k < maxIterations && success; ++k)
	{
		success = false;
		edges.clear();

		//Analyze edges
		for (PxU32 i = 0; i < triangles.size(); i += 3)
		{
			checkEdge(triangles[i], triangles[i + 1], edges, points, maxEdgeLength);
			checkEdge(triangles[i + 1], triangles[i + 2], edges, points, maxEdgeLength);
			checkEdge(triangles[i], triangles[i + 2], edges, points, maxEdgeLength);
		}

		//Subdivide triangles if required
		PxU32 size = triangles.size();
		for (PxU32 i = 0; i < size; i += 3)
		{
			PxI32 ab = getEdge(triangles[i], triangles[i + 1], edges);
			PxI32 bc = getEdge(triangles[i + 1], triangles[i + 2], edges);
			PxI32 ac = getEdge(triangles[i], triangles[i + 2], edges);
			if (ab >= 0 || bc >= 0 || ac >= 0)
			{
				PxU32 s = triangles.size();
				subdivideTriangle(i / 3, ab, bc, ac, triangles, points);
				success = true;
				if (triangleMap)
				{
					for (PxU32 j = s / 3; j < triangles.size() / 3; ++j)
						triangleMap->pushBack((*triangleMap)[i / 3]);
				}
			}
		}
		if (triangles.size() >= triangleCountThreshold)
			break;
	}
	return numIndices != triangles.size();
}
