//
// 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-2021 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 "ScScene.h"
#include "ScConstraintProjectionManager.h"
#include "ScBodySim.h"
#include "ScStaticSim.h"
#include "PxsContext.h"
#include "ScConstraintCore.h"
#include "ScConstraintSim.h"
#include "ScConstraintInteraction.h"
#include "ScElementSimInteraction.h"
#include "CmVisualization.h"
#include "ScObjectIDTracker.h"
#include "DyContext.h"

using namespace physx;

PX_FORCE_INLINE void invalidateConstraintGroupsOnAdd(Sc::ConstraintProjectionManager& cpm, Sc::BodySim* b0, Sc::BodySim* b1, Sc::ConstraintSim& constraint)
{
	// constraint groups get built by starting from dirty constraints that need projection. If a non-projecting constraint gets added
	// we need to restart the whole process (we do not want to track dirty non-projecting constraints because of a scenario where
	// all constraints of a group get switched to non-projecting which should kill the group and not rebuild a new one).
	if (b0 && b0->getConstraintGroup())
		cpm.invalidateGroup(*b0->getConstraintGroup(), &constraint);
	if (b1 && b1->getConstraintGroup())
		cpm.invalidateGroup(*b1->getConstraintGroup(), &constraint);
}

Sc::ConstraintSim::ConstraintSim(ConstraintCore& core, RigidCore* r0, RigidCore* r1, Scene& scene) :
	mScene		(scene),
	mCore		(core),
	mInteraction(NULL),
	mFlags		(0)
{
	mBodies[0] = (r0 && (r0->getActorCoreType() != PxActorType::eRIGID_STATIC)) ? static_cast<BodySim*>(r0->getSim()) : 0;
	mBodies[1] = (r1 && (r1->getActorCoreType() != PxActorType::eRIGID_STATIC)) ? static_cast<BodySim*>(r1->getSim()) : 0;
	
	mLowLevelConstraint.index = scene.getConstraintIDTracker().createID();
	Ps::Array<Dy::ConstraintWriteback, Ps::VirtualAllocator>& writeBackPool = scene.getDynamicsContext()->getConstraintWriteBackPool();
	if (mLowLevelConstraint.index >= writeBackPool.capacity())
	{
		writeBackPool.reserve(writeBackPool.capacity() * 2);
	}

	writeBackPool.resize(PxMax(writeBackPool.size(), mLowLevelConstraint.index + 1));
	writeBackPool[mLowLevelConstraint.index].initialize();

	if (!createLLConstraint())
		return;

	PxReal linBreakForce, angBreakForce;
	core.getBreakForce(linBreakForce, angBreakForce);
	if ((linBreakForce < PX_MAX_F32) || (angBreakForce < PX_MAX_F32))
		setFlag(eBREAKABLE);

	core.setSim(this);

	ConstraintProjectionManager& cpm = scene.getProjectionManager();
	if (!needsProjection())
		invalidateConstraintGroupsOnAdd(cpm, mBodies[0], mBodies[1], *this);
	else
		cpm.addToPendingGroupUpdates(*this);

	ConstraintSim* cs = this;  // to make the Wii U compiler happy
	mInteraction = mScene.getConstraintInteractionPool()->construct(cs, 
																	r0 ? *r0->getSim() : scene.getStaticAnchor(), 
																	r1 ? *r1->getSim() : scene.getStaticAnchor());

	PX_ASSERT(!mInteraction->isRegistered());  // constraint interactions must not register in the scene, there is a list of Sc::ConstraintSim instead
}

Sc::ConstraintSim::~ConstraintSim()
{
	PX_ASSERT(mInteraction);  // This is fine now, a body which gets removed from the scene removes all constraints automatically
	PX_ASSERT(!mInteraction->isRegistered());  // constraint interactions must not register in the scene, there is a list of Sc::ConstraintSim instead

	if (readFlag(ConstraintSim::ePENDING_GROUP_UPDATE))
		mScene.getProjectionManager().removeFromPendingGroupUpdates(*this);

	if (!isBroken())
		mInteraction->destroy();

	mScene.getConstraintIDTracker().releaseID(mLowLevelConstraint.index);
	mScene.getConstraintInteractionPool()->destroy(mInteraction);

	destroyLLConstraint();

	mCore.setSim(NULL);
}

