// 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.

#include "ExtDelaunayTetrahedralizer.h"
#include "foundation/PxSort.h"

#include "foundation/PxHashMap.h"
#include "ExtUtilities.h"

using namespace physx;
using namespace Ext;

//http://tizian.cs.uni-bonn.de/publications/BaudsonKlein.pdf Page 44
static const PxI32 neighborFaces[4][3] = { { 0, 1, 2 }, { 0, 3, 1 }, { 0, 2, 3 }, { 1, 3, 2 } };
static const PxI32 tetTip[4] = { 3, 2, 1, 0 };

namespace
{
	struct Plane
	{
		PxVec3d normal;
		PxF64 planeD;

		PX_FORCE_INLINE Plane(const PxVec3d& n, PxF64 d) : normal(n), planeD(d)
		{ }

		PX_FORCE_INLINE Plane(const PxVec3d& n, const PxVec3d& pointOnPlane) : normal(n)
		{
			planeD = -pointOnPlane.dot(normal);
		}

		PX_FORCE_INLINE Plane(const PxVec3d& a, const PxVec3d& b, const PxVec3d& c)
		{
			normal = (b - a).cross(c - a);
			PxF64 norm = normal.magnitude();
			normal /= norm;

			planeD = -a.dot(normal);
		}

		PX_FORCE_INLINE PxF64 signedDistance(const PxVec3d& p)
		{
			return normal.dot(p) + planeD;
		}

		PX_FORCE_INLINE PxF64 unsignedDistance(const PxVec3d& p)
		{
			return PxAbs(signedDistance(p));
		}
	};
}

DelaunayTetrahedralizer::DelaunayTetrahedralizer(const PxVec3d& min, const PxVec3d& max)
{
	for(PxI32 i = 0; i < 4; ++i)
		neighbors.pushBack(-1);

	PxF64 radius = 1.1 * 0.5 * (max - min).magnitude();

	PxF64 scaledRadius = 6 * radius;
	centeredNormalizedPoints.pushBack(PxVec3d(-scaledRadius, -scaledRadius, -scaledRadius));
	centeredNormalizedPoints.pushBack(PxVec3d(scaledRadius, scaledRadius, -scaledRadius));
	centeredNormalizedPoints.pushBack(PxVec3d(-scaledRadius, scaledRadius, scaledRadius));
	centeredNormalizedPoints.pushBack(PxVec3d(scaledRadius, -scaledRadius, scaledRadius));

	numAdditionalPointsAtBeginning = 4;

	result.pushBack(Tetrahedron(0, 1, 2, 3));	
	for (PxI32 i = 0; i < 4; ++i)
		vertexToTet.pushBack(0);
}

void physx::Ext::buildNeighborhood(const PxI32* tets, PxU32 numTets, PxArray<PxI32>& result)
{
	PxU32 l = 4 * numTets;
	result.clear();
	result.resize(l, -1);

	PxHashMap<SortedTriangle, PxI32, TriangleHash> faces;
	for (PxU32 i = 0; i < numTets; ++i)
	{
		const PxI32* tet = &tets[4 * i];
		if (tet[0] < 0)
			continue;

		for (PxI32 j = 0; j < 4; ++j)
		{
			SortedTriangle tri(tet[neighborFaces[j][0]], tet[neighborFaces[j][1]], tet[neighborFaces[j][2]]);
			if (const PxPair<const SortedTriangle, PxI32>* ptr = faces.find(tri))
			{
				if (ptr->second < 0) 
				{
					//PX_ASSERT(false); //Invalid tetmesh since a face is shared by more than 2 tetrahedra
					continue;
				}

				result[4 * i + j] = ptr->second;
				result[ptr->second] = 4 * i + j;

				faces[tri] = -1;
			}
			else
				faces.insert(tri, 4 * i + j);
		}
	}
}

void physx::Ext::buildNeighborhood(const PxArray<Tetrahedron>& tets, PxArray<PxI32>& result)
{
	buildNeighborhood(reinterpret_cast<const PxI32*>(tets.begin()), tets.size(), result);
}

static bool shareFace(const Tetrahedron& t1, const Tetrahedron& t2)
{
	PxI32 counter = 0;

	if (t1.contains(t2[0])) ++counter;
	if (t1.contains(t2[1])) ++counter;
	if (t1.contains(t2[2])) ++counter;
	if (t1.contains(t2[3])) ++counter;

	if (counter == 4)
		PX_ASSERT(false);
	return counter == 3;
}

// PT: this function is currently unused
//Keep for debugging & verification
/*static*/ void validateNeighborhood(const PxArray<Tetrahedron>& tets, PxArray<PxI32>& neighbors)
{
	PxI32 borderCounter = 0;
	PX_UNUSED(borderCounter);
	for (PxU32 i = 0; i < neighbors.size(); ++i)
		if (neighbors[i] == -1)
			++borderCounter;

	//if (borderCounter != 4)
	//    throw new Exception();

	PxArray<PxI32> n;
	buildNeighborhood(tets, n);

	for (PxU32 i = 0; i < n.size(); ++i)
	{
		if (n[i] < 0)
			continue;
		if (n[i] != neighbors[i])			
			PX_ASSERT(false);			
	}

	for (PxU32 i = 0; i < neighbors.size(); ++i)
	{
		if (neighbors[i] < 0)
			continue;

		Tetrahedron tet1 = tets[i >> 2];
		Tetrahedron tet2 = tets[neighbors[i] >> 2];

		if (!shareFace(tet1, tet2))
			PX_ASSERT(false);

		PxI32 face1 = i & 3;
		SortedTriangle tri1(tet1[neighborFaces[face1][0]], tet1[neighborFaces[face1][1]], tet1[neighborFaces[face1][2]]);

		PxI32 face2 = neighbors[i] & 3;
		SortedTriangle tri2(tet2[neighborFaces[face2][0]], tet2[neighborFaces[face2][1]], tet2[neighborFaces[face2][2]]);

		if (tri1.A != tri2.A || tri1.B != tri2.B || tri1.C != tri2.C)
			PX_ASSERT(false);
	}
}

static void buildVertexToTet(const PxArray<Tetrahedron>& tets, PxI32 numPoints, PxArray<PxI32>& result)
{
	result.clear();
	result.resize(numPoints, -1);	

	for (PxU32 i = 0; i < tets.size(); ++i)
	{
		const Tetrahedron& tet = tets[i];
		if (tet[0] < 0)
			continue;
		result[tet[0]] = i;
		result[tet[1]] = i;
		result[tet[2]] = i;
		result[tet[3]] = i;
	}
}

DelaunayTetrahedralizer::DelaunayTetrahedralizer(PxArray<PxVec3d>& points, PxArray<Tetrahedron>& tets)
{
	initialize(points, tets);
}

void DelaunayTetrahedralizer::initialize(PxArray<PxVec3d>& points, PxArray<Tetrahedron>& tets)
{
	clearLockedEdges();
	clearLockedTriangles();

	centeredNormalizedPoints = points;
	result = tets;

	numAdditionalPointsAtBeginning = 0;

	buildNeighborhood(tets, neighbors);
	buildVertexToTet(tets, points.size(), vertexToTet);

	for (PxU32 i = 0; i < tets.size(); ++i)
		if (tets[i][0] < 0)
			unusedTets.pushBack(i);
}

static PX_FORCE_INLINE PxF64 inSphere(const PxVec3d& pa, const PxVec3d& pb, const PxVec3d& pc, const PxVec3d& pd, const PxVec3d& candidiate)
{
	PxF64 aex = pa.x - candidiate.x;
	PxF64 bex = pb.x - candidiate.x;
	PxF64 cex = pc.x - candidiate.x;
	PxF64 dex = pd.x - candidiate.x;
	PxF64 aey = pa.y - candidiate.y;
	PxF64 bey = pb.y - candidiate.y;
	PxF64 cey = pc.y - candidiate.y;
	PxF64 dey = pd.y - candidiate.y;
	PxF64 aez = pa.z - candidiate.z;
	PxF64 bez = pb.z - candidiate.z;
	PxF64 cez = pc.z - candidiate.z;
	PxF64 dez = pd.z - candidiate.z;

	PxF64 ab = aex * bey - bex * aey;
	PxF64 bc = bex * cey - cex * bey;
	PxF64 cd = cex * dey - dex * cey;
	PxF64 da = dex * aey - aex * dey;

	PxF64 ac = aex * cey - cex * aey;
	PxF64 bd = bex * dey - dex * bey;

	PxF64 abc = aez * bc - bez * ac + cez * ab;
	PxF64 bcd = bez * cd - cez * bd + dez * bc;
	PxF64 cda = cez * da + dez * ac + aez * cd;
	PxF64 dab = dez * ab + aez * bd + bez * da;

	PxF64 alift = aex * aex + aey * aey + aez * aez;
	PxF64 blift = bex * bex + bey * bey + bez * bez;
	PxF64 clift = cex * cex + cey * cey + cez * cez;
	PxF64 dlift = dex * dex + dey * dey + dez * dez;

	return (dlift * abc - clift * dab) + (blift * cda - alift * bcd);
}

static PX_FORCE_INLINE bool isDelaunay(const PxArray<PxVec3d>& points, const PxArray<Tetrahedron>& tets, const PxArray<PxI32>& neighbors, PxI32 faceId)
{
	PxI32 neighborPointer = neighbors[faceId];
	if (neighborPointer < 0)
		return true; //Border faces are always delaunay

	PxI32 tet1Id = faceId >> 2;
	PxI32 tet2Id = neighborPointer >> 2;
	const PxI32* localTriangle1 = neighborFaces[faceId & 3];
	PxI32 localTip1 = tetTip[faceId & 3];
	PxI32 localTip2 = tetTip[neighborPointer & 3];

	Tetrahedron tet1 = tets[tet1Id];
	Tetrahedron tet2 = tets[tet2Id];

	return inSphere(points[tet1[localTriangle1[0]]], points[tet1[localTriangle1[1]]], points[tet1[localTriangle1[2]]], points[tet1[localTip1]], points[tet2[localTip2]]) > 0;
}

static PX_FORCE_INLINE PxI32 storeNewTet(PxArray<Tetrahedron>& tets, PxArray<PxI32>& neighbors, const Tetrahedron& tet, PxArray<PxI32>& unusedTets)
{
	if (unusedTets.size() == 0)
	{
		PxU32 tetId = tets.size();
		tets.pushBack(tet);
		for (PxI32 i = 0; i < 4; ++i)
			neighbors.pushBack(-1);
		return tetId;
	}
	else
	{
		PxI32 tetId = unusedTets[unusedTets.size() - 1];
		unusedTets.remove(unusedTets.size() - 1);
		tets[tetId] = tet;
		return tetId;
	}
}

static PX_FORCE_INLINE PxI32 localFaceId(PxI32 localA, PxI32 localB, PxI32 localC)
{
	PxI32 result = localA + localB + localC - 3;
	PX_ASSERT(result >= 0 || result <= 3);
	return result;
}

static PX_FORCE_INLINE PxI32 localFaceId(const Tetrahedron& tet, PxI32 globalA, PxI32 globalB, PxI32 globalC)
{
	PxI32 result = localFaceId(tet.indexOf(globalA), tet.indexOf(globalB), tet.indexOf(globalC));
	return result;
}
	
static PX_FORCE_INLINE void setFaceNeighbor(PxArray<PxI32>& neighbors, PxI32 tetId, PxI32 faceId, PxI32 newNeighborTet, PxI32 newNeighborFace)
{
	neighbors[4 * tetId + faceId] = 4 * newNeighborTet + newNeighborFace;
	neighbors[4 * newNeighborTet + newNeighborFace] = 4 * tetId + faceId;
}

static PX_FORCE_INLINE void setFaceNeighbor(PxArray<PxI32>& neighbors, PxI32 tetId, PxI32 faceId, PxI32 newNeighbor)
{
	neighbors[4 * tetId + faceId] = newNeighbor;

	if (newNeighbor >= 0)
		neighbors[newNeighbor] = 4 * tetId + faceId;
}

static PX_FORCE_INLINE void setFaceNeighbor(PxArray<PxI32>& affectedFaces, PxArray<PxI32>& neighbors, PxI32 tetId, PxI32 faceId, PxI32 newNeighbor)
{
	setFaceNeighbor(neighbors, tetId, faceId, newNeighbor);
	affectedFaces.pushBack(newNeighbor);
}
	
