/*
	OgreNewt library - connecting Ogre and Newton!

	Demo04_Raycasting - how raycasting and convexcasting works in Newton, implemented with a simple way to drag objects around.
*/
#include "OgreNewtonApplication.h"
#include "OgreNewtonFrameListener.h"

#include <OgreNewt.h>
#include <OgreNewt_BasicFrameListener.h>


OgreNewtonApplication::OgreNewtonApplication(void)
{
	// create OgreNewt world.
	m_World = new OgreNewt::World();

	mEntityCount = 0;
}

OgreNewtonApplication::~OgreNewtonApplication(void)
{
	// destroy world.
	delete m_World;
}



void OgreNewtonApplication::createFrameListener()
{
	mNewtonListener = new OgreNewt::BasicFrameListener( mWindow, m_World, 100 );
	mRoot->addFrameListener(mNewtonListener);

	mFrameListener = new OgreNewtonFrameListener( mWindow, mCamera, mSceneMgr, m_World);
	mRoot->addFrameListener(mFrameListener);
}

void OgreNewtonApplication::destroyScene()
{
}

OgreNewt::Body* OgreNewtonApplication::makeSimpleBox( Ogre::Vector3 size, Ogre::Vector3 pos, Ogre::Quaternion orient, const char* materialName )
{
	Entity* box1;
	SceneNode* box1node;

	box1 = mSceneMgr->createEntity( "Entity"+Ogre::StringConverter::toString(mEntityCount++), "box.mesh" );
	box1node = mSceneMgr->getRootSceneNode()->createChildSceneNode();
	box1node->attachObject( box1 );
	box1node->setScale( size );

	OgreNewt::ConvexCollisionPtr col (new OgreNewt::CollisionPrimitives::Box( m_World, size, 0 ));
	OgreNewt::Body* bod = new OgreNewt::Body( m_World, col );

	bod->setPositionOrientation( pos, orient );

	// base mass on the size of the object.
	Ogre::Real mass = size.x * size.y * size.z * 2.5;
	// calculate the inertia based on box formula and mass
	Ogre::Vector3 inertia, offset;
	col->calculateInertialMatrix(inertia, offset);

#ifdef OGRENEWT_NO_COLLISION_SHAREDPTR
	delete col;
#endif

	bod->attachNode( box1node );
	bod->setMassMatrix( mass, mass*inertia );
	bod->setCenterOfMass(offset);
	bod->setStandardForceCallback();

	box1->setMaterialName( materialName );

	return bod;
}


void OgreNewtonApplication::buildPyramid (Ogre::Vector3 blockBoxSize, Ogre::Vector3 pos, Ogre::Quaternion orient, int heightCount)
{
	// find the initial location on the floor 
	Ogre::Vector3 start(pos.x, 1000.0f, pos.z);
	Ogre::Vector3 end(pos.x, -1000.0f, pos.z);
	OgreNewt::BasicRaycast castRay (m_World, start, end, true);
	OgreNewt::BasicRaycast::BasicRaycastInfo info = castRay.getFirstHit();
	pos.y = start.y + (end.y - start.y) * info.mDistance;

	Real step = blockBoxSize.x + 0.01f;
	//Real x0 = pos.x - step * heightCount / 2.0f; 

	Ogre::Matrix3 mat;
	orient.ToRotationMatrix (mat);
	Ogre::Vector3 dir (mat.GetColumn (0));
	Ogre::Vector3 x0 = pos - dir * (step * heightCount / 2.0f); 

	OgreNewt::ConvexCollisionPtr castingShape (new OgreNewt::CollisionPrimitives::Box( m_World, blockBoxSize, 0 ));
	for (int i = 0; i < heightCount; i ++) {
		pos = x0;
		for (int j = 0; j < heightCount - i; j ++) {
			OgreNewt::Body* box;

			// find the placement position on the ground for the box by using a convex cast
			Ogre::Vector3 start(pos);
			Ogre::Vector3 end (end);
			Ogre::Quaternion rotation;
			start.y += 2;
			end.y -= 2;
			OgreNewt::BasicConvexcast placementCast (m_World, castingShape, start, orient, end, 1, 0);
			start.y = start.y + (end.y - start.y) * placementCast.getDistanceToFirstHit();

			// place a box at this position
			box = makeSimpleBox (blockBoxSize, start, orient, "Simple/NewtonLogo");

			// move to the next place
			pos += dir * step;
		}
		x0 += dir * (step * 0.5f);
		x0.y += blockBoxSize.y;
	}
}