bool Sc::ConstraintSim::createLLConstraint()
{
	Dy::Constraint& llc = mLowLevelConstraint;
	ConstraintCore& core = getCore();
	PxU32 constantBlockSize = core.getConstantBlockSize();

	void* constantBlock = mScene.allocateConstraintBlock(constantBlockSize);
	if(!constantBlock)
	{
		Ps::getFoundation().error(PxErrorCode::eINTERNAL_ERROR, __FILE__, __LINE__, "Constraint: could not allocate low-level resources.");
		return false;
	}

	//Ensure the constant block isn't just random data because some functions may attempt to use it before it is
	//setup.  Specifically pvd visualization of joints
	//-CN

	PxMemZero( constantBlock, constantBlockSize);

	core.getBreakForce(llc.linBreakForce, llc.angBreakForce);
	llc.flags					= core.getFlags();
	llc.constantBlockSize		= PxU16(constantBlockSize);

	llc.solverPrep				= core.getSolverPrep();
	llc.project					= core.getProject();
	llc.constantBlock			= constantBlock;

	//llc.index = mLowLevelConstraint.index;
	llc.body0					= mBodies[0] ? &mBodies[0]->getLowLevelBody() : 0;
	llc.body1					= mBodies[1] ? &mBodies[1]->getLowLevelBody() : 0;
	llc.bodyCore0				= mBodies[0] ? &llc.body0->getCore() : NULL;
	llc.bodyCore1				= mBodies[1] ? &llc.body1->getCore() : NULL;

	llc.minResponseThreshold	= core.getMinResponseThreshold();

	return true;
}

void Sc::ConstraintSim::destroyLLConstraint()
{
	if(mLowLevelConstraint.constantBlock)
	{
		mScene.deallocateConstraintBlock(mLowLevelConstraint.constantBlock,
										 mLowLevelConstraint.constantBlockSize);
	}
}

void Sc::ConstraintSim::preBodiesChange()
{
	PX_ASSERT(mInteraction);

	BodySim* b = getConstraintGroupBody();
	if (b)
		mScene.getProjectionManager().invalidateGroup(*b->getConstraintGroup(), this);

	if (!isBroken())
		mInteraction->destroy();

	mScene.getConstraintInteractionPool()->destroy(mInteraction);
	mInteraction = NULL;
}

void Sc::ConstraintSim::postBodiesChange(RigidCore* r0, RigidCore* r1)
{
	PX_ASSERT(mInteraction == NULL);

	BodySim* b0 = (r0 && (r0->getActorCoreType() != PxActorType::eRIGID_STATIC)) ? static_cast<BodySim*>(r0->getSim()) : 0;
	BodySim* b1 = (r1 && (r1->getActorCoreType() != PxActorType::eRIGID_STATIC)) ? static_cast<BodySim*>(r1->getSim()) : 0;

	ConstraintProjectionManager& cpm = mScene.getProjectionManager();
	PxConstraintFlags::InternalType projectionNeeded = getCore().getFlags() & PxConstraintFlag::ePROJECTION;  // can not use "needsProjection()" because that takes into account whether the constraint is broken
	if (!projectionNeeded)
		invalidateConstraintGroupsOnAdd(cpm, b0, b1, *this);
	else if (!readFlag(ConstraintSim::ePENDING_GROUP_UPDATE))
		cpm.addToPendingGroupUpdates(*this);

	Dy::Constraint& c = mLowLevelConstraint;

	c.body0 = b0 ? &b0->getLowLevelBody() : NULL;
	c.body1 = b1 ? &b1->getLowLevelBody() : NULL;

	c.bodyCore0 = c.body0 ? &c.body0->getCore() : NULL;
	c.bodyCore1 = c.body1 ? &c.body1->getCore() : NULL;

	mBodies[0] = b0;
	mBodies[1] = b1;

	ConstraintSim* cs = this;  // to make the Wii U compiler happy
	mInteraction = mScene.getConstraintInteractionPool()->construct(cs, 
																	r0 ? *r0->getSim() : mScene.getStaticAnchor(), 
																	r1 ? *r1->getSim() : mScene.getStaticAnchor());
}