static void flip1to4(PxI32 pointToInsert, PxI32 tetId, PxArray<PxI32>& neighbors, PxArray<PxI32>& vertexToTet, PxArray<Tetrahedron>& tets, PxArray<PxI32>& unusedTets, PxArray<PxI32>& affectedFaces)
{
	const Tetrahedron origTet = tets[tetId];
	Tetrahedron tet(origTet[0], origTet[1], origTet[2], pointToInsert);
	tets[tetId] = tet;
	Tetrahedron tet2(origTet[0], origTet[3], origTet[1], pointToInsert);
	Tetrahedron tet3(origTet[0], origTet[2], origTet[3], pointToInsert);
	Tetrahedron tet4(origTet[1], origTet[3], origTet[2], pointToInsert);

	PxI32 tet2Id = storeNewTet(tets, neighbors, tet2, unusedTets);
	PxI32 tet3Id = storeNewTet(tets, neighbors, tet3, unusedTets);
	PxI32 tet4Id = storeNewTet(tets, neighbors, tet4, unusedTets);

	PxI32 n1 = neighbors[4 * tetId + localFaceId(origTet, origTet[0], origTet[1], origTet[2])];
	PxI32 n2 = neighbors[4 * tetId + localFaceId(origTet, origTet[0], origTet[1], origTet[3])];
	PxI32 n3 = neighbors[4 * tetId + localFaceId(origTet, origTet[0], origTet[2], origTet[3])];
	PxI32 n4 = neighbors[4 * tetId + localFaceId(origTet, origTet[1], origTet[2], origTet[3])];

	setFaceNeighbor(affectedFaces, neighbors, tetId, localFaceId(tet, origTet[0], origTet[1], origTet[2]), n1);
	setFaceNeighbor(affectedFaces, neighbors, tet2Id, localFaceId(tet2, origTet[0], origTet[1], origTet[3]), n2);
	setFaceNeighbor(affectedFaces, neighbors, tet3Id, localFaceId(tet3, origTet[0], origTet[2], origTet[3]), n3);
	setFaceNeighbor(affectedFaces, neighbors, tet4Id, localFaceId(tet4, origTet[1], origTet[2], origTet[3]), n4);

	setFaceNeighbor(neighbors, tetId, localFaceId(tet, pointToInsert, origTet[0], origTet[1]), tet2Id, localFaceId(tet2, pointToInsert, origTet[0], origTet[1]));
	setFaceNeighbor(neighbors, tetId, localFaceId(tet, pointToInsert, origTet[0], origTet[2]), tet3Id, localFaceId(tet3, pointToInsert, origTet[0], origTet[2]));
	setFaceNeighbor(neighbors, tetId, localFaceId(tet, pointToInsert, origTet[1], origTet[2]), tet4Id, localFaceId(tet4, pointToInsert, origTet[1], origTet[2]));

	setFaceNeighbor(neighbors, tet2Id, localFaceId(tet2, pointToInsert, origTet[0], origTet[3]), tet3Id, localFaceId(tet3, pointToInsert, origTet[0], origTet[3]));
	setFaceNeighbor(neighbors, tet2Id, localFaceId(tet2, pointToInsert, origTet[1], origTet[3]), tet4Id, localFaceId(tet4, pointToInsert, origTet[1], origTet[3]));

	setFaceNeighbor(neighbors, tet3Id, localFaceId(tet3, pointToInsert, origTet[2], origTet[3]), tet4Id, localFaceId(tet4, pointToInsert, origTet[2], origTet[3]));

	vertexToTet[tet[0]] = tetId; vertexToTet[tet[1]] = tetId; vertexToTet[tet[2]] = tetId; vertexToTet[tet[3]] = tetId;
	vertexToTet[tet2[0]] = tet2Id; vertexToTet[tet2[1]] = tet2Id; vertexToTet[tet2[2]] = tet2Id; vertexToTet[tet2[3]] = tet2Id;
	vertexToTet[tet3[0]] = tet3Id; vertexToTet[tet3[1]] = tet3Id; vertexToTet[tet3[2]] = tet3Id; vertexToTet[tet3[3]] = tet3Id;
	vertexToTet[tet4[0]] = tet4Id; vertexToTet[tet4[1]] = tet4Id; vertexToTet[tet4[2]] = tet4Id; vertexToTet[tet4[3]] = tet4Id;
}

static bool flip2to3(PxI32 tet1Id, PxI32 tet2Id,
	PxArray<PxI32>& neighbors, PxArray<PxI32>& vertexToTet, PxArray<Tetrahedron>& tets, PxArray<PxI32>& unusedTets, PxArray<PxI32>& affectedFaces,
	PxI32 tip1, PxI32 tip2, const Triangle& tri)
{
	// 2->3 flip
	Tetrahedron tet1(tip2, tip1, tri[0], tri[1]);
	Tetrahedron tet2(tip2, tip1, tri[1], tri[2]);
	Tetrahedron tet3(tip2, tip1, tri[2], tri[0]);


	PxI32 n1 = neighbors[4 * tet1Id + localFaceId(tets[tet1Id], tri[0], tri[1], tip1)];
	PxI32 n2 = neighbors[4 * tet2Id + localFaceId(tets[tet2Id], tri[0], tri[1], tip2)];
	if (n1 >= 0 && (n1 >> 2) == (n2 >> 2))
	{
		if (Tetrahedron::identical(tet1, tets[n1 >> 2]))
			return false;
	}

	PxI32 n3 = neighbors[4 * tet1Id + localFaceId(tets[tet1Id], tri[1], tri[2], tip1)];
	PxI32 n4 = neighbors[4 * tet2Id + localFaceId(tets[tet2Id], tri[1], tri[2], tip2)];
	if (n3 >= 0 && (n3 >> 2) == (n4 >> 2))
	{
		if (Tetrahedron::identical(tet2, tets[n3 >> 2]))
			return false;
	}

	PxI32 n5 = neighbors[4 * tet1Id + localFaceId(tets[tet1Id], tri[2], tri[0], tip1)];
	PxI32 n6 = neighbors[4 * tet2Id + localFaceId(tets[tet2Id], tri[2], tri[0], tip2)];
	if (n5 >= 0 && (n5 >> 2) == (n6 >> 2))
	{
		if (Tetrahedron::identical(tet3, tets[n5 >> 2]))
			return false;
	}

	PxI32 tet3Id = storeNewTet(tets, neighbors, tet3, unusedTets);		

	setFaceNeighbor(affectedFaces, neighbors, tet1Id, localFaceId(tet1, tri[0], tri[1], tip1), n1);
	setFaceNeighbor(affectedFaces, neighbors, tet1Id, localFaceId(tet1, tri[0], tri[1], tip2), n2);
	setFaceNeighbor(neighbors, tet1Id, localFaceId(tet1, tri[0], tip1, tip2), tet3Id, localFaceId(tet3, tri[0], tip1, tip2)); //Interior face
	setFaceNeighbor(neighbors, tet1Id, localFaceId(tet1, tri[1], tip1, tip2), tet2Id, localFaceId(tet2, tri[1], tip1, tip2)); //Interior face

	setFaceNeighbor(affectedFaces, neighbors, tet2Id, localFaceId(tet2, tri[1], tri[2], tip1), n3);
	setFaceNeighbor(affectedFaces, neighbors, tet2Id, localFaceId(tet2, tri[1], tri[2], tip2), n4);
	setFaceNeighbor(neighbors, tet2Id, localFaceId(tet2, tri[2], tip1, tip2), tet3Id, localFaceId(tet3, tri[2], tip1, tip2)); //Interior face

	setFaceNeighbor(affectedFaces, neighbors, tet3Id, localFaceId(tet3, tri[2], tri[0], tip1), n5);
	setFaceNeighbor(affectedFaces, neighbors, tet3Id, localFaceId(tet3, tri[2], tri[0], tip2), n6);

	tets[tet1Id] = tet1;
	tets[tet2Id] = tet2;

	vertexToTet[tet1[0]] = tet1Id; vertexToTet[tet1[1]] = tet1Id; vertexToTet[tet1[2]] = tet1Id; vertexToTet[tet1[3]] = tet1Id;
	vertexToTet[tet2[0]] = tet2Id; vertexToTet[tet2[1]] = tet2Id; vertexToTet[tet2[2]] = tet2Id; vertexToTet[tet2[3]] = tet2Id;
	vertexToTet[tet3[0]] = tet3Id; vertexToTet[tet3[1]] = tet3Id; vertexToTet[tet3[2]] = tet3Id; vertexToTet[tet3[3]] = tet3Id;

	return true;
}

static bool flip3to2(PxI32 tet1Id, PxI32 tet2Id, PxI32 tet3Id,
	PxArray<PxI32>& neighbors, PxArray<PxI32>& vertexToTet, PxArray<Tetrahedron>& tets, PxArray<PxI32>& unusedTets, PxArray<PxI32>& affectedFaces,
	PxI32 tip1, PxI32 tip2, PxI32 reflexEdgeA, PxI32 reflexEdgeB, PxI32 nonReflexTrianglePoint)
{
	// 3->2 flip
	Tetrahedron tet1(tip1, tip2, reflexEdgeA, nonReflexTrianglePoint);
	Tetrahedron tet2(tip2, tip1, reflexEdgeB, nonReflexTrianglePoint);
		
	PxI32 n1 = neighbors[4 * tet1Id + localFaceId(tets[tet1Id], reflexEdgeA, tip1, nonReflexTrianglePoint)];
	PxI32 n2 = neighbors[4 * tet2Id + localFaceId(tets[tet2Id], reflexEdgeA, tip2, nonReflexTrianglePoint)];
	PxI32 n3 = neighbors[4 * tet3Id + localFaceId(tets[tet3Id], reflexEdgeA, tip1, tip2)];
	if (n1 >= 0 && n2 >= 0 && (n1 >> 2) == (n2 >> 2) && Tetrahedron::identical(tet1, tets[n1 >> 2]))
		return false;
	if (n1 >= 0 && n3 >= 0 && (n1 >> 2) == (n3 >> 2) && Tetrahedron::identical(tet1, tets[n1 >> 2]))
		return false;
	if (n2 >= 0 && n3 >= 0 && (n2 >> 2) == (n3 >> 2) && Tetrahedron::identical(tet1, tets[n2 >> 2]))
		return false;
		
	PxI32 n4 = neighbors[4 * tet1Id + localFaceId(tets[tet1Id], reflexEdgeB, tip1, nonReflexTrianglePoint)];
	PxI32 n5 = neighbors[4 * tet2Id + localFaceId(tets[tet2Id], reflexEdgeB, tip2, nonReflexTrianglePoint)];
	PxI32 n6 = neighbors[4 * tet3Id + localFaceId(tets[tet3Id], reflexEdgeB, tip1, tip2)];
	if (n4 >= 0 && n5 >= 0 && (n4 >> 2) == (n5 >> 2) && Tetrahedron::identical(tet2, tets[n4 >> 2]))
		return false;
	if (n4 >= 0 && n6 >= 0 && (n4 >> 2) == (n6 >> 2) && Tetrahedron::identical(tet2, tets[n4 >> 2]))
		return false;
	if (n5 >= 0 && n6 >= 0 && (n5 >> 2) == (n6 >> 2) && Tetrahedron::identical(tet2, tets[n5 >> 2]))
		return false;

	setFaceNeighbor(affectedFaces, neighbors, tet1Id, localFaceId(tet1, reflexEdgeA, tip1, nonReflexTrianglePoint), n1);
	setFaceNeighbor(affectedFaces, neighbors, tet1Id, localFaceId(tet1, reflexEdgeA, tip2, nonReflexTrianglePoint), n2);
	setFaceNeighbor(affectedFaces, neighbors, tet1Id, localFaceId(tet1, reflexEdgeA, tip1, tip2), n3);
	setFaceNeighbor(neighbors, tet1Id, localFaceId(tet1, nonReflexTrianglePoint, tip1, tip2), tet2Id, localFaceId(tet2, nonReflexTrianglePoint, tip1, tip2)); //Interior face // Marker (*)

	setFaceNeighbor(affectedFaces, neighbors, tet2Id, localFaceId(tet2, reflexEdgeB, tip1, nonReflexTrianglePoint), n4);
	setFaceNeighbor(affectedFaces, neighbors, tet2Id, localFaceId(tet2, reflexEdgeB, tip2, nonReflexTrianglePoint), n5);
	setFaceNeighbor(affectedFaces, neighbors, tet2Id, localFaceId(tet2, reflexEdgeB, tip1, tip2), n6);
		
	tets[tet1Id] = tet1;
	tets[tet2Id] = tet2;

	tets[tet3Id] = Tetrahedron(-1, -1, -1, -1);
	for (PxI32 i = 0; i < 4; ++i)
		neighbors[4 * tet3Id + i] = -2;
	unusedTets.pushBack(tet3Id);

	vertexToTet[tet1[0]] = tet1Id; vertexToTet[tet1[1]] = tet1Id; vertexToTet[tet1[2]] = tet1Id; vertexToTet[tet1[3]] = tet1Id;
	vertexToTet[tet2[0]] = tet2Id; vertexToTet[tet2[1]] = tet2Id; vertexToTet[tet2[2]] = tet2Id; vertexToTet[tet2[3]] = tet2Id;
	return true;
}

