#ifndef SCENE_CODE_H
#define SCENE_CODE_H

#include "scene.h"
#include <strstream>
#include <set>

namespace cbk
{
	Scene::Scene(void)
	{
		setWidth(800);
		setHeight(600);
		_cameraScrollSpeed = 300.0f;
		_cameraZoomSpeed = 10.0f;
		_hinstance = 0;
		_device = 0;
		setMouseScrollArea(0.02f, 0.02f);
		_deviceLost = false;
		_FPSFrames = 0;
		_FPSTime = 0;
		_lastFPS = 0;

		_terrainRenderMode = 0;
		_renderBoundingBox = false;
		_renderBSPPlanes = false;
		_renderOctreeNodes = false;

		octree.SetOverlap(0.00001f);
		octree.SetSize(cbk::Vector(2000.0f, 2000.0f, 2000.0f));

		for (register unsigned int x = 0; x < 3; x++)
			_mouseState[x] = false;

		for (register unsigned int x = 0; x <= _maxKey; x++)
			_keyState[x] = false;
	}

	inline Scene::~Scene(void)
	{
		{
			std::list<CBK::Shape*>::iterator cObject = _objects.begin();
			std::list<CBK::Shape*>::iterator end = _objects.end();
			for (; cObject != end; cObject++)
				delete (*cObject);
			_objects.clear();
			_objectsNamed.clear();
		}
		{
			stdext::hash_map<std::string, CBK::Camera*>::iterator cCamera = _cameras.begin();
			stdext::hash_map<std::string, CBK::Camera*>::iterator end = _cameras.end();
			for (; cCamera != end; cCamera++)
				delete (cCamera->second);
			_cameras.clear();
		}
		{
			stdext::hash_map<std::string, CBK::Light*>::iterator cLight = _lights.begin();
			stdext::hash_map<std::string, CBK::Light*>::iterator end = _lights.end();
			for (; cLight != end; cLight++)
				delete (cLight->second);
			_lights.clear();
		}
		{
			_curTerrainMesh = 0;
			_curTerrainTransform = 0;
			stdext::hash_map<std::string, CBK::Shape*>::iterator cTerrain = _terrain.begin();
			stdext::hash_map<std::string, CBK::Shape*>::iterator end = _terrain.end();
			for (; cTerrain != end; cTerrain++)
			{
				cTerrain->second->setMesh(0);
				delete (cTerrain->second);
			}
			_terrain.clear();
		}

		delete fps[0];
		delete fps[1];
		delete obj[0];
		delete obj[1];
		delete sel[0];
		delete sel[1];
	}

	inline Scene& Scene::setup(const HINSTANCE& instance, const int& width, const int& height)
	{
		if (width != 0)
			setWidth(width);

		if (height != 0)
			setHeight(height);

		setInstance(instance);
		createDevice();
		if (_device == 0) return *this;

		_viewport.Width = _width;
		_viewport.Height = _height;
		_viewport.X = 0;
		_viewport.Y = 0;
		_viewport.MinZ = 0.0f;
		_viewport.MaxZ = 1.0f;

		D3DXMatrixPerspectiveFovLH(
				&_matProjection,
				D3DX_PI * 0.5f, // 90 - degree
				float(_width) / float(_height),
				100.0f,
				1200.0f);
		_device->SetTransform(D3DTS_PROJECTION, &_matProjection);

		return *this;
	}

