#include "common.h"

#include "General.h"
#include "Building.h"
#include "Treadable.h"
#include "Train.h"
#include "Pools.h"
#include "Timer.h"
#include "Camera.h"
#include "World.h"
#include "FileMgr.h"
#include "ZoneCull.h"
#include "Zones.h"

#include "Debug.h"
#include "Renderer.h"

int32     CCullZones::NumCullZones;
CCullZone CCullZones::aZones[NUMCULLZONES];
int32     CCullZones::NumAttributeZones;
CAttributeZone CCullZones::aAttributeZones[NUMATTRIBZONES];
uint16    CCullZones::aIndices[NUMZONEINDICES];
int16     CCullZones::aPointersToBigBuildingsForBuildings[NUMBUILDINGS];
int16     CCullZones::aPointersToBigBuildingsForTreadables[NUMTREADABLES];

int32 CCullZones::CurrentWantedLevelDrop_Player;
int32 CCullZones::CurrentFlags_Camera;
int32 CCullZones::CurrentFlags_Player;
int32 CCullZones::OldCullZone;
int32 CCullZones::EntityIndicesUsed;
bool CCullZones::bCurrentSubwayIsInvisible;
bool CCullZones::bCullZonesDisabled;

#define NUMUNCOMPRESSED (6000)
#define NUMTEMPINDICES (140000)

void
CCullZones::Init(void)
{
	int i;

	NumAttributeZones = 0;
	CurrentWantedLevelDrop_Player = 0;
	CurrentFlags_Camera = 0;
	CurrentFlags_Player = 0;
	bCurrentSubwayIsInvisible = false;
	NumCullZones = 0;
	OldCullZone = -1;
	EntityIndicesUsed = 0;

	for(i = 0; i < NUMBUILDINGS; i++)
		aPointersToBigBuildingsForBuildings[i] = -1;
	for(i = 0; i < NUMTREADABLES; i++)
		aPointersToBigBuildingsForTreadables[i] = -1;
}


uint16* pTempArrayIndices;
int TempEntityIndicesUsed;

void
CCullZones::ResolveVisibilities(void)
{
	int fd;

	CFileMgr::SetDir("");
	fd = CFileMgr::OpenFile("DATA\\cullzone.dat", "rb");
	if(fd > 0){
		CFileMgr::Read(fd, (char*)&NumCullZones, sizeof(NumCullZones));
		CFileMgr::Read(fd, (char*)aZones, sizeof(aZones));
		CFileMgr::Read(fd, (char*)&NumAttributeZones, sizeof(NumAttributeZones));
		CFileMgr::Read(fd, (char*)aAttributeZones, sizeof(aAttributeZones));
		CFileMgr::Read(fd, (char*)aIndices, sizeof(aIndices));
		CFileMgr::Read(fd, (char*)aPointersToBigBuildingsForBuildings, sizeof(aPointersToBigBuildingsForBuildings));
		CFileMgr::Read(fd, (char*)aPointersToBigBuildingsForTreadables, sizeof(aPointersToBigBuildingsForTreadables));
		CFileMgr::CloseFile(fd);
	}else{
#ifndef MASTER
		EntityIndicesUsed = 0;
		BuildListForBigBuildings();
		pTempArrayIndices = new uint16[NUMTEMPINDICES];
		TempEntityIndicesUsed = 0;

//		if(!LoadTempFile())	// not in final game
		{
			for (int i = 0; i < NumCullZones; i++) {
//printf("testing zone %d (%d indices)\n", i, TempEntityIndicesUsed);
				DoVisibilityTestCullZone(i, true);
			}

//			SaveTempFile();	// not in final game
		}

		CompressIndicesArray();
		delete[] pTempArrayIndices;
		pTempArrayIndices = nil;

		fd = CFileMgr::OpenFileForWriting("data\\cullzone.dat");
		if (fd != 0) {
			CFileMgr::Write(fd, (char*)&NumCullZones, sizeof(NumCullZones));
			CFileMgr::Write(fd, (char*)aZones, sizeof(aZones));
			CFileMgr::Write(fd, (char*)&NumAttributeZones, sizeof(NumAttributeZones));
			CFileMgr::Write(fd, (char*)&aAttributeZones, sizeof(aAttributeZones));
			CFileMgr::Write(fd, (char*)&aIndices, sizeof(aIndices));
			CFileMgr::Write(fd, (char*)&aPointersToBigBuildingsForBuildings, sizeof(aPointersToBigBuildingsForBuildings));
			CFileMgr::Write(fd, (char*)&aPointersToBigBuildingsForTreadables, sizeof(aPointersToBigBuildingsForTreadables));
			CFileMgr::CloseFile(fd);
		}
#endif
	}
}

bool
CCullZones::LoadTempFile(void)
{
	int fd = CFileMgr::OpenFile("cullzone.tmp");
	if (fd != 0) {
		CFileMgr::Read(fd, (char*)&NumCullZones, sizeof(NumCullZones));
		CFileMgr::Read(fd, (char*)aZones, sizeof(aZones));
		CFileMgr::Read(fd, (char*)&NumAttributeZones, sizeof(NumAttributeZones));
		CFileMgr::Read(fd, (char*)&aAttributeZones, sizeof(aAttributeZones));
		CFileMgr::Read(fd, (char*)pTempArrayIndices, NUMTEMPINDICES*sizeof(uint16));
		CFileMgr::Read(fd, (char*)&TempEntityIndicesUsed, sizeof(TempEntityIndicesUsed));
		CFileMgr::Read(fd, (char*)&aPointersToBigBuildingsForBuildings, sizeof(aPointersToBigBuildingsForBuildings));
		CFileMgr::Read(fd, (char*)&aPointersToBigBuildingsForTreadables, sizeof(aPointersToBigBuildingsForTreadables));
		CFileMgr::CloseFile(fd);
		return true;
	}
	return false;
}

void
CCullZones::SaveTempFile(void)
{
	int fd = CFileMgr::OpenFileForWriting("cullzone.tmp");
	if (fd != 0) {
		CFileMgr::Write(fd, (char*)&NumCullZones, sizeof(NumCullZones));
		CFileMgr::Write(fd, (char*)aZones, sizeof(aZones));
		CFileMgr::Write(fd, (char*)&NumAttributeZones, sizeof(NumAttributeZones));
		CFileMgr::Write(fd, (char*)&aAttributeZones, sizeof(aAttributeZones));
		CFileMgr::Write(fd, (char*)pTempArrayIndices, NUMTEMPINDICES*sizeof(uint16));
		CFileMgr::Write(fd, (char*)&TempEntityIndicesUsed, sizeof(TempEntityIndicesUsed));
		CFileMgr::Write(fd, (char*)&aPointersToBigBuildingsForBuildings, sizeof(aPointersToBigBuildingsForBuildings));
		CFileMgr::Write(fd, (char*)&aPointersToBigBuildingsForTreadables, sizeof(aPointersToBigBuildingsForTreadables));
		CFileMgr::CloseFile(fd);
	}
}


void
CCullZones::BuildListForBigBuildings()
{
	for (int i = CPools::GetBuildingPool()->GetSize()-1; i >= 0; i--) {
		CBuilding *building = CPools::GetBuildingPool()->GetSlot(i);
		if (building == nil || !building->bIsBIGBuilding) continue;
		CSimpleModelInfo *nonlod = ((CSimpleModelInfo *)CModelInfo::GetModelInfo(building->GetModelIndex()))->GetRelatedModel();
		if (nonlod == nil) continue;

		for (int j = CPools::GetBuildingPool()->GetSize()-1; j >= 0; j--) {
			CBuilding *building2 = CPools::GetBuildingPool()->GetSlot(j);
			if (building2 == nil || building2->bIsBIGBuilding) continue;
			if (CModelInfo::GetModelInfo(building2->GetModelIndex()) == nonlod) {
				if ((building2->GetPosition() - building->GetPosition()).Magnitude() < 5.0f) {
					aPointersToBigBuildingsForBuildings[j] = i;
				}
			}
		}

		for (int j = CPools::GetTreadablePool()->GetSize()-1; j >= 0; j--) {
			CTreadable *treadable = CPools::GetTreadablePool()->GetSlot(j);
			if (treadable == nil || treadable->bIsBIGBuilding) continue;
			if (CModelInfo::GetModelInfo(treadable->GetModelIndex()) == nonlod) {
				if ((treadable->GetPosition() - building->GetPosition()).Magnitude() < 5.0f) {
					aPointersToBigBuildingsForTreadables[j] = i;
				}
			}
		}
	}
}

