1
0
cuberite-2a/src/LightingThread.cpp
Alexander Harkness b5a23e5cd9 Lighting now generally consistent with vanilla (#3988)
* Lighting now generally consistent with vanilla

Skylight is now dispersed by some blocks, instead of passing
through unimpeded.

Some blocks which were not marked as transparent are now marked as
such.

Water and other such blocks now attenuate light with the correct
intensity.

Generally changes were based on documentation in the Minecraft
Wiki: https://minecraft.gamepedia.com/Opacity#On_block_light ,
however during play-testing on vanilla lava was found not to
attenuate sky or block-light so the attenuation was removed.

This fixes #3849

* Add API documentation for IsSkylightDispersant

* Rename m_SkylightDispersant to m_IsSkylightDispersant

* Update comment for m_Transparent property of Blocks
2017-09-07 11:56:17 +01:00

608 lines
15 KiB
C++

// LightingThread.cpp
// Implements the cLightingThread class representing the thread that processes requests for lighting
#include "Globals.h"
#include "LightingThread.h"
#include "ChunkMap.h"
#include "World.h"
/** Chunk data callback that takes the chunk data and puts them into cLightingThread's m_BlockTypes[] / m_HeightMap[]: */
class cReader :
public cChunkDataCallback
{
virtual void ChunkData(const cChunkData & a_ChunkBuffer) override
{
BLOCKTYPE * OutputRows = m_BlockTypes;
int InputIdx = 0;
int OutputIdx = m_ReadingChunkX + m_ReadingChunkZ * cChunkDef::Width * 3;
int MaxHeight = std::min(+cChunkDef::Height, m_MaxHeight + 16); // Need 16 blocks above the highest
for (int y = 0; y < MaxHeight; y++)
{
for (int z = 0; z < cChunkDef::Width; z++)
{
a_ChunkBuffer.CopyBlockTypes(OutputRows + OutputIdx * 16, static_cast<size_t>(InputIdx * 16), 16);
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
{
// Copy the entire heightmap, distribute it into the 3x3 chunk blob:
typedef struct {HEIGHTTYPE m_Row[16]; } ROW;
const ROW * InputRows = reinterpret_cast<const ROW *>(a_Heightmap);
ROW * OutputRows = reinterpret_cast<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
// Find the highest block in the entire chunk, use it as a base for m_MaxHeight:
HEIGHTTYPE MaxHeight = m_MaxHeight;
for (size_t i = 0; i < ARRAYCOUNT(*a_Heightmap); i++)
{
if ((*a_Heightmap)[i] > MaxHeight)
{
MaxHeight = (*a_Heightmap)[i];
}
}
m_MaxHeight = MaxHeight;
}
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
HEIGHTTYPE m_MaxHeight; // Maximum value in this chunk's heightmap
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)
cReader(BLOCKTYPE * a_BlockTypes, HEIGHTTYPE * a_HeightMap) :
m_ReadingChunkX(0),
m_ReadingChunkZ(0),
m_MaxHeight(0),
m_BlockTypes(a_BlockTypes),
m_HeightMap(a_HeightMap)
{
}
} ;
////////////////////////////////////////////////////////////////////////////////
// cLightingThread:
cLightingThread::cLightingThread(void) :
super("cLightingThread"),
m_World(nullptr),
m_MaxHeight(0),
m_NumSeeds(0)
{
}
cLightingThread::~cLightingThread()
{
Stop();
}
bool cLightingThread::Start(cWorld * a_World)
{
ASSERT(m_World == nullptr); // Not started yet
m_World = a_World;
return super::Start();
}
void cLightingThread::Stop(void)
{
{
cCSLock Lock(m_CS);
for (cChunkStays::iterator itr = m_PendingQueue.begin(), end = m_PendingQueue.end(); itr != end; ++itr)
{
(*itr)->Disable();
delete *itr;
}
m_PendingQueue.clear();
for (cChunkStays::iterator itr = m_Queue.begin(), end = m_Queue.end(); itr != end; ++itr)
{
(*itr)->Disable();
delete *itr;
}
m_Queue.clear();
}
m_ShouldTerminate = true;
m_evtItemAdded.Set();
Wait();
}
void cLightingThread::QueueChunk(int a_ChunkX, int a_ChunkZ, std::unique_ptr<cChunkCoordCallback> a_CallbackAfter)
{
ASSERT(m_World != nullptr); // Did you call Start() properly?
cChunkStay * ChunkStay = new cLightingChunkStay(*this, a_ChunkX, a_ChunkZ, std::move(a_CallbackAfter));
{
// The ChunkStay will enqueue itself using the QueueChunkStay() once it is fully loaded
// In the meantime, put it into the PendingQueue so that it can be removed when stopping the thread
cCSLock Lock(m_CS);
m_PendingQueue.push_back(ChunkStay);
}
ChunkStay->Enable(*m_World->GetChunkMap());
}
void cLightingThread::WaitForQueueEmpty(void)
{
cCSLock Lock(m_CS);
while (!m_ShouldTerminate && (!m_Queue.empty() || !m_PendingQueue.empty()))
{
cCSUnlock Unlock(Lock);
m_evtQueueEmpty.Wait();
}
}
size_t cLightingThread::GetQueueLength(void)
{
cCSLock Lock(m_CS);
return m_Queue.size() + m_PendingQueue.size();
}
void cLightingThread::Execute(void)
{
for (;;)
{
{
cCSLock Lock(m_CS);
if (m_Queue.empty())
{
cCSUnlock Unlock(Lock);
m_evtItemAdded.Wait();
}
}
if (m_ShouldTerminate)
{
return;
}
// Process one items from the queue:
cLightingChunkStay * Item;
{
cCSLock Lock(m_CS);
if (m_Queue.empty())
{
continue;
}
Item = static_cast<cLightingChunkStay *>(m_Queue.front());
m_Queue.pop_front();
if (m_Queue.empty())
{
m_evtQueueEmpty.Set();
}
} // CSLock(m_CS)
LightChunk(*Item);
Item->Disable();
delete Item;
}
}
void cLightingThread::LightChunk(cLightingChunkStay & a_Item)
{
// If the chunk is already lit, skip it (report as success):
if (m_World->IsChunkLighted(a_Item.m_ChunkX, a_Item.m_ChunkZ))
{
if (a_Item.m_CallbackAfter != nullptr)
{
a_Item.m_CallbackAfter->Call(a_Item.m_ChunkX, a_Item.m_ChunkZ, true);
}
return;
}
cChunkDef::BlockNibbles BlockLight, SkyLight;
ReadChunks(a_Item.m_ChunkX, a_Item.m_ChunkZ);
PrepareBlockLight();
CalcLight(m_BlockLight);
PrepareSkyLight();
/*
// DEBUG: Save chunk data with highlighted seeds for visual inspection:
cFile f4;
if (
f4.Open(Printf("Chunk_%d_%d_seeds.grab", a_Item.m_ChunkX, a_Item.m_ChunkZ), cFile::fmWrite)
)
{
for (int z = 0; z < cChunkDef::Width * 3; z++)
{
for (int y = cChunkDef::Height / 2; y >= 0; y--)
{
unsigned char Seeds [cChunkDef::Width * 3];
memcpy(Seeds, m_BlockTypes + y * BlocksPerYLayer + z * cChunkDef::Width * 3, cChunkDef::Width * 3);
for (int x = 0; x < cChunkDef::Width * 3; x++)
{
if (m_IsSeed1[y * BlocksPerYLayer + z * cChunkDef::Width * 3 + x])
{
Seeds[x] = E_BLOCK_DIAMOND_BLOCK;
}
}
f4.Write(Seeds, cChunkDef::Width * 3);
}
}
f4.Close();
}
//*/
CalcLight(m_SkyLight);
/*
// DEBUG: Save XY slices of the chunk data and lighting for visual inspection:
cFile f1, f2, f3;
if (
f1.Open(Printf("Chunk_%d_%d_data.grab", a_Item.m_ChunkX, a_Item.m_ChunkZ), cFile::fmWrite) &&
f2.Open(Printf("Chunk_%d_%d_sky.grab", a_Item.m_ChunkX, a_Item.m_ChunkZ), cFile::fmWrite) &&
f3.Open(Printf("Chunk_%d_%d_glow.grab", a_Item.m_ChunkX, a_Item.m_ChunkZ), cFile::fmWrite)
)
{
for (int z = 0; z < cChunkDef::Width * 3; z++)
{
for (int y = cChunkDef::Height / 2; y >= 0; y--)
{
f1.Write(m_BlockTypes + y * BlocksPerYLayer + z * cChunkDef::Width * 3, cChunkDef::Width * 3);
unsigned char SkyLight [cChunkDef::Width * 3];
unsigned char BlockLight[cChunkDef::Width * 3];
for (int x = 0; x < cChunkDef::Width * 3; x++)
{
SkyLight[x] = m_SkyLight [y * BlocksPerYLayer + z * cChunkDef::Width * 3 + x] << 4;
BlockLight[x] = m_BlockLight[y * BlocksPerYLayer + z * cChunkDef::Width * 3 + x] << 4;
}
f2.Write(SkyLight, cChunkDef::Width * 3);
f3.Write(BlockLight, cChunkDef::Width * 3);
}
}
f1.Close();
f2.Close();
f3.Close();
}
//*/
CompressLight(m_BlockLight, BlockLight);
CompressLight(m_SkyLight, SkyLight);
m_World->ChunkLighted(a_Item.m_ChunkX, a_Item.m_ChunkZ, BlockLight, SkyLight);
if (a_Item.m_CallbackAfter != nullptr)
{
a_Item.m_CallbackAfter->Call(a_Item.m_ChunkX, a_Item.m_ChunkZ, true);
}
}
void cLightingThread::ReadChunks(int a_ChunkX, int a_ChunkZ)
{
cReader Reader(m_BlockTypes, m_HeightMap);
for (int z = 0; z < 3; z++)
{
Reader.m_ReadingChunkZ = z;
for (int x = 0; x < 3; x++)
{
Reader.m_ReadingChunkX = x;
VERIFY(m_World->GetChunkData(a_ChunkX + x - 1, a_ChunkZ + z - 1, Reader));
} // for z
} // for x
memset(m_BlockLight, 0, sizeof(m_BlockLight));
memset(m_SkyLight, 0, sizeof(m_SkyLight));
m_MaxHeight = Reader.m_MaxHeight;
}
void cLightingThread::PrepareSkyLight(void)
{
// Clear seeds:
memset(m_IsSeed1, 0, sizeof(m_IsSeed1));
m_NumSeeds = 0;
// Fill the top of the chunk with all-light:
if (m_MaxHeight < cChunkDef::Height - 1)
{
std::fill(m_SkyLight + (m_MaxHeight + 1) * BlocksPerYLayer, m_SkyLight + ARRAYCOUNT(m_SkyLight), 15);
}
// 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;
// Find the lowest block in this column that receives full sunlight (go through transparent blocks):
int Current = m_HeightMap[idx];
ASSERT(Current < cChunkDef::Height);
while (
(Current >= 0) &&
cBlockInfo::IsTransparent(m_BlockTypes[idx + Current * BlocksPerYLayer]) &&
!cBlockInfo::IsSkylightDispersant(m_BlockTypes[idx + Current * BlocksPerYLayer])
)
{
Current -= 1; // Sunlight goes down unchanged through this block
}
Current += 1; // Point to the last sunlit block, rather than the first non-transparent one
// The other neighbors don't need transparent-block-checking. At worst we'll have a few dud seeds above the ground.
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 = std::max(std::max(Neighbor1, Neighbor2), std::max(Neighbor3, Neighbor4)); // Maximum of the four neighbors
// Fill the column from m_MaxHeight to Current with all-light:
for (int y = m_MaxHeight, Index = idx + y * BlocksPerYLayer; y >= Current; y--, Index -= BlocksPerYLayer)
{
m_SkyLight[Index] = 15;
}
// Add Current as a seed:
if (Current < cChunkDef::Height)
{
int CurrentIdx = idx + Current * BlocksPerYLayer;
m_IsSeed1[CurrentIdx] = true;
m_SeedIdx1[m_NumSeeds++] = static_cast<UInt32>(CurrentIdx);
}
// Add seed from Current up to the highest neighbor:
for (int y = Current + 1, Index = idx + y * BlocksPerYLayer; y < MaxNeighbor; y++, Index += BlocksPerYLayer)
{
m_IsSeed1[Index] = true;
m_SeedIdx1[m_NumSeeds++] = static_cast<UInt32>(Index);
}
}
}
}
void cLightingThread::PrepareBlockLight()
{
// Clear seeds:
memset(m_IsSeed1, 0, sizeof(m_IsSeed1));
memset(m_IsSeed2, 0, sizeof(m_IsSeed2));
m_NumSeeds = 0;
// Add each emissive block into the seeds:
for (int Idx = 0; Idx < (m_MaxHeight * BlocksPerYLayer); ++Idx)
{
if (cBlockInfo::GetLightValue(m_BlockTypes[Idx]) == 0)
{
// Not a light-emissive block
continue;
}
// Add current block as a seed:
m_IsSeed1[Idx] = true;
m_SeedIdx1[m_NumSeeds++] = static_cast<UInt32>(Idx);
// Light it up:
m_BlockLight[Idx] = cBlockInfo::GetLightValue(m_BlockTypes[Idx]);
}
}
void cLightingThread::CalcLight(NIBBLETYPE * a_Light)
{
size_t 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,
size_t a_NumSeedsIn, unsigned char * a_IsSeedIn, unsigned int * a_SeedIdxIn,
size_t & a_NumSeedsOut, unsigned char * a_IsSeedOut, unsigned int * a_SeedIdxOut
)
{
UNUSED(a_IsSeedIn);
size_t NumSeedsOut = 0;
for (size_t i = 0; i < a_NumSeedsIn; i++)
{
UInt32 SeedIdx = static_cast<UInt32>(a_SeedIdxIn[i]);
int SeedX = SeedIdx % (cChunkDef::Width * 3);
int SeedZ = (SeedIdx / (cChunkDef::Width * 3)) % (cChunkDef::Width * 3);
// Propagate seed:
if (SeedX < cChunkDef::Width * 3 - 1)
{
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 - 1)
{
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 (SeedIdx < (cChunkDef::Height - 1) * BlocksPerYLayer)
{
PropagateLight(a_Light, SeedIdx, SeedIdx + BlocksPerYLayer, NumSeedsOut, a_IsSeedOut, a_SeedIdxOut);
}
if (SeedIdx >= BlocksPerYLayer)
{
PropagateLight(a_Light, SeedIdx, SeedIdx - BlocksPerYLayer, 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++] = static_cast<NIBBLETYPE>(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;
}
}
void cLightingThread::QueueChunkStay(cLightingChunkStay & a_ChunkStay)
{
// Move the ChunkStay from the Pending queue to the lighting queue.
{
cCSLock Lock(m_CS);
m_PendingQueue.remove(&a_ChunkStay);
m_Queue.push_back(&a_ChunkStay);
}
m_evtItemAdded.Set();
}
////////////////////////////////////////////////////////////////////////////////
// cLightingThread::cLightingChunkStay:
cLightingThread::cLightingChunkStay::cLightingChunkStay(cLightingThread & a_LightingThread, int a_ChunkX, int a_ChunkZ, std::unique_ptr<cChunkCoordCallback> a_CallbackAfter) :
m_LightingThread(a_LightingThread),
m_ChunkX(a_ChunkX),
m_ChunkZ(a_ChunkZ),
m_CallbackAfter(std::move(a_CallbackAfter))
{
Add(a_ChunkX + 1, a_ChunkZ + 1);
Add(a_ChunkX + 1, a_ChunkZ);
Add(a_ChunkX + 1, a_ChunkZ - 1);
Add(a_ChunkX, a_ChunkZ + 1);
Add(a_ChunkX, a_ChunkZ);
Add(a_ChunkX, a_ChunkZ - 1);
Add(a_ChunkX - 1, a_ChunkZ + 1);
Add(a_ChunkX - 1, a_ChunkZ);
Add(a_ChunkX - 1, a_ChunkZ - 1);
}
bool cLightingThread::cLightingChunkStay::OnAllChunksAvailable(void)
{
m_LightingThread.QueueChunkStay(*this);
// Keep the ChunkStay alive:
return false;
}
void cLightingThread::cLightingChunkStay::OnDisabled(void)
{
// Nothing needed in this callback
}