#include "ElementRendererHeaders.hpp"
#include <MLR\MLR_I_TMesh.hpp>

//############################################################################
//##############################  Element  ###################################
//############################################################################

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

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

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

	//
	// Stuff::Sphere Cull
	//
	SYNC_METHOD(Element, CleanSphere),
	SYNC_METHOD(Element, DirtySphere),
	SYNC_METHOD(Element, CleanSphere),
	SYNC_METHOD(Element, DirtySphere),
	SYNC_METHOD(Element, None),
	SYNC_METHOD(Element, Defer),
	SYNC_METHOD(Element, None),
	SYNC_METHOD(Element, Defer),

	//
	// Stuff::OBB Cull
	//
	SYNC_METHOD(Element, CleanOBB),
	SYNC_METHOD(Element, DirtyOBB),
	SYNC_METHOD(Element, CleanOBB),
	SYNC_METHOD(Element, DirtyOBB),
	SYNC_METHOD(Element, None),
	SYNC_METHOD(Element, Defer),
	SYNC_METHOD(Element, None),
	SYNC_METHOD(Element, Defer),

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

	SYNC_METHOD(Element, Clean),
	SYNC_METHOD(Element, Dirty),
	SYNC_METHOD(Element, Clean),
	SYNC_METHOD(Element, 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::CullMethod
	ElementRenderer::Element::CullMethods[CullStateCount]=
{
	//
	// No billboarding
	//
	CULL_METHOD(Element, Never,				Never),
	CULL_METHOD(Element, Never,				Never),
	CULL_METHOD(Element, Sphere,			Sphere),
	CULL_METHOD(Element, OBB,				OBB),
	CULL_METHOD(Element, Never,				Never),
	CULL_METHOD(Element, Never,				Never),
	CULL_METHOD(Element, Always,			Always),
	CULL_METHOD(Element, Always,			Always),

	//
	// Billboarding around X
	//
	CULL_METHOD(Element, DeferredXNever,	Never),
	CULL_METHOD(Element, DeferredXNever,	Never),
	CULL_METHOD(Element, DeferredXSphere,	Sphere),
	CULL_METHOD(Element, DeferredXOBB,		OBB),
	CULL_METHOD(Element, DeferredXNever,	Never),
	CULL_METHOD(Element, DeferredXNever,	Never),
	CULL_METHOD(Element, DeferredXAlways,	Always),
	CULL_METHOD(Element, DeferredXAlways,	Always),

	//
	// Billboarding around Y
	//
	CULL_METHOD(Element, DeferredYNever,	Never),
	CULL_METHOD(Element, DeferredYNever,	Never),
	CULL_METHOD(Element, DeferredYSphere,	Sphere),
	CULL_METHOD(Element, DeferredYOBB,		OBB),
	CULL_METHOD(Element, DeferredYNever,	Never),
	CULL_METHOD(Element, DeferredYNever,	Never),
	CULL_METHOD(Element, DeferredYAlways,	Always),
	CULL_METHOD(Element, DeferredYAlways,	Always),

	//
	// Billboarding around X and Y
	//
	CULL_METHOD(Element, DeferredXYNever,	Never),
	CULL_METHOD(Element, DeferredXYNever,	Never),
	CULL_METHOD(Element, DeferredXYSphere,	Sphere),
	CULL_METHOD(Element, DeferredXYOBB,		OBB),
	CULL_METHOD(Element, DeferredXYNever,	Never),
	CULL_METHOD(Element, DeferredXYNever,	Never),
	CULL_METHOD(Element, DeferredXYAlways,	Always),
	CULL_METHOD(Element, DeferredXYAlways,	Always)
};

Stuff::RGBAColor
	ElementRenderer::Element::FadeColor;

ElementRenderer::Element::Callback*
	ElementRenderer::Element::CallbackSet = NULL;

ElementRenderer::Element::Callback
	ElementRenderer::Element::Callbacks[1<<ElementRenderer::Element::CallbackBits]=
{
	NULL,
	DrawGreenBoundsCallback,
	DrawYellowBoundsCallback,
	DrawRedBoundsCallback,
	DrawGreenBoundsOnceCallback,
	DrawYellowBoundsOnceCallback,
	DrawRedBoundsOnceCallback,
	NULL
};

MidLevelRenderer::MLRShape
	*Sphere_Object = NULL,
	*Box_Object = NULL;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::Element::InitializeClass()
{
	Verify(!DefaultData);

	DefaultData =
		new ClassData(
			ElementClassID,
			"ElementRenderer::Element",
			BaseClass::DefaultData,
			NULL
		);
	Check_Object(DefaultData);

	Verify(!Sphere_Object);
	Verify(!Box_Object);

	MidLevelRenderer::MLRState state;
	state.SetAlphaMode(MidLevelRenderer::MLRState::AlphaInvAlphaMode);
	state.SetBackFaceOff();
	state.SetPriority(8);

	MidLevelRenderer::IcoInfo icoInfo;
	icoInfo.depth = 1;
	icoInfo.indexed = true;
	icoInfo.radius = 1.0f;
	icoInfo.all = 1.0f;
	icoInfo.onOff = true;

	Sphere_Object =
		MidLevelRenderer::CreateIndexedTriIcosahedron_NoColor_NoLit(
			icoInfo,
			&state
		);

	gos_PushCurrentHeap(MidLevelRenderer::ShapeHeap);
	Box_Object = new MidLevelRenderer::MLRShape(1);
	Check_Object(Box_Object);
	MidLevelRenderer::MLR_I_PMesh *cube =
		MidLevelRenderer::CreateIndexedCube_NoColor_NoLit(1.0f, &state);
	Check_Object(cube);
	gos_PopCurrentHeap();

	Box_Object->Add(cube);
	cube->DetachReference();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::Element::TerminateClass()
{
	Box_Object->DetachReference();
	Box_Object = NULL;
	Sphere_Object->DetachReference();
	Sphere_Object = NULL;

	Check_Object(DefaultData);
	delete DefaultData;
	DefaultData = NULL;
}

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

	//
	//--------------------------------
	// Initialize the member variables
	//--------------------------------
	//
	m_data = NULL;
	m_parent = NULL;
	m_worldOBB.localToParent = m_localOBB.localToParent = Stuff::LinearMatrix4D::Identity;
	m_localOBB.axisExtents = Stuff::Vector3D::Identity;
	m_localOBB.sphereRadius = 0.0f;
	m_worldOBB.axisExtents = Stuff::Vector3D::Identity;
	m_worldOBB.sphereRadius = -1.0f;
	m_newLocalToParent = Stuff::LinearMatrix4D::Identity;
	m_localToParent = Stuff::LinearMatrix4D::Identity;
	m_localToWorld = Stuff::LinearMatrix4D::Identity;
	m_stateChange = NULL;

	//
	//----------------------------
	// Now set the method handlers
	//----------------------------
	//
	m_state = RootMode | MatrixDirtyFlag | LocalToParentIsIdentityFlag;
	unsigned sync_state = (m_state>>SyncStateBit)&SyncStateMask;
	Verify(sync_state < SyncStateCount);
	m_sync = SyncMethods[sync_state];
	unsigned cull_state = (m_state>>CullStateBit)&CullStateMask;
	Verify(cull_state < CullStateCount);
	m_cameraCull = CullMethods[cull_state].m_cameraCull;
	m_rayCull = CullMethods[cull_state].m_rayCull;
	m_sphereTest = CullMethods[cull_state].m_sphereTest;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ElementRenderer::Element::Element(
	ClassData *class_data,
	Element *element
):
	Plug(class_data)
{
	Check_Pointer(this);
	Check_Object(class_data);

	//
	//--------------------------------
	// Initialize the member variables
	//--------------------------------
	//
	m_data = NULL;
	m_parent = NULL;
	m_localOBB = element->m_localOBB;
	m_worldOBB.localToParent = Stuff::LinearMatrix4D::Identity;
	m_worldOBB.axisExtents = Stuff::Vector3D::Identity;
	m_worldOBB.sphereRadius = -1.0f;
	m_newLocalToParent = element->m_newLocalToParent;
	m_localToParent = element->m_localToParent;
	m_localToWorld = element->m_localToWorld;
	if (element->m_stateChange)
	{
		m_stateChange = new StateChange(*element->m_stateChange);
		Check_Object(m_stateChange);
	}
	else
		m_stateChange = NULL;

	//
	//----------------------------
	// Now set the method handlers
	//----------------------------
	//
	m_state = element->m_state;
	m_state &= ~CullMask;
	m_state |= RootMode | MatrixDirtyFlag;
	unsigned sync_state = (m_state>>SyncStateBit)&SyncStateMask;
	Verify(sync_state < SyncStateCount);
	m_sync = SyncMethods[sync_state];
	unsigned cull_state = (m_state>>CullStateBit)&CullStateMask;
	Verify(cull_state < CullStateCount);
	m_cameraCull = CullMethods[cull_state].m_cameraCull;
	m_rayCull = CullMethods[cull_state].m_rayCull;
	m_sphereTest = CullMethods[cull_state].m_sphereTest;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ElementRenderer::Element::Element(
	ClassData *class_data,
	Stuff::MemoryStream *stream,
	int version
):
	Plug(class_data)
{
	Check_Pointer(this);
	Check_Object(class_data);
	Check_Object(stream);

	//
	//--------------------------------
	// Initialize the member variables
	//--------------------------------
	//
	m_data = NULL;
	m_parent = NULL;
	m_localToParent = Stuff::LinearMatrix4D::Identity;
	m_localToWorld = Stuff::LinearMatrix4D::Identity;
	m_stateChange = NULL;
	m_worldOBB.localToParent = Stuff::LinearMatrix4D::Identity;
	m_worldOBB.axisExtents = Stuff::Vector3D::Identity;
	m_worldOBB.sphereRadius = -1.0f;

	//
	//----------------------------------------------------------------------
	// Read mask and element state.  If we are reading something from before
	// version 6, make room for the new locked flag
	//----------------------------------------------------------------------
	//
	if (version < 7)
	{
		int collision_mask;
		*stream >> collision_mask;
	}
	*stream >> m_state;
	if (version < 6)
		m_state = (m_state&1) | ((m_state << 1)&0xFFFFFFFC);

	//
	//------------------------------
	// Read the StateChange and name
	//------------------------------
	//
	if (m_state & PropertyChangeFlag)
	{
		m_stateChange = new StateChange(stream, version);
		Check_Object(m_stateChange);
	}
	if (version < 12)
	{
		Stuff::MString name;
		*stream >> name;
	}
	if (!IsLocalToParentIdentity())
		*stream >> m_newLocalToParent;
	else
		m_newLocalToParent = Stuff::LinearMatrix4D::Identity;

	//
	//-------------------------
	// Read the bounding volume
	//-------------------------
	//
	if (IsBoundedByOBB())
	{
		*stream >> m_localOBB.localToParent >> m_localOBB.axisExtents
			>> m_localOBB.sphereRadius;
	}
	else
	{
		Stuff::Point3D center;
		*stream >> center;
		m_localOBB.localToParent = Stuff::LinearMatrix4D::Identity;
		m_localOBB.localToParent.BuildTranslation(center);
		m_localOBB.axisExtents = Stuff::Vector3D::Identity;
		*stream >> m_localOBB.sphereRadius;
	}

	//
	//----------------------------
	// Now set the method handlers
	//----------------------------
	//
	m_state |= MatrixDirtyFlag;
	unsigned sync_state = (m_state>>SyncStateBit)&SyncStateMask;
	Verify(sync_state < SyncStateCount);
	m_sync = SyncMethods[sync_state];
	unsigned cull_state = (m_state>>CullStateBit)&CullStateMask;
	Verify(cull_state < CullStateCount);
	m_cameraCull = CullMethods[cull_state].m_cameraCull;
	m_rayCull = CullMethods[cull_state].m_rayCull;
	m_sphereTest = CullMethods[cull_state].m_sphereTest;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ElementRenderer::Element::~Element()
{
	Check_Object(this);

	//
	//-------------------
	// Unhook from parent
	//-------------------
	//
	if (m_parent)
	{
		Check_Object(m_parent);
		m_parent->DetachChild(this);
	}

	//
	//--------------------------
	// Unhook the properties set
	//--------------------------
	//
	if (m_stateChange)
	{
		Check_Object(m_stateChange);
		delete m_stateChange;
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ElementRenderer::Element*
	ElementRenderer::Element::Create(
		Stuff::MemoryStream *stream,
		int version,
		ShapeHolder shapes
	)
{
	RegisteredClass::ClassID class_id;
	*stream >> class_id;
	ClassData* class_data = Cast_Pointer(ClassData*, FindClassData(class_id));
	Check_Object(class_data);
	Element *child = (*class_data->m_elementFactory)(stream, version, shapes);
	Check_Object(child);
	return child;
}

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

	//
	//--------------------------
	// Write stuff to the stream
	//--------------------------
	//
	*stream << static_cast<int>(GetClassID()) << m_state;
	if (m_stateChange)
	{
		Check_Object(m_stateChange);
		Verify(m_state & PropertyChangeFlag);
		m_stateChange->Save(stream);
	}
	if (!IsLocalToParentIdentity())
		*stream << m_localToParent;

	//
	//--------------------------
	// Write the bounding volume
	//--------------------------
	//
	if (IsBoundedByOBB())
	{
		*stream << m_localOBB.localToParent << m_localOBB.axisExtents
			<< m_localOBB.sphereRadius;
	}
	else
	{
		Stuff::Point3D center(m_localOBB.localToParent);
		*stream << center;
		*stream << m_localOBB.sphereRadius;
	}
}

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::Element::ReadStateFromPage(
		Stuff::Page *page,
		const Stuff::MString &prefix,
		unsigned *state,
		unsigned *delta,
		Stuff::OBB *bounds
	)
{
	Check_Object(&prefix);
	Check_Object(page);
	Check_Pointer(state);
	Check_Pointer(bounds);

	//
	//------------------------
	// Is it an identity state
	//------------------------
	//
	*delta = 0;
	bool identity;
	if (page->GetEntry(prefix+"LocalToParentIsIdentity", &identity))
	{
		if (identity)
			*state |= LocalToParentIsIdentityFlag;
		else
			*state &= ~LocalToParentIsIdentityFlag;
	}

	//
	//---------------------------
	// Is there a bounding sphere
	//---------------------------
	//
	const char* sphere = NULL;
	if (page->GetEntry(prefix+"BoundingSphere", &sphere))
	{
		*state &= ~OBBFlag;
		*delta |= OBBFlag;
		bounds->localToParent = Stuff::LinearMatrix4D::Identity;
		Check_Pointer(sphere);
		#if defined(_ARMOR)
			int count=
		#endif
		sscanf(
			sphere,
			"%f %f %f %f",
			&bounds->localToParent(3,0),
			&bounds->localToParent(3,1),
			&bounds->localToParent(3,2),
			&bounds->sphereRadius
		);
		Verify(count == 4);
		bounds->axisExtents = Stuff::Vector3D::Identity;
		*state |= BoundsLockedFlag;
		*delta |= BoundsLockedFlag;
	}

	//
	//------------------------
	// Is there a bounding Stuff::OBB
	//------------------------
	//
	else
	{
		const char* obb = NULL;
		if (page->GetEntry(prefix+"Stuff::OBB", &obb))
		{
			*state |= OBBFlag;
			*delta |= OBBFlag;
			Stuff::YawPitchRoll rotation;
			Check_Pointer(obb);
			sscanf(
				obb,
				"%f %f %f %f %f %f %f %f %f",
				&bounds->localToParent(3,0),
				&bounds->localToParent(3,1),
				&bounds->localToParent(3,2),
				&rotation.yaw.angle,
				&rotation.pitch.angle,
				&rotation.roll.angle,
				&bounds->axisExtents.x,
				&bounds->axisExtents.y,
				&bounds->axisExtents.z
			);
			rotation.yaw *= Stuff::Radians_Per_Degree;
			rotation.pitch *= Stuff::Radians_Per_Degree;
			rotation.roll *= Stuff::Radians_Per_Degree;
			bounds->localToParent.BuildRotation(rotation);
			bounds->sphereRadius = bounds->axisExtents.GetLength();
			*state |= BoundsLockedFlag;
			*delta |= BoundsLockedFlag;
		}

		//
		//----------------------------------------------------------------
		// Otherwise, there is no Stuff::OBB, so clear out everything and set the
		// radius to -1
		//----------------------------------------------------------------
		//
		else
		{
			bounds->localToParent = Stuff::LinearMatrix4D::Identity;
			bounds->axisExtents = Stuff::Vector3D::Identity;
			bounds->sphereRadius = -1.0f;
		}
	}

	//
	//------------------
	// Get the cull mode
	//------------------
	//
	const char* cull_mode = NULL;
	if (page->GetEntry(prefix+"Cull", &cull_mode))
	{
		*state &= ~CullMask;
		*delta |= CullMask;
		Check_Pointer(cull_mode);
		if (!_stricmp(cull_mode, "VolumeCull"))
			*state |= VolumeCullMode;
		else if (!_stricmp(cull_mode, "AlwaysCull"))
		{
			*state |= AlwaysCullMode|BoundsLockedFlag;
			*delta |= BoundsLockedFlag;
		}
		else if (!_stricmp(cull_mode, "NeverCull"))
		{
			*state |= NeverCullMode|BoundsLockedFlag;
			*delta |= BoundsLockedFlag;
		}
		else
			Verify(!_stricmp(cull_mode, "RootCull"));
	}

	//
	//------------------------
	// Look for the align bits
	//------------------------
	//
	bool align_x;
	if (page->GetEntry(prefix+"AlignZUsingX", &align_x))
	{
		if (align_x)
		{
			*state |= AlignXFlag|SyncOnCullFlag;
			*delta |= AlignXFlag|SyncOnCullFlag;
		}
	}

	bool align_y;
	if (page->GetEntry(prefix+"AlignZUsingY", &align_y))
	{
		if (align_y)
		{
			*state |= AlignYFlag|SyncOnCullFlag;
			*delta |= AlignYFlag|SyncOnCullFlag;
		}
	}

	bool shadow;
	if (page->GetEntry(prefix+"DisableShadow", &shadow))
	{
		if (shadow)
		{
			*state |= NoShadowFlag;
			*delta |= NoShadowFlag;
		}
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::Element::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 sync_state = (m_state>>SyncStateBit)&SyncStateMask;
	Verify(sync_state < SyncStateCount);
	m_sync = SyncMethods[sync_state];
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::Element::NoneSyncMethod()
{
	Check_Object(this);
	SYNC_LOGIC("None::Element");
	MatrixIsClean();
}

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

	//
	//-------------------------------------
	// Copy the matrix and reset the method
	//-------------------------------------
	//
	m_localToWorld = m_localToParent = m_newLocalToParent;
	MatrixIsClean();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::Element::DeferSyncMethod()
{
	Check_Object(this);
	Verify(IsMatrixDirty());
	SYNC_LOGIC("Defer::Element");

	//
	//-------------------------------------
	// Copy the matrix and reset the method
	//-------------------------------------
	//
	m_localToParent = m_newLocalToParent;
	MatrixIsClean();
}

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

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

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

	//
	//-------------------------------------
	// Copy the matrix and reset the method
	//-------------------------------------
	//
	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();
}

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

	//
	//------------------------------------------------------------------
	// 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);
}

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

	//
	//-------------------------------------
	// Copy the matrix and reset the method
	//-------------------------------------
	//
	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();

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

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

	//
	//------------------------------------------------------------------
	// 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);
}

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

	//
	//-------------------------------------
	// Copy the matrix and reset the method
	//-------------------------------------
	//
	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();

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::Element::SetCullState()
{
	Check_Object(this);

	unsigned cull_state = (m_state>>CullStateBit)&CullStateMask;
	Verify(cull_state < CullStateCount);
	m_cameraCull = CullMethods[cull_state].m_cameraCull;
	m_rayCull = CullMethods[cull_state].m_rayCull;
	m_sphereTest = CullMethods[cull_state].m_sphereTest;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	ElementRenderer::Element::NeverCullMethod(CameraElement *camera)
{
	Check_Object(this);
	Check_Object(camera);
	Verify(!IsMatrixDirty());

	return 0x2f;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	ElementRenderer::Element::AlwaysCullMethod(CameraElement *camera)
{
	Check_Object(this);
	Check_Object(camera);
	Verify(!IsMatrixDirty() && GetCullMode()==AlwaysCullMode);

	return -1;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	ElementRenderer::Element::SphereCullMethod(CameraElement *camera)
{
	Check_Object(this);
	Check_Object(camera);
	Verify(m_worldOBB.sphereRadius > 0.0f);
	Verify(
		!IsMatrixDirty() && IsBoundedBySphere() && GetCullMode()==VolumeCullMode
	);

	//
	//-------------------------------
	// We only use the sphere portion
	//-------------------------------
	//
	int cull_state = 0;
	Stuff::Point3D center(m_worldOBB.localToParent);

	//
	//-----------------------------------------------------------------------
	// Run each of the camera bounding planes against the sphere.  First find
	// the distance from the sphere center point to the bounding plane
	//-----------------------------------------------------------------------
	//
	const Stuff::Plane* plane = &camera->GetWorldCullingPlane(0);
	for (int i=0; i<ElementRenderer::CameraElement::CullingPlaneCount; ++i, ++plane)
	{
		Stuff::Scalar distance = plane->GetDistanceTo(center);

		//
		//-------------------------------------------------------------------
		// Now, if this distance is greater than the sphere radius, we have a
		// trivial reject
		//-------------------------------------------------------------------
		//
		if (distance >= m_worldOBB.sphereRadius)
			return -1;

		//
		//----------------------------------------------------------------------
		// Make room in the clip state for this plane result, then if the center
		// is not more than the radius away from the plane, it will need
		// culling by this plane
		//----------------------------------------------------------------------
		//
		cull_state <<= 1;
		if (distance > -m_worldOBB.sphereRadius)
			cull_state |= 1;
	}
	if (!cull_state)
		camera->m_acceptingElement = this;
	return cull_state;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	ElementRenderer::Element::OBBCullMethod(CameraElement *camera)
{
	Check_Object(this);
	Check_Object(camera);
	Verify(
		!IsMatrixDirty() && IsBoundedByOBB() && GetCullMode()==VolumeCullMode
	);
	Verify(Stuff::Close_Enough(m_localOBB.sphereRadius, m_localOBB.axisExtents.GetLength(), m_localOBB.sphereRadius*0.02f));

	//
	//------------------------------------------------------------------------
	// We will be testing the planes inside the matrix, so compute the inverse
	//------------------------------------------------------------------------
	//
	Stuff::LinearMatrix4D parent_to_local;
	parent_to_local.Invert(m_worldOBB.localToParent);

	//
	//-----------------------------------------------------------------------
	// Run each of the camera bounding planes against the sphere.  First find
	// the distance from the sphere center point to the bounding plane
	//-----------------------------------------------------------------------
	//
	Stuff::Point3D
		inside_extents,
		outside_extents;
	int cull_state = 0;
	const Stuff::Plane* plane = &camera->GetWorldCullingPlane(0);
	for (int i=0; i<ElementRenderer::CameraElement::CullingPlaneCount; ++i, ++plane)
	{
		Stuff::Plane local_plane;
		local_plane.Multiply(*plane, parent_to_local);

		//
		//---------------------------------------------------------------------
		// Look at the resulting vector to find out which corners of the box we
		// should be testing
		//---------------------------------------------------------------------
		//
		if (local_plane.normal.x > 0.0f)
		{
			inside_extents.x = -m_worldOBB.axisExtents.x;
			outside_extents.x = m_worldOBB.axisExtents.x;
		}
		else
		{
			inside_extents.x = m_worldOBB.axisExtents.x;
			outside_extents.x = -m_worldOBB.axisExtents.x;
		}

		if (local_plane.normal.y > 0.0f)
		{
			inside_extents.y = -m_worldOBB.axisExtents.y;
			outside_extents.y = m_worldOBB.axisExtents.y;
		}
		else
		{
			inside_extents.y = m_worldOBB.axisExtents.y;
			outside_extents.y = -m_worldOBB.axisExtents.y;
		}

		if (local_plane.normal.z > 0.0f)
		{
			inside_extents.z = -m_worldOBB.axisExtents.z;
			outside_extents.z = m_worldOBB.axisExtents.z;
		}
		else
		{
			inside_extents.z = m_worldOBB.axisExtents.z;
			outside_extents.z = -m_worldOBB.axisExtents.z;
		}

		//
		//-------------------------------------------------------------------
		// Now, if this distance is greater than the sphere radius, we have a
		// trivial reject
		//-------------------------------------------------------------------
		//
		Stuff::Scalar distance = local_plane.GetDistanceTo(inside_extents);
		if (distance > 0.0f)
			return -1;

		//
		//----------------------------------------------------------------------
		// Make room in the clip state for this plane result, then if the center
		// is not more than the radius away from the plane, it will need
		// culling by this plane
		//----------------------------------------------------------------------
		//
		distance = local_plane.GetDistanceTo(outside_extents);
		cull_state <<= 1;
		if (distance > 0.0f)
			cull_state |= 1;
	}
	if (!cull_state)
		camera->m_acceptingElement = this;
	return cull_state;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::Element::AlignX(CameraElement *camera)
{
	Check_Object(this);
	Check_Object(camera);
	Verify(!IsMatrixDirty());

	//
	//----------------------------------------------------------------------
	// We have to move the camera into the same parent space as the local
	// matrix will be in, then figure out the relative direction the element
	// should face
	//----------------------------------------------------------------------
	//
	Check_Object(m_parent);
	Stuff::Point3D world_camera(camera->GetLocalToWorld());
	Stuff::Point3D parent_camera;
	parent_camera.MultiplyByInverse(
		world_camera,
		m_parent->GetLocalToWorld()
	);
	Stuff::Vector3D camera_direction;
	Stuff::Point3D local_origin(m_newLocalToParent);
	camera_direction.Subtract(parent_camera, local_origin);

	//
	//------------------------------------------------------------------------
	// Now align the matrix in the direction we wish it to point, then set the
	// matrix inside the element and sync
	//------------------------------------------------------------------------
	//
	m_newLocalToParent.AlignLocalAxisToWorldVector(camera_direction, Stuff::Z_Axis, Stuff::X_Axis, -1);

	//
	//------------------------------------------------------------------------
	// In order for the sync to work properly, we have to disable the deferred
	// bit for the actual sync, then re-enable it afterwords.  After the sync,
	// do the standard sphere cull
	//------------------------------------------------------------------------
	//
	m_state &= ~(SyncOnCullFlag|LocalToParentIsIdentityFlag);
	m_state |= MatrixDirtyFlag;
	SetSyncState();
	Sync();
	m_state |= SyncOnCullFlag;
	SetSyncState();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	ElementRenderer::Element::DeferredXNeverCullMethod(CameraElement *camera)
{
	Check_Object(this);
	Check_Object(camera);
	Verify(!IsMatrixDirty());

	AlignX(camera);
	return 0;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	ElementRenderer::Element::DeferredXAlwaysCullMethod(CameraElement *camera)
{
	Check_Object(this);
	Check_Object(camera);
	Verify(!IsMatrixDirty() && GetCullMode()==AlwaysCullMode);

	AlignX(camera);
	return -1;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	ElementRenderer::Element::DeferredXSphereCullMethod(CameraElement *camera)
{
	Check_Object(this);
	Check_Object(camera);
	Verify(
		!IsMatrixDirty() && IsBoundedBySphere() && GetCullMode()==VolumeCullMode
	);

	AlignX(camera);
	return SphereCullMethod(camera);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	ElementRenderer::Element::DeferredXOBBCullMethod(CameraElement *camera)
{
	Check_Object(this);
	Check_Object(camera);
	Verify(
		!IsMatrixDirty() && IsBoundedByOBB() && GetCullMode()==VolumeCullMode
	);

	AlignX(camera);
	return OBBCullMethod(camera);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::Element::AlignY(CameraElement *camera)
{
	Check_Object(this);
	Check_Object(camera);
	Verify(!IsMatrixDirty());

	//
	//----------------------------------------------------------------------
	// We have to move the camera into the same parent space as the local
	// matrix will be in, then figure out the relative direction the element
	// should face
	//----------------------------------------------------------------------
	//
	Check_Object(m_parent);
	Stuff::Point3D world_camera(camera->GetLocalToWorld());
	Stuff::Point3D parent_camera;
	parent_camera.MultiplyByInverse(
		world_camera,
		m_parent->GetLocalToWorld()
	);
	Stuff::Vector3D camera_direction;
	Stuff::Point3D local_origin(m_newLocalToParent);
	camera_direction.Subtract(parent_camera, local_origin);

	//
	//------------------------------------------------------------------------
	// Now align the matrix in the direction we wish it to point, then set the
	// matrix inside the element and sync
	//------------------------------------------------------------------------
	//
	m_newLocalToParent.AlignLocalAxisToWorldVector(camera_direction, Stuff::Z_Axis, Stuff::Y_Axis, -1);

	//
	//------------------------------------------------------------------------
	// In order for the sync to work properly, we have to disable the deferred
	// bit for the actual sync, then re-enable it afterwords.  After the sync,
	// do the standard sphere cull
	//------------------------------------------------------------------------
	//
	m_state &= ~(SyncOnCullFlag|LocalToParentIsIdentityFlag);
	m_state |= MatrixDirtyFlag;
	SetSyncState();
	Sync();
	m_state |= SyncOnCullFlag;
	SetSyncState();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	ElementRenderer::Element::DeferredYNeverCullMethod(CameraElement *camera)
{
	Check_Object(this);
	Check_Object(camera);
	Verify(!IsMatrixDirty());

	AlignY(camera);
	return 0;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	ElementRenderer::Element::DeferredYAlwaysCullMethod(CameraElement *camera)
{
	Check_Object(this);
	Check_Object(camera);
	Verify(!IsMatrixDirty() && GetCullMode()==AlwaysCullMode);

	AlignY(camera);
	return -1;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	ElementRenderer::Element::DeferredYSphereCullMethod(CameraElement *camera)
{
	Check_Object(this);
	Check_Object(camera);
	Verify(
		!IsMatrixDirty() && IsBoundedBySphere() && GetCullMode()==VolumeCullMode
	);

	AlignY(camera);
	return SphereCullMethod(camera);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	ElementRenderer::Element::DeferredYOBBCullMethod(CameraElement *camera)
{
	Check_Object(this);
	Check_Object(camera);
	Verify(
		!IsMatrixDirty() && IsBoundedByOBB() && GetCullMode()==VolumeCullMode
	);

	AlignY(camera);
	return OBBCullMethod(camera);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::Element::AlignXY(CameraElement *camera)
{
	Check_Object(this);
	Check_Object(camera);
	Verify(!IsMatrixDirty());

	//
	//----------------------------------------------------------------------
	// We have to move the camera into the same parent space as the local
	// matrix will be in, then figure out the relative direction the element
	// should face
	//----------------------------------------------------------------------
	//
	Check_Object(m_parent);
	Stuff::Point3D world_camera(camera->GetLocalToWorld());
	Stuff::Point3D parent_camera;
	parent_camera.MultiplyByInverse(
		world_camera,
		m_parent->GetLocalToWorld()
	);
	Stuff::Vector3D camera_direction;
	Stuff::Point3D local_origin(m_newLocalToParent);
	camera_direction.Subtract(parent_camera, local_origin);

	//
	//------------------------------------------------------------------------
	// Now align the matrix in the direction we wish it to point, then set the
	// matrix inside the element and sync
	//------------------------------------------------------------------------
	//
	Stuff::LinearMatrix4D old_pos = m_newLocalToParent;
	m_newLocalToParent.AlignLocalAxisToWorldVector(camera_direction, Stuff::Z_Axis, Stuff::Y_Axis, Stuff::X_Axis);

	//
	//------------------------------------------------------------------------
	// In order for the sync to work properly, we have to disable the deferred
	// bit for the actual sync, then re-enable it afterwords.  After the sync,
	// do the standard sphere cull
	//------------------------------------------------------------------------
	//
	m_state &= ~(SyncOnCullFlag|LocalToParentIsIdentityFlag);
	m_state |= MatrixDirtyFlag;
	SetSyncState();
	Sync();
	m_state |= SyncOnCullFlag;
	SetSyncState();
	m_newLocalToParent = old_pos;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	ElementRenderer::Element::DeferredXYNeverCullMethod(CameraElement *camera)
{
	Check_Object(this);
	Check_Object(camera);
	Verify(!IsMatrixDirty());

	AlignXY(camera);
	return 0;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	ElementRenderer::Element::DeferredXYAlwaysCullMethod(CameraElement *camera)
{
	Check_Object(this);
	Check_Object(camera);
	Verify(!IsMatrixDirty() && GetCullMode()==AlwaysCullMode);

	AlignXY(camera);
	return -1;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	ElementRenderer::Element::DeferredXYSphereCullMethod(CameraElement *camera)
{
	Check_Object(this);
	Check_Object(camera);
	Verify(
		!IsMatrixDirty() && IsBoundedBySphere() && GetCullMode()==VolumeCullMode
	);

	AlignXY(camera);
	return SphereCullMethod(camera);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	ElementRenderer::Element::DeferredXYOBBCullMethod(CameraElement *camera)
{
	Check_Object(this);
	Check_Object(camera);
	Verify(
		!IsMatrixDirty() && IsBoundedByOBB() && GetCullMode()==VolumeCullMode
	);

	AlignXY(camera);
	return OBBCullMethod(camera);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
bool
	ElementRenderer::Element::NeverRayCullMethod(CollisionQuery *query)
{
	Check_Object(this);
	Check_Object(query);
	Verify(GetCullMode() == NeverCullMode || GetCullMode() == RootMode);
	Verify(!query->m_data);

	return true;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
bool
	ElementRenderer::Element::AlwaysRayCullMethod(CollisionQuery *query)
{
	Check_Object(this);
	Check_Object(query);
	Verify(GetCullMode() == AlwaysCullMode);
	Verify(!query->m_data);

	return false;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
bool
	ElementRenderer::Element::SphereRayCullMethod(CollisionQuery *query)
{
	Check_Object(this);
	Check_Object(query);
	Verify(m_worldOBB.sphereRadius > 0.0f);
	Verify(GetCullMode()==VolumeCullMode && IsBoundedBySphere());
	Verify(!query->m_data);

	//
	//-------------------------------
	// We only use the sphere portion
	//-------------------------------
	//
	Stuff::Sphere sphere(m_worldOBB);
	Stuff::Scalar penetration;
	Check_Object(query->m_line);
	return query->m_line->GetDistanceTo(sphere, &penetration) >= 0.0f;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
bool
	ElementRenderer::Element::OBBRayCullMethod(CollisionQuery *query)
{
	Check_Object(this);
	Check_Object(query);
	Verify(GetCullMode()==VolumeCullMode && IsBoundedByOBB());
	Verify(Stuff::Close_Enough(m_localOBB.sphereRadius, m_localOBB.axisExtents.GetLength(), m_localOBB.sphereRadius*0.02f));
	Verify(!query->m_data);

	//
	//-------------------------------
	// We only use the sphere portion
	//-------------------------------
	//
	Check_Object(query->m_line);
	return query->m_line->GetDistanceTo(m_worldOBB) >= 0.0f;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	ElementRenderer::Element::NeverSphereTestMethod(SphereTest *query)
{
	Check_Object(this);
	Check_Object(query);
	Verify(GetCullMode() == NeverCullMode || GetCullMode() == RootMode);

	return query->m_type;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	ElementRenderer::Element::AlwaysSphereTestMethod(SphereTest *query)
{
	Check_Object(this);
	Check_Object(query);
	Verify(GetCullMode() == AlwaysCullMode);

	return SphereTest::Disjoint;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	ElementRenderer::Element::SphereSphereTestMethod(SphereTest *query)
{
	Check_Object(this);
	Check_Object(query);
	Verify(m_worldOBB.sphereRadius > 0.0f);
	Verify(GetCullMode()==VolumeCullMode && IsBoundedBySphere());

	//
	//-------------------------------
	// We only use the sphere portion
	//-------------------------------
	//
	Stuff::Sphere sphere(m_worldOBB);
	Stuff::Vector3D diff;
	diff.Subtract(sphere.center, query->m_sphere.center);
	int result = SphereTest::Disjoint;
	Stuff::Scalar range = diff.GetLengthSquared();
	Stuff::Scalar slop;
	switch (query->m_type)
	{
	case SphereTest::Contains:
		slop = sphere.radius - query->m_sphere.radius;
		if (slop >= 0.0f)
		{
			if (range <= slop*slop)
				result = SphereTest::Contains;
		}
		break;

	case SphereTest::Intersects:
		slop = sphere.radius + query->m_sphere.radius;
		if (range <= slop*slop)
			result = SphereTest::Intersects;
		break;

	case SphereTest::Contained:
		slop = sphere.radius - query->m_sphere.radius;
		if (slop <= 0.0f)
		{
			if (range <= slop*slop)
				result = SphereTest::Contained;
		}
		break;

	default:
		slop = sphere.radius + query->m_sphere.radius;
		if (range <= slop*slop)
		{
			result |= SphereTest::Intersects;
			slop = sphere.radius - query->m_sphere.radius;
			if (slop >= 0.0f)
			{
				if (range <= slop*slop)
					result |= SphereTest::Contains;
			}
			if (slop <= 0.0f)
			{
				if (range <= slop*slop)
					result |= SphereTest::Contained;
			}
		}
		break;
	}
	return result;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	ElementRenderer::Element::OBBSphereTestMethod(SphereTest *query)
{
	Check_Object(this);
	Check_Object(query);
	Verify(GetCullMode()==VolumeCullMode && IsBoundedByOBB());
	Verify(Stuff::Close_Enough(m_localOBB.sphereRadius, m_localOBB.axisExtents.GetLength(), m_localOBB.sphereRadius*0.02f));

	STOP(("Not implemented"));
	return SphereTest::Disjoint;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
int
	ElementRenderer::Element::MixLights(
		LightList new_lights,
		const LightList parent_lights,
		const LightList child_lights
	)
{
	unsigned i;
	for (i=0; i<MidLevelRenderer::Limits::Max_Number_Of_Lights_Per_Primitive && parent_lights[i]; ++i)
		new_lights[i] = parent_lights[i];
	for (int j=0; i<MidLevelRenderer::Limits::Max_Number_Of_Lights_Per_Primitive && child_lights[j]; ++i,++j)
	{
		Verify(j < MidLevelRenderer::Limits::Max_Number_Of_Lights_Per_Primitive);
		new_lights[i] = child_lights[j];
	}
	if (i < MidLevelRenderer::Limits::Max_Number_Of_Lights_Per_Primitive)
		new_lights[i] = NULL;
	Verify(i <= MidLevelRenderer::Limits::Max_Number_Of_Lights_Per_Primitive);
	return i;
}

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

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

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

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

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::Element::DrawOBB(
		const Stuff::OBB& obb,
		const Stuff::RGBAColor &color,
		CameraElement *camera,
		const StateChange *state,
		int culling_state
	)
{
	Check_Object(&obb);
	Check_Object(&color);
	Check_Object(camera);
	Check_Object(state);

	//
	//------------------
	// Set up the fields
	//------------------
	//
	MidLevelRenderer::DrawScalableShapeInformation info;
	info.clippingFlags.SetClippingState(culling_state);
	info.shapeToWorld = &obb.localToParent;
	info.worldToShape = NULL;
	info.paintMeColor = &color;
	info.state = state->GetMLRState();
	info.state.SetTextureHandle(0);
	info.state.SetAlphaMode(MidLevelRenderer::MLRState::AlphaInvAlphaMode);
	info.state.SetZBufferWriteOff();
	info.state.SetPriority(1);
	info.state.SetFogMode(0);
	Check_Object(Box_Object);
	info.shape = Box_Object;
	info.scaling = &obb.axisExtents;

	//
	//---------------
	// Draw the shape
	//---------------
	//
	MidLevelRenderer::MLRClipper *clipper = camera->GetClipper();
	Check_Object(clipper);
	clipper->DrawScalableShape(&info);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::Element::DrawSphere(
		const Stuff::OBB& obb,
		const Stuff::RGBAColor &color,
		CameraElement *camera,
		const StateChange *state,
		int culling_state
	)
{
	Check_Object(&obb);
	Check_Object(&color);
	Check_Object(camera);
	Check_Object(state);

	//
	//------------------
	// Set up the fields
	//------------------
	//
	MidLevelRenderer::DrawScalableShapeInformation info;
	info.clippingFlags.SetClippingState(culling_state);
	info.shapeToWorld = &obb.localToParent;
	info.worldToShape = NULL;
	info.paintMeColor = &color;
	info.state = state->GetMLRState();
	info.state.SetTextureHandle(0);
	info.state.SetAlphaMode(MidLevelRenderer::MLRState::AlphaInvAlphaMode);
	info.state.SetZBufferWriteOff();
	info.state.SetPriority(1);
	info.state.SetFogMode(0);
	Verify(Stuff::Small_Enough(obb.axisExtents.GetLengthSquared()));
	Stuff::Vector3D scale;
	Check_Object(Sphere_Object);
	info.shape = Sphere_Object;
	info.scaling = &scale;
	scale.x = scale.y = scale.z = obb.sphereRadius;

	//
	//---------------
	// Draw the shape
	//---------------
	//
	MidLevelRenderer::MLRClipper *clipper = camera->GetClipper();
	Check_Object(clipper);
	clipper->DrawScalableShape(&info);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::Element::DrawGreenBoundsCallback(
		CameraElement *camera,
		const StateChange *state,
		int culling_state,
		Element *element
	)
{
	Check_Object(camera);
	Check_Object(state);
	Check_Object(element);

	//
	//----------------------------------
	// Only volume cull bounds are shown
	//----------------------------------
	//
	if (element->GetCullMode() != VolumeCullMode)
		return;

	//
	//------------------
	// Draw green bounds
	//------------------
	//
	Stuff::RGBAColor color(0.5f, 1.0f, 0.5f, 0.3f);
	if (element->IsBoundedBySphere())
		DrawSphere(element->m_worldOBB, color, camera, state, culling_state);
	else
		DrawOBB(element->m_worldOBB, color, camera, state, culling_state);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::Element::DrawYellowBoundsCallback(
		CameraElement *camera,
		const StateChange *state,
		int culling_state,
		Element *element
	)
{
	Check_Object(camera);
	Check_Object(state);
	Check_Object(element);

	//
	//----------------------------------
	// Only volume cull bounds are shown
	//----------------------------------
	//
	if (element->GetCullMode() != VolumeCullMode)
		return;

	//
	//-------------------
	// Draw yellow bounds
	//-------------------
	//
	Stuff::RGBAColor color(1.0f, 1.0f, 0.5f, 0.3f);
	if (element->IsBoundedBySphere())
		DrawSphere(element->m_worldOBB, color, camera, state, culling_state);
	else
		DrawOBB(element->m_worldOBB, color, camera, state, culling_state);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::Element::DrawRedBoundsCallback(
		CameraElement *camera,
		const StateChange *state,
		int culling_state,
		Element *element
	)
{
	Check_Object(camera);
	Check_Object(state);
	Check_Object(element);

	//
	//----------------------------------
	// Only volume cull bounds are shown
	//----------------------------------
	//
	if (element->GetCullMode() != VolumeCullMode)
		return;

	//
	//----------------
	// Draw red bounds
	//----------------
	//
	Stuff::RGBAColor color(1.0f, 0.5f, 0.5f, 0.3f);
	if (element->IsBoundedBySphere())
		DrawSphere(element->m_worldOBB, color, camera, state, culling_state);
	else
		DrawOBB(element->m_worldOBB, color, camera, state, culling_state);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::Element::DrawGreenBoundsOnceCallback(
		CameraElement *camera,
		const StateChange *state,
		int culling_state,
		Element *element
	)
{
	Check_Object(camera);
	Check_Object(state);
	Check_Object(element);

	//
	//----------------------------------
	// Only volume cull bounds are shown
	//----------------------------------
	//
	if (element->GetCullMode() != VolumeCullMode)
		return;

	//
	//------------------
	// Draw green bounds
	//------------------
	//
	Stuff::RGBAColor color(0.5f, 1.0f, 0.5f, 0.3f);
	if (element->IsBoundedBySphere())
		DrawSphere(element->m_worldOBB, color, camera, state, culling_state);
	else
		DrawOBB(element->m_worldOBB, color, camera, state, culling_state);
	element->SetCallbackIndex(0);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::Element::DrawYellowBoundsOnceCallback(
		CameraElement *camera,
		const StateChange *state,
		int culling_state,
		Element *element
	)
{
	Check_Object(camera);
	Check_Object(state);
	Check_Object(element);

	//
	//----------------------------------
	// Only volume cull bounds are shown
	//----------------------------------
	//
	if (element->GetCullMode() != VolumeCullMode)
		return;

	//
	//-------------------
	// Draw yellow bounds
	//-------------------
	//
	Stuff::RGBAColor color(1.0f, 1.0f, 0.5f, 0.3f);
	if (element->IsBoundedBySphere())
		DrawSphere(element->m_worldOBB, color, camera, state, culling_state);
	else
		DrawOBB(element->m_worldOBB, color, camera, state, culling_state);
	element->SetCallbackIndex(0);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::Element::DrawRedBoundsOnceCallback(
		CameraElement *camera,
		const StateChange *state,
		int culling_state,
		Element *element
	)
{
	Check_Object(camera);
	Check_Object(state);
	Check_Object(element);

	//
	//----------------------------------
	// Only volume cull bounds are shown
	//----------------------------------
	//
	if (element->GetCullMode() != VolumeCullMode)
		return;

	//
	//----------------
	// Draw red bounds
	//----------------
	//
	Stuff::RGBAColor color(1.0f, 0.5f, 0.5f, 0.3f);
	if (element->IsBoundedBySphere())
		DrawSphere(element->m_worldOBB, color, camera, state, culling_state);
	else
		DrawOBB(element->m_worldOBB, color, camera, state, culling_state);
	element->SetCallbackIndex(0);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::Element::AdoptStateChange(StateChange *set)
{
	Check_Object(this);
	Check_Object(set);

	//
	//--------------------------------------------
	// Detach the old property set if it was there
	//--------------------------------------------
	//
	if (m_stateChange)
	{
		Check_Object(m_stateChange);
		delete m_stateChange;
	}

	//
	//----------------------------
	// Attach the new property set
	//----------------------------
	//
	m_stateChange = set;
	m_state |= PropertyChangeFlag;
	SetDrawState();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	ElementRenderer::Element::DestroyStateChange()
{
	Check_Object(this);

	//
	//--------------------------------------------
	// Detach the old property set if it was there
	//--------------------------------------------
	//
	if (m_stateChange)
	{
		Check_Object(m_stateChange);
		delete m_stateChange;
		m_stateChange = NULL;
		m_state &= ~PropertyChangeFlag;
		SetDrawState();
	}
}

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

//#############################################################################
//##########################    Element__ClassData    #########################
//#############################################################################

void
	ElementRenderer::Element__ClassData::TestInstance()
{
	Verify(IsDerivedFrom(ElementRenderer::Element::DefaultData));
}