void
CCullZones::DoVisibilityTestCullZone(int zoneId, bool findIndices)
{
	aZones[zoneId].m_groupIndexCount[0] = 0;
	aZones[zoneId].m_groupIndexCount[1] = 0;
	aZones[zoneId].m_groupIndexCount[2] = 0;
	aZones[zoneId].m_indexStart = TempEntityIndicesUsed;
	aZones[zoneId].FindTestPoints();

	if (!findIndices) return;

	for (int i = CPools::GetBuildingPool()->GetSize() - 1; i >= 0; i--) {
		CBuilding *building = CPools::GetBuildingPool()->GetSlot(i);
		if (building != nil && !building->bIsBIGBuilding && aZones[zoneId].IsEntityCloseEnoughToZone(building, aPointersToBigBuildingsForBuildings[i] != -1)) {
			CBuilding *LODbuilding = nil;
			if (aPointersToBigBuildingsForBuildings[i] != -1)
				LODbuilding = CPools::GetBuildingPool()->GetSlot(aPointersToBigBuildingsForBuildings[i]);

			if (!aZones[zoneId].TestEntityVisibilityFromCullZone(building, 0.0f, LODbuilding)) {
				assert(TempEntityIndicesUsed < NUMTEMPINDICES);
				pTempArrayIndices[TempEntityIndicesUsed++] = i;
				aZones[zoneId].m_groupIndexCount[0]++;
			}
		}
	}

	for (int i = CPools::GetTreadablePool()->GetSize() - 1; i >= 0; i--) {
		CBuilding* building = CPools::GetTreadablePool()->GetSlot(i);
		if (building != nil && aZones[zoneId].IsEntityCloseEnoughToZone(building, aPointersToBigBuildingsForTreadables[i] != -1)) {
			CBuilding *LODbuilding = nil;
			if (aPointersToBigBuildingsForTreadables[i] != -1)
				LODbuilding = CPools::GetBuildingPool()->GetSlot(aPointersToBigBuildingsForTreadables[i]);

			if (!aZones[zoneId].TestEntityVisibilityFromCullZone(building, 10.0f, LODbuilding)) {
				assert(TempEntityIndicesUsed < NUMTEMPINDICES);
				pTempArrayIndices[TempEntityIndicesUsed++] = i;
				aZones[zoneId].m_groupIndexCount[1]++;
			}
		}
	}

	for (int i = CPools::GetTreadablePool()->GetSize() - 1; i >= 0; i--) {
		CBuilding *building = CPools::GetTreadablePool()->GetSlot(i);
		if (building != nil && aZones[zoneId].CalcDistToCullZoneSquared(building->GetPosition().x, building->GetPosition().y) < SQR(200.0f)) {
			int start = aZones[zoneId].m_groupIndexCount[0] + aZones[zoneId].m_indexStart;
			int end = aZones[zoneId].m_groupIndexCount[1] + start;

			bool alreadyAdded = false;

			for (int k = start; k < end; k++) {
#ifdef FIX_BUGS
				if (pTempArrayIndices[k] == i)
#else
				if (aIndices[k] == i)
#endif
					alreadyAdded = true;
			}

			if (!alreadyAdded) {
				CBuilding *LODbuilding = nil;
				if (aPointersToBigBuildingsForTreadables[i] != -1)
					LODbuilding = CPools::GetBuildingPool()->GetSlot(aPointersToBigBuildingsForTreadables[i]);
				if (!aZones[zoneId].TestEntityVisibilityFromCullZone(building, 0.0f, LODbuilding)) {
					assert(TempEntityIndicesUsed < NUMTEMPINDICES);
					pTempArrayIndices[TempEntityIndicesUsed++] = i;
					aZones[zoneId].m_groupIndexCount[2]++;
				}
			}
		}
	}
}

void
CCullZones::Update(void)
{
	bool invisible;

	if(bCullZonesDisabled)
		return;

	switch(CTimer::GetFrameCounter() & 7){
	case 0:
	case 4:
		/* Update Cull zone */
		ForceCullZoneCoors(TheCamera.GetGameCamPosition());
		break;

	case 2:
		/* Update camera attributes */
		CurrentFlags_Camera = FindAttributesForCoors(TheCamera.GetGameCamPosition(), nil);
		invisible = (CurrentFlags_Camera & ATTRZONE_SUBWAYVISIBLE) == 0;
		if(invisible != bCurrentSubwayIsInvisible){
			MarkSubwayAsInvisible(!invisible);
			bCurrentSubwayIsInvisible = invisible;
		}
		break;

	case 6:
		/* Update player attributes */
		CurrentFlags_Player = FindAttributesForCoors(FindPlayerCoors(),
			&CurrentWantedLevelDrop_Player);
		break;
	}
}

void
CCullZones::ForceCullZoneCoors(CVector coors)
{
	int32 z;
	z = FindCullZoneForCoors(coors);
	if(z != OldCullZone){
		if(OldCullZone >= 0)
			aZones[OldCullZone].DoStuffLeavingZone();
		if(z >= 0)
			aZones[z].DoStuffEnteringZone();
		OldCullZone = z;
	}
}

int32
CCullZones::FindCullZoneForCoors(CVector coors)
{
	int i;

	for(i = 0; i < NumCullZones; i++)
		if(coors.x >= aZones[i].minx && coors.x <= aZones[i].maxx &&
		   coors.y >= aZones[i].miny && coors.y <= aZones[i].maxy &&
		   coors.z >= aZones[i].minz && coors.z <= aZones[i].maxz)
			return i;
	return -1;
}

int32
CCullZones::FindAttributesForCoors(CVector coors, int32 *wantedLevel)
{
	int i;
	int32 attribs;

	if (wantedLevel)
		*wantedLevel = 0;
	attribs = 0;
	for(i = 0; i < NumAttributeZones; i++)
		if(coors.x >= aAttributeZones[i].minx && coors.x <= aAttributeZones[i].maxx &&
		   coors.y >= aAttributeZones[i].miny && coors.y <= aAttributeZones[i].maxy &&
		   coors.z >= aAttributeZones[i].minz && coors.z <= aAttributeZones[i].maxz){
			attribs |= aAttributeZones[i].attributes;
			if(wantedLevel)
				*wantedLevel = Max(*wantedLevel, aAttributeZones[i].wantedLevel);
		}
	return attribs;
}

CAttributeZone*
CCullZones::FindZoneWithStairsAttributeForPlayer(void)
{
	int i;
	CVector coors;

	coors = FindPlayerCoors();
	for(i = 0; i < NumAttributeZones; i++)
		if(aAttributeZones[i].attributes & ATTRZONE_STAIRS &&
		   coors.x >= aAttributeZones[i].minx && coors.x <= aAttributeZones[i].maxx &&
		   coors.y >= aAttributeZones[i].miny && coors.y <= aAttributeZones[i].maxy &&
		   coors.z >= aAttributeZones[i].minz && coors.z <= aAttributeZones[i].maxz)
			return &aAttributeZones[i];
	return nil;
}

void
CCullZones::MarkSubwayAsInvisible(bool visible)
{
	int i, n;
	CEntity *e;
	CVehicle *v;

	n = CPools::GetBuildingPool()->GetSize()-1;
	for(i = n; i >= 0; i--){
		e = CPools::GetBuildingPool()->GetSlot(i);
		if(e && e->bIsSubway)
			e->bIsVisible = visible;
	}

	n = CPools::GetTreadablePool()->GetSize()-1;
	for(i = n; i >= 0; i--){
		e = CPools::GetTreadablePool()->GetSlot(i);
		if(e && e->bIsSubway)
			e->bIsVisible = visible;
	}

	n = CPools::GetVehiclePool()->GetSize()-1;
	for(i = n; i >= 0; i--){
		v = CPools::GetVehiclePool()->GetSlot(i);
		if(v && v->IsTrain() && ((CTrain*)v)->m_nTrackId != TRACK_ELTRAIN)
			v->bIsVisible = visible;
	}
}

