532 lines
12 KiB
C++
532 lines
12 KiB
C++
|
|
// LightingThread.cpp
|
|
|
|
// Implements the cLightingThread class representing the thread that processes requests for lighting
|
|
|
|
#include "Globals.h"
|
|
#include "LightingThread.h"
|
|
#include "cChunkMap.h"
|
|
#include "cWorld.h"
|
|
|
|
|
|
|
|
|
|
|
|
/// If more than this many chunks are in the queue, a warning is printed to the log
|
|
#define WARN_ON_QUEUE_SIZE 800
|
|
|
|
|
|
|
|
|
|
|
|
/// Chunk data callback that takes the chunk data and puts them into cLightingThread's m_BlockTypes[] / m_HeightMap[]:
|
|
class cReader :
|
|
public cChunkDataCallback
|
|
{
|
|
virtual void BlockTypes(const BLOCKTYPE * a_Type) override
|
|
{
|
|
// ROW is a block of 16 Blocks, one whole row is copied at a time (hopefully the compiler will optimize that)
|
|
// C++ doesn't permit copying arrays, but arrays as a part of a struct is ok :)
|
|
typedef struct {BLOCKTYPE m_Row[16]; } ROW;
|
|
ROW * InputRows = (ROW *)a_Type;
|
|
ROW * OutputRows = (ROW *)m_BlockTypes;
|
|
int InputIdx = 0;
|
|
int OutputIdx = m_ReadingChunkX + m_ReadingChunkZ * cChunkDef::Width * 3;
|
|
for (int y = 0; y < cChunkDef::Height; y++)
|
|
{
|
|
for (int z = 0; z < cChunkDef::Width; z++)
|
|
{
|
|
OutputRows[OutputIdx] = InputRows[InputIdx++];
|
|
OutputIdx += 3;
|
|
} // for z
|
|
// Skip into the next y-level in the 3x3 chunk blob; each level has cChunkDef::Width * 9 rows
|
|
// We've already walked cChunkDef::Width * 3 in the "for z" cycle, that makes cChunkDef::Width * 6 rows left to skip
|
|
OutputIdx += cChunkDef::Width * 6;
|
|
} // for y
|
|
} // BlockTypes()
|
|
|
|
|
|
virtual void HeightMap(const cChunkDef::HeightMap * a_Heightmap) override
|
|
{
|
|
typedef struct {HEIGHTTYPE m_Row[16]; } ROW;
|
|
ROW * InputRows = (ROW *)a_Heightmap;
|
|
ROW * OutputRows = (ROW *)m_HeightMap;
|
|
int InputIdx = 0;
|
|
int OutputIdx = m_ReadingChunkX + m_ReadingChunkZ * cChunkDef::Width * 3;
|
|
for (int z = 0; z < cChunkDef::Width; z++)
|
|
{
|
|
OutputRows[OutputIdx] = InputRows[InputIdx++];
|
|
OutputIdx += 3;
|
|
} // for z
|
|
}
|
|
|
|
public:
|
|
int m_ReadingChunkX; // 0, 1 or 2; x-offset of the chunk we're reading from the BlockTypes start
|
|
int m_ReadingChunkZ; // 0, 1 or 2; z-offset of the chunk we're reading from the BlockTypes start
|
|
BLOCKTYPE * m_BlockTypes; // 3x3 chunks of block types, organized as a single XZY blob of data (instead of 3x3 XZY blobs)
|
|
HEIGHTTYPE * m_HeightMap; // 3x3 chunks of height map, organized as a single XZY blob of data (instead of 3x3 XZY blobs)
|
|
} ;
|
|
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// cLightingThread:
|
|
|
|
cLightingThread::cLightingThread(void) :
|
|
super("cLightingThread"),
|
|
m_World(NULL)
|
|
{
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
cLightingThread::~cLightingThread()
|
|
{
|
|
Stop();
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool cLightingThread::Start(cWorld * a_World)
|
|
{
|
|
ASSERT(m_World == NULL); // Not started yet
|
|
m_World = a_World;
|
|
|
|
return super::Start();
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void cLightingThread::Stop(void)
|
|
{
|
|
{
|
|
cCSLock Lock(m_CS);
|
|
m_Queue.clear();
|
|
}
|
|
m_ShouldTerminate = true;
|
|
m_evtItemAdded.Set();
|
|
|
|
Wait();
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void cLightingThread::QueueChunk(int a_ChunkX, int a_ChunkZ, cChunkCoordCallback * a_CallbackAfter)
|
|
{
|
|
ASSERT(m_World != NULL); // Did you call Start() properly?
|
|
|
|
cChunkStay * ChunkStay = new cChunkStay(m_World);
|
|
ChunkStay->Add(a_ChunkX + 1, ZERO_CHUNK_Y, a_ChunkZ + 1);
|
|
ChunkStay->Add(a_ChunkX + 1, ZERO_CHUNK_Y, a_ChunkZ);
|
|
ChunkStay->Add(a_ChunkX + 1, ZERO_CHUNK_Y, a_ChunkZ - 1);
|
|
ChunkStay->Add(a_ChunkX, ZERO_CHUNK_Y, a_ChunkZ + 1);
|
|
ChunkStay->Add(a_ChunkX, ZERO_CHUNK_Y, a_ChunkZ);
|
|
ChunkStay->Add(a_ChunkX, ZERO_CHUNK_Y, a_ChunkZ - 1);
|
|
ChunkStay->Add(a_ChunkX - 1, ZERO_CHUNK_Y, a_ChunkZ + 1);
|
|
ChunkStay->Add(a_ChunkX - 1, ZERO_CHUNK_Y, a_ChunkZ);
|
|
ChunkStay->Add(a_ChunkX - 1, ZERO_CHUNK_Y, a_ChunkZ - 1);
|
|
ChunkStay->Enable();
|
|
ChunkStay->Load();
|
|
cCSLock Lock(m_CS);
|
|
m_Queue.push_back(sItem(a_ChunkX, a_ChunkZ, ChunkStay, a_CallbackAfter));
|
|
if (m_Queue.size() > WARN_ON_QUEUE_SIZE)
|
|
{
|
|
LOGINFO("Lighting thread overloaded, %d items in queue", m_Queue.size());
|
|
}
|
|
m_evtItemAdded.Set();
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void cLightingThread::WaitForQueueEmpty(void)
|
|
{
|
|
cCSLock Lock(m_CS);
|
|
while (!m_ShouldTerminate && (!m_Queue.empty() || !m_PostponedQueue.empty()))
|
|
{
|
|
cCSUnlock Unlock(Lock);
|
|
m_evtQueueEmpty.Wait();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
size_t cLightingThread::GetQueueLength(void)
|
|
{
|
|
cCSLock Lock(m_CS);
|
|
return m_Queue.size() + m_PostponedQueue.size();
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void cLightingThread::ChunkReady(int a_ChunkX, int a_ChunkZ)
|
|
{
|
|
// Check all the items in the m_PostponedQueue, if the chunk is their neighbor, move the item to m_Queue
|
|
|
|
bool NewlyAdded = false;
|
|
{
|
|
cCSLock Lock(m_CS);
|
|
for (sItems::iterator itr = m_PostponedQueue.begin(); itr != m_PostponedQueue.end(); )
|
|
{
|
|
if (
|
|
(itr->x - a_ChunkX >= -1) && (itr->x - a_ChunkX <= 1) &&
|
|
(itr->x - a_ChunkX >= -1) && (itr->x - a_ChunkX <= 1)
|
|
)
|
|
{
|
|
// It is a neighbor
|
|
m_Queue.push_back(*itr);
|
|
itr = m_PostponedQueue.erase(itr);
|
|
NewlyAdded = true;
|
|
}
|
|
else
|
|
{
|
|
++itr;
|
|
}
|
|
} // for itr - m_PostponedQueue[]
|
|
} // Lock(m_CS)
|
|
|
|
if (NewlyAdded)
|
|
{
|
|
m_evtItemAdded.Set(); // Notify the thread it has some work to do
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void cLightingThread::Execute(void)
|
|
{
|
|
while (true)
|
|
{
|
|
{
|
|
cCSLock Lock(m_CS);
|
|
if (m_Queue.size() == 0)
|
|
{
|
|
cCSUnlock Unlock(Lock);
|
|
m_evtItemAdded.Wait();
|
|
}
|
|
}
|
|
|
|
if (m_ShouldTerminate)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Process one items from the queue:
|
|
sItem Item;
|
|
{
|
|
cCSLock Lock(m_CS);
|
|
if (m_Queue.empty())
|
|
{
|
|
continue;
|
|
}
|
|
Item = m_Queue.front();
|
|
m_Queue.pop_front();
|
|
if (m_Queue.empty())
|
|
{
|
|
m_evtQueueEmpty.Set();
|
|
}
|
|
} // CSLock(m_CS)
|
|
|
|
LightChunk(Item);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void cLightingThread::LightChunk(cLightingThread::sItem & a_Item)
|
|
{
|
|
cChunkDef::BlockNibbles BlockLight, SkyLight;
|
|
|
|
if (!ReadChunks(a_Item.x, a_Item.z))
|
|
{
|
|
// Neighbors not available. Re-queue in the postponed queue
|
|
cCSLock Lock(m_CS);
|
|
m_PostponedQueue.push_back(a_Item);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
// DEBUG: torch somewhere:
|
|
m_BlockTypes[19 + 24 * cChunkDef::Width * 3 + (m_HeightMap[24 + 24 * cChunkDef::Width * 3] / 2) * BlocksPerYLayer] = E_BLOCK_TORCH;
|
|
// m_HeightMap[24 + 24 * cChunkDef::Width * 3]++;
|
|
*/
|
|
|
|
PrepareBlockLight();
|
|
CalcLight(m_BlockLight);
|
|
|
|
PrepareSkyLight();
|
|
CalcLight(m_SkyLight);
|
|
|
|
CompressLight(m_BlockLight, BlockLight);
|
|
CompressLight(m_SkyLight, SkyLight);
|
|
|
|
/*
|
|
// DEBUG:
|
|
{
|
|
cFile f("chunk_BlockTypes.dat", cFile::fmWrite);
|
|
if (f.IsOpen())
|
|
{
|
|
f.Write(m_BlockTypes, sizeof(m_BlockTypes));
|
|
}
|
|
}
|
|
|
|
// DEBUG:
|
|
{
|
|
cFile f("Chunk_SkyLight.dat", cFile::fmWrite);
|
|
if (f.IsOpen())
|
|
{
|
|
f.Write(m_SkyLight, sizeof(m_SkyLight));
|
|
}
|
|
}
|
|
|
|
// DEBUG:
|
|
{
|
|
cFile f("Chunk_BlockLight.dat", cFile::fmWrite);
|
|
if (f.IsOpen())
|
|
{
|
|
f.Write(m_BlockLight, sizeof(m_BlockLight));
|
|
}
|
|
}
|
|
*/
|
|
|
|
m_World->ChunkLighted(a_Item.x, a_Item.z, BlockLight, SkyLight);
|
|
|
|
if (a_Item.m_Callback != NULL)
|
|
{
|
|
a_Item.m_Callback->Call(a_Item.x, a_Item.z);
|
|
}
|
|
delete a_Item.m_ChunkStay;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool cLightingThread::ReadChunks(int a_ChunkX, int a_ChunkZ)
|
|
{
|
|
cReader Reader;
|
|
Reader.m_BlockTypes = m_BlockTypes;
|
|
Reader.m_HeightMap = m_HeightMap;
|
|
|
|
for (int z = 0; z < 3; z++)
|
|
{
|
|
Reader.m_ReadingChunkZ = z;
|
|
for (int x = 0; x < 3; x++)
|
|
{
|
|
Reader.m_ReadingChunkX = x;
|
|
if (!m_World->GetChunkData(a_ChunkX + x - 1, ZERO_CHUNK_Y, a_ChunkZ + z - 1, Reader))
|
|
{
|
|
return false;
|
|
}
|
|
} // for z
|
|
} // for x
|
|
|
|
memset(m_BlockLight, 0, sizeof(m_BlockLight));
|
|
memset(m_SkyLight, 0, sizeof(m_SkyLight));
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void cLightingThread::PrepareSkyLight(void)
|
|
{
|
|
// Clear seeds:
|
|
memset(m_IsSeed1, 0, sizeof(m_IsSeed1));
|
|
m_NumSeeds = 0;
|
|
|
|
// Walk every column that has all XZ neighbors
|
|
for (int z = 1; z < cChunkDef::Width * 3 - 1; z++)
|
|
{
|
|
int BaseZ = z * cChunkDef::Width * 3;
|
|
for (int x = 1; x < cChunkDef::Width * 3 - 1; x++)
|
|
{
|
|
int idx = BaseZ + x;
|
|
int Current = m_HeightMap[idx] + 1;
|
|
int Neighbor1 = m_HeightMap[idx + 1] + 1; // X + 1
|
|
int Neighbor2 = m_HeightMap[idx - 1] + 1; // X - 1
|
|
int Neighbor3 = m_HeightMap[idx + cChunkDef::Width * 3] + 1; // Z + 1
|
|
int Neighbor4 = m_HeightMap[idx - cChunkDef::Width * 3] + 1; // Z - 1
|
|
int MaxNeighbor = MAX(MAX(Neighbor1, Neighbor2), MAX(Neighbor3, Neighbor4)); // Maximum of the four neighbors
|
|
|
|
// TODO: The following cycle can be transofrmed into two separate cycles with no condition inside them, one lighting and the other seeding
|
|
for (int y = Current, Index = idx + y * BlocksPerYLayer; y < cChunkDef::Height; y++, Index += BlocksPerYLayer)
|
|
{
|
|
// If all the XZ neighbors are lower than y, abort for the current column (but light up the rest of it):
|
|
if (y >= MaxNeighbor)
|
|
{
|
|
for (int y2 = y; y2 < cChunkDef::Height; y2++, Index += BlocksPerYLayer)
|
|
{
|
|
m_SkyLight[Index] = 15;
|
|
} // for y2
|
|
break; // for y
|
|
}
|
|
|
|
// Add current block as a seed:
|
|
m_IsSeed1[Index] = true;
|
|
m_SeedIdx1[m_NumSeeds++] = Index;
|
|
|
|
// Light it up to full skylight:
|
|
m_SkyLight[Index] = 15;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void cLightingThread::PrepareBlockLight(void)
|
|
{
|
|
// Clear seeds:
|
|
memset(m_IsSeed1, 0, sizeof(m_IsSeed1));
|
|
m_NumSeeds = 0;
|
|
|
|
// Walk every column that has all XZ neighbors, make a seed for each light-emitting block:
|
|
for (int z = 1; z < cChunkDef::Width * 3 - 1; z++)
|
|
{
|
|
int BaseZ = z * cChunkDef::Width * 3;
|
|
for (int x = 1; x < cChunkDef::Width * 3 - 1; x++)
|
|
{
|
|
int idx = BaseZ + x;
|
|
for (int y = m_HeightMap[idx], Index = idx + y * BlocksPerYLayer; y >= 0; y--, Index -= BlocksPerYLayer)
|
|
{
|
|
if (g_BlockLightValue[m_BlockTypes[Index]] == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Add current block as a seed:
|
|
m_IsSeed1[Index] = true;
|
|
m_SeedIdx1[m_NumSeeds++] = Index;
|
|
|
|
// Light it up:
|
|
m_BlockLight[Index] = g_BlockLightValue[m_BlockTypes[Index]];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void cLightingThread::CalcLight(NIBBLETYPE * a_Light)
|
|
{
|
|
int NumSeeds2 = 0;
|
|
while (m_NumSeeds > 0)
|
|
{
|
|
// Buffer 1 -> buffer 2
|
|
memset(m_IsSeed2, 0, sizeof(m_IsSeed2));
|
|
NumSeeds2 = 0;
|
|
CalcLightStep(a_Light, m_NumSeeds, m_IsSeed1, m_SeedIdx1, NumSeeds2, m_IsSeed2, m_SeedIdx2);
|
|
if (NumSeeds2 == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Buffer 2 -> buffer 1
|
|
memset(m_IsSeed1, 0, sizeof(m_IsSeed1));
|
|
m_NumSeeds = 0;
|
|
CalcLightStep(a_Light, NumSeeds2, m_IsSeed2, m_SeedIdx2, m_NumSeeds, m_IsSeed1, m_SeedIdx1);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void cLightingThread::CalcLightStep(
|
|
NIBBLETYPE * a_Light,
|
|
int a_NumSeedsIn, unsigned char * a_IsSeedIn, unsigned int * a_SeedIdxIn,
|
|
int & a_NumSeedsOut, unsigned char * a_IsSeedOut, unsigned int * a_SeedIdxOut
|
|
)
|
|
{
|
|
int NumSeedsOut = 0;
|
|
for (int i = 0; i < a_NumSeedsIn; i++)
|
|
{
|
|
int SeedIdx = a_SeedIdxIn[i];
|
|
int SeedX = SeedIdx % (cChunkDef::Width * 3);
|
|
int SeedZ = (SeedIdx / (cChunkDef::Width * 3)) % (cChunkDef::Width * 3);
|
|
int SeedY = SeedIdx / BlocksPerYLayer;
|
|
|
|
// Propagate seed:
|
|
if (SeedX < cChunkDef::Width * 3)
|
|
{
|
|
PropagateLight(a_Light, SeedIdx, SeedIdx + 1, NumSeedsOut, a_IsSeedOut, a_SeedIdxOut);
|
|
}
|
|
if (SeedX > 0)
|
|
{
|
|
PropagateLight(a_Light, SeedIdx, SeedIdx - 1, NumSeedsOut, a_IsSeedOut, a_SeedIdxOut);
|
|
}
|
|
if (SeedZ < cChunkDef::Width * 3)
|
|
{
|
|
PropagateLight(a_Light, SeedIdx, SeedIdx + cChunkDef::Width * 3, NumSeedsOut, a_IsSeedOut, a_SeedIdxOut);
|
|
}
|
|
if (SeedZ > 0)
|
|
{
|
|
PropagateLight(a_Light, SeedIdx, SeedIdx - cChunkDef::Width * 3, NumSeedsOut, a_IsSeedOut, a_SeedIdxOut);
|
|
}
|
|
if (SeedY < cChunkDef::Height)
|
|
{
|
|
PropagateLight(a_Light, SeedIdx, SeedIdx + cChunkDef::Width * cChunkDef::Width * 3 * 3, NumSeedsOut, a_IsSeedOut, a_SeedIdxOut);
|
|
}
|
|
if (SeedY > 0)
|
|
{
|
|
PropagateLight(a_Light, SeedIdx, SeedIdx - cChunkDef::Width * cChunkDef::Width * 3 * 3, NumSeedsOut, a_IsSeedOut, a_SeedIdxOut);
|
|
}
|
|
} // for i - a_SeedIdxIn[]
|
|
a_NumSeedsOut = NumSeedsOut;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void cLightingThread::CompressLight(NIBBLETYPE * a_LightArray, NIBBLETYPE * a_ChunkLight)
|
|
{
|
|
int InIdx = cChunkDef::Width * 49; // Index to the first nibble of the middle chunk in the a_LightArray
|
|
int OutIdx = 0;
|
|
for (int y = 0; y < cChunkDef::Height; y++)
|
|
{
|
|
for (int z = 0; z < cChunkDef::Width; z++)
|
|
{
|
|
for (int x = 0; x < cChunkDef::Width; x += 2)
|
|
{
|
|
a_ChunkLight[OutIdx++] = (a_LightArray[InIdx + 1] << 4) | a_LightArray[InIdx];
|
|
InIdx += 2;
|
|
}
|
|
InIdx += cChunkDef::Width * 2;
|
|
}
|
|
// Skip into the next y-level in the 3x3 chunk blob; each level has cChunkDef::Width * 9 rows
|
|
// We've already walked cChunkDef::Width * 3 in the "for z" cycle, that makes cChunkDef::Width * 6 rows left to skip
|
|
InIdx += cChunkDef::Width * cChunkDef::Width * 6;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|