static PX_FORCE_INLINE PxF64 orient3D(const PxVec3d& a, const PxVec3d& b, const PxVec3d& c, const PxVec3d& d)
{
	return (a - d).dot((b - d).cross(c - d));
}

static PX_FORCE_INLINE bool edgeIsLocked(const PxHashSet<PxU64>& lockedEdges, int edgeA, int edgeB)
{
	return lockedEdges.contains(key(edgeA, edgeB));
}

static PX_FORCE_INLINE bool faceIsLocked(const PxHashSet<SortedTriangle, TriangleHash>& lockedTriangles, int triA, int triB, int triC)
{
	return lockedTriangles.contains(SortedTriangle(triA, triB, triC));
}

static void flip(const PxArray<PxVec3d>& points, PxArray<Tetrahedron>& tets, PxArray<PxI32>& neighbors, PxArray<PxI32>& vertexToTet, PxI32 faceId, PxArray<PxI32>& unusedTets, PxArray<PxI32>& affectedFaces, 
	const PxHashSet<SortedTriangle, TriangleHash>& lockedFaces, const PxHashSet<PxU64>& lockedEdges)
{
	PxI32 neighborPointer = neighbors[faceId];
	if (neighborPointer < 0)
		return;

	PxI32 tet1Id = faceId >> 2;
	const PxI32* localTriangle1 = neighborFaces[faceId & 3];
	Tetrahedron tet1 = tets[tet1Id];
	Triangle tri(tet1[localTriangle1[0]], tet1[localTriangle1[1]], tet1[localTriangle1[2]]);
	PxI32 localTip1 = tetTip[faceId & 3];
	PxI32 localTip2 = tetTip[neighborPointer & 3];
	PxI32 tip1 = tet1[localTip1];

	PxI32 tet2Id = neighborPointer >> 2;
	Tetrahedron tet2 = tets[tet2Id];
	PxI32 tip2 = tet2[localTip2];

	PX_ASSERT(tet2.contains(tri[0]) && tet2.contains(tri[1]) && tet2.contains(tri[2]));
		
	PxI32 face1 = -1;
	PxI32 face2 = -1;
	PxI32 numReflexEdges = 0;
	PxI32 reflexEdgeA = -1;
	PxI32 reflexEdgeB = -1;
	PxI32 nonReflexTrianglePoint = -1;
	PxF64 ab = orient3D(points[tri[0]], points[tri[1]], points[tip1], points[tip2]);
	if (ab < 0) { ++numReflexEdges; face1 = localFaceId(localTriangle1[0], localTriangle1[1], localTip1); reflexEdgeA = tri[0]; reflexEdgeB = tri[1]; nonReflexTrianglePoint = tri[2]; face2 = localFaceId(tet2, tri[0], tri[1], tip2); }
	PxF64 bc = orient3D(points[tri[1]], points[tri[2]], points[tip1], points[tip2]);
	if (bc < 0) { ++numReflexEdges; face1 = localFaceId(localTriangle1[1], localTriangle1[2], localTip1); reflexEdgeA = tri[1]; reflexEdgeB = tri[2]; nonReflexTrianglePoint = tri[0]; face2 = localFaceId(tet2, tri[1], tri[2], tip2); }
	PxF64 ca = orient3D(points[tri[2]], points[tri[0]], points[tip1], points[tip2]);
	if (ca < 0) { ++numReflexEdges; face1 = localFaceId(localTriangle1[2], localTriangle1[0], localTip1); reflexEdgeA = tri[2]; reflexEdgeB = tri[0]; nonReflexTrianglePoint = tri[1]; face2 = localFaceId(tet2, tri[2], tri[0], tip2); }

	if (numReflexEdges == 0)
	{
		if (!faceIsLocked(lockedFaces, tri[0], tri[1], tri[2]))
			flip2to3(tet1Id, tet2Id, neighbors, vertexToTet, tets, unusedTets, affectedFaces, tip1, tip2, tri);
	}
	else if (numReflexEdges == 1)
	{
		PxI32 candidate1 = neighbors[4 * tet1Id + face1] >> 2;
		PxI32 candidate2 = neighbors[4 * tet2Id + face2] >> 2;
		if (candidate1 == candidate2 && candidate1 >= 0)
		{
			if (!edgeIsLocked(lockedEdges, reflexEdgeA, reflexEdgeB) &&
				!faceIsLocked(lockedFaces, reflexEdgeA, reflexEdgeB, nonReflexTrianglePoint) &&
				!faceIsLocked(lockedFaces, reflexEdgeA, reflexEdgeB, tip1) &&
				!faceIsLocked(lockedFaces, reflexEdgeA, reflexEdgeB, tip2))
				flip3to2(tet1Id, tet2Id, candidate1, neighbors, vertexToTet, tets, unusedTets, affectedFaces, tip1, tip2, reflexEdgeA, reflexEdgeB, nonReflexTrianglePoint);
		}
	}
	else if (numReflexEdges == 2)
	{
		//Cannot do anything			
	}
	else if (numReflexEdges == 3)
	{
		//Something is wrong if we end up here - maybe there are degenerate tetrahedra or issues due to numerical rounding			
	}
}

static void flip(PxArray<PxI32>& faces, PxArray<PxI32>& neighbors, PxArray<PxI32>& vertexToTet, const PxArray<PxVec3d>& points,
	PxArray<Tetrahedron>& tets, PxArray<PxI32>& unusedTets, PxArray<PxI32>& affectedFaces,
	const PxHashSet<SortedTriangle, TriangleHash>& lockedFaces, const PxHashSet<PxU64>& lockedEdges)
{
	while (faces.size() > 0)
	{
		PxI32 faceId = faces.popBack();
		if (faceId < 0)
			continue;

		if (isDelaunay(points, tets, neighbors, faceId))
			continue;

		affectedFaces.clear();
		flip(points, tets, neighbors, vertexToTet, faceId, unusedTets, affectedFaces, lockedFaces, lockedEdges);
			
		for (PxU32 j = 0; j < affectedFaces.size(); ++j)
			if (faces.find(affectedFaces[j]) == faces.end())
				faces.pushBack(affectedFaces[j]);
	}
}

static void insertAndFlip(PxI32 pointToInsert, PxI32 tetId, PxArray<PxI32>& neighbors, PxArray<PxI32>& vertexToTet, const PxArray<PxVec3d>& points,
	PxArray<Tetrahedron>& tets, PxArray<PxI32>& unusedTets, PxArray<PxI32>& affectedFaces,
	const PxHashSet<SortedTriangle, TriangleHash>& lockedFaces, const PxHashSet<PxU64>& lockedEdges)
{
	flip1to4(pointToInsert, tetId, neighbors, vertexToTet, tets, unusedTets, affectedFaces);
		
	PxArray<PxI32> stack;
	for (PxU32 j = 0; j < affectedFaces.size(); ++j)
		if (stack.find(affectedFaces[j]) == stack.end())
			stack.pushBack(affectedFaces[j]);

	flip(stack, neighbors, vertexToTet, points, tets, unusedTets, affectedFaces, lockedFaces, lockedEdges);
}

static PxI32 searchAll(const PxVec3d& p, const PxArray<Tetrahedron>& tets, const PxArray<PxVec3d>& points)
{
	for (PxU32 i = 0; i < tets.size(); ++i)
	{
		const Tetrahedron& tet = tets[i];
		if (tet[0] < 0)
			continue;

		PxI32 j = 0;
		for (; j < 4; j++)
		{
			Plane plane(points[tet[neighborFaces[j][0]]], points[tet[neighborFaces[j][1]]], points[tet[neighborFaces[j][2]]]);
			PxF64 distP = plane.signedDistance(p);
			if (distP < 0)
				break;
		}
		if (j == 4)
			return i;
	}
	return -1;
}

static bool runDelaunay(const PxArray<PxVec3d>& points, PxI32 start, PxI32 end, PxArray<Tetrahedron>& tets, PxArray<PxI32>& neighbors, PxArray<PxI32>& vertexToTet, PxArray<PxI32>& unusedTets,
	const PxHashSet<SortedTriangle, TriangleHash>& lockedFaces, const PxHashSet<PxU64>& lockedEdges)
{
	PxI32 tetId = 0;

	PxArray<PxI32> affectedFaces;
	for (PxI32 i = start; i < end; ++i)
	{
		const PxVec3d p = points[i];
		if (!PxIsFinite(p.x) || !PxIsFinite(p.y) || !PxIsFinite(p.z))
			continue;

		if (tetId < 0 || unusedTets.find(tetId) != unusedTets.end())
			tetId = 0;

		while (unusedTets.find(tetId) != unusedTets.end())
			++tetId;

		PxU32 counter = 0;
		bool tetLocated = false;
		while (!tetLocated)
		{
			const Tetrahedron& tet = tets[tetId];
			const PxVec3d center = (points[tet[0]] + points[tet[1]] + points[tet[2]] + points[tet[3]]) * 0.25;

			PxF64 minDist = DBL_MAX;
			PxI32 minFaceNr = -1;

			for (PxI32 j = 0; j < 4; j++)
			{
				Plane plane(points[tet[neighborFaces[j][0]]], points[tet[neighborFaces[j][1]]], points[tet[neighborFaces[j][2]]]);
				PxF64 distP = plane.signedDistance(p);
				PxF64 distCenter = plane.signedDistance(center);
				PxF64 delta = distP - distCenter;
				if (delta == 0.0)
					continue;
				delta = -distCenter / delta;
				if (delta >= 0.0 && delta < minDist)
				{
					minDist = delta;
					minFaceNr = j;
				}
			}
			if (minDist >= 1.0)
				tetLocated = true;
			else
			{
				tetId = neighbors[4 * tetId + minFaceNr] >> 2;
				if (tetId < 0)
				{
					tetId = searchAll(p, tets, points);
					tetLocated = true;
				}
			}

			++counter;
			if (counter > tets.size())
			{
				tetId = searchAll(p, tets, points);
				if (tetId < 0)
					return false;
				tetLocated = true;
			}
		}

		insertAndFlip(i, tetId, neighbors, vertexToTet, points, tets, unusedTets, affectedFaces, lockedFaces, lockedEdges);
	}
	return true;
}

bool DelaunayTetrahedralizer::insertPoints(const PxArray<PxVec3d>& inPoints, PxI32 start, PxI32 end)
{
	for (PxI32 i = start; i < end; ++i) 
	{
		centeredNormalizedPoints.pushBack(inPoints[i]);
		vertexToTet.pushBack(-1);
	}

	if (!runDelaunay(centeredNormalizedPoints, start + numAdditionalPointsAtBeginning, end + numAdditionalPointsAtBeginning, result, neighbors, vertexToTet, unusedTets, lockedTriangles, lockedEdges))
		return false;
	return true;
}

void DelaunayTetrahedralizer::exportTetrahedra(PxArray<Tetrahedron>& tetrahedra)
{
	tetrahedra.clear();
	for (PxU32 i = 0; i < result.size(); ++i)
	{
		const Tetrahedron& tet = result[i];
		if (tet[0] >= numAdditionalPointsAtBeginning && tet[1] >= numAdditionalPointsAtBeginning && tet[2] >= numAdditionalPointsAtBeginning && tet[3] >= numAdditionalPointsAtBeginning)
			tetrahedra.pushBack(Tetrahedron(tet[0] - numAdditionalPointsAtBeginning, tet[1] - numAdditionalPointsAtBeginning, tet[2] - numAdditionalPointsAtBeginning, tet[3] - numAdditionalPointsAtBeginning));
	}
}

void DelaunayTetrahedralizer::insertPoints(const PxArray<PxVec3d>& inPoints, PxI32 start, PxI32 end, PxArray<Tetrahedron>& tetrahedra)
{
	insertPoints(inPoints, start, end);
	exportTetrahedra(tetrahedra);
}

//Code below this line is for tetmesh manipulation and not directly required to generate a Delaunay tetrahedron mesh. It is used to impmrove the quality of a tetrahedral mesh.

