// 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 "foundation/PxMemory.h"
#include "GuBV32.h"
#include "CmSerialize.h"
#include "CmUtils.h"
#include "foundation/PxUtilities.h"
#include "foundation/PxVecMath.h"

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

BV32Tree::BV32Tree(SourceMesh* meshInterface, const PxBounds3& localBounds)
{
	reset();
	init(meshInterface, localBounds);
}

BV32Tree::BV32Tree()
{
	reset();
}

void BV32Tree::release()
{
	if (!mUserAllocated)
	{
		PX_DELETE_ARRAY(mNodes);
		PX_FREE(mPackedNodes);
		PX_FREE(mTreeDepthInfo);
		PX_FREE(mRemapPackedNodeIndexWithDepth);
	}
	mNodes = NULL;
	mNbNodes = 0;
	mMaxTreeDepth = 0;
}

BV32Tree::~BV32Tree()
{
	release();
}

void BV32Tree::reset()
{
	mMeshInterface = NULL;
	mNbNodes = 0;
	mNodes = NULL;
	mNbPackedNodes = 0;
	mPackedNodes = NULL;
	mMaxTreeDepth = 0;
	mTreeDepthInfo = NULL;
	mRemapPackedNodeIndexWithDepth = NULL;
	mInitData = 0;
	mUserAllocated = false;
}

void BV32Tree::operator=(BV32Tree& v)
{
	mMeshInterface = v.mMeshInterface;
	mLocalBounds = v.mLocalBounds;
	mNbNodes = v.mNbNodes;
	mNodes = v.mNodes;
	mInitData = v.mInitData;
	mUserAllocated = v.mUserAllocated;
	v.reset();
}

bool BV32Tree::init(SourceMeshBase* meshInterface, const PxBounds3& localBounds)
{
	mMeshInterface = meshInterface;
	mLocalBounds.init(localBounds);
	return true;
}

// PX_SERIALIZATION
BV32Tree::BV32Tree(const PxEMPTY)
{
	mUserAllocated = true;
}

void BV32Tree::exportExtraData(PxSerializationContext& stream)
{
	stream.alignData(16);
	stream.writeData(mPackedNodes, mNbPackedNodes*sizeof(BV32DataPacked));
}

void BV32Tree::importExtraData(PxDeserializationContext& context)
{
	context.alignExtraData(16);
	mPackedNodes = context.readExtraData<BV32DataPacked>(mNbPackedNodes);
}
//~PX_SERIALIZATION

bool BV32Tree::load(PxInputStream& stream, bool mismatch_)
{
	PX_ASSERT(!mUserAllocated);

	release();

	PxI8 a, b, c, d;
	readChunk(a, b, c, d, stream);
	if(a != 'B' || b != 'V' || c != '3' || d != '2')
		return false;

	bool mismatch;
	PxU32 fileVersion;
	if(!readBigEndianVersionNumber(stream, mismatch_, fileVersion, mismatch))
		return false;

	mLocalBounds.mCenter.x = readFloat(mismatch, stream);
	mLocalBounds.mCenter.y = readFloat(mismatch, stream);
	mLocalBounds.mCenter.z = readFloat(mismatch, stream);
	mLocalBounds.mExtentsMagnitude = readFloat(mismatch, stream);

	mInitData = readDword(mismatch, stream);

	/*const PxU32 nbNodes = readDword(mismatch, stream);
	mNbNodes = nbNodes;

	if (nbNodes)
	{
		BV32Data* nodes = PX_NEW(BV32Data)[nbNodes];

		mNodes = nodes;
		PxMarkSerializedMemory(nodes, sizeof(BV32Data)*nbNodes);

		for (PxU32 i = 0; i<nbNodes; i++)
		{
			BV32Data& node = nodes[i];

			readFloatBuffer(&node.mCenter.x, 3, mismatch, stream);
			node.mData = readDword(mismatch, stream);
			readFloatBuffer(&node.mExtents.x, 3, mismatch, stream);
		}
	}*/

	//read SOA format node data
	const PxU32 nbPackedNodes = readDword(mismatch, stream);
	mNbPackedNodes = nbPackedNodes;

	if (nbPackedNodes)
	{
		mPackedNodes = reinterpret_cast<BV32DataPacked*>(PX_ALLOC(sizeof(BV32DataPacked)*nbPackedNodes, "BV32DataPacked"));

		PxMarkSerializedMemory(mPackedNodes, sizeof(BV32DataPacked)*nbPackedNodes);

		for (PxU32 i = 0; i < nbPackedNodes; ++i)
		{
			BV32DataPacked& node = mPackedNodes[i];
			node.mNbNodes = readDword(mismatch, stream);
			PX_ASSERT(node.mNbNodes > 0);
			node.mDepth = readDword(mismatch, stream);
			ReadDwordBuffer(node.mData, node.mNbNodes, mismatch, stream);
			const PxU32 nbElements = 4 * node.mNbNodes;
			readFloatBuffer(&node.mMin[0].x, nbElements, mismatch, stream);
			readFloatBuffer(&node.mMax[0].x, nbElements, mismatch, stream);
			
		}
	}

	const PxU32 maxTreeDepth = readDword(mismatch, stream);
	mMaxTreeDepth = maxTreeDepth;

	if (maxTreeDepth > 0)
	{
		mTreeDepthInfo = reinterpret_cast<BV32DataDepthInfo*>(PX_ALLOC(sizeof(BV32DataDepthInfo)*maxTreeDepth, "BV32DataDepthInfo"));

		for (PxU32 i = 0; i < maxTreeDepth; ++i)
		{
			BV32DataDepthInfo& info = mTreeDepthInfo[i];

			info.offset = readDword(mismatch, stream);
			info.count = readDword(mismatch, stream);
		}

		mRemapPackedNodeIndexWithDepth = reinterpret_cast<PxU32*>(PX_ALLOC(sizeof(PxU32)*nbPackedNodes, "PxU32"));

		ReadDwordBuffer(mRemapPackedNodeIndexWithDepth, nbPackedNodes, mismatch, stream);
	}
	return true;
}

