/* 
 * 
 * Confidential Information of Telekinesys Research Limited (t/a Havok).  Not for disclosure or distribution without Havok's
 * prior written consent.This software contains code, techniques and know-how which is confidential and proprietary to Havok.
 * Level 2 and Level 3 source code contains trade secrets of Havok. Havok Software (C) Copyright 1999-2008 Telekinesys Research Limited t/a Havok. All Rights Reserved. Use of this software is subject to the terms of an end user license agreement.
 * 
 */

#include <Demos/demos.h>
#include <Common/Base/System/hkBaseSystem.h>
#include <Common/Base/Monitor/hkMonitorStream.h>
#include <Common/Base/Thread/CriticalSection/hkCriticalSection.h>
#include <Common/Base/Thread/Semaphore/hkSemaphore.h>

#include <Physics/Collide/Shape/Convex/Capsule/hkpCapsuleShape.h>
#include <Physics/Dynamics/World/hkpPhysicsSystem.h>

#include <Physics/Utilities/VisualDebugger/Viewer/Collide/hkpShapeDisplayViewer.h>
#include <Physics/Utilities/Thread/Multithreading/hkpMultithreadingUtil.h>

#include <Common/Visualize/hkDebugDisplay.h>

#include <Demos/DemoCommon/Utilities/GameUtils/GameUtils.h>
#include <Demos/Physics/Api/Dynamics/Multithreaded/PhysicsInSeparateThread/PhysicsInSeparateThreadDemo.h>

// reuse our hollow sphere
#include <Demos/Physics/Api/Collide/Shapes/HeightField/UserHeightField/UserHeightFieldDemo.h>


static const hkReal SPHERE_RADIUS = 3.0f;

struct PhysicsInSeparateThreadDemoVariant
{
	const char*	 m_name;
	int m_numRagdolls;
	int m_maxNumDedicatedPhysicsThreads;
	int m_initialNumDedicatedPhysicsThreads;
	const char*  m_details;
};

static const char* detail = 
"This demo shows how to run the physics in a separate thread to the graphics engine. Press 'T' and 'G' keys to add/remove threads (in the last variant only).";
static const PhysicsInSeparateThreadDemoVariant g_variants[] =
{
	{ "50 ragdolls, single threaded",  50, 0, 0, detail },
	{ "50 ragdolls, separate physics thread",  50, 1, 1, detail },
	{ "50 ragdolls, separate multiple physics threads",  50, 6, 1,  detail },
	{ "50 ragdolls, separate multiple physics threads -- all threads running initially",  50, 6, 6,  detail },
};


PhysicsInSeparateThreadDemo::PhysicsInSeparateThreadDemo(hkDemoEnvironment* env): hkDefaultPhysicsDemo( env )
{
	const PhysicsInSeparateThreadDemoVariant& variant = g_variants[ this->getDemoVariant() ];

	m_numCurrentRagdolls = 0;
	m_timeToSpawnNewRagdoll = 0.5f;
	m_timeToSpawnDebris = 0.4f;

	//
	// Setup the camera
	//
	{
		hkVector4 from(0.0f, 10.0f, 0.0f);
		hkVector4 to  (0.0f, 0.0f, 0.0f);
		hkVector4 up(0.0f, 0.0f, 1.0f);
		setupDefaultCameras( env, from, to, up, 1.f, 1000.0f );
	}

	//	Create the world
	{
		hkpWorldCinfo info;
		info.m_gravity.set( 0.0f, 0.0f, -10.0f );
		info.m_simulationType = variant.m_maxNumDedicatedPhysicsThreads < 1 ? hkpWorldCinfo::SIMULATION_TYPE_CONTINUOUS : hkpWorldCinfo::SIMULATION_TYPE_MULTITHREADED;

		m_world = new hkpWorld( info );
		m_world->lock();

		// Register ALL agents (though some may not be necessary)
		hkpAgentRegisterUtil::registerAllAgents(m_world->getCollisionDispatcher());

	}

	// Setup graphics viewers
	setupGraphics();

	//
	// Collision Filter
	//
	{ 
		hkpGroupFilter* filter = new hkpGroupFilter();
		hkpGroupFilterSetup::setupGroupFilter( filter );
		m_world->setCollisionFilter(filter);
		filter->removeReference();
		m_filter = filter;
	}

	// Now we make the actual hollow sphere shape, and we choose a radius of 3 here
	{
		hkpShape* heightFieldShape = new HollowSphereShape( SPHERE_RADIUS );
		{
			hkpRigidBodyCinfo ci;
			ci.m_motionType = hkpMotion::MOTION_KEYFRAMED;
			ci.m_shape = heightFieldShape;
			ci.m_friction = 5.0f;

			hkpRigidBody * rigidBody = new hkpRigidBody( ci );

			hkVector4 angVel( 0, 1.8f ,0);
			rigidBody->setAngularVelocity( angVel );

			m_world->addEntity(rigidBody)->removeReference();
		}

		// Then we just want to cleanup the last reference we have to
		// the shape we created, and that is it, demo complete.
		heightFieldShape->removeReference();

		// We now build geometry for a sphere that will represent the heightfield 
		// (but will not be part of the simulation, i.e. display only)
		{
			// We use a slightly larger radius to compensate for the error in the polygon sphere approximation
			m_sphereDisp = new hkDisplaySphere(hkSphere(hkVector4::getZero(), 1.1f * SPHERE_RADIUS), 10, 10);

			hkArray<hkDisplayGeometry*> displayGeometries;
			displayGeometries.pushBack(m_sphereDisp);

			hkDebugDisplay::getInstance().addGeometry( displayGeometries, hkTransform::getIdentity(), (hkUlong)m_sphereDisp, 0, 0 );
			HK_SET_OBJECT_COLOR((hkUlong)m_sphereDisp, hkColor::rgbFromChars(255, 255, 255, 100));
		}
	}
	createAndAddRagdoll();

	//
	// Lets create our physics thread(s)
	//
	HK_ASSERT2(0xad675433, variant.m_initialNumDedicatedPhysicsThreads <= variant.m_maxNumDedicatedPhysicsThreads, "Variant parameters inconsistent.");
	if ( variant.m_maxNumDedicatedPhysicsThreads )
	{
		hkpMultithreadingUtilCinfo ci;
		ci.m_world = m_world;
		ci.m_numThreads = variant.m_initialNumDedicatedPhysicsThreads;
		m_multithreadingUtil = new hkpMultithreadingUtil(ci);
	}
	
	// Worlds using hkMultithreadedSimulation must mark/unmark it for writes/reads before/after every access.
	m_world->unlock();
}