void
CCullZones::AddCullZone(CVector const &position,
                        float minx, float maxx,
                        float miny, float maxy,
                        float minz, float maxz,
                        uint16 flag, int16 wantedLevel)
{
	CCullZone *cull;
	CAttributeZone *attrib;

	CVector v;
	if((flag & ATTRZONE_NOTCULLZONE) == 0){
		cull = &aZones[NumCullZones++];
		v = position;
		// reposition start point to the start/end of the
		// alley next to the big building in the industrial district.
		// probably isn't analyzed correctly otherwise?s
		if((v-CVector(1032.14f, -624.255f, 24.93f)).Magnitude() < 1.0f)
			v = CVector(1061.7f, -613.0f, 19.0f);
		if((v-CVector(1029.48f, -495.757f, 21.98f)).Magnitude() < 1.0f)
			v = CVector(1061.4f, -506.0f, 18.5f);
		cull->position.x = clamp(v.x, minx, maxx);
		cull->position.y = clamp(v.y, miny, maxy);
		cull->position.z = clamp(v.z, minz, maxz);
		cull->minx = minx;
		cull->maxx = maxx;
		cull->miny = miny;
		cull->maxy = maxy;
		cull->minz = minz;
		cull->maxz = maxz;
		cull->m_groupIndexCount[0] = 0;
		cull->m_groupIndexCount[1] = 0;
		cull->m_groupIndexCount[2] = 0;
		cull->m_indexStart = 0;
	}
	if(flag & ~ATTRZONE_NOTCULLZONE){
		attrib = &aAttributeZones[NumAttributeZones++];
		attrib->minx = minx;
		attrib->maxx = maxx;
		attrib->miny = miny;
		attrib->maxy = maxy;
		attrib->minz = minz;
		attrib->maxz = maxz;
		attrib->attributes = flag;
		attrib->wantedLevel = wantedLevel;
	}
}

uint16 *pExtraArrayIndices;

void
CCullZones::CompressIndicesArray()
{
	uint16 set[3];

	// These are used to hold the compressed groups in sets of 3
	int numExtraIndices = 0;
	pExtraArrayIndices = new uint16[NUMTEMPINDICES];

	for(int numOccurrences = 6; numOccurrences > 1; numOccurrences--){
		if(NumCullZones == 0)
			break;

//printf("checking occurrences %d\n", numOccurrences);
		int attempt = 0;
		while(attempt < 10000){
			for(;;){
				attempt++;

				int zone = CGeneral::GetRandomNumber() % NumCullZones;
				int group = CGeneral::GetRandomNumber() % 3;
				if(!PickRandomSetForGroup(zone, group, set))
					break;
				if(!DoWeHaveMoreThanXOccurencesOfSet(numOccurrences, set))
					break;

				// add this set
				attempt = 1;
				int setId = numExtraIndices + NUMUNCOMPRESSED;
				pExtraArrayIndices[numExtraIndices++] = set[0];
				pExtraArrayIndices[numExtraIndices++] = set[1];
				pExtraArrayIndices[numExtraIndices++] = set[2];
				ReplaceSetForAllGroups(set, setId);
			}
		}
	}

	TidyUpAndMergeLists(pExtraArrayIndices, numExtraIndices);

	delete[] pExtraArrayIndices;
}

// Get three random indices for this group of a zone
bool
CCullZones::PickRandomSetForGroup(int32 zone, int32 group, uint16 *set)
{
	int32 start;
	int32 size;

	aZones[zone].GetGroupStartAndSize(group, start, size);
	if(size <= 0)
		return false;

	int numIndices = 0;
	for(int i = 0; i < size; i++)
		if(pTempArrayIndices[start + i] != 0xFFFF)
			numIndices++;
	if(numIndices < 3)
		return false;

	int first = CGeneral::GetRandomNumber() % (numIndices-2);

	numIndices = 0;
	int n = 0;
	for(int i = 0; i < size; i++)
		if(pTempArrayIndices[start + i] != 0xFFFF){
			if(n++ < first) continue;

			set[numIndices++] = pTempArrayIndices[start + i];
			if(numIndices == 3)
				break;
		}
	return true;
}

bool
CCullZones::DoWeHaveMoreThanXOccurencesOfSet(int32 count, uint16 *set)
{
	int32 curCount;
	int32 start;
	int32 size;

	curCount = 0;
	for (int i = 0; i < NumCullZones; i++) {
		for (int group = 0; group < 3; group++) {
			aZones[i].GetGroupStartAndSize(group, start, size);
			if(size <= 0) continue;

			// check if the set is a subset of the group
			int n = 0;
			for (int j = 0; j < size; j++) {
				for (int k = 0; k < 3; k++) {
					if (pTempArrayIndices[start+j] == set[k])
						n++;
				}
			}
			// yes it is
			if(n == 3){
				curCount++;
				// check if we have seen this set often enough
				if(curCount >= count)
					return true;
			}
		}
	}
	return false;
}

void
CCullZones::ReplaceSetForAllGroups(uint16 *set, uint16 setid)
{
	int32 start;
	int32 size;

	for(int i = 0; i < NumCullZones; i++)
		for(int group = 0; group < 3; group++){
			aZones[i].GetGroupStartAndSize(group, start, size);
			if(size <= 0) continue;

			// check if the set is a subset of the group
			int n = 0;
			for(int j = 0; j < size; j++){
				for(int k = 0; k < 3; k++){
					if(pTempArrayIndices[start+j] == set[k])
						n++;
				}
			}

			// yes it is, so replace it
			if(n == 3){
				bool insertedSet = false;
				for(int j = 0; j < size; j++){
					for(int k = 0; k < 3; k++){
						// replace first element by set, invalidate others
						if(pTempArrayIndices[start+j] == set[k]){
							if(!insertedSet)
								pTempArrayIndices[start+j] = setid;
							else
								pTempArrayIndices[start+j] = 0xFFFF;
							insertedSet = true;
						}
					}
				}
			}
		}
}

void
CCullZones::TidyUpAndMergeLists(uint16 *extraIndices, int32 numExtraIndices)
{
	int numTempIndices = 0;
	for(int i = 0; i < TempEntityIndicesUsed; i++)
		if(pTempArrayIndices[i] != 0xFFFF)
			numTempIndices++;

	// Fix up zone ranges such that there are no holes
	for(int i = 0; i < NumCullZones; i++){
		int j;
		int start = 0;
		for(j = 0; j < aZones[i].m_indexStart; j++)
			if(pTempArrayIndices[j] != 0xFFFF)
				start++;

		aZones[i].m_indexStart = start;
		aZones[i].m_numBuildings = 0;
		aZones[i].m_numTreadablesPlus10m = 0;
		aZones[i].m_numTreadables = 0;

		for(int k = 0; k < aZones[i].m_groupIndexCount[0]; k++)
			if(pTempArrayIndices[j++] != 0xFFFF)
				aZones[i].m_numBuildings++;
		for(int k = 0; k < aZones[i].m_groupIndexCount[1]; k++)
			if(pTempArrayIndices[j++] != 0xFFFF)
				aZones[i].m_numTreadablesPlus10m++;
		for(int k = 0; k < aZones[i].m_groupIndexCount[2]; k++)
			if(pTempArrayIndices[j++] != 0xFFFF)
				aZones[i].m_numTreadables++;
	}

	// Now copy the actually used indices
	EntityIndicesUsed = 0;
	for(int i = 0; i < TempEntityIndicesUsed; i++)
		if(pTempArrayIndices[i] != 0xFFFF){
			assert(EntityIndicesUsed < NUMZONEINDICES);
			if(pTempArrayIndices[i] < NUMUNCOMPRESSED)
				aIndices[EntityIndicesUsed++] = pTempArrayIndices[i];
			else
				aIndices[EntityIndicesUsed++] = pTempArrayIndices[i] + numTempIndices;
		}
	for(int i = 0; i < numExtraIndices; i++)
		if(extraIndices[i] != 0xFFFF){
			assert(EntityIndicesUsed < NUMZONEINDICES);
			if(extraIndices[i] < NUMUNCOMPRESSED)
				aIndices[EntityIndicesUsed++] = extraIndices[i];
			else
				aIndices[EntityIndicesUsed++] = extraIndices[i] + numTempIndices;
		}
}



