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