// 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 "ExtSqManager.h"
	#define SQ_DEBUG_VIZ_STATIC_COLOR	PxU32(PxDebugColor::eARGB_BLUE)
	#define SQ_DEBUG_VIZ_DYNAMIC_COLOR	PxU32(PxDebugColor::eARGB_RED)
	#define SQ_DEBUG_VIZ_STATIC_COLOR2	PxU32(PxDebugColor::eARGB_DARKBLUE)
	#define SQ_DEBUG_VIZ_DYNAMIC_COLOR2	PxU32(PxDebugColor::eARGB_DARKRED)
	#define SQ_DEBUG_VIZ_COMPOUND_COLOR	PxU32(PxDebugColor::eARGB_MAGENTA)
#include "GuBounds.h"

using namespace physx;
using namespace Sq;
using namespace Gu;

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

#include "SqFactory.h"
#include "common/PxProfileZone.h"
#include "common/PxRenderBuffer.h"
#include "GuBVH.h"
#include "foundation/PxAlloca.h"

// PT: this is a customized version of physx::Sq::PrunerManager that supports more than 2 hardcoded pruners.
// It might not be possible to support the whole PxSceneQuerySystem API with an arbitrary number of pruners.

ExtPrunerManager::ExtPrunerManager(PxU64 contextID, float inflation, const Adapter& adapter, bool usesTreeOfPruners) :
	mAdapter				(adapter),
	mTreeOfPruners			(NULL),
	mContextID				(contextID),
	mStaticTimestamp		(0),
	mRebuildRateHint		(100),
	mInflation				(inflation),
	mPrunerNeedsUpdating	(false),
	mTimestampNeedsUpdating	(false),
	mUsesTreeOfPruners		(usesTreeOfPruners)
{
	mCompoundPrunerExt.mPruner = createCompoundPruner(contextID);
}

ExtPrunerManager::~ExtPrunerManager()
{
	PX_DELETE(mTreeOfPruners);

	const PxU32 nb = mPrunerExt.size();
	for(PxU32 i=0;i<nb;i++)
	{
		PrunerExt* pe = mPrunerExt[i];
		PX_DELETE(pe);
	}
}

PxU32 ExtPrunerManager::addPruner(Pruner* pruner, PxU32 preallocated)
{
	const PxU32 index = mPrunerExt.size();
	PrunerExt* pe = PX_NEW(PrunerExt);
	pe->init(pruner);
	if(preallocated)
		pe->preallocate(preallocated);
	mPrunerExt.pushBack(pe);
	return index;
}

void ExtPrunerManager::preallocate(PxU32 prunerIndex, PxU32 nbShapes)
{
	const bool preallocateCompoundPruner = prunerIndex==0xffffffff;
	if(preallocateCompoundPruner)
	{
		mCompoundPrunerExt.preallocate(nbShapes);
	}
	else
	{
		if(prunerIndex>=mPrunerExt.size())
		{
			PxGetFoundation().error(PxErrorCode::eINVALID_PARAMETER, PX_FL, "Invalid pruner index");
			return;
		}

		mPrunerExt[prunerIndex]->preallocate(nbShapes);
	}
}

void ExtPrunerManager::flushMemory()
{
	const PxU32 nb = mPrunerExt.size();
	for(PxU32 i=0;i<nb;i++)
	{
		PrunerExt* pe = mPrunerExt[i];
		pe->flushMemory();
	}

	mCompoundPrunerExt.flushMemory();
}