void
CCullZone::DoStuffLeavingZone(void)
{
	int i;

	for(i = 0; i < m_numBuildings; i++)
		DoStuffLeavingZone_OneBuilding(CCullZones::aIndices[m_indexStart + i]);
	for(; i < m_numBuildings + m_numTreadablesPlus10m + m_numTreadables ; i++)
		DoStuffLeavingZone_OneTreadableBoth(CCullZones::aIndices[m_indexStart + i]);
}

void
CCullZone::DoStuffLeavingZone_OneBuilding(uint16 i)
{
	int16 bb;
	int j;


	if(i < NUMUNCOMPRESSED){
		CPools::GetBuildingPool()->GetSlot(i)->bZoneCulled = false;
		bb = CCullZones::aPointersToBigBuildingsForBuildings[i];
		if(bb != -1)
			CPools::GetBuildingPool()->GetSlot(bb)->bZoneCulled = false;
	}else{
		i -= NUMUNCOMPRESSED;
		for(j = 0; j < 3; j++)
			DoStuffLeavingZone_OneBuilding(CCullZones::aIndices[i+j]);
	}
}

void
CCullZone::DoStuffLeavingZone_OneTreadableBoth(uint16 i)
{
	int16 bb;
	int j;

	if(i < NUMUNCOMPRESSED){
		CPools::GetTreadablePool()->GetSlot(i)->bZoneCulled = false;
		CPools::GetTreadablePool()->GetSlot(i)->bZoneCulled2 = false;
		bb = CCullZones::aPointersToBigBuildingsForTreadables[i];
		if(bb != -1)
			CPools::GetBuildingPool()->GetSlot(bb)->bZoneCulled = false;
	}else{
		i -= NUMUNCOMPRESSED;
		for(j = 0; j < 3; j++)
			DoStuffLeavingZone_OneTreadableBoth(CCullZones::aIndices[i+j]);
	}
}

void
CCullZone::DoStuffEnteringZone(void)
{
	int i;

	for(i = 0; i < m_numBuildings; i++)
		DoStuffEnteringZone_OneBuilding(CCullZones::aIndices[m_indexStart + i]);
	for(; i < m_numBuildings + m_numTreadablesPlus10m; i++)
		DoStuffEnteringZone_OneTreadablePlus10m(CCullZones::aIndices[m_indexStart + i]);
	for(; i < m_numBuildings + m_numTreadablesPlus10m + m_numTreadables; i++)
		DoStuffEnteringZone_OneTreadable(CCullZones::aIndices[m_indexStart + i]);
}

void
CCullZone::DoStuffEnteringZone_OneBuilding(uint16 i)
{
	int16 bb;
	int j;

	if(i < NUMUNCOMPRESSED){
		CPools::GetBuildingPool()->GetSlot(i)->bZoneCulled = true;
		bb = CCullZones::aPointersToBigBuildingsForBuildings[i];
		if(bb != -1)
			CPools::GetBuildingPool()->GetSlot(bb)->bZoneCulled = true;
	}else{
		i -= NUMUNCOMPRESSED;
		for(j = 0; j < 3; j++)
			DoStuffEnteringZone_OneBuilding(CCullZones::aIndices[i+j]);
	}
}

void
CCullZone::DoStuffEnteringZone_OneTreadablePlus10m(uint16 i)
{
	int16 bb;
	int j;

	if(i < NUMUNCOMPRESSED){
		CPools::GetTreadablePool()->GetSlot(i)->bZoneCulled = true;
		CPools::GetTreadablePool()->GetSlot(i)->bZoneCulled2 = true;
		bb = CCullZones::aPointersToBigBuildingsForTreadables[i];
		if(bb != -1)
			CPools::GetBuildingPool()->GetSlot(bb)->bZoneCulled = true;
	}else{
		i -= NUMUNCOMPRESSED;
		for(j = 0; j < 3; j++)
			DoStuffEnteringZone_OneTreadablePlus10m(CCullZones::aIndices[i+j]);
	}
}

void
CCullZone::DoStuffEnteringZone_OneTreadable(uint16 i)
{
	int16 bb;
	int j;

	if(i < NUMUNCOMPRESSED){
		CPools::GetTreadablePool()->GetSlot(i)->bZoneCulled = true;
		bb = CCullZones::aPointersToBigBuildingsForTreadables[i];
		if(bb != -1)
			CPools::GetBuildingPool()->GetSlot(bb)->bZoneCulled = true;
	}else{
		i -= NUMUNCOMPRESSED;
		for(j = 0; j < 3; j++)
			DoStuffEnteringZone_OneTreadable(CCullZones::aIndices[i+j]);
	}
}

float
CCullZone::CalcDistToCullZoneSquared(float x, float y)
{
	float rx, ry;

	if (x < minx) rx = sq(x - minx);
	else if (x > maxx) rx = sq(x - maxx);
	else rx = 0.0f;

	if (y < miny) ry = sq(y - miny);
	else if (y > maxy) ry = sq(y - maxy);
	else ry = 0.0f;

	return rx + ry;
}

bool
CCullZone::TestLine(CVector vec1, CVector vec2)
{
	CColPoint colPoint;
	CEntity *entity;

	if (CWorld::ProcessLineOfSight(vec1, vec2, colPoint, entity, true, false, false, false, false, true, false))
		return true;
	if (CWorld::ProcessLineOfSight(CVector(vec1.x + 0.05f, vec1.y, vec1.z), CVector(vec2.x + 0.05f, vec2.y, vec2.z), colPoint, entity, true, false, false, false, false, true, false))
		return true;
	if (CWorld::ProcessLineOfSight(CVector(vec1.x - 0.05f, vec1.y, vec1.z), CVector(vec2.x - 0.05f, vec2.y, vec2.z), colPoint, entity, true, false, false, false, false, true, false))
		return true;
	if (CWorld::ProcessLineOfSight(CVector(vec1.x, vec1.y + 0.05f, vec1.z), CVector(vec2.x, vec2.y + 0.05f, vec2.z), colPoint, entity, true, false, false, false, false, true, false))
		return true;
	if (CWorld::ProcessLineOfSight(CVector(vec1.x, vec1.y - 0.05f, vec1.z), CVector(vec2.x, vec2.y - 0.05f, vec2.z), colPoint, entity, true, false, false, false, false, true, false))
		return true;
	if (CWorld::ProcessLineOfSight(CVector(vec1.x, vec1.y, vec1.z + 0.05f), CVector(vec2.x, vec2.y, vec2.z + 0.05f), colPoint, entity, true, false, false, false, false, true, false))
		return true;
	return CWorld::ProcessLineOfSight(CVector(vec1.x, vec1.y, vec1.z - 0.05f), CVector(vec2.x, vec2.y, vec2.z - 0.05f), colPoint, entity, true, false, false, false, false, true, false);
}

bool
CCullZone::DoThoroughLineTest(CVector start, CVector end, CEntity *testEntity)
{
	CColPoint colPoint;
	CEntity *entity;

	if(CWorld::ProcessLineOfSight(start, end, colPoint, entity, true, false, false, false, false, true, false) &&
	   testEntity != entity)
		return false;

	CVector side;
#ifdef FIX_BUGS
	if(start.x != end.x || start.y != end.y)
#else
	if(start.x != end.x && start.y != end.y)
#endif
		side = CVector(0.0f, 0.0f, 1.0f);
	else
		side = CVector(1.0f, 0.0f, 0.0f);
	CVector up = CrossProduct(side, end - start);
	side = CrossProduct(up, end - start);
	side.Normalise();
	up.Normalise();
	side *= 0.1f;
	up *= 0.1f;

	if(CWorld::ProcessLineOfSight(start+side, end+side, colPoint, entity, true, false, false, false, false, true, false) &&
	   testEntity != entity)
		return false;
	if(CWorld::ProcessLineOfSight(start-side, end-side, colPoint, entity, true, false, false, false, false, true, false) &&
	   testEntity != entity)
		return false;
	if(CWorld::ProcessLineOfSight(start+up, end+up, colPoint, entity, true, false, false, false, false, true, false) &&
	   testEntity != entity)
		return false;
	if(CWorld::ProcessLineOfSight(start-up, end-up, colPoint, entity, true, false, false, false, false, true, false) &&
	   testEntity != entity)
		return false;
	return true;
}

