//////////////////////////////////////////////////////////////////////////
// A lot of this code has been copied and adapted from Jimon's Framework
// His Lib is a great place to start your own framework as it has a lot
// of useful features.
//////////////////////////////////////////////////////////////////////////

#include <irrlicht.h>
#include <NxPhysics.h>
#include <NxCooking.h>

#pragma comment(lib, "Irrlicht.lib")
#pragma comment(lib, "PhysXLoader.lib")
#pragma comment(lib, "NxCooking.lib")

using namespace irr;

NxScene* pscene;
IrrlichtDevice* device;
NxPhysicsSDK* gps;


// This is a class to "combine" Irrlicht's Nodes with PhysX actors
class PhysXNode:public irr::IReferenceCounted
{
protected:

	NxActor * Actor; //Pointer to the actor
	irr::scene::ISceneNode * Node; //Pointer to the node
	bool updaterot; //Whether to update rotation of the node 
	//(you might want this one at 0 if you are using a custom character controller)
	bool tokill;  //Set this one to True when you want to get rid of this actor.

public:

	PhysXNode(NxActor * actor,irr::scene::ISceneNode * node,bool rotu)
		:Actor(actor),Node(node),updaterot(rotu),tokill(0)
	{

	}
	~PhysXNode()
	{
	}

	void SetActor(NxActor * new_actor){
		Actor = new_actor;
	}
	NxActor * GetActor(){
		return Actor;
	}

	void SetNode(irr::scene::ISceneNode * new_node){
		Node = new_node;
	}
	irr::scene::ISceneNode * GetNode(){
		return Node;
	}
	void SetToKill(bool killit){
		tokill=killit;
	}

	//This is pretty much a function you won't have to touch. Ever.
	void Update(){

		if (!Node || !Actor)
			return;

		NxMat34 pose = Actor->getGlobalPose();

		const NxVec3 pos = pose.t;
		const NxMat33 orient = pose.M;
		irr::core::matrix4 irrMat;
		orient.getColumnMajorStride4(&irrMat[0]);
		pos.get(&irrMat[12]);

		irrMat[3] = irrMat[7] = irrMat[11] = 0.0f;
		irrMat[15] = 1.0f;

		Node->setPosition(irrMat.getTranslation());
		if(updaterot)
			Node->setRotation(irrMat.getRotationDegrees());

		if(tokill)
		{
			NxScene* pscene=&Actor->getScene();
			pscene->releaseActor(*Actor);
			Actor=0;
		}
	}
};


//Array of the class above. All dynamic objects will be "dumped" in here.
irr::core::array<PhysXNode*> Nodes;


//////////////////////////////////////////////////////////////////////////

// These functions are a one time workout. Make them right once,
// copy-paste them in all your projects with minimum editing.

//////////////////////////////////////////////////////////////////////////

NxReal UpdateTime () {
	NxReal delta;
	static unsigned int cur_time, last_time;
	cur_time = device->getTimer ()->getRealTime ();

	delta = (NxReal)((float)(cur_time - last_time) / 1000.0);
	last_time = cur_time;
	return delta;
}

void StartPhysics () {
	NxReal deltaTime = UpdateTime();
	pscene->simulate(deltaTime);
	pscene->flushStream();
	while (!pscene->fetchResults(NX_RIGID_BODY_FINISHED, false));
	//Update the array of actor-nodes above.
	for (irr::u32 i=0;i<Nodes.size();i++)
	{
		Nodes[i]->Update();
	}
}


void InitNx (IrrlichtDevice* dev) {
	gps = NxCreatePhysicsSDK (NX_PHYSICS_SDK_VERSION);
	if (gps == 0)
		return;

	//Examples for setting parameters. More in Ageia Demos.
	//gps->setParameter (NX_SKIN_WIDTH, 0.01f);
	//gps->setParameter(NX_VISUALIZATION_SCALE, 1);
	//gps->setParameter(NX_VISUALIZE_COLLISION_SHAPES, 1);
	//gps->setParameter(NX_VISUALIZE_ACTOR_AXES, 1);

	NxVec3 def_grav(0,-100.0f,0);

	NxSceneDesc scd;
	scd.gravity = def_grav;  //This can be the default gravity. Set as appropriate for your project. 0 is a valid value.
	//scd.userContactReport     = &gContactReport;  //This can be a contactreport. Check Ageia Demos
	//scd.simType				= NX_SIMULATION_SW; // SW or HW. Software or Hardware
	pscene = gps->createScene (scd);

	//Default material for actors. You can change an actor's material if you want to after creating one.
	NxMaterial *defmat = pscene->getMaterialFromIndex (0);
	defmat->setRestitution (0.10f);
	defmat->setStaticFriction (0.2f);
	defmat->setDynamicFriction (0.4f);
	//Play around with these to see how they work.

	NxInitCooking(); //Allow Cooking

	UpdateTime ();

	if (pscene)
		StartPhysics ();

}

