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