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 ];