//===========================================================================//
// File:	Map.cpp                                                   //
// Project: MUNGA           Brick: Interest Manager	                       //
// Contents: Interface specifications for interest manager						  //
//---------------------------------------------------------------------------//
//   Date    Who  Modification                                               //
// --------  ---  -----------------------------------------------------------//
// 11/04/94  ECH  Initial coding.                                            //
// 11/29/94  JMA  Changed Identities to IDs                                  //
// 02/10/95  CPB  Added GaugeInterestType                                    //
// 08/25/97  ECH  Infrastructure changes.                                    //
//---------------------------------------------------------------------------//
// Copyright (C) 1994-1995, Virtual World Entertainment, Inc.                //
// All Rights reserved worldwide                                             //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL               //
//===========================================================================//

#include "AdeptHeaders.hpp"
#include "Zone.hpp"
#include "Map.hpp"
#include "RendererManager.hpp"
#include "ExecutionState.hpp"
#include "Application.hpp"
#include <ElementRenderer\GridElement.hpp>
#include <Compost\TerrainTextureLogistic.hpp>
#include <Compost\TexturePool.hpp>
#include "Tile.hpp"
#include "CollisionVolume.hpp"
#include "CollisionGrid.hpp"
#include "LightManager.hpp"
#include <ElementRenderer\StateChange.hpp>
#include <ElementRenderer\CameraElement.hpp>
#include <ElementRenderer\LightElement.hpp>
#include <MLR\MLRInfiniteLightWithFalloff.hpp>
#include "dropzone.hpp"

#ifdef LAB_ONLY
#include <GameOS\ToolOS.hpp>
bool
	saveMapBspDifference = false;
#endif

//#############################################################################
//###########################    Zone    #################################
//#############################################################################


Zone::ClassData
	*Zone::DefaultData = NULL;

DWORD Executed_Zone_Entity_Count;
DWORD Zone_Entity_Count;
DWORD Tile_Entity_Count;