	//http://local.wasp.uwa.edu.au/~pbourke/geometry/sphereline/
	CBK::Shape* Scene::trace(D3DXVECTOR3* hitLocation, const D3DXVECTOR3& start, const D3DXVECTOR3& end, const bool collideObjects) const
	{
		//Store some values that are constant across all objects
		const D3DXVECTOR3 eMs = end - start;

		//Store the closest hit (init to a large distance and a null object)
		float curDistance = 65535.0f;
		CBK::Shape* curHit = 0;

		if (collideObjects)
		{
			const float tMagSq = D3DXVec3LengthSq(&eMs);
			//Loop though the objects
			if (tMagSq != 0) //if tMagSq is 0, we cannot collide with object...the length of the ray is 0
			{
				std::list<CBK::Shape*>::const_iterator cObj = _objects.begin();
				std::list<CBK::Shape*>::const_iterator lend = _objects.end();
				for (; cObj != lend; cObj++)
				{
					//Get some values for this object
					CBK::Shape* cObject = *cObj;
					const D3DXVECTOR3& cPos = cObject->getRealPosition();
					const D3DXVECTOR3 oMs = cPos - start;

					//Find where on the line the hit occured
					const float u = D3DXVec3Dot(&oMs, &eMs);
					if ((u >= 0) && (u <= tMagSq))
					{
						//Calculate the hit point
						const D3DXVECTOR3 hitPoint = start + (eMs * (u / tMagSq));

						//and the hit distance (from the hit point to the object's collision center
						const D3DXVECTOR3 hpMo = hitPoint - cPos;
						const float dist = D3DXVec3Length(&hpMo);

						//If it is close enough
						if (dist <= cObject->getCollideSphereRadius())
						{
							//and this hit is closest than our previous hit
							if (dist < curDistance)
							{
								curHit = cObject;
								curDistance = dist;
								if (hitLocation != 0)
									*hitLocation = hitPoint;
							}
						}
					}
				}
			}
		}

		//If no hits were found, check for terrain collision
		if (eMs.y != 0) //If eMs.y is 0, we cannot collide with terrain...the ray is perpendicular to the gound
		{
			if (curHit == 0)
			{
				float tHeight = 0.0f;
				for (register unsigned int x = 0; x < 99; x++)
				{
					const float t = -(start.y - tHeight) / eMs.y;

					//Make sure the hit was within the range of the segment
					if ((t > 0.0f) && (t < 1.0f))
					{
						//Calculate the hit point
						D3DXVECTOR3 hitPoint = start + (t * eMs);

						tHeight = _curTerrainMesh->getHeight(hitPoint.x, hitPoint.z);

						//Adjust to the proper y value for the terrain
						hitPoint.y = tHeight;

						if (hitLocation != 0)
							*hitLocation = hitPoint;
					}
				}
			}
		}

		//Return the closest hit found (will be 0 if no hits)
		return curHit;
	}

	Scene& Scene::clearSelection(void)
	{
		std::list<CBK::Shape*>::iterator cObj = _selection.begin();
		std::list<CBK::Shape*>::iterator lend = _selection.end();

		for (; cObj != lend; cObj++)
			(*cObj)->select(false);

		_selection.clear();

		return *this;
	}

	Scene& Scene::mouseLUp(const float& x, const float& y, const bool shift, const bool ctrl, const bool lButton, const bool rButton, const bool mButton)
	{
		_mouseState[MOUSE_Left] = false;

		//Store the last mouse positions
		mousePos oPos = _mousePosition;
		_mousePosition.x = long(x * _width);
		_mousePosition.y = long(y * _height);

		if (!shift)
			clearSelection();

		if ((timeGetTime() - _clickTime) > 200)
		{
			D3DXVECTOR3 point1 = _mousePositionNear;
			D3DXVECTOR3 point2;
			mouseCoords(&_mousePositionNear, &_mousePositionFar, _mousePosition.x, _mousePosition.y);
			point2 = _mousePositionNear;

			D3DXVECTOR3 pMin, pMax;

			pMin.x = min(point1.x, point2.x);
			pMin.y = 100.0f;
			pMin.z = min(point1.z, point2.z);
			pMax.x = max(point1.x, point2.x);
			pMax.y = 1200.0f;
			pMax.z = max(point1.z, point2.z);

			D3DXMATRIX viewProjection;
			D3DXMatrixPerspectiveOffCenterLH(&viewProjection, pMin.x, pMax.x, pMin.z, pMax.z, pMin.y, pMax.y);
//			D3DXMatrixPerspectiveLH(&viewProjection, pMax.x - pMin.x, pMax.z - pMin.z, pMin.y, pMax.y);
			CBK::Camera cCam;
			cCam.setDevice(_device);
			{
				D3DXVECTOR3 temp;
				temp = _curCamera->getPosition();
				cCam.setPosition(-temp.x * 2.0f, temp.y, -temp.z * 2.0f);
			}
			cCam.setRotation(_curCamera->getRotation());
			const D3DXMATRIX& matView = cCam.getViewMatrix();
//			viewProjection = matView * viewProjection;

			D3DXPLANE m_frustum[4];

			//http://www.chadvernon.com/blog/tutorials/managed-directx-2/frustum-culling/
			m_frustum[0].a = viewProjection._14 + viewProjection._11;
			m_frustum[0].b = viewProjection._24 + viewProjection._21;
			m_frustum[0].c = viewProjection._34 + viewProjection._31;
			m_frustum[0].d = viewProjection._44 + viewProjection._41;

			// Right plane
			m_frustum[1].a = viewProjection._14 - viewProjection._11;  
			m_frustum[1].b = viewProjection._24 - viewProjection._21;
			m_frustum[1].c = viewProjection._34 - viewProjection._31;
			m_frustum[1].d = viewProjection._44 - viewProjection._41;

			// Top plane
			m_frustum[2].a = viewProjection._14 - viewProjection._12;  
			m_frustum[2].b = viewProjection._24 - viewProjection._22;
			m_frustum[2].c = viewProjection._34 - viewProjection._32;
			m_frustum[2].d = viewProjection._44 - viewProjection._42;

			// Bottom plane
			m_frustum[3].a = viewProjection._14 + viewProjection._12;  
			m_frustum[3].b = viewProjection._24 + viewProjection._22;
			m_frustum[3].c = viewProjection._34 + viewProjection._32;
			m_frustum[3].d = viewProjection._44 + viewProjection._42;

			// Normalize planes
			register unsigned int i;
			for (i = 0; i < 4; i++ )
				D3DXPlaneNormalize(&m_frustum[i], &m_frustum[i]);

			std::list<CBK::Shape*>::iterator cObj = _objects.begin();
			std::list<CBK::Shape*>::iterator end = _objects.end();
			for (; cObj != end; cObj++)
			{
				D3DXVECTOR3 cPos =  (*cObj)->getRealPosition();
				D3DXVECTOR4 temp;
				D3DXVec3Transform(&temp, &cPos, &matView);
				cPos = D3DXVECTOR3(temp);

//				D3DXVECTOR3 cPos =  (*cObj)->getTranslation();
//				D3DXVec3Project(&cPos, &cPos, &tViewPort, &matProjection, &matView, &(*cObj)->getTransform());
//				D3DXVECTOR4 temp = D3DXVECTOR4(cPos, 1.0f);
//				D3DXVec4Transform(&temp, &temp, &matView);
//				cPos = D3DXVECTOR3(temp.x, temp.y, temp.z);

				const float radius = (*cObj)->getCollideSphereRadius();

//				if ((cPos.x < 0) || (cPos.x > 100) || (cPos.y < 0) || (cPos.y > 100))
//					continue;

				for (i = 0; i < 4; i++ )
					if ((D3DXPlaneDotCoord(&m_frustum[i], &cPos) + radius) < 0)
						break;

				if (i < 4)
					continue;

				(*cObj)->select();
				_selection.push_back((*cObj));
			}
		}
		else
		{
			CBK::Shape* hit = mouseCoords(&_mousePositionNear, &_mousePositionFar, _mousePosition.x, _mousePosition.y, true);
			if (hit != 0)
			{
				if (!hit->selected())
				{
					hit->select();
					_selection.push_back(hit);
				}
			}
		}

		return *this;
	}