void BV32Tree::createSOAformatNode(BV32DataPacked& packedData, 
	const BV32Data& node, const PxU32 childOffset, PxU32& currentIndex, PxU32& nbPackedNodes)
{
	//found the next 32 nodes and fill it in SOA format
	
	const PxU32 nbChildren = node.getNbChildren();
	const PxU32 offset = node.getChildOffset();

	packedData.mDepth = node.mDepth;

	for (PxU32 i = 0; i < nbChildren; ++i)
	{
		BV32Data& child = mNodes[offset + i];

		packedData.mMin[i] = PxVec4(child.mMin, 0.f);
		packedData.mMax[i] = PxVec4(child.mMax, 0.f);
		packedData.mData[i] = PxU32(child.mData);
	}

	packedData.mNbNodes = nbChildren;
	
	PxU32 NbToGo = 0;
	PxU32 NextIDs[32];
	PxMemSet(NextIDs, PX_INVALID_U32, sizeof(PxU32) * 32);
	const BV32Data* ChildNodes[32];
	PxMemSet(ChildNodes, 0, sizeof(BV32Data*) * 32);
	
	for (PxU32 i = 0; i< nbChildren; i++)
	{
		BV32Data& child = mNodes[offset + i];

		if (!child.isLeaf())
		{
			const PxU32 NextID = currentIndex;

			const PxU32 ChildSize = child.getNbChildren() - child.mNbLeafNodes;
			currentIndex += ChildSize;

			//packedData.mData[i] = (packedData.mData[i] & ((1 << GU_BV4_CHILD_OFFSET_SHIFT_COUNT) - 1)) | (NextID << GU_BV4_CHILD_OFFSET_SHIFT_COUNT);
			packedData.mData[i] = (packedData.mData[i] & ((1 << GU_BV4_CHILD_OFFSET_SHIFT_COUNT) - 1)) | ((childOffset + NbToGo) << GU_BV4_CHILD_OFFSET_SHIFT_COUNT);

			NextIDs[NbToGo] = NextID;
			ChildNodes[NbToGo] = &child;
			NbToGo++;
		}
	}

	nbPackedNodes += NbToGo;
	for (PxU32 i = 0; i < NbToGo; ++i)
	{
		const BV32Data& child = *ChildNodes[i];
	
		BV32DataPacked& childData = mPackedNodes[childOffset+i];
		
		createSOAformatNode(childData, child, NextIDs[i], currentIndex, nbPackedNodes);
	}
}