PrunerHandle ExtPrunerManager::addPrunerShape(const PrunerPayload& payload, PxU32 prunerIndex, bool dynamic, PrunerCompoundId compoundId, const PxBounds3& bounds, const PxTransform& transform, bool hasPruningStructure)
{
	if(compoundId==INVALID_COMPOUND_ID && prunerIndex>=mPrunerExt.size())
	{
		PxGetFoundation().error(PxErrorCode::eINVALID_PARAMETER, PX_FL, "Invalid pruner index");
		return INVALID_PRUNERHANDLE;
	}

	mPrunerNeedsUpdating = true;

	if(!dynamic)
		invalidateStaticTimestamp();

	PrunerHandle handle;
	if(compoundId == INVALID_COMPOUND_ID)
	{
		PX_ASSERT(prunerIndex<mPrunerExt.size());
		PX_ASSERT(mPrunerExt[prunerIndex]->pruner());
		mPrunerExt[prunerIndex]->pruner()->addObjects(&handle, &bounds, &payload, &transform, 1, hasPruningStructure);
		//mPrunerExt[prunerIndex].growDirtyList(handle);
	}
	else
	{
		PX_ASSERT(mCompoundPrunerExt.pruner());
		mCompoundPrunerExt.pruner()->addObject(compoundId, handle, bounds, payload, transform);
	}

	return handle;
}

void ExtPrunerManager::removePrunerShape(PxU32 prunerIndex, bool dynamic, PrunerCompoundId compoundId, PrunerHandle shapeHandle, PrunerPayloadRemovalCallback* removalCallback)
{
	if(compoundId==INVALID_COMPOUND_ID && prunerIndex>=mPrunerExt.size())
	{
		PxGetFoundation().error(PxErrorCode::eINVALID_PARAMETER, PX_FL, "Invalid pruner index");
		return;
	}

	mPrunerNeedsUpdating = true;

	if(!dynamic)
		invalidateStaticTimestamp();

	if(compoundId == INVALID_COMPOUND_ID)
	{
		PX_ASSERT(prunerIndex<mPrunerExt.size());
		PX_ASSERT(mPrunerExt[prunerIndex]->pruner());

		mPrunerExt[prunerIndex]->removeFromDirtyList(shapeHandle);
		mPrunerExt[prunerIndex]->pruner()->removeObjects(&shapeHandle, 1, removalCallback);
	}
	else
	{
		mCompoundPrunerExt.removeFromDirtyList(compoundId, shapeHandle);
		mCompoundPrunerExt.pruner()->removeObject(compoundId, shapeHandle, removalCallback);
	}
}

void ExtPrunerManager::markForUpdate(PxU32 prunerIndex, bool dynamic, PrunerCompoundId compoundId, PrunerHandle shapeHandle, const PxTransform& transform)
{ 
	if(compoundId==INVALID_COMPOUND_ID && prunerIndex>=mPrunerExt.size())
	{
		PxGetFoundation().error(PxErrorCode::eINVALID_PARAMETER, PX_FL, "Invalid pruner index");
		return;
	}

	mPrunerNeedsUpdating = true;

	if(!dynamic)
		invalidateStaticTimestamp();

	if(compoundId == INVALID_COMPOUND_ID)
	{
		PX_ASSERT(prunerIndex<mPrunerExt.size());
		PX_ASSERT(mPrunerExt[prunerIndex]->pruner());

		// PT: TODO: at this point do we still need a dirty list? we could just update the bounds directly?
		mPrunerExt[prunerIndex]->addToDirtyList(shapeHandle, dynamic, transform);
	}
	else
		mCompoundPrunerExt.addToDirtyList(compoundId, shapeHandle, transform);
}

void ExtPrunerManager::setDynamicTreeRebuildRateHint(PxU32 rebuildRateHint)
{
	mRebuildRateHint = rebuildRateHint;

	// PT: we are still using the same rebuild hint for all pruners here, which may or may
	// not make sense. We could also have a different build rate for each pruner.

	const PxU32 nb = mPrunerExt.size();
	for(PxU32 i=0;i<nb;i++)
	{
		PrunerExt* pe = mPrunerExt[i];
		Pruner* pruner = pe->pruner();
		if(pruner && pruner->isDynamic())
			static_cast<DynamicPruner*>(pruner)->setRebuildRateHint(rebuildRateHint);
	}
}

