1 /**
2 Copyright: Copyright (c) 2013-2016 Andrey Penechko.
3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
4 Authors: Andrey Penechko.
5 */
6 module voxelman.core.meshgen;
7 
8 import std.experimental.logger;
9 import std.conv : to;
10 import core.exception : Throwable;
11 
12 import voxelman.container.buffer;
13 import voxelman.math;
14 
15 import voxelman.block.plugin;
16 import voxelman.blockentity.plugin;
17 
18 import voxelman.block.utils;
19 import voxelman.core.chunkmesh;
20 import voxelman.core.config;
21 import voxelman.utils.worker;
22 import voxelman.world.storage.chunk;
23 import voxelman.world.storage.coordinates;
24 
25 
26 enum MeshGenTaskType : ubyte
27 {
28 	genMesh,
29 	unloadMesh
30 }
31 
32 struct MeshGenTaskHeader
33 {
34 	MeshGenTaskType type;
35 	size_t meshGroupId;
36 	ChunkWorldPos cwp;
37 }
38 
39 //version = DBG_OUT;
40 void meshWorkerThread(shared(Worker)* workerInfo, BlockInfoTable blockInfos, BlockEntityInfoTable beInfos)
41 {
42 	try
43 	{
44 		while (workerInfo.needsToRun)
45 		{
46 			workerInfo.waitForNotify();
47 
48 			// receive
49 			//   MeshGenTaskHeader taskHeader;
50 			//   ChunkLayerItem[7] blockLayers;
51 			//   ChunkLayerItem[7] entityLayers;
52 			// or
53 			//   MeshGenTaskHeader taskHeader;
54 			//
55 			// send
56 			//   MeshGenTaskHeader taskHeader;
57 			//   MeshVertex[][2] meshes;
58 			//   uint[7] blockTimestamps;
59 			//   uint[7] entityTimestamps;
60 			// or
61 			//   MeshGenTaskHeader taskHeader;
62 			if (!workerInfo.taskQueue.empty)
63 			{
64 				auto taskHeader = workerInfo.taskQueue.popItem!MeshGenTaskHeader();
65 
66 				if (taskHeader.type == MeshGenTaskType.genMesh)
67 				{
68 					// mesh task.
69 					ChunkLayerItem[7] blockLayers = workerInfo.taskQueue.popItem!(ChunkLayerItem[7])();
70 					ChunkLayerItem[7] entityLayers = workerInfo.taskQueue.popItem!(ChunkLayerItem[7])();
71 
72 					MeshVertex[][2] meshes = chunkMeshWorker(blockLayers, entityLayers, blockInfos, beInfos);
73 
74 					uint[7] blockTimestamps;
75 					uint[7] entityTimestamps;
76 					foreach(i; 0..7) blockTimestamps[i] = blockLayers[i].timestamp;
77 					foreach(i; 0..7) entityTimestamps[i] = entityLayers[i].timestamp;
78 
79 					workerInfo.resultQueue.startMessage();
80 					workerInfo.resultQueue.pushMessagePart(taskHeader);
81 					workerInfo.resultQueue.pushMessagePart(meshes);
82 					workerInfo.resultQueue.pushMessagePart(blockTimestamps);
83 					workerInfo.resultQueue.pushMessagePart(entityTimestamps);
84 					workerInfo.resultQueue.endMessage();
85 				}
86 				else
87 				{
88 					// remove mesh task. Resend it to main thread.
89 					workerInfo.resultQueue.pushItem(taskHeader);
90 				}
91 			}
92 		}
93 	}
94 	catch(Throwable t)
95 	{
96 		infof("%s from mesh worker", t.to!string);
97 		throw t;
98 	}
99 	version(DBG_OUT)infof("Mesh worker stopped");
100 }
101 
102 MeshVertex[][2] chunkMeshWorker(ChunkLayerItem[7] blockLayers,
103 	ChunkLayerItem[7] entityLayers, BlockInfoTable blockInfos, BlockEntityInfoTable beInfos)
104 {
105 	Buffer!MeshVertex[3] geometry; // 2 - solid, 1 - semiTransparent
106 
107 	foreach (layer; blockLayers)
108 		assert(layer.type != StorageType.compressedArray, "[MESHING] Data needs to be uncompressed");
109 
110 	BlockEntityMap[7] maps;
111 	foreach (i, layer; entityLayers) maps[i] = getHashMapFromLayer(layer);
112 
113 	BlockEntityData getBlockEntity(ushort blockIndex, BlockEntityMap map) {
114 		ulong* entity = blockIndex in map;
115 		if (entity is null) return BlockEntityData.init;
116 		return BlockEntityData(*entity);
117 	}
118 
119 	Solidity solidity(int tx, int ty, int tz, Side side)
120 	{
121 		ChunkAndBlockAt chAndBlock = chunkAndBlockAt(tx, ty, tz);
122 		BlockId blockId = blockLayers[chAndBlock.chunk].getBlockId(
123 			chAndBlock.blockX, chAndBlock.blockY, chAndBlock.blockZ);
124 
125 		if (isBlockEntity(blockId)) {
126 			ushort entityBlockIndex = blockIndexFromBlockId(blockId);
127 			BlockEntityData data = getBlockEntity(entityBlockIndex, maps[chAndBlock.chunk]);
128 			auto entityInfo = beInfos[data.id];
129 
130 			auto entityChunkPos = BlockChunkPos(entityBlockIndex);
131 
132 			ivec3 blockChunkPos = ivec3(chAndBlock.blockX, chAndBlock.blockY, chAndBlock.blockZ);
133 			ivec3 blockEntityPos = blockChunkPos - entityChunkPos.vector;
134 
135 			return entityInfo.sideSolidity(side, blockChunkPos, blockEntityPos, data);
136 		} else {
137 			return blockInfos[blockId].solidity;
138 		}
139 	}
140 
141 	ubyte checkSideSolidities(Solidity curSolidity, ubyte bx, ubyte by, ubyte bz)
142 	{
143 		ubyte sides = 0;
144 		ubyte flag = 1;
145 		foreach(ubyte side; 0..6) {
146 			byte[3] offset = sideOffsets[side]; // Offset to adjacent block
147 			if(curSolidity > solidity(bx+offset[0], by+offset[1], bz+offset[2], oppSide[side])) {
148 				sides |= flag;
149 			}
150 			flag <<= 1;
151 		}
152 		return sides;
153 	}
154 
155 	void meshBlock(BlockId blockId, ushort blockIndex, Solidity curSolidity)
156 	{
157 		ubyte bx = blockIndex & CHUNK_SIZE_BITS;
158 		ubyte by = (blockIndex / CHUNK_SIZE_SQR) & CHUNK_SIZE_BITS;
159 		ubyte bz = (blockIndex / CHUNK_SIZE) & CHUNK_SIZE_BITS;
160 
161 		// Bit flags of sides to render
162 		ubyte sides = checkSideSolidities(curSolidity, bx, by, bz);
163 
164 		if (isBlockEntity(blockId))
165 		{
166 			ushort entityBlockIndex = blockIndexFromBlockId(blockId);
167 			BlockEntityData data = getBlockEntity(entityBlockIndex, maps[6]);
168 
169 			// entity chunk pos
170 			auto entityChunkPos = BlockChunkPos(entityBlockIndex);
171 
172 			//ivec3 worldPos;
173 			ivec3 blockChunkPos = ivec3(bx, by, bz);
174 			ivec3 blockEntityPos = blockChunkPos - entityChunkPos.vector;
175 
176 			auto entityInfo = beInfos[data.id];
177 
178 			entityInfo.meshHandler(
179 				geometry[],
180 				data,
181 				entityInfo.color,
182 				sides,
183 				//worldPos,
184 				blockChunkPos,
185 				blockEntityPos);
186 		}
187 		else
188 		{
189 			blockInfos[blockId].meshHandler(geometry[curSolidity], blockInfos[blockId].color, bx, by, bz, sides);
190 		}
191 	}
192 
193 	if (blockLayers[6].isUniform)
194 	{
195 		BlockId blockId = blockLayers[6].getUniform!BlockId;
196 		Meshhandler meshHandler = blockInfos[blockId].meshHandler;
197 		ubyte[3] color = blockInfos[blockId].color;
198 		Solidity curSolidity = blockInfos[blockId].solidity;
199 
200 		if (curSolidity != Solidity.transparent)
201 		{
202 			foreach (ushort index; 0..CHUNK_SIZE_CUBE)
203 			{
204 				meshBlock(blockId, index, curSolidity);
205 			}
206 		}
207 	}
208 	else
209 	{
210 		auto blocks = blockLayers[6].getArray!BlockId();
211 		assert(blocks.length == CHUNK_SIZE_CUBE);
212 		foreach (ushort index, BlockId blockId; blocks)
213 		{
214 			if (blockInfos[blockId].isVisible)
215 			{
216 				auto curSolidity = blockInfos[blockId].solidity;
217 				if (curSolidity == Solidity.transparent)
218 					continue;
219 
220 				meshBlock(blockId, index, curSolidity);
221 			}
222 		}
223 	}
224 
225 	MeshVertex[][2] meshes;
226 	meshes[0] = geometry[2].data; // solid geometry
227 	meshes[1] = geometry[1].data; // semi-transparent geometry
228 
229 	// Add root to data.
230 	// Data can be collected by GC if no-one is referencing it.
231 	// It is needed to pass array trough shared queue.
232 	// Root is removed inside ChunkMeshMan
233 	import core.memory : GC;
234 	if (meshes[0]) GC.addRoot(meshes[0].ptr); // TODO remove when moved to non-GC allocator
235 	if (meshes[1]) GC.addRoot(meshes[1].ptr); //
236 
237 	return meshes;
238 }