/* 
 * 
 * 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 <Demos/Physics/UseCase/Destruction/BreakOff/BreakOffExtendedMeshDemo.h>

#include <Physics/Collide/Shape/Compound/Collection/List/hkpListShape.h>
#include <Physics/Collide/Shape/Convex/ConvexTranslate/hkpConvexTranslateShape.h>
#include <Physics/Collide/Shape/Compound/Tree/Mopp/Modifiers/hkpRemoveTerminalsMoppModifier.h>

#include <Graphics/Common/DisplayObject/hkgDisplayObject.h>
#include <Graphics/Common/Geometry/VertexSet/hkgVertexSet.h>
#include <Graphics/Bridge/DisplayHandler/hkgDisplayHandler.h>

#include <Common/Serialize/Util/hkLoader.h>
#include <Physics/Utilities/Serialize/hkpPhysicsData.h>
#include <Common/Serialize/Util/hkRootLevelContainer.h>
#include <Demos/DemoCommon/Utilities/Asset/hkAssetManagementUtil.h>

#include <Physics/Internal/Collide/Mopp/Code/hkpMoppCode.h>
#include <Physics/Collide/Shape/Compound/Tree/Mopp/hkpMoppUtility.h>
#include <Physics/Collide/Shape/Compound/Tree/Mopp/hkpMoppBvTreeShape.h>
#include <Physics/Collide/Shape/Compound/Collection/SimpleMesh/hkpSimpleMeshShape.h>
#include <Physics/Collide/Shape/Compound/Collection/ExtendedMeshShape/hkpExtendedMeshShape.h>
#include <Physics/Dynamics/World/hkpPhysicsSystem.h>

struct BreakOffExtendedMeshVariant
{
	const char*		m_name;
	const char*		m_file;
	const char*		m_details;
};


static const BreakOffExtendedMeshVariant g_variants[] =
{
	{ "Shack",	"Resources/ShowCase/Gdc2007/testShack.hkx",	"BreakOffUtil with shack" },
	{ "Fence",	"Resources/ShowCase/Gdc2007/testFence.hkx",	"BreakOffUtil with fence" },
};


static hkgGeometry* emptyGeom;

static hkpShapeCollection* createExtendedMeshShape( hkLoader* loader,  const char* file, hkArray<hkpShapeKey>& keysToBreak )
{
	hkString assetFile = hkAssetManagementUtil::getFilePath( file );
	hkRootLevelContainer* container = loader->load( assetFile.cString() );

	hkpPhysicsData* physics = reinterpret_cast<hkpPhysicsData*>( container->findObjectByType( hkpPhysicsDataClass.getName() ));
	HK_ASSERT2(0x345654e4, physics, "No physics data in test scene");
	hkpListShape* list = (hkpListShape*)physics->getPhysicsSystems()[0]->getRigidBodies()[0]->getCollidable()->getShape();

	// Convert list into an extended mesh shape
	hkpExtendedMeshShape* extendedMesh = new hkpExtendedMeshShape();

	hkArray<const hkpConvexShape*> convexShapes;

	for (int childIdx=0; childIdx < list->getNumChildShapes(); childIdx++)
	{
		const hkpShape* child = list->getChildShapeInl( childIdx );

		switch (child->getType())
		{
			case HK_SHAPE_CONVEX:
			case HK_SHAPE_CONVEX_TRANSFORM:
			case HK_SHAPE_CONVEX_TRANSLATE:
				{
					const hkpConvexShape* convexChild =  (const hkpConvexShape*) child;
					convexShapes.pushBack( convexChild );
				}
				break;

			case HK_SHAPE_MOPP:
				{
					hkpMoppBvTreeShape* moppShape = (hkpMoppBvTreeShape*)child;
					const hkpShapeCollection* shapeCollection = moppShape->getShapeCollection();

					if (shapeCollection->getType() == HK_SHAPE_TRIANGLE_COLLECTION )
					{
						// Summe a simple mesh shape - the tools always produce this
						hkpSimpleMeshShape* meshShape = (hkpSimpleMeshShape*)moppShape->getShapeCollection();

						//Put triangles in a single subpart
						hkpExtendedMeshShape::TrianglesSubpart triSubpart;
						triSubpart.m_numTriangleShapes = meshShape->m_triangles.getSize();
						triSubpart.m_vertexBase = (float*)meshShape->m_vertices.begin();
						triSubpart.m_vertexStriding = sizeof(hkVector4);
						
						triSubpart.m_numVertices = meshShape->m_vertices.getSize();
						triSubpart.m_indexBase = meshShape->m_triangles.begin();
						triSubpart.m_stridingType = hkpExtendedMeshShape::INDICES_INT32;
						triSubpart.m_indexStriding = sizeof(hkpSimpleMeshShape::Triangle);
						extendedMesh->addTrianglesSubpart( triSubpart );
					}
				}
				break;

			default:
				break;
		}
	}

	hkpExtendedMeshShape::ShapesSubpart shapes( convexShapes.begin(), convexShapes.getSize(), hkTransform::getIdentity() );

	extendedMesh->addShapesSubpart( shapes );
	for (int s=0; s < convexShapes.getSize(); s++)
	{
		hkpShapeKey key = 0x80000000 | s;
		keysToBreak.pushBack( key );
	}
	return extendedMesh;
}

BreakOffExtendedMeshDemo::BreakOffExtendedMeshDemo(hkDemoEnvironment* env)
:	hkDefaultPhysicsDemo(env)
{
	const BreakOffExtendedMeshVariant& variant = g_variants[ m_variantId ];
	//
	// Setup the camera
	//
	{
		hkVector4 from(0.0f, -10.0f, 10.0f);
		hkVector4 to  (0.0f, 0.0f,  0.0f);
		hkVector4 up  (0.0f, 0.0f,  1.0f);
		setupDefaultCameras( env, from, to, up );
	}

	m_loader = new hkLoader();

	//
	// Create the world
	//
	{
		hkpWorldCinfo info;

		info.m_gravity.set(0.0f, 0.0f, -10);


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

		hkpAgentRegisterUtil::registerAllAgents(m_world->getCollisionDispatcher());
		setupGraphics();
	}

	//
	//	Create the break off utility
	//
	{
		m_breakUtil = new hkpBreakOffPartsUtil( m_world, this );
	}

	//
	// Create a mopp with lots of instances of 
	//
	{
		hkArray<hkpShapeKey> keysToBreak;
		hkpShapeCollection* terrainShape = createExtendedMeshShape( m_loader, variant.m_file, keysToBreak );


		hkpMoppCompilerInput input;
		input.m_enableChunkSubdivision = true;

		hkpMoppCode* moppCode = hkpMoppUtility::buildCode(terrainShape, input);
		hkpMoppBvTreeShape* shape = new hkpMoppBvTreeShape(terrainShape,  moppCode);
		terrainShape->removeReference();
		moppCode->removeReference();

		hkpRigidBodyCinfo ci;
		ci.m_shape = shape;
		ci.m_motionType = hkpMotion::MOTION_FIXED;
		ci.m_numUserDatasInContactPointProperties = 1;

		hkpEntity* ground = new hkpRigidBody( ci );
		ground->addProperty( HK_PROPERTY_DEBUG_DISPLAY_COLOR, int(0xffb0b0b0) );
		m_world->addEntity( ground );

		for (int i=0; i < keysToBreak.getSize(); i++)
		{
			m_breakUtil->markPieceBreakable( ground, keysToBreak[i], 250.0f );
		}

		ground->removeReference();
		shape->removeReference();
	}

	//
	//	Create our moving box
	//
	hkpRigidBody* car;
	{
		hkpBoxShape* shape  = new hkpBoxShape( hkVector4( .5f, .5f, .5f), 0 );


		hkpRigidBodyCinfo ci;
		{
			ci.m_shape = shape;
			ci.m_qualityType = HK_COLLIDABLE_QUALITY_CRITICAL;
			ci.m_motionType = hkpMotion::MOTION_BOX_INERTIA;
			ci.m_restitution = 1.0f;
			ci.m_friction = 0.0f;
			ci.m_position.set( 12.0f, 10.f, 1.0f );
			ci.m_linearVelocity.set( -10.0f, -10.0f, 2.0f );
			hkpInertiaTensorComputer::setShapeVolumeMassProperties( shape, 1000.0f, ci );
		}

		car = new hkpRigidBody(ci);
		m_world->addEntity( car );
		car->removeReference();		// Remove reference, since we no longer want to remember this
		shape->removeReference();
	}

	m_world->unlock();

	emptyGeom = hkgGeometry::create();
}


void BreakOffExtendedMeshDemo::removeSubShapeFromDisplay(hkpRigidBody*  body, hkpMoppBvTreeShape* bvTreeShape, int shapeKey)
{
	// remap the key if we have an extended mesh shape
	hkpExtendedMeshShape* extendedMesh = (hkpExtendedMeshShape*)bvTreeShape->getShapeCollection();

	if ( extendedMesh->getSubpartType(shapeKey) == hkpExtendedMeshShape::SUBPART_TRIANGLES )
		return;

	shapeKey = extendedMesh->getTerminalIndexInSubPart( shapeKey );
	
	hkpShapeKey displayKey = shapeKey + (extendedMesh->getNumTrianglesSubparts() ? 1 : 0);	// All triangles stored in the same geometry - the first one

	{
		int geometryIndex = displayKey;
		const hkpCollidable* collidable = body->getCollidable();
		hkgDisplayObject* displayObject = m_env->m_displayHandler->findDisplayObject(hkUlong(collidable));
		displayObject->setGeometry( geometryIndex, emptyGeom );
	}

}

void BreakOffExtendedMeshDemo::removeSubShapeFromDisplay(hkpRigidBody* body, hkpListShape* listShape, int shapeKey)
{
		// get the vertex set
	hkgVertexSet* vertexSet;
	{
		int geometryIndex = shapeKey;
		const hkpCollidable* collidable = body->getCollidable();
		hkgDisplayObject* displayObject = m_env->m_displayHandler->findDisplayObject(hkUlong(collidable));
		hkgGeometry* geometry = displayObject->getGeometry(geometryIndex);
		hkgFaceSet* faceSet = geometry->getMaterialFaceSet(0)->getFaceSet(0);
		vertexSet = faceSet->getVertices();
	}

	// Simply zero-out all vertices. This will degenerate the triangles but that should be ok.
	{
		m_env->m_window->getContext()->lock();
		vertexSet->lock();
		hkVector4 newPos(0,0,0);
		for (int i = 0; i < vertexSet->getNumVerts(); i++)
		{
			vertexSet->setVertexComponentData(HKG_VERTEX_COMPONENT_POS, i, &newPos(0));
		}
		vertexSet->unlock();
		m_env->m_window->getContext()->unlock();
	}
}

hkResult BreakOffExtendedMeshDemo::breakOffSubPart( const ContactImpulseLimitBreachedEvent& event, hkArray<hkpShapeKey>& keysBrokenOffOut, hkpPhysicsSystem& systemOut ) 
{
		//	
		//	Remove the broken pieces from the car or landscape
		//
	hkpRigidBody* breakingBody = event.m_breakingBody;
	hkpShape* shape = const_cast<hkpShape*>(breakingBody->getCollidable()->getShape());

	for (int p = 0; p < event.m_points.getSize(); p++)
	{
		const ContactImpulseLimitBreachedEvent::PointInfo& pointInfo = event.m_points[p];

		hkpShapeKey brokenPieceKey = pointInfo.m_brokenShapeKey;
		const hkpShape* newShape = HK_NULL;
		{
			hkpShapeContainer::ShapeBuffer buffer;
			switch (shape->m_type )
			{
			case HK_SHAPE_LIST:
				{
					hkpListShape* list = static_cast<hkpListShape*>(shape);
					newShape = list->m_childInfo[brokenPieceKey].m_shape;

					hkpBreakOffPartsUtil::removeKeysFromListShape( breakingBody, &brokenPieceKey, 1 );

					removeSubShapeFromDisplay( event.m_breakingBody, list, brokenPieceKey );
					keysBrokenOffOut.pushBack( brokenPieceKey );

					break;
				}
			case HK_SHAPE_MOPP:
				{
					hkpMoppBvTreeShape* moppShape = static_cast<hkpMoppBvTreeShape*>( shape );
					newShape = moppShape->getShapeCollection()->getChildShape( brokenPieceKey, buffer );
					HK_ASSERT( 0xf0213445, (void*)newShape != (void*)buffer );

					hkpBreakOffPartsUtil::removeKeysFromListShape( breakingBody, &brokenPieceKey, 1 );

					removeSubShapeFromDisplay( event.m_breakingBody, moppShape, brokenPieceKey );
					keysBrokenOffOut.pushBack( brokenPieceKey );
					break;
				}
			default:
				HK_ASSERT2( 0xf03df569, 0, "This shape is not implemented yet" );
				return HK_FAILURE;
			}
		}

			//
			//	Create the new broken off piece
			//
		hkpRigidBodyCinfo rigidBodyCinfo;
		{
			rigidBodyCinfo.m_shape = newShape;
			rigidBodyCinfo.m_position        = breakingBody->getPosition();
			rigidBodyCinfo.m_rotation        = breakingBody->getRotation();
			rigidBodyCinfo.m_linearVelocity  = breakingBody->getLinearVelocity();
			rigidBodyCinfo.m_angularVelocity = breakingBody->getAngularVelocity();	
			rigidBodyCinfo.m_mass = 100.0f;
			rigidBodyCinfo.m_qualityType = HK_COLLIDABLE_QUALITY_DEBRIS;
			hkpInertiaTensorComputer::setShapeVolumeMassProperties( newShape, rigidBodyCinfo.m_mass, rigidBodyCinfo );
			hkpInertiaTensorComputer::clipInertia( 20.0f, rigidBodyCinfo );
		}

		hkpRigidBody* newBody = new hkpRigidBody( rigidBodyCinfo );
		newBody->addProperty( HK_PROPERTY_DEBUG_DISPLAY_COLOR, int(0xffb0b0b0) );
		systemOut.addRigidBody( newBody );
		newBody->removeReference();

		// if the original unbroken body was fixed, the colliding impulse is lost, simply apply the impulse to the new piece
 		if ( breakingBody->isFixedOrKeyframed() )
 		{
 			hkReal maxImpulse = pointInfo.m_properties->m_maxImpulse;
 			hkVector4 impulse; impulse.setMul4( -maxImpulse * 0.2f, pointInfo.m_contactPoint->getNormal() );
 			newBody->applyPointImpulse( impulse, pointInfo.m_contactPoint->getPosition() );
 		}		
	}

	return HK_SUCCESS;
}


hkDemo::Result BreakOffExtendedMeshDemo::stepDemo()
{
	return hkDefaultPhysicsDemo::stepDemo();
}

BreakOffExtendedMeshDemo::~BreakOffExtendedMeshDemo()
{
	m_world->lock();

	m_breakUtil->removeReference();

	m_world->unlock();

	if(m_world)
	{
		m_world->lock();
		m_world->removeReference();
		m_world = HK_NULL;
	}

	emptyGeom->removeReference();

	delete m_loader;
}


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

static const char helpString[] = \
"A demo which shows how to brake of single parts from a compound body, e.g. a mirror from a car and a street lamppost";

HK_DECLARE_DEMO_VARIANT_USING_STRUCT( BreakOffExtendedMeshDemo, PHYSICS, BreakOffExtendedMeshVariant, 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
*
*/
