#include <ElementRenderer\ListElement.hpp>
#include <ElementRenderer\LODElement.hpp>
#include <ElementRenderer\ScalableShapeElement.hpp>
#include <ElementRenderer\ScreenQuadsElement.hpp>
#include <ElementRenderer\CameraElement.hpp>
#include <ElementRenderer\PointCloudElement.hpp>
#include <ElementRenderer\LineCloudElement.hpp>
#include <ElementRenderer\TriangleCloudElement.hpp>
#include <ElementRenderer\StateChange.hpp>
#include <ElementRenderer\gosFXElement.hpp>
#include <MLR\MLRHeaders.hpp>

#include "BuildCurves.hpp"

//
// Globals used for test
//
ElementRenderer::ListElement *Scene=NULL;

ElementRenderer::SwitchElement *Switch=NULL;
enum {SwitchSize=10};

#define EFFECT 7

//
// data for base shape on '1'
//
ElementRenderer::ShapeElement *Shape=NULL;
Stuff::RGBAColor Shape_Colors[8]={
	Stuff::RGBAColor(0.5f, 0.5f, 0.5f, 1.0f),
	Stuff::RGBAColor(0.5f, 0.5f, 1.0f, 1.0f),
	Stuff::RGBAColor(0.5f, 1.0f, 0.5f, 1.0f),
	Stuff::RGBAColor(0.5f, 1.0f, 1.0f, 1.0f),
	Stuff::RGBAColor(1.0f, 0.5f, 0.5f, 1.0f),
	Stuff::RGBAColor(1.0f, 0.5f, 1.0f, 1.0f),
	Stuff::RGBAColor(1.0f, 1.0f, 0.5f, 1.0f),
	Stuff::RGBAColor(1.0f, 1.0f, 1.0f, 1.0f)
};

//
// data for point cloud on '2'
//
enum {MaxPoints=300};
unsigned Point_Count=MaxPoints;
Stuff::Point3D
	Point_Data[MaxPoints],
	Point_Dest[MaxPoints];
Stuff::RGBAColor Color_Data[MaxPoints];
Stuff::Scalar Time_Left[MaxPoints];
Stuff::Scalar Last_Frame = 0.0;

//
// data for LOD on '3'
//
Stuff::RGBAColor Lod0_Colors[8]={
	Stuff::RGBAColor(1.0f, 1.0f, 1.0f, 1.0f),
	Stuff::RGBAColor(1.0f, 0.0f, 0.0f, 1.0f),
	Stuff::RGBAColor(1.0f, 0.0f, 0.0f, 1.0f),
	Stuff::RGBAColor(1.0f, 0.0f, 0.0f, 1.0f),
	Stuff::RGBAColor(1.0f, 0.0f, 0.0f, 1.0f),
	Stuff::RGBAColor(1.0f, 0.0f, 0.0f, 1.0f),
	Stuff::RGBAColor(1.0f, 0.0f, 0.0f, 1.0f),
	Stuff::RGBAColor(1.0f, 1.0f, 1.0f, 1.0f)
};

Stuff::RGBAColor Lod1_Colors[8]={
	Stuff::RGBAColor(1.0f, 1.0f, 1.0f, 1.0f),
	Stuff::RGBAColor(0.0f, 1.0f, 0.0f, 1.0f),
	Stuff::RGBAColor(0.0f, 1.0f, 0.0f, 1.0f),
	Stuff::RGBAColor(0.0f, 1.0f, 0.0f, 1.0f),
	Stuff::RGBAColor(0.0f, 1.0f, 0.0f, 1.0f),
	Stuff::RGBAColor(0.0f, 1.0f, 0.0f, 1.0f),
	Stuff::RGBAColor(0.0f, 1.0f, 0.0f, 1.0f),
	Stuff::RGBAColor(1.0f, 1.0f, 1.0f, 1.0f)
};

Stuff::RGBAColor Lod2_Colors[8]={
	Stuff::RGBAColor(1.0f, 1.0f, 1.0f, 1.0f),
	Stuff::RGBAColor(0.0f, 0.0f, 1.0f, 1.0f),
	Stuff::RGBAColor(0.0f, 0.0f, 1.0f, 1.0f),
	Stuff::RGBAColor(0.0f, 0.0f, 1.0f, 1.0f),
	Stuff::RGBAColor(0.0f, 0.0f, 1.0f, 1.0f),
	Stuff::RGBAColor(0.0f, 0.0f, 1.0f, 1.0f),
	Stuff::RGBAColor(0.0f, 0.0f, 1.0f, 1.0f),
	Stuff::RGBAColor(1.0f, 1.0f, 1.0f, 1.0f)
};

ElementRenderer::LODElement::Entry Lod_Entries[3]={
	{0.0f, 225.0f},
	{225.0f, 400.0f},
	{400.0f, 500.0f}
};

//
// Data for triangle cloud on '6'
//
Stuff::RGBAColor Tri_Color_Data[MaxPoints];
unsigned Triangle_Count = MaxPoints/3;

//
// Data for scaling mlr_shape on '7'
//
ElementRenderer::ScalableShapeElement *Scalable;
Stuff::Vector3D New_Scale;
Stuff::RGBAColor New_Color;
Stuff::Scalar Scale_Time;