void ExtPrunerManager::afterSync(bool buildStep, bool commit)
{
	PX_PROFILE_ZONE("Sim.sceneQueryBuildStep", mContextID);

	if(!buildStep && !commit)
	{
		mPrunerNeedsUpdating = true;
		return;
	}

	// flush user modified objects
	flushShapes();

	// PT: TODO: with N pruners this part would be worth multi-threading

	const PxU32 nb = mPrunerExt.size();
	for(PxU32 i=0;i<nb;i++)
	{
		PrunerExt* pe = mPrunerExt[i];
		Pruner* pruner = pe->pruner();
		if(pruner)
		{
			if(pruner->isDynamic())
				static_cast<DynamicPruner*>(pruner)->buildStep(true);

			if(commit)
				pruner->commit();
		}
	}

	if(commit)
	{
		if(mUsesTreeOfPruners)
			createTreeOfPruners();
	}

	mPrunerNeedsUpdating = !commit;
}

void ExtPrunerManager::flushShapes()
{
	PX_PROFILE_ZONE("SceneQuery.flushShapes", mContextID);

	// must already have acquired writer lock here

	const float inflation = 1.0f + mInflation;

	bool mustInvalidateStaticTimestamp = false;
	const PxU32 nb = mPrunerExt.size();
	for(PxU32 i=0;i<nb;i++)
	{
		PrunerExt* pe = mPrunerExt[i];
		if(pe->processDirtyList(i, mAdapter, inflation))
			mustInvalidateStaticTimestamp = true;
	}

	if(mustInvalidateStaticTimestamp)
		invalidateStaticTimestamp();

	mCompoundPrunerExt.flushShapes(mAdapter, inflation);
}

void ExtPrunerManager::flushUpdates()
{
	PX_PROFILE_ZONE("SceneQuery.flushUpdates", mContextID);

	if(mPrunerNeedsUpdating)
	{
		// no need to take lock if manual sq update is enabled
		// as flushUpdates will only be called from NpScene::flushQueryUpdates()
		mSQLock.lock();

		if(mPrunerNeedsUpdating)
		{
			flushShapes();

			const PxU32 nb = mPrunerExt.size();
			for(PxU32 i=0;i<nb;i++)
			{
				PrunerExt* pe = mPrunerExt[i];
				if(pe->pruner())
					pe->pruner()->commit();
			}

			if(mUsesTreeOfPruners)
				createTreeOfPruners();

			PxMemoryBarrier();
			mPrunerNeedsUpdating = false;
		}
		mSQLock.unlock();
	}
}

void ExtPrunerManager::forceRebuildDynamicTree(PxU32 prunerIndex)
{
	// PT: beware here when called from the PxScene, the prunerIndex may not match

	PX_PROFILE_ZONE("SceneQuery.forceDynamicTreeRebuild", mContextID);

	if(prunerIndex>=mPrunerExt.size())
	{
		PxGetFoundation().error(PxErrorCode::eINVALID_PARAMETER, PX_FL, "Invalid pruner index");
		return;
	}

	PxMutex::ScopedLock lock(mSQLock);

	PrunerExt* pe = mPrunerExt[prunerIndex];
	Pruner* pruner = pe->pruner();

	if(pruner && pruner->isDynamic())
	{
		static_cast<DynamicPruner*>(pruner)->purge();
		static_cast<DynamicPruner*>(pruner)->commit();
	}
}

void* ExtPrunerManager::prepareSceneQueriesUpdate(PxU32 prunerIndex)
{
	// PT: beware here when called from the PxScene, the prunerIndex may not match

	if(prunerIndex>=mPrunerExt.size())
	{
		PxGetFoundation().error(PxErrorCode::eINVALID_PARAMETER, PX_FL, "Invalid pruner index");
		return NULL;
	}

	PX_ASSERT(mPrunerExt[prunerIndex]->pruner());

	bool retVal = false;
	Pruner* pruner = mPrunerExt[prunerIndex]->pruner();
	if(pruner && pruner->isDynamic())
		retVal = static_cast<DynamicPruner*>(pruner)->prepareBuild();

	return retVal ? pruner : NULL;
}

void ExtPrunerManager::sceneQueryBuildStep(void* handle)
{
	PX_PROFILE_ZONE("SceneQuery.sceneQueryBuildStep", mContextID);

	Pruner* pruner = reinterpret_cast<Pruner*>(handle);

	if(pruner && pruner->isDynamic())
	{
		const bool buildFinished = static_cast<DynamicPruner*>(pruner)->buildStep(false);
		if(buildFinished)
			mPrunerNeedsUpdating = true;
	}
}