//https://cs.nyu.edu/~panozzo/papers/SLIM-2016.pdf
static PxF64 evaluateAmipsEnergyPow3(const PxVec3d& a, const PxVec3d& b, const PxVec3d& c, const PxVec3d& d)
{
	PxF64 x1 = a.x + d.x - 2.0 * b.x;
	PxF64 x2 = a.x + b.x + d.x - 3.0 * c.x;
	PxF64 y1 = a.y + d.y - 2.0 * b.y;
	PxF64 y2 = a.y + b.y + d.y - 3.0 * c.y;
	PxF64 z1 = a.z + d.z - 2.0 * b.z;
	PxF64 z2 = a.z + b.z + d.z - 3.0 * c.z;

	PxF64 f = (1.0 / (PxSqrt(3.0) * PxSqrt(6.0))) *
		((a.x - d.x) * (y1 * z2 - y2 * z1) -
		(a.y - d.y) * (z2 * x1 - x2 * z1) +
			(a.z - d.z) * (y2 * x1 - y1 * x2));

	if (f >= 0)
		return 0.25 * DBL_MAX;

	PxF64 g = a.x * (b.x + c.x + d.x - 3.0 * a.x) +
		a.y * (b.y + c.y + d.y - 3.0 * a.y) +
		a.z * (b.z + c.z + d.z - 3.0 * a.z) +
		b.x * (a.x + c.x + d.x - 3.0 * b.x) +
		b.y * (a.y + c.y + d.y - 3.0 * b.y) +
		b.z * (a.z + c.z + d.z - 3.0 * b.z) +
		c.x * x2 +
		c.y * y2 +
		c.z * z2 +
		d.x * (a.x + b.x + c.x - 3.0 * d.x) +
		d.y * (a.y + b.y + c.y - 3.0 * d.y) +
		d.z * (a.z + b.z + c.z - 3.0 * d.z);

	return -0.125 * (g * g * g) / (f * f);
	//return -0.5 * PxPow(f * f, -1.0 / 3.0) * g;
}

static PX_FORCE_INLINE PxF64 _pow(const PxF64 a, const PxF64 b)
{
	return PxF64(PxPow(PxF32(a), PxF32(b)));
}

static PX_FORCE_INLINE PxF64 evaluateAmipsEnergy(const PxVec3d& a, const PxVec3d& b, const PxVec3d& c, const PxVec3d& d)
{
	return _pow(evaluateAmipsEnergyPow3(a, b, c, d), 1.0 / 3.0);
}

static PxF64 maxEnergyPow3(const PxArray<PxVec3d>& points, const PxArray<Tetrahedron>& tets)
{
	PxF64 maxEnergy = 0;
	for (PxU32 i = 0; i < tets.size(); ++i)
	{
		const Tetrahedron& tet = tets[i];
		if (tet[0] < 0)
			continue;
		PxF64 e = evaluateAmipsEnergyPow3(points[tet[0]], points[tet[1]], points[tet[2]], points[tet[3]]);
		if (e > maxEnergy)
			maxEnergy = e;
	}
	return maxEnergy;
}

static PX_FORCE_INLINE PxF64 maxEnergy(const PxArray<PxVec3d>& points, const PxArray<Tetrahedron>& tets)
{
	return pow(maxEnergyPow3(points, tets), 1.0 / 3.0);
}

static PxF64 maxEnergyPow3(const PxArray<PxVec3d>& points, const PxArray<Tetrahedron>& tets, const PxArray<PxI32>& tetIds)
{
	PxF64 maxEnergy = 0;
	for (PxU32 i = 0; i < tetIds.size(); ++i)
	{
		const Tetrahedron& tet = tets[tetIds[i]];
		if (tet[0] < 0)
			continue;
		PxF64 e = evaluateAmipsEnergyPow3(points[tet[0]], points[tet[1]], points[tet[2]], points[tet[3]]);
		if (e > maxEnergy)
			maxEnergy = e;
	}
	return maxEnergy;
}

static PxF64 minAbsTetVolume(const PxArray<PxVec3d>& points, const PxArray<Tetrahedron>& tets)
{
	PxF64 minVol = DBL_MAX;
	for (PxU32 i = 0; i < tets.size(); ++i) {
		const Tetrahedron& tet = tets[i];
		if (tet[0] < 0)
			continue;
		PxF64 vol = PxAbs(tetVolume(points[tet[0]], points[tet[1]], points[tet[2]], points[tet[3]]));
		if (vol < minVol)
			minVol = vol;
	}
	return minVol;
}

static PxF64 minTetVolume(const PxArray<PxVec3d>& points, const PxArray<Tetrahedron>& tets)
{
	PxF64 minVol = DBL_MAX;
	for (PxU32 i = 0; i < tets.size(); ++i) {
		const Tetrahedron& tet = tets[i];
		if (tet[0] < 0)
			continue;
		PxF64 vol = tetVolume(points[tet[0]], points[tet[1]], points[tet[2]], points[tet[3]]);
		if (vol < minVol)
			minVol = vol;
	}
	return minVol;
}
	
static PxF64 minTetVolume(const PxArray<PxVec3d>& points, const PxArray<Tetrahedron>& tets, const PxArray<PxI32>& indices)
{
	PxF64 minVol = DBL_MAX;
	for (PxU32 i = 0; i < indices.size(); ++i) {
		const Tetrahedron& tet = tets[indices[i]];
		if (tet[0] < 0)
			continue;
		PxF64 vol = tetVolume(points[tet[0]], points[tet[1]], points[tet[2]], points[tet[3]]);
		if (vol < minVol)
			minVol = vol;
	}
	return minVol;
}

PxF64 MinimizeMaxAmipsEnergy::quality(const PxArray<PxI32> tetIndices) const
{
	return maxEnergyPow3(points, tetrahedra, tetIndices);
}

PxF64 MinimizeMaxAmipsEnergy::quality(const PxArray<Tetrahedron> tetrahedraToCheck) const
{
	return maxEnergyPow3(points, tetrahedraToCheck);
}

bool MinimizeMaxAmipsEnergy::improved(PxF64 previousQuality, PxF64 newQuality) const
{
	return newQuality < previousQuality; //Minimize quality for Amips energy
}

PxF64 MaximizeMinTetVolume::quality(const PxArray<PxI32> tetIndices) const
{
	return minTetVolume(points, tetrahedra, tetIndices);
}

PxF64 MaximizeMinTetVolume::quality(const PxArray<Tetrahedron> tetrahedraToCheck) const
{
	return minTetVolume(points, tetrahedraToCheck);
}

bool MaximizeMinTetVolume::improved(PxF64 previousQuality, PxF64 newQuality) const
{
	return newQuality > previousQuality; //Maximize quality for min volume
}

static void fixVertexToTet(PxArray<PxI32>& vertexToTet, const PxArray<PxI32>& newTetIds, const PxArray<Tetrahedron>& tets)
{
	for (PxU32 i = 0; i < newTetIds.size(); ++i)
	{
		PxI32 id = newTetIds[i];
		const Tetrahedron& tet = tets[id];
		if (tet[0] < 0)
			continue;
		while (PxU32(tet[0]) >= vertexToTet.size()) vertexToTet.pushBack(-1);
		while (PxU32(tet[1]) >= vertexToTet.size()) vertexToTet.pushBack(-1);
		while (PxU32(tet[2]) >= vertexToTet.size()) vertexToTet.pushBack(-1);
		while (PxU32(tet[3]) >= vertexToTet.size()) vertexToTet.pushBack(-1);
		vertexToTet[tet[0]] = id; vertexToTet[tet[1]] = id; vertexToTet[tet[2]] = id; vertexToTet[tet[3]] = id;
	}
}

static void fixNeighborhoodLocally(const PxArray<PxI32>& removedTets, const PxArray<PxI32>& tetIndices, const PxArray<Tetrahedron>& tets, 
	PxArray<PxI32>& neighborhood, PxArray<PxI32>* affectedFaces = NULL)
{
	for (PxU32 k = 0; k < removedTets.size(); ++k)
	{
		PxI32 i = removedTets[k];
		for (PxI32 j = 0; j < 4; ++j)
		{
			PxI32 faceId = 4 * i + j;
			neighborhood[faceId] = -1;

			if (affectedFaces != NULL && affectedFaces->find(faceId) == affectedFaces->end())
				affectedFaces->pushBack(faceId);
		}
	}

	PxHashMap<SortedTriangle, PxI32, TriangleHash> faces;
	for(PxU32 k = 0; k< tetIndices.size(); ++k)
	{
		PxI32 i = tetIndices[k];
		if (i < 0)
			continue;

		const Tetrahedron& tet = tets[i];
		if (tet[0] < 0)
			continue;

		for (PxI32 j = 0; j < 4; ++j)
		{
			SortedTriangle tri(tet[neighborFaces[j][0]], tet[neighborFaces[j][1]], tet[neighborFaces[j][2]]);
			if (const PxPair<const SortedTriangle, PxI32>* ptr = faces.find(tri))
			{
				neighborhood[4 * i + j] = ptr->second;
				neighborhood[ptr->second] = 4 * i + j;
			}
			else
				faces.insert(tri, 4 * i + j);
		}
	}

	//validateNeighborhood(tets, neighborhood);
}

static void collectTetsConnectedToVertex(PxArray<PxI32>& faces, PxHashSet<PxI32>& tetsDone, const PxArray<Tetrahedron>& tets, const PxArray<PxI32>& tetNeighbors, const PxArray<PxI32>& vertexToTet,
	PxI32 vertexIndex, PxArray<PxI32>& tetIds, PxI32 secondVertexId = -1, PxI32 thirdVertexId = -1)
{
	tetIds.clear();
	int tetIndex = vertexToTet[vertexIndex];
	if (tetIndex < 0)
		return; //Free floating point

	if ((secondVertexId < 0 || tets[tetIndex].contains(secondVertexId)) && (thirdVertexId < 0 || tets[tetIndex].contains(thirdVertexId)))
		tetIds.pushBack(tetIndex);

	faces.clear();
	tetsDone.clear();
	for (int i = 0; i < 4; ++i)
	{
		if (tetNeighbors[4 * tetIndex + i] >= 0)
			faces.pushBack(4 * tetIndex + i);
	}
	tetsDone.insert(tetIndex);

	while (faces.size() > 0)
	{
		PxI32 faceId = faces.popBack();
		int tetId = tetNeighbors[faceId] >> 2;

		if (tetId < 0 || tetIds.find(tetId) != tetIds.end())
			continue;

		const Tetrahedron& tet = tets[tetId];
		int id = tet.indexOf(vertexIndex);
		if (id >= 0)
		{
			if ((secondVertexId < 0 || tets[tetId].contains(secondVertexId)) && (thirdVertexId < 0 || tets[tetId].contains(thirdVertexId)))
				tetIds.pushBack(tetId);

			const PxI32* f = neighborFaces[id];
			for (int i = 0; i < 3; ++i)
			{
				int candidate = 4 * tetId + f[i];
				if (tetsDone.insert(tetNeighbors[candidate] >> 2))
				{
					int otherTetId = tetNeighbors[candidate] >> 2;
					if (otherTetId >= 0 && tets[otherTetId].contains(vertexIndex))
						faces.pushBack(candidate);
				}
			}
		}
	}
}

void DelaunayTetrahedralizer::collectTetsConnectedToVertex(PxI32 vertexIndex, PxArray<PxI32>& tetIds)
{
	stackMemory.clear();
	::collectTetsConnectedToVertex(stackMemory.faces, stackMemory.hashSet, result, neighbors, vertexToTet, vertexIndex, tetIds, -1, -1);
}

void DelaunayTetrahedralizer::collectTetsConnectedToVertex(PxArray<PxI32>& faces, PxHashSet<PxI32>& tetsDone, PxI32 vertexIndex, PxArray<PxI32>& tetIds)
{
	faces.clear();
	tetsDone.clear();
	::collectTetsConnectedToVertex(faces, tetsDone, result, neighbors, vertexToTet, vertexIndex, tetIds);
}

void DelaunayTetrahedralizer::collectTetsConnectedToEdge(PxI32 edgeStart, PxI32 edgeEnd, PxArray<PxI32>& tetIds)
{
	if (edgeStart < edgeEnd)
		PxSwap(edgeStart, edgeEnd);

	stackMemory.clear();
	::collectTetsConnectedToVertex(stackMemory.faces, stackMemory.hashSet, result, neighbors, vertexToTet, edgeStart, tetIds, edgeEnd);
}

static PX_FORCE_INLINE bool containsDuplicates(PxI32 a, PxI32 b, PxI32 c, PxI32 d)
{
	return a == b || a == c || a == d || b == c || b == d || c == d;
}

//Returns true if the volume of a tet would become negative if a vertex it contains would get replaced by another one 
static bool tetFlipped(const Tetrahedron& tet, PxI32 vertexToReplace, PxI32 replacement, const PxArray<PxVec3d>& points, PxF64 volumeChangeThreshold = 0.1)
{
	PxF64 volumeBefore = tetVolume(points[tet[0]], points[tet[1]], points[tet[2]], points[tet[3]]);

	PxI32 a = tet[0] == vertexToReplace ? replacement : tet[0];
	PxI32 b = tet[1] == vertexToReplace ? replacement : tet[1];
	PxI32 c = tet[2] == vertexToReplace ? replacement : tet[2];
	PxI32 d = tet[3] == vertexToReplace ? replacement : tet[3];

	if (containsDuplicates(a, b, c, d))
		return false;

	PxF64 volume = tetVolume(points[a], points[b], points[c], points[d]);
	if (volume < 0)
		return true;
	if (PxAbs(volume / volumeBefore) < volumeChangeThreshold)
		return true;
	return false;
}