bool
CCullZone::IsEntityCloseEnoughToZone(CEntity *entity, bool checkLevel)
{
	const CVector &pos = entity->GetPosition();

	CSimpleModelInfo *minfo = (CSimpleModelInfo*)CModelInfo::GetModelInfo(entity->GetModelIndex());
	float distToZone = CalcDistToCullZone(pos.x, pos.y);
	float lodDist;
	if (minfo->m_noFade)
		lodDist = minfo->GetLargestLodDistance() + STREAM_DISTANCE;
	else
		lodDist = minfo->GetLargestLodDistance() + STREAM_DISTANCE + FADE_DISTANCE;

	if (lodDist > distToZone) return true;
	if (!checkLevel) return false;
	CVector tempPos(minx, miny, minz);
	return CTheZones::GetLevelFromPosition(&pos) == CTheZones::GetLevelFromPosition(&tempPos);
}

bool
CCullZone::PointFallsWithinZone(CVector pos, float radius)
{
	if(minx - radius > pos.x ||
	   maxx + radius < pos.x ||
	   miny - radius > pos.y ||
	   maxy + radius < pos.y ||
	   minz - radius > pos.z ||
	   maxz + radius < pos.z)
		return false;
	return true;
}


CVector ExtraFudgePointsCoors[] = {
	CVector(978.0, -394.0, 18.0),
	CVector(1189.7, -414.6, 27.0),
	CVector(978.8, -391.0, 19.0),
	CVector(1199.0, -502.3, 28.0),
	CVector(1037.0, -391.9, 18.4),
	CVector(1140.0, -608.7, 16.0),
	CVector(1051.0, -26.0, 11.0),
	CVector(951.5, -345.1, 12.0),
	CVector(958.2, -394.6, 16.0),
	CVector(1036.5, -390.0, 15.2),
	CVector(960.6, -390.5, 20.9),
	CVector(1061.0, -640.6, 16.3),
	CVector(1034.5, -388.96, 14.78),
	CVector(1038.4, -13.98, 12.2),
	CVector(1047.2, -16.7, 10.6),
	CVector(1257.9, -333.3, 40.0),
	CVector(885.6, -424.9, 17.0),
	CVector(1127.5, -795.8, 17.7),
	CVector(1133.0, -716.0, 19.0),
	CVector(1125.0, -694.0, 18.5),
	CVector(1125.0, -670.0, 16.3),
	CVector(1051.6, 36.3, 17.9),
	CVector(1054.6, -11.4, 15.0),
	CVector(1058.9, -278.0, 15.0),
	CVector(1059.4, -261.0, 10.9),
	CVector(1051.5, -638.5, 16.5),
	CVector(1058.2, -643.4, 15.5),
	CVector(1058.2, -643.4, 18.0),
	CVector(826.0, -260.0, 7.0),
	CVector(826.0, -260.0, 11.0),
	CVector(833.0, -603.6, 16.4),
	CVector(833.0, -603.6, 20.0),
	CVector(1002.0, -318.5, 10.5),
	CVector(998.0, -318.0, 9.8),
	CVector(1127.0, -183.0, 18.1),
	CVector(1123.0, -331.5, 23.8),
	CVector(1123.8, -429.0, 24.0),
	CVector(1197.0, -30.0, 13.7),
	CVector(1117.5, -230.0, 17.3),
	CVector(1117.5, -230.0, 20.0),
	CVector(1120.0, -281.6, 21.5),
	CVector(1120.0, -281.6, 24.0),
	CVector(1084.5, -1022.7, 17.0),
	CVector(1071.5, 5.4, 4.6),
	CVector(1177.2, -215.7, 27.6),
	CVector(841.6, -460.0, 19.7),
	CVector(874.8, -456.6, 16.6),
	CVector(918.3, -451.8, 17.8),
	CVector(844.0, -495.7, 16.7),
	CVector(842.0, -493.4, 21.0),
	CVector(1433.5, -774.4, 16.9),
	CVector(1051.0, -205.0, 7.5),
	CVector(885.5, -425.6, 15.6),
	CVector(182.6, -470.4, 27.8),
	CVector(132.5, -930.2, 29.0),
	CVector(124.7, -904.0, 28.0),
	CVector(-50.0, -686.0, 22.0),
	CVector(-49.1, -694.5, 22.5),
	CVector(1063.8, -404.45, 16.2),
	CVector(1062.2, -405.5, 17.0)
};
int32 NumTestPoints;
int32 aTestPointsX[100];
int32 aTestPointsY[100];
int32 aTestPointsZ[100];
CVector aTestPoints[100];
int32 ElementsX, ElementsY, ElementsZ;
float StepX, StepY, StepZ;
int32 Memsize;
uint8 *pMem;
#define MEM(x, y, z) pMem[((x)*ElementsY + (y))*ElementsZ + (z)]
#define FLAG_FREE 1
#define FLAG_PROCESSED 2

int32 MinValX, MaxValX;
int32 MinValY, MaxValY;
int32 MinValZ, MaxValZ;
int32 Point1, Point2;
int32 NewPointX, NewPointY, NewPointZ;