bool BV32Tree::refit(float epsilon)
{
	using namespace aos;

	if (!mPackedNodes)
	{
		PxBounds3 bounds;
		bounds.setEmpty();
		if (mMeshInterface)
		{
			PxU32 nbVerts = mMeshInterface->getNbVertices();
			const PxVec3* verts = mMeshInterface->getVerts();
			while (nbVerts--)
				bounds.include(*verts++);
			mLocalBounds.init(bounds);
		}
		return true;
	}

	class PxBounds3Padded : public PxBounds3
	{
	public:
		PX_FORCE_INLINE PxBounds3Padded() {}
		PX_FORCE_INLINE ~PxBounds3Padded() {}
		PxU32	padding;
	};

	PxU32 nb = mNbPackedNodes;

	while (nb--)
	{
		BV32DataPacked* PX_RESTRICT current = mPackedNodes + nb;
		const PxU32 nbChildren = current->mNbNodes;

		for (PxU32 j = 0; j< nbChildren; j++)
		{
			if (current->isLeaf(j))
			{
				PxU32 nbTets = current->getNbReferencedPrimitives(j);
				PxU32 primIndex = current->getPrimitiveStartIndex(j);

				Vec4V minV = V4Load(FLT_MAX);
				Vec4V maxV = V4Load(-FLT_MAX);

				//TetrahedronPointers
				do
				{
					PX_ASSERT(primIndex< mMeshInterface->getNbPrimitives());

					//meshInterface->getTriangle(VP, primIndex);
					Vec4V tMin, tMax;
					mMeshInterface->getPrimitiveBox(primIndex, tMin, tMax);
					minV = V4Min(minV, tMin);
					maxV = V4Max(maxV, tMax);

					primIndex++;
				} while (--nbTets);

				const Vec4V epsilonV = V4Load(epsilon);
				minV = V4Sub(minV, epsilonV);
				maxV = V4Add(maxV, epsilonV);

				PxBounds3Padded refitBox;
				V4StoreU_Safe(minV, &refitBox.minimum.x);
				V4StoreU_Safe(maxV, &refitBox.maximum.x);

				current->mMin[j].x = refitBox.minimum.x;
				current->mMin[j].y = refitBox.minimum.y;
				current->mMin[j].z = refitBox.minimum.z;
				current->mMax[j].x = refitBox.maximum.x;
				current->mMax[j].y = refitBox.maximum.y;
				current->mMax[j].z = refitBox.maximum.z;
			}
			else
			{
				PxU32 childOffset = current->getChildOffset(j);

				PX_ASSERT(childOffset < mNbPackedNodes);
				
				BV32DataPacked* next = mPackedNodes + childOffset;

				const PxU32 nextNbChilds = next->mNbNodes;

				Vec4V minV = V4Load(FLT_MAX);
				Vec4V maxV = V4Load(-FLT_MAX);

				for (PxU32 a = 0; a < nextNbChilds; ++a)
				{
					const Vec4V tMin = V4LoadU(&next->mMin[a].x);
					const Vec4V tMax = V4LoadU(&next->mMax[a].x);

					minV = V4Min(minV, tMin);
					maxV = V4Max(maxV, tMax);
				}

				PxBounds3Padded refitBox;
				V4StoreU_Safe(minV, &refitBox.minimum.x);
				V4StoreU_Safe(maxV, &refitBox.maximum.x);

				current->mMin[j].x = refitBox.minimum.x;
				current->mMin[j].y = refitBox.minimum.y;
				current->mMin[j].z = refitBox.minimum.z;

				current->mMax[j].x = refitBox.maximum.x;
				current->mMax[j].y = refitBox.maximum.y;
				current->mMax[j].z = refitBox.maximum.z;
			}
		}
	}

	BV32DataPacked* root = mPackedNodes;
	{
		PxBounds3 globalBounds;
		globalBounds.setEmpty();

		const PxU32 nbChildren = root->mNbNodes;

		Vec4V minV = V4Load(FLT_MAX);
		Vec4V maxV = V4Load(-FLT_MAX);

		for (PxU32 a = 0; a < nbChildren; ++a)
		{
			const Vec4V tMin = V4LoadU(&root->mMin[a].x);
			const Vec4V tMax = V4LoadU(&root->mMax[a].x);

			minV = V4Min(minV, tMin);
			maxV = V4Max(maxV, tMax);
		}

		PxBounds3Padded refitBox;
		V4StoreU_Safe(minV, &refitBox.minimum.x);
		V4StoreU_Safe(maxV, &refitBox.maximum.x);
		globalBounds.minimum.x = refitBox.minimum.x;
		globalBounds.minimum.y = refitBox.minimum.y;
		globalBounds.minimum.z = refitBox.minimum.z;

		globalBounds.maximum.x = refitBox.maximum.x;
		globalBounds.maximum.y = refitBox.maximum.y;
		globalBounds.maximum.z = refitBox.maximum.z;

		mLocalBounds.init(globalBounds);
	}
	return true;
}