PhysicsInSeparateThreadDemo::~PhysicsInSeparateThreadDemo()
{
	hkDebugDisplay::getInstance().removeGeometry( (hkUlong)m_sphereDisp, 0, 0);
	delete m_sphereDisp;

	//m_multithreadingUtil deleted in hkDefaultPhysicsDemo();
}


hkDemo::Result PhysicsInSeparateThreadDemo::stepDemo()
{
	const PhysicsInSeparateThreadDemoVariant& variant = g_variants[ this->getDemoVariant() ];

	// Worlds using hkMultithreadedSimulation must mark/unmark it for writes/reads before/after every access.
	m_world->lock();
	{
		// add more objects over time
		if ( m_numCurrentRagdolls  < variant.m_numRagdolls)
		{
			const hkReal physicsDeltaTime = 1.0f / 60.0f;
			m_timeToSpawnDebris -= physicsDeltaTime;
			if (m_timeToSpawnDebris < 0)
			{
				m_timeToSpawnDebris = .2f;
				createAndAddDebris();
			}

			m_timeToSpawnNewRagdoll -= physicsDeltaTime;
			if (m_timeToSpawnNewRagdoll < 0 )
			{
				m_numCurrentRagdolls++;
				m_timeToSpawnNewRagdoll = .4f;
				createAndAddRagdoll();
			}
		}
	}

	// Worlds using hkMultithreadedSimulation must mark/unmark it for writes/reads before/after every access.
 	m_world->unlock();

	hkDefaultPhysicsDemo::stepDemo();

	return DEMO_OK;
}


void PhysicsInSeparateThreadDemo::createAndAddDebris()
{
	hkpBoxShape* boxShape = new hkpBoxShape(hkVector4(.1f,.1f,.1f));

	hkpRigidBodyCinfo rbInfo;
	rbInfo.m_mass = 10;
	hkpInertiaTensorComputer::setShapeVolumeMassProperties(boxShape, 10, rbInfo);

	rbInfo.m_rotation.setIdentity();
	rbInfo.m_shape = boxShape;
	rbInfo.m_position.set(1,0,0);

	// Create the body as a continuous object. This tests multithreading with TOIs and continuous collision detection.
	rbInfo.m_qualityType = HK_COLLIDABLE_QUALITY_CRITICAL;

	hkpRigidBody* rb = new hkpRigidBody(rbInfo);

	m_world->addEntity(rb);
	boxShape->removeReference();
	rb->removeReference();
}

void PhysicsInSeparateThreadDemo::createAndAddRagdoll()
{
	hkVector4		position = hkVector4(0.0f, 0.0f, 0.0f);
	hkQuaternion    rotation; rotation.setIdentity();

	hkpPhysicsSystem* system = GameUtils::createRagdoll( 1.2f, position, rotation, m_filter->getNewSystemGroup(), GameUtils::RPT_CAPSULE );

	m_world->addPhysicsSystem(system);
	system->removeReference();
}




#if defined(HK_COMPILER_MWERKS)
#	pragma force_active on
#	pragma fullpath_file on
#endif

static const char helpString[] = \
"Running physics and graphics in separate threads";

HK_DECLARE_DEMO_VARIANT_USING_STRUCT( PhysicsInSeparateThreadDemo, PHYSICS, PhysicsInSeparateThreadDemoVariant, g_variants, helpString); 

/*
* Havok SDK - NO SOURCE PC DOWNLOAD, BUILD(#20080529)
*
* Confidential Information of Havok.  (C) Copyright 1999-2008 
* Telekinesys Research Limited t/a Havok. All Rights Reserved. The Havok
* Logo, and the Havok buzzsaw logo are trademarks of Havok.  Title, ownership
* rights, and intellectual property rights in the Havok software remain in
* Havok and/or its suppliers.
*
* Use of this software for evaluation purposes is subject to and indicates 
* acceptance of the End User licence Agreement for this product. A copy of 
* the license is included with this software and is also available at
* www.havok.com/tryhavok
*
*/