void ExtPrunerManager::visualize(PxU32 prunerIndex, PxRenderOutput& out) const
{
	// PT: beware here when called from the PxScene, the prunerIndex may not match
	// This is quite awkward and should be improved.

	// PT: problem here is that this function will be called by the regular PhysX scene when plugged to its PxSceneDesc,
	// and the calling code only understands the regular PhysX pruners.

	const bool visualizeCompoundPruner = prunerIndex==0xffffffff;
	if(visualizeCompoundPruner)
	{
		const CompoundPruner* cp = mCompoundPrunerExt.pruner();
		if(cp)
			cp->visualizeEx(out, SQ_DEBUG_VIZ_COMPOUND_COLOR, true, true);
	}
	else
	{
		if(prunerIndex>=mPrunerExt.size())
		{
			PxGetFoundation().error(PxErrorCode::eINVALID_PARAMETER, PX_FL, "Invalid pruner index");
			return;
		}

		PrunerExt* pe = mPrunerExt[prunerIndex];
		Pruner* pruner = pe->pruner();
		if(pruner)
		{
			// PT: TODO: this doesn't really work: the static color was for static shapes but they
			// could still be stored in a dynamic pruner. So this code doesn't use a color scheme
			// consistent with what we use in the default code.
			if(pruner->isDynamic())
			{
				//if(visDynamic)
					pruner->visualize(out, SQ_DEBUG_VIZ_DYNAMIC_COLOR, SQ_DEBUG_VIZ_DYNAMIC_COLOR2);
			}
			else
			{
				//if(visStatic)
					pruner->visualize(out, SQ_DEBUG_VIZ_STATIC_COLOR, SQ_DEBUG_VIZ_STATIC_COLOR2);
			}
		}
	}
}

void ExtPrunerManager::shiftOrigin(const PxVec3& shift)
{
	const PxU32 nb = mPrunerExt.size();
	for(PxU32 i=0;i<nb;i++)
	{
		PrunerExt* pe = mPrunerExt[i];
		Pruner* pruner = pe->pruner();
		if(pruner)
			pruner->shiftOrigin(shift);
	}

	mCompoundPrunerExt.pruner()->shiftOrigin(shift);
}

void ExtPrunerManager::addCompoundShape(const PxBVH& pxbvh, PrunerCompoundId compoundId, const PxTransform& compoundTransform, PrunerHandle* prunerHandle, const PrunerPayload* payloads, const PxTransform* transforms, bool isDynamic)
{
	const Gu::BVH& bvh = static_cast<const Gu::BVH&>(pxbvh);

	PX_ASSERT(mCompoundPrunerExt.mPruner);
	mCompoundPrunerExt.mPruner->addCompound(prunerHandle, bvh, compoundId, compoundTransform, isDynamic, payloads, transforms);
	if(!isDynamic)
		invalidateStaticTimestamp();
}

void ExtPrunerManager::updateCompoundActor(PrunerCompoundId compoundId, const PxTransform& compoundTransform)
{	
	PX_ASSERT(mCompoundPrunerExt.mPruner);
	const bool isDynamic = mCompoundPrunerExt.mPruner->updateCompound(compoundId, compoundTransform);
	if(!isDynamic)
		invalidateStaticTimestamp();
}

void ExtPrunerManager::removeCompoundActor(PrunerCompoundId compoundId, PrunerPayloadRemovalCallback* removalCallback)
{
	PX_ASSERT(mCompoundPrunerExt.mPruner);
	const bool isDynamic = mCompoundPrunerExt.mPruner->removeCompound(compoundId, removalCallback);
	if(!isDynamic)
		invalidateStaticTimestamp();
}

