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 */