#include "ElementRendererHeaders.hpp"

//############################################################################
//############################  ListElement  ################################
//############################################################################

HGOSHEAP
	ElementRenderer::ListElement::s_Heap = NULL;

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

ElementRenderer::Element::SyncMethod
	ElementRenderer::ListElement::SyncMethods[SyncStateCount]=
{
	//
	// Root mode
	//
	SYNC_METHOD(ListElement,	CleanRoot),
	SYNC_METHOD(ListElement,	DirtyRoot),
	SYNC_METHOD(ListElement,	CleanRoot),
	SYNC_METHOD(ListElement,	DirtyRoot),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		Defer),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		Defer),
	SYNC_METHOD(ListElement,	CleanRoot),
	SYNC_METHOD(ListElement,	DirtyRoot),
	SYNC_METHOD(ListElement,	CleanRoot),
	SYNC_METHOD(ListElement,	DirtyRoot),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		Defer),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		Defer),

	//
	// Sphere Cull
	//
	SYNC_METHOD(ListElement,	CleanSphere),
	SYNC_METHOD(ListElement,	MatrixDirtySphere),
	SYNC_METHOD(ListElement,	BoundsWrongSphere),
	SYNC_METHOD(ListElement,	FullSphere),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		Defer),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		Defer),

	//
	// OBB Cull
	//
	SYNC_METHOD(ListElement,	CleanOBB),
	SYNC_METHOD(ListElement,	MatrixDirtyOBB),
	SYNC_METHOD(ListElement,	BoundsWrongOBB),
	SYNC_METHOD(ListElement,	FullOBB),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		Defer),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		Defer),

	//
	// Never Cull
	//
	SYNC_METHOD(ListElement,	Clean),
	SYNC_METHOD(ListElement,	Dirty),
	SYNC_METHOD(ListElement,	Clean),
	SYNC_METHOD(ListElement,	Dirty),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		Defer),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		Defer),
	SYNC_METHOD(ListElement,	Clean),
	SYNC_METHOD(ListElement,	Dirty),
	SYNC_METHOD(ListElement,	Clean),
	SYNC_METHOD(ListElement,	Dirty),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		Defer),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		Defer),

	//
	// Always Cull
	//
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		None)
};

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::ListElement::InitializeClass()
{
	Verify(!s_Heap);
	s_Heap = gos_CreateMemoryHeap("List", 0, g_LibraryHeap);
	Check_Pointer(s_Heap);

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::ListElement::TerminateClass()
{
	Unregister_Object(DefaultData);
	delete DefaultData;
	DefaultData = NULL;

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ElementRenderer::ListElement::ListElement(
	ClassData *class_data,
	Stuff::MemoryStream *stream,
	int version,
	ShapeHolder shapes,
	bool load_children
):
	Element(class_data, stream, version)
{
	Check_Pointer(this);
	Check_Object(stream);

	unsigned index = (m_state>>SyncStateBit) & SyncStateMask;
	Verify(index < SyncStateCount);
	m_sync = SyncMethods[index];
	index = (m_state>>CullStateBit) & CullStateMask;
	Verify(index < CullStateCount);
	m_cameraCull = CullMethods[index].m_cameraCull;
	m_rayCull = CullMethods[index].m_rayCull;
	m_sphereTest = CullMethods[index].m_sphereTest;
	index = (m_state>>DrawStateBit) & DrawStateMask;
	Verify(index < DrawStateCount);
	m_draw = DrawMethods[index];

	//
	//---------------------------------
	// Create the shape from the stream
	//---------------------------------
	//
	if (load_children)
	{
		if (version >= 11)
			*stream >> m_activeChildren;
		else
		{
			int count;
			*stream >> count;
			Verify(static_cast<unsigned>(count) < 65536);
			m_activeChildren = static_cast<WORD>(count);
		}
		m_list.SetLength(m_activeChildren);
		for (WORD s=0; s<m_activeChildren; ++s)
		{
			m_list[s] = Create(stream, version);
			Element *child = m_list[s];
			SetParentElement(child, this);
			if (child->GetCullMode() != AlwaysCullMode)
			{
				if (child->m_localOBB.sphereRadius > 0.0f || child->AreBoundsWrong())
					child->SetVolumeCullMode();
				else
					child->SetNeverCullMode();
			}
		}
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ElementRenderer::ListElement::ListElement(ClassData *class_data):
	Element(class_data)
{
	Check_Pointer(this);

	m_activeChildren = 0;
	unsigned index = (m_state>>SyncStateBit) & SyncStateMask;
	Verify(index < SyncStateCount);
	m_sync = SyncMethods[index];
	index = (m_state>>CullStateBit) & CullStateMask;
	Verify(index < CullStateCount);
	m_cameraCull = CullMethods[index].m_cameraCull;
	m_rayCull = CullMethods[index].m_rayCull;
	m_sphereTest = CullMethods[index].m_sphereTest;
	index = (m_state>>DrawStateBit) & DrawStateMask;
	Verify(index < DrawStateCount);
	m_draw = DrawMethods[index];
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ElementRenderer::ListElement::ListElement(
	GroupElement *group,
	ClassData *class_data
):
	Element(class_data, group)
{
	Check_Pointer(this);

	//
	//-------------------------
	// Set up the state methods
	//-------------------------
	//
	unsigned index = (m_state>>SyncStateBit) & SyncStateMask;
	Verify(index < SyncStateCount);
	m_sync = SyncMethods[index];
	index = (m_state>>CullStateBit) & CullStateMask;
	Verify(index < CullStateCount);
	m_cameraCull = CullMethods[index].m_cameraCull;
	m_rayCull = CullMethods[index].m_rayCull;
	m_sphereTest = CullMethods[index].m_sphereTest;
	index = (m_state>>DrawStateBit) & DrawStateMask;
	Verify(index < DrawStateCount);
	m_draw = DrawMethods[index];

	//
	//--------------------------
	// Figure out the group size
	//--------------------------
	//
	Stuff::ChainIteratorOf<Element*> *iterator = group->MakeIterator();
	Register_Object(iterator);
	m_activeChildren = static_cast<WORD>(iterator->GetSize());
	m_list.SetLength(m_activeChildren);

	//
	//------------------
	// Fill in the array
	//------------------
	//
	iterator->First();
	for (WORD i=0; i<m_activeChildren; ++i)
	{
		Element *child = iterator->ReadAndNext();
		Check_Object(child);
		group->DetachChild(child);
		AttachIndexedChild(i, child);
	}
	Unregister_Object(iterator);
	delete iterator;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ElementRenderer::ListElement::~ListElement()
{
	Check_Object(this);
	Verify(m_list.GetLength() < 65536);
	WORD child_count = static_cast<WORD>(m_list.GetLength());
	for (WORD s=0; s<child_count; ++s)
	{
		Element *element = m_list[s];
		if (element)
		{
			Unregister_Object(element);
			delete element;
		}
	}
}

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

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

	return element;
}

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

	//
	//--------------------
	// Save each Switch entry
	//--------------------
	//
	WORD child_count = (WORD)m_list.GetLength();
	*stream << child_count;
	for (WORD i=0; i<child_count; ++i)
	{
		Check_Object(m_list[i]);
		m_list[i]->Save(stream);
	}
}

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::ListElement::AttachIndexedChild(
		WORD index,
		Element *element
	)
{
	Check_Object(this);
	Verify(index < m_list.GetLength());
	m_list[index] = element;
	SetParentElement(element, this);
	if (element->GetCullMode() != AlwaysCullMode)
	{
		if (element->m_localOBB.sphereRadius > 0.0f || element->AreBoundsWrong())
			element->SetVolumeCullMode();
		else
			element->SetNeverCullMode();
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::ListElement::SetSize(WORD max_size)
{
	Check_Object(this);
	m_list.SetLength(max_size);
	m_activeChildren = max_size;
	for (WORD i=0; i<max_size; ++i)
		m_list[i] = NULL;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::ListElement::ReSize(WORD max_size)
{
	Check_Object(this);
	m_list.SetLength(max_size);
	m_activeChildren = max_size;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::ListElement::AttachChild(Element *child)
{
	Check_Object(this);
	Check_Object(child);
	STOP(("List elements can't have dynamic children!"));
}

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

	//
	//-------------------------------
	// Find the element and unhook it
	//-------------------------------
	//
	Verify(m_list.GetLength() < 65536);
	WORD child_count = static_cast<WORD>(m_list.GetLength());
	for (WORD i=0; i<child_count; ++i)
	{
		if (m_list[i] == child)
		{
			m_list[i] = NULL;
			break;
		}
	}
	child->SetRootMode();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::ListElement::SetSyncState()
{
	Check_Object(this);

	Verify(
		GetCullMode() != VolumeCullMode
		 || AreBoundsWrong()
		 || IsSyncDeferred()
		 || (
				m_localOBB.sphereRadius>0.0f
				 && (IsMatrixDirty() || m_worldOBB.sphereRadius>0.0f
			)
		)
	);
	Verify(
		GetCullMode() == RootMode || m_parent != NULL || AreBoundsWrong()
	);
	unsigned index = (m_state>>SyncStateBit) & SyncStateMask;
	Verify(index < SyncStateCount);
	m_sync = SyncMethods[index];
}

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::ListElement::CleanRootSyncMethod()
{
	Check_Object(this);
	Verify(!IsMatrixDirty() && GetCullMode()==RootMode);
	SYNC_LOGIC("Root::Clean::List");

	//
	//--------------------
	// Update our children
	//--------------------
	//
	for (WORD i=0; i<m_activeChildren; ++i)
	{
		Element *element = m_list[i];
		Check_Object(element);
		element->Sync();
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::ListElement::DirtyRootSyncMethod()
{
	Check_Object(this);
	Verify(IsMatrixDirty() && GetCullMode()==RootMode);
	SYNC_LOGIC("Root::Dirty::List");

	//
	//------------------------------------------------------
	// If the local-to-parent matrix is dirty, make it clean
	//------------------------------------------------------
	//
	m_localToWorld = m_localToParent = m_newLocalToParent;
	MatrixIsClean();
	CleanRootSyncMethod();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::ListElement::CleanSyncMethod()
{
	Check_Object(this);
	Verify(!IsMatrixDirty() && GetCullMode()==NeverCullMode);
	SYNC_LOGIC("Clean::List");

	//
	//-------------------------------
	// Update our m_localToWorld matrix
	//-------------------------------
	//
	Check_Object(m_parent);
	if (!IsLocalToParentIdentity())
		m_localToWorld.Multiply(m_localToParent, m_parent->GetLocalToWorld());
	else
		m_localToWorld = m_parent->GetLocalToWorld();

	//
	//--------------------
	// Update our children
	//--------------------
	//
	for (WORD i=0; i<m_activeChildren; ++i)
	{
		Element *element = m_list[i];
		Check_Object(element);
		element->Sync();
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::ListElement::DirtySyncMethod()
{
	Check_Object(this);
	Verify(IsMatrixDirty() && GetCullMode()==NeverCullMode);
	SYNC_LOGIC("Dirty::List");

	//
	//--------------------
	// Update our matrices
	//--------------------
	//
	m_localToParent = m_newLocalToParent;
	Check_Object(m_parent);
	if (!IsLocalToParentIdentity())
		m_localToWorld.Multiply(m_localToParent, m_parent->GetLocalToWorld());
	else
		m_localToWorld = m_parent->GetLocalToWorld();

	//
	//--------------------
	// Update our children
	//--------------------
	//
	for (WORD i=0; i<m_activeChildren; ++i)
	{
		Element *element = m_list[i];
		Check_Object(element);
		element->Sync();
	}
	MatrixIsClean();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::ListElement::CleanSphereSyncMethod()
{
	Check_Object(this);
	Verify(m_localOBB.sphereRadius > 0.0f);
	Verify(
		!IsMatrixDirty() && GetCullMode()==VolumeCullMode && IsBoundedBySphere()
	);
	SYNC_LOGIC("Sphere::Clean::List");

	//
	//-------------------------------
	// Update our m_localToWorld matrix
	//-------------------------------
	//
	Check_Object(m_parent);
	if (!IsLocalToParentIdentity())
		m_localToWorld.Multiply(m_localToParent, m_parent->GetLocalToWorld());
	else
		m_localToWorld = m_parent->GetLocalToWorld();

	//
	//------------------------------
	// Transform the bounding sphere
	//------------------------------
	//
	m_worldOBB.MultiplySphereOnly(m_localOBB, m_localToWorld);

	//
	//--------------------
	// Update our children
	//--------------------
	//
	for (WORD i=0; i<m_activeChildren; ++i)
	{
		Element *element = m_list[i];
		Check_Object(element);
		element->Sync();
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::ListElement::MatrixDirtySphereSyncMethod()
{
	Check_Object(this);
	Verify(m_localOBB.sphereRadius > 0.0f);
	Verify(
		IsMatrixDirty() && GetCullMode()==VolumeCullMode && IsBoundedBySphere()
	);
	SYNC_LOGIC("Sphere::Dirty::List");

	//
	//--------------------
	// Update our matrices
	//--------------------
	//
	m_localToParent = m_newLocalToParent;
	Check_Object(m_parent);
	if (!IsLocalToParentIdentity())
		m_localToWorld.Multiply(m_localToParent, m_parent->GetLocalToWorld());
	else
		m_localToWorld = m_parent->GetLocalToWorld();

	//
	//------------------------------
	// Transform the bounding sphere
	//------------------------------
	//
	m_worldOBB.MultiplySphereOnly(m_localOBB, m_localToWorld);
	MatrixIsClean();

	//
	//--------------------
	// Update our children
	//--------------------
	//
	for (WORD i=0; i<m_activeChildren; ++i)
	{
		Element *element = m_list[i];
		Check_Object(element);
		element->Sync();
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::ListElement::BoundsWrongSphereSyncMethod()
{
	Check_Object(this);
	Verify(
		!IsMatrixDirty() && AreBoundsWrong()
		 && GetCullMode()==VolumeCullMode && IsBoundedBySphere()
	);
	SYNC_LOGIC("Sphere::RecalcBounds::List");

	//
	//-------------------------------
	// Update our m_localToWorld matrix
	//-------------------------------
	//
	Check_Object(m_parent);
	if (!IsLocalToParentIdentity())
		m_localToWorld.Multiply(m_localToParent, m_parent->GetLocalToWorld());
	else
		m_localToWorld = m_parent->GetLocalToWorld();

	//
	//--------------------
	// Update our children
	//--------------------
	//
	WORD i;
	Stuff::Point3D centroid = Stuff::Point3D::Identity;
	WORD bounded_children = 0;
	for (i=0; i<m_activeChildren; ++i)
	{
		Element *element = m_list[i];
		Check_Object(element);
		element->Sync();
		if (element->GetCullMode() == VolumeCullMode)
		{
			++bounded_children;
			Stuff::LinearMatrix4D child_to_parent = element->GetLocalToParent();
			Stuff::Point3D child_center(element->m_localOBB.localToParent);
			Stuff::Point3D child_center_in_parent;
			child_center_in_parent.Multiply(child_center, child_to_parent);
			centroid += child_center_in_parent;
		}
	}

	//
	//---------------------------------------------------
	// If we have no bounded children, we will never cull
	//---------------------------------------------------
	//
	if (!bounded_children)
	{
		m_localOBB.sphereRadius = 0.0f;
		SetNeverCullMode();
	}

	//
	//-----------------------------------------------------------------
	// Otherwise, find the tightest bounds from our calculated centroid
	//-----------------------------------------------------------------
	//
	else
	{
		centroid /= static_cast<Stuff::Scalar>(bounded_children);
		m_localOBB.localToParent.BuildTranslation(centroid);
		m_localOBB.sphereRadius = -1.0f;
		for (i=0; i<m_activeChildren; ++i)
		{
			Element *element = m_list[i];
			Check_Object(element);
			if (element->GetCullMode() == VolumeCullMode)
			{
				Stuff::LinearMatrix4D child_to_parent = element->GetLocalToParent();
				Stuff::Point3D child_center(element->m_localOBB.localToParent);
				Stuff::Point3D child_center_in_parent;
				child_center_in_parent.Multiply(child_center, child_to_parent);
				child_center_in_parent -= centroid;
				Stuff::Scalar range =
					child_center_in_parent.GetLength()
					 + element->m_localOBB.sphereRadius;
				if (range > m_localOBB.sphereRadius)
					m_localOBB.sphereRadius = range;
			}
		}
		Verify(m_localOBB.sphereRadius > 0.0f);
		m_worldOBB.MultiplySphereOnly(m_localOBB, m_localToWorld);
	}
	BoundsAreRight();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::ListElement::FullSphereSyncMethod()
{
	Check_Object(this);
	Verify(
		IsMatrixDirty() && AreBoundsWrong()
		 && GetCullMode()==VolumeCullMode && IsBoundedBySphere()
	);
	SYNC_LOGIC("Sphere::Full::List");

	//
	//--------------------
	// Update our matrices
	//--------------------
	//
	m_localToParent = m_newLocalToParent;
	Check_Object(m_parent);
	if (!IsLocalToParentIdentity())
		m_localToWorld.Multiply(m_localToParent, m_parent->GetLocalToWorld());
	else
		m_localToWorld = m_parent->GetLocalToWorld();

	//
	//--------------------
	// Update our children
	//--------------------
	//
	WORD i;
	Stuff::Point3D centroid = Stuff::Point3D::Identity;
	WORD bounded_children = 0;
	for (i=0; i<m_activeChildren; ++i)
	{
		Element *element = m_list[i];
		Check_Object(element);
		element->Sync();
		if (element->GetCullMode() == VolumeCullMode)
		{
			++bounded_children;
			Stuff::LinearMatrix4D child_to_parent = element->GetLocalToParent();
			Stuff::Point3D child_center(element->m_localOBB.localToParent);
			Stuff::Point3D child_center_in_parent;
			child_center_in_parent.Multiply(child_center, child_to_parent);
			centroid += child_center_in_parent;
		}
	}

	//
	//---------------------------------------------------
	// If we have no bounded children, we will never cull
	//---------------------------------------------------
	//
	if (!bounded_children)
	{
		m_localOBB.sphereRadius = 0.0f;
		SetNeverCullMode();
	}

	//
	//-----------------------------------------------------------------
	// Otherwise, find the tightest bounds from our calculated centroid
	//-----------------------------------------------------------------
	//
	else
	{
		centroid /= static_cast<Stuff::Scalar>(bounded_children);
		m_localOBB.localToParent.BuildTranslation(centroid);
		m_localOBB.sphereRadius = -1.0f;
		for (i=0; i<m_activeChildren; ++i)
		{
			Element *element = m_list[i];
			Check_Object(element);
			if (element->GetCullMode() == VolumeCullMode)
			{
				Stuff::LinearMatrix4D child_to_parent = element->GetLocalToParent();
				Stuff::Point3D child_center(element->m_localOBB.localToParent);
				Stuff::Point3D child_center_in_parent;
				child_center_in_parent.Multiply(child_center, child_to_parent);
				child_center_in_parent -= centroid;
				Stuff::Scalar range =
					child_center_in_parent.GetLength()
					 + element->m_localOBB.sphereRadius;
				if (range > m_localOBB.sphereRadius)
					m_localOBB.sphereRadius = range;
			}
		}
		Verify(m_localOBB.sphereRadius > 0.0f);
		m_worldOBB.MultiplySphereOnly(m_localOBB, m_localToWorld);
	}
	m_state &= ~(MatrixDirtyFlag|BoundsWrongFlag);
	SetSyncState();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::ListElement::CleanOBBSyncMethod()
{
	Check_Object(this);
	Verify(
		!IsMatrixDirty() && GetCullMode()==VolumeCullMode && IsBoundedByOBB()
	);
	Verify(Stuff::Close_Enough(m_localOBB.sphereRadius, m_localOBB.axisExtents.GetLength(), m_localOBB.sphereRadius*0.02f));
	SYNC_LOGIC("OBB::Clean::List");

	//
	//--------------------
	// Update our matrices
	//--------------------
	//
	m_localToParent = m_newLocalToParent;
	Check_Object(m_parent);
	if (!IsLocalToParentIdentity())
		m_localToWorld.Multiply(m_localToParent, m_parent->GetLocalToWorld());
	else
		m_localToWorld = m_parent->GetLocalToWorld();

	//
	//------------------------------
	// Transform the bounding sphere
	//------------------------------
	//
	m_worldOBB.Multiply(m_localOBB, m_localToWorld);
	MatrixIsClean();

	//
	//--------------------
	// Update our children
	//--------------------
	//
	for (WORD i=0; i<m_activeChildren; ++i)
	{
		Element *element = m_list[i];
		Check_Object(element);
		element->Sync();
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::ListElement::MatrixDirtyOBBSyncMethod()
{
	Check_Object(this);
	Verify(
		IsMatrixDirty() && GetCullMode()==VolumeCullMode && IsBoundedByOBB()
	);
	Verify(Stuff::Close_Enough(m_localOBB.sphereRadius, m_localOBB.axisExtents.GetLength(), m_localOBB.sphereRadius*0.02f));
	SYNC_LOGIC("OBB::Dirty::List");

	//
	//--------------------
	// Update our matrices
	//--------------------
	//
	m_localToParent = m_newLocalToParent;
	Check_Object(m_parent);
	if (!IsLocalToParentIdentity())
		m_localToWorld.Multiply(m_localToParent, m_parent->GetLocalToWorld());
	else
		m_localToWorld = m_parent->GetLocalToWorld();

	//
	//------------------------------
	// Transform the bounding sphere
	//------------------------------
	//
	m_worldOBB.Multiply(m_localOBB, m_localToWorld);
	MatrixIsClean();

	//
	//--------------------
	// Update our children
	//--------------------
	//
	for (WORD i=0; i<m_activeChildren; ++i)
	{
		Element *element = m_list[i];
		Check_Object(element);
		element->Sync();
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::ListElement::BoundsWrongOBBSyncMethod()
{
	Check_Object(this);
	STOP(("OBB's cannot recalculate bounds!"));
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::ListElement::FullOBBSyncMethod()
{
	Check_Object(this);
	STOP(("OBB's cannot recalculate bounds!"));
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
bool
	ElementRenderer::ListElement::CastCulledRay(CollisionQuery *query)
{
	Check_Object(this);
	Check_Object(query);
	Verify(!query->m_data);

	//
	//----------------------
	// Test all the children
	//----------------------
	//
	bool result = false;
	void *client_data = m_data;
	for (WORD i=0; i<m_activeChildren; ++i)
	{
		Element *element = m_list[i];
		Check_Object(element);
		if (element->CastRay(query))
		{
			result = true;
			if (query->m_data)
			{
				client_data = query->m_data;
				query->m_data = NULL;
			}
		}
	}

	//
	//-----------------------------------------------------------------------
	// If we hit something, set the client data if it hasn't already been set
	//-----------------------------------------------------------------------
	//
	if (result)
		query->m_data = client_data;
	else
		Verify(!query->m_data);
	return result;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ElementRenderer::Element*
	ElementRenderer::ListElement::FindSmallestElementContainingCulled(SphereTest *test)
{
	Check_Object(this);
	Check_Object(test);

	//
	//------------------
	// Draw our children
	//------------------
	//
	Element *result = this;
	for (WORD i=0; i<m_activeChildren; ++i)
	{
		Element *element = m_list[i];
		Check_Object(element);
		Element *temp = element->FindSmallestElementContaining(test);
		if (temp)
			result = temp;
	}
	return result;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::ListElement::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

	//
	//----------------------
	// Draw all the children
	//----------------------
	//
	for (WORD i=0; i<m_activeChildren; ++i)
	{
		Element *element = m_list[i];
		Check_Object(element);
		camera->DrawElement(element, inherited_state);
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::ListElement::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

	//
	//----------------------
	// Draw all the children
	//----------------------
	//
	for (WORD i=0; i<m_activeChildren; ++i)
	{
		Element *element = m_list[i];
		Check_Object(element);
		camera->DrawElement(element, &mixed);
	}
}

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

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::ListElement::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::ListElement::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);
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::ListElement::CastShadow(
		const Stuff::LinearMatrix4D &shadow_to_world,
		const Stuff::UnitVector3D &sun_in_world,
		Stuff::Scalar radius
	)
{
	Check_Object(this);

	if (!IsShadowed())
		return;
	for(WORD i=0;i<GetActiveCount();i++)
	{
		Element *child = GetIndexedElement(i);
		Check_Object(child);
		child->CastShadow(shadow_to_world, sun_in_world, radius);
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::ListElement::MakeFootStep(
		MidLevelRenderer::MLRShape *shape,
		const Stuff::LinearMatrix4D &foot,
		Stuff::Scalar radius,
		MidLevelRenderer::MLRTexture *tex
	)
{
	Check_Object(this);

	for(WORD i=0;i<GetActiveCount();i++)
	{
		Element *child = GetIndexedElement(i);
		Check_Object(child);
		child->MakeFootStep(shape, foot, radius, tex);
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	ElementRenderer::ListElement::CountTriangles()
{
	Check_Object(this);

	int result = 0;
	for (WORD i=0; i<m_activeChildren; ++i)
	{
		Element *element = m_list[i];
		Check_Object(element);
		result += element->CountTriangles();
	}
	return result;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//	Returns array of vertex coordinates and number of vertices
void
	ElementRenderer::ListElement::GetCoordData(
		Stuff::DynamicArrayOf<Stuff::Point3D> *array)
{
	Check_Object(this);
//	array->SetLength(0);

	for (WORD i=0; i<m_activeChildren; ++i)
	{
		Element *element = m_list[i];
		Check_Object(element);

		Stuff::DynamicArrayOf<Stuff::Point3D> child_array;
		element->GetCoordData(&child_array);
			
		int point_count = array->GetLength();
		array->SetLength(point_count+child_array.GetLength());
		
		for (int v=0; v<child_array.GetLength(); ++v)
		{
			(*array)[point_count+v]=child_array[v];
		}
	}
}