/* 
 * 
 * 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 <Graphics/Bridge/DisplayHandler/hkgDisplayHandler.h>

#include <Physics/Utilities/VisualDebugger/Viewer/hkpShapeDisplayBuilder.h>

#include <Common/Visualize/hkDebugDisplay.h>
#include <Common/Visualize/Shape/hkDisplayGeometry.h>

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

#include <Demos/Physics/Api/Collide/AsynchronousQueries/PairLinearCastMultithreading/PairLinearCastMultithreadingApiDemo.h>


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


struct PairLinearCastMultithreadingApiDemoVariant
{
	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 demo demonstrates how to perform a linear cast in a multithreaded "							\
	"environment through the use of a dedicated hkpPairLinearCastJob. By putting such a job on "			\
	"the job queue it can be processed by other threads or SPUs (on PS3). "								\
	"One command of this job will cast exactly one collidable against another collidable along a "		\
	"predefined path. You can place an arbitrary number of commands into one job, as the job will "		\
	"automatically split itself into parallel sub-jobs. ";


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


PairLinearCastMultithreadingApiDemo::PairLinearCastMultithreadingApiDemo(hkDemoEnvironment* env)
	: hkDefaultPhysicsDemo(env, DEMO_FLAGS_NO_SERIALIZE),
	m_semaphore(0,1000),
	m_time(0.0f)
{
	const PairLinearCastMultithreadingApiDemoVariant& variant = g_PairLinearCastMultithreadingApiDemoVariants[m_variantId];

	//
	// Setup the camera.
	//
	{
		hkVector4 from(0.0f, 45.0f, 10.0f);
		hkVector4 to(0.0f, 0.0f, 5.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);
			info.setBroadPhaseWorldSize( 1000.0f );
			info.m_gravity.setZero4();
		}

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

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

		setupGraphics();
	}

	//
	// Create a simple sphere.
	//
	{
		hkVector4 position(0.0f, 0.0f, 20.0f);
		m_sphere = GameUtils::createSphere(1.5f, 0, position);
		m_world->addEntity(m_sphere);
		m_sphere->removeReference();
	}

	//
	// Create a simple box.
	//
	{
		hkVector4 position(0.0f, 0.0f, 5.0f);
		hkVector4 size(3.0f, 3.0f, 3.0f);
		m_box = GameUtils::createBox(size, 0, position);

		hkVector4 axis(0,1,0);
		axis.normalize3();
		hkQuaternion rotation(axis, HK_REAL_PI * 0.25f );
		m_box->setRotation(rotation);

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

	//
	// Some magic to create a second graphical representation of the sphere.
	//
	{
		hkArray<hkDisplayGeometry*> geom;
		hkpShapeDisplayBuilder::hkpShapeDisplayBuilderEnvironment sdbe;
		hkpShapeDisplayBuilder builder(sdbe);
		builder.buildShapeDisplay( m_sphere->getCollidable()->getShape(), hkTransform::getIdentity(), geom );
		m_env->m_displayHandler->addGeometry(geom, m_sphere->getCollidable()->getTransform(), 1 , 0, 0 );
		delete geom[0];
	}

	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 == PairLinearCastMultithreadingApiDemoVariant::MULTITHREADED_ON_PPU		 ) m_util->initPpu(input);
		else if ( variant.m_demoType == PairLinearCastMultithreadingApiDemoVariant::MULTITHREADED_ON_SPU		 ) m_util->initSpu(input);
		else if ( variant.m_demoType == PairLinearCastMultithreadingApiDemoVariant::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 == PairLinearCastMultithreadingApiDemoVariant::MULTITHREADED_ON_SPU ) m_allowZeroActiveSpus = false;

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


PairLinearCastMultithreadingApiDemo::~PairLinearCastMultithreadingApiDemo()
{
	delete m_util;

	m_env->m_displayHandler->removeGeometry(1, 0, 0);
}


hkDemo::Result PairLinearCastMultithreadingApiDemo::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(0xaf152fe1, msg);
		return hkDemo::DEMO_OK;
	}

	const PairLinearCastMultithreadingApiDemoVariant& variant = g_PairLinearCastMultithreadingApiDemoVariants[m_variantId];

	//
	// Setup the output array where the resulting collision points will be returned.
	//
	hkpRootCdPoint* collisionPoints = hkAllocateChunk<hkpRootCdPoint>(1, HK_MEMORY_CLASS_DEMO);

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

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

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

			m_box->unmarkForRead();
			m_sphere->unmarkForRead();

			// We perform a linear cast of the first object moving towards the second.
			command->m_from = m_sphere->getPosition();
			command->m_to   = m_box->getPosition();

			//
			// Move around target position.
			//
			hkReal xShift;
			//hkReal yShift;
			{
				m_time += m_timestep;
				xShift = 5.5f * hkMath::sin(m_time * 1.0f);
				//yShift = 5.5f * hkMath::cos(m_time * 1.0f);
			}
			command->m_to(0) += xShift;
			//command->m_to(1) += yShift;
			command->m_to(2) -= 5.0f;
		}

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

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

	//
	// Setup hkpPairLinearCastJob.
	//
	hkReal queryTolerance = 100.0f;
	hkpPairLinearCastJob pairLinearCastJob(jobHeader, command, 1, m_world->m_collisionInput->m_filter, queryTolerance, &m_semaphore);

	//
	// Put the job on the queue, kick-off the PPU/SPU threads and wait for everything to finish.
	//
	{
		//
		// Put the pairGetClosestPointsJob on the job queue.
		//
		m_util->putJobOntoQueue(&pairLinearCastJob, pairLinearCastJob.getJobType());

		//
		// Start PPU worker threads and/or SPURS threads.
		//
		if		( variant.m_demoType == PairLinearCastMultithreadingApiDemoVariant::MULTITHREADED_ON_PPU		 ) m_util->startPpu(m_multithreadingUtil);
		else if ( variant.m_demoType == PairLinearCastMultithreadingApiDemoVariant::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 == PairLinearCastMultithreadingApiDemoVariant::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 == PairLinearCastMultithreadingApiDemoVariant::MULTITHREADED_ON_SPU		 ) m_util->waitForSpuFinished();
		else if ( variant.m_demoType == PairLinearCastMultithreadingApiDemoVariant::MULTITHREADED_ON_PPU_AND_SPU ) m_util->waitForSpuFinished();
	}

	//
	// Display results.
	//
	{
		hkVector4 from	= command->m_from;
		hkVector4 to	= command->m_to;
		hkVector4 path;
		path.setSub4(to, from);

		if ( command->m_numResultsOut > 0 )
		{
			for ( int r = 0; r < command->m_numResultsOut; r++)
			{
				const hkContactPoint& contactPoint = command->m_results[r].m_contact;

				hkReal fraction = contactPoint.getDistance();

				// Calculate the position on the linear cast's path where the first collidable hit the second collidable.
				hkVector4 pointOnPath;
				{
					pointOnPath.setAddMul4( from, path, fraction );
				}

				// Draw the linear cast line from startpoint to hitpoint.
				HK_DISPLAY_LINE( from, pointOnPath, hkColor::RED );

				// Draw the collision normal.
				HK_DISPLAY_ARROW( pointOnPath, contactPoint.getNormal(), hkColor::BLUE );

				// Draw the linear cast line from hitpoint to endpoint.
				HK_DISPLAY_LINE( pointOnPath, to, hkColor::BLACK );

				//
				// Display sphere's position at 'time of collision'.
				//
				{
					hkTransform castTransform = command->m_collidableA->getTransform();
					castTransform.setTranslation(pointOnPath);
					m_env->m_displayHandler->updateGeometry(castTransform, 1, 0);
				}
			}
		}
		else
		{
			// Display the whole linear cast line.
			HK_DISPLAY_LINE( from, to, hkColor::BLACK );

			// Display the casted collidable at cast's endpoint.
			{
				hkTransform castTransform = command->m_collidableA->getTransform();
				m_env->m_displayHandler->updateGeometry(castTransform, 1, 0);
			}
		}
	}

	//
	// Free temporarily allocated memory.
	//
	hkDeallocateChunk(jobHeader,       1, HK_MEMORY_CLASS_DEMO);
	hkDeallocateChunk(command,         1, HK_MEMORY_CLASS_DEMO);
	hkDeallocateChunk(collisionPoints, 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();
}


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


HK_DECLARE_DEMO_VARIANT_USING_STRUCT( PairLinearCastMultithreadingApiDemo, OTHER, PairLinearCastMultithreadingApiDemoVariant, g_PairLinearCastMultithreadingApiDemoVariants, 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
*
*/
