#include "ElementRendererHeaders.hpp"

//############################################################################
//############################  SwitchElement  ################################
//############################################################################

HGOSHEAP
	ElementRenderer::SwitchElement::s_Heap = NULL;

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

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

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

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

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

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

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

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

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

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

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ElementRenderer::SwitchElement::SwitchElement(
	ClassData *class_data,
	Stuff::MemoryStream *stream,
	int version,
	ShapeHolder shapes
):
	ListElement(class_data, stream, version, shapes)
{
	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
	//---------------------------------
	//
	Verify(m_list.GetLength() < 65536);
	*stream >> m_switch;
	}

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

	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];

	m_switch = 0;

}

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

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

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

	return element;
}

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

	//
	//--------------------
	// Save each Switch entry
	//--------------------
	//
	*stream << m_switch;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::SwitchElement::TestInstance()
{
	Verify(!AreBoundsLocked());
	Verify(IsDerivedFrom(DefaultData));
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::SwitchElement::SetSize(WORD max_size)
{
	Check_Object(this);
	BaseClass::SetSize(max_size);
	m_switch = max_size;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::SwitchElement::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::SwitchElement::SetDrawState()
{
	Check_Object(this);
	unsigned index = (m_state>>DrawStateBit) & DrawStateMask;
	Verify(index < DrawStateCount);
	m_draw = DrawMethods[index];
}

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

	//
	//--------------------
	// Update our children
	//--------------------
	//
	if (m_switch < m_list.GetLength())
	{
		Element *element = m_list[m_switch];
		if (element)
		{
			Check_Object(element);
			element->Sync();
		}
	}
	else if (m_switch & 0xf0) // special case
	{
		Element *element = GetFirstSwitchElement();
		if (element)
		{
			Check_Object(element);
			element->Sync();
		}
		element = GetSecondSwitchElement();
		if (element)
		{
			Check_Object(element);
			element->Sync();
		}
	}
}

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

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

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

	//
	//-------------------------------
	// 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
	//--------------------
	//
	if (m_switch < m_list.GetLength())
	{
		Element *element = m_list[m_switch];
		if (element)
		{
			Check_Object(element);
			element->Sync();
		}
	}
	else if (m_switch & 0xf0) // special case
	{
		Element *element = GetFirstSwitchElement();
		if (element)
		{
			Check_Object(element);
			element->Sync();
		}
		element = GetSecondSwitchElement();
		if (element)
		{
			Check_Object(element);
			element->Sync();
		}
	}
}

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

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

	//
	//--------------------
	// Update our children
	//--------------------
	//
	if (m_switch < m_list.GetLength())
	{
		Element *element = m_list[m_switch];
		if (element)
		{
			Check_Object(element);
			element->Sync();
		}
	}
	else if (m_switch & 0xf0) // special case
	{
		Element *element = GetFirstSwitchElement();
		if (element)
		{
			Check_Object(element);
			element->Sync();
		}
		element = GetSecondSwitchElement();
		if (element)
		{
			Check_Object(element);
			element->Sync();
		}
	}
}

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

	//
	//-------------------------------
	// 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
	//--------------------
	//
	if (m_switch < m_list.GetLength())
	{
		Element *element = m_list[m_switch];
		if (element)
		{
			Check_Object(element);
			element->Sync();
		}
	}
	else if (m_switch & 0xf0) // special case
	{
		Element *element = GetFirstSwitchElement();
		if (element)
		{
			Check_Object(element);
			element->Sync();
		}
		element = GetSecondSwitchElement();
		if (element)
		{
			Check_Object(element);
			element->Sync();
		}
	}
}

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

	//
	//--------------------
	// 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
	//--------------------
	//
	if (m_switch < m_list.GetLength())
	{
		Element *element = m_list[m_switch];
		if (element)
		{
			Check_Object(element);
			element->Sync();
		}
	}
	else if (m_switch & 0xf0) // special case
	{
		Element *element = GetFirstSwitchElement();
		if (element)
		{
			Check_Object(element);
			element->Sync();
		}
		element = GetSecondSwitchElement();
		if (element)
		{
			Check_Object(element);
			element->Sync();
		}
	}
}

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

	//
	//-------------------------------
	// 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;
	Verify(m_list.GetLength() < 65536);
	WORD switch_count = static_cast<WORD>(m_list.GetLength());
	Stuff::Point3D centroid = Stuff::Point3D::Identity;
	WORD bounded_children = 0;
	for (i=0; i<switch_count; ++i)
	{
		Element *element = m_list[i];
		if (element)
		{
			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<switch_count; ++i)
		{
			Element *element = m_list[i];
			if (element)
			{
				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::SwitchElement::FullSphereSyncMethod()
{
	Check_Object(this);
	Verify(
		IsMatrixDirty() && AreBoundsWrong()
		 && GetCullMode()==VolumeCullMode && IsBoundedBySphere()
	);
	SYNC_LOGIC("Sphere::Full::Switch");

	//
	//--------------------
	// 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;
	Verify(m_list.GetLength() < 65536);
	WORD switch_count = static_cast<WORD>(m_list.GetLength());
	Stuff::Point3D centroid = Stuff::Point3D::Identity;
	WORD bounded_children = 0;
	for (i=0; i<switch_count; ++i)
	{
		Element *element = m_list[i];
		if (element)
		{
			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<switch_count; ++i)
		{
			Element *element = m_list[i];
			if (element)
			{
				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::SwitchElement::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::Switch");

	//
	//-------------------------------
	// 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.Multiply(m_localOBB, m_localToWorld);

	//
	//--------------------
	// Update our children
	//--------------------
	//
	if (m_switch < m_list.GetLength())
	{
		Element *element = m_list[m_switch];
		if (element)
		{
			Check_Object(element);
			element->Sync();
		}
	}
	else if (m_switch & 0xf0) // special case
	{
		Element *element = GetFirstSwitchElement();
		if (element)
		{
			Check_Object(element);
			element->Sync();
		}
		element = GetSecondSwitchElement();
		if (element)
		{
			Check_Object(element);
			element->Sync();
		}
	}
}

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

	//
	//--------------------
	// 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
	//--------------------
	//
	if (m_switch < m_list.GetLength())
	{
		Element *element = m_list[m_switch];
		if (element)
		{
			Check_Object(element);
			element->Sync();
		}
	}
	else if (m_switch & 0xf0) // special case
	{
		Element *element = GetFirstSwitchElement();
		if (element)
		{
			Check_Object(element);
			element->Sync();
		}
		element = GetSecondSwitchElement();
		if (element)
		{
			Check_Object(element);
			element->Sync();
		}
	}
}

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

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

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

	//
	//-----------------------------
	// Test it against the MLRShape
	//-----------------------------
	//
	if (m_switch < m_list.GetLength())
	{
		Element *element = m_list[m_switch];
		if (element)
		{
			Check_Object(element);
			if (element->CastRay(query))
			{
				if (!query->m_data)
					query->m_data = m_data;
				return true;
			}
		}
	}
	else if (m_switch & 0xf0) // special case
	{
		Element *element = GetFirstSwitchElement();
		if (element)
		{
			Check_Object(element);
			if (element->CastRay(query))
			{
				if (!query->m_data)
					query->m_data = m_data;
				return true;
			}
		}
		element = GetSecondSwitchElement();
		if (element)
		{
			Check_Object(element);
			if (element->CastRay(query))
			{
				if (!query->m_data)
					query->m_data = m_data;
				return true;
			}
		}
	}
	return false;
}

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

	//
	//------------------
	// Draw our children
	//------------------
	//
	Element *result = this;
	if (m_switch < m_list.GetLength())
	{
		Element *element = m_list[m_switch];
		if (element)
		{
			Check_Object(element);
			Element *temp = element->FindSmallestElementContaining(test);
			if (temp)
				result = temp;
		}
	}
	else if (m_switch & 0xf0) // special case
	{
		Element *element = GetFirstSwitchElement();
		if (element)
		{
			Check_Object(element);
			Element *temp = element->FindSmallestElementContaining(test);
			if (temp)
				result = temp;
		}
		element = GetSecondSwitchElement();
		if (element)
		{
			Check_Object(element);
			Element *temp = element->FindSmallestElementContaining(test);
			if (temp)
				result = temp;
		}
	}
	return result;
}

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

	//
	//------------------------------
	// Only draw the switch selected
	//------------------------------
	//
	if (m_switch < m_list.GetLength())
	{
		Element *element = m_list[m_switch];
		if (element)
		{
			Check_Object(element);
			camera->DrawElement(element, inherited_state);
		}
	}
	else if (m_switch & 0xf0) // special case
	{
		Element *element = GetFirstSwitchElement();
		if (element)
		{
			Check_Object(element);
			camera->DrawElement(element, inherited_state);
		}
		element = GetSecondSwitchElement();
		if (element)
		{
			Check_Object(element);
			camera->DrawElement(element, inherited_state);
		}
	}
}

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

	//
	//------------------------------
	// Only draw the switch selected
	//------------------------------
	//
	if (m_switch < m_list.GetLength())
	{
		Element *element = m_list[m_switch];
		if (element)
		{
			Check_Object(element);
			camera->DrawElement(element, &mixed);
		}
	}
	else if (m_switch & 0xf0) // special case
	{
		Element *element = GetFirstSwitchElement();
		if (element)
		{
			Check_Object(element);
			camera->DrawElement(element, &mixed);
		}
		element = GetSecondSwitchElement();
		if (element)
		{
			Check_Object(element);
			camera->DrawElement(element, &mixed);
		}
	}
}

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

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

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

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

	if (!IsShadowed())
		return;
	WORD current = GetSwitch();
	if (current < GetActiveCount())
	{
		Element *element = GetIndexedElement(current);
		if (element)
		{
			Check_Object(element);
			element->CastShadow(shadow_to_world, sun_in_world, radius);
		}
	}				
}

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

	WORD current = GetSwitch();
	if (current < GetActiveCount())
	{
		Element *element = GetIndexedElement(current);
		if (element)
		{
			Check_Object(element);
			element->MakeFootStep(shape, foot, radius, tex);
		}
	}				
}