//
// Data for gosFX on '8'
//
ElementRenderer::gosFXElement *Effect;

gosFX::Effect *gos_Effect;
Stuff::DynamicMemoryStream * dms;
Stuff::Radian Effect_Angle;

//
// Data for the screen quad
//
ElementRenderer::ScreenQuadsElement *Quad;
bool *Quad_Status;

//
// Data for camera
//
ElementRenderer::CameraElement *Camera=NULL;
Stuff::YawPitchRange Camera_Direction(0.0f, 0.0f, -10.0f);
Stuff::Vector2DOf<Stuff::Scalar> Camera_Shift(0.0f, 0.0f);
ElementRenderer::StateChange *Camera_State=NULL;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void __stdcall InitializeGameEngine()
{
	//
	//---------------------
	// Initialize libraries
	//---------------------
	//
	Stuff::ArmorLevel = 4;

	Stuff::InitializeClasses();
	MidLevelRenderer::InitializeClasses();
	gosFX::InitializeClasses();
	ElementRenderer::InitializeClasses();

	Stuff::NotationFile::TestClass();

	//
	//------------------------
	// Set up the texture pool
	//------------------------
	//
	gos_PushCurrentHeap(MidLevelRenderer::Heap);
	MidLevelRenderer::TGAFilePool *pool = new MidLevelRenderer::TGAFilePool("Assets\\Graphics\\");
	Check_Object(pool);
	MidLevelRenderer::MLRTexturePool::Instance =
		new MidLevelRenderer::MLRTexturePool(pool);
	Check_Object(MidLevelRenderer::MLRTexturePool::Instance);
	MidLevelRenderer::MLRTexture *texture =
		MidLevelRenderer::MLRTexturePool::Instance->Add("texture");
	Check_Object(texture);
	MidLevelRenderer::MLRTexture *texture2 =
		MidLevelRenderer::MLRTexturePool::Instance->Add("lightspot");
	Check_Object(texture2);

	MidLevelRenderer::MLRState state;
//	state.SetAlphaMode(MidLevelRenderer::MLRState::OneZeroMode);
	state.SetTextureHandle(texture->GetTextureHandle());
	state.SetPriority(MidLevelRenderer::MLRState::DefaultPriority);
	
	MidLevelRenderer::MLRState state2;
	state2.SetTextureHandle(texture2->GetTextureHandle());
	state2.SetZBufferWriteOff();
	state2.SetAlphaMode(MidLevelRenderer::MLRState::OneOneMode);
	state2.SetPriority(MidLevelRenderer::MLRState::DefaultPriority + 1);
	gos_PopCurrentHeap();

	//
	//-------------------------------------------------------------------
	// Build the scene root and a shifting node for testing element types
	//-------------------------------------------------------------------
	//
	gos_PushCurrentHeap(ElementRenderer::ListElement::s_Heap);
	Scene = new ElementRenderer::ListElement;
	Check_Object(Scene);
	Scene->SetSize(2);
	gos_PopCurrentHeap();

	gos_PushCurrentHeap(ElementRenderer::SwitchElement::s_Heap);
	Switch = new ElementRenderer::SwitchElement;
	Check_Object(Switch);
	Switch->SetSize(SwitchSize);
	Scene->AttachIndexedChild(0, Switch);
	Switch->SetNeverCullMode();

	//
	//----------------------
	// Build a default shape
	//----------------------
	//
	Shape = new ElementRenderer::ShapeElement;
	Check_Object(Shape);
	MidLevelRenderer::MLRShape *two_pass_shape = Shape->GetMLRShape();
	Check_Object(two_pass_shape);
	MidLevelRenderer::MLR_I_C_DT_PMesh *cube_2pass =
		MidLevelRenderer::CreateIndexedCube_Color_NoLit_2Tex(
			1.0f,
			Shape_Colors,
			&state,
			&state2
		);
	Check_Object(cube_2pass);
	two_pass_shape->Add(cube_2pass);
	cube_2pass->DetachReference();
	Shape->m_localOBB.sphereRadius = Stuff::Sqrt(3.0f);
#if 0
	Shape->m_localOBB.axisExtents = Stuff::Vector3D(1.0f, 1.0f, 1.0f);
	Shape->SetOBBMode();
#endif
	ElementRenderer::Element::CallbackSet = ElementRenderer::Element::Callbacks;
	Switch->AttachIndexedChild(0, Shape);
	Shape->SetVolumeCullMode();
	Shape->SetName("Cube");
//	Shape->SetCallbackIndex(ElementRenderer::Element::DrawGreenBounds);
	Shape->Sync();

	//
	//--------------------
	// Build a point cloud
	//--------------------
	//
	ElementRenderer::PointCloudElement *cloud =
		new ElementRenderer::PointCloudElement(ELEMENTS(Point_Data));
	Check_Object(cloud);
	cloud->SetDataPointers(&Point_Count, Point_Data, Color_Data);
	unsigned i;
	for (i=0; i<ELEMENTS(Point_Data); ++i)
	{
		Color_Data[i] = Stuff::RGBAColor(1.0f, 1.0f, 1.0f, 1.0f);
		Point_Data[i].x = 2.0f*Stuff::Random::GetFraction()-1.0f;
		Point_Data[i].y = 2.0f*Stuff::Random::GetFraction()-1.0f;
		Point_Data[i].z = 2.0f*Stuff::Random::GetFraction()-1.0f;
		Point_Dest[i].x = 2.0f*Stuff::Random::GetFraction()-1.0f;
		Point_Dest[i].y = 2.0f*Stuff::Random::GetFraction()-1.0f;
		Point_Dest[i].z = 2.0f*Stuff::Random::GetFraction()-1.0f;
		Time_Left[i] = 2.0f*Stuff::Random::GetFraction()+1.0f;
	}
	cloud->m_localOBB.sphereRadius = Stuff::Sqrt(3.0f);
	Switch->AttachIndexedChild(1, cloud);
	cloud->SetVolumeCullMode();
	cloud->SetName("Point Cloud");

	//
	//-------------------
	// Build a line cloud
	//-------------------
	//
	ElementRenderer::LineCloudElement *lineCloud =
		new ElementRenderer::LineCloudElement(ELEMENTS(Point_Data));
	Check_Object(lineCloud);
	lineCloud->SetDataPointers(&Point_Count, Point_Data, Color_Data);
	for (i=0; i<ELEMENTS(Point_Data); ++i)
	{
		Color_Data[i] = Stuff::RGBAColor(1.0f, 1.0f, 1.0f, 1.0f);
		Point_Data[i].x = 2.0f*Stuff::Random::GetFraction()-1.0f;
		Point_Data[i].y = 2.0f*Stuff::Random::GetFraction()-1.0f;
		Point_Data[i].z = 2.0f*Stuff::Random::GetFraction()-1.0f;
		Point_Dest[i].x = 2.0f*Stuff::Random::GetFraction()-1.0f;
		Point_Dest[i].y = 2.0f*Stuff::Random::GetFraction()-1.0f;
		Point_Dest[i].z = 2.0f*Stuff::Random::GetFraction()-1.0f;
		Time_Left[i] = 2.0f*Stuff::Random::GetFraction()+1.0f;
	}
	lineCloud->m_localOBB.sphereRadius = Stuff::Sqrt(3.0f);
	Switch->AttachIndexedChild(8, lineCloud);
	lineCloud->SetVolumeCullMode();
	lineCloud->SetName("Line Cloud");

	//
	//--------------------
	// Build an LOD object
	//--------------------
	//
	ElementRenderer::LODElement *lod =
		new ElementRenderer::LODElement;
	Check_Object(lod);
	lod->SetSize(ELEMENTS(Lod_Entries));
	lod->SetName("LOD");

	ElementRenderer::ShapeElement *child =
		new ElementRenderer::ShapeElement;
	Check_Object(child);
	MidLevelRenderer::MLRShape *lod_0_shape = child->GetMLRShape();
	Check_Object(lod_0_shape);
	MidLevelRenderer::MLR_I_C_PMesh *lod_0_mesh =
		MidLevelRenderer::CreateIndexedCube_Color_NoLit(1.0f, Lod0_Colors, &state);
	Check_Object(lod_0_mesh);
	lod_0_shape->Add(lod_0_mesh);
	lod_0_mesh->DetachReference();
	child->m_localOBB.sphereRadius = Stuff::Sqrt(3.0f);
	child->m_localOBB.axisExtents = Stuff::Vector3D(1.0f, 1.0f, 1.0f);
	child->SetOBBMode();
	lod->AttachLOD(0, child, Lod_Entries[0]);
	child->SetVolumeCullMode();
	child->SetName("LOD0");

	child =
		new ElementRenderer::ShapeElement;
	Check_Object(child);
	MidLevelRenderer::MLRShape *lod_1_shape = child->GetMLRShape();
	Check_Object(lod_1_shape);
	MidLevelRenderer::MLR_I_C_PMesh *lod_1_mesh =
		MidLevelRenderer::CreateIndexedCube_Color_NoLit(1.0f, Lod1_Colors, &state);
	Check_Object(lod_1_mesh);
	lod_1_shape->Add(lod_1_mesh);
	lod_1_mesh->DetachReference();
	child->m_localOBB.sphereRadius = Stuff::Sqrt(3.0f);
	child->m_localOBB.axisExtents = Stuff::Vector3D(1.0f, 1.0f, 1.0f);
	child->SetOBBMode();
	lod->AttachLOD(1, child, Lod_Entries[1]);
	child->SetVolumeCullMode();
	child->SetName("LOD1");

	child = new ElementRenderer::ShapeElement;
	Check_Object(child);
	MidLevelRenderer::MLRShape *lod_2_shape = child->GetMLRShape();
	Check_Object(lod_2_shape);
	MidLevelRenderer::MLR_I_C_PMesh *lod_2_mesh =
		MidLevelRenderer::CreateIndexedCube_Color_NoLit(1.0f, Lod2_Colors, &state);
	Check_Object(lod_2_mesh);
	lod_2_shape->Add(lod_2_mesh);
	lod_2_mesh->DetachReference();
	child->m_localOBB.sphereRadius = Stuff::Sqrt(3.0f);
	child->m_localOBB.axisExtents = Stuff::Vector3D(1.0f, 1.0f, 1.0f);
	child->SetOBBMode();
	lod->AttachLOD(2, child, Lod_Entries[2]);
	child->SetVolumeCullMode();
	child->SetName("LOD2");

	Switch->AttachIndexedChild(2, lod);
	lod->NeedNewBounds();

	//
	//----------------------------------------------------------
	// Build a shape like option one, but enable Y-Axis aligning
	//----------------------------------------------------------
	//
	ElementRenderer::GroupElement *g =
		new ElementRenderer::GroupElement;
	Check_Object(g);
	Switch->AttachIndexedChild(3, g);

	child = new ElementRenderer::ShapeElement;
	Check_Object(child);
	MidLevelRenderer::MLRShape *y_shape = child->GetMLRShape();
	Check_Object(y_shape);
	MidLevelRenderer::MLR_I_C_PMesh *cube =
		MidLevelRenderer::CreateIndexedCube_Color_NoLit(
			1.0f,
			NULL, //Shape_Colors,
			&state
		);
	Check_Object(cube);
	y_shape->Add(cube);
	cube->DetachReference();
	child->m_localOBB.sphereRadius = Stuff::Sqrt(3.0f);
	child->m_localOBB.axisExtents = Stuff::Vector3D(1.0f, 1.0f, 1.0f);
	child->SetOBBMode();
	g->AttachChild(child);
	child->SetAlignY();
	child->SetVolumeCullMode();
	child->SetName("YAxis");

	Stuff::Scalar x,y;
	Stuff::LinearMatrix4D matrix;
	for (x=-10.0f; x <= 10.0f; x += 4.0f)
		for (y=-10.0f; y <= 10.0f; y += 4.0f)
			if (x != 0.0f || y != 0)
			{
				ElementRenderer::ShapeElement *element =
					new ElementRenderer::ShapeElement(child);
				Check_Object(element);
				g->AttachChild(element);
				matrix = Stuff::LinearMatrix4D::Identity;
				matrix.BuildTranslation(Stuff::Point3D(x, y, 0.0f));
				element->SetLocalToParent(matrix);
			}

	//
	//-----------------------------------------------------------
	// Build a shape like option one, but enable XY-Axis aligning
	//-----------------------------------------------------------
	//
	g = new ElementRenderer::GroupElement;
	Check_Object(g);
	Switch->AttachIndexedChild(4, g);

	child = new ElementRenderer::ShapeElement;
	Check_Object(child);
	MidLevelRenderer::MLRShape *xy_shape = child->GetMLRShape();
	Check_Object(xy_shape);
	xy_shape->Add(cube);
	child->m_localOBB.sphereRadius = Stuff::Sqrt(3.0f);
	child->m_localOBB.axisExtents = Stuff::Vector3D(1.0f, 1.0f, 1.0f);
	child->SetOBBMode();
	g->AttachChild(child);
	child->SetAlignX();
	child->SetAlignY();
	child->SetVolumeCullMode();
	child->SetName("XYAxis");

	for (x=-10.0f; x <= 10.0f; x += 4.0f)
		for (y=-10.0f; y <= 10.0f; y += 4.0f)
			if (x != 0.0f || y != 0)
			{
				ElementRenderer::ShapeElement *element =
					new ElementRenderer::ShapeElement(child);
				Check_Object(element);
				g->AttachChild(element);
				matrix = Stuff::LinearMatrix4D::Identity;
				matrix.BuildTranslation(Stuff::Point3D(x, y, 0.0f));
				element->SetLocalToParent(matrix);
			}

	//
	//-----------------------
	// Build a triangle cloud
	//-----------------------
	//
	ElementRenderer::TriangleCloudElement *tri_cloud =
		new ElementRenderer::TriangleCloudElement(Triangle_Count);
	Check_Object(tri_cloud);
	tri_cloud->SetDataPointers(&Triangle_Count, Point_Data, Tri_Color_Data);
	for (i=0; i<ELEMENTS(Tri_Color_Data); ++i)
	{
		Tri_Color_Data[i].red = Stuff::Random::GetFraction();
		Tri_Color_Data[i].green = Stuff::Random::GetFraction();
		Tri_Color_Data[i].blue = Stuff::Random::GetFraction();
		Tri_Color_Data[i].alpha = Stuff::Random::GetFraction();
	}
	tri_cloud->m_localOBB.sphereRadius = Stuff::Sqrt(3.0f);
	Switch->AttachIndexedChild(5, tri_cloud);
	tri_cloud->SetVolumeCullMode();
	tri_cloud->SetName("Triangle cloud");

	//
	//-------------------------------------------
	// Build a mlr_shape like option one, but scale it
	//-------------------------------------------
	//
	Scalable =
		new ElementRenderer::ScalableShapeElement;
	Check_Object(Scalable);
	MidLevelRenderer::MLRShape *scaling_shape = Scalable->GetMLRShape();
	Check_Object(scaling_shape);
	scaling_shape->Add(cube);
	Scalable->m_localOBB.sphereRadius = Stuff::Sqrt(3.0f);
	Scalable->m_localOBB.axisExtents = Stuff::Vector3D(1.0f, 1.0f, 1.0f);
	Scalable->SetOBBMode();
	Switch->AttachIndexedChild(6, Scalable);
	Scalable->SetVolumeCullMode();
	New_Scale.x = 3.75f*Stuff::Random::GetFraction() + 0.25f;
	New_Scale.y = 3.75f*Stuff::Random::GetFraction() + 0.25f;
	New_Scale.z = 3.75f*Stuff::Random::GetFraction() + 0.25f;
	New_Color.red = Stuff::Random::GetFraction();
	New_Color.green = Stuff::Random::GetFraction();
	New_Color.blue = Stuff::Random::GetFraction();
	New_Color.alpha = Stuff::Random::GetFraction();
	Scale_Time = 2.0f*Stuff::Random::GetFraction()+1.0f;
	Scalable->SetName("Scalable");

	//
	//---------------------
	// Build the FX library
	//---------------------
	//
	gos_PushCurrentHeap(gosFX::Heap);
	gosFX::EffectLibrary::Instance = new gosFX::EffectLibrary;
	Check_Object(gosFX::EffectLibrary::Instance);
	gosFX::EffectLibrary::Instance->m_effects.SetLength(EFFECT_COUNT);
	gosFX::EffectLibrary::Instance->m_effects.AssignValue(NULL,EFFECT_COUNT);

	CreateShardCloud(gosFX::EffectLibrary::Instance, 0);
	CreateShape(gosFX::EffectLibrary::Instance, 1, scaling_shape);
	CreatePointCloud(gosFX::EffectLibrary::Instance, 2 /*, 1*/);
	CreateShapeCloud(gosFX::EffectLibrary::Instance, 3, scaling_shape);
	CreatePertCloud(gosFX::EffectLibrary::Instance, 4);
	CreateCardCloud(gosFX::EffectLibrary::Instance, 5, texture /*, 0*/);
	CreateEffectCloud(gosFX::EffectLibrary::Instance, 6, 2);
	CreateCard(gosFX::EffectLibrary::Instance, 7, texture);
	CreateTube(gosFX::EffectLibrary::Instance, 8, texture);

	MString fileName("Assets\\Graphics\\test.EBF");
	CreateDebris(gosFX::EffectLibrary::Instance, 9, &fileName);

	CreateBeam(gosFX::EffectLibrary::Instance, 10, texture);
	CreateFlare(gosFX::EffectLibrary::Instance, 11, texture);

	gos_PopCurrentHeap();

	//
	//-------------------------
	// Build a simple FX holder
	//-------------------------
	//
	gos_Effect =
		gosFX::EffectLibrary::Instance->MakeEffect(
			EFFECT,
			gosFX::Effect::LoopFlag
//			 | gosFX::Effect::LocalSpaceSimulationMode
//			 | gosFX::Effect::StaticWorldSpaceSimulationMode
			 | gosFX::Effect::DynamicWorldSpaceSimulationMode
		);
	Check_Object(gos_Effect);
	Effect = new ElementRenderer::gosFXElement(gos_Effect);
	Check_Object(Effect);
	Effect->m_localOBB.sphereRadius = Stuff::Sqrt(3.0f);
	Switch->AttachIndexedChild(7,Effect);
	Effect->SetVolumeCullMode();
	Effect->SetName("gosFX");
	Stuff::LinearMatrix4D effect_origin(true);
//	effect_origin.BuildRotation(YawPitchRoll(0.0f, 0.0f, Stuff::Pi_Over_2));
	Effect->SetLocalToParent(effect_origin);
	Effect->Sync();

	//
	//---------------
	// Build a camera
	//---------------
	//
	Camera_State = new ElementRenderer::StateChange();
	Check_Object(Camera_State);
	Camera_State->EnableBackfaceCulling();
	Camera_State->EnableDithering();
	Camera_State->EnablePerspectiveCorrection();
	Camera_State->EnableZBufferWrite();
	Camera_State->EnableZBufferCompare();
	Camera_State->DisableChildWireFrameControl();
	Camera = new ElementRenderer::CameraElement(Camera_State);
	Check_Object(Camera);
	Scene->AttachIndexedChild(1, Camera);
	Camera->SetPerspective(1.0f, 1100.0f, Stuff::Pi_Over_3);

	Camera->SetViewport(0.0f, 0.0f, 1.0f, 1.0f);

	Camera->SetScene(Scene);
	Stuff::LinearMatrix4D origin;
	origin.BuildRotation(
		Stuff::EulerAngles(Camera_Direction.pitch, Camera_Direction.yaw, 0.0f)
	);
	origin.BuildTranslation(Stuff::Point3D(Camera_Direction));
	Camera->SetLocalToParent(origin);
	Camera->LockBounds();

	//
	//-------------------------------
	// Build the screen Quads element
	//-------------------------------
	//
	Quad = new ElementRenderer::ScreenQuadsElement(1);
	Check_Object(Quad);
	Stuff::Vector4D *corners;
	Stuff::RGBAColor *colors;
	Stuff::Vector2DOf<Stuff::Scalar> *uvs;
	Quad->GetQuadData(0, &corners, &colors, &uvs, &Quad_Status);
	corners[0] = Stuff::Vector4D(0.1f, 0.1f, 0.0f, 1.0f);
	corners[1] = Stuff::Vector4D(0.1f, 0.2f, 0.0f, 1.0f);
	corners[2] = Stuff::Vector4D(0.2f, 0.2f, 0.0f, 1.0f);
	corners[3] = Stuff::Vector4D(0.2f, 0.1f, 0.0f, 1.0f);
	colors[0] = Stuff::RGBAColor(1.0f, 0.0f, 0.0f, 1.0f);
	colors[1] = Stuff::RGBAColor(0.0f, 1.0f, 0.0f, 1.0f);
	colors[2] = Stuff::RGBAColor(0.0f, 0.0f, 1.0f, 1.0f);
	colors[3] = Stuff::RGBAColor(1.0f, 1.0f, 1.0f, 1.0f);
	uvs[0] = Stuff::Vector2DOf<Stuff::Scalar>(0.0f, 0.0f);
	uvs[1] = Stuff::Vector2DOf<Stuff::Scalar>(0.0f, 0.0f);
	uvs[2] = Stuff::Vector2DOf<Stuff::Scalar>(0.0f, 0.0f);
	uvs[3] = Stuff::Vector2DOf<Stuff::Scalar>(0.0f, 0.0f);
	Camera->AttachChild(Quad);
	ElementRenderer::StateChange *quad_state = new ElementRenderer::StateChange();
	Check_Object(quad_state);
	quad_state->SetRenderPriority(ElementRenderer::StateChange::HUDPriority1);
	Quad->AdoptStateChange(quad_state);

	//
	//-----------------------------------------------------
	// Sync the matrices of the scene and load the textures
	//-----------------------------------------------------
	//
	Scene->Sync();
	MidLevelRenderer::MLRTexturePool::Instance->LoadImages();
	gos_PopCurrentHeap();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void __stdcall DoGameLogic()
{
	//
	//------------------------
	// Animate the point cloud
	//------------------------
	//
	Stuff::Scalar now = static_cast<Stuff::Scalar>(gos_GetElapsedTime());
	Stuff::Scalar step = now - Last_Frame;
	Last_Frame = now;
	for (int i=0; i<Point_Count; ++i)
	{
		Time_Left[i] -= step;
		if (Time_Left[i] <= 0.0f)
		{
			Point_Data[i] = Point_Dest[i];
			Point_Dest[i].x = 2.0f*Stuff::Random::GetFraction()-1.0f;
			Point_Dest[i].y = 2.0f*Stuff::Random::GetFraction()-1.0f;
			Point_Dest[i].z = 2.0f*Stuff::Random::GetFraction()-1.0f;
			Time_Left[i] = 2.0f*Stuff::Random::GetFraction()+1.0f;
		}
		else
		{
			Point_Data[i].Lerp(
				Point_Data[i],
				Point_Dest[i],
				step / (step + Time_Left[i])
			);
		}
	}

	//
	//------------------
	// Animate the scale
	//------------------
	//
	Scale_Time -= step;
	Check_Object(Scalable);
	if (Scale_Time <= 0.0f)
	{
		Scalable->SetColor(New_Color);
		Scalable->SetScale(New_Scale);
		New_Scale.x = 3.75f*Stuff::Random::GetFraction() + 0.25f;
		New_Scale.y = 3.75f*Stuff::Random::GetFraction() + 0.25f;
		New_Scale.z = 3.75f*Stuff::Random::GetFraction() + 0.25f;
		New_Color.red = Stuff::Random::GetFraction();
		New_Color.green = Stuff::Random::GetFraction();
		New_Color.blue = Stuff::Random::GetFraction();
		New_Color.alpha = Stuff::Random::GetFraction();
		Scale_Time = 2.0f*Stuff::Random::GetFraction()+1.0f;
	}
	else
	{
		Stuff::Vector3D scale;
		Stuff::Scalar t = step / (step + Scale_Time);
		scale.Lerp(Scalable->GetScale(), New_Scale, t);
		Scalable->SetScale(scale);
		Stuff::RGBAColor color;
		color.Lerp(Scalable->GetColor(), New_Color, t);
		Scalable->SetColor(color);
	}

	//
	//--------------
	// Run the gosFX
	//--------------
	//
#if 1
	Stuff::LinearMatrix4D effect_origin = Effect->GetLocalToParent();
	Effect_Angle += step * Stuff::Pi_Over_2;
	Effect_Angle.Normalize();
	Stuff::YawPitchRoll ypr(-Effect_Angle, Stuff::Pi_Over_2, 0.0f);
	Stuff::SinCosPair xz;
	xz = Effect_Angle;
	effect_origin(3, Stuff::X_Axis) = xz.cosine*3.0f;
	effect_origin(3, Stuff::Z_Axis) = xz.sine*3.0f;
	effect_origin.BuildRotation(ypr);
	Effect->SetLocalToParent(effect_origin);
#endif

	//
	//-----------------------------------------------
	// Read the keyboard to see how to set the switch
	//-----------------------------------------------
	//
	int temp;
	char key = (char)(gos_GetKey()&255);

	switch (key)
	{
	case '1':
	case '2':
	case '3':
	case '4':
	case '5':
	case '6':
	case '7':
	case '8':
	case '9':
		if (key<'1'+SwitchSize)
			Switch->SetSwitch(static_cast<unsigned short>(key-'1'));
		if (key == '8')
		{
			Point3D end(
				Effect->GetLocalToWorld()(3,0) * xz.cosine,
				Effect->GetLocalToWorld()(3,1) * xz.cosine,
				Effect->GetLocalToWorld()(3,2) * xz.cosine
			);
			gosFX::Beam::ExecuteInfo
				info(
					gos_GetElapsedTime(),
					&Effect->GetLocalToWorld(),
					&end,
					&Effect->m_localOBB
				);
			#if EFFECT!=10
				Effect->GetEffect()->Start(&info);
			#else
				Cast_Object(gosFX::Beam*, Effect->GetEffect())->Start(&info);
			#endif
			Effect->SetNeverCullMode();
		}
		else
		{
			Effect->GetEffect()->Kill();
		}
		break;

	//
	//--------------
	// Blending mode
	//--------------
	//
	case 'a':
		Check_Object(Camera_State);
		temp = Camera_State->GetAlphaMode() + 1;
		if (temp>ElementRenderer::StateChange::AlphaInvAlphaMode)
			Camera_State->SetAlphaMode(ElementRenderer::StateChange::OneZeroMode);
		else
			Camera_State->SetAlphaMode(
				static_cast<ElementRenderer::StateChange::AlphaMode>(temp)
			);
		break;

	//
	//---------------
	// Backface mode
	//---------------
	//
	case 'b':
		Check_Object(Camera_State);
		if (Camera_State->IsBackfaceCullingEnabled())
			Camera_State->DisableBackfaceCulling();
		else
			Camera_State->EnableBackfaceCulling();
		break;

	//
	//---------------------
	// Zbuffer compare mode
	//---------------------
	//
	case 'c':
		Check_Object(Camera_State);
		if (Camera_State->IsZBufferCompareEnabled())
			Camera_State->DisableZBufferCompare();
		else
			Camera_State->EnableZBufferCompare();
		break;

	//
	//---------------
	// Dithering mode
	//---------------
	//
	case 'd':
		Check_Object(Camera_State);
		if (Camera_State->IsDitheringEnabled())
			Camera_State->DisableDithering();
		else
			Camera_State->EnableDithering();
		break;

	//
	//----------------
	// Filtering modes
	//----------------
	//
	case 'f':
		Check_Object(Camera_State);
		temp = Camera_State->GetFilterMode() + 1;
		if (temp>ElementRenderer::StateChange::TrilinearMode)
			Camera_State->SetFilterMode(ElementRenderer::StateChange::PointSampleMode);
		else
			Camera_State->SetFilterMode(
				static_cast<ElementRenderer::StateChange::FilterMode>(temp)
			);
		break;

	//
	//-----------------
	// perspective mode
	//-----------------
	//
	case 'p':
		Check_Object(Camera_State);
		if (Camera_State->IsPerspectiveCorrectionEnabled())
			Camera_State->DisablePerspectiveCorrection();
		else
			Camera_State->EnablePerspectiveCorrection();
		break;

	//
	//-----------------------
	// toggle the screen quad
	//-----------------------
	//
	case 'q':
		Check_Pointer(Quad_Status);
		*Quad_Status = (*Quad_Status) ? false : true;
		break;

	//
	//---------------
	// texture handle
	//---------------
	//
	case 's':
		Check_Object(Effect);
		Effect->GetEffect()->Stop();
		break;

	//
	//---------------
	// texture handle
	//---------------
	//
	case 't':
		Check_Object(Camera_State);
		if (
			Camera_State->GetMLRState().GetRenderPermissionMask()
			 & MidLevelRenderer::MLRState::TextureMask
		)
			Camera_State->DisableChildTextureHandleControl();
		else
			Camera_State->EnableChildTextureHandleControl();
		break;

	//
	//---------------
	// Wireframe mode
	//---------------
	//
	case 'w':
		Check_Object(Camera_State);
		temp = Camera_State->GetWireFrameMode() + 1;
		if (temp>ElementRenderer::StateChange::SolidAndWireMode)
			Camera_State->SetWireFrameMode(ElementRenderer::StateChange::SolidMode);
		else
			Camera_State->SetWireFrameMode(
				static_cast<ElementRenderer::StateChange::WireFrameMode>(temp)
			);
		break;

	//
	//-------------------
	// Zbuffer write mode
	//-------------------
	//
	case 'z':
		Check_Object(Camera_State);
		if (Camera_State->IsZBufferWriteEnabled())
			Camera_State->DisableZBufferWrite();
		else
			Camera_State->EnableZBufferWrite();
		break;
	}

	//
	//---------------
	// Read the mouse
	//---------------
	//
	static float
		last_x_pos,
		last_y_pos;
	float
		x_pos,
		y_pos;
	static bool
		first_time = true;
	int
		x_delta,
		y_delta;
	DWORD
		buttons;
	Stuff::Scalar
		x_speed = 0.0f,
		y_speed = 0.0f;
	gos_GetMouseInfo(&x_pos, &y_pos, &x_delta, &y_delta, NULL, &buttons);
	if (first_time)
		first_time = false;
	else
	{
		x_speed = x_pos - last_x_pos;
		y_speed = y_pos - last_y_pos;
	}
	last_x_pos = x_pos;
	last_y_pos = y_pos;

	//
	//------------------------------------------------------
	// If the left Ctrl button is held down, move the camera
	//------------------------------------------------------
	//
	gosEnum_KeyStatus status = gos_GetKeyStatus(KEY_LCONTROL);
	if (status != KEY_FREE && status != KEY_RELEASED)
	{
		Camera_Direction.pitch += y_speed;
		Camera_Direction.yaw -= x_speed;
	}

	//
	//------------------------------------------------------
	// If the left Shift button is held down, pan the camera
	//------------------------------------------------------
	//
	status = gos_GetKeyStatus(KEY_LSHIFT);
	if (status != KEY_FREE && status != KEY_RELEASED)
	{
		Camera_Shift.x += x_speed;
		Camera_Shift.y += y_speed;
	}

	//
	//-----------------------------------------------------
	// If the left Alt button is held down, zoom the camera
	//-----------------------------------------------------
	//
	status = gos_GetKeyStatus(KEY_LMENU);
	if (status != KEY_FREE && status != KEY_RELEASED)
	{
		Camera_Direction.range += y_speed;
	}

	//
	//----------------------
	// Set the camera matrix
	//----------------------
	//
	Stuff::LinearMatrix4D origin;
	origin.BuildRotation(
		Stuff::EulerAngles(Camera_Direction.pitch, Camera_Direction.yaw, 0.0f)
	);
	Stuff::UnitVector3D
		world_left,
		world_up;
	origin.GetLocalLeftInWorld(&world_left);
	origin.GetLocalUpInWorld(&world_up);
	Stuff::Point3D translation(Camera_Direction);
	translation.AddScaled(translation, world_left, Camera_Shift.x);
	translation.AddScaled(translation, world_up, Camera_Shift.y);
	origin.BuildTranslation(translation);
	Camera->SetLocalToParent(origin);

	//
	//-------------------------------------------------------
	// Now that we have moved everything, update the matrices
	//-------------------------------------------------------
	//
	Scene->Sync();

	//
	//------------------------
	// Now animate the effects
	//------------------------
	//
	Point3D end(
		Effect->GetLocalToWorld()(3,0) * xz.cosine,
		Effect->GetLocalToWorld()(3,1) * xz.cosine,
		Effect->GetLocalToWorld()(3,2) * xz.cosine
	);
	gosFX::Beam::ExecuteInfo
		info(
			gos_GetElapsedTime(),
			&Effect->GetLocalToWorld(),
			&end,
			&Effect->m_localOBB
		);
	#if EFFECT!=10
		if (Effect->GetEffect()->Execute(&info))
	#else
		if (Cast_Object(gosFX::Beam*, Effect->GetEffect())->Execute(&info))
	#endif
	{
		if (Effect->m_localOBB.sphereRadius > 0.0f)
			Effect->NeedNewBounds();
		else
			Effect->SetNeverCullMode();
	}
	else
		Effect->SetAlwaysCullMode();
	Effect->Sync();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void __stdcall UpdateDisplay()
{
	Camera->DrawScene();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void __stdcall TerminateGameEngine()
{
	//
	//-----------------
	// Delete the scene
	//-----------------
	//
	Check_Object(Scene);
	delete Scene;

	//
	//-------------------
	// Turn off libraries
	//-------------------
	//
	ElementRenderer::TerminateClasses();
	gosFX::TerminateClasses();
	MidLevelRenderer::TerminateClasses();
	Stuff::TerminateClasses();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void __stdcall GetGameOSEnvironment(char* CommandLine)
{
	Environment.applicationName			= "fart";
	Environment.version					= "";
//	Environment.directoryPath			= "code\\MW4Application";
	Environment.allowMultipleApps		= false;

	Environment.screenWidth				= 640;
	Environment.screenHeight			= 480;
	Environment.bitDepth				= 16;
	Environment.FullScreenDevice		= 0;
	Environment.Renderer				= 0;
	Environment.fullScreen				= false;
	Environment.disableZBuffer			= false;
	Environment.AntiAlias				= false;
	Environment.RenderToVram			= false;

	Environment.Key_FullScreen			= 0;
	Environment.Key_SwitchMonitors		= KEY_F5;
	Environment.Key_Exit				= 0;

	Environment.debugLog				= "ElementTest.txt";
	Environment.spew					= "GameOS_Memory";
	Environment.TimeStampSpew			= false;
	Environment.memoryTraceLevel		= 5;

	Environment.MaxTimeDelta			= 0.25f;
	Environment.MinimumTimeDelta		= 0.25f;

	Environment.soundDisable			= true;
	Environment.soundHiFi				= true;
	Environment.soundDevice				= 0;
	Environment.soundChannels			= 16;

	Environment.NetworkGame				= false;

	Environment.Texture_S_256			= 0;
	Environment.Texture_S_128			= 0;
	Environment.Texture_S_64			= 0;
	Environment.Texture_S_32			= 0;
	Environment.Texture_S_16			= 0;

	Environment.Texture_K_256			= 0;
	Environment.Texture_K_128			= 0;
	Environment.Texture_K_64			= 0;
	Environment.Texture_K_32			= 0;
	Environment.Texture_K_16			= 0;

	Environment.Texture_A_256			= 0;
	Environment.Texture_A_128			= 0;
	Environment.Texture_A_64			= 0;
	Environment.Texture_A_32			= 0;
	Environment.Texture_A_16			= 0;

	Environment.GetGameInformation		= NULL;
	Environment.InitializeGameEngine	= InitializeGameEngine;
	Environment.DoGameLogic				= DoGameLogic;
	Environment.UpdateRenderers			= UpdateDisplay;
	Environment.TerminateGameEngine		= TerminateGameEngine;
}