	Scene& Scene::mouseRUp(const float& x, const float& y, const bool shift, const bool ctrl, const bool lButton, const bool rButton, const bool mButton)
	{
		_mouseState[MOUSE_Right] = false;

		//Store the last mouse positions
		_mousePosition.x = long(x * _width);
		_mousePosition.y = long(y * _height);
		CBK::Shape* hit = mouseCoords(&_mousePositionNear, &_mousePositionFar, _mousePosition.x, _mousePosition.y, true);

		return *this;
	}

	Scene& Scene::mouseMUp(const float& x, const float& y, const bool shift, const bool ctrl, const bool lButton, const bool rButton, const bool mButton)
	{
		_mouseState[MOUSE_Middle] = false;

		//Store the last mouse positions
		_mousePosition.x = long(x * _width);
		_mousePosition.y = long(y * _height);
		CBK::Shape* hit = mouseCoords(&_mousePositionNear, &_mousePositionFar, _mousePosition.x, _mousePosition.y, true);

		return *this;
	}

	Scene& Scene::mouseLClick(const float& x, const float& y, const bool shift, const bool ctrl, const bool lButton, const bool rButton, const bool mButton)
	{
		_mouseState[MOUSE_Left] = true;

		//Store the last mouse positions
		_mousePosition.x = long(x * _width);
		_mousePosition.y = long(y * _height);
		_mousePositionDown = _mousePosition;
		_clickTime = timeGetTime();
		mouseCoords(&_mousePositionNear, &_mousePositionFar, _mousePosition.x, _mousePosition.y);

		return *this;
	}

	Scene& Scene::mouseRClick(const float& x, const float& y, const bool shift, const bool ctrl, const bool lButton, const bool rButton, const bool mButton)
	{
		_mouseState[MOUSE_Right] = true;

		//Store the last mouse positions
		_mousePosition.x = long(x * _width);
		_mousePosition.y = long(y * _height);
		_mousePositionDown = _mousePosition;
		_clickTime = timeGetTime();
		mouseCoords(&_mousePositionNear, &_mousePositionFar, _mousePosition.x, _mousePosition.y);

		D3DXVECTOR3 hitLoc;
		//traceTerrain(&hitLoc, _mousePosition);
		trace(&hitLoc, _mousePositionNear, _mousePositionFar, false);

		if (_selection.size() > 0)
		{
			std::list<CBK::Shape*>::iterator cObj = _selection.begin();
			std::list<CBK::Shape*>::iterator lend = _selection.end();

			for (; cObj != lend; cObj++)
				(*cObj)->moveTo(hitLoc);
		}
		else
		{
			char cStr[256] = {0};
			std::strstream string(cStr, 255);
			string << "obj" << _objects.size();

			CBK::Shape* cObject;
			cObject = new CBK::Shape(new CBK::Shapes::Sphere(CBK::MeshParam(getDevice())),getDevice());
			cObject->load("");
			cObject->setMaterialLoader(getMaterialFactory());
			cObject->setTextureLoader(getTextureFactory());
			cObject->setMaterial("0 0 1 1 0 0 1 1 1 1 1 1 0 0 0 0 20");
			cObject->move(hitLoc);
			cObject->addLight(getLight("Sun"));
			cObject->addLight(getLight("Back"));
			cObject->addLight(getLight("Top"));
			cObject->scale(10.0f, 10.0f, 10.0f);
			cObject->setCurrentTerrain(_curTerrainTransform);
			addObject(cStr, cObject);
		}

		return *this;
	}

