#include "ElementRendererHeaders.hpp"
#include "GroupElement.hpp"

//#define BOUNDS_BUG "jmalbert"

#if defined(BOUNDS_BUG)
	int prefix_len=0;
#endif

//############################################################################
//############################  GroupElement  ################################
//############################################################################

HGOSHEAP
	ElementRenderer::GroupElement::s_Heap = NULL;

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

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

	SYNC_METHOD(GroupElement,	CleanRoot),
	SYNC_METHOD(GroupElement,	DirtyRoot),
	SYNC_METHOD(GroupElement,	CleanRoot),
	SYNC_METHOD(GroupElement,	DirtyRoot),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		Defer),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		Defer),

	//
	// Stuff::Sphere Cull
	//
	SYNC_METHOD(GroupElement,	CleanSphere),
	SYNC_METHOD(GroupElement,	MatrixDirtySphere),
	SYNC_METHOD(GroupElement,	BoundsWrongSphere),
	SYNC_METHOD(GroupElement,	FullSphere),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		Defer),
	SYNC_METHOD(Element,		None),
	SYNC_METHOD(Element,		Defer),

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

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

	SYNC_METHOD(GroupElement,	Clean),
	SYNC_METHOD(GroupElement,	Dirty),
	SYNC_METHOD(GroupElement,	Clean),
	SYNC_METHOD(GroupElement,	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::GroupElement::DrawMethods[DrawStateCount]=
{
	DRAW_METHOD(GroupElement,	Inherit),
	DRAW_METHOD(GroupElement,	Override)
};

static DWORD
	Wasted_Syncs;

#if !defined(NO_STATS)
	#define WASTE_CHECK()\
		if (Stuff::ArmorLevel>0 && !children.GetCurrent())\
			 Set_Statistic(Wasted_Syncs, Wasted_Syncs+1);
#else
	#define WASTE_CHECK()
#endif

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

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

	Wasted_Syncs = 0;
#if !defined(NO_STATS)
	AddStatistic("Wasted Group Syncs", "syncs", gos_DWORD, &Wasted_Syncs, Stat_AutoReset);
#endif
}

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

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ElementRenderer::GroupElement::GroupElement(
	ClassData *class_data,
	Stuff::MemoryStream *stream,
	int version,
	ShapeHolder shapes
):
	Element(class_data, stream, version),
	m_group(NULL)
{
	Check_Pointer(this);
	Check_Object(stream);
	Check_Object(class_data);

	//
	//------------------
	// Update the tables
	//------------------
	//
	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];

	//
	//----------------------------
	// Do the child creation thing
	//----------------------------
	//
	int count;
	*stream >> count;
	while (count--)
	{
		Element *child = Create(stream, version);
		Check_Object(child);
		AttachChild(child);
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ElementRenderer::GroupElement::GroupElement(ClassData *class_data):
	Element(class_data),
	m_group(NULL)
{
	Check_Pointer(this);
	Check_Object(class_data);

	//
	//------------------
	// Update the tables
	//------------------
	//
	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::GroupElement::~GroupElement()
{
	Check_Object(this);

	//
	//------------------------
	// Detach all the children
	//------------------------
	//
	Stuff::ChainIteratorOf<Element*> children(&m_group);
	Element *child;
	while ((child = children.ReadAndNext()) != NULL)
	{
		Unregister_Object(child);
		delete child;
	}
}

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

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

	return element;
}

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

	//
	//-----------------------------------------------------------
	// Write out the child count and then the children themselves
	//-----------------------------------------------------------
	//
	Stuff::ChainIteratorOf<Element*> elements(&m_group);
	Element *element;
	*stream << elements.GetSize();
	while ((element = elements.ReadAndNext()) != NULL)
	{
		Check_Object(element);
		element->Save(stream);
	}
}

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::GroupElement::AttachChild(Element *child)
{
	Check_Object(this);
	Check_Object(child);

	//
	//--------------------------------------------------------------------
	// Increment the child's reference count, and add it to our child list
	//--------------------------------------------------------------------
	//
	Verify(!child->GetParentElement());
	Verify(!m_group.IsPlugMember(child));
	m_group.Add(child);
	SetParentElement(child, this);
	if (!AreBoundsLocked())
		NeedNewBounds();
	if (child->GetCullMode() == RootMode)
	{
		if (child->m_localOBB.sphereRadius > 0.0f || child->AreBoundsWrong())
			child->SetVolumeCullMode();
		else
			child->SetNeverCullMode();
	}
}

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

	//
	//-------------------------------------------------------------------
	// Decrement the child's reference count, and remove it from our list
	//-------------------------------------------------------------------
	//
	Verify(child->GetParentElement() == this);
	m_group.RemovePlug(child);
	SetParentElement(child, NULL);
	child->SetRootMode();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::GroupElement::DetachChildren()
{
	Check_Object(this);

	Stuff::ChainIteratorOf<Element*> children(&m_group);
	Element *child;
	while ((child = children.ReadAndNext()) != NULL)
	{
		Verify(child->GetParentElement() == this);
		m_group.RemovePlug(child);
		SetParentElement(child, NULL);
		child->SetRootMode();
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::GroupElement::SetSyncState()
{
	Check_Object(this);
/*
	Verify(
		GetCullMode() != VolumeCullMode
		 || AreBoundsWrong()
		 || IsSyncDeferred()
		 || (
				m_localOBB.sphereRadius>0.0f
				 && (IsMatrixDirty() || m_worldOBB.sphereRadius>0.0f
			)
		)
	);
*/
	Verify(
		GetCullMode() == RootMode
		 || GetCullMode() == AlwaysCullMode
		 || m_parent != NULL
		 || AreBoundsWrong()
	);
	unsigned index = (m_state>>SyncStateBit) & SyncStateMask;
	Verify(index < SyncStateCount);
	m_sync = SyncMethods[index];
}

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

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

	//
	//--------------------
	// Update our children
	//--------------------
	//
	Stuff::ChainIteratorOf<Element*> children(&m_group);
	Element *child;
	WASTE_CHECK();
	while ((child = children.ReadAndNext()) != NULL)
	{
		Check_Object(child);
		child->Sync();
	}
}

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

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

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

	//
	//------------------------------------------------------------------
	// Concatenated our m_localToWorld from m_localToParent and our parent's
	// m_localToWorld
	//------------------------------------------------------------------
	//
	Check_Object(m_parent);
	if (!IsLocalToParentIdentity())
		m_localToWorld.Multiply(m_localToParent, m_parent->GetLocalToWorld());
	else
		m_localToWorld = m_parent->GetLocalToWorld();

	//
	//--------------------
	// Update our children
	//--------------------
	//
	Stuff::ChainIteratorOf<Element*> children(&m_group);
	Element *child;
	WASTE_CHECK();
	while ((child = children.ReadAndNext()) != NULL)
	{
		Check_Object(child);
		child->Sync();
	}
}

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

	//
	//------------------------------------------------------
	// If the local-to-parent matrix is dirty, make it clean
	//------------------------------------------------------
	//
	m_localToParent = m_newLocalToParent;

	//
	//------------------------------------------------------------------
	// Concatenated our m_localToWorld from m_localToParent and our parent's
	// m_localToWorld
	//------------------------------------------------------------------
	//
	Check_Object(m_parent);
	if (!IsLocalToParentIdentity())
		m_localToWorld.Multiply(m_localToParent, m_parent->GetLocalToWorld());
	else
		m_localToWorld = m_parent->GetLocalToWorld();
	MatrixIsClean();

	//
	//--------------------
	// Update our children
	//--------------------
	//
	Stuff::ChainIteratorOf<Element*> children(&m_group);
	Element *child;
	WASTE_CHECK();
	while ((child = children.ReadAndNext()) != NULL)
	{
		Check_Object(child);
		child->Sync();
	}
}

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

	//
	//------------------------------------------------------------------
	// Concatenated our m_localToWorld from m_localToParent and our parent's
	// m_localToWorld
	//------------------------------------------------------------------
	//
	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
	//--------------------
	//
	Stuff::ChainIteratorOf<Element*> children(&m_group);
	Element *child;
	WASTE_CHECK();
	while ((child = children.ReadAndNext()) != NULL)
	{
		Check_Object(child);
		child->Sync();
	}
}

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

	//
	//--------------------
	// Update the matrices
	//--------------------
	//
	m_localToParent = m_newLocalToParent;
	Check_Object(&m_localToParent);

	Check_Object(m_parent);
	
	if (!IsLocalToParentIdentity())
	{
		Check_Object(&m_parent->GetLocalToWorld());
		m_localToWorld.Multiply(m_localToParent, m_parent->GetLocalToWorld());
		Check_Object(&m_localToWorld);
	}
	else
	{
		m_localToWorld = m_parent->GetLocalToWorld();
		Check_Object(&m_localToWorld);
	}
	//
	//--------------------------------------------------------------
	// Transform the bounding sphere, then mark the element as clean
	//--------------------------------------------------------------
	//
	m_worldOBB.MultiplySphereOnly(m_localOBB, m_localToWorld);
	MatrixIsClean();

	//
	//--------------------
	// Update our children
	//--------------------
	//
	Stuff::ChainIteratorOf<Element*> children(&m_group);
	Element *child;
	WASTE_CHECK();
	while ((child = children.ReadAndNext()) != NULL)
	{
		Check_Object(child);
		child->Sync();
	}
}

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

	//
	//------------------------------------------------------------------
	// Concatenated our m_localToWorld from m_localToParent and our parent's
	// m_localToWorld
	//------------------------------------------------------------------
	//
	Check_Object(m_parent);
	if (!IsLocalToParentIdentity())
		m_localToWorld.Multiply(m_localToParent, m_parent->GetLocalToWorld());
	else
		m_localToWorld = m_parent->GetLocalToWorld();

	//
	//------------------------------------------------------------------------
	// If our bounds are locked, just run the children and transform our local
	// bounds
	//------------------------------------------------------------------------
	//
	Stuff::ChainIteratorOf<Element*> children(&m_group);
	Element *child;
	WASTE_CHECK();
	if (AreBoundsLocked())
	{
		Verify(m_localOBB.sphereRadius > 0.0f);
		m_worldOBB.MultiplySphereOnly(m_localOBB, m_localToWorld);
		BoundsAreRight();
		while ((child = children.ReadAndNext()) != NULL)
		{
			Check_Object(child);
			child->Sync();
		}
		return;
	}

	//
	//-----------------------------------------------------
	// Update our children, then get their bounding spheres
	//-----------------------------------------------------
	//
	#if defined(BOUNDS_BUG)
		for (int i=0; i<prefix_len; ++i)
			SPEW((BOUNDS_BUG, " +"));
		++prefix_len;
		SPEW((BOUNDS_BUG, "%s = %x", GetClassData()->GetClassName(), this));
	#endif
	Stuff::Sphere bounds(Stuff::Point3D::Identity, 0.0f);
	while ((child = children.ReadAndNext()) != NULL)
	{
		Check_Object(child);
		child->Sync();
		if (child->GetCullMode() == VolumeCullMode)
		{
			Stuff::LinearMatrix4D child_to_parent = child->GetLocalToParent();
			Stuff::Point3D child_center(child->m_localOBB.localToParent);

			//
			//--------------------------------------------------------------
			// If we are derived from a group, we can dynamically move so we
			// need to expand the bounding volume
			//--------------------------------------------------------------
			//
			#if defined(BOUNDS_BUG)
				for (int i=0; i<prefix_len; ++i)
					SPEW((BOUNDS_BUG, " +"));
			#endif
			Stuff::Sphere child_sphere;
			if (child->IsDerivedFrom(ElementRenderer::GroupElement::DefaultData))
			{
				child_sphere.center = child_to_parent;
				child_sphere.radius =
					child_center.GetLength() + child->m_localOBB.sphereRadius;
				#if defined(BOUNDS_BUG)
					SPEW((BOUNDS_BUG, "%s %x = +", child->GetClassData()->GetClassName(), child));
					Spew(BOUNDS_BUG, child_sphere.center);
					SPEW((
						BOUNDS_BUG,
						", %f(%f+%f)",
						child_sphere.radius,
						child_center.GetLength(),
						child->m_localOBB.sphereRadius
					));
				#endif
			}

			//
			//-------------------------------------------------------------
			// If not, the object is static, so just deal with the centroid
			//-------------------------------------------------------------
			//
			else
			{
				child_sphere.center.Multiply(child_center, child_to_parent);
				child_sphere.radius = child->m_localOBB.sphereRadius;
				#if defined(BOUNDS_BUG)
					SPEW((BOUNDS_BUG, "%s %x = +", child->GetClassData()->GetClassName(), child));
					Spew(BOUNDS_BUG, child_sphere.center);
					SPEW((BOUNDS_BUG, ",%f", child_sphere.radius));
				#endif
			}

			//
			//---------------------
			// Build the new sphere
			//---------------------
			//
			if (bounds.radius == 0.0f)
				bounds = child_sphere;
			else
				bounds.Union(bounds, child_sphere);
		}
	}
	#if defined(BOUNDS_BUG)
		--prefix_len;
		for (i=0; i<prefix_len; ++i)
			SPEW((BOUNDS_BUG, " +"));
		SPEW((BOUNDS_BUG, "Bounds = +"));
		Spew(BOUNDS_BUG, bounds.center);
		SPEW((BOUNDS_BUG, ",%f", bounds.radius));
	#endif
	m_localOBB.BuildSphere(bounds);

	//
	//-----------------------------------------------------------------------
	// If we have no bounded children, we will never cull, otherwise take the
	// local bounds into world space
	//-----------------------------------------------------------------------
	//
	if (m_localOBB.sphereRadius == 0.0f)
		SetNeverCullMode();
	else
		m_worldOBB.MultiplySphereOnly(m_localOBB, m_localToWorld);
	BoundsAreRight();
}

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

	//
	//--------------------
	// Update the 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();


	//
	//------------------------------------------------------------------------
	// If our bounds are locked, just run the children and transform our local
	// bounds
	//------------------------------------------------------------------------
	//
	Stuff::ChainIteratorOf<Element*> children(&m_group);
	Element *child;
	WASTE_CHECK();
	if (AreBoundsLocked())
	{
		Verify(m_localOBB.sphereRadius > 0.0f);
		m_worldOBB.MultiplySphereOnly(m_localOBB, m_localToWorld);
		m_state &= ~(MatrixDirtyFlag|BoundsWrongFlag);
		SetSyncState();
		while ((child = children.ReadAndNext()) != NULL)
		{
			Check_Object(child);
			child->Sync();
		}
		return;
	}

	//
	//-----------------------------------------------------
	// Update our children, then get their bounding spheres
	//-----------------------------------------------------
	//
	#if defined(BOUNDS_BUG)
		if (!prefix_len)
			SPEW((BOUNDS_BUG, "%s = %x", GetClassData()->GetClassName(), this));
		++prefix_len;
	#endif
	Stuff::Sphere bounds(Stuff::Point3D::Identity, 0.0f);
	#if defined(BOUNDS_BUG)
		int child_count = 0;
	#endif
	while ((child = children.ReadAndNext()) != NULL)
	{
		Check_Object(child);
		#if defined(BOUNDS_BUG)
			for (int i=0; i<prefix_len; ++i)
				SPEW((BOUNDS_BUG, " +"));
			SPEW((BOUNDS_BUG, "%d: syncing %s(%x)", child_count, child->GetClassData()->GetClassName(), child));
		#endif
		child->Sync();
		#if defined(BOUNDS_BUG)
			for (i=0; i<=prefix_len; ++i)
				SPEW((BOUNDS_BUG, " +"));
		#endif
		if (child->GetCullMode() == VolumeCullMode)
		{
			Stuff::LinearMatrix4D child_to_parent = child->GetLocalToParent();
			Stuff::Point3D child_center(child->m_localOBB.localToParent);

			//
			//--------------------------------------------------------------
			// If we are derived from a group, we can dynamically move so we
			// need to expand the bounding volume
			//--------------------------------------------------------------
			//
			Stuff::Sphere child_sphere;
			if (child->IsDerivedFrom(ElementRenderer::GroupElement::DefaultData))
			{
				child_sphere.center = child_to_parent;
				child_sphere.radius =
					child_center.GetLength() + child->m_localOBB.sphereRadius;
				#if defined(BOUNDS_BUG)
					Spew(BOUNDS_BUG, child_sphere.center);
					SPEW((
						BOUNDS_BUG, ", %f(%f+%f)",
						child_sphere.radius,
						child_center.GetLength(),
						child->m_localOBB.sphereRadius
					));
				#endif
			}

			//
			//-------------------------------------------------------------
			// If not, the object is static, so just deal with the centroid
			//-------------------------------------------------------------
			//
			else
			{
				child_sphere.center.Multiply(child_center, child_to_parent);
				child_sphere.radius = child->m_localOBB.sphereRadius;
				#if defined(BOUNDS_BUG)
					Spew(BOUNDS_BUG, child_sphere.center);
					SPEW((BOUNDS_BUG, ", %f", child_sphere.radius));
				#endif
			}

			//
			//---------------------
			// Build the new sphere
			//---------------------
			//
			if (bounds.radius == 0.0f)
				bounds = child_sphere;
			else
				bounds.Union(bounds, child_sphere);
		}
		#if defined(BOUNDS_BUG)
			else
				SPEW((BOUNDS_BUG, " isn't volume culled"));
			++child_count;
		#endif
	}
	#if defined(BOUNDS_BUG)
		--prefix_len;
		for (int i=0; i<prefix_len; ++i)
			SPEW((BOUNDS_BUG, " +"));
		SPEW((BOUNDS_BUG, "%d children, Bounds = +", child_count));
		Spew(BOUNDS_BUG, bounds.center);
		SPEW((BOUNDS_BUG, ",%f", bounds.radius));
	#endif
	m_localOBB.BuildSphere(bounds);

	//
	//-----------------------------------------------------------------------
	// If we have no bounded children, we will never cull, otherwise take the
	// local bounds into world space
	//-----------------------------------------------------------------------
	//
	if (m_localOBB.sphereRadius == 0.0f)
		SetNeverCullMode();
	else
		m_worldOBB.MultiplySphereOnly(m_localOBB, m_localToWorld);
	m_state &= ~(MatrixDirtyFlag|BoundsWrongFlag);
	SetSyncState();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::GroupElement::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::Group");

	//
	//------------------------------------------------------------------
	// Concatenated our m_localToWorld from m_localToParent and our parent's
	// m_localToWorld
	//------------------------------------------------------------------
	//
	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);

	//
	//--------------------
	// Update our children
	//--------------------
	//
	Stuff::ChainIteratorOf<Element*> children(&m_group);
	Element *child;
	WASTE_CHECK();
	while ((child = children.ReadAndNext()) != NULL)
	{
		Check_Object(child);
		child->Sync();
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::GroupElement::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::Group");

	//
	//--------------------
	// Update the matrices
	//--------------------
	//
	m_localToParent = m_newLocalToParent;
	Check_Object(&m_localToParent);

	Check_Object(m_parent);
	
	if (!IsLocalToParentIdentity())
	{
		Check_Object(&m_parent->GetLocalToWorld());
		m_localToWorld.Multiply(m_localToParent, m_parent->GetLocalToWorld());
		Check_Object(&m_localToWorld);
	}
	else
	{
		m_localToWorld = m_parent->GetLocalToWorld();
		Check_Object(&m_localToWorld);
	}
	//
	//--------------------------------------------------------------
	// Transform the bounding sphere, then mark the element as clean
	//--------------------------------------------------------------
	//
	m_worldOBB.Multiply(m_localOBB, m_localToWorld);
	MatrixIsClean();

	//
	//--------------------
	// Update our children
	//--------------------
	//
	Stuff::ChainIteratorOf<Element*> children(&m_group);
	Element *child;
	WASTE_CHECK();
	while ((child = children.ReadAndNext()) != NULL)
	{
		Check_Object(child);
		child->Sync();
	}
}

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

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

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

	//
	//------------------
	// Test our children
	//------------------
	//
	Stuff::ChainIteratorOf<Element*> children(&m_group);
	Element *child;
	bool result = false;
	void *client_data = m_data;
	while ((child = children.ReadAndNext()) != NULL)
	{
		Check_Object(child);
		if (child->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::GroupElement::FindSmallestElementContainingCulled(SphereTest *test)
{
	Check_Object(this);
	Check_Object(test);

	//
	//------------------
	// Draw our children
	//------------------
	//
	Stuff::ChainIteratorOf<Element*> children(&m_group);
	Element *child;
	Element *result = this;
	while ((child = children.ReadAndNext()) != NULL)
	{
		Check_Object(child);
		Element *temp = child->FindSmallestElementContaining(test);
		if (temp)
			result = temp;
	}
	return result;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::GroupElement::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 our children
	//------------------
	//
	Stuff::ChainIteratorOf<Element*> children(&m_group);
	Element *child;
	while ((child = children.ReadAndNext()) != NULL)
	{
		Check_Object(child);
		camera->DrawElement(child, inherited_state);
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::GroupElement::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 our children
	//------------------
	//
	Stuff::ChainIteratorOf<Element*> children(&m_group);
	Element *child;
	while ((child = children.ReadAndNext()) != NULL)
	{
		Check_Object(child);
		camera->DrawElement(child, &mixed);
	}
}

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

	Stuff::ChainIteratorOf<Element*> children(&m_group);
	Element *child = NULL;
	while ((child = children.ReadAndNext()) != NULL)
	{
		Check_Object(child);
		child->CleanDamage();
	}
}

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

	Stuff::ChainIteratorOf<Element*> children(&m_group);
	Element *child = NULL;
	while ((child = children.ReadAndNext()) != NULL)
	{
		Check_Object(child);
		child->ApplyDamage(damage_spot, radius);
	}
}

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

	Stuff::ChainIteratorOf<Element*> children(&m_group);
	Element *child = NULL;
	while ((child = children.ReadAndNext()) != NULL)
	{
		Check_Object(child);
		child->ApplyDamageDecal(damage_spot, radius, texture);
	}
}

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

	if (!IsShadowed())
		return;
	Stuff::ChainIteratorOf<Element*> children(&m_group);
	Element *child = NULL;
	while ((child = children.ReadAndNext()) != NULL)
	{
		Check_Object(child);
		child->CastShadow(shadow_to_world, sun_in_world, radius);
	}
}

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

	Stuff::ChainIteratorOf<Element*> children(&m_group);
	Element *child = NULL;
	while ((child = children.ReadAndNext()) != NULL)
	{
		Check_Object(child);
		child->MakeFootStep(shape, foot, radius, tex);
	}
}

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

	//
	//------------------
	// Draw our children
	//------------------
	//
	int result = 0;
	Stuff::ChainIteratorOf<Element*> children(&m_group);
	Element *child;
	while ((child = children.ReadAndNext()) != NULL)
	{
		Check_Object(child);
		result += child->CountTriangles();
	}
	return result;
}

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

	Stuff::ChainIteratorOf<Element*> children(&m_group);
	Element *child;
	while ((child = children.ReadAndNext()) != NULL)
	{
		Stuff::DynamicArrayOf<Stuff::Point3D> child_array;
		child->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];
		}
	}

}