void ExitNx () {
	NxCloseCooking(); //Close Mesh Cooking
	if (gps != 0) {
		if (pscene != 0)
			gps->releaseScene (*pscene);
		gps->release ();
	}
};

//////////////////////////////////////////////////////////////////////////

// Those are pretty much the functions and classes you need to have a working
// interoperability between Irrlicht and PhysX when it comes
// to updating.
// Let the fun start!

//////////////////////////////////////////////////////////////////////////

//This function will create a basic ground plane. A floor :p
void CreateGroundPlane () {
	NxPlaneShapeDesc psd;
	NxActorDesc ad;
	ad.shapes.pushBack (&psd);
	pscene->createActor(ad);
	irr::scene::IAnimatedMeshSceneNode* ground;
	ground=device->getSceneManager()->addAnimatedMeshSceneNode(device->getSceneManager()->addHillPlaneMesh("floor",irr::core::dimension2df(50,50),irr::core::dimension2d<u32>(32,32)));
	ground->getMaterial(0).setFlag(video::EMF_LIGHTING,false);
	ground->getMaterial(0).setTexture(0,device->getVideoDriver()->getTexture("ground.jpg"));
}

//This function will create a box.
void AddABoxMesh(NxVec3 position)
{
	scene::ISceneNode* cube = device->getSceneManager()->addCubeSceneNode(40);
	cube->getMaterial(0).setTexture(0,device->getVideoDriver()->getTexture("box.jpg"));
	cube->getMaterial(0).setFlag(video::EMF_LIGHTING,false);

	//How the actor "works"
	NxBodyDesc BodyDesc;
	BodyDesc.angularDamping	= 1.5f; //Will make the body go all gravity on us

	//How the actor looks like. Equivalent to the mesh.
	NxBoxShapeDesc BoxDesc;
	BoxDesc.dimensions		= NxVec3(20.0f,20.0f,20.0f);

	//General info about the actor.
	NxActorDesc CubeActorDesc;
	CubeActorDesc.shapes.pushBack(&BoxDesc); //We tell PhysX how he looks first.
	CubeActorDesc.body			= &BodyDesc; //Then how he should "work"
	CubeActorDesc.density		= 99.0f;     //We give him a density, so he will have a mass.
	CubeActorDesc.globalPose.t  = position;  //And we set him in space somewhere.

	NxActor * bull;
	bull=pscene->createActor(CubeActorDesc); //Now we create him!
	Nodes.push_back(new PhysXNode(bull,cube,1)); // We add a PhysXNode to the list of nodes to get updated with physics
	// the "1" parameter means we also want it to get rotated by physx, not only moved. Bad for bullets, a must for most other objects.
}

int main()
{
	//We start the irrlicht device.
	SIrrlichtCreationParameters stats;
	stats.WindowSize=irr::core::dimension2d<s32>(800,600);
	stats.Bits=32;
	stats.DriverType=video::EDT_DIRECT3D9;

	device=createDeviceEx(stats);
	device->setWindowCaption(L"Quick'n'easy PhysX + Irrlicht Demo");

	//Add a camera.
	device->getSceneManager()->addCameraSceneNodeFPS()->setPosition(irr::core::vector3df(0,100,-500));

	//Add a light
	device->getSceneManager()->addLightSceneNode(0,core::vector3df(0,300,0));

	//Start the physics
	InitNx(device);

	//Create our floor
	CreateGroundPlane();

	//We go wild on boxes
	for(int i=0;i<50;i++)
		AddABoxMesh(NxVec3(0,90+50*i,0));

	for(int i=0;i<50;i++)
		AddABoxMesh(NxVec3(70,90+50*i,0));

	for(int i=0;i<50;i++)
		AddABoxMesh(NxVec3(70,90+50*i,70));

	for(int i=0;i<50;i++)
		AddABoxMesh(NxVec3(0,90+50*i,70));

	for(int i=0;i<50;i++)
		AddABoxMesh(NxVec3(140,90+50*i,70));

	for(int i=0;i<50;i++)
		AddABoxMesh(NxVec3(140,90+50*i,140));

	//The main loop.
	while(device->run())
	{
		device->getVideoDriver()->beginScene(1,1,irr::video::SColor(255,100,100,140));

		//The only thing to add in the loop to make physics work.
		if (pscene) {
			StartPhysics ();
		}
		
		device->getSceneManager()->drawAll();

		irr::core::stringw caption;
		caption="Quick'n'easy PhysX + Irrlicht Demo - FPS: ";
		caption+=device->getVideoDriver()->getFPS();
		device->setWindowCaption(caption.c_str());

		device->getVideoDriver()->endScene();
	}

	ExitNx();
	device->drop();

	return 0;
}

//VOILA!!!!

//Lovely, eh?