#include "ElementRendererHeaders.hpp"

//############################################################################
//############################  LODElement  ################################
//############################################################################

HGOSHEAP
	ElementRenderer::LODElement::s_Heap = NULL;

ElementRenderer::LODElement::ClassData*
	ElementRenderer::LODElement::DefaultData = NULL;

ElementRenderer::Element::DrawMethod
	ElementRenderer::LODElement::DrawMethods[DrawStateCount]=
{
	DRAW_METHOD(LODElement,	Inherit),
	DRAW_METHOD(LODElement,	Override)
};

Stuff::Scalar ElementRenderer::LODElement::s_Offset = 0.0f;
bool ElementRenderer::LODElement::s_ShadeLODs = false;
Stuff::RGBAColor ElementRenderer::LODElement::s_Shades[6]={
	Stuff::RGBAColor(1.0f, 1.0f, 1.0f, 1.0),
	Stuff::RGBAColor(1.0f, 0.0f, 0.0f, 1.0),
	Stuff::RGBAColor(0.0f, 1.0f, 0.0f, 1.0),
	Stuff::RGBAColor(0.0f, 0.0f, 1.0f, 1.0),
	Stuff::RGBAColor(1.0f, 1.0f, 0.0f, 1.0),
	Stuff::RGBAColor(1.0f, 0.0f, 1.0f, 1.0)
};

static bool
	__stdcall Check100m()
{
	return ElementRenderer::LODElement::s_Offset == 625.0f;
}

static bool
	__stdcall Check250m()
{
	return ElementRenderer::LODElement::s_Offset == 2500.0f;
}

static bool
	__stdcall Check500m()
{
	return ElementRenderer::LODElement::s_Offset == 10000.0f;
}

static void
	__stdcall Enable100m()
{
	ElementRenderer::LODElement::s_Offset = (ElementRenderer::LODElement::s_Offset == 625.0f) ? 0.0f : 625.0f;
}

static void
	__stdcall Enable250m()
{
	ElementRenderer::LODElement::s_Offset = (ElementRenderer::LODElement::s_Offset == 2500.0f) ? 0.0f : 2500.0f;
}

static void
	__stdcall Enable500m()
{
	ElementRenderer::LODElement::s_Offset = (ElementRenderer::LODElement::s_Offset == 10000.0f) ? 0.0f : 10000.0f;
}

extern DWORD Disable_Lights;