	Scene& Scene::mouseMClick(const float& x, const float& y, const bool shift, const bool ctrl, const bool lButton, const bool rButton, const bool mButton)
	{
		_mouseState[MOUSE_Middle] = true;
		_curCamera->setTerrainOffset(500.0f);

		return *this;
	}

	Scene& Scene::mouseScroll(const float& distance, const float& x, const float& y, const bool shift, const bool ctrl, const bool lButton, const bool rButton, const bool mButton)
	{
		//Store the last mouse positions
		_mousePosition.x = long(x * _width);
		_mousePosition.y = long(y * _height);
		mouseCoords(&_mousePositionNear, &_mousePositionFar, _mousePosition.x, _mousePosition.y);

		//Change camera offset
		float cCameraOffset = _curCamera->getTerrainOffset() - (distance * _cameraZoomSpeed);
		if (cCameraOffset < 200.0f)
			cCameraOffset = 200.0f;
		else if (cCameraOffset > 1000.0f)
			cCameraOffset = 1000.0f;

		_curCamera->setTerrainOffset(cCameraOffset);
		return *this;
	}

	Scene& Scene::mouseMove(const float& x, const float& y, const bool shift, const bool ctrl, const bool lButton, const bool rButton, const bool mButton)
	{
		//Check to see if we should scroll
		if (x < _mouseScrollArea.x)
			_vectCameraScroll.x = -_cameraScrollSpeed;
		else if (x > ((_viewport.Width - (_mouseScrollArea.x * _viewport.Width)) / _viewport.Width))
			_vectCameraScroll.x = _cameraScrollSpeed;
		else
			_vectCameraScroll.x = 0.0f;

		if (y < _mouseScrollArea.y)
			_vectCameraScroll.y = _cameraScrollSpeed;
		else if (y > ((_viewport.Height - (_mouseScrollArea.y * _viewport.Height)) / _viewport.Height))
			_vectCameraScroll.y = -_cameraScrollSpeed;
		else
			_vectCameraScroll.y = 0.0f;

		return *this;
	}

	Scene& Scene::setCameraScrollSpeed(const float& speed)
	{
		_cameraScrollSpeed = speed;
		return *this;
	}

	const float& Scene::getCameraScrollSpeed(void) const
	{
		return _cameraScrollSpeed;
	}

	Scene& Scene::setCameraZoomSpeed(const float& speed)
	{
		_cameraZoomSpeed = speed;
		return *this;
	}

	const float& Scene::getCameraZoomSpeed(void) const
	{
		return _cameraZoomSpeed;
	}

	Scene& Scene::setMouseScrollArea(const float& width, const float& height)
	{
		_mouseScrollArea.x = width;
		_mouseScrollArea.y = height;
		return *this;
	}

	CBK::Shape* Scene::mouseCoords(D3DXVECTOR3* nearPoint, D3DXVECTOR3* farPoint, const long& x, const long& y, const bool doTrace)
	{
		//Convert our coords to two points, one for the near plane and one for the far
		D3DXVECTOR3 mcNear(float(x), float(y), 0.0f), mcFar(float(x), float(y), 1.0f);

		//Some temp varibles
		D3DXMATRIX worldMatrix;

		//Get the near and far points from the various values (as needed)
		D3DXMatrixIdentity(&worldMatrix);
		if ((nearPoint != 0) || (doTrace))
			D3DXVec3Unproject(&mcNear, &mcNear, &_viewport, &_matProjection, &_curCamera->getViewMatrix(), &worldMatrix);
		if ((farPoint != 0) || (doTrace))
			D3DXVec3Unproject(&mcFar, &mcFar, &_viewport, &_matProjection, &_curCamera->getViewMatrix(), &worldMatrix);

		//Save out our points if the user asked for them
		if (nearPoint != 0)
			*nearPoint = mcNear;

		if (farPoint != 0)
			*farPoint = mcFar;

		if (doTrace)
			return trace(0, mcNear, mcFar);
		else
			return false;
	}