void
CCullZone::FindTestPoints()
{
	static int CZNumber;

	NumTestPoints = 0;
	ElementsX = (maxx-minx) < 1.0f ? 2 : (maxx-minx)+1.0f;
	ElementsY = (maxy-miny) < 1.0f ? 2 : (maxy-miny)+1.0f;
	ElementsZ = (maxz-minz) < 1.0f ? 2 : (maxz-minz)+1.0f;
	if(ElementsX > 32) ElementsX = 32;
	if(ElementsY > 32) ElementsY = 32;
	if(ElementsZ > 32) ElementsZ = 32;
	Memsize = ElementsX * ElementsY * ElementsZ;
	StepX = (maxx-minx)/(ElementsX-1);
	StepY = (maxy-miny)/(ElementsY-1);
	StepZ = (maxz-minz)/(ElementsZ-1);

	pMem = new uint8[Memsize];
	memset(pMem, 0, Memsize);

	// indices of center
	int x = ElementsX * (position.x-minx)/(maxx-minx);
	x = clamp(x, 0, ElementsX-1);
	int y = ElementsY * (position.y-miny)/(maxy-miny);
	y = clamp(y, 0, ElementsY-1);
	int z = ElementsZ * (position.z-minz)/(maxz-minz);
	z = clamp(z, 0, ElementsZ-1);

	// Mark which test points inside the zone are not occupied by buildings.
	// To do this, mark the start point as free and do a food fill.

	// NB: we just assume the start position is free here!
	MEM(x, y, z) |= FLAG_FREE;
	aTestPointsX[NumTestPoints] = x;
	aTestPointsY[NumTestPoints] = y;
	aTestPointsZ[NumTestPoints] = z;
	NumTestPoints++;

	bool notDoneYet;
	do{
		notDoneYet = false;
		for(x = 0; x < ElementsX; x++){
			for(y = 0; y < ElementsY; y++){
				for(z = 0; z < ElementsZ; z++){
					if(!(MEM(x, y, z) & FLAG_FREE) || MEM(x, y, z) & FLAG_PROCESSED)
						continue;

					float pX = x*StepX + minx;
					float pY = y*StepY + miny;
					float pZ = z*StepZ + minz;

					if(x > 0 && !(MEM(x-1, y, z) & (FLAG_FREE | FLAG_PROCESSED)) &&
					   !TestLine(CVector(pX, pY, pZ), CVector(pX-StepX, pY, pZ)))
						MEM(x-1, y, z) |= FLAG_FREE;
					if(x < ElementsX-1 && !(MEM(x+1, y, z) & (FLAG_FREE | FLAG_PROCESSED)) &&
					   !TestLine(CVector(pX, pY, pZ), CVector(pX+StepX, pY, pZ)))
						MEM(x+1, y, z) |= FLAG_FREE;

					if(y > 0 && !(MEM(x, y-1, z) & (FLAG_FREE | FLAG_PROCESSED)) &&
					   !TestLine(CVector(pX, pY, pZ), CVector(pX, pY-StepY, pZ)))
						MEM(x, y-1, z) |= FLAG_FREE;
					if(y < ElementsY-1 && !(MEM(x, y+1, z) & (FLAG_FREE | FLAG_PROCESSED)) &&
					   !TestLine(CVector(pX, pY, pZ), CVector(pX, pY+StepY, pZ)))
						MEM(x, y+1, z) |= FLAG_FREE;

					if(z > 0 && !(MEM(x, y, z-1) & (FLAG_FREE | FLAG_PROCESSED)) &&
					   !TestLine(CVector(pX, pY, pZ), CVector(pX, pY, pZ-StepZ)))
						MEM(x, y, z-1) |= FLAG_FREE;
					if(z < ElementsZ-1 && !(MEM(x, y, z+1) & (FLAG_FREE | FLAG_PROCESSED)) &&
					   !TestLine(CVector(pX, pY, pZ), CVector(pX, pY, pZ+StepZ)))
						MEM(x, y, z+1) |= FLAG_FREE;

					notDoneYet = true;
					MEM(x, y, z) |= FLAG_PROCESSED;
				}
			}
		}
	}while(notDoneYet);

	bool done;

	// Find bound planes of free space

	// increase x, bounds in y and z
	x = 0;
	do{
		done = false;
		int minA = 10000;
		int minB = 10000;
		int maxA = -10000;
		int maxB = -10000;
		for(y = 0; y < ElementsY; y++)
			for(z = 0; z < ElementsZ; z++)
				if(MEM(x, y, z) & FLAG_FREE){
					if(y + z < minA){
						minA = y + z;
						aTestPointsX[NumTestPoints] = x;
						aTestPointsY[NumTestPoints] = y;
						aTestPointsZ[NumTestPoints] = z;
					}
					if(y + z > maxA){
						maxA = y + z;
						aTestPointsX[NumTestPoints+1] = x;
						aTestPointsY[NumTestPoints+1] = y;
						aTestPointsZ[NumTestPoints+1] = z;
					}
					if(y - z < minB){
						minB = y - z;
						aTestPointsX[NumTestPoints+2] = x;
						aTestPointsY[NumTestPoints+2] = y;
						aTestPointsZ[NumTestPoints+2] = z;
					}
					if(y - z > maxB){
						maxB = y - z;
						aTestPointsX[NumTestPoints+3] = x;
						aTestPointsY[NumTestPoints+3] = y;
						aTestPointsZ[NumTestPoints+3] = z;
					}
					done = true;
				}
		x++;
	}while(!done);
	NumTestPoints += 4;

	// decrease x, bounds in y and z
	x = ElementsX-1;
	do{
		done = false;
		int minA = 10000;
		int minB = 10000;
		int maxA = -10000;
		int maxB = -10000;
		for(y = 0; y < ElementsY; y++)
			for(z = 0; z < ElementsZ; z++)
				if(MEM(x, y, z) & FLAG_FREE){
					if(y + z < minA){
						minA = y + z;
						aTestPointsX[NumTestPoints] = x;
						aTestPointsY[NumTestPoints] = y;
						aTestPointsZ[NumTestPoints] = z;
					}
					if(y + z > maxA){
						maxA = y + z;
						aTestPointsX[NumTestPoints+1] = x;
						aTestPointsY[NumTestPoints+1] = y;
						aTestPointsZ[NumTestPoints+1] = z;
					}
					if(y - z < minB){
						minB = y - z;
						aTestPointsX[NumTestPoints+2] = x;
						aTestPointsY[NumTestPoints+2] = y;
						aTestPointsZ[NumTestPoints+2] = z;
					}
					if(y - z > maxB){
						maxB = y - z;
						aTestPointsX[NumTestPoints+3] = x;
						aTestPointsY[NumTestPoints+3] = y;
						aTestPointsZ[NumTestPoints+3] = z;
					}
					done = true;
				}
		x--;
	}while(!done);
	NumTestPoints += 4;

	// increase y, bounds in x and z
	y = 0;
	do{
		done = false;
		int minA = 10000;
		int minB = 10000;
		int maxA = -10000;
		int maxB = -10000;
		for(x = 0; x < ElementsX; x++)
			for(z = 0; z < ElementsZ; z++)
				if(MEM(x, y, z) & FLAG_FREE){
					if(x + z < minA){
						minA = x + z;
						aTestPointsX[NumTestPoints] = x;
						aTestPointsY[NumTestPoints] = y;
						aTestPointsZ[NumTestPoints] = z;
					}
					if(x + z > maxA){
						maxA = x + z;
						aTestPointsX[NumTestPoints+1] = x;
						aTestPointsY[NumTestPoints+1] = y;
						aTestPointsZ[NumTestPoints+1] = z;
					}
					if(x - z < minB){
						minB = x - z;
						aTestPointsX[NumTestPoints+2] = x;
						aTestPointsY[NumTestPoints+2] = y;
						aTestPointsZ[NumTestPoints+2] = z;
					}
					if(x - z > maxB){
						maxB = x - z;
						aTestPointsX[NumTestPoints+3] = x;
						aTestPointsY[NumTestPoints+3] = y;
						aTestPointsZ[NumTestPoints+3] = z;
					}
					done = true;
				}
		y++;
	}while(!done);
	NumTestPoints += 4;

	// decrease y, bounds in x and z
	y = ElementsY-1;
	do{
		done = false;
		int minA = 10000;
		int minB = 10000;
		int maxA = -10000;
		int maxB = -10000;
		for(x = 0; x < ElementsX; x++)
			for(z = 0; z < ElementsZ; z++)
				if(MEM(x, y, z) & FLAG_FREE){
					if(x + z < minA){
						minA = x + z;
						aTestPointsX[NumTestPoints] = x;
						aTestPointsY[NumTestPoints] = y;
						aTestPointsZ[NumTestPoints] = z;
					}
					if(x + z > maxA){
						maxA = x + z;
						aTestPointsX[NumTestPoints+1] = x;
						aTestPointsY[NumTestPoints+1] = y;
						aTestPointsZ[NumTestPoints+1] = z;
					}
					if(x - z < minB){
						minB = x - z;
						aTestPointsX[NumTestPoints+2] = x;
						aTestPointsY[NumTestPoints+2] = y;
						aTestPointsZ[NumTestPoints+2] = z;
					}
					if(x - z > maxB){
						maxB = x - z;
						aTestPointsX[NumTestPoints+3] = x;
						aTestPointsY[NumTestPoints+3] = y;
						aTestPointsZ[NumTestPoints+3] = z;
					}
					done = true;
				}
		y--;
	}while(!done);
	NumTestPoints += 4;

	// increase z, bounds in x and y
	z = 0;
	do{
		done = false;
		int minA = 10000;
		int minB = 10000;
		int maxA = -10000;
		int maxB = -10000;
		for(x = 0; x < ElementsX; x++)
			for(y = 0; y < ElementsY; y++)
				if(MEM(x, y, z) & FLAG_FREE){
					if(x + y < minA){
						minA = x + y;
						aTestPointsX[NumTestPoints] = x;
						aTestPointsY[NumTestPoints] = y;
						aTestPointsZ[NumTestPoints] = z;
					}
					if(x + y > maxA){
						maxA = x + y;
						aTestPointsX[NumTestPoints+1] = x;
						aTestPointsY[NumTestPoints+1] = y;
						aTestPointsZ[NumTestPoints+1] = z;
					}
					if(x - y < minB){
						minB = x - y;
						aTestPointsX[NumTestPoints+2] = x;
						aTestPointsY[NumTestPoints+2] = y;
						aTestPointsZ[NumTestPoints+2] = z;
					}
					if(x - y > maxB){
						maxB = x - y;
						aTestPointsX[NumTestPoints+3] = x;
						aTestPointsY[NumTestPoints+3] = y;
						aTestPointsZ[NumTestPoints+3] = z;
					}
					done = true;
				}
		z++;
	}while(!done);
	NumTestPoints += 4;

	// decrease z, bounds in x and y
	z = ElementsZ-1;
	do{
		done = false;
		int minA = 10000;
		int minB = 10000;
		int maxA = -10000;
		int maxB = -10000;
		for(x = 0; x < ElementsX; x++)
			for(y = 0; y < ElementsY; y++)
				if(MEM(x, y, z) & FLAG_FREE){
					if(x + y < minA){
						minA = x + y;
						aTestPointsX[NumTestPoints] = x;
						aTestPointsY[NumTestPoints] = y;
						aTestPointsZ[NumTestPoints] = z;
					}
					if(x + y > maxA){
						maxA = x + y;
						aTestPointsX[NumTestPoints+1] = x;
						aTestPointsY[NumTestPoints+1] = y;
						aTestPointsZ[NumTestPoints+1] = z;
					}
					if(x - y < minB){
						minB = x - y;
						aTestPointsX[NumTestPoints+2] = x;
						aTestPointsY[NumTestPoints+2] = y;
						aTestPointsZ[NumTestPoints+2] = z;
					}
					if(x - y > maxB){
						maxB = x - y;
						aTestPointsX[NumTestPoints+3] = x;
						aTestPointsY[NumTestPoints+3] = y;
						aTestPointsZ[NumTestPoints+3] = z;
					}
					done = true;
				}
		z--;
	}while(!done);
	NumTestPoints += 4;

	// divide the axis aligned bounding planes into 4 and place some test points

	// x = 0 plane
	MinValY = 999999;
	MinValZ = 999999;
	MaxValY = 0;
	MaxValZ = 0;
	for(y = 0; y < ElementsY; y++)
		for(z = 0; z < ElementsZ; z++)
			if(MEM(0, y, z) & FLAG_FREE){
				if(y < MinValY) MinValY = y;
				if(z < MinValZ) MinValZ = z;
				if(y > MaxValY) MaxValY = y;
				if(z > MaxValZ) MaxValZ = z;
			}
	// pick 4 points in the found bounds and add new test points
	if(MaxValY != 0 && MaxValZ != 0)
		for(Point1 = 0; Point1 < 2; Point1++)
			for(Point2 = 0; Point2 < 2; Point2++){
				NewPointY = (Point1 + 0.5f)*(MaxValY - MinValY)*0.5f + MinValY;
				NewPointZ = (Point2 + 0.5f)*(MaxValZ - MinValZ)*0.5f + MinValZ;
				if(MEM(0, NewPointY, NewPointZ) & FLAG_FREE){
					aTestPointsX[NumTestPoints] = 0;
					aTestPointsY[NumTestPoints] = NewPointY;
					aTestPointsZ[NumTestPoints] = NewPointZ;
					NumTestPoints++;
				}
			}

	// x = ElementsX-1 plane
	MinValY = 999999;
	MinValZ = 999999;
	MaxValY = 0;
	MaxValZ = 0;
	for(y = 0; y < ElementsY; y++)
		for(z = 0; z < ElementsZ; z++)
			if(MEM(ElementsX-1, y, z) & FLAG_FREE){
				if(y < MinValY) MinValY = y;
				if(z < MinValZ) MinValZ = z;
				if(y > MaxValY) MaxValY = y;
				if(z > MaxValZ) MaxValZ = z;
			}
	// pick 4 points in the found bounds and add new test points
	if(MaxValY != 0 && MaxValZ != 0)
		for(Point1 = 0; Point1 < 2; Point1++)
			for(Point2 = 0; Point2 < 2; Point2++){
				NewPointY = (Point1 + 0.5f)*(MaxValY - MinValY)*0.5f + MinValY;
				NewPointZ = (Point2 + 0.5f)*(MaxValZ - MinValZ)*0.5f + MinValZ;
				if(MEM(ElementsX-1, NewPointY, NewPointZ) & FLAG_FREE){
					aTestPointsX[NumTestPoints] = ElementsX-1;
					aTestPointsY[NumTestPoints] = NewPointY;
					aTestPointsZ[NumTestPoints] = NewPointZ;
					NumTestPoints++;
				}
			}

	// y = 0 plane
	MinValX = 999999;
	MinValZ = 999999;
	MaxValX = 0;
	MaxValZ = 0;
	for(x = 0; x < ElementsX; x++)
		for(z = 0; z < ElementsZ; z++)
			if(MEM(x, 0, z) & FLAG_FREE){
				if(x < MinValX) MinValX = x;
				if(z < MinValZ) MinValZ = z;
				if(x > MaxValX) MaxValX = x;
				if(z > MaxValZ) MaxValZ = z;
			}
	// pick 4 points in the found bounds and add new test points
	if(MaxValX != 0 && MaxValZ != 0)
		for(Point1 = 0; Point1 < 2; Point1++)
			for(Point2 = 0; Point2 < 2; Point2++){
				NewPointX = (Point1 + 0.5f)*(MaxValX - MinValX)*0.5f + MinValX;
				NewPointZ = (Point2 + 0.5f)*(MaxValZ - MinValZ)*0.5f + MinValZ;
				if(MEM(NewPointX, 0, NewPointZ) & FLAG_FREE){
					aTestPointsX[NumTestPoints] = NewPointX;
					aTestPointsY[NumTestPoints] = 0;
					aTestPointsZ[NumTestPoints] = NewPointZ;
					NumTestPoints++;
				}
			}

	// y = ElementsY-1 plane
	MinValX = 999999;
	MinValZ = 999999;
	MaxValX = 0;
	MaxValZ = 0;
	for(x = 0; x < ElementsX; x++)
		for(z = 0; z < ElementsZ; z++)
			if(MEM(x, ElementsY-1, z) & FLAG_FREE){
				if(x < MinValX) MinValX = x;
				if(z < MinValZ) MinValZ = z;
				if(x > MaxValX) MaxValX = x;
				if(z > MaxValZ) MaxValZ = z;
			}
	// pick 4 points in the found bounds and add new test points
	if(MaxValX != 0 && MaxValZ != 0)
		for(Point1 = 0; Point1 < 2; Point1++)
			for(Point2 = 0; Point2 < 2; Point2++){
				NewPointX = (Point1 + 0.5f)*(MaxValX - MinValX)*0.5f + MinValX;
				NewPointZ = (Point2 + 0.5f)*(MaxValZ - MinValZ)*0.5f + MinValZ;
				if(MEM(NewPointX, ElementsY-1, NewPointZ) & FLAG_FREE){
					aTestPointsX[NumTestPoints] = NewPointX;
					aTestPointsY[NumTestPoints] = ElementsY-1;
					aTestPointsZ[NumTestPoints] = NewPointZ;
					NumTestPoints++;
				}
			}

	// z = 0 plane
	MinValX = 999999;
	MinValY = 999999;
	MaxValX = 0;
	MaxValY = 0;
	for(x = 0; x < ElementsX; x++)
		for(y = 0; y < ElementsY; y++)
			if(MEM(x, y, 0) & FLAG_FREE){
				if(x < MinValX) MinValX = x;
				if(y < MinValY) MinValY = y;
				if(x > MaxValX) MaxValX = x;
				if(y > MaxValY) MaxValY = y;
			}
	// pick 4 points in the found bounds and add new test points
	if(MaxValX != 0 && MaxValY != 0)
		for(Point1 = 0; Point1 < 2; Point1++)
			for(Point2 = 0; Point2 < 2; Point2++){
				NewPointX = (Point1 + 0.5f)*(MaxValX - MinValX)*0.5f + MinValX;
				NewPointY = (Point2 + 0.5f)*(MaxValY - MinValY)*0.5f + MinValY;
				if(MEM(NewPointX, NewPointY, 0) & FLAG_FREE){
					aTestPointsX[NumTestPoints] = NewPointX;
					aTestPointsY[NumTestPoints] = NewPointY;
					aTestPointsZ[NumTestPoints] = 0;
					NumTestPoints++;
				}
			}

	// z = ElementsZ-1 plane
	MinValX = 999999;
	MinValY = 999999;
	MaxValX = 0;
	MaxValY = 0;
	for(x = 0; x < ElementsX; x++)
		for(y = 0; y < ElementsY; y++)
			if(MEM(x, y, ElementsZ-1) & FLAG_FREE){
				if(x < MinValX) MinValX = x;
				if(y < MinValY) MinValY = y;
				if(x > MaxValX) MaxValX = x;
				if(y > MaxValY) MaxValY = y;
			}
	// pick 4 points in the found bounds and add new test points
	if(MaxValX != 0 && MaxValY != 0)
		for(Point1 = 0; Point1 < 2; Point1++)
			for(Point2 = 0; Point2 < 2; Point2++){
				NewPointX = (Point1 + 0.5f)*(MaxValX - MinValX)*0.5f + MinValX;
				NewPointY = (Point2 + 0.5f)*(MaxValY - MinValY)*0.5f + MinValY;
				if(MEM(NewPointX, NewPointY, ElementsZ-1) & FLAG_FREE){
					aTestPointsX[NumTestPoints] = NewPointX;
					aTestPointsY[NumTestPoints] = NewPointY;
					aTestPointsZ[NumTestPoints] = ElementsZ-1;
					NumTestPoints++;
				}
			}

	// add some hardcoded test points
	for(int i = 0; i < ARRAY_SIZE(ExtraFudgePointsCoors); i++)
		if(PointFallsWithinZone(ExtraFudgePointsCoors[i], 0.0f)){
			x = ElementsX * (ExtraFudgePointsCoors[i].x-minx)/(maxx-minx);
			y = ElementsY * (ExtraFudgePointsCoors[i].y-miny)/(maxy-miny);
			z = ElementsZ * (ExtraFudgePointsCoors[i].z-minz)/(maxz-minz);
			if(MEM(x, y, z) & FLAG_FREE){
				aTestPointsX[NumTestPoints] = x;
				aTestPointsY[NumTestPoints] = y;
				aTestPointsZ[NumTestPoints] = z;
				NumTestPoints++;
			}
		}

	// remove duplicate points
	for(int i = 0; i < NumTestPoints; i++)
		for(int j = i+1; j < NumTestPoints; j++)
			if(aTestPointsX[j] == aTestPointsX[i] &&
			   aTestPointsY[j] == aTestPointsY[i] &&
			   aTestPointsZ[j] == aTestPointsZ[i]){
				// get rid of [j]
				for(int k = j; k < NumTestPoints-1; k++){
					aTestPointsX[k] = aTestPointsX[k+1];
					aTestPointsY[k] = aTestPointsY[k+1];
					aTestPointsZ[k] = aTestPointsZ[k+1];
				}
				NumTestPoints--;
			}

	// convert points to floating point
	for(int i = 0; i < NumTestPoints; i++){
		aTestPoints[i].x = aTestPointsX[i]*StepX + minx;
		aTestPoints[i].y = aTestPointsY[i]*StepY + miny;
		aTestPoints[i].z = aTestPointsZ[i]*StepZ + minz;
	}

	CZNumber++;

	delete[] pMem;
	pMem = nil;
}