static bool __stdcall CheckShadeLODs() {return ElementRenderer::LODElement::s_ShadeLODs;}
static void __stdcall EnableShadeLODs()
{
	ElementRenderer::LODElement::s_ShadeLODs = !ElementRenderer::LODElement::s_ShadeLODs;
	if (ElementRenderer::LODElement::s_ShadeLODs)
		Disable_Lights = true;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::LODElement::InitializeClass(Stuff::NotationFile *startup_ini)
{
	Verify(!s_Heap);
	s_Heap = gos_CreateMemoryHeap("LOD", 0, g_LibraryHeap);
	Check_Pointer(s_Heap);

	Verify(!DefaultData);
	DefaultData =
		new ClassData(
			LODElementClassID,
			"ElementRenderer::LODElement",
			BaseClass::DefaultData,
			reinterpret_cast<Factory>(&Make)
		);
	Register_Object(DefaultData);

	AddDebuggerMenuItem("Libraries\\ElementRenderer\\Shade LODs", CheckShadeLODs, EnableShadeLODs, 0 );
	AddDebuggerMenuItem("Libraries\\Graphics Options\\+25m Shape LODs", Check100m, Enable100m, 0 );
	AddDebuggerMenuItem("Libraries\\Graphics Options\\+50m Shape LODs", Check250m, Enable250m, 0 );
	AddDebuggerMenuItem("Libraries\\Graphics Options\\+100m Shape LODs", Check500m, Enable500m, 0 );

	if (startup_ini)
	{
		Check_Object(startup_ini);
		Stuff::Page *page = startup_ini->FindPage("Graphics Options");
		if (page)
		{
			Check_Object(page);
			page->GetEntry("LOD", &s_Offset);
		}
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::LODElement::TerminateClass(Stuff::NotationFile *startup_ini)
{
	if (startup_ini)
	{
		Check_Object(startup_ini);
		Stuff::Page *page = startup_ini->SetPage("Graphics Options");
		Check_Object(page);
		page->SetEntry("LOD", s_Offset);
	}

	Unregister_Object(DefaultData);
	delete DefaultData;
	DefaultData = NULL;

	Check_Pointer(s_Heap);
	gos_DestroyMemoryHeap(s_Heap);
	s_Heap = NULL;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ElementRenderer::LODElement::LODElement(
	Stuff::MemoryStream *stream,
	int version,
	ShapeHolder shapes
):
	SwitchElement(DefaultData, stream, version, shapes)
{
	Check_Pointer(this);
	Check_Object(stream);

	unsigned index = (m_state>>DrawStateBit) & DrawStateMask;
	Verify(index < DrawStateCount);
	m_draw = DrawMethods[index];

	//
	//---------------------------------
	// Create the shape from the stream
	//---------------------------------
	//
	Verify(m_list.GetLength() < 65536);
	WORD lod_count = static_cast<WORD>(m_list.GetLength());
	m_LODs.SetLength(lod_count);
	for (WORD lod=0; lod<lod_count; ++lod)
		*stream >> m_LODs[lod].m_nearSquared >> m_LODs[lod].m_farSquared;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ElementRenderer::LODElement::LODElement():
	SwitchElement(DefaultData)
{
	Check_Pointer(this);

	unsigned index = (m_state>>DrawStateBit) & DrawStateMask;
	Verify(index < DrawStateCount);
	m_draw = DrawMethods[index];
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ElementRenderer::LODElement::~LODElement()
{
	Check_Object(this);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ElementRenderer::LODElement*
	ElementRenderer::LODElement::Make(
		Stuff::MemoryStream *stream,
		int version,
		ShapeHolder shapes
	)
{
	Check_Object(stream);

	gos_PushCurrentHeap(s_Heap);
	LODElement *element = new LODElement(stream, version, shapes);
	gos_PopCurrentHeap();

	return element;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::LODElement::Save(Stuff::MemoryStream *stream)
{
	Check_Object(this);
	Check_Object(stream);
	BaseClass::Save(stream);

	//
	//--------------------
	// Save each LOD entry
	//--------------------
	//
	Verify(m_list.GetLength() < 65536);
	WORD lod_count = static_cast<WORD>(m_LODs.GetLength());
	for (WORD lod=0; lod<lod_count; ++lod)
		*stream << m_LODs[lod].m_nearSquared << m_LODs[lod].m_farSquared;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::LODElement::TestInstance()
{
	Verify(IsDerivedFrom(DefaultData));
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::LODElement::AttachLOD(
		WORD index,
		Element *element,
		const Entry &entry
	)
{
	Check_Object(this);
	Verify(index < m_LODs.GetLength());
	m_LODs[index] = entry;
	AttachIndexedChild(index, element);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::LODElement::SetSize(WORD max_size)
{
	Check_Object(this);
	m_LODs.SetLength(max_size);
	for (WORD i=0; i<max_size; ++i)
	{
		m_LODs[i].m_nearSquared = 0.0f;
		m_LODs[i].m_farSquared = 0.0f;
	}
	BaseClass::SetSize(max_size);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::LODElement::DetachChild(Element *child)
{
	Check_Object(this);
	Check_Object(child);

	//
	//-------------------------------
	// Find the element and unhook it
	//-------------------------------
	//
	Verify(m_list.GetLength() < 65536);
	WORD lod_count = static_cast<WORD>(m_LODs.GetLength());
	for (WORD i=0; i<lod_count; ++i)
	{
		Entry *entry = &m_LODs[i];
		if (m_list[i] == child)
		{
			entry->m_nearSquared = 0.0f;
			entry->m_farSquared = 0.0f;
			break;
		}
	}
	BaseClass::DetachChild(child);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::LODElement::SetDrawState()
{
	Check_Object(this);
	unsigned index = (m_state>>DrawStateBit) & DrawStateMask;
	Verify(index < DrawStateCount);
	m_draw = DrawMethods[index];
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::LODElement::InheritDrawMethod(
		CameraElement *camera,
		const StateChange *inherited_state,
		int culling_state
	)
{
	Check_Object(this);
	Check_Object(inherited_state);
	Check_Object(camera);
	Verify(culling_state != -1);

	//
	//--------------------------------------
	// Draw our bounds if we are supposed to
	//--------------------------------------
	//
	#if defined(LAB_ONLY)
		Callback *callback = GetCallbackSet();
		unsigned index = GetCallbackIndex();
		if (callback && callback[index])
		{
			Check_Pointer(callback[index]);
			(*callback[index])(camera, inherited_state, culling_state, this);
		}
	#endif

	//
	//----------------------------------------------------------------------
	// We need to find out which lod the camera should traverse into, so
	// transform the camera into LOD space and get the distance from the LOD
	//----------------------------------------------------------------------
	//
	Stuff::Point3D camera_origin(camera->GetLocalToWorld());
	Stuff::Point3D lod_origin(GetLocalToWorld());
	camera_origin -= lod_origin;
	Stuff::Scalar distance = camera_origin.GetLengthSquared() + s_Offset;
	Min_Clamp(distance, 0.0f);

	//
	//---------------------------------------------------
	// Search the lod entries looking for the first match
	//---------------------------------------------------
	//
	for (WORD i=0; i<m_LODs.GetLength(); ++i)
	{
		Entry *entry = &m_LODs[i];
		if (entry->m_nearSquared <= distance && entry->m_farSquared >= distance)
		{
			#if defined(LAB_ONLY)
				Stuff::RGBAColor old_fade;
				if (s_ShadeLODs && i<ELEMENTS(s_Shades))
				{
					old_fade = FadeColor;
					FadeColor = s_Shades[i];
				}
			#endif
			Element *element = m_list[i];
			Check_Object(element);
			SetSwitch(i);
			camera->DrawElement(element, inherited_state);
			#if defined(LAB_ONLY)
				if (s_ShadeLODs && i<ELEMENTS(s_Shades))
					FadeColor = old_fade;
			#endif
			break;
		}
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::LODElement::OverrideDrawMethod(
		CameraElement *camera,
		const StateChange *inherited_state,
		int culling_state
	)
{
	Check_Object(this);
	Check_Object(inherited_state);
	Check_Object(camera);
	Verify(culling_state != -1);

	//
	//-------------------
	// Mix the properties
	//-------------------
	//
	Check_Object(m_stateChange);
	StateChange mixed;
	mixed.Mix(*inherited_state, *m_stateChange);
	MixLights(
		mixed.m_lights,
		inherited_state->m_lights,
		m_stateChange->m_lights
	);

	//
	//--------------------------------------
	// Draw our bounds if we are supposed to
	//--------------------------------------
	//
	#if defined(LAB_ONLY)
		Callback *callback = GetCallbackSet();
		unsigned index = GetCallbackIndex();
		if (callback && callback[index])
		{
			Check_Pointer(callback[index]);
			(*callback[index])(camera, inherited_state, culling_state, this);
		}
	#endif

	//
	//----------------------------------------------------------------------
	// We need to find out which lod the camera should traverse into, so
	// transform the camera into LOD space and get the distance from the LOD
	//----------------------------------------------------------------------
	//
	Stuff::Point3D camera_origin(camera->GetLocalToWorld());
	Stuff::Point3D lod_origin(GetLocalToWorld());
	camera_origin -= lod_origin;
	Stuff::Scalar distance = camera_origin.GetLengthSquared() + s_Offset;
	Min_Clamp(distance, 0.0f);

	//
	//---------------------------------------------------
	// Search the lod entries looking for the first match
	//---------------------------------------------------
	//
	for (WORD i=0; i<m_LODs.GetLength(); ++i)
	{
		Entry *entry = &m_LODs[m_switch];
		if (entry->m_nearSquared <= distance && entry->m_farSquared >= distance)
		{
			#if defined(LAB_ONLY)
				Stuff::RGBAColor old_fade;
				if (s_ShadeLODs && i<ELEMENTS(s_Shades))
				{
					old_fade = FadeColor;
					FadeColor = s_Shades[i];
				}
			#endif
			Element *element = m_list[m_switch];
			Check_Object(element);
			SetSwitch(i);
			camera->DrawElement(element, &mixed);
			#if defined(LAB_ONLY)
				if (s_ShadeLODs && i<ELEMENTS(s_Shades))
					FadeColor = old_fade;
			#endif
			break;
		}
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::LODElement::CleanDamage()
{
	Check_Object(this);

	for(WORD i=0;i<GetActiveCount();i++)
	{
		Element *child = GetIndexedElement(i);
		Check_Object(child);
		child->CleanDamage();
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::LODElement::ApplyDamage(
		const Stuff::LinearMatrix4D &damage_spot,
		Stuff::Scalar radius
	)
{
	Check_Object(this);

	for(WORD i=0;i<GetActiveCount();i++)
	{
		Element *child = GetIndexedElement(i);
		Check_Object(child);
		child->ApplyDamage(damage_spot, radius);
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::LODElement::ApplyDamageDecal(
		const Stuff::LinearMatrix4D &damage_spot,
		Stuff::Scalar radius,
		MidLevelRenderer::MLRTexture *texture
	)
{
	Check_Object(this);
	Check_Object(texture);

	for(WORD i=0;i<GetActiveCount();i++)
	{
		Element *child = GetIndexedElement(i);
		Check_Object(child);
		child->ApplyDamageDecal(damage_spot, radius, texture);
	}
}
