summaryrefslogtreecommitdiffstats
path: root/src/LightingThread.h
blob: 45a18fdd9a9a9612bb393b051a9792c5947553c0 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165

// LightingThread.h

// Interfaces to the cLightingThread class representing the thread that processes requests for lighting

/*
Lighting is done on whole chunks. For each chunk to be lighted, the whole 3x3 chunk area around it is read,
then it is processed, so that the middle chunk area has valid lighting, and the lighting is copied into the ChunkMap.
Lighting is calculated in full char arrays instead of nibbles, so that accessing the arrays is fast.
Lighting is calculated in a flood-fill fashion:
1. Generate seeds from where the light spreads (full skylight / light-emitting blocks)
2. For each seed:
	- Spread the light 1 block in each of the 6 cardinal directions, if the blocktype allows
	- If the recipient block has had lower lighting value than that being spread, make it a new seed
3. Repeat step 2, until there are no more seeds
The seeds need two fast operations:
	- Check if a block at [x, y, z] is already a seed
	- Get the next seed in the row
For that reason it is stored in two arrays, one stores a bool saying a seed is in that position,
the other is an array of seed coords, encoded as a single int.
Step 2 needs two separate storages for old seeds and new seeds, so there are two actual storages for that purpose,
their content is swapped after each full step-2-cycle.

The thread has two queues of chunks that are to be lighted.
The first queue, m_Queue, is the only one that is publicly visible, chunks get queued there by external requests.
The second one, m_PostponedQueue, is for chunks that have been taken out of m_Queue and didn't have neighbors ready.
Chunks from m_PostponedQueue are moved back into m_Queue when their neighbors get valid, using the ChunkReady callback.
*/



#pragma once

#include "OSSupport/IsThread.h"
#include "ChunkDef.h"
#include "ChunkStay.h"





// fwd: "cWorld.h"
class cWorld;





class cLightingThread :
	public cIsThread
{
	typedef cIsThread super;

protected:

	friend class cReader;

	typedef std::array<BLOCKTYPE, cChunkDef::NumBlocks> BlockDataArray;
	typedef std::array<HEIGHTTYPE, cChunkDef::Width * cChunkDef::Width> HeightDataArray;

	class cLightingChunkStay :
		public cChunkStay
	{
	public:
		std::unique_ptr<cChunkCoordCallback> m_CallbackAfter;

		int m_ChunkX;
		int m_ChunkZ;

		/** The highest block in the current chunk data */
		HEIGHTTYPE m_MaxHeight;

		std::unique_ptr<BlockDataArray> m_BlockTypes;
		std::unique_ptr<HeightDataArray> m_HeightMap;

		cLightingChunkStay(cLightingThread & a_LightingThread, int a_ChunkX, int a_ChunkZ, std::unique_ptr<cChunkCoordCallback> a_CallbackAfter);

	protected:
		virtual void OnChunkAvailable(int a_ChunkX, int a_ChunkZ) override
		{
			UNUSED(a_ChunkX);
			UNUSED(a_ChunkZ);
		}
		virtual bool OnAllChunksAvailable(void) override;
		virtual void OnDisabled(void) override;

	private:
		cLightingThread & m_LightingThread;
	};

	typedef std::vector<cLightingChunkStay *> cChunkStays;
	typedef std::array<NIBBLETYPE, cChunkDef::NumBlocks / 2> BlockNibbles;

	cWorld * m_World;

	/** The mutex to protect m_Queue and m_PendingQueue */
	cCriticalSection m_CS;

	/** The ChunkStays that are waiting for load. Used for stopping the thread. */
	cChunkStays m_PendingQueue;

	/** The ChunkStays that are loaded. */
	cChunkStays m_Queue;

	cEvent m_evtItemDataLoaded;

	virtual void Execute(void) override;

	/** Lights the entire chunk. If neighbor chunks don't exist, touches them and re-queues the chunk */
	void LightChunk(cLightingChunkStay & a_Item);

	/** Uses m_HeightMap to initialize the m_SkyLight[] data; fills in seeds for the skylight */
	std::vector<size_t> PrepareSkyLight(const cLightingChunkStay & a_Item, BlockDataArray & a_SkyLight);

	/** Uses m_BlockTypes to initialize the m_BlockLight[] data; fills in seeds for the blocklight */
	std::vector<size_t> PrepareBlockLight(const cLightingChunkStay & a_Item, BlockDataArray & a_BlockLight);

	/** Calculates light in the light array specified, using stored seeds */
	void CalcLight(const cLightingChunkStay & a_Item, std::vector<size_t> a_IndicesToProcess, BlockDataArray & a_Light);

	void DiscoverLightAdjacents(std::vector<size_t> & a_IndicesToProcess, const cLightingChunkStay & a_Item, BlockDataArray & a_Light, size_t a_OriginatorIndex);

	/** Does one step in the light calculation - one seed propagation and seed recalculation */
	void UpdateLightAdjacent(std::vector<size_t> & a_IndicesToProcess, const cLightingChunkStay & a_Item, BlockDataArray & a_Light, size_t a_OriginatorIndex, size_t a_DestinationIndex);

	inline bool PropagateLightToAdjacent(const cLightingChunkStay & a_Item, BlockDataArray & a_Light, size_t a_OriginatorIndex, size_t a_DestinationIndex)
	{
		ASSERT(a_OriginatorIndex < a_Light.size());
		ASSERT(a_DestinationIndex < a_Light.size());

		auto Falloff = cBlockInfo::GetSpreadLightFalloff((*a_Item.m_BlockTypes)[a_DestinationIndex]);
		if (a_Light[a_OriginatorIndex] <= a_Light[a_DestinationIndex] + Falloff)
		{
			// We're not offering more light than the dest block already has
			return false;
		}

		a_Light[a_DestinationIndex] = a_Light[a_OriginatorIndex] - Falloff;
		return true;
	}

	/** Compresses from 1-block-per-byte (faster calc) into 2-blocks-per-byte (MC storage): */
	static void CompressLight(const BlockDataArray & a_LightArray, BlockNibbles & a_ChunkLight);

public:

	cLightingThread(void);
	~cLightingThread();

	bool Start(cWorld * a_World);

	void Stop(void);

	/** Queues the entire chunk for lighting
	Takes a 3-by-3 chunks' worth of buffered data (blocktypes and heightmap) centered around the chunk to light
	The callback, if specified, is called after the lighting has been processed. */
	void QueueChunk(int a_ChunkX, int a_ChunkZ, std::unique_ptr<cChunkCoordCallback> a_CallbackAfter);

	size_t GetQueueLength(void);

} ;