void Sc::ConstraintSim::checkMaxForceExceeded()
{
	PX_ASSERT(readFlag(eCHECK_MAX_FORCE_EXCEEDED));

	Dy::ConstraintWriteback& solverOutput = mScene.getDynamicsContext()->getConstraintWriteBackPool()[mLowLevelConstraint.index];
	if(solverOutput.broken)
	{
		setFlag(ConstraintSim::eBROKEN);
		mScene.addBrokenConstraint(&mCore);
		mCore.breakApart();
		mInteraction->destroy();

		// update related SIPs
		{
			ActorSim& a0 = mInteraction->getActorSim0();
			ActorSim& a1 = mInteraction->getActorSim1();
			ActorSim& actor = (a0.getActorInteractionCount()<  a1.getActorInteractionCount()) ? a0 : a1;

			actor.setActorsInteractionsDirty(InteractionDirtyFlag::eFILTER_STATE, NULL, InteractionFlag::eRB_ELEMENT);
			// because broken constraints can re-enable contact response between the two bodies
		}

		PX_ASSERT(!readFlag(eCHECK_MAX_FORCE_EXCEEDED));
	}
}

void Sc::ConstraintSim::getForce(PxVec3& lin, PxVec3& ang)
{
	const PxReal recipDt = mScene.getOneOverDt();
	Dy::ConstraintWriteback& solverOutput= mScene.getDynamicsContext()->getConstraintWriteBackPool()[mLowLevelConstraint.index];
	lin = solverOutput.linearImpulse * recipDt;
	ang = solverOutput.angularImpulse * recipDt;
}

void Sc::ConstraintSim::setBreakForceLL(PxReal linear, PxReal angular)
{
	PxU8 wasBreakable = readFlag(eBREAKABLE);
	PxU8 isBreakable;
	if ((linear < PX_MAX_F32) || (angular < PX_MAX_F32))
		isBreakable = eBREAKABLE;
	else
		isBreakable = 0;

	if (isBreakable != wasBreakable)
	{
		if (isBreakable)
		{
			PX_ASSERT(!readFlag(eCHECK_MAX_FORCE_EXCEEDED));
			setFlag(eBREAKABLE);
			if (mInteraction->readInteractionFlag(InteractionFlag::eIS_ACTIVE))
				mScene.addActiveBreakableConstraint(this, mInteraction);
		}
		else
		{
			if (readFlag(eCHECK_MAX_FORCE_EXCEEDED))
				mScene.removeActiveBreakableConstraint(this);
			clearFlag(eBREAKABLE);
		}
	}

	mLowLevelConstraint.linBreakForce = linear;
	mLowLevelConstraint.angBreakForce = angular;
}