static PX_FORCE_INLINE Tetrahedron getTet(const Tetrahedron& tet, PxI32 vertexToReplace, PxI32 replacement)
{
	return Tetrahedron(tet[0] == vertexToReplace ? replacement : tet[0],
		tet[1] == vertexToReplace ? replacement : tet[1],
		tet[2] == vertexToReplace ? replacement : tet[2],
		tet[3] == vertexToReplace ? replacement : tet[3]);
}

static PX_FORCE_INLINE bool containsDuplicates(const Tetrahedron& tet) 
{
	return tet[0] == tet[1] || tet[0] == tet[2] || tet[0] == tet[3] || tet[1] == tet[2] || tet[1] == tet[3] || tet[2] == tet[3];
}

//Returns true if a tetmesh's edge, defined by vertex indices keep and remove, can be collapsed without leading to an invalid tetmesh topology
static bool canCollapseEdge(PxI32 keep, PxI32 remove, const PxArray<PxVec3d>& points, 
	const PxArray<PxI32>& tetsConnectedToKeepVertex, const PxArray<PxI32>& tetsConnectedToRemoveVertex, const PxArray<Tetrahedron>& allTet, 
	PxF64& qualityAfterCollapse, PxF64 volumeChangeThreshold = 0.1, BaseTetAnalyzer* tetAnalyzer = NULL)
{
	const PxArray<PxI32>& tets = tetsConnectedToRemoveVertex;
	//If a tet would get a negative volume due to the edge collapse, then this edge is not collapsible
	for (PxU32 i = 0; i < tets.size(); ++i)
	{
		const Tetrahedron& tet = allTet[tets[i]];
		if (tet[0] < 0)
			return false;
		if (tetFlipped(tet, remove, keep, points, volumeChangeThreshold))
			return false;
	}

	PxHashMap<SortedTriangle, PxI32, TriangleHash> triangles;

	PxHashSet<PxI32> candidates;
	for (PxU32 i = 0; i < tets.size(); ++i)
		candidates.insert(tets[i]);
	const PxArray<PxI32>& tets2 = tetsConnectedToKeepVertex;
	for (PxU32 i = 0; i < tets2.size(); ++i)
		if (allTet[tets2[i]][0] >= 0)
			candidates.insert(tets2[i]);

	PxArray<PxI32> affectedTets;
	affectedTets.reserve(candidates.size());
	PxArray<Tetrahedron> remainingTets;
	remainingTets.reserve(candidates.size());
	//If a tet-face would get referenced by more than 2 tetrahedra due to the edge collapse, then this edge is not collapsible 
	for (PxHashSet<PxI32>::Iterator iter = candidates.getIterator(); !iter.done(); ++iter)
	{
		PxI32 tetRef = *iter;
		affectedTets.pushBack(tetRef);
		Tetrahedron tet = getTet(allTet[tetRef], remove, keep);
		if (containsDuplicates(tet))
			continue;

		remainingTets.pushBack(tet);

		for (PxU32 j = 0; j < 4; ++j)
		{
			SortedTriangle tri(tet[neighborFaces[j][0]], tet[neighborFaces[j][1]], tet[neighborFaces[j][2]]);
			if (const PxPair<const SortedTriangle, PxI32>* ptr = triangles.find(tri))
				triangles[tri] = ptr->second + 1;
			else
				triangles.insert(tri, 1);
		}
	}

	for (PxHashMap<SortedTriangle, PxI32, TriangleHash>::Iterator iter = triangles.getIterator(); !iter.done(); ++iter)
		if (iter->second > 2)
			return false;

	if (tetAnalyzer == NULL)
		return true;

	qualityAfterCollapse = tetAnalyzer->quality(remainingTets);
	return tetAnalyzer->improved(tetAnalyzer->quality(affectedTets), qualityAfterCollapse);
}

bool DelaunayTetrahedralizer::canCollapseEdge(PxI32 edgeVertexToKeep, PxI32 edgeVertexToRemove, PxF64 volumeChangeThreshold, BaseTetAnalyzer* tetAnalyzer)
{
	PxF64 qualityAfterCollapse;
	PxArray<PxI32> tetsA, tetsB;
	stackMemory.clear();
	::collectTetsConnectedToVertex(stackMemory.faces, stackMemory.hashSet, result, neighbors, vertexToTet, edgeVertexToKeep, tetsA);
	stackMemory.clear();
	::collectTetsConnectedToVertex(stackMemory.faces, stackMemory.hashSet, result, neighbors, vertexToTet, edgeVertexToRemove, tetsB);
	return canCollapseEdge(edgeVertexToKeep, edgeVertexToRemove, tetsA, tetsB, qualityAfterCollapse, volumeChangeThreshold, tetAnalyzer);
}

bool DelaunayTetrahedralizer::canCollapseEdge(PxI32 edgeVertexAToKeep, PxI32 edgeVertexBToRemove, const PxArray<PxI32>& tetsConnectedToA, const PxArray<PxI32>& tetsConnectedToB,
	PxF64& qualityAfterCollapse, PxF64 volumeChangeThreshold, BaseTetAnalyzer* tetAnalyzer)
{
	return ::canCollapseEdge(edgeVertexAToKeep, edgeVertexBToRemove, centeredNormalizedPoints,
		tetsConnectedToA, //physx::Ext::collectTetsConnectedToVertex(result, neighbors, vertexToTet, edgeVertexAToKeep),
		tetsConnectedToB, //physx::Ext::collectTetsConnectedToVertex(result, neighbors, vertexToTet, edgeVertexBToRemove),
		result, qualityAfterCollapse, volumeChangeThreshold, tetAnalyzer);
}

static void collapseEdge(PxI32 keep, PxI32 remove, const PxArray<PxI32>& tetsConnectedToKeepVertex, const PxArray<PxI32>& tetsConnectedToRemoveVertex,
	PxArray<Tetrahedron>& allTets, PxArray<PxI32>& neighborhood, PxArray<PxI32>& vertexToTet, PxArray<PxI32>& unusedTets, PxArray<PxI32>& changedTets)
{
	PxArray<PxI32> removedTets;
	changedTets.clear();

	const PxArray<PxI32>& tets = tetsConnectedToRemoveVertex;
	for (PxI32 i = tets.size() - 1; i >= 0; --i)
	{
		PxI32 tetId = tets[i];
		Tetrahedron tet = allTets[tetId];
		tet.replace(remove, keep);
		if (containsDuplicates(tet))
		{
			for (PxU32 j = 0; j < 4; ++j) 
			{
				if (vertexToTet[tet[j]] == tetId) vertexToTet[tet[j]] = -1;
				tet[j] = -1;
			}
			removedTets.pushBack(tetId);
			unusedTets.pushBack(tetId);
		}
		else
		{
			changedTets.pushBack(tetId);
		}
		allTets[tetId] = tet;
	}

	for (PxU32 i = 0; i < tetsConnectedToKeepVertex.size(); ++i)
	{
		PxI32 id = tetsConnectedToKeepVertex[i];
		if (removedTets.find(id) == removedTets.end())
			changedTets.pushBack(id);
	}

	for (PxU32 i = 0; i < removedTets.size(); ++i)
	{
		PxI32 id = removedTets[i];
		for (PxI32 j = 0; j < 4; ++j)
		{
			PxI32 n = neighborhood[4 * id + j];
			if (n >= 0)
				neighborhood[n] = -1;
		}
	}

	fixNeighborhoodLocally(removedTets, changedTets, allTets, neighborhood);
	fixVertexToTet(vertexToTet, changedTets, allTets);
	vertexToTet[remove] = -1;
}

void DelaunayTetrahedralizer::collapseEdge(PxI32 edgeVertexToKeep, PxI32 edgeVertexToRemove)
{
	PxArray<PxI32> changedTets;

	PxArray<PxI32> keepTetIds;
	PxArray<PxI32> removeTetIds;
	stackMemory.clear();
	::collectTetsConnectedToVertex(stackMemory.faces, stackMemory.hashSet, result, neighbors, vertexToTet, edgeVertexToKeep, keepTetIds);
	stackMemory.clear();
	::collectTetsConnectedToVertex(stackMemory.faces, stackMemory.hashSet, result, neighbors, vertexToTet, edgeVertexToRemove, removeTetIds);
	::collapseEdge(edgeVertexToKeep, edgeVertexToRemove,
		keepTetIds,
		removeTetIds,
		result, neighbors, vertexToTet, unusedTets, changedTets);
}

void DelaunayTetrahedralizer::collapseEdge(PxI32 edgeVertexAToKeep, PxI32 edgeVertexBToRemove, const PxArray<PxI32>& tetsConnectedToA, const PxArray<PxI32>& tetsConnectedToB)
{
	PxArray<PxI32> changedTets;
	::collapseEdge(edgeVertexAToKeep, edgeVertexBToRemove,
		tetsConnectedToA, tetsConnectedToB,
		result, neighbors, vertexToTet, unusedTets, changedTets);
}

static bool buildRing(const PxArray<Edge>& edges, PxArray<PxI32>& result)
{
	result.reserve(edges.size() + 1);
	const Edge& first = edges[0];
	result.pushBack(first.first);
	result.pushBack(first.second);

	PxU32 currentEdge = 0;
	PxI32 connector = first.second;

	for (PxU32 j = 1; j < edges.size(); ++j)
	{
		for (PxU32 i = 1; i < edges.size(); ++i)
		{
			if (i == currentEdge)
				continue;

			const Edge& e = edges[i];
			if (e.first == connector)
			{
				currentEdge = i;
				result.pushBack(e.second);
				connector = e.second;
			}
			else if (e.second == connector)
			{
				currentEdge = i;
				result.pushBack(e.first);
				connector = e.first;
			}
			if (connector == result[0])
			{
				result.remove(result.size() - 1);
				if (result.size() != edges.size())
				{
					result.clear();
					return false; //Multiple segments - unexpected input
				}
				return true; //Cyclic
			}
		}
	}
	result.clear();
	return false;
}

static void addRange(PxHashSet<PxI32>& set, const PxArray<PxI32>& list)
{
	for (PxU32 i = 0; i < list.size(); ++i)
		set.insert(list[i]);
}

static Edge createEdge(PxI32 x, PxI32 y, bool swap)
{
	if (swap)
		return Edge(y, x);
	else
		return Edge(x, y);
}

static Edge getOtherEdge(const Tetrahedron& tet, PxI32 x, PxI32 y)
{
	x = tet.indexOf(x);
	y = tet.indexOf(y);

	bool swap = x > y;
	if (swap)
		PxSwap(x, y);

	if (x == 0 && y == 1) return createEdge(tet[2], tet[3], swap);
	if (x == 0 && y == 2) return createEdge(tet[3], tet[1], swap);
	if (x == 0 && y == 3) return createEdge(tet[1], tet[2], swap);
	if (x == 1 && y == 2) return createEdge(tet[0], tet[3], swap);
	if (x == 1 && y == 3) return createEdge(tet[2], tet[0], swap);
	if (x == 2 && y == 3) return createEdge(tet[0], tet[1], swap);

	return Edge(-1, -1);
}