	inline Scene& Scene::setWidth(const int& width)
	{
		if (_device == 0)
			_width = width;
		return *this;
	}

	inline Scene& Scene::setHeight(const int& height)
	{
		if (_device == 0)
			_height = height;
		return *this;
	}

	inline Scene& Scene::setInstance(const HINSTANCE& instance)
	{
		if (_device == 0)
			_hinstance = instance;
		return *this;
	}

	inline Scene& Scene::createDevice(void)
	{
		if ((_hinstance == 0) || (_width == 0) || (_height == 0))
			return *this;

		if (!d3d::InitD3D(_hinstance, _width, _height, true, D3DDEVTYPE_HAL, &_device, &_deviceParams, &_hwnd))
			return *this;

		fps[0] = new CBK::Text(_device, "Times New Roman", "FPS:");
		fps[0]->setAlignmentHorizontal(CBK::Text::HOFF_Right);
		fps[0]->setPoint1(128, 0);
		fps[0]->setPoint2(128, 0);

		fps[1] = new CBK::Text(_device, "Times New Roman", "0");
		fps[1]->setAlignmentHorizontal(CBK::Text::HOFF_Left);
		fps[1]->setPoint1(136, 0);

		obj[0] = new CBK::Text(_device, "Times New Roman", "Objects:");
		obj[0]->setAlignmentHorizontal(CBK::Text::HOFF_Right);
		obj[0]->setPoint1(128, 32);
		obj[0]->setPoint2(128, 32);

		obj[1] = new CBK::Text(_device, "Times New Roman", "0");
		obj[1]->setAlignmentHorizontal(CBK::Text::HOFF_Left);
		obj[1]->setPoint1(136, 32);

		sel[0] = new CBK::Text(_device, "Times New Roman", "Selection:");
		sel[0]->setAlignmentHorizontal(CBK::Text::HOFF_Right);
		sel[0]->setPoint1(128, 64);
		sel[0]->setPoint2(128, 64);

		sel[1] = new CBK::Text(_device, "Times New Roman", "0");
		sel[1]->setAlignmentHorizontal(CBK::Text::HOFF_Left);
		sel[1]->setPoint1(136, 64);

		return *this;
	}

	inline const int& Scene::getWidth(void) const
	{
		return _width;
	}

	inline const int& Scene::getHeight(void) const
	{
		return _height;
	}

	inline Scene::operator IDirect3DDevice9*(void) const
	{
		return _device;
	}

	inline IDirect3DDevice9* Scene::getDevice(void) const
	{
		return _device;
	}

	inline const HINSTANCE& Scene::getInstance(void) const
	{
		return _hinstance;
	}

	inline Scene::MaterialFactory* Scene::getMaterialFactory(void)
	{
		return &_materialLoader;
	}

	inline Scene::TextureFactory* Scene::getTextureFactory(void)
	{
		return &_textureLoader;
	}

	inline Scene::MeshFactory* Scene::getMeshFactory(void)
	{
		return &_meshLoader;
	}