bool
CCullZone::TestEntityVisibilityFromCullZone(CEntity *entity, float extraDist, CEntity *LODentity)
{
	CColModel *colmodel = entity->GetColModel();
	float boundMaxX = colmodel->boundingBox.max.x;
	float boundMaxY = colmodel->boundingBox.max.y;
	float boundMaxZ = colmodel->boundingBox.max.z;
	float boundMinX = colmodel->boundingBox.min.x;
	float boundMinY = colmodel->boundingBox.min.y;
	float boundMinZ = colmodel->boundingBox.min.z;
	if(LODentity){
		colmodel = LODentity->GetColModel();
		boundMaxX = Max(boundMaxX, colmodel->boundingBox.max.x);
		boundMaxY = Max(boundMaxY, colmodel->boundingBox.max.y);
		boundMaxZ = Max(boundMaxZ, colmodel->boundingBox.max.z);
		boundMinX = Min(boundMinX, colmodel->boundingBox.min.x);
		boundMinY = Min(boundMinY, colmodel->boundingBox.min.y);
		boundMinZ = Min(boundMinZ, colmodel->boundingBox.min.z);
	}

	if(boundMaxZ-boundMinZ + extraDist < 0.5f)
		boundMaxZ = boundMinZ + 0.5f;
	else
		boundMaxZ += extraDist;

	CVector vecMin = entity->GetMatrix() * CVector(boundMinX, boundMinY, boundMinZ);
	CVector vecMaxX = entity->GetMatrix() * CVector(boundMaxX, boundMinY, boundMinZ);
	CVector vecMaxY = entity->GetMatrix() * CVector(boundMinX, boundMaxY, boundMinZ);
	CVector vecMaxZ = entity->GetMatrix() * CVector(boundMinX, boundMinY, boundMaxZ);
	CVector dirx = vecMaxX - vecMin;
	CVector diry = vecMaxY - vecMin;
	CVector dirz = vecMaxZ - vecMin;

	// If building intersects zone at all, it's visible
	int x, y, z;
	for(x = 0; x < 9; x++){
		CVector posX = vecMin + x/8.0f*dirx;
		for(y = 0; y < 9; y++){
			CVector posY = posX + y/8.0f*diry;
			for(z = 0; z < 9; z++){
 				CVector posZ = posY + z/8.0f*dirz;
				if(PointFallsWithinZone(posZ, 2.0f))
					return true;
			}
		}
	}

	float distToZone = CalcDistToCullZone(entity->GetPosition().x, entity->GetPosition().y)/15.0f;
	distToZone = Max(distToZone, 7.0f);
	int numX = (boundMaxX - boundMinX)/distToZone + 2.0f;
	int numY = (boundMaxY - boundMinY)/distToZone + 2.0f;
	int numZ = (boundMaxZ - boundMinZ)/distToZone + 2.0f;

	float stepX = 1.0f/(numX-1);
	float stepY = 1.0f/(numY-1);
	float stepZ = 1.0f/(numZ-1);
	float midX = (boundMaxX + boundMinX)/2.0f;
	float midY = (boundMaxY + boundMinY)/2.0f;
	float midZ = (boundMaxZ + boundMinZ)/2.0f;

	// check both xy planes
	for(int i = 0; i < NumTestPoints; i++){
		CVector testPoint = aTestPoints[i];
		CVector mid = entity->GetMatrix() * CVector(midX, midY, midZ);
		mid.z += 0.1f;
		if(DoThoroughLineTest(testPoint, mid, entity))
			return true;

		CVector ray = entity->GetPosition() - testPoint;

		float dotX = DotProduct(ray, dirx);
		float dotY = DotProduct(ray, diry);
		float dotZ = DotProduct(ray, dirz);

		for(x = 0; x < numX; x++){
			CVector pMinZ = vecMin + x*stepX*dirx;
			CVector pMaxZ = vecMin + x*stepX*dirx + dirz;
			for(y = 0; y < numY; y++)
				if(dotZ > 0.0f){
					if(DoThoroughLineTest(testPoint, pMinZ + y*stepY*diry, entity))
						return true;
				}else{
					if(DoThoroughLineTest(testPoint, pMaxZ + y*stepY*diry, entity))
						return true;
				}
		}

		for(x = 0; x < numX; x++){
			CVector pMinY = vecMin + x*stepX*dirx;
			CVector pMaxY = vecMin + x*stepX*dirx + diry;
			for(z = 1; z < numZ-1; z++)	// edge cases already handled
				if(dotY > 0.0f){
					if(DoThoroughLineTest(testPoint, pMinY + z*stepZ*dirz, entity))
						return true;
				}else{
					if(DoThoroughLineTest(testPoint, pMaxY + z*stepZ*dirz, entity))
						return true;
				}
		}

		for(y = 1; y < numY-1; y++){	// edge cases already handled
			CVector pMinX = vecMin + y*stepY*diry;
			CVector pMaxX = vecMin + y*stepY*diry + dirx;
			for(z = 1; z < numZ-1; z++)	// edge cases already handled
				if(dotX > 0.0f){
					if(DoThoroughLineTest(testPoint, pMinX + z*stepZ*dirz, entity))
						return true;
				}else{
					if(DoThoroughLineTest(testPoint, pMaxX + z*stepZ*dirz, entity))
						return true;
				}
		}
	}

	return false;
}