void ExtPrunerManager::sync(PxU32 prunerIndex, const PrunerHandle* handles, const PxU32* boundsIndices, const PxBounds3* bounds, const PxTransform32* transforms, PxU32 count, const PxBitMap& ignoredIndices)
{
	if(!count)
		return;

	if(prunerIndex>=mPrunerExt.size())
	{
		PxGetFoundation().error(PxErrorCode::eINVALID_PARAMETER, PX_FL, "Invalid pruner index");
		return;
	}

	Pruner* pruner = getPruner(prunerIndex);
	if(!pruner)
		return;

	PxU32 startIndex = 0;
	PxU32 numIndices = count;

	// if shape sim map is not empty, parse the indices and skip update for the dirty one
	if(ignoredIndices.count())
	{
		// PT: I think this codepath was used with SCB / buffered changes, but it's not needed anymore
		numIndices = 0;

		for(PxU32 i=0; i<count; i++)
		{
//			if(ignoredIndices.test(boundsIndices[i]))
			if(ignoredIndices.boundedTest(boundsIndices[i]))
			{
				pruner->updateObjects(handles + startIndex, numIndices, mInflation, boundsIndices + startIndex, bounds, transforms);
				numIndices = 0;
				startIndex = i + 1;
			}
			else
				numIndices++;
		}
		// PT: we fallback to the next line on purpose - no "else"
	}

	pruner->updateObjects(handles + startIndex, numIndices, mInflation, boundsIndices + startIndex, bounds, transforms);
}

PxU32 ExtPrunerManager::startCustomBuildstep()
{
	PX_PROFILE_ZONE("SceneQuery.startCustomBuildstep", mContextID);

	// flush user modified objects
	//flushShapes();
	{
		PX_PROFILE_ZONE("SceneQuery.flushShapes", mContextID);
		// PT: only flush the compound pruner synchronously
		// must already have acquired writer lock here
		const float inflation = 1.0f + mInflation;
		mCompoundPrunerExt.flushShapes(mAdapter, inflation);
	}

	mTimestampNeedsUpdating = false;

	return mPrunerExt.size();
}

void ExtPrunerManager::customBuildstep(PxU32 index)
{
	PX_PROFILE_ZONE("SceneQuery.customBuildstep", mContextID);

	PX_ASSERT(index<mPrunerExt.size());

	PrunerExt* pe = mPrunerExt[index];
	Pruner* pruner = pe->pruner();

	//void ExtPrunerManager::flushShapes()
	{
		PX_PROFILE_ZONE("SceneQuery.flushShapes", mContextID);
		// must already have acquired writer lock here
		const float inflation = 1.0f + mInflation;
		if(pe->processDirtyList(index, mAdapter, inflation))
			mTimestampNeedsUpdating = true;
	}

	if(pruner)
	{
		if(pruner->isDynamic())
			static_cast<DynamicPruner*>(pruner)->buildStep(true);	// PT: "true" because that parameter was made for PxSceneQuerySystem::sceneQueryBuildStep(), not us

		pruner->commit();
	}
}

void ExtPrunerManager::finishCustomBuildstep()
{
	PX_PROFILE_ZONE("SceneQuery.finishCustomBuildstep", mContextID);

	if(mUsesTreeOfPruners)
		createTreeOfPruners();

	mPrunerNeedsUpdating = false;
	if(mTimestampNeedsUpdating)
		invalidateStaticTimestamp();
}

void ExtPrunerManager::createTreeOfPruners()
{
	PX_PROFILE_ZONE("SceneQuery.createTreeOfPruners", mContextID);

	PX_DELETE(mTreeOfPruners);

	mTreeOfPruners = PX_NEW(BVH)(NULL);

	const PxU32 nb = mPrunerExt.size();

	PxBounds3* prunerBounds = reinterpret_cast<PxBounds3*>(PxAlloca(sizeof(PxBounds3)*(nb+1)));

	PxU32 nbBounds = 0;
	for(PxU32 i=0;i<nb;i++)
	{
		PrunerExt* pe = mPrunerExt[i];
		Pruner* pruner = pe->pruner();
		if(pruner)
			pruner->getGlobalBounds(prunerBounds[nbBounds++]);
	}

	mTreeOfPruners->init(nbBounds, NULL, prunerBounds, sizeof(PxBounds3), BVH_SPLATTER_POINTS, 1, 0.01f);
}