	inline Scene& Scene::update(const float& dt)
	{
		std::list<CBK::Shape*>::iterator cObj = _objects.begin();
		std::list<CBK::Shape*>::iterator end = _objects.end();
		bsptree.Clear();

		unsigned int objMoved = 0;

		for (; cObj != end; cObj++)
		{
			const D3DXVECTOR3 beginPos = (*cObj)->getRealPosition();
			(*cObj)->update(dt);
			if (((*cObj)->getRealPosition()).y < -1000.0f)
			{
				std::list<CBK::Shape*>::iterator cObj2 = _objects.begin();
				std::list<CBK::Shape*>::iterator end2 = _objects.end();
				for (; cObj2 != end2; cObj2++)
					if ((*cObj2) != (*cObj))
						(*cObj2)->removeObject(*cObj);
				std::list<CBK::Shape*>::iterator kill = cObj;
				cObj++;
				_objects.erase(kill);
				if (cObj == end)
					break;
				else
					continue;
			}
			D3DXVECTOR3 endPos = (*cObj)->getRealPosition();

/*			Octree<CBK::Shape*>::oNode* cNode = (*cObj)->getOctreeNode();

			std::vector<const Octree<CBK::Shape*>::oNode*> out;
			do
			{
				out.clear();
				if (cNode == 0)
				{
					const BoundingBox& bb = *(*cObj)->getBoundingBox();
					(*cObj)->setOctreeNode(octree.Insert(*cObj, endPos, bb, (*cObj)->getMaxScale(), &out));
					objMoved++;
				}
				else if ((beginPos != endPos) || (!cNode->isInTree()))
				{
					octree.Remove(cNode);
					cNode->setLocation(endPos);
					octree.Insert(cNode, &out);
					objMoved++;
				}

				if (out.size() > 0)
				{
					const std::vector<const Octree<CBK::Shape*>::oNode*>::const_iterator end = out.end();
					std::vector<const Octree<CBK::Shape*>::oNode*>::const_iterator x = out.begin();

					std::set<const Octree<CBK::Shape*>::oNode*> done;

					for (; x != end; x++)
						if ((*x) != cNode)
						{
							if (done.find(*x) != done.end())
							{
								D3DXVECTOR3 t = endPos - (*x)->getData()->getRealPosition();
								D3DXVec3Normalize(&t, &t);
								(*cObj)->move(t * 20.0f);
								done.insert(*x);
							}
						}
				}

				 endPos = (*cObj)->getRealPosition();
			} while (out.size() > 0);

*/			bsptree.Insert(*cObj, endPos, *(*cObj)->getBoundingBox(), (*cObj)->getMaxScale());
		}
		if (objMoved > 0)
			octree.Cleanup();

//		bsptree.Insert(Vector(1.0f, 0.0f, 0.0f), Vector(1.0f, 0.0f, 0.0f));

		_FPSFrames++;
		_FPSTime += dt;
		if (_FPSTime >= 0.25f)
		{
			_lastFPS = _FPSFrames / _FPSTime;
			_FPSFrames = 0;
			_FPSTime = 0.0f;
			char stringFPS[256] = {0};
			std::strstream myBuffFPS(stringFPS, 255);
			myBuffFPS << _lastFPS;
			fps[1]->setText(stringFPS);
		}

		char stringObj[256] = {0};
		std::strstream myBuffObj(stringObj, 255);
		myBuffObj << _objects.size();
		obj[1]->setText(stringObj);

		char stringSel[256] = {0};
		std::strstream myBuffSel(stringSel, 255);
		myBuffSel << _selection.size();
		sel[1]->setText(stringSel);

		if (_keyState['A'])
			mouseRClick(0.5f, 0.5f, false, false, false, true, false);

		if (_curCamera != 0)
		{
			if (_curTerrainMesh != 0)
			{
				D3DXVECTOR3 cPos = _curCamera->getPosition();
				float cTHeight = _curTerrainMesh->getHeight(cPos.x, cPos.z);

				if (cPos.y < cTHeight)
				{
					_curCamera->setPosition(cPos.x, cTHeight + 10.0f, cPos.z);
					cPos = _curCamera->getPosition();
				}

				cTHeight += _curCamera->getTerrainOffset();

				if (cTHeight != cPos.y)
				{
					const float MaxMove = 220.0f * dt;
					const float move = 200.0f * dt;
					const float diff = cTHeight - cPos.y;
					if (diff < 0)
						if (MaxMove >= diff)
							_curCamera->walk(move);
						else
							_curCamera->setPosition(cPos.x, cTHeight, cPos.z);
					else
						if (MaxMove <= diff)
							_curCamera->walk(-move);
						else
							_curCamera->setPosition(cPos.x, cTHeight, cPos.z);

					_curCamera->apply();
				}
			}

			if (_vectCameraScroll.x != 0.0f)
				_curCamera->strafe(_vectCameraScroll.x * dt);
			if (_vectCameraScroll.y != 0.0f)
				_curCamera->fly(_vectCameraScroll.y * dt);
			_curCamera->apply();
		}

		return *this;
	}

	Scene& Scene::keyDown(const long& keyCode)
	{
		if (keyCode <= _maxKey)
			_keyState[keyCode] = true;

		switch (keyCode)
		{
			case VK_F1:
				_terrainRenderMode++;
				if (_terrainRenderMode == 1)
					_terrainRenderMode = 2;
				else if (_terrainRenderMode > 3)
					_terrainRenderMode = 0;
				break;

			case VK_F2:
				_renderBoundingBox = !_renderBoundingBox;
				break;

			case VK_F3:
				_renderBSPPlanes = !_renderBSPPlanes;
				break;

			case VK_F4:
				_renderOctreeNodes = !_renderOctreeNodes;
				break;
		}

		return *this;
	}

	Scene& Scene::keyUp(const long& keyCode)
	{
		if (keyCode <= _maxKey)
			_keyState[keyCode] = false;

		return *this;
	}