static bool removeEdgeByFlip(PxI32 edgeA, PxI32 edgeB, PxArray<PxI32>& faces, PxHashSet<PxI32>& hashset, PxArray<PxI32>& tetIndices,
	PxArray<Tetrahedron>& tets, PxArray<PxVec3d>& pts, PxArray<PxI32>& unusedTets, PxArray<PxI32>& neighbors, PxArray<PxI32>& vertexToTet, 
	BaseTetAnalyzer* qualityAnalyzer = NULL)
{
	//validateNeighborhood(tets, neighbors);

	PxArray<Edge> ringEdges;
	ringEdges.reserve(tetIndices.size());
	for (PxU32 i = 0; i < tetIndices.size(); ++i)
		ringEdges.pushBack(getOtherEdge(tets[tetIndices[i]], edgeA, edgeB));

	PxArray<PxI32> ring;
	buildRing(ringEdges, ring);
	if (ring.size() == 0)
		return false;

	PxArray<PxI32> ringCopy(ring);

	const PxVec3d& a = pts[edgeA];
	const PxVec3d& b = pts[edgeB];

	PxArray<Tetrahedron> newTets;
	newTets.reserve(ring.size() * 2);
	PxArray<Triangle> newFaces;
	while (ring.size() >= 3)
	{
		PxF64 shortestDist = DBL_MAX;
		PxI32 id = -1;
		for (PxI32 i = 0; i < PxI32(ring.size()); ++i)
		{
			const PxVec3d& s = pts[ring[(i - 1 + ring.size()) % ring.size()]];
			const PxVec3d& middle = pts[ring[i]];
			const PxVec3d& e = pts[ring[(i + 1) % ring.size()]];
			const PxF64 d = (s - e).magnitudeSquared();
			if (d < shortestDist)
			{
				const PxF64 vol1 = tetVolume(s, middle, e, b);
				const PxF64 vol2 = tetVolume(a, s, middle, e);
				if (vol1 > 0 && vol2 > 0)
				{
					shortestDist = d;
					id = i;
				}
			}
		}

		if (id < 0)
			return false;

		{
			const PxI32& s = ring[(id - 1 + ring.size()) % ring.size()];
			const PxI32& middle = ring[id];
			const PxI32& e = ring[(id + 1) % ring.size()];
			newTets.pushBack(Tetrahedron(s, middle, e, edgeB));
			newTets.pushBack(Tetrahedron(edgeA, s, middle, e));

			newFaces.pushBack(Triangle(s, middle, e));
			if (ring.size() > 3)
			{
				newFaces.pushBack(Triangle(s, e, edgeA));
				newFaces.pushBack(Triangle(s, e, edgeB));
			}
		}
			
		ring.remove(id);
	}

	//Analyze and decide if operation should be done
	if (qualityAnalyzer != NULL && !qualityAnalyzer->improved(qualityAnalyzer->quality(tetIndices), qualityAnalyzer->quality(newTets))) 
		return false;

	PxArray<PxI32> newTetIds;
	for (PxU32 i = 0; i < tetIndices.size(); ++i)
	{
		for (PxI32 j = 0; j < 4; ++j)
		{
			PxI32 id = 4 * tetIndices[i] + j;
			PxI32 neighborTet = neighbors[id] >> 2;
			if (neighborTet >= 0 && tetIndices.find(neighborTet) == tetIndices.end() && newTetIds.find(neighborTet) == newTetIds.end())
				newTetIds.pushBack(neighborTet);
		}
	}
	
	PxHashSet<PxI32> set;
	PxArray<PxI32> tetIds;
	collectTetsConnectedToVertex(faces, hashset, tets, neighbors, vertexToTet, edgeA, tetIds);
	addRange(set, tetIds);

	faces.forceSize_Unsafe(0);
	hashset.clear();
	tetIds.forceSize_Unsafe(0);
	collectTetsConnectedToVertex(faces, hashset, tets, neighbors, vertexToTet, edgeB, tetIds);

	addRange(set, tetIds);

	for (PxU32 i = 0; i < ringCopy.size(); ++i)
	{
		faces.forceSize_Unsafe(0);
		hashset.clear();
		tetIds.forceSize_Unsafe(0);
		collectTetsConnectedToVertex(faces, hashset, tets, neighbors, vertexToTet, ringCopy[i], tetIds);
		addRange(set, tetIds);
	}
	for (PxHashSet<PxI32>::Iterator iter = set.getIterator(); !iter.done(); ++iter)
	{
		PxI32 i = *iter;
		const Tetrahedron& neighborTet = tets[i];
		for (PxU32 j = 0; j < newFaces.size(); ++j)
			if (neighborTet.containsFace(newFaces[j]))
				return false;

		for (PxU32 j = 0; j < newTets.size(); ++j)
		{
			const Tetrahedron& t = newTets[j];
			if (Tetrahedron::identical(t, neighborTet))
				return false;
		}
	}

	for (PxU32 i = 0; i < tetIndices.size(); ++i)
	{
		tets[tetIndices[i]] = Tetrahedron(-1, -1, -1, -1);
		unusedTets.pushBack(tetIndices[i]);
	}

	PxU32 l = newTetIds.size();
	for (PxU32 i = 0; i < newTets.size(); ++i)
		newTetIds.pushBack(storeNewTet(tets, neighbors, newTets[i], unusedTets));

	fixNeighborhoodLocally(tetIndices, newTetIds, tets, neighbors); 
	tetIndices.clear();
	for (PxU32 i = l; i < newTetIds.size(); ++i)
		tetIndices.pushBack(newTetIds[i]);
	fixVertexToTet(vertexToTet, tetIndices, tets);

	return true;
}

bool DelaunayTetrahedralizer::removeEdgeByFlip(PxI32 edgeA, PxI32 edgeB, PxArray<PxI32>& tetIndices, BaseTetAnalyzer* qualityAnalyzer)
{
	stackMemory.clear();
	return ::removeEdgeByFlip(edgeA, edgeB, stackMemory.faces, stackMemory.hashSet, tetIndices, result, centeredNormalizedPoints, unusedTets, neighbors, vertexToTet, qualityAnalyzer);
}

void DelaunayTetrahedralizer::addLockedEdges(const PxArray<Triangle>& triangles)
{
	for (PxU32 i = 0; i < triangles.size(); ++i)
	{
		const Triangle& t = triangles[i];
		lockedEdges.insert(key(t[0] + numAdditionalPointsAtBeginning, t[1] + numAdditionalPointsAtBeginning));
		lockedEdges.insert(key(t[0] + numAdditionalPointsAtBeginning, t[2] + numAdditionalPointsAtBeginning));
		lockedEdges.insert(key(t[1] + numAdditionalPointsAtBeginning, t[2] + numAdditionalPointsAtBeginning));
	}
}

void DelaunayTetrahedralizer::addLockedTriangles(const PxArray<Triangle>& triangles)
{
	for (PxU32 i = 0; i < triangles.size(); ++i)
	{
		const Triangle& tri = triangles[i];
		lockedTriangles.insert(SortedTriangle(tri[0] + numAdditionalPointsAtBeginning, tri[1] + numAdditionalPointsAtBeginning, tri[2] + numAdditionalPointsAtBeginning));
	}
}

static void previewFlip3to2(PxI32 tip1, PxI32 tip2, PxI32 reflexEdgeA, PxI32 reflexEdgeB, PxI32 nonReflexTrianglePoint, Tetrahedron& tet1, Tetrahedron& tet2)
{
	// 3->2 flip
	tet1 = Tetrahedron(tip1, tip2, reflexEdgeA, nonReflexTrianglePoint);
	tet2 = Tetrahedron(tip2, tip1, reflexEdgeB, nonReflexTrianglePoint);
}

static bool previewFlip2to3(PxI32 tet1Id, PxI32 tet2Id, const PxArray<PxI32>& neighbors, const PxArray<Tetrahedron>& tets, 
	PxI32 tip1, PxI32 tip2, Triangle tri, Tetrahedron& tet1, Tetrahedron& tet2, Tetrahedron& tet3)
{
	// 2->3 flip
	tet1 = Tetrahedron(tip2, tip1, tri[0], tri[1]);
	tet2 = Tetrahedron(tip2, tip1, tri[1], tri[2]);
	tet3 = Tetrahedron(tip2, tip1, tri[2], tri[0]);

	PxI32 n1 = neighbors[4 * tet1Id + localFaceId(tets[tet1Id], tri[0], tri[1], tip1)];
	PxI32 n2 = neighbors[4 * tet2Id + localFaceId(tets[tet2Id], tri[0], tri[1], tip2)];
	if (n1 >= 0 && (n1 >> 2) == (n2 >> 2))
	{
		if (Tetrahedron::identical(tet1, tets[n1 >> 2]))
			return false;
	}

	PxI32 n3 = neighbors[4 * tet1Id + localFaceId(tets[tet1Id], tri[1], tri[2], tip1)];
	PxI32 n4 = neighbors[4 * tet2Id + localFaceId(tets[tet2Id], tri[1], tri[2], tip2)];
	if (n3 >= 0 && (n3 >> 2) == (n4 >> 2))
	{
		if (Tetrahedron::identical(tet2, tets[n3 >> 2]))
			return false;
	}

	PxI32 n5 = neighbors[4 * tet1Id + localFaceId(tets[tet1Id], tri[2], tri[0], tip1)];
	PxI32 n6 = neighbors[4 * tet2Id + localFaceId(tets[tet2Id], tri[2], tri[0], tip2)];
	if (n5 >= 0 && (n5 >> 2) == (n6 >> 2))
	{
		if (Tetrahedron::identical(tet3, tets[n5 >> 2]))
			return false;
	}
	return true;
}

static bool previewFlip(const PxArray<PxVec3d>& points, const PxArray<Tetrahedron>& tets, const PxArray<PxI32>& neighbors, PxI32 faceId, 
	PxArray<Tetrahedron>& newTets, PxArray<PxI32>& removedTets,
	const PxHashSet<SortedTriangle, TriangleHash>& lockedFaces, const PxHashSet<PxU64>& lockedEdges)
{
	newTets.clear();
	removedTets.clear();

	PxI32 neighborPointer = neighbors[faceId];
	if (neighborPointer < 0)
		return false;

	PxI32 tet1Id = faceId >> 2;
	const PxI32* localTriangle1 = neighborFaces[faceId & 3];
	Tetrahedron tet1 = tets[tet1Id];
	Triangle tri(tet1[localTriangle1[0]], tet1[localTriangle1[1]], tet1[localTriangle1[2]]);
	PxI32 localTip1 = tetTip[faceId & 3];
	PxI32 localTip2 = tetTip[neighborPointer & 3];
	PxI32 tip1 = tet1[localTip1];

	PxI32 tet2Id = neighborPointer >> 2;
	Tetrahedron tet2 = tets[tet2Id];
	PxI32 tip2 = tet2[localTip2];
		
	if (!tet2.contains(tri[0]) || !tet2.contains(tri[1]) || !tet2.contains(tri[2]))
	{
		return false;
	}

	PxI32 face1 = -1;
	PxI32 face2 = -1;
	PxI32 numReflexEdges = 0;
	PxI32 reflexEdgeA = -1;
	PxI32 reflexEdgeB = -1;
	PxI32 nonReflexTrianglePoint = -1;
	PxF64 ab = orient3D(points[tri[0]], points[tri[1]], points[tip1], points[tip2]);
	if (ab < 0) { ++numReflexEdges; face1 = localFaceId(localTriangle1[0], localTriangle1[1], localTip1); reflexEdgeA = tri[0]; reflexEdgeB = tri[1]; nonReflexTrianglePoint = tri[2]; face2 = localFaceId(tet2, tri[0], tri[1], tip2); }
	PxF64 bc = orient3D(points[tri[1]], points[tri[2]], points[tip1], points[tip2]);
	if (bc < 0) { ++numReflexEdges; face1 = localFaceId(localTriangle1[1], localTriangle1[2], localTip1); reflexEdgeA = tri[1]; reflexEdgeB = tri[2]; nonReflexTrianglePoint = tri[0]; face2 = localFaceId(tet2, tri[1], tri[2], tip2); }
	PxF64 ca = orient3D(points[tri[2]], points[tri[0]], points[tip1], points[tip2]);
	if (ca < 0) { ++numReflexEdges; face1 = localFaceId(localTriangle1[2], localTriangle1[0], localTip1); reflexEdgeA = tri[2]; reflexEdgeB = tri[0]; nonReflexTrianglePoint = tri[1]; face2 = localFaceId(tet2, tri[2], tri[0], tip2); }

	if (numReflexEdges == 0)
	{
		if (!faceIsLocked(lockedFaces, tri[0], tri[1], tri[2]))
		{
			Tetrahedron tet3;
			if (previewFlip2to3(tet1Id, tet2Id, neighbors, tets, tip1, tip2, tri, tet1, tet2, tet3))
			{
				newTets.pushBack(tet1); newTets.pushBack(tet2); newTets.pushBack(tet3);
				removedTets.pushBack(tet1Id); removedTets.pushBack(tet2Id);
				return true;
			}
			else return true;
		}
	}
	else if (numReflexEdges == 1)
	{
		PxI32 candidate1 = neighbors[4 * tet1Id + face1] >> 2;
		PxI32 candidate2 = neighbors[4 * tet2Id + face2] >> 2;
		if (candidate1 == candidate2 && candidate1 >= 0)
		{
			if (!edgeIsLocked(lockedEdges, reflexEdgeA, reflexEdgeB) &&
				!faceIsLocked(lockedFaces, reflexEdgeA, reflexEdgeB, nonReflexTrianglePoint) &&
				!faceIsLocked(lockedFaces, reflexEdgeA, reflexEdgeB, tip1) &&
				!faceIsLocked(lockedFaces, reflexEdgeA, reflexEdgeB, tip2))
			{
				previewFlip3to2(tip1, tip2, reflexEdgeA, reflexEdgeB, nonReflexTrianglePoint, tet1, tet2);
				newTets.pushBack(tet1); newTets.pushBack(tet2);
				removedTets.pushBack(tet1Id); removedTets.pushBack(tet2Id); removedTets.pushBack(candidate1);
				return true;
			}
		}
	}
	else if (numReflexEdges == 2)
	{
		//Cannot do anything
	}
	else if (numReflexEdges == 3)
	{
	}
	return true;
}

static void collectCommonFaces(const PxArray<PxI32>& a, const PxArray<PxI32>& b, const PxArray<PxI32>& neighbors, PxArray<PxI32>& result)
{
	result.clear();
	for (PxU32 i = 0; i < a.size(); ++i)
	{
		PxI32 tetId = a[i];
		for (int j = 0; j < 4; ++j)
		{
			int id = 4 * tetId + j;
			if (b.find(neighbors[id] >> 2) != b.end())
			{
				if (result.find(id) == result.end())
					result.pushBack(id);
			}
		}
	}
}

