 92c59963f8
			
		
	
	
		92c59963f8
		
	
	
	
	
		
			
			git-svn-id: http://mc-server.googlecode.com/svn/trunk@606 0a769ca7-a7f5-676a-18bf-c427514a06d6
		
			
				
	
	
		
			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;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 |