static bool Show_Lights=false;
static bool Hide_ZoneEntities=false;
static bool __stdcall CheckHideZoneEntities() {return Hide_ZoneEntities;}
static void __stdcall EnableHideZoneEntities() {Hide_ZoneEntities = !Hide_ZoneEntities;}
static bool __stdcall CheckShowLights() {return Show_Lights!=0;}
static void __stdcall EnableShowLights() {Show_Lights = !Show_Lights;}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Zone::InitializeClass()
{
	Verify(!DefaultData);
	DefaultData =
		new ClassData(
			ZoneClassID,
			"Adept::Zone",
			ElementRenderer::GroupElement::DefaultData,
			reinterpret_cast<Factory>(&Make)
		);
	Register_Object(DefaultData);

	Zone_Entity_Count = 0;
	Tile_Entity_Count = 0;

#if !defined(NO_STATS)
	AddStatistic("Zone Entities", "entities", gos_DWORD, &Zone_Entity_Count, 0);
	AddStatistic("Tile Entities", "entities", gos_DWORD, &Tile_Entity_Count, 0);
	AddStatistic("Executed Zone Entities", "entities", gos_DWORD, &Executed_Zone_Entity_Count, Stat_AutoReset);
#endif

	AddDebuggerMenuItem("Libraries\\Adept\\Hide Zone Entities", CheckHideZoneEntities, EnableHideZoneEntities, 0 );
	AddDebuggerMenuItem("Libraries\\Adept\\Show lights", CheckShowLights, EnableShowLights, 0 );
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Zone::TerminateClass()
{
	Unregister_Object(DefaultData);
	delete DefaultData;
	DefaultData = NULL;
}

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Zone::Zone():
	GroupElement(DefaultData),
	m_dormant(NULL),
	m_simulated(NULL),
	m_rendered(NULL),
	m_executors(NULL),
	m_tiledEntities(NULL),
	m_flags(0)
{
	m_draw = static_cast<DrawMethod>(&Zone::Draw);
	AdoptStateChange(new ElementRenderer::StateChange);
	Check_Pointer(this);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Zone::~Zone()
{
	Check_Object(this);

	//
	//------------------------------------
	// Detach any children that were tiled
	//------------------------------------
	//
	ChainIteratorOf<ElementRenderer::Element*> tiled_children(&m_tiledEntities);
	ElementRenderer::Element *child;
	while ((child = tiled_children.ReadAndNext()) != NULL) {
	  if (child->GetParentElement()) // jcem... why?
		child->DetachFromParent();
	}

	//
	//-------------------------------------------------------------------------
	// Delete the ground element, then detach anything else hooked to this zone
	//-------------------------------------------------------------------------
	//
	ChainIteratorOf<ElementRenderer::Element*> children(&m_group);
	while ((child = children.ReadAndNext()) != NULL) {
	  if (child->GetParentElement()) // jcem... why?
		child->DetachFromParent();
	}
	Check_Object(m_tileGrid);
	delete m_tileGrid;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Zone::Save(MemoryStream *stream)
{
	Check_Object(this);
	Check_Object(stream);

	//
	//--------------------------
	// Write stuff to the stream
	//--------------------------
	//
	*stream << static_cast<int>(ElementRenderer::GroupElementClassID) << 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
	{
		Point3D center(m_localOBB.localToParent);
		*stream << center;
		*stream << m_localOBB.sphereRadius;
	}

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

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Zone::CreateTileGrid(
		Stuff::Scalar row,
		Stuff::Scalar column
	)
{
	Check_Object(this);

	//
	//------------------------------------------------
	// Create the tile grid and attach it to ourselves
	//------------------------------------------------
	//
	m_tileGrid =
		new ElementRenderer::GridElement(
			e_TilesPerZone,
			e_TilesPerZone,
			row,
			column,
			c_ZoneSize,
			c_ZoneSize
		);
	Check_Object(m_tileGrid);
	AttachChild(m_tileGrid);
	m_tileGrid->LockBounds();
	Check_Object(CollisionGrid::Instance);

	//
	//-----------------------------------------------------------------
	// Now create a group node under each tile.  Also hook it up to the
	// collision grid
	//-----------------------------------------------------------------
	//
	Verify((m_row+1)*e_TilesPerZone <= 256);
	Verify((m_column+1)*e_TilesPerZone <= 256);
	for (BYTE r=0; r<e_TilesPerZone; ++r)
	{
		for (BYTE c=0; c<e_TilesPerZone; ++c)
		{
			Tile *tile =
				new Tile(
					this,
					static_cast<BYTE>(m_row*e_TilesPerZone+r),
					static_cast<BYTE>(m_column*e_TilesPerZone+c)
				);
			Check_Object(tile);
			CollisionGrid::Instance->AttachIndexedChild(
				tile->m_row,
				tile->m_column,
				tile
			);
			m_tileGrid->AttachIndexedChild(r, c, tile);
			tile->LockBounds();
		}
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Zone::SetInterestLevel(
		Entity* entity,
		Entity::InterestLevel type
	)
{
	Check_Object(this);
	Check_Object(entity);
	Verify(!entity->m_pendingInterestMessage.GetCurrent());

	//
	//--------------------------------------
	// Make sure this isn't a trivial change
	//--------------------------------------
	//
	Entity::InterestLevel
		zone_interest = GetInterestLevel(),
		entity_interest = entity->GetInterestLevel();
	if (type == entity_interest)
		return;

	//
	//-----------------------------------------------------------------
	// Look for the interaction between the zone and new interest state
	//-----------------------------------------------------------------
	//
	switch ((zone_interest<<2) | type)
	{
	case (Entity::DormantInterestLevel<<2)|Entity::DormantInterestLevel:
		Verify(m_dormant.IsPlugMember(entity));
		Verify(!m_simulated.IsPlugMember(entity));
		Verify(!m_rendered.IsPlugMember(entity));
		entity->BecomeUninteresting();
		Verify(!m_executors.IsPlugMember(entity));
		break;

	case (Entity::DormantInterestLevel<<2)|Entity::RenderingInterestLevel:
		Verify(m_dormant.IsPlugMember(entity));
		Verify(!m_simulated.IsPlugMember(entity));
		Verify(!m_rendered.IsPlugMember(entity));
		Verify(!m_executors.IsPlugMember(entity));
		break;

	case (Entity::DormantInterestLevel<<2)|Entity::SimulationInterestLevel:
		Verify(m_dormant.IsPlugMember(entity));
		Verify(!m_simulated.IsPlugMember(entity));
		Verify(!m_rendered.IsPlugMember(entity));
		Verify(!m_executors.IsPlugMember(entity));
		break;

	case (Entity::SimulationInterestLevel<<2)|Entity::DormantInterestLevel:
		Verify(!m_dormant.IsPlugMember(entity));
		Verify(m_simulated.IsPlugMember(entity));
		Verify(!m_rendered.IsPlugMember(entity));
		m_simulated.RemovePlug(entity);
		m_dormant.Add(entity);
		entity->BecomeUninteresting();
		Verify(!m_executors.IsPlugMember(entity));
		break;

	case (Entity::SimulationInterestLevel<<2)|Entity::SimulationInterestLevel:
		if (entity_interest == Entity::DormantInterestLevel)
		{
			Verify(m_dormant.IsPlugMember(entity));
			Verify(!m_simulated.IsPlugMember(entity));
			Verify(!m_rendered.IsPlugMember(entity));
			m_dormant.RemovePlug(entity);
			m_simulated.Add(entity);
			entity->BecomeInteresting(false);
		}
		break;

	case (Entity::SimulationInterestLevel<<2)|Entity::RenderingInterestLevel:
		if (entity_interest == Entity::DormantInterestLevel)
		{
			Verify(m_dormant.IsPlugMember(entity));
			Verify(!m_simulated.IsPlugMember(entity));
			Verify(!m_rendered.IsPlugMember(entity));
			m_dormant.RemovePlug(entity);
			m_simulated.Add(entity);
			entity->BecomeInteresting(false);
		}
		break;

	case (Entity::RenderingInterestLevel<<2)|Entity::DormantInterestLevel:
		if (entity_interest == Entity::SimulationInterestLevel)
		{
			Verify(!m_rendered.IsPlugMember(entity));
			Verify(m_simulated.IsPlugMember(entity));
			m_simulated.RemovePlug(entity);
		}
		else
		{
			Verify(!m_simulated.IsPlugMember(entity));
			Verify(m_rendered.IsPlugMember(entity));
			m_rendered.RemovePlug(entity);
		}
		Verify(!m_dormant.IsPlugMember(entity));
		m_dormant.Add(entity);
		entity->BecomeUninteresting();
		Verify(!m_executors.IsPlugMember(entity));
		break;

	case (Entity::RenderingInterestLevel<<2)|Entity::SimulationInterestLevel:
		Verify(!m_simulated.IsPlugMember(entity));
		if (entity_interest == Entity::DormantInterestLevel)
		{
			Verify(m_dormant.IsPlugMember(entity));
			m_dormant.RemovePlug(entity);
		}
		else
		{
			Verify(m_rendered.IsPlugMember(entity));
			m_rendered.RemovePlug(entity);
		}
		Verify(!m_simulated.IsPlugMember(entity));
		m_simulated.Add(entity);
		entity->BecomeInteresting(false);
		break;

	case (Entity::RenderingInterestLevel<<2)|Entity::RenderingInterestLevel:
		Verify(!m_rendered.IsPlugMember(entity));
		if (entity_interest == Entity::DormantInterestLevel)
		{
			Verify(m_dormant.IsPlugMember(entity));
			m_dormant.RemovePlug(entity);
		}
		else
		{
			Verify(m_simulated.IsPlugMember(entity));
			m_simulated.RemovePlug(entity);
		}
		Verify(!m_rendered.IsPlugMember(entity));
		m_rendered.Add(entity);
		entity->BecomeInteresting(true);
		break;

	default:
		STOP(("Illegal interest level setting"));
		break;
	}

	//
	//--------------------
	// Set the entity mask
	//--------------------
	//
	entity->SetInterestMask(type);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Zone::AttachChild(ElementRenderer::Element *child)
{
	Check_Object(this);
	Check_Object(child);
	Verify(
		Close_Enough(
			GetLocalToWorld(),
			LinearMatrix4D::Identity
		)
	);
	Verify(
		Close_Enough(
			GetNewLocalToParent(),
			LinearMatrix4D::Identity
		)
	);
	Verify(!m_rendered.IsPlugMember(child));
	Verify(!m_simulated.IsPlugMember(child));
	Verify(!m_dormant.IsPlugMember(child));

#ifdef _DEBUG // jcem
	Entity* pEnt = static_cast<Entity*>(child->GetClientData());
	if (pEnt && pEnt->IsDerivedFrom(DropZone::DefaultData)) {
		pEnt++;
		pEnt--;
	}
#endif // _DEBUG
	//
	//-------------------------------------------------------------------
	// If this is the ground, attach it directly to ourselves and be done
	//-------------------------------------------------------------------
	//
	if (child == m_tileGrid)
	{
		GroupElement::AttachChild(child);
		return;
	}

	//
	//-------------------------------------------------------------------
	// Attach the entity to the appropriate parent in the video hierarchy
	//-------------------------------------------------------------------
	//
	Entity *entity = Cast_Object(Entity*, child->GetClientData());
	Verify(!entity->m_pendingInterestMessage.GetCurrent());
	Verify(!m_executors.IsPlugMember(child));
	Tile *tile = NULL;
	if (!entity->IsTileBound())
	{
		GroupElement::AttachChild(child);
		Set_Statistic(Zone_Entity_Count, Zone_Entity_Count+1);
		gosASSERT(!m_tiledEntities.IsPlugMember(child));
	}
	else
	{
		Set_Statistic(Tile_Entity_Count, Tile_Entity_Count+1);
		Point3D location(child->GetNewLocalToParent());
		ElementRenderer::GridElement *tile_grid =
			Cast_Object(ElementRenderer::GridElement*, m_tileGrid);
		Verify(
			Close_Enough(
				tile_grid->GetLocalToWorld(),
				LinearMatrix4D::Identity
			)
		);
		Verify(
			Close_Enough(
				tile_grid->GetNewLocalToParent(),
				LinearMatrix4D::Identity
			)
		);
		WORD index = tile_grid->FindElementIndex(location.z, location.x);
		tile =
			Cast_Object(
				Tile*,
				tile_grid->ListElement::GetIndexedElement(index)
			);
		tile->AttachChild(child);
		Verify(!m_tiledEntities.IsPlugMember(child));
		m_tiledEntities.Add(child);
	}

	//
	//-----------------------------------------------------------------
	// Look at the combination of the zone/entity interest level to see
	// what chains to hook up
	//-----------------------------------------------------------------
	//
	Entity::InterestLevel
		zone_interest = GetInterestLevel(),
		entity_interest = entity->GetInterestLevel();

	switch ((zone_interest<<2) | entity_interest)
	{
	case (Entity::DormantInterestLevel<<2)|Entity::DormantInterestLevel:
	case (Entity::DormantInterestLevel<<2)|Entity::RenderingInterestLevel:
	case (Entity::DormantInterestLevel<<2)|Entity::SimulationInterestLevel:
		{
			
			if (!entity->IsNeverExecuteState())
			{
				EnableExecution();
				Verify(entity != Map::GetInstance());
				m_executors.Add(entity);
			}
		}
		break;

	case (Entity::SimulationInterestLevel<<2)|Entity::DormantInterestLevel:
		{
			m_simulated.Add(entity);
			entity->BecomeInteresting(false);
Non_Dormant_Zone_Gets_Dormant_Entity:
			if (!entity->IsNeverExecuteState())
			{
				Verify(entity != Map::GetInstance());
				m_executors.Add(entity);
			}
			HookUpCollision(entity);
		}
		break;

	case (Entity::SimulationInterestLevel<<2)|Entity::SimulationInterestLevel:
		{
			m_simulated.Add(entity);
			if (!entity->IsNeverExecuteState())
			{
				Verify(entity != Map::GetInstance());
				m_executors.Add(entity);
			}
			HookUpCollision(entity);
		}
		break;

	case (Entity::SimulationInterestLevel<<2)|Entity::RenderingInterestLevel:
		{
			m_simulated.Add(entity);
			if (!entity->IsNeverExecuteState())
			{
				Verify(entity != Map::GetInstance());
				m_executors.Add(entity);
			}
			HookUpCollision(entity);
		}
		break;

	case (Entity::RenderingInterestLevel<<2)|Entity::DormantInterestLevel:
		m_rendered.Add(entity);
		entity->BecomeInteresting(true);
		goto Non_Dormant_Zone_Gets_Dormant_Entity;

	case (Entity::RenderingInterestLevel<<2)|Entity::SimulationInterestLevel:
		{
			m_rendered.Add(entity);
			entity->BecomeInteresting(true);
			if (!entity->IsNeverExecuteState())
			{
				Verify(entity != Map::GetInstance());
				m_executors.Add(entity);
			}
			HookUpCollision(entity);
		}
		break;

	case (Entity::RenderingInterestLevel<<2)|Entity::RenderingInterestLevel:
		{
			m_rendered.Add(entity);
			if (!entity->IsNeverExecuteState())
			{
				Verify(entity != Map::GetInstance());
				m_executors.Add(entity);
			}
			HookUpCollision(entity);
		}
		break;

	default:
		STOP(("Illegal interest level setting"));
		break;
	}
}

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

#ifdef _DEBUG // jcem
	Entity* pEnt = static_cast<Entity*>(child->GetClientData());
	if (pEnt && pEnt->IsDerivedFrom(DropZone::DefaultData)) {
		pEnt++;
		pEnt--;
	}
#endif // _DEBUG
	//
	//-----------------------------------------------------
	// If this is the tile grid, let GroupElement handle it
	//-----------------------------------------------------
	//
	if (child == m_tileGrid)
	{
		GroupElement::DetachChild(child);
		return;
	}

	//
	//-------------------------------------------------------
	// Remove the entity from execution if it is on the chain
	//-------------------------------------------------------
	//
	Entity *entity = Cast_Object(Entity*, child->GetClientData());

	if (!entity->IsNeverExecuteState())
	{
		Verify(m_executors.IsPlugMember(entity));
		RemoveExecutor(entity);
	}
	else
		Verify(!m_executors.IsPlugMember(entity));

	//
	//-------------------
	// Deal with the zone
	//-------------------
	//
	Entity::InterestLevel
		zone_interest = GetInterestLevel(),
		entity_interest = entity->GetInterestLevel();

	switch ((zone_interest<<2) | entity_interest)
	{
	case (Entity::DormantInterestLevel<<2)|Entity::DormantInterestLevel:
	case (Entity::DormantInterestLevel<<2)|Entity::SimulationInterestLevel:
	case (Entity::DormantInterestLevel<<2)|Entity::RenderingInterestLevel:
	case (Entity::SimulationInterestLevel<<2)|Entity::DormantInterestLevel:
	case (Entity::RenderingInterestLevel<<2)|Entity::DormantInterestLevel:
		Verify(m_dormant.IsPlugMember(entity));
		Verify(!m_simulated.IsPlugMember(entity));
		Verify(!m_rendered.IsPlugMember(entity));
		m_dormant.RemovePlug(entity);
		break;

	case (Entity::SimulationInterestLevel<<2)|Entity::RenderingInterestLevel:
	case (Entity::SimulationInterestLevel<<2)|Entity::SimulationInterestLevel:
	case (Entity::RenderingInterestLevel<<2)|Entity::SimulationInterestLevel:
		UnhookCollision(entity);
Not_Quite_Done:
		Verify(!m_dormant.IsPlugMember(entity));
		Verify(m_simulated.IsPlugMember(entity));
		Verify(!m_rendered.IsPlugMember(entity));
		m_simulated.RemovePlug(entity);
		break;

	case (Entity::RenderingInterestLevel<<2)|Entity::RenderingInterestLevel:
		UnhookCollision(entity);
		if (entity->m_pendingInterestMessage.GetCurrent())
			goto Not_Quite_Done;
		Verify(!m_dormant.IsPlugMember(entity));
		Verify(!m_simulated.IsPlugMember(entity));
		Verify(m_rendered.IsPlugMember(entity));
		m_rendered.RemovePlug(entity);
		break;

	default:
		STOP(("Illegal interest level setting"));
		break;
	}

	//
	//---------------------------------------------------------------------
	// Detach the entity from the appropriate parent in the video hierarchy
	//---------------------------------------------------------------------
	//
	Tile *tile = NULL;
	if (!entity->IsTileBound())
	{
		GroupElement::DetachChild(child);
		Set_Statistic(Zone_Entity_Count, Zone_Entity_Count-1);
		gosASSERT(!m_tiledEntities.IsPlugMember(child));
	}
	else
	{
		Set_Statistic(Tile_Entity_Count, Tile_Entity_Count-1);
		Point3D location(child->GetNewLocalToParent());
		ElementRenderer::GridElement *tile_grid =
			Cast_Object(ElementRenderer::GridElement*, m_tileGrid);
		Verify(
			Close_Enough(
				tile_grid->GetLocalToWorld(),
				LinearMatrix4D::Identity
			)
		);
		Verify(
			Close_Enough(
				tile_grid->GetNewLocalToParent(),
				LinearMatrix4D::Identity
			)
		);
		WORD index = tile_grid->FindElementIndex(location.z, location.x);
		tile =
			Cast_Object(
				Tile*,
				tile_grid->ListElement::GetIndexedElement(index)
			);
		Verify(tile == child->GetParentElement());
		tile->DetachChild(child);
		m_tiledEntities.RemovePlug(child);
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Zone::SetDrawState()
{
	Check_Object(this);
	m_draw = static_cast<DrawMethod>(&Zone::Draw);
}

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

	//
	//-------------------
	// Mix the properties
	//-------------------
	//
	ElementRenderer::StateChange mixed;
	mixed.Mix(*inherited_state, *m_stateChange);
	int zone_lights = MixLights(
		mixed.m_lights,
		inherited_state->m_lights,
		m_stateChange->m_lights
	);
	Verify(zone_lights <= MidLevelRenderer::Limits::Max_Number_Of_Lights_Per_Primitive);

	//
	//--------------------------------------
	// 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 the tile grid
	//-------------------
	//
	ChainIteratorOf<ElementRenderer::Element*> children(&m_group);
	ElementRenderer::Element *child = children.ReadAndNext();
	Check_Object(child);
	Verify(Cast_Object(ElementRenderer::GridElement*, child) != NULL);
	camera->DrawElement(child, inherited_state);

	//
	//-------------------------------------------------------------------
	// We will only process a child for lights if it is not culled, so do
	// that part of the function ourself
	//-------------------------------------------------------------------
	//
	Check_Object(CollisionGrid::Instance);
	Scalar row_scale = 1.0f/CollisionGrid::Instance->m_rowCellSize;
	Scalar column_scale = 1.0f/CollisionGrid::Instance->m_columnCellSize;
	#if defined(LAB_ONLY)
		if (Hide_ZoneEntities)
			goto Do_Lights;
	#endif
	while ((child = children.ReadAndNext()) != NULL)
	{
		Check_Object(child);
		Verify(child->GetClassID() == ElementRenderer::GroupElementClassID);
		int culling_state = child->Cull(camera);
		if (culling_state == -1)
			continue;

		//
		//--------------------------------------------------------------------
		// The child was not culled, so check it for lights.  To get lighting,
		// it needs a bounding volume and needs to enable the light flags
		//--------------------------------------------------------------------
		//
		const OBB &obb = child->GetWorldOBB();
		Scalar radius = obb.sphereRadius;
		ElementRenderer::StateChange *state = child->GetStateChange();
		if (radius>0.0f && state && state->GetLightingMode() != 0)
		{
			Check_Object(state);

			//
			//-------------------------------
			// Figure out the grid boundaries
			//-------------------------------
			//
			Point3D center(obb.localToParent);
			Scalar min_z =
				(center.z - radius - CollisionGrid::Instance->m_rowOffset) * row_scale;
			BYTE min_row =
				(min_z < 0.0f) ? (BYTE)0 : Truncate_Float_To_Byte(min_z);
			Scalar max_z =
				(center.z + radius - CollisionGrid::Instance->m_rowOffset) * row_scale;
			BYTE max_row = Truncate_Float_To_Byte(max_z);
			if (max_row >= CollisionGrid::Instance->m_rowCellCount)
				max_row =
					static_cast<BYTE>(CollisionGrid::Instance->m_rowCellCount - 1);

			Scalar min_x =
				(center.x - radius - CollisionGrid::Instance->m_columnOffset) * column_scale;
			BYTE min_col =
				(min_x < 0.0f) ? (BYTE)0 : Truncate_Float_To_Byte(min_x);
			Scalar max_x =
				(center.x + radius - CollisionGrid::Instance->m_columnOffset) * column_scale;
			BYTE max_col = Truncate_Float_To_Byte(max_x);
			if (max_col >= CollisionGrid::Instance->m_columnCellCount)
				max_col =
					static_cast<BYTE>(CollisionGrid::Instance->m_columnCellCount - 1);

			Verify(
				min_col <= max_col
				 && max_col < CollisionGrid::Instance->m_columnCellCount
			);
			Verify(
				min_row <= max_row
				 && max_row < CollisionGrid::Instance->m_rowCellCount
			);

			//
			//----------------------------------------------------
			// Copy the lights we have so far into the final array
			//----------------------------------------------------
			//
			int base_light = zone_lights;
			int max_light = zone_lights;
			ElementRenderer::StateChange object_state(mixed);
			int k;
			for (k=0; k<base_light; ++k)
			{
				Check_Object(mixed.m_lights[k]);
				object_state.m_lights[k] = mixed.m_lights[k];
			}

			//
			//----------------------
			// Now look at each tile
			//----------------------
			//
			for (BYTE z=min_row; z<=max_row; ++z)
			{
				for (BYTE x=min_col; x<=max_col; ++x)
				{
					Tile *tile = CollisionGrid::Instance->GetTile(z, x);
					Check_Object(tile);

					//
					//----------------------------------------
					// Make sure the light array is up to date
					//----------------------------------------
					//
					if (tile->m_lightsChanged)
						tile->UpdateLights();
					ElementRenderer::StateChange *tile_state = tile->GetStateChange();
					Check_Object(tile_state);
					if (max_light==base_light)
					{
						for (
							k=0;
							k<MidLevelRenderer::Limits::Max_Number_Of_Lights_Per_Primitive
							 && tile_state->m_lights[k];
							++k
						)
						{
							Verify(max_light < MidLevelRenderer::Limits::Max_Number_Of_Lights_Per_Primitive);
							MidLevelRenderer::MLRLight *light = tile_state->m_lights[k];
							Check_Object(light);
							if (light->GetIntensity() < SMALL)
								continue;
							MidLevelRenderer::MLRInfiniteLightWithFalloff *bounded_light =
								Cast_Object(MidLevelRenderer::MLRInfiniteLightWithFalloff*, light);
							Stuff::Point3D lc(bounded_light->GetLightToWorldMatrix());
							Stuff::Vector3D diff;
							diff.Subtract(center, lc);
							Stuff::Scalar range =
								m_worldOBB.sphereRadius + bounded_light->GetFalloffFar();
							if (diff.GetLengthSquared() > range*range)
								continue;
							object_state.m_lights[max_light++] = light;
							if (max_light == MidLevelRenderer::Limits::Max_Number_Of_Lights_Per_Primitive)
								goto Draw_Now;
						}
					}

					//
					//-------------------------------------------------------------------
					// Check each tile light to see if it should be put into the new list
					//-------------------------------------------------------------------
					//
					else
					{
						for (
							int j=0;
							j<MidLevelRenderer::Limits::Max_Number_Of_Lights_Per_Primitive
							 && tile_state->m_lights[j];
							++j
						)
						{
							Verify(max_light < MidLevelRenderer::Limits::Max_Number_Of_Lights_Per_Primitive);
							MidLevelRenderer::MLRLight *light = tile_state->m_lights[j];
							Check_Object(light);
							if (light->GetIntensity() < SMALL)
								continue;
							MidLevelRenderer::MLRInfiniteLightWithFalloff *bounded_light =
								Cast_Object(MidLevelRenderer::MLRInfiniteLightWithFalloff*, light);
							Stuff::Point3D lc(bounded_light->GetLightToWorldMatrix());
							Stuff::Vector3D diff;
							diff.Subtract(center, lc);
							Stuff::Scalar range =
								m_worldOBB.sphereRadius + bounded_light->GetFalloffFar();
							if (diff.GetLengthSquared() > range*range)
								continue;
							for (int kk=base_light; kk<max_light; ++kk)
							{
								if (light == object_state.m_lights[kk])
									break;
							}
							if (kk == max_light)
							{
								object_state.m_lights[max_light++] = light;
								if (max_light == MidLevelRenderer::Limits::Max_Number_Of_Lights_Per_Primitive)
									goto Draw_Now;
							}
						}
					}
				}
			}

			//
			//---------------------------------------------------
			// We have figured out the lights, so draw it already
			//---------------------------------------------------
			//
			if (max_light < MidLevelRenderer::Limits::Max_Number_Of_Lights_Per_Primitive)
				object_state.m_lights[max_light] = NULL;
Draw_Now:
			child->DrawTo(camera, &object_state, culling_state);
		}
		else
			child->DrawTo(camera, &mixed, culling_state);
		camera->m_acceptingElement = NULL;
	}

	//
	//--------------------------------------
	// Draw our bounds if we are supposed to
	//--------------------------------------
	//
#if defined(LAB_ONLY)
Do_Lights:
	if (Show_Lights)
	{
		Stuff::OBB obb;
		obb.axisExtents = Vector3D::Identity;
		for (BYTE z=static_cast<BYTE>(m_row<<3); z<=static_cast<BYTE>((m_row<<3)+7); ++z)
		{
			for (BYTE x=static_cast<BYTE>(m_column<<3); x<=static_cast<BYTE>((m_column<<3)+7); ++x)
			{
				Tile *tile = CollisionGrid::Instance->GetTile(z, x);
				Check_Object(tile);

				//
				//----------------------------------------
				// Make sure the light array is up to date
				//----------------------------------------
				//
				if (tile->m_lightsChanged)
					tile->UpdateLights();

				//
				//--------------------------------------------------------------
				// Step through each tile and draw all its lights if they are on
				//--------------------------------------------------------------
				//
				ElementRenderer::StateChange *tile_state = tile->GetStateChange();
				Check_Object(tile_state);
				for (
					int k=0;
					k<MidLevelRenderer::Limits::Max_Number_Of_Lights_Per_Primitive
					 && tile_state->m_lights[k];
					++k
				)
				{
					MidLevelRenderer::MLRLight *light = tile_state->m_lights[k];
					Check_Object(light);
					Scalar intensity = light->GetIntensity();
					if (intensity>0.0f)
					{
						MidLevelRenderer::MLRInfiniteLightWithFalloff* finite = Cast_Object(MidLevelRenderer::MLRInfiniteLightWithFalloff*,light);
						obb.localToParent = LinearMatrix4D::Identity;
						Point3D position;
						finite->GetInWorldPosition(position);
						obb.localToParent.BuildTranslation(position);
						obb.sphereRadius = finite->GetFalloffFar();
						Stuff::RGBAColor color;
						finite->GetColor(color);
						color.alpha = intensity*0.3f;
						DrawSphere(obb, color, camera, inherited_state, 0x2f);
					}
				}
			}
		}
	}
#endif
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Zone::EnableExecution()
{
	Check_Object(this);
	Check_Object(Map::GetInstance());
	Verify(GetInterestLevel() == Entity::DormantInterestLevel);

	//
	//----------------------------------------------------------
	// Set the zone status and add it to the Map::Instance chain
	//----------------------------------------------------------
	//
	ActivateZone();
	Map::GetInstance()->m_executingZones.Add(this);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Zone::ActivateZone()
{
	Check_Object(this);
	Check_Object(Map::GetInstance());
	Verify(GetInterestLevel() == Entity::DormantInterestLevel);

	//
	//----------------------
	// Load up the materials
	//----------------------
	//
	unsigned index = m_row*Map::GetInstance()->m_columnZoneCount + m_column;
	Zone::ResourceData *zone_res = Map::GetInstance()->GetZoneResourceData(index);
	Check_Object(zone_res);
	if (zone_res->m_materialResourceID != ResourceID::Null)
	{
		m_materialResource.FindID(zone_res->m_materialResourceID);
		Verify(m_materialResource.DoesResourceExist());
		m_materialArray = Cast_Pointer(BYTE*, m_materialResource.GetPointer());
		Verify(m_materialResource.GetSize() == 0x10000);
	}
	else
		m_materialArray = NULL;

	//
	//----------------
	// Load up the bsp
	//----------------
	//
	m_bspResource.FindID(zone_res->m_bspResourceID);
	Verify(m_bspResource.DoesResourceExist());
	m_bspResource >> m_planeCount;
	m_planes = Cast_Pointer(const Plane*, m_bspResource.GetPointer());
	m_bspResource.AdvancePointer(m_planeCount * sizeof(Plane));
	for (WORD i=0; i<64; ++i)
	{
		int bsp_size;
		m_bspResource >> bsp_size;
		Tile *tile = Cast_Object(Tile*, m_tileGrid->GetIndexedElement(i));
		tile->m_BSPCount = *Cast_Pointer(WORD*, m_bspResource.GetPointer());
		m_bspResource.AdvancePointer(sizeof(WORD));
		Verify(!tile->m_BSP);
		tile->m_BSP = Cast_Pointer(TerrainBSP*, m_bspResource.GetPointer());
		m_bspResource.AdvancePointer(tile->m_BSPCount*sizeof(TerrainBSP));
	}

	//
	//-----------------------------------------
	// Hook up each of the children to the zone
	//-----------------------------------------
	//
	SetInterestLevel(Entity::SimulationInterestLevel);
	Verify(!Map::GetInstance()->m_executingZones.IsPlugMember(this));
	Map::GetInstance()->m_executingZones.Add(this);
	ChainIteratorOf<Entity*> invalids(&m_dormant);
	Entity *entity;
	while ((entity = invalids.GetCurrent()) != NULL)
	{
		Check_Object(entity);
		Verify(!entity->m_pendingInterestMessage.GetCurrent());
		entity->SyncMatrices(true);
		Verify(!m_rendered.IsPlugMember(entity));
		Verify(!m_simulated.IsPlugMember(entity));
		m_simulated.Add(entity);
		entity->BecomeInteresting(false);
		Verify(entity->GetInterestLevel() != Entity::DormantInterestLevel);

		//
		//-------------------------------------------------------------
		// If we are executing, place the entity on its execution chain
		//-------------------------------------------------------------
		//
		if (!entity->IsNeverExecuteState())
		{
			if (GetInterestLevel() == Entity::DormantInterestLevel)
				EnableExecution();
			Verify(entity != Map::GetInstance());
			Verify(!m_executors.IsPlugMember(entity));
			m_executors.Add(entity);
		}

		//
		//------------------------------------------------------------------
		// Deal with the collision needs and remove it from the invalid list
		//------------------------------------------------------------------
		//
		HookUpCollision(entity);
		invalids.Remove();
		Verify(!m_dormant.IsPlugMember(entity));
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Zone::EnableRendering()
{
	Check_Object(this);
	Verify(GetInterestLevel() != Entity::RenderingInterestLevel);
	Verify(!Application::GetInstance()->m_localMissionParameters->m_runDedicated);

	//
	//-----------------------------------------
	// Make sure that the zone can be simulated
	//-----------------------------------------
	//
	Check_Object(Map::GetInstance());
	Map::GetInstance()->m_renderedZones.Add(this);
	if (GetInterestLevel() == Entity::DormantInterestLevel)
		ActivateZone();
	Verify(GetInterestLevel() == Entity::SimulationInterestLevel);
	SetInterestLevel(Entity::RenderingInterestLevel);

	//
	//----------------------------------
	// Load up the ground into the tiles
	//----------------------------------
	//
	unsigned index = m_row*Map::GetInstance()->m_columnZoneCount + m_column;
	Zone::ResourceData *zone_res = Map::GetInstance()->GetZoneResourceData(index);
	Check_Object(zone_res);
	Resource shape_stream(zone_res->m_geometryResourceID);
	Check_Object(m_tileGrid);
	int version = ElementRenderer::ReadERFVersion(&shape_stream);

#ifdef LAB_ONLY
	BYTE *raw_power = NULL;
	HGOSFILE fd = NULL;
	
	if(saveMapBspDifference==true)
	{
		char file_name[16];

		sprintf(file_name, "HDiff_%c%c.raw", 'A'+m_row, 'A'+m_column);
		raw_power = new BYTE [256*256*3];
		memset(raw_power, 0, 256*256*3);

		gos_OpenFile( &fd, file_name, READWRITE );
	}
#endif

	for (WORD i=0; i<64; ++i)
	{
		Tile* tile =
			Cast_Object(
				Tile*,
				m_tileGrid->GetIndexedElement(i)
			);
		
		Verify(!tile->m_ground);
		tile->m_ground =
			ElementRenderer::Element::Create(&shape_stream, version);
		Check_Object(tile->m_ground);

#ifdef LAB_ONLY
		if(saveMapBspDifference==true)
		{
			DynamicArrayOf<Point3D> lotsOfPoints;

			tile->m_ground->GetCoordData(&lotsOfPoints);

			Scalar row_offset, column_offset, row_cell_size, column_cell_size;

			m_tileGrid->GetDimensions(&row_offset, &column_offset, &row_cell_size, &column_cell_size);

			int x_z_miss = 0;

			for(int j=0;j<lotsOfPoints.GetLength();j++)
			{
				Stuff::Line3D line;
				Point3D point;
				
				point = lotsOfPoints[j];
				point.y += 500.0f;
				
				line.m_length = 1000.0f;
				line.m_direction = Vector3D::Down;
				line.SetOrigin(point);
	
				//
				// Prepare query
				//	
				Stuff::Normal3D inormal;
				Stuff::Line3D iline;
				Stuff::Normal3D normal;
	
				TerrainBSP::CollisionQuery
					bsp_query(&line, &normal, this, tile);

				bool hit = tile->m_BSP->TestLine(&bsp_query, 0.0f, line.m_length);

				if(hit)
				{
					Scalar diff = lotsOfPoints[j].y - (point.y-line.m_length);

					if(diff>Stuff::SMALL)
					{
						int x = (int)(fmod(lotsOfPoints[j].x - column_offset, 1280.0)/5.f);
						int z = (int)(fmod(lotsOfPoints[j].z - row_offset, 1280.0)/5.f);

						if(x>=0&&x<256&&z>=0&&z<256)
						{
							raw_power[3*(z*256 + x)] = Truncate_Float_To_Byte(diff*100.f > 255.f ? 0xff : diff*100.f);
						}
						else
						{
							x_z_miss++;
						}
					}
					else if(diff<-Stuff::SMALL)
					{
						int x = (int)(fmod(lotsOfPoints[j].x - column_offset, 1280.0)/5.f);
						int z = (int)(fmod(lotsOfPoints[j].z - row_offset, 1280.0)/5.f);

						if(x>=0&&x<256&&z>=0&&z<256)
						{
							raw_power[3*(z*256 + x)+2] = Truncate_Float_To_Byte(diff*100.f < -255.f ? 0xff : -diff*100.f);
						}
						else
						{
							x_z_miss++;
						}
					}
				}
				else
				{
					int x = (int)(fmod(lotsOfPoints[j].x - column_offset, 1280.0)/5.f);
					int z = (int)(fmod(lotsOfPoints[j].z - row_offset, 1280.0)/5.f);

					if(x>=0&&x<256&&z>=0&&z<256)
					{
						raw_power[3*(z*256 + x)+1] = 0xff;
					}
				}
			}
		}
#endif

		tile->AttachChild(tile->m_ground);
	}
#ifdef LAB_ONLY
	if(saveMapBspDifference==true)
	{
		gos_WriteFile(fd, raw_power, 256*256*3);
		gos_CloseFile(fd);
		delete [] raw_power;
	}
#endif

	m_tileGrid->Sync();
	if (Compost::TerrainTextureLogistic::Instance)
	{
		Compost::TerrainTextureLogistic::Instance->AttachZone(
			m_tileGrid,
			static_cast<BYTE>(m_row*e_TilesPerZone),
			static_cast<BYTE>(m_column*e_TilesPerZone)
		);
	}

	//
	//------------------------------------------------
	// Now, make the non-rendered entities interesting
	//------------------------------------------------
	//
	Check_Object(RendererManager::Instance);
	ChainIteratorOf<Entity*> nonrenders(&m_simulated);
	Entity *entity;
	Entity::BecomeInterestingMessage message(ReplicatorID::Null, true);
	while ((entity = nonrenders.ReadAndNext()) != NULL)
	{
		Check_Object(entity);
		AbstractEvent *event = entity->m_pendingInterestMessage.GetCurrent();
		if (event)
		{
			Check_Object(event);
			delete event;
		}
		Verify(!m_dormant.IsPlugMember(entity));
		Verify(!m_rendered.IsPlugMember(entity));
		Verify(m_simulated.IsPlugMember(entity));
		message.replicatorID = entity->GetReplicatorID();
		entity->m_pendingInterestMessage.Add(GeneralEventQueue::Instance->Post(entity, &message));
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Zone::DisableExecution()
{
	Check_Object(this);
	Check_Object(Map::GetInstance());
	Verify(GetInterestLevel() != Entity::DormantInterestLevel);

	//
	//------------------------------------------------
	// Set the zone status and add it to the Map::Instance chain
	//------------------------------------------------
	//
	SetInterestLevel(Entity::DormantInterestLevel);
	Map::GetInstance()->m_executingZones.RemovePlug(this);
	STOP(("Not implemented"));
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Zone::DeactivateZone()
{
	Check_Object(this);
	Check_Object(Map::GetInstance());
	Verify(GetInterestLevel() != Entity::DormantInterestLevel);

	//
	//-----------------------------------------
	// Hook up each of the children to the zone
	//-----------------------------------------
	//
	SetInterestLevel(Entity::DormantInterestLevel);
	STOP(("Not implemented"));
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Zone::DisableRendering()
{
	Check_Object(this);
	Check_Object(Map::GetInstance());
	Verify(GetInterestLevel() == Entity::RenderingInterestLevel);

	//
	//------------------------------------------------
	// Set the zone status and add it to the Map::Instance chain
	//------------------------------------------------
	//
	SetInterestLevel(Entity::SimulationInterestLevel);
	Map::GetInstance()->m_renderedZones.RemovePlug(this);

	//
	//------------------------------------------------
	// Now, make the non-rendered entities interesting
	//------------------------------------------------
	//
	Check_Object(RendererManager::Instance);
	ChainIteratorOf<Entity*> renders(&m_rendered);
	Entity *entity;
	while ((entity = renders.GetCurrent()) != NULL)
	{
		Check_Object(entity);
		Verify(!entity->m_pendingInterestMessage.GetCurrent());
		renders.Remove();
		Verify(!m_simulated.IsPlugMember(entity));
		Verify(!m_dormant.IsPlugMember(entity));
		Verify(!m_rendered.IsPlugMember(entity));
		m_simulated.Add(entity);
		entity->BecomeInteresting(false);
	}

	//
	//------------------
	// Delete the ground
	//------------------
	//
	for (WORD i=0; i<64; ++i)
	{
		Tile* tile =
			Cast_Object(
				Tile*,
				m_tileGrid->GetIndexedElement(i)
			);
		Check_Object(tile->m_ground);
		delete tile->m_ground;
		tile->m_ground = NULL;
	}

	if (Compost::TerrainTextureLogistic::Instance)
	{
		Compost::TerrainTextureLogistic::Instance->DetachMeshes(
			m_row*e_TilesPerZone,
			m_column*e_TilesPerZone,
			e_TilesPerZone,
			e_TilesPerZone
		);
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Zone::AddExecutor(Entity *entity)
{
	Check_Object(this);
	Check_Object(entity);

	Verify(!m_executors.IsPlugMember(entity));
	Entity::InterestLevel
		zone_interest = GetInterestLevel(),
		entity_interest = entity->GetInterestLevel();

	switch ((zone_interest<<2) | entity_interest)
	{
	case (Entity::DormantInterestLevel<<2)|Entity::SimulationInterestLevel:
	case (Entity::DormantInterestLevel<<2)|Entity::DormantInterestLevel:
	case (Entity::DormantInterestLevel<<2)|Entity::RenderingInterestLevel:
	case (Entity::SimulationInterestLevel<<2)|Entity::DormantInterestLevel:
	case (Entity::RenderingInterestLevel<<2)|Entity::DormantInterestLevel:
		Verify(m_dormant.IsPlugMember(entity));
		Verify(!m_simulated.IsPlugMember(entity));
		Verify(!m_rendered.IsPlugMember(entity));
		break;

	case (Entity::SimulationInterestLevel<<2)|Entity::SimulationInterestLevel:
	case (Entity::SimulationInterestLevel<<2)|Entity::RenderingInterestLevel:
	case (Entity::RenderingInterestLevel<<2)|Entity::SimulationInterestLevel:
		Verify(!m_dormant.IsPlugMember(entity));
		Verify(m_simulated.IsPlugMember(entity));
		Verify(!m_rendered.IsPlugMember(entity));
		m_executors.Add(entity);
		break;

	case (Entity::RenderingInterestLevel<<2)|Entity::RenderingInterestLevel:
		Verify(!m_dormant.IsPlugMember(entity));
		Verify(!m_simulated.IsPlugMember(entity));
		Verify(m_rendered.IsPlugMember(entity));
		m_executors.Add(entity);
		break;

	default:
		STOP(("Illegal interest level setting"));
		break;
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Zone::RemoveExecutor(Entity *entity)
{
	Check_Object(this);
	Check_Object(entity);

	Entity::InterestLevel
		zone_interest = GetInterestLevel(),
		entity_interest = entity->GetInterestLevel();

	switch ((zone_interest<<2) | entity_interest)
	{
	case (Entity::DormantInterestLevel<<2)|Entity::DormantInterestLevel:
	case (Entity::DormantInterestLevel<<2)|Entity::SimulationInterestLevel:
	case (Entity::DormantInterestLevel<<2)|Entity::RenderingInterestLevel:
	case (Entity::SimulationInterestLevel<<2)|Entity::DormantInterestLevel:
	case (Entity::RenderingInterestLevel<<2)|Entity::DormantInterestLevel:
		Verify(m_dormant.IsPlugMember(entity));
		Verify(!m_rendered.IsPlugMember(entity));
		Verify(!m_simulated.IsPlugMember(entity));
		Verify(!m_executors.IsPlugMember(entity));
		break;

	case (Entity::SimulationInterestLevel<<2)|Entity::SimulationInterestLevel:
	case (Entity::SimulationInterestLevel<<2)|Entity::RenderingInterestLevel:
	case (Entity::RenderingInterestLevel<<2)|Entity::SimulationInterestLevel:
Not_Quite_Done:
		Verify(m_simulated.IsPlugMember(entity));
		Verify(!m_dormant.IsPlugMember(entity));
		Verify(!m_rendered.IsPlugMember(entity));
		Verify(m_executors.IsPlugMember(entity));
		m_executors.RemovePlug(entity);
		break;


	case (Entity::RenderingInterestLevel<<2)|Entity::RenderingInterestLevel:
		if (entity->m_pendingInterestMessage.GetCurrent())
			goto Not_Quite_Done;
		Verify(!m_simulated.IsPlugMember(entity));
		Verify(!m_dormant.IsPlugMember(entity));
		Verify(m_rendered.IsPlugMember(entity));
		Verify(m_executors.IsPlugMember(entity));
		m_executors.RemovePlug(entity);
		break;

	default:
		STOP(("Illegal interest level setting"));
		break;
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Zone::HookUpCollision(Entity *entity)
{
	Check_Object(this);
	Check_Object(entity);

	//
	//------------------------------------------
	// If this entity won't collide, we are done
	//------------------------------------------
	//
	if (entity->GetCollisionMask() == Entity::NeverCollidesMask)
		return;

	//
	//-----------------------------------------------------------------------
	// If the entity is a collider, look to see if it is also a collidee.  If
	// not, put it on the line collider list
	//-----------------------------------------------------------------------
	//
	if (entity->IsACollider())
	{
		Verify(!entity->IsTileBound());
		Check_Object(CollisionGrid::Instance);
		CollisionVolume *bounds = entity->GetSolidVolume();
		if (!bounds)
			CollisionGrid::Instance->m_lineColliders.Add(entity);
		else
		{
			CollisionGrid::Instance->m_solidColliders.Add(entity);
			bounds->m_worldSpaceBounds.Multiply(
				bounds->m_localSpaceBounds,
				entity->GetLocalToWorld()
			);
			ExtentBox aabb(bounds->m_worldSpaceBounds);
			Check_Object(CollisionGrid::Instance);
			Scalar row_scale = 1.0f/CollisionGrid::Instance->m_rowCellSize;
			Scalar column_scale = 1.0f/CollisionGrid::Instance->m_columnCellSize;

			//
			//-------------------------------
			// Figure out the grid boundaries
			//-------------------------------
			//
			Scalar min_z =
				(aabb.minZ - CollisionGrid::Instance->m_rowOffset) * row_scale;
			entity->minTileRow =
				(min_z < 0.0f) ? (BYTE)0 : Truncate_Float_To_Byte(min_z);
			Scalar max_z =
				(aabb.maxZ - CollisionGrid::Instance->m_rowOffset) * row_scale;
			entity->maxTileRow = Truncate_Float_To_Byte(max_z);
			if (entity->maxTileRow >= CollisionGrid::Instance->m_rowCellCount)
				entity->maxTileRow =
					static_cast<BYTE>(CollisionGrid::Instance->m_rowCellCount - 1);

			Scalar min_x =
				(aabb.minX - CollisionGrid::Instance->m_columnOffset) * column_scale;
			entity->minTileColumn =
				(min_x < 0.0f) ? (BYTE)0 : Truncate_Float_To_Byte(min_x);
			Scalar max_x =
				(aabb.maxX - CollisionGrid::Instance->m_columnOffset) * column_scale;
			entity->maxTileColumn = Truncate_Float_To_Byte(max_x);
			if (entity->maxTileColumn >= CollisionGrid::Instance->m_columnCellCount)
				entity->maxTileColumn =
					static_cast<BYTE>(CollisionGrid::Instance->m_columnCellCount - 1);

			//
			//---------------------------------------------------------------------
			// Attach the entity to each tile that overlaps its extents.  If this
			// is the first time that tile is being used this frame, attach it to
			// the colliding tile list
			//---------------------------------------------------------------------
			//
			Verify(
				entity->minTileColumn <= entity->maxTileColumn
				 && entity->maxTileColumn < CollisionGrid::Instance->m_columnCellCount
			);
			Verify(
				entity->minTileRow <= entity->maxTileRow
				 && entity->maxTileRow < CollisionGrid::Instance->m_rowCellCount
			);
			for (BYTE z=entity->minTileRow; z<=entity->maxTileRow; ++z)
			{
				for (BYTE x=entity->minTileColumn; x<=entity->maxTileColumn; ++x)
				{
					Tile *tile = CollisionGrid::Instance->GetTile(z, x);
					Check_Object(tile);
					Verify(tile->m_row == z && tile->m_column == x);
					ChainIteratorOf<Entity*> temp(&tile->m_colliders);
					if (!temp.GetCurrent())
						CollisionGrid::Instance->m_collidingTiles.Add(tile);
					Verify(!tile->m_colliders.IsPlugMember(entity));
					Verify(!tile->m_collidees.IsPlugMember(entity));
					tile->m_colliders.Add(entity);
				}
			}
		}
		return;
	}

	//
	//----------------------------------------------------------------
	// If it is not a collider, but is a collidee, it then must have a
	// collision volume
	//----------------------------------------------------------------
	//
	CollisionVolume *bounds = entity->GetHierarchicalVolume();
	if (!bounds)
		bounds = entity->GetSolidVolume();
	Check_Object(bounds);

	//
	//---------------------------------------------------------------------
	// If the collidee is tile bound, it will only collide in the specified
	// tile
	//---------------------------------------------------------------------
	//
	if (entity->IsTileBound())
	{
		ElementRenderer::Element *element = entity->GetElement();
		Check_Object(element);
		Tile *tile = Cast_Object(Tile*, element->GetParentElement());
		Verify(!tile->m_collidees.IsPlugMember(entity));
		Verify(!tile->m_colliders.IsPlugMember(entity));
		tile->m_collidees.Add(entity);
		tile->AddEntityToDamagableList(entity);
		Verify(tile->m_row < 256 && tile->m_column < 256);
		entity->minTileRow = static_cast<BYTE>(tile->m_row);
		entity->maxTileRow = static_cast<BYTE>(tile->m_row);
		entity->minTileColumn = static_cast<BYTE>(tile->m_column);
		entity->maxTileColumn = static_cast<BYTE>(tile->m_column);
	}

	//
	//---------------------------------------------
	// Otherwise, attach it to any tile it overlaps
	//---------------------------------------------
	//
	else
	{
		bounds->m_worldSpaceBounds.Multiply(bounds->m_localSpaceBounds, entity->GetLocalToWorld());
		ExtentBox aabb(bounds->m_worldSpaceBounds);
		Check_Object(CollisionGrid::Instance);
		Scalar row_scale = 1.0f/CollisionGrid::Instance->m_rowCellSize;
		Scalar column_scale = 1.0f/CollisionGrid::Instance->m_columnCellSize;

		//
		//-------------------------------
		// Figure out the grid boundaries
		//-------------------------------
		//
		Scalar min_z =
			(aabb.minZ - CollisionGrid::Instance->m_rowOffset) * row_scale;
		entity->minTileRow =
			(min_z < 0.0f) ? (BYTE)0 : Truncate_Float_To_Byte(min_z);
		Scalar max_z =
			(aabb.maxZ - CollisionGrid::Instance->m_rowOffset) * row_scale;
		entity->maxTileRow = Truncate_Float_To_Byte(max_z);
		if (entity->maxTileRow >= CollisionGrid::Instance->m_rowCellCount)
			entity->maxTileRow =
				static_cast<BYTE>(CollisionGrid::Instance->m_rowCellCount - 1);

		Scalar min_x =
			(aabb.minX - CollisionGrid::Instance->m_columnOffset) * column_scale;
		entity->minTileColumn =
			(min_x < 0.0f) ? (BYTE)0 : Truncate_Float_To_Byte(min_x);
		Scalar max_x =
			(aabb.maxX - CollisionGrid::Instance->m_columnOffset) * column_scale;
		entity->maxTileColumn = Truncate_Float_To_Byte(max_x);
		if (entity->maxTileColumn >= CollisionGrid::Instance->m_columnCellCount)
			entity->maxTileColumn =
				static_cast<BYTE>(CollisionGrid::Instance->m_columnCellCount - 1);

		//
		//---------------------------------------------------------------------
		// Attach the entity to each tile that overlaps its extents.  If this
		// is the first time that tile is being used this frame, attach it to
		// the colliding tile list
		//---------------------------------------------------------------------
		//
		Verify(
			entity->minTileColumn <= entity->maxTileColumn
			 && entity->maxTileColumn < CollisionGrid::Instance->m_columnCellCount
		);
		Verify(
			entity->minTileRow <= entity->maxTileRow
			 && entity->maxTileRow < CollisionGrid::Instance->m_rowCellCount
		);
		for (BYTE z=entity->minTileRow; z<=entity->maxTileRow; ++z)
		{
			for (BYTE x=entity->minTileColumn; x<=entity->maxTileColumn; ++x)
			{
				Tile *tile = CollisionGrid::Instance->GetTile(z, x);
				Check_Object(tile);
				Verify(!tile->m_colliders.IsPlugMember(entity));
				Verify(!tile->m_collidees.IsPlugMember(entity));
				tile->m_collidees.Add(entity);
				tile->AddEntityToDamagableList(entity);
			}
		}
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Zone::UnhookCollision(Entity *entity)
{
	Check_Object(this);
	Check_Object(entity);

	//
	//-----------------------------------------------------------------------
	// If the entity is a collider, look to see if it is also a collidee.  If
	// not, put it on the line collider list
	//-----------------------------------------------------------------------
	//
	if (entity->IsACollider())
	{
		Verify(!entity->IsTileBound());
		CollisionVolume *bounds = entity->GetSolidVolume();
		if (!bounds)
		{
			Check_Object(CollisionGrid::Instance);
			CollisionGrid::Instance->m_lineColliders.RemovePlug(entity);
		}
		else
		{
			Verify(!CollisionGrid::Instance->m_lineColliders.IsPlugMember(entity));
			Check_Object(bounds);
			CollisionGrid::Instance->m_solidColliders.RemovePlug(entity);
			for (BYTE z=entity->minTileRow; z<=entity->maxTileRow; ++z)
			{
				for (BYTE x=entity->minTileColumn; x<=entity->maxTileColumn; ++x)
				{
					Tile *tile = CollisionGrid::Instance->GetTile(z, x);
					if (tile)
					{
						Check_Object(tile);
						Verify(!tile->m_collidees.IsPlugMember(entity));
//						Verify(tile->m_colliders.IsPlugMember(entity));
						tile->m_colliders.RemovePlug(entity);
					}
				}
			}
		}
	}

	//
	//----------------------------------------------------------------
	// If it is not a collider, but is a collidee, it then must have a
	// collision volume
	//----------------------------------------------------------------
	//
	else if (entity->GetCollisionMask() != Entity::NeverCollidesMask)
	{
		Verify(!CollisionGrid::Instance->m_lineColliders.IsPlugMember(entity));

		//
		//---------------------------------------------------------------------
		// If the collidee is tile bound, it will only collide in the specified
		// tile
		//---------------------------------------------------------------------
		//
		if (entity->IsTileBound())
		{
			ElementRenderer::Element *element = entity->GetElement();
			Check_Object(element);
			Tile *tile = Cast_Object(Tile*, element->GetParentElement());
			Verify(tile->m_collidees.IsPlugMember(entity));
			Verify(!tile->m_colliders.IsPlugMember(entity));
			tile->m_collidees.RemovePlug(entity);
		}

		//
		//---------------------------------------------
		// Otherwise, attach it to any tile it overlaps
		//---------------------------------------------
		//
		else
		{

			//
			//---------------------------------------------------------------------
			// Attach the entity to each tile that overlaps its extents.  If this
			// is the first time that tile is being used this frame, attach it to
			// the colliding tile list
			//---------------------------------------------------------------------
			//
			Verify(
				entity->minTileColumn <= entity->maxTileColumn
				 && entity->maxTileColumn < CollisionGrid::Instance->m_columnCellCount
			);
			Verify(
				entity->minTileRow <= entity->maxTileRow
				 && entity->maxTileRow < CollisionGrid::Instance->m_rowCellCount
			);
			for (BYTE z=entity->minTileRow; z<=entity->maxTileRow; ++z)
			{
				for (BYTE x=entity->minTileColumn; x<=entity->maxTileColumn; ++x)
				{
					Tile *tile = CollisionGrid::Instance->GetTile(z, x);
					if (tile)
					{
						Check_Object(tile);
						Verify(tile->m_collidees.IsPlugMember(entity));
						Verify(!tile->m_colliders.IsPlugMember(entity));
						tile->m_collidees.RemovePlug(entity);
					}
					#if defined(LAB_ONLY) && 0	// doesn't really work yet.... (
						else
							PAUSE(("%s was placed across zone boundaries!", (const char*)entity->instanceName));
					#endif
				}
			}
		}
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Zone::PreCollisionExecute(Time till)
{
	Check_Object(this);
	Verify(GetInterestLevel() != Entity::DormantInterestLevel);
	PRECOLLISION_LOGIC("Zone");

	//
	//-----------------
	// Execute entities
	//-----------------
	//
	Check_Object(Map::GetInstance());
	ChainIteratorOf<Entity*> entities(&m_executors);
	Entity *entity;
	while ((entity = entities.ReadAndNext()) != NULL)
	{
		Check_Object(entity);
		Verify(entity->GetInterestLevel() != Entity::DormantInterestLevel);
		if (entity->GetTimeSlice(till) > 0.0f)
		{
			Set_Statistic(Executed_Zone_Entity_Count, Executed_Zone_Entity_Count+1);
			int hash = entity->GetExecutionSlot();
			entity->nextPreExecution = Map::GetInstance()->m_executionSlots[hash];
			Map::GetInstance()->m_executionSlots[hash] = entity;
		}
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	Zone::SyncMatrices(bool update_matrix)
{
	Check_Object(this);
	Verify(GetInterestLevel() != Entity::DormantInterestLevel);
	SYNC_LOGIC("Zone");

	//
	//----------------------------
	// Sync the executing entities
	//----------------------------
	//
	ChainIteratorOf<Entity*> entities(&m_executors);
	Entity *entity;
	while ((entity = entities.ReadAndNext()) != NULL)
	{
		Check_Object(entity);
		entity->SyncMatrices(update_matrix);
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
BYTE
	Zone::GetMaterial(Point3D &point)
{
	Check_Object(this);

	//
	//-------------------------------------------------------
	// If there is no material array, return the default case
	//-------------------------------------------------------
	//
	if (!m_materialArray)
		return 0;

	//
	//------------------------------------------------------
	// Adjust the point for zero,zero being in the SE corner
	//------------------------------------------------------
	//
	Scalar scale = 1.0f / c_ZoneSize;
	Scalar local_x = point.x - CollisionGrid::Instance->m_columnOffset;
	Scalar local_z = point.z - CollisionGrid::Instance->m_rowOffset;
	local_x *= scale;
	local_z *= scale;
	local_x -= m_column;
	local_z -= m_row;

	//
	//------------------------------------
	// Figure out what cell we would be in
	//------------------------------------
	//
	int x = 256 - static_cast<int>(floor(256.0*local_x));
	Clamp(x, 0, 255);
	int z = 256 - static_cast<int>(floor(256.0*local_z));
	Clamp(z, 0, 255);
	int index = (z<<8) + x;
	Verify(index >= 0 && index < 0x10000);
	return m_materialArray[index];
}