static void previewFlip2to3(PxI32 tip1, PxI32 tip2, const Triangle& tri, Tetrahedron& tet1, Tetrahedron& tet2, Tetrahedron& tet3)
{
	// 2->3 flip
	tet1 = Tetrahedron(tip2, tip1, tri[0], tri[1]);
	tet2 = Tetrahedron(tip2, tip1, tri[1], tri[2]);
	tet3 = Tetrahedron(tip2, tip1, tri[2], tri[0]);
}

bool DelaunayTetrahedralizer::recoverEdgeByFlip(PxI32 eStart, PxI32 eEnd, RecoverEdgeMemoryCache& cache)
{
	PxArray<PxI32>& tetsStart = cache.resultStart;
	PxArray<PxI32>& tetsEnd = cache.resultEnd;
	collectTetsConnectedToVertex(cache.facesStart, cache.tetsDoneStart, eStart, tetsStart);
	collectTetsConnectedToVertex(cache.facesEnd, cache.tetsDoneEnd, eEnd, tetsEnd);

	for (PxU32 i = 0; i < tetsEnd.size(); ++i)
	{
		const Tetrahedron& tet = result[tetsEnd[i]];
		if (tet.contains(eStart))
			return true;
	}

	PxArray<PxI32> commonFaces;
	collectCommonFaces(tetsStart, tetsEnd, neighbors, commonFaces);

	if (commonFaces.size() > 0)
	{
		for (PxU32 i = 0; i < commonFaces.size(); ++i)
		{
			PxI32 faceId = commonFaces[i];
			PxI32 tetId = faceId >> 2;
			const Tetrahedron& tet = result[tetId];
			const PxI32* local = neighborFaces[faceId & 3];
			Triangle tri(tet[local[0]], tet[local[1]], tet[local[2]]);

			if (tri.contains(eStart) || tri.contains(eEnd))
				continue;

			int n = neighbors[faceId];
			int tetId2 = n >> 2;
			int tip1 = tet[tetTip[faceId & 3]];
			int tip2 = result[tetId2][tetTip[n & 3]];

			Tetrahedron tet1, tet2, tet3;
			previewFlip2to3(tip1, tip2, tri, tet1, tet2, tet3);

			if (tetVolume(tet1, centeredNormalizedPoints) > 0 &&
				tetVolume(tet2, centeredNormalizedPoints) > 0 &&
				tetVolume(tet3, centeredNormalizedPoints) > 0)
			{
				PxArray<PxI32> affectedFaces;
				if (flip2to3(tetId, tetId2, neighbors, vertexToTet, result, unusedTets, affectedFaces, tip1, tip2, tri))
					return true;
			}
		}
	}
	return false;
}

static void insert(PxArray<int>& list, int a, int b, int id)
{
	for (PxI32 i = 0; i < PxI32(list.size()); ++i)
		if (list[i] == a || list[i] == b)
		{
			list.pushBack(-1);
			for (PxI32 j = list.size() - 2; j > i; --j)
			{
				list[j + 1] = list[j];
			}
			list[i + 1] = id;
			return;
		}
	PX_ASSERT(false);
}

void DelaunayTetrahedralizer::generateTetmeshEnforcingEdges(const PxArray<PxVec3d>& trianglePoints, const PxArray<Triangle>& triangles, PxArray<PxArray<PxI32>>& allEdges,
	PxArray<PxArray<PxI32>>& pointToOriginalTriangle, 
	PxArray<PxVec3d>& points, PxArray<Tetrahedron>& finalTets)
{
	clearLockedEdges();
	clearLockedTriangles();
	addLockedEdges(triangles);
	addLockedTriangles(triangles);
		
	points.resize(trianglePoints.size());
	for (PxU32 i = 0; i < trianglePoints.size(); ++i)
		points[i] = trianglePoints[i];
		
	insertPoints(points, 0, points.size());

	allEdges.clear();
	allEdges.reserve(lockedEdges.size());
	PxHashMap<PxU64, PxU32> edgeToIndex;
	PxArray<PxI32> list;
	for (PxHashSet<PxU64>::Iterator iter = lockedEdges.getIterator(); !iter.done(); ++iter)
	{
		edgeToIndex.insert(*iter, allEdges.size());
			
		list.forceSize_Unsafe(0);
		list.pushBack(PxI32((*iter) >> 32));
		list.pushBack(PxI32((*iter)));
		allEdges.pushBack(list);
	}


	pointToOriginalTriangle.clear();
	for (PxU32 i = 0; i < points.size(); ++i)
		pointToOriginalTriangle.pushBack(PxArray<PxI32>());
	for (PxU32 i = 0; i < triangles.size(); ++i)
	{
		const Triangle& tri = triangles[i];
		pointToOriginalTriangle[tri[0]].pushBack(i);
		pointToOriginalTriangle[tri[1]].pushBack(i);
		pointToOriginalTriangle[tri[2]].pushBack(i);
	}
	
	for (PxU32 i = 0;i < points.size(); ++i) {
		PxArray<PxI32>& tempList = pointToOriginalTriangle[i];
		PxSort(tempList.begin(), tempList.size());
	}

	PxArray<PxI32> intersection;
	RecoverEdgeMemoryCache cache;
	while (true) {
		PxArray<PxU64> copy;
		copy.reserve(lockedEdges.size());
		for (PxHashSet<PxU64>::Iterator e = lockedEdges.getIterator(); !e.done(); ++e)
			copy.pushBack(*e);			
		PxI32 missing = 0;
		for (PxU32 k = 0; k < copy.size(); ++k)
		{
			PxU64 e = copy[k];
			PxI32 a = PxI32(e >> 32);
			PxI32 b = PxI32(e);
			if (!recoverEdgeByFlip(a, b, cache))
			{
				PxU32 id = centeredNormalizedPoints.size();
				PxU32 i = edgeToIndex[e];
				if (allEdges[i].size() < 10)
				{
					PxVec3d p = (centeredNormalizedPoints[a] + centeredNormalizedPoints[b]) * 0.5;					
					points.pushBack(p);
					insertPoints(points, points.size() - 1, points.size());

					intersection.forceSize_Unsafe(0);
					intersectionOfSortedLists(pointToOriginalTriangle[a - numAdditionalPointsAtBeginning], pointToOriginalTriangle[b - numAdditionalPointsAtBeginning], intersection);
					pointToOriginalTriangle.pushBack(intersection);

					insert(allEdges[i], a, b, id);
					edgeToIndex.insert(key(a, id), i);
					edgeToIndex.insert(key(b, id), i);
					edgeToIndex.erase(e);

					lockedEdges.erase(e);
					lockedEdges.insert(key(a, id));
					lockedEdges.insert(key(b, id));
					++missing;
				}
			}
		}
		if (missing == 0)
			break;
	}

	for (PxU32 i = 0; i < allEdges.size(); ++i)
	{
		PxArray<PxI32>& tempList = allEdges[i];
		for (PxU32 j = 0; j < tempList.size(); ++j)
			tempList[j] -= numAdditionalPointsAtBeginning;
	}

	exportTetrahedra(finalTets);
}

static PxI32 optimizeByFlipping(PxArray<PxI32>& faces, PxArray<PxI32>& neighbors, PxArray<PxI32>& vertexToTet,
	const PxArray<PxVec3d>& points, PxArray<Tetrahedron>& tets, PxArray<PxI32>& unusedTets, const BaseTetAnalyzer& qualityAnalyzer,
	PxArray<PxI32>& affectedFaces,
	const PxHashSet<SortedTriangle, TriangleHash>& lockedFaces, const PxHashSet<PxU64>& lockedEdges)
{
	PxI32 counter = 0;
	while (faces.size() > 0)
	{
		PxI32 faceId = faces.popBack();
		if (faceId < 0)
			continue;

		PxArray<Tetrahedron> newTets;
		PxArray<PxI32> removedTets;
		if (!previewFlip(points, tets, neighbors, faceId, newTets, removedTets, lockedFaces, lockedEdges))
			continue;

		if (!qualityAnalyzer.improved(qualityAnalyzer.quality(removedTets), qualityAnalyzer.quality(newTets)))
			continue;

		affectedFaces.clear();
		flip(points, tets, neighbors, vertexToTet, faceId, unusedTets, affectedFaces, lockedFaces, lockedEdges);
			
		for (PxU32 j = 0; j < affectedFaces.size(); ++j)
			if (faces.find(affectedFaces[j]) == faces.end())
				faces.pushBack(affectedFaces[j]);

		++counter;
	}
	return counter;
}

bool DelaunayTetrahedralizer::optimizeByFlipping(PxArray<PxI32>& affectedFaces, const BaseTetAnalyzer& qualityAnalyzer)
{
	PxArray<PxI32> stack;
	for (PxU32 i = 0; i < affectedFaces.size(); ++i)
		stack.pushBack(affectedFaces[i]);
	PxI32 counter = ::optimizeByFlipping(stack, neighbors, vertexToTet, centeredNormalizedPoints, result, unusedTets, qualityAnalyzer, affectedFaces, lockedTriangles, lockedEdges);
	return counter > 0;
}

static void insertPointIntoEdge(PxI32 newPointIndex, PxI32 edgeA, PxI32 edgeB, PxArray<Tetrahedron>& tets,
	PxArray<PxI32>& neighbors, PxArray<PxI32>& vertexToTet, PxArray<PxI32>& unusedTets, PxArray<PxI32>* affectedFaces, PxArray<PxI32>& affectedTets)
{
	PxU32 l = affectedTets.size();
	if (l == 0)
		return;

	PxArray<PxI32> removedTets;
	for (PxU32 i = 0; i < affectedTets.size(); ++i)
		removedTets.pushBack(affectedTets[i]);

	PxArray<PxI32> newTets;
	for (PxU32 i = 0; i < affectedTets.size(); ++i)
		newTets.pushBack(affectedTets[i]);

	for (PxU32 i = 0; i < l; ++i)
	{
		for (PxI32 j = 0; j < 4; ++j)
		{
			PxI32 id = 4 * affectedTets[i] + j;
			PxI32 neighborTet = neighbors[id] >> 2;
			if (neighborTet >= 0 && affectedTets.find(neighborTet) == affectedTets.end())
				affectedTets.pushBack(neighborTet);
		}
	}
	PxU32 l2 = affectedTets.size();

	for (PxU32 i = 0; i < l; ++i)
	{
		PxI32 id = affectedTets[i];
		const Tetrahedron& tet = tets[id];

		Edge oppositeEdge = getOtherEdge(tet, edgeA, edgeB);

		tets[id] = Tetrahedron(oppositeEdge.first, oppositeEdge.second, edgeA, newPointIndex);
		PxI32 j = storeNewTet(tets, neighbors, Tetrahedron(oppositeEdge.first, oppositeEdge.second, newPointIndex, edgeB), unusedTets);
		affectedTets.pushBack(j);
		newTets.pushBack(j);
	}

	fixNeighborhoodLocally(removedTets, affectedTets, tets, neighbors, affectedFaces);
	fixVertexToTet(vertexToTet, newTets, tets);
	affectedTets.removeRange(l, l2 - l);
}

void DelaunayTetrahedralizer::insertPointIntoEdge(PxI32 newPointIndex, PxI32 edgeA, PxI32 edgeB, PxArray<PxI32>& affectedTets, BaseTetAnalyzer* qualityAnalyzer)
{
	PxArray<PxI32> affectedFaces;
	::insertPointIntoEdge(newPointIndex, edgeA, edgeB, result, neighbors, vertexToTet, unusedTets, &affectedFaces, affectedTets);

	if (qualityAnalyzer != NULL)
		optimizeByFlipping(affectedFaces, *qualityAnalyzer);
}

static bool containsAll(const PxArray<PxI32>& list, const PxArray<PxI32>& itemsToBePresentInList)
{
	for (PxU32 i = 0; i < itemsToBePresentInList.size(); ++i)
		if (list.find(itemsToBePresentInList[i]) == list.end())
			return false;
	return true;
}

