/* 
 * 
 * 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 <Physics/Collide/Query/Collector/PointCollector/hkpFixedBufferCdPointCollector.h>
#include <Physics/Collide/Query/Multithreaded/hkpCollisionJobs.h>

#include <Common/Visualize/hkDebugDisplay.h>

#include <Demos/DemoCommon/Utilities/GameUtils/GameUtils.h>
#include <Demos/DemoCommon/Utilities/CollisionQuery/CollisionQueryMultithreadedUtility.h>

#include <Demos/Physics/Api/Collide/AsynchronousQueries/ClosestPointsMultithreading/ClosestPointsMultithreadingApiDemo.h>


#if defined(HK_PLATFORM_PS3)
//	# of real SPUs
#	define NUM_SPUS 6
#else
//	# of simulated SPUs
#	define NUM_SPUS 1
#endif


struct ClosestPointsMultithreadingApiDemoVariant
{
	enum DemoType
	{
		MULTITHREADED_ON_PPU,
		MULTITHREADED_ON_SPU,
		MULTITHREADED_ON_PPU_AND_SPU,
	};

	const char*	m_name;
	DemoType	m_demoType;
	const char*	m_details;
};


static const char helpString[] = \
	"This demonstrates how to find the closest point(s) between a given pair of objects using multithreading either on PPU or on SPU (if available).\n" \
	"Hold down \221 to find the closest points between the box and ALL its surrounding objects (using an hkpWorldGetClosestPointsJob).\n" \
	"Release \221 to find the closest points between the box and ONE of the surrounding objects (using an hkpPairGetClosestPointsJob).";


static const ClosestPointsMultithreadingApiDemoVariant g_ClosestPointsMultithreadingApiDemoVariants[] =
{
#if defined(HK_PLATFORM_HAS_SPU)
	{ "Multithreaded on PPU",		ClosestPointsMultithreadingApiDemoVariant::MULTITHREADED_ON_PPU,			helpString },
	{ "Multithreaded on SPU",		ClosestPointsMultithreadingApiDemoVariant::MULTITHREADED_ON_SPU,			helpString },
	{ "Multithreaded on PPU & SPU",	ClosestPointsMultithreadingApiDemoVariant::MULTITHREADED_ON_PPU_AND_SPU,	helpString },
#else
	{ "Multithreaded",				ClosestPointsMultithreadingApiDemoVariant::MULTITHREADED_ON_PPU,			helpString },
#endif
};


ClosestPointsMultithreadingApiDemo::ClosestPointsMultithreadingApiDemo(hkDemoEnvironment* env)
	: hkDefaultPhysicsDemo(env),
	m_semaphore(0,1000)
{
	const ClosestPointsMultithreadingApiDemoVariant& variant =  g_ClosestPointsMultithreadingApiDemoVariants[m_variantId];

	//
	// Setup the camera.
	//
	{
		hkVector4 from( 0.0f, 4.0f, 12.0f);
		hkVector4 to  ( 0.0f,-2.0f,  0.0f);
		hkVector4 up  ( 0.0f, 1.0f,  0.0f);
		setupDefaultCameras(env, from, to, up);
	}

	//
	// Create the world.
	//
	{
		hkpWorldCinfo info;
		info.setupSolverInfo(hkpWorldCinfo::SOLVER_TYPE_4ITERS_MEDIUM); 

		// Set gravity to zero so body floats.
		info.m_gravity.set(0.0f, 0.0f, 0.0f);	
		info.setBroadPhaseWorldSize( 100.0f );
		m_world = new hkpWorld(info);
		m_world->lock();

		// Disable backface culling, since we have mopp's etc.
		setGraphicsState(HKG_ENABLED_CULLFACE, false);

		setupGraphics();
	}

	// register all agents(however, we put all objects into the some group,
	// so no collision will be detected
	hkpAgentRegisterUtil::registerAllAgents( m_world->getCollisionDispatcher() );

	//
	// Create some bodies (reuse the ShapeRayCastApi demo)
	//
	createBodies();

	m_world->unlock();

	//
	// Setup multithreading.
	//
	{
		CollisionQueryMultithreadedUtility::InputPs3 input;
		{
			input.m_world				= m_world;
			input.m_numSpus				= NUM_SPUS;
			input.m_spuUtil				= &m_spuUtil;
			input.m_monitorCacheArray	= &m_monitorCaches[0];
		}

		m_util = new CollisionQueryMultithreadedUtility();

		if		( variant.m_demoType == ClosestPointsMultithreadingApiDemoVariant::MULTITHREADED_ON_PPU			) m_util->initPpu(input);
		else if ( variant.m_demoType == ClosestPointsMultithreadingApiDemoVariant::MULTITHREADED_ON_SPU			) m_util->initSpu(input);
		else if ( variant.m_demoType == ClosestPointsMultithreadingApiDemoVariant::MULTITHREADED_ON_PPU_AND_SPU ) m_util->initPpuAndSpu(input);

		// Special case for this demo variant: we do not allow the # of active SPUs to drop to zero as this can cause a deadlock.
		if ( variant.m_demoType == ClosestPointsMultithreadingApiDemoVariant::MULTITHREADED_ON_SPU ) m_allowZeroActiveSpus = false;

		// register the default addCdPoint() function; you are free to register your own implementation here though
		hkpFixedBufferCdPointCollector::registerDefaultAddCdPointFunction();
	}
}


ClosestPointsMultithreadingApiDemo::~ClosestPointsMultithreadingApiDemo()
{
	delete m_util;
}


void ClosestPointsMultithreadingApiDemo::createBodies()
{
	//
	//	Create master tmplt for some of our special shapes
	//
	hkpRigidBodyCinfo tmplt;
	tmplt.m_linearDamping = 1.0f;
	tmplt.m_angularDamping = 1.0f;

	//
	// Create a box
	//
	{
		hkVector4 pos; pos.set(0,0,0);
		hkVector4 size; size.set(1,1,1);
		m_box = GameUtils::createBox(size, 1, pos);
		m_world->addEntity(m_box);
		m_box->removeReference();
	}

	//
	// Create a sphere
	//
	{
		hkVector4 pos; pos.set(-3,-3,0);
		m_sphereA = GameUtils::createSphere(1.0f, 1.0f, pos);
		m_world->addEntity(m_sphereA);
		m_sphereA->removeReference();
	}

	//
	// Create a sphere
	//
	{
		hkVector4 pos; pos.set(3,-3,0);
		m_sphereB = GameUtils::createSphere(1.0f, 1.0f, pos);
		m_world->addEntity(m_sphereB);
		m_sphereB->removeReference();
	}
}


hkDemo::Result ClosestPointsMultithreadingApiDemo::stepDemo()
{
	if ( !m_multithreadingUtil )
	{
		char* msg = "Demo only works when started with -mt (i.e. with multithreading enabled).";
		m_env->m_textDisplay->outputText(msg, 20, 250, 0xffffffff);
		HK_WARN(0xaf152fe0, msg);
		return hkDemo::DEMO_OK;
	}

	if ( m_env->m_gamePad->isButtonPressed(HKG_PAD_BUTTON_1) )
	{
		return stepDemoWorldGetClosestPoints();
	}
	else
	{
		return stepDemoPairGetClosestPoints();
	}
}


hkDemo::Result ClosestPointsMultithreadingApiDemo::stepDemoPairGetClosestPoints()
{
	const ClosestPointsMultithreadingApiDemoVariant& variant = g_ClosestPointsMultithreadingApiDemoVariants[m_variantId];

	//
	// Setup the output array where the resulting closest point/shortest distance (in this case only one) will be returned.
	//
	hkpRootCdPoint* closestPoint = hkAllocateChunk<hkpRootCdPoint>(1, HK_MEMORY_CLASS_DEMO);

	//
	// Setup the pairGetClosestPointraycast command.
	//
	hkpPairGetClosestPointsCommand* command;
	{
		command = hkAllocateChunk<hkpPairGetClosestPointsCommand>(1, HK_MEMORY_CLASS_DEMO);

		// Init input data.
		{
			// hkpWorldObject::getCollidable() needs a read-lock on the object
			m_box->markForRead();
			m_sphereA->markForRead();

			command->m_collidableA = m_box->getCollidable();
			command->m_collidableB = m_sphereA->getCollidable();

			m_sphereA->unmarkForRead();
			m_box->unmarkForRead();
		}

		// Init output struct.
		{
			command->m_results			= closestPoint;
			command->m_resultsCapacity	= 1;
			command->m_numResultsOut	= 0;
		}
	}

	//
	// create the job header
	//
	hkpCollisionQueryJobHeader* jobHeader;
	{
		jobHeader = hkAllocateChunk<hkpCollisionQueryJobHeader>(1, HK_MEMORY_CLASS_DEMO);
	}

	//
	// setup getClosestPointsJob
	//
	hkReal queryTolerance = 10.0f;
	hkpPairGetClosestPointsJob pairGetClosestPointsJob(jobHeader, command, 1, queryTolerance, &m_semaphore, 5);

	{
		//
		// Put the pairGetClosestPointsJob on the job queue.
		//
		m_util->putJobOntoQueue(&pairGetClosestPointsJob, pairGetClosestPointsJob.getJobType());

		//
		// Start PPU worker threads and/or SPURS threads.
		//
		if		( variant.m_demoType == ClosestPointsMultithreadingApiDemoVariant::MULTITHREADED_ON_PPU		    ) m_util->startPpu(m_multithreadingUtil);
		else if ( variant.m_demoType == ClosestPointsMultithreadingApiDemoVariant::MULTITHREADED_ON_SPU		    ) m_util->startPpuAndSpu(m_multithreadingUtil); // hacky workaround: we only need to start PPU here to reset the timers
		else if ( variant.m_demoType == ClosestPointsMultithreadingApiDemoVariant::MULTITHREADED_ON_PPU_AND_SPU ) m_util->startPpuAndSpu(m_multithreadingUtil);

		//
		// Wait for the one single job we started to finish.
		//
		m_semaphore.acquire();

		//
		// Wait until all SPUSs have finished properly.
		//
		if		( variant.m_demoType == ClosestPointsMultithreadingApiDemoVariant::MULTITHREADED_ON_SPU		    ) m_util->waitForSpuFinished();
		else if ( variant.m_demoType == ClosestPointsMultithreadingApiDemoVariant::MULTITHREADED_ON_PPU_AND_SPU ) m_util->waitForSpuFinished();
	}

	//
	// process return values
	//
	{
		//
		// Draw the shortest distance returned by our own command.
		//
		if ( command->m_numResultsOut > 0 )
		{
			const hkContactPoint& pt = command->m_results[0].m_contact;
			drawContactPoint(pt);
		}
	}

	//
	// Free temporarily allocated memory.
	//
	hkDeallocateChunk(jobHeader,    1, HK_MEMORY_CLASS_DEMO);
	hkDeallocateChunk(command,      1, HK_MEMORY_CLASS_DEMO);
	hkDeallocateChunk(closestPoint, 1, HK_MEMORY_CLASS_DEMO);

	// Do not clear the timers during stepDemo()'s stepWorld() call as this would reset our query timings
	m_clearTimersOnStepWorld = false;

	return hkDefaultPhysicsDemo::stepDemo();
}


hkDemo::Result ClosestPointsMultithreadingApiDemo::stepDemoWorldGetClosestPoints()
{
	const ClosestPointsMultithreadingApiDemoVariant& variant = g_ClosestPointsMultithreadingApiDemoVariants[m_variantId];

	//
	// Setup the output array where the resulting closest points/shortest distances (in this case two) will be returned.
	//
	hkpRootCdPoint* closestPoint = hkAllocateChunk<hkpRootCdPoint>(2, HK_MEMORY_CLASS_DEMO);

	//
	// Setup the hkWorldGetClosestPoints command.
	//
	hkpWorldGetClosestPointsCommand* command;
	{
		command = hkAllocateChunk<hkpWorldGetClosestPointsCommand>(1, HK_MEMORY_CLASS_DEMO);

		// Init input data.
		{
			// hkpWorldObject::getCollidable() needs a read-lock on the object
			m_box->markForRead();
			command->m_collidable = m_box->getCollidable();
			m_box->unmarkForRead();
		}

		// Init output struct.
		{
			command->m_results			= closestPoint;
			command->m_resultsCapacity	= 2;
			command->m_numResultsOut	= 0;
		}
	}

	// Reasonably large subCommandBuffer (for this situation).
	const int subCommandBufferCapacity = 5;
	hkpPairGetClosestPointsCommand* subCommandBuffer = hkAllocateChunk<hkpPairGetClosestPointsCommand>(subCommandBufferCapacity, HK_MEMORY_CLASS_DEMO);

	//
	// create the job header
	//
	hkpCollisionQueryJobHeader* jobHeader;
	{
		jobHeader = hkAllocateChunk<hkpCollisionQueryJobHeader>(1, HK_MEMORY_CLASS_DEMO);
	}

	//
	// setup hkpWorldGetClosestPointsJob
	//
	hkReal queryTolerance = 10.0f;

	hkpWorldGetClosestPointsJob worldGetClosestPointsJob(jobHeader, command, 1, subCommandBuffer, subCommandBufferCapacity, m_world->m_broadPhase, queryTolerance, &m_semaphore);

	//
	// Put the job on the queue, kick-off the PPU/SPU threads and wait for everything to finish.
	//
	{
		m_world->lockReadOnly();

		//
		// Put the worldGetClosestPointsJob job on the job queue.
		//
		m_util->putJobOntoQueue(&worldGetClosestPointsJob, worldGetClosestPointsJob.getJobType());

		//
		// Start PPU worker threads and/or SPURS threads.
		//
		if		( variant.m_demoType == ClosestPointsMultithreadingApiDemoVariant::MULTITHREADED_ON_PPU			) m_util->startPpu(m_multithreadingUtil);
		else if ( variant.m_demoType == ClosestPointsMultithreadingApiDemoVariant::MULTITHREADED_ON_SPU			) m_util->startPpuAndSpu(m_multithreadingUtil); // hacky workaround: we only need to start PPU here to reset the timers
		else if ( variant.m_demoType == ClosestPointsMultithreadingApiDemoVariant::MULTITHREADED_ON_PPU_AND_SPU ) m_util->startPpuAndSpu(m_multithreadingUtil);

		//
		// Wait for the one single job we started to finish.
		//
		m_semaphore.acquire();

		//
		// Wait until all SPUSs have finished properly.
		//
		if		( variant.m_demoType == ClosestPointsMultithreadingApiDemoVariant::MULTITHREADED_ON_SPU			) m_util->waitForSpuFinished();
		else if ( variant.m_demoType == ClosestPointsMultithreadingApiDemoVariant::MULTITHREADED_ON_PPU_AND_SPU ) m_util->waitForSpuFinished();

		m_world->unlockReadOnly();
	}

	//
	// Draw closest distances as returned by the job.
	//
	{
		for (hkUint32 r = 0; r < command->m_numResultsOut; r++)
		{
			const hkContactPoint& pt = command->m_results[r].m_contact;
			drawContactPoint(pt);
		}
	}

	//
	// Free temporarily allocated memory.
	//
	hkDeallocateChunk(jobHeader,    1, HK_MEMORY_CLASS_DEMO);
	hkDeallocateChunk(subCommandBuffer, subCommandBufferCapacity, HK_MEMORY_CLASS_DEMO);
	hkDeallocateChunk(command,      1, HK_MEMORY_CLASS_DEMO);
	hkDeallocateChunk(closestPoint, 1, HK_MEMORY_CLASS_DEMO);

	// Do not clear the timers during stepDemo()'s stepWorld() call as this would reset our query timings
	m_clearTimersOnStepWorld = false;

	return hkDefaultPhysicsDemo::stepDemo();
}


void ClosestPointsMultithreadingApiDemo::drawContactPoint(const hkContactPoint& contactPoint)
{
	// getPosition() returns a point on B by convention
	const hkVector4 pointOnBInWorld   = contactPoint.getPosition();

	// normal goes from B to A by convention
	const hkVector4 normalBtoAInWorld = contactPoint.getNormal();

	// pointOnA = pointOnB + dist * normalBToA
	hkVector4 pointOnAInWorld;
	pointOnAInWorld.setAddMul4(pointOnBInWorld, normalBtoAInWorld, contactPoint.getDistance());

	// Draw the line from A to B and the normal on B
	HK_DISPLAY_LINE(  pointOnAInWorld, pointOnBInWorld, hkColor::GREEN );
}


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


HK_DECLARE_DEMO_VARIANT_USING_STRUCT( ClosestPointsMultithreadingApiDemo, OTHER, ClosestPointsMultithreadingApiDemoVariant, g_ClosestPointsMultithreadingApiDemoVariants, HK_NULL );

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