	bool Scene::_recoverDevice(void)
	{
		if (_device->TestCooperativeLevel() == D3DERR_DEVICENOTRESET)
		{
			D3DPRESENT_PARAMETERS temp = _deviceParams;
			if (_device->Reset(&temp) == D3D_OK)
			{
				D3DXMatrixPerspectiveFovLH(&_matProjection, D3DX_PI * 0.5f,	float(_width) / float(_height),	1.0f, 1500.0f);
				_device->SetTransform(D3DTS_PROJECTION, &_matProjection);
				{
					stdext::hash_map<std::string, kLibrary::factory::_Storage<CBK::Texture, IDirect3DDevice9*>*>::iterator begin = _textureLoader.getBegin();
					stdext::hash_map<std::string, kLibrary::factory::_Storage<CBK::Texture, IDirect3DDevice9*>*>::iterator end = _textureLoader.getEnd();

					for (; begin != end; begin++)
						(*(begin->second))->reset();
				}
				{
					stdext::hash_map<std::string, kLibrary::factory::_Storage<CBK::Shapes::xLoader, const CBK::MeshParam&>*>::iterator begin = _meshLoader.getBegin();
					stdext::hash_map<std::string, kLibrary::factory::_Storage<CBK::Shapes::xLoader, const CBK::MeshParam&>*>::iterator end = _meshLoader.getEnd();

					for (; begin != end; begin++)
						(*(begin->second))->reset();
				}
				{
					std::list<CBK::Shape*>::iterator cObject = _objects.begin();
					std::list<CBK::Shape*>::iterator end = _objects.end();
					for (; cObject != end; cObject++)
						(*cObject)->reset();
				}
				{
					stdext::hash_map<std::string, CBK::Shape*>::iterator cObject = _terrain.begin();
					stdext::hash_map<std::string, CBK::Shape*>::iterator end = _terrain.end();
					for (; cObject != end; cObject++)
						cObject->second->reset();
				}
				_curCamera->apply();
				{
					for (register DWORD x = 0; x < CBK::Shape::getMaxNumLights(); x++)
						CBK::Light::disable(_device, x);
				}
				{
					for (register DWORD x = 0; x < 4; x++)
						CBK::Texture::clear(_device, x);
				}
				CBK::Material::clear(_device);

				RECT clipCursorRect;
				GetClientRect(_hwnd, &clipCursorRect);
				ClipCursor(&clipCursorRect);

				_deviceLost = false;
				return true;
			}
			else
				return false;
		}
		else
			return false;
	}

	inline Scene& Scene::draw(void)
	{
		if (_deviceLost)
			if (!_recoverDevice())
				return *this;

		_device->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xff00ff00, 1.0f, 0);
		_device->BeginScene();

		if (_curTerrainTransform != 0)
			_curTerrainTransform->draw(_terrainRenderMode);

		/*
		{
			std::list<CBK::Shape*>::iterator cObject = _objects.begin();
			std::list<CBK::Shape*>::iterator end = _objects.end();
			for (; cObject != end; cObject++)
				(*cObject)->updateDistances(cObject, end).apply();
		}
		*/
		{
			std::list<const BSPTree<CBK::Shape*>::Division*> obj;
			if (_renderBSPPlanes)
				bsptree.drawPlanes(_device);
			if (_renderOctreeNodes)
				octree.drawNodes(_device);
			bsptree.Search(&obj, _curCamera->getViewFrustum(_matProjection, _viewport), _curCamera->getPosition());

			std::list<const BSPTree<CBK::Shape*>::Division*>::iterator cObject = obj.begin();
			std::list<const BSPTree<CBK::Shape*>::Division*>::iterator end = obj.end();
			if (!_renderBoundingBox)
				for (; cObject != end; cObject++)
				{
					const BSPTree<CBK::Shape*>::Division* a = (*cObject);
					CBK::Shape* b = a->getData();
					b->apply();
				}
			else
				for (; cObject != end; cObject++)
				{
					const BSPTree<CBK::Shape*>::Division* a = (*cObject);
					CBK::Shape* b = a->getData();
					b->apply();
					b->drawBoundingBox();
				}

			char stringObj[256] = {0};
			std::strstream myBuffObj(stringObj, 255);
			myBuffObj << obj.size();
			this->obj[1]->setText(stringObj);
		}

		fps[0]->draw();
		fps[1]->draw();
		obj[0]->draw();
		obj[1]->draw();
		sel[0]->draw();
		sel[1]->draw();

		_device->EndScene();
		_deviceLost = (_device->Present(0, 0, 0, 0) == D3DERR_DEVICELOST);