void Sc::ConstraintSim::postFlagChange(PxConstraintFlags oldFlags, PxConstraintFlags newFlags)
{
	mLowLevelConstraint.flags = newFlags;

	// PT: don't convert to bool if not needed
	const PxU32 hadProjection   = (oldFlags & PxConstraintFlag::ePROJECTION);
	const PxU32 needsProjection = (newFlags & PxConstraintFlag::ePROJECTION);

	if(needsProjection && !hadProjection)
	{
		PX_ASSERT(!readFlag(ConstraintSim::ePENDING_GROUP_UPDATE)); // Non-projecting constrainst should not be part of the update list

		Sc::BodySim* b0 = getBody(0);
		Sc::BodySim* b1 = getBody(1);
		if ((!b0 || b0->getConstraintGroup()) && (!b1 || b1->getConstraintGroup()))
		{
			// Already part of a constraint group but not as a projection constraint -> re-generate projection tree
			PX_ASSERT(b0 != NULL || b1 != NULL);
			if (b0)
				b0->getConstraintGroup()->markForProjectionTreeRebuild(mScene.getProjectionManager());
			else
				b1->getConstraintGroup()->markForProjectionTreeRebuild(mScene.getProjectionManager());
		}
		else
		{
			// Not part of a constraint group yet
			mScene.getProjectionManager().addToPendingGroupUpdates(*this);
		}
	}
	else if(!needsProjection && hadProjection)
	{
		if (!readFlag(ConstraintSim::ePENDING_GROUP_UPDATE))
		{
			Sc::BodySim* b = getConstraintGroupBody();
			if (b)
			{
				PX_ASSERT(b->getConstraintGroup());
				mScene.getProjectionManager().invalidateGroup(*b->getConstraintGroup(), NULL);
			}
			// This is conservative but it could be the case that this constraint with projection was the only
			// one in the group and thus the whole group must be killed. If we had a counter for the number of
			// projecting constraints per group, we could just update the projection tree if the counter was
			// larger than 1. But switching the projection flag does not seem likely anyway.
		}
		else
			mScene.getProjectionManager().removeFromPendingGroupUpdates(*this);  // Was part of a group which got invalidated

		PX_ASSERT(!readFlag(ConstraintSim::ePENDING_GROUP_UPDATE));  // make sure the expected post-condition is met for all paths
	}
}

Sc::RigidSim& Sc::ConstraintSim::getRigid(PxU32 i)
{
	PX_ASSERT(mInteraction);

	if (i == 0)
		return static_cast<RigidSim&>(mInteraction->getActorSim0());
	else
		return static_cast<RigidSim&>(mInteraction->getActorSim1());
}

bool Sc::ConstraintSim::hasDynamicBody()
{
	return (mBodies[0] && (!mBodies[0]->isKinematic())) || (mBodies[1] && (!mBodies[1]->isKinematic()));
}

static void constrainMotion(PxsRigidBody* body, PxTransform& targetPose)
{
	//Now constraint deltaPos and deltaRot
	const PxU32 lockFlags = body->mCore->lockFlags;

	if (lockFlags)
	{
		const PxTransform& currBody2World = body->mCore->body2World;

		PxVec3 deltaPos = targetPose.p - currBody2World.p;

		PxQuat deltaQ = targetPose.q * currBody2World.q.getConjugate();

		if (deltaQ.w < 0)	//shortest angle.
			deltaQ = -deltaQ;

		PxReal angle;
		PxVec3 axis;
		deltaQ.toRadiansAndUnitAxis(angle, axis);
		PxVec3 deltaRot = axis * angle;

		if (lockFlags & PxRigidDynamicLockFlag::eLOCK_LINEAR_X)
			deltaPos.x = 0.0f;
		if (lockFlags & PxRigidDynamicLockFlag::eLOCK_LINEAR_Y)
			deltaPos.y = 0.0f;
		if (lockFlags & PxRigidDynamicLockFlag::eLOCK_LINEAR_Z)
			deltaPos.z = 0.0f;
		if (lockFlags & PxRigidDynamicLockFlag::eLOCK_ANGULAR_X)
			deltaRot.x = 0.0f;
		if (lockFlags & PxRigidDynamicLockFlag::eLOCK_ANGULAR_Y)
			deltaRot.y = 0.0f;
		if (lockFlags & PxRigidDynamicLockFlag::eLOCK_ANGULAR_Z)
			deltaRot.z = 0.0f;

		targetPose.p = currBody2World.p + deltaPos;

		PxReal w2 = deltaRot.magnitudeSquared();
		if (w2 != 0.0f)
		{
			PxReal w = PxSqrt(w2);

			const PxReal v = w * 0.5f;
			PxReal s, q;
			Ps::sincos(v, s, q);
			s /= w;

			const PxVec3 pqr = deltaRot * s;
			const PxQuat quatVel(pqr.x, pqr.y, pqr.z, 0);
			PxQuat result = quatVel * currBody2World.q;

			result += currBody2World.q * q;

			targetPose.q = result.getNormalized();
		}
		else
		{
			targetPose.q = currBody2World.q;
		}
	}
}