void OgreNewtonApplication::buildJenga (Ogre::Vector3 pos, int heightCount)
{

//	set the Jenga rectangle using the golden ratio for base
	Ogre::Vector3 blockBoxSize (0.2f, 0.125f, 0.6f);

	// find the base position
	Ogre::Vector3 start(pos.x, 1000.0f, pos.z);
	Ogre::Vector3 end(pos.x, -1000.0f, pos.z);
	OgreNewt::BasicRaycast castRay (m_World, start, end, true);
	OgreNewt::BasicRaycast::BasicRaycastInfo info = castRay.getFirstHit();
	pos.y = start.y + (end.y - start.y) * info.mDistance;

	// separate a bit the block alone the horizontal direction
	Real spacing = blockBoxSize.x + 0.01f;
	OgreNewt::ConvexCollisionPtr castingShape (new OgreNewt::CollisionPrimitives::Box( m_World, blockBoxSize, 0 ));
	for (int i = 0; i < heightCount; i ++) { 
		
		Ogre::Quaternion orient (Ogre::Radian (0.5f * 3.1416f * i), Ogre::Vector3 (0.0f, 1.0f, 0.0f));
	
		Ogre::Matrix3 mat;
		orient.ToRotationMatrix (mat);
		Ogre::Vector3 dir (mat.GetColumn (0));
		Ogre::Vector3 location = pos - dir * spacing; 

		for (int j = 0; j < 3; j ++) { 
			OgreNewt::Body* box;

			Ogre::Vector3 start(location);
			Ogre::Vector3 end (location);
			Ogre::Quaternion rotation;
			start.y += 2;
			end.y -= 2;
			OgreNewt::BasicConvexcast placementCast (m_World, castingShape, start, orient, end, 1, 0);
			start.y = start.y + (end.y - start.y) * placementCast.getDistanceToFirstHit();

			box = makeSimpleBox (blockBoxSize, start, orient, "Simple/Wood");
			location += dir * spacing;
		}

		pos.y += blockBoxSize.y;
	}

}




void OgreNewtonApplication::createScene()
{
	// sky box.
	mSceneMgr->setSkyBox(true, "Examples/CloudyNoonSkyBox", 500.0f);

	
	// shadows on!
	mSceneMgr->setShadowTechnique( Ogre::SHADOWTYPE_STENCIL_ADDITIVE );


	// floor object!
	Entity* floor;
	SceneNode* floornode;

	floor = mSceneMgr->createEntity("Floor", "playground.mesh" );
	floornode = mSceneMgr->getRootSceneNode()->createChildSceneNode( "FloorNode" );
	floornode->attachObject( floor );
	floor->setCastShadows( false );
	OgreNewt::CollisionPtr col = OgreNewt::CollisionPtr(new OgreNewt::CollisionPrimitives::TreeCollision( m_World, floor, true, 0 ));
	OgreNewt::Body* bod = new OgreNewt::Body( m_World, col );
#ifdef OGRENEWT_NO_COLLISION_SHAREDPTR
	delete col;
#endif
	bod->attachNode( floornode );
	bod->setPositionOrientation( Ogre::Vector3(0.0,-10.0,0.0), Ogre::Quaternion::IDENTITY );

	// place a Pyramid
	Ogre::Vector3 updir (0.0f, 1.0f, 0.0f);
	Ogre::Vector3 pyramidBlock (0.75f, 0.35f, 0.5f);
	buildPyramid (pyramidBlock, Ogre::Vector3 (-5.0f, 0.0f, -5.0f), Ogre::Quaternion (Radian (Degree ( 45.0f)), updir), 15);
	buildPyramid (pyramidBlock, Ogre::Vector3 ( 5.0f, 0.0f, -5.0f), Ogre::Quaternion (Radian (Degree (-45.0f)), updir), 15);

	// place a jenga tower
	buildJenga (Ogre::Vector3 ( 2, 0, 0), 20);
	buildJenga (Ogre::Vector3 (-2, 0, 0), 20);
	
	// position camera
	Ogre::Vector3 start(0.0f, 1000.0f, 10.0f);
	Ogre::Vector3 end(0.0f, -1000.0f, 10.0f);
	OgreNewt::BasicRaycast castRay (m_World, start, end, true);
	OgreNewt::BasicRaycast::BasicRaycastInfo info = castRay.getFirstHit();

	mCamera->setPosition(0.0, 2.0f + start.y + (end.y - start.y) * info.mDistance, 10.0);

	// set the near and far clip plane
	mCamera->setNearClipDistance(0.2f);
	mCamera->setFarClipDistance(1000.0f);

	//make a light
	Ogre::Light* light;

	light = mSceneMgr->createLight( "Light1" );
	light->setType( Ogre::Light::LT_POINT );
	light->setPosition( Ogre::Vector3(0.0, 100.0, 100.0) );
}