		return *this;
	}

	inline CBK::Shape* Scene::getObject(const std::string& name)
	{
		stdext::hash_map<std::string, std::list<CBK::Shape*>::iterator>::iterator found = _objectsNamed.find(name);
		if (found == _objectsNamed.end())
		{
			return 0;
		}
		else
		{
			return *found->second;
		}
	}

	inline Scene& Scene::addObject(const std::string& name, CBK::Shape* object)
	{
		object->setOctree(&octree);
		_objects.push_front(object);
		std::list<CBK::Shape*>::iterator newObj = _objects.begin();
		std::pair<std::string, std::list<CBK::Shape*>::iterator> newObject(name, newObj);
		_objectsNamed.insert(newObject);

		return *this;
	}

	inline Scene& Scene::removeObject(const std::string& name)
	{
		stdext::hash_map<std::string, std::list<CBK::Shape*>::iterator>::iterator found = _objectsNamed.find(name);
		if (found != _objectsNamed.end())
		{
			_objects.erase(found->second);
			_objectsNamed.erase(found);
		}
		
		return *this;
	}

	inline Scene& Scene::sortObjects(void)
	{
		_objects.sort(_objSortFunc);

		return *this;
	}

	bool Scene::_objSortFunc(const CBK::Shape* first, const CBK::Shape* second)
	{
		if (first->getTexture(first->getSubsetCount() - 1) < second->getTexture())
			return true;
		else if (first->getTexture(first->getSubsetCount() - 1) > second->getTexture())
			return false;
		else if ((first->getTexture(first->getSubsetCount() - 1) == second->getTexture()) && (first->getTexture() < second->getTexture(second->getSubsetCount() - 1)))
			return true;
		else
			return false;
	}

	inline CBK::Camera* Scene::getCamera(const std::string& name) const
	{
		stdext::hash_map<std::string, CBK::Camera*>::const_iterator found = _cameras.find(name);
		if (found == _cameras.end())
		{
			return 0;
		}
		else
		{
			return found->second;
		}
	}

	inline Scene& Scene::addCamera(const std::string& name, CBK::Camera* object)
	{
		std::pair<std::string, CBK::Camera*> newCamera(name, object);
		_cameras.insert(newCamera);

		return *this;
	}

	inline Scene& Scene::removeCamera(const std::string& name)
	{
		stdext::hash_map<std::string, CBK::Camera*>::iterator found = _cameras.find(name);
		if (found != _cameras.end())
		{
			_cameras.erase(found);
		}
		return *this;
	}

	inline Scene& Scene::applyCamera(const std::string& name)
	{
		_curCamera = getCamera(name);
		_curCamera->apply();
		return *this;
	}

	inline CBK::Light* Scene::getLight(const std::string& name) const
	{
		stdext::hash_map<std::string, CBK::Light*>::const_iterator found = _lights.find(name);
		if (found == _lights.end())
		{
			return 0;
		}
		else
		{
			return found->second;
		}
	}

	inline Scene& Scene::addLight(const std::string& name, CBK::Light* object)
	{
		std::pair<std::string, CBK::Light*> newLight(name, object);
		_lights.insert(newLight);

		return *this;
	}

	inline Scene& Scene::removeLight(const std::string& name)
	{
		stdext::hash_map<std::string, CBK::Light*>::iterator found = _lights.find(name);
		if (found != _lights.end())
		{
			_lights.erase(found);
		}
		return *this;
	}

	inline Scene& Scene::applyLight(const std::string& name, const int index)
	{
		getLight(name)->apply(index);
		return *this;
	}

	inline CBK::Shape* Scene::addTerrian(const std::string& file, const std::string& name, const int numVertsPerRow, const int numVertsPerCol, const int cellSpacing, const float heightScale)
	{
		CBK::Terrain* cMesh = _terrainLoader.load(file, CBK::TerrainParam(getDevice(), numVertsPerRow, numVertsPerCol, cellSpacing, heightScale));
		cMesh->genTexture();
		CBK::Shape* cShape = new CBK::Shape(cMesh, getDevice());

		_terrain.insert(std::pair<std::string, CBK::Shape*>(name, cShape));

		return cShape;
	}

	inline Scene& Scene::applyTerrain(const std::string& name)
	{
		CBK::Shape* newTerrain = getTerrian(name);
		
		if (_curTerrainTransform != newTerrain)
		{
			_curTerrainTransform = newTerrain;
			_curTerrainMesh = dynamic_cast<CBK::Terrain*>(_curTerrainTransform->getMesh());

			std::list<CBK::Shape*>::iterator cObject = _objects.begin();
			std::list<CBK::Shape*>::iterator end = _objects.end();
			for (; cObject != end; cObject++)
				(*cObject)->setCurrentTerrain(newTerrain);
		}

		return *this;
	}

	inline CBK::Shape* Scene::getTerrian(const std::string& name)
	{
		stdext::hash_map<std::string, CBK::Shape*>::const_iterator found = _terrain.find(name);
		if (found == _terrain.end())
		{
			return 0;
		}
		else
		{
			return found->second;
		}
	}

	inline Scene& Scene::removeTerrian(const std::string& name)
	{
		stdext::hash_map<std::string, CBK::Shape*>::iterator found = _terrain.find(name);
		if (found != _terrain.end())
		{
			if (_curTerrainTransform == found->second)
				_curTerrainTransform = 0;
			if (_curTerrainMesh == found->second->getMesh())
				_curTerrainMesh = 0;
			_terrainLoader.unload(dynamic_cast<CBK::Terrain*>(found->second->getMesh()));
			found->second->setMesh(0);
			delete found->second;
			_terrain.erase(found);
		}
		return *this;
	}
}

#endif //SCENE_CODE_H