void Sc::ConstraintSim::projectPose(BodySim* childBody, Ps::Array<BodySim*>& projectedBodies)
{
#if PX_DEBUG
	// We expect bodies in low level constraints to have same order as high level counterpart
	PxsRigidBody* b0 = mLowLevelConstraint.body0;
	PxsRigidBody* b1 = mLowLevelConstraint.body1;
	PX_ASSERT(	(childBody == getBody(0) && &childBody->getLowLevelBody() == b0) ||
				(childBody == getBody(1) && &childBody->getLowLevelBody() == b1) );
#endif

	Dy::Constraint& constraint = getLowLevelConstraint();
	bool projectToBody0 = childBody == getBody(1);

	PxsRigidBody* body0 = constraint.body0, 
				* body1 = constraint.body1;

	PxTransform body0ToWorld = body0 ? body0->getPose() : PxTransform(PxIdentity);
	PxTransform body1ToWorld = body1 ? body1->getPose() : PxTransform(PxIdentity);

	(*constraint.project)(constraint.constantBlock, body0ToWorld, body1ToWorld, projectToBody0);

	if(projectToBody0)
	{
		PX_ASSERT(body1);
		//Constrain new pose to valid world motion
		constrainMotion(body1, body1ToWorld);
		body1->setPose(body1ToWorld);
		projectedBodies.pushBack(getBody(1));
	}
	else
	{
		PX_ASSERT(body0);
		//Constrain new pose to valid world motion
		constrainMotion(body0, body0ToWorld);
		body0->setPose(body0ToWorld);
		projectedBodies.pushBack(getBody(0));
	}
}

bool Sc::ConstraintSim::needsProjection()
{
	const Dy::ConstraintWriteback& solverOutput = mScene.getDynamicsContext()->getConstraintWriteBackPool()[mLowLevelConstraint.index];
	return (getCore().getFlags() & PxConstraintFlag::ePROJECTION ) && !solverOutput.broken;
}

PX_INLINE Sc::BodySim* Sc::ConstraintSim::getConstraintGroupBody()
{
	BodySim* b = NULL;
	if (mBodies[0] && mBodies[0]->getConstraintGroup())
		b = mBodies[0];
	else if (mBodies[1] && mBodies[1]->getConstraintGroup())
		b = mBodies[1];

	return b;
}

void Sc::ConstraintSim::visualize(PxRenderBuffer& output)
{
	if(!(getCore().getFlags() & PxConstraintFlag::eVISUALIZATION))
		return;

	PxsRigidBody* b0 = mLowLevelConstraint.body0;
	PxsRigidBody* b1 = mLowLevelConstraint.body1;
	
	const PxTransform idt(PxIdentity);
	const PxTransform& t0 = b0 ? b0->getPose() : idt;
	const PxTransform& t1 = b1 ? b1->getPose() : idt;

	const PxReal frameScale = mScene.getVisualizationScale() * mScene.getVisualizationParameter(PxVisualizationParameter::eJOINT_LOCAL_FRAMES);
	const PxReal limitScale = mScene.getVisualizationScale() * mScene.getVisualizationParameter(PxVisualizationParameter::eJOINT_LIMITS);

	Cm::RenderOutput renderOut(static_cast<Cm::RenderBuffer &>(output));
	Cm::ConstraintImmediateVisualizer viz(frameScale, limitScale, renderOut);

	PxU32 flags = 0;
	if(frameScale!=0.0f)
		flags |= PxConstraintVisualizationFlag::eLOCAL_FRAMES;
	if(limitScale!=0.0f)
		flags |= PxConstraintVisualizationFlag::eLIMITS;

	mCore.getVisualize()(viz, mLowLevelConstraint.constantBlock, t0, t1, flags);
}

void Sc::ConstraintSim::setConstantsLL(void* addr)
{
	PxMemCopy(mLowLevelConstraint.constantBlock, addr, mLowLevelConstraint.constantBlockSize);

	getAnyBody()->getScene().getSimulationController()->updateJoint(mInteraction->getEdgeIndex(), &mLowLevelConstraint);
}