bool physx::Ext::optimizeByCollapsing(DelaunayTetrahedralizer& del, const PxArray<EdgeWithLength>& edges,
	PxArray<PxArray<PxI32>>& pointToOriginalTriangle, PxI32 numFixPoints, BaseTetAnalyzer* qualityAnalyzer)
{
	PxI32 l = PxI32(pointToOriginalTriangle.size());

	bool success = false;
	PxArray<PxI32> tetsA;
	PxArray<PxI32> tetsB;

	for (PxU32 i = 0; i < edges.size(); ++i)
	{
		const EdgeWithLength& e = edges[i];

		tetsA.forceSize_Unsafe(0);
		del.collectTetsConnectedToVertex(e.A, tetsA);
		if (tetsA.empty()) continue; 
			
		bool edgeExists = false;
		for (PxU32 j = 0; j < tetsA.size(); ++j)
		{
			const Tetrahedron& tet = del.tetrahedron(tetsA[j]);
			if (tet.contains(e.B))
			{
				edgeExists = true;
				break;
			}
		}
		if (!edgeExists)
			continue;

		tetsB.forceSize_Unsafe(0);
		del.collectTetsConnectedToVertex(e.B, tetsB);
		if (tetsB.empty()) continue;					   

		PxF64 firstQuality = 0, secondQuality = 0;
		bool firstOptionValid = e.B >= numFixPoints && (e.B >= l || (e.A < l && containsAll(pointToOriginalTriangle[e.A], pointToOriginalTriangle[e.B]))) &&
			del.canCollapseEdge(e.A, e.B, tetsA, tetsB, firstQuality, 0, qualityAnalyzer);
		bool secondOptionValid = e.A >= numFixPoints && (e.A >= l || (e.B < l && containsAll(pointToOriginalTriangle[e.B], pointToOriginalTriangle[e.A]))) &&
			del.canCollapseEdge(e.B, e.A, tetsB, tetsA, secondQuality, 0, qualityAnalyzer);

		if (qualityAnalyzer != NULL && firstOptionValid && secondOptionValid)
		{
			if (qualityAnalyzer->improved(firstQuality, secondQuality))
				firstOptionValid = false;
			else
				secondOptionValid = false;
		}
		if (firstOptionValid)
		{
			if (e.B >= numFixPoints)
			{
				del.collapseEdge(e.A, e.B, tetsA, tetsB);
				if (e.B < l)
					pointToOriginalTriangle[e.B].clear();
				success = true;
			}
		}
		else if (secondOptionValid)
		{
			if (e.A >= numFixPoints)
			{
				del.collapseEdge(e.B, e.A, tetsB, tetsA);
				if (e.A < l)
					pointToOriginalTriangle[e.A].clear();
				success = true;
			}
		}
	}
	return success;
}

bool physx::Ext::optimizeBySwapping(DelaunayTetrahedralizer& del, const PxArray<EdgeWithLength>& edges,
	const PxArray<PxArray<PxI32>>& pointToOriginalTriangle, BaseTetAnalyzer* qualityAnalyzer)
{
	PxI32 l = PxI32(pointToOriginalTriangle.size());

	bool success = false;
	PxArray<PxI32> tetIds;
	for (PxU32 i = 0; i < edges.size(); ++i)
	{
		const EdgeWithLength& e = edges[i];

		const bool isInteriorEdge = e.A >= l || e.B >= l || !intersectionOfSortedListsContainsElements(pointToOriginalTriangle[e.A], pointToOriginalTriangle[e.B]);
		if (isInteriorEdge)
		{
			tetIds.forceSize_Unsafe(0);
			del.collectTetsConnectedToEdge(e.A, e.B, tetIds);
			if (tetIds.size() <= 2)
				continue; //This would mean we have a surface edge

			if (del.removeEdgeByFlip(e.A, e.B, tetIds, qualityAnalyzer))
				success = true;
		}
	}
	return success;
}

static void patternSearchOptimize(PxI32 pointToModify, PxF64 step, PxArray<PxVec3d>& points, BaseTetAnalyzer& score, const PxArray<PxI32>& tetrahedra, PxF64 minStep)
{
	const PxVec3d dir[] = { PxVec3d(1.0, 0.0, 0.0), PxVec3d(-1.0, 0.0, 0.0), PxVec3d(0.0, 1.0, 0.0), PxVec3d(0.0, -1.0, 0.0), PxVec3d(0.0, 0.0, 1.0), PxVec3d(0.0, 0.0, -1.0), };
	const PxI32 oppositeDir[] = { 1, 0, 3, 2, 5, 4 };
	PxF64 currentScore = score.quality(tetrahedra); // score();
	PxVec3d p = points[pointToModify];
	PxI32 skip = -1;

	PxI32 maxIter = 100;
	PxI32 iter = 0;
	while (step > minStep && iter < maxIter)
	{
		PxF64 best = currentScore;
		PxI32 id = -1;
		for (PxI32 i = 0; i < 6; ++i)
		{
			if (i == skip)
				continue; //That point was the best before current iteration - it cannot be the best anymore

			points[pointToModify] = p + dir[i] * step;
			PxF64 s = score.quality(tetrahedra); // score();
			if (score.improved(best, s))
			{
				best = s;
				id = i;
			}
		}
		if (id >= 0)
		{
			p = p + dir[id] * step;
			points[pointToModify] = p;
			currentScore = best;
			skip = oppositeDir[id];
		}
		else
		{
			points[pointToModify] = p;
			step = step * 0.5;
			skip = -1;
		}
		++iter;
	}
}

bool physx::Ext::optimizeBySplitting(DelaunayTetrahedralizer& del, const PxArray<EdgeWithLength>& edges, const PxArray<PxArray<PxI32>>& pointToOriginalTriangle,
	PxI32 maxPointsToInsert, bool sortByQuality, BaseTetAnalyzer* qualityAnalyzer, PxF64 qualityThreshold)
{
	const PxF64 qualityThresholdPow3 = qualityThreshold * qualityThreshold * qualityThreshold;

	PxArray<PxF64> tetQualityBuffer;
	tetQualityBuffer.resize(del.numTetrahedra());
	for (PxU32 i = 0; i < del.numTetrahedra(); ++i) {
		const Tetrahedron& tet = del.tetrahedron(i);
		if (tet[0] >= 0)
			tetQualityBuffer[i] = evaluateAmipsEnergyPow3(del.point(tet[0]), del.point(tet[1]), del.point(tet[2]), del.point(tet[3]));
	}

	PxI32 l = PxI32(pointToOriginalTriangle.size());
	PxArray<SplitEdge> edgesToSplit;
	PxArray<PxI32> tetIds;
	for (PxI32 i = edges.size() - 1; i >= 0; --i)
	{
		const EdgeWithLength& e = edges[i];

		const bool isInteriorEdge = e.A >= l || e.B >= l || !intersectionOfSortedListsContainsElements(pointToOriginalTriangle[e.A], pointToOriginalTriangle[e.B]);
		if (isInteriorEdge)
		{
			tetIds.forceSize_Unsafe(0);
			del.collectTetsConnectedToEdge(e.A, e.B, tetIds);
			if (tetIds.size() <= 2)
				continue; //This would mean we got a surface edge...
			PxF64 max = 0;
			for (PxU32 j = 0; j < tetIds.size(); ++j)
			{
				if (tetQualityBuffer[tetIds[j]] > max)
					max = tetQualityBuffer[tetIds[j]];
			}
			if (max > qualityThresholdPow3)
				edgesToSplit.pushBack(SplitEdge(e.A, e.B, max, (del.point(e.A) - del.point(e.B)).magnitudeSquared(), isInteriorEdge));
		}
	}

	if (sortByQuality) 
	{
		PxSort(edgesToSplit.begin(), edgesToSplit.size(), PxGreater<SplitEdge>());
	}
	PxI32 counter = 0;
	PxArray<PxI32> tetsConnectedToEdge;
	PxArray<PxI32> tetsConnectedToVertex;
	for (PxU32 i = 0; i < edgesToSplit.size(); ++i)
	{
		const SplitEdge& e = edgesToSplit[i];
		if (i > 0 && edgesToSplit[i - 1].Q == e.Q)
			continue;

		if (counter == maxPointsToInsert)
			break;

		PxU32 id = del.addPoint((del.point(e.A) + del.point(e.B)) * 0.5);
		tetsConnectedToEdge.forceSize_Unsafe(0);
			
		del.collectTetsConnectedToEdge(e.A, e.B, tetsConnectedToEdge);
		del.insertPointIntoEdge(id, e.A, e.B, tetsConnectedToEdge, qualityAnalyzer);

		if (e.InteriorEdge && qualityAnalyzer)
		{
			tetsConnectedToVertex.forceSize_Unsafe(0);
			del.collectTetsConnectedToVertex(id, tetsConnectedToVertex);
			patternSearchOptimize(id, e.L, del.points(), *qualityAnalyzer, tetsConnectedToVertex, 0.01 * e.L);
		}

		++counter;
	}
	return edgesToSplit.size() > 0;
}

static void updateEdges(PxArray<EdgeWithLength>& edges, PxHashSet<PxU64>& tetEdges, const PxArray<Tetrahedron>& tets, const PxArray<PxVec3d>& points)
{
	edges.clear();
	tetEdges.clear();
	for (PxU32 i = 0; i < tets.size(); ++i)
	{
		const Tetrahedron& tet = tets[i];
		if (tet[0] < 0) continue;
		tetEdges.insert(key(tet[0], tet[1]));
		tetEdges.insert(key(tet[0], tet[2]));
		tetEdges.insert(key(tet[0], tet[3]));
		tetEdges.insert(key(tet[1], tet[2]));
		tetEdges.insert(key(tet[1], tet[3]));
		tetEdges.insert(key(tet[2], tet[3]));
	}
	for (PxHashSet<PxU64>::Iterator iter = tetEdges.getIterator(); !iter.done(); ++iter)
	{
		PxU64 e = *iter;
		PxI32 a = PxI32(e >> 32);
		PxI32 b = PxI32(e);
		edges.pushBack(EdgeWithLength(a, b, (points[a] - points[b]).magnitudeSquared()));
	}
	PxSort(edges.begin(), edges.size(), PxLess<EdgeWithLength>());
}

void physx::Ext::optimize(DelaunayTetrahedralizer& del, PxArray<PxArray<PxI32>>& pointToOriginalTriangle, PxI32 numFixPoints,
	PxArray<PxVec3d>& optimizedPoints, PxArray<Tetrahedron>& optimizedTets, PxI32 numPasses)
{
	PxHashSet<PxU64> tetEdges;
	PxArray<EdgeWithLength> edges;
	updateEdges(edges, tetEdges, del.tetrahedra(), del.points());

	MinimizeMaxAmipsEnergy qualityAnalyzer(del.points(), del.tetrahedra());
	//MaximizeMinTetVolume qualityAnalyzer(del.points(), del.tetrahedra());

	optimizedTets = PxArray<Tetrahedron>(del.tetrahedra());
	optimizedPoints = PxArray<PxVec3d>(del.points());

	PxF64 minVolBefore = minAbsTetVolume(del.points(), del.tetrahedra());
	PxF64 maxEnergyBefore = maxEnergy(del.points(), del.tetrahedra());
	PxI32 numPointsToInsertPerPass = PxMax(20, PxI32(0.05 * del.numPoints()));
	for (PxI32 j = 0; j < numPasses; ++j)
	{
		//(1) Splitting
		updateEdges(edges, tetEdges, del.tetrahedra(), del.points());
		optimizeBySplitting(del, edges, pointToOriginalTriangle, numPointsToInsertPerPass, true, &qualityAnalyzer);
				
		//(3) Swapping
		updateEdges(edges, tetEdges, del.tetrahedra(), del.points());
		optimizeBySwapping(del, edges, pointToOriginalTriangle, &qualityAnalyzer);
			
		//(2) Collapsing
		updateEdges(edges, tetEdges, del.tetrahedra(), del.points());
		optimizeByCollapsing(del, edges, pointToOriginalTriangle, numFixPoints, &qualityAnalyzer);
			
		//(4) Smoothing
		//Note done here - seems not to improve the quality that much

		PxF64 energy = maxEnergy(del.points(), del.tetrahedra());
		PxF64 minVol = minAbsTetVolume(del.points(), del.tetrahedra());

		if (energy / minVol >= maxEnergyBefore / minVolBefore/*minVol <= minVolBefore*/)
		{
			del.initialize(optimizedPoints, optimizedTets);

			bool swapped = true, collapsed = true;
			PxI32 maxReductionLoops = 30;
			PxI32 counter = 0;
			while (swapped || collapsed)
			{
				//(3) Swapping
				updateEdges(edges, tetEdges, del.tetrahedra(), del.points());
				swapped = optimizeBySwapping(del, edges, pointToOriginalTriangle, &qualityAnalyzer);

				//(2) Collapsing
				updateEdges(edges, tetEdges, del.tetrahedra(), del.points());
				collapsed = optimizeByCollapsing(del, edges, pointToOriginalTriangle, numFixPoints, &qualityAnalyzer);

				if (counter >= maxReductionLoops)
					break;

				++counter;
			}

			optimizedTets = PxArray<Tetrahedron>(del.tetrahedra());
			optimizedPoints = PxArray<PxVec3d>(del.points());
			return;
		}

		optimizedTets = PxArray<Tetrahedron>(del.tetrahedra());
		optimizedPoints = PxArray<PxVec3d>(del.points());

		minVolBefore = minVol;
		maxEnergyBefore = energy;
	}
}
