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.block.utils;
7 
8 import std.experimental.logger;
9 
10 import voxelman.container.buffer;
11 import voxelman.math;
12 import voxelman.core.config;
13 import voxelman.world.storage.coordinates;
14 import voxelman.world.storage.chunk;
15 import voxelman.utils.mapping;
16 import voxelman.core.chunkmesh;
17 import voxelman.geometry.cube;
18 
19 
20 enum SideMask : ubyte
21 {
22 	zneg = 0b_00_0001,
23 	zpos = 0b_00_0010,
24 
25 	xpos = 0b_00_0100,
26 	xneg = 0b_00_1000,
27 
28 	ypos = 0b_01_0000,
29 	yneg = 0b_10_0000,
30 }
31 
32 enum MetadataSideMask : ushort
33 {
34 	zneg = 0b_11,
35 	zpos = 0b_11_00,
36 
37 	xpos = 0b_11_00_00,
38 	xneg = 0b_11_00_00_00,
39 
40 	ypos = 0b_11_00_00_00_00,
41 	yneg = 0b_11_00_00_00_00_00,
42 }
43 
44 struct ChunkAndBlockAt
45 {
46 	ubyte chunk;
47 	ubyte blockX, blockY, blockZ;
48 }
49 
50 // 0-5 sides, or 6 if center
51 ChunkAndBlockAt chunkAndBlockAt(int x, int y, int z)
52 {
53 	ubyte bx = cast(ubyte)x;
54 	ubyte by = cast(ubyte)y;
55 	ubyte bz = cast(ubyte)z;
56 	if(x == -1) return ChunkAndBlockAt(CubeSide.xneg, CHUNK_SIZE-1, by, bz);
57 	else if(x == CHUNK_SIZE) return ChunkAndBlockAt(CubeSide.xpos, 0, by, bz);
58 
59 	if(y == -1) return ChunkAndBlockAt(CubeSide.yneg, bx, CHUNK_SIZE-1, bz);
60 	else if(y == CHUNK_SIZE) return ChunkAndBlockAt(CubeSide.ypos, bx, 0, bz);
61 
62 	if(z == -1) return ChunkAndBlockAt(CubeSide.zneg, bx, by, CHUNK_SIZE-1);
63 	else if(z == CHUNK_SIZE) return ChunkAndBlockAt(CubeSide.zpos, bx, by, 0);
64 
65 	return ChunkAndBlockAt(6, bx, by, bz);
66 }
67 
68 CubeSide sideFromNormal(ivec3 normal)
69 {
70 	if (normal.x == 1)
71 		return CubeSide.xpos;
72 	else if (normal.x == -1)
73 		return CubeSide.xneg;
74 
75 	if (normal.y == 1)
76 		return CubeSide.ypos;
77 	else if (normal.y == -1)
78 		return CubeSide.yneg;
79 
80 	if (normal.z == 1)
81 		return CubeSide.zpos;
82 	else if (normal.z == -1)
83 		return CubeSide.zneg;
84 
85 	return CubeSide.zneg;
86 }
87 
88 void makeNullMesh(ref Buffer!MeshVertex, ubyte[3], ubyte, ubyte, ubyte, ubyte) {}
89 
90 void makeColoredBlockMesh(ref Buffer!MeshVertex output,
91 	ubyte[3] color, ubyte bx, ubyte by, ubyte bz, ubyte sides)
92 {
93 	import std.random;
94 	static immutable(float)[] shadowMultipliers = [
95 		0.7, 0.75, 0.6, 0.5, 0.85, 0.4,
96 	];
97 
98 	auto index = BlockChunkIndex(bx, by, bz).index;
99 	auto rnd = Xorshift32(index);
100 	float randomTint = uniform(0.90f, 1.0f, rnd);
101 
102 	float r = cast(ubyte)(color[0] * randomTint);
103 	float g = cast(ubyte)(color[1] * randomTint);
104 	float b = cast(ubyte)(color[2] * randomTint);
105 	ubyte[3] finalColor;
106 
107 	ubyte flag = 1;
108 	foreach(ubyte i; 0..6)
109 	{
110 		if (sides & flag)
111 		{
112 			finalColor = [
113 				cast(ubyte)(shadowMultipliers[i] * r),
114 				cast(ubyte)(shadowMultipliers[i] * g),
115 				cast(ubyte)(shadowMultipliers[i] * b)];
116 			for (size_t v = 0; v!=18; v+=3)
117 			{
118 				output.put(MeshVertex(
119 					cubeFaces[18*i+v  ] + bx,
120 					cubeFaces[18*i+v+1] + by,
121 					cubeFaces[18*i+v+2] + bz,
122 					finalColor));
123 			} // for v
124 		} // if
125 		flag <<= 1;
126 	} // for i
127 }
128 
129 ushort packColor(ubyte[3] c) {
130 	return (c[0]>>3) | (c[1]&31) << 5 | (c[2]&31) << 10;
131 }
132 ushort packColor(ubyte r, ubyte g, ubyte b) {
133 	return (r>>3) | (g&31) << 5 | (b&31) << 10;
134 }
135 
136 alias BlockUpdateHandler = void delegate(BlockWorldPos bwp);
137 alias Meshhandler = void function(ref Buffer!MeshVertex output,
138 	ubyte[3] color, ubyte bx, ubyte by, ubyte bz, ubyte sides);
139 
140 // solidity number increases with solidity
141 enum Solidity : ubyte
142 {
143 	transparent,
144 	semiTransparent,
145 	solid,
146 }
147 
148 struct BlockInfo
149 {
150 	string name;
151 	Meshhandler meshHandler = &makeNullMesh;
152 	ubyte[3] color;
153 	bool isVisible = true;
154 	Solidity solidity = Solidity.solid;
155 	//bool isSolid() @property const { return solidity == Solidity.solid; }
156 	size_t id;
157 }
158 
159 BlockInfo entityBlock = BlockInfo("Entity", &makeColoredBlockMesh);
160 struct BlockInfoTable
161 {
162 	immutable(BlockInfo)[] blockInfos;
163 	size_t length() {return blockInfos.length; }
164 	BlockInfo opIndex(BlockId blockId) {
165 		if (blockId >= blockInfos.length)
166 			return entityBlock;
167 		return blockInfos[blockId];
168 	}
169 }
170 
171 /// Returned when registering block.
172 /// Use this to set block properties.
173 struct BlockInfoSetter
174 {
175 	private Mapping!(BlockInfo)* mapping;
176 	private size_t blockId;
177 	private ref BlockInfo info() {return (*mapping)[blockId]; }
178 
179 	ref BlockInfoSetter meshHandler(Meshhandler val) { info.meshHandler = val; return this; }
180 	ref BlockInfoSetter color(ubyte[3] color ...) { info.color = color; return this; }
181 	ref BlockInfoSetter colorHex(uint hex) { info.color = [(hex>>16)&0xFF,(hex>>8)&0xFF,hex&0xFF]; return this; }
182 	ref BlockInfoSetter isVisible(bool val) { info.isVisible = val; return this; }
183 	ref BlockInfoSetter solidity(Solidity val) { info.solidity = val; return this; }
184 }
185 
186 void regBaseBlocks(BlockInfoSetter delegate(string name) regBlock)
187 {
188 	regBlock("unknown").color(0,0,0).isVisible(false).solidity(Solidity.solid).meshHandler(&makeNullMesh);
189 	regBlock("air").color(0,0,0).isVisible(false).solidity(Solidity.transparent).meshHandler(&makeNullMesh);
190 	regBlock("grass").colorHex(0x7EEE11).meshHandler(&makeColoredBlockMesh);
191 	regBlock("dirt").colorHex(0x835929).meshHandler(&makeColoredBlockMesh);
192 	regBlock("stone").colorHex(0x8B8D7A).meshHandler(&makeColoredBlockMesh);
193 	regBlock("sand").colorHex(0xA68117).meshHandler(&makeColoredBlockMesh);
194 	regBlock("water").colorHex(0x0055AA).meshHandler(&makeColoredBlockMesh).solidity(Solidity.semiTransparent);
195 }
196 
197 // Chunk metadata
198 // 00 1 22_22_22_22_22_22
199 // 00 - 2 bits representing chunk's minimal solidity
200 // 1 - 1 bit representing if metadata is presented
201 // 2 - 12 bits -- solidity of each side
202 
203 // Works only with uncompressed data.
204 ushort calcChunkFullMetadata(Layer)(const ref Layer blockLayer, BlockInfoTable blockInfos)
205 {
206 	if (blockLayer.type == StorageType.uniform) {
207 		return calcChunkFullMetadata(blockLayer.getUniform!BlockId, blockInfos);
208 	} else if (blockLayer.type == StorageType.fullArray) {
209 		return calcChunkFullMetadata(blockLayer.getArray!BlockId, blockInfos);
210 	} else assert(false);
211 }
212 
213 ushort calcChunkFullMetadata(BlockId[] blocks, BlockInfoTable blockInfos)
214 {
215 	ushort sideMeta = calcChunkSideMetadata(blocks, blockInfos);
216 	ushort solidityBits = calcSolidityBits(blocks, blockInfos);
217 	return cast(ushort) (sideMeta | solidityBits<<CHUNK_SIDE_METADATA_BITS);
218 }
219 
220 ushort calcChunkFullMetadata(BlockId uniformBlock, BlockInfoTable blockInfos)
221 {
222 	ushort sideMeta = calcChunkSideMetadata(uniformBlock, blockInfos);
223 	ushort solidityBits = calcSolidityBits(uniformBlock, blockInfos);
224 	return cast(ushort) (sideMeta | solidityBits<<CHUNK_SIDE_METADATA_BITS);
225 }
226 
227 // ditto
228 ushort calcChunkSideMetadata(Layer)(Layer blockLayer, BlockInfoTable blockInfos)
229 {
230 	if (blockLayer.isUniform) return calcChunkSideMetadata(blockLayer.getUniform!BlockId, blockInfos);
231 	else return calcChunkSideMetadata(blockLayer.getArray!BlockId, blockInfos);
232 }
233 
234 // ditto
235 ushort calcChunkSideMetadata(Layer)(Layer blockLayer, BlockInfoTable blockInfos)
236 	if (isSomeLayer!Layer)
237 {
238 	if (blockLayer.type == StorageType.uniform) {
239 		return calcChunkSideMetadata(blockLayer.getUniform!BlockId, blockInfos);
240 	} else if (blockLayer.type == StorageType.fullArray) {
241 		BlockId[] blocks = blockLayer.getArray!BlockId;
242 		return calcChunkSideMetadata(blocks, blockInfos);
243 	} else assert(false);
244 }
245 
246 // ditto
247 ubyte calcSolidityBits(Layer)(Layer blockLayer, BlockInfoTable blockInfos)
248 	if (isSomeLayer!Layer)
249 {
250 	if (blockLayer.type == StorageType.uniform) {
251 		return calcSolidityBits(blockLayer.getUniform!BlockId, blockInfos);
252 	} else if (blockLayer.type == StorageType.fullArray) {
253 		BlockId[] blocks = blockLayer.getArray!BlockId;
254 		return calcSolidityBits(blocks, blockInfos);
255 	} else assert(false);
256 }
257 
258 ubyte calcSolidityBits(BlockId uniformBlock, BlockInfoTable blockInfos)
259 {
260 	Solidity solidity = blockInfos[uniformBlock].solidity;
261 	enum ubyte[3] bits = [0b001, 0b010, 0b100];
262 	return bits[solidity];
263 }
264 
265 ubyte calcSolidityBits(BlockId[] blocks, BlockInfoTable blockInfos)
266 {
267 	bool[3] presentSolidities;
268 	foreach(i; 0..CHUNK_SIZE_CUBE) {
269 		Solidity solidity = blockInfos[blocks[i]].solidity;
270 		presentSolidities[solidity] = true;
271 	}
272 	ubyte solidityBits;
273 	ubyte solidityFlag = 1;
274 	foreach(sol; presentSolidities) {
275 		if (sol) solidityBits |= solidityFlag;
276 		solidityFlag <<= 1;
277 	}
278 	return solidityBits;
279 }
280 
281 bool isChunkSideSolid(const ushort metadata, const CubeSide side)
282 {
283 	return chunkSideSolidity(metadata, side) == Solidity.solid;
284 }
285 
286 Solidity chunkSideSolidity(const ushort metadata, const CubeSide side)
287 {
288 	if (metadata & 0b1_00_00_00_00_00_00) // if metadata is presented
289 		return cast(Solidity)((metadata>>(side*2)) & 0b11);
290 	else
291 		return Solidity.transparent; // otherwise non-solid
292 }
293 
294 /// Returns true if chunk has blocks of specified solidity.
295 /// If metadata is invalid then chunk is assumed to have blocks of every solidity.
296 bool hasSolidity(const ushort metadata, Solidity solidity)
297 {
298 	ubyte solidityBits;
299 	if (metadata & 0b1_00_00_00_00_00_00) {// if metadata is valid
300 		solidityBits = (metadata>>CHUNK_SIDE_METADATA_BITS) & 0b111;
301 	} else {
302 		solidityBits = 0b111; // assume every solidity.
303 	}
304 	return (solidityBits & (1 << solidity)) > 0;
305 }
306 
307 /// Returns true if chunk has blocks only of specified solidity.
308 /// If metadata is invalid then chunk is assumed to have blocks of every solidity, and returns false.
309 bool hasOnlySolidity(const ushort metadata, Solidity solidity)
310 {
311 	if (metadata & 0b1_00_00_00_00_00_00) {// if metadata is valid
312 		ubyte solidityBits = (metadata>>CHUNK_SIDE_METADATA_BITS) & 0b111;
313 		return solidityBits == (1 << solidity);
314 	} else {
315 		return false; // assume has every solidity.
316 	}
317 }
318 
319 bool[8] singleSolidityTable = [false, true, true, false, true, false, false, false];
320 Solidity[8] bitsToSolidityTable = [Solidity.transparent, Solidity.transparent, Solidity.semiTransparent,
321 	Solidity.transparent, Solidity.solid, Solidity.transparent, Solidity.transparent, Solidity.transparent];
322 
323 /// Returns true if chunk has blocks of only single solidity.
324 /// If returns true then solidity has solidity of all blocks.
325 bool hasSingleSolidity(const ushort metadata, out Solidity solidity)
326 {
327 	if (metadata & 0b1_00_00_00_00_00_00) {// if metadata is valid
328 		ubyte solidityBits = (metadata>>CHUNK_SIDE_METADATA_BITS) & 0b111;
329 		solidity = bitsToSolidityTable[solidityBits];
330 		return singleSolidityTable[solidityBits];
331 	} else {
332 		return false; // assume has every solidity.
333 	}
334 }
335 
336 bool isMoreSolidThan(Solidity first, Solidity second)
337 {
338 	return first > second;
339 }
340 
341 void printChunkMetadata(ushort metadata)
342 {
343 	if (metadata & 0b1_00_00_00_00_00_00) {// if metadata is valid
344 		char[6] sideSolidityChars;
345 		char[3] letters = "TMS";
346 		foreach(side; 0..6) {
347 			Solidity sideSolidity = cast(Solidity)((metadata>>(side*2)) & 0b11);
348 			sideSolidityChars[side] = letters[sideSolidity];
349 		}
350 		ubyte solidityBits = (metadata>>CHUNK_SIDE_METADATA_BITS) & 0b111;
351 		char trans = solidityBits & 1 ? 'T' : ' ';
352 		char semi = solidityBits & 0b10 ? 'M' : ' ';
353 		char solid = solidityBits & 0b100 ? 'S' : ' ';
354 		Solidity singleSolidity;
355 		bool single = hasSingleSolidity(metadata, singleSolidity);
356 		infof("meta [%s%s%s] (%s) {%s, %s}", trans, semi, solid, sideSolidityChars, single, singleSolidity);
357 	} else {
358 		infof("non-valid metadata");
359 	}
360 }
361 
362 ushort calcChunkSideMetadata(BlockId uniformBlock, BlockInfoTable blockInfos)
363 {
364 	Solidity solidity = blockInfos[uniformBlock].solidity;
365 	// 13th bit == 1 when metadata is present, 12 bits = solidity of 6 chunk sides. 2 bits per side
366 	static immutable ushort[3] metadatas = [0b1_00_00_00_00_00_00, 0b1_01_01_01_01_01_01, 0b1_10_10_10_10_10_10];
367 	return metadatas[solidity];
368 }
369 
370 enum CHUNK_SIDE_METADATA_BITS = 13;
371 
372 ushort calcChunkSideMetadata(BlockId[] blocks, BlockInfoTable blockInfos)
373 {
374 	ushort flags = 0b1_00_00_00_00_00_00; // all sides are solid
375 	Solidity sideSolidity = Solidity.solid;
376 	foreach(index; 0..CHUNK_SIZE_SQR) // yneg
377 	{
378 		if (sideSolidity > blockInfos[blocks[index]].solidity)
379 		{
380 			sideSolidity -= 1;
381 			if (sideSolidity == Solidity.transparent)
382 				break;
383 		}
384 	}
385 	flags = cast(ushort)(flags | (sideSolidity << (CubeSide.yneg*2)));
386 
387 	sideSolidity = Solidity.solid;
388 	outer_zneg:
389 	foreach(y; 0..CHUNK_SIZE)
390 	foreach(x; 0..CHUNK_SIZE)
391 	{
392 		size_t index = y*CHUNK_SIZE_SQR | x; // zneg
393 		if (sideSolidity > blockInfos[blocks[index]].solidity)
394 		{
395 			sideSolidity -= 1;
396 			if (sideSolidity == Solidity.transparent)
397 				break outer_zneg;
398 		}
399 	}
400 	flags = cast(ushort)(flags | (sideSolidity << (CubeSide.zneg*2)));
401 
402 	sideSolidity = Solidity.solid;
403 	outer_zpos:
404 	foreach(y; 0..CHUNK_SIZE)
405 	foreach(x; 0..CHUNK_SIZE)
406 	{
407 		size_t index = (CHUNK_SIZE-1) * CHUNK_SIZE | y*CHUNK_SIZE_SQR | x; // zpos
408 		if (sideSolidity > blockInfos[blocks[index]].solidity)
409 		{
410 			sideSolidity -= 1;
411 			if (sideSolidity == Solidity.transparent)
412 				break outer_zpos;
413 		}
414 	}
415 	flags = cast(ushort)(flags | (sideSolidity << (CubeSide.zpos*2)));
416 
417 	sideSolidity = Solidity.solid;
418 	outer_xpos:
419 	foreach(y; 0..CHUNK_SIZE)
420 	foreach(z; 0..CHUNK_SIZE)
421 	{
422 		size_t index = z * CHUNK_SIZE | y*CHUNK_SIZE_SQR | (CHUNK_SIZE-1); // xpos
423 		if (sideSolidity > blockInfos[blocks[index]].solidity)
424 		{
425 			sideSolidity -= 1;
426 			if (sideSolidity == Solidity.transparent)
427 				break outer_xpos;
428 		}
429 	}
430 	flags = cast(ushort)(flags | (sideSolidity << (CubeSide.xpos*2)));
431 
432 	sideSolidity = Solidity.solid;
433 	outer_xneg:
434 	foreach(y; 0..CHUNK_SIZE)
435 	foreach(z; 0..CHUNK_SIZE)
436 	{
437 		size_t index = z * CHUNK_SIZE | y*CHUNK_SIZE_SQR; // xneg
438 		if (sideSolidity > blockInfos[blocks[index]].solidity)
439 		{
440 			sideSolidity -= 1;
441 			if (sideSolidity == Solidity.transparent)
442 				break outer_xneg;
443 		}
444 	}
445 	flags = cast(ushort)(flags | (sideSolidity << (CubeSide.xneg*2)));
446 
447 	sideSolidity = Solidity.solid;
448 	foreach(index; CHUNK_SIZE_CUBE-CHUNK_SIZE_SQR..CHUNK_SIZE_CUBE) // ypos
449 	{
450 		if (sideSolidity > blockInfos[blocks[index]].solidity)
451 		{
452 			sideSolidity -= 1;
453 			if (sideSolidity == Solidity.transparent)
454 				break;
455 		}
456 	}
457 	flags = cast(ushort)(flags | (sideSolidity << (CubeSide.ypos*2)));
458 
459 	return flags;
460 }
461 
462 
463 /*
464 void iterateSides()
465 {
466 	foreach(index; 0..CHUNK_SIZE_SQR) // yneg
467 
468 	{// zneg
469 		ubyte z = 0;
470 		foreach(y; 0..CHUNK_SIZE)
471 			foreach(x; 0..CHUNK_SIZE)
472 				index = z * CHUNK_SIZE | y*CHUNK_SIZE_SQR | x;
473 	}
474 
475 	{// zpos
476 		ubyte z = CHUNK_SIZE-1;
477 		foreach(y; 0..CHUNK_SIZE)
478 			foreach(x; 0..CHUNK_SIZE)
479 				index = z * CHUNK_SIZE | y*CHUNK_SIZE_SQR | x;
480 	}
481 
482 	{// xpos
483 		ubyte x = CHUNK_SIZE-1;
484 		foreach(y; 0..CHUNK_SIZE)
485 			foreach(z; 0..CHUNK_SIZE)
486 				index = z * CHUNK_SIZE | y*CHUNK_SIZE_SQR | x;
487 	}
488 
489 	{// xneg
490 		ubyte x = 0;
491 		foreach(y; 0..CHUNK_SIZE)
492 			foreach(z; 0..CHUNK_SIZE)
493 				index = z * CHUNK_SIZE | y*CHUNK_SIZE_SQR | x;
494 	}
495 
496 	foreach(index; CHUNK_SIZE_CUBE-CHUNK_SIZE_SQR..CHUNK_SIZE_CUBE) // ypos
497 }
498 */