#include "stdafx.h"

WorldManager3D World3D;


void MTUnloadArea3D(Area3D &area, Ptr user, int thread){ area.Unload(); }
void MTLoadArea3D(Area3D &area, Ptr user, int thread){ area.Build(); }
WorldManager3D::WorldManager3D(){ changed = false; Active_areas.del(); Cached_areas.del(); Unloading_areas.del();  m_grid.del(); pw.del(); }
WorldManager3D::~WorldManager3D(){  thread_x.del(); Active_areas.del(); Cached_areas.del(); Unloading_areas.del();  m_grid.del(); pw.del(); }
void WorldManager3D::del(){ thread_x.del(); Active_areas.del(); Cached_areas.del(); Unloading_areas.del();  m_grid.del(); pw.del(); }
void WorldManager3D::Create(){
	gr.load("Data/Grass.gfx"); grNrm.load("Data/GrassNrm.gfx");
	dr.load("Data/Dirt.gfx"); drNrm.load("Data/DirtNrm.gfx");
	matA.base_0 = &gr; matA.base_1 = &grNrm; matA.cull = false;
	matB.color.set(BROWN.asVec(), 1); matB.cull = false;
	matC.base_0 = &dr; matC.base_1 = &drNrm; matC.cull = false;
	m_areasize = 64;
	ps.areaSize(AreaSize());
	pw.create(AreaSize());
	actRng = (D.viewRange()/2) / AreaSize();
}


void  WorldManager3D::ActiveRange(int _ActiveRange){
	actRng = _ActiveRange / AreaSize();
	CheckAreas();
}

void WorldManager3D::CheckAreas(){
	for (int x = CurPos.x - actRng - 1; x <= (CurPos.x + actRng + 1); x++){
		for (int y = CurPos.y - actRng - 1; y <= (CurPos.y + actRng + 1); y++){
			if (Area3D * area = m_grid.get(VecI2(x, y))->data()){
				if (Dist2(area->pos.xz(), CurPos) - 1 <= Sqr(actRng) + 5){
					if (area->state == AREA_UNLOADED){
						if (Cached_areas.include(area)){ changed = true; }
					}
				}
			}
		}
	}
}




/******************************************************************************
Thread functions must be of "bool Name(Thread &thread)" format.
They return true when want to continue thread processing, and false when want to stop them.
As input parameter thread functions receive reference to the 'Thread' which calls them.
/******************************************************************************/
bool ThreadFuncUpdateArea3D(Thread &thread_caller)
{
	if (thread_caller.wantToStop())return false;
	if (!World3D.changed)World3D.CheckAreas();
	if (World3D.changed){
		REPA(World3D.Active_areas){
			if (thread_caller.wantToStop())return false;
			C VecI2 pos = World3D.Active_areas[i]->pos.xz();
			if (Dist2(pos, World3D.CurPos) - 1 > Sqr(World3D.ActiveRange())){
				World3D.Cached_areas.include(World3D.Active_areas[i]);
				World3D.Active_areas.exclude(World3D.Active_areas[i]);
			}
		}
		MultiThreadedCall<Area3D>(World3D.Cached_areas, MTLoadArea3D, NULL,Cpu.threads() -1);
		REPA(World3D.Cached_areas){
			if (thread_caller.wantToStop())return false;
			C VecI2 pos = World3D.Cached_areas[i]->pos.xz();
			if (Dist2(pos, World3D.CurPos) - 1 <= Sqr(World3D.ActiveRange())){
				World3D.Active_areas.include(World3D.Cached_areas[i]);
				World3D.Cached_areas.exclude(World3D.Cached_areas[i]);
				
			}
			if (Dist2(pos, World3D.CurPos) - 1 > Sqr(World3D.ActiveRange()) + 5){
				World3D.Cached_areas[i]->RemovePhysics();
				World3D.Unloading_areas.include(World3D.Cached_areas[i]);
				World3D.Cached_areas.exclude(World3D.Cached_areas[i]);
			}
		}
		MultiThreadedCall<Area3D>(World3D.Unloading_areas, MTUnloadArea3D, NULL, Cpu.threads() - 1);
		REPA(World3D.Unloading_areas){
			if (thread_caller.wantToStop())return false;
			C VecI2 pos = World3D.Unloading_areas[i]->pos.xz();
			World3D.Unloading_areas.exclude(World3D.Unloading_areas[i]);
			World3D.m_grid.del(World3D.m_grid.find(pos));
		}
		
		
		REPA(World3D.Active_areas){
			if (thread_caller.wantToStop())return false;
			World3D.Active_areas[i]->CreatePhysics();
		}
		World3D.changed = false;
	}
	return true; // continue processing
}

void WorldManager3D::update(C Vec2 &at){
	CurPos.set(at.x / AreaSize(), at.y / AreaSize());
	if (CurPos.x<0)CurPos.x -= 1;
	if (CurPos.y<0)CurPos.y -= 1;
	Physics.startSimulation();
	Physics.stopSimulation();
	
	if (!thread_x.active()){
		thread_x.create(ThreadFuncUpdateArea3D, 0, 0); // thread_x  will use 'ThreadFunc' 
	}
	if (Time.appTime() > m_last_update){
		m_last_update = Time.appTime() + 0.33;
		REPAO(Active_areas)->update();
	}
}



Area3D *WorldManager3D::GetArea(C VecI &v){ if (Cell<Area3D> *area = m_grid.find(v.xz())){ return area->data(); }return nullptr; }


void WorldManager3D::Draw(){
	Flt lod_s = D.lodFactorShadow(); D.lodFactorShadow(1);
	REPAO(Active_areas)->Draw(); // draw mesh
	REPAO(Cached_areas)->Draw(); // draw mesh
	D.lodFactorShadow(lod_s);
	D.text(0, 0.8, S + "Active Areas: " + Active_areas.elms());
	D.text(0, 0.7, S + "Cached Areas: " + Cached_areas.elms());
	D.text(0, 0.6, S + "Unloading Areas: " + Unloading_areas.elms());
}


void WorldManager3D::Load(C Str &world){}