1 /** 2 Copyright: Copyright (c) 2016-2018 Andrey Penechko. 3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 4 Authors: Andrey Penechko. 5 */ 6 module railroad.rail.utils; 7 8 import voxelman.math; 9 import voxelman.geometry; 10 import voxelman.world.block; 11 import voxelman.world.blockentity.blockentitydata; 12 import voxelman.world.blockentity.blockentityaccess; 13 import voxelman.world.blockentity.utils; 14 import voxelman.world.storage; 15 import voxelman.world.mesh.utils : FaceSide, oppFaceSides; 16 17 enum RAIL_TILE_SIZE = 8; 18 immutable ivec3 railSizeVector = ivec3(RAIL_TILE_SIZE, 1, RAIL_TILE_SIZE); 19 immutable ivec4 railPickOffset = ivec4(RAIL_TILE_SIZE/2, 0, RAIL_TILE_SIZE/2, 0); 20 21 ivec3 railTilePos(ivec3 bwp) { 22 return ivec3(floor(cast(float)bwp.x / RAIL_TILE_SIZE) * RAIL_TILE_SIZE, 23 bwp.y, 24 floor(cast(float)bwp.z / RAIL_TILE_SIZE) * RAIL_TILE_SIZE); 25 } 26 27 ivec3 calcBlockTilePos(ivec3 bwp) 28 { 29 ivec3 tilePos = railTilePos(bwp); 30 return bwp - tilePos; 31 } 32 33 RailData getRailAt(RailPos railPos, ushort railEntityId, 34 WorldAccess worldAccess, BlockEntityAccess entityAccess) 35 { 36 auto bwp = railPos.toBlockWorldPos; 37 bwp.vector += railPickOffset; 38 auto blockId = worldAccess.getBlock(bwp); 39 40 if (isBlockEntity(blockId)) 41 { 42 ushort blockIndex = blockEntityIndexFromBlockId(blockId); 43 BlockEntityData entity = entityAccess.getBlockEntity(railPos.chunkPos, blockIndex); 44 45 if (entity.id == railEntityId) 46 return RailData(entity); 47 } 48 return RailData(); 49 } 50 51 enum RailEditOp 52 { 53 add, 54 remove 55 } 56 57 struct RailPos { 58 this(svec4 vec) { 59 vector = vec; 60 } 61 this(BlockWorldPos bwp) 62 { 63 vector = svec4( 64 floor(cast(float)bwp.x / RAIL_TILE_SIZE), 65 cast(float)bwp.y, 66 floor(cast(float)bwp.z / RAIL_TILE_SIZE), 67 bwp.w); 68 } 69 ChunkWorldPos chunkPos() const { 70 return ChunkWorldPos(toBlockWorldPos()); 71 } 72 BlockWorldPos toBlockWorldPos() const { 73 return BlockWorldPos( 74 vector.x * RAIL_TILE_SIZE, 75 vector.y, 76 vector.z * RAIL_TILE_SIZE, 77 vector.w); 78 } 79 WorldBox toBlockBox() const 80 { 81 return WorldBox(toBlockWorldPos().xyz, railSizeVector, vector.w); 82 } 83 BlockWorldPos deletePos() const 84 { 85 auto bwp = toBlockWorldPos; 86 bwp.vector += railPickOffset; 87 return bwp; 88 } 89 svec4 vector; 90 alias vector this; 91 92 ulong asUlong() const @property 93 { 94 ulong res = cast(ulong)vector.w<<48 | 95 cast(ulong)(cast(ushort)vector.z)<<32 | 96 cast(ulong)(cast(ushort)vector.y)<<16 | 97 cast(ulong)(cast(ushort)vector.x); 98 return res; 99 } 100 101 RailPos posInDirection(FaceSide direction) 102 { 103 byte[3] offset = sideToOffset[direction]; 104 return RailPos(svec4( 105 vector.x+offset[0], 106 vector.y+offset[1], 107 vector.z+offset[2], 108 vector.w)); 109 } 110 } 111 112 immutable byte[3][4] sideToOffset = [ 113 [ 0, 0,-1], 114 [-1, 0, 0], 115 [ 0, 0, 1], 116 [ 1, 0, 0]]; 117 118 struct RailData 119 { 120 ubyte data; 121 122 this(ubyte eData) { 123 data = eData; 124 } 125 126 this(RailSegment segment) { 127 data = railSegmentData[segment]; 128 } 129 130 this(BlockEntityData beData) { 131 data = cast(ubyte)(beData.entityData); 132 } 133 134 bool isSlope() const { 135 return (data & SLOPE_RAIL_BIT) != 0; 136 } 137 138 bool hasSingleSegment() const { 139 if ((data & SLOPE_RAIL_BIT) != 0) 140 { 141 return 1; 142 } 143 else 144 { 145 import core.bitop : popcnt; 146 return popcnt(data) == 1; 147 } 148 } 149 150 bool empty() const { 151 return data == 0; 152 } 153 154 void addRail(RailData newRail) 155 { 156 if (newRail.isSlope || isSlope) 157 { 158 data = newRail.data; 159 } 160 else 161 { 162 data |= newRail.data; 163 } 164 } 165 166 void removeRail(RailData newRail) 167 { 168 if (data == newRail.data) // slope or multiple same rails 169 { 170 data = 0; 171 } 172 else 173 { 174 data &= ~cast(int)(newRail.data); 175 } 176 } 177 178 void editRail(RailData railData, RailEditOp editOp) 179 { 180 final switch(editOp) 181 { 182 case RailEditOp.add: addRail(railData); break; // combine rails 183 case RailEditOp.remove: removeRail(railData); break; // remove rails 184 } 185 } 186 187 SegmentRange getSegments() const 188 { 189 return SegmentRange(data); 190 } 191 192 // returns segments that connect to the side 193 SegmentBuffer getSegmentsFromSide(FaceSide side) 194 { 195 SegmentBuffer result; 196 foreach(RailSegment segment; getSegments) 197 { 198 if (segmentInfos[segment].sideConnections[side]) 199 result.put(segment); 200 } 201 return result; 202 } 203 204 // returns segments that connect to the side 205 // and create smooth curve with adjacent segment 206 // adjacent segment is guaranteed to be connected to the side 207 SegmentBuffer getSegmentsFromSideSmooth(FaceSide side, RailSegment adjacent) 208 { 209 assert(segmentInfos[adjacent].sideConnections[oppFaceSides[side]], 210 "Adjacent segment must be connected to the side"); 211 SegmentBuffer result; 212 foreach(RailSegment segment; getSegments) 213 { 214 if (segmentInfos[segment].sideConnections[side]) 215 { 216 // TODO 217 result.put(segment); 218 } 219 } 220 return result; 221 } 222 223 Solidity bottomSolidity(ivec3 blockTilePos) const 224 { 225 foreach(segment; getSegments) 226 { 227 if (isSegmentSolid(segment, blockTilePos)) 228 return Solidity.solid; 229 } 230 231 return Solidity.transparent; 232 } 233 234 WorldBox boundingBox(RailPos railPos) const 235 { 236 return boundingBox(railPos.toBlockWorldPos()); 237 } 238 239 WorldBox boundingBox(BlockWorldPos bwp) const 240 { 241 if (isSlope) 242 { 243 auto segment = data - SLOPE_RAIL_BIT + RailSegment.znegUp; 244 ivec3 tilePos = railTilePos(bwp.xyz); 245 ivec3 railPos = tilePos + railSegmentOffsets[segment]; 246 ivec3 railSize = railSegmentSizes[segment]; 247 return WorldBox(railPos, railSize, cast(ushort)(bwp.w)); 248 } 249 else 250 { 251 ivec3 tilePos = railTilePos(bwp.xyz); 252 253 Box commonBox; 254 ubyte flag = 1; 255 foreach(segment; 0..6) 256 { 257 if (flag & data) 258 { 259 ivec3 segmentPos = railSegmentOffsets[segment]; 260 ivec3 segmentSize = railSegmentSizes[segment]; 261 Box box = Box(segmentPos, segmentSize); 262 if (commonBox.empty) 263 commonBox = box; 264 else 265 commonBox = calcCommonBox(commonBox, box); 266 } 267 268 flag <<= 1; 269 } 270 commonBox.position += tilePos; 271 272 return WorldBox(commonBox, cast(ushort)(bwp.w)); 273 } 274 } 275 } 276 277 import voxelman.container.fixedbuffer; 278 alias SegmentBuffer = FixedBuffer!(RailSegment, 3); 279 280 struct SegmentRange 281 { 282 this(ubyte _data) 283 { 284 data = _data; 285 } 286 287 private ubyte data; 288 289 int opApply(scope int delegate(size_t, RailSegment) del) 290 { 291 size_t index; 292 int proxyDel(RailSegment segment) { return del(index++, segment); } 293 return opApply(&proxyDel); 294 } 295 296 int opApply(scope int delegate(RailSegment) del) 297 { 298 if ((data & SLOPE_RAIL_BIT) != 0) 299 { 300 ubyte segment = cast(ubyte)(data - SLOPE_RAIL_BIT + RailSegment.znegUp); 301 if (auto ret = del(cast(RailSegment)segment)) 302 return ret; 303 } 304 else if (data != 0) 305 { 306 import core.bitop : bsf; 307 308 ubyte segment = cast(ubyte)bsf(data); 309 ubyte flag = cast(ubyte)(1 << segment); 310 311 while(segment <= RailSegment.xposZneg) 312 { 313 if (flag & data) 314 if (auto ret = del(cast(RailSegment)segment)) 315 return ret; 316 flag <<= 1; 317 ++segment; 318 } 319 } 320 return 0; 321 } 322 } 323 324 enum RailSegment 325 { 326 zneg, 327 xpos, 328 329 xnegZneg, 330 xnegZpos, 331 xposZpos, 332 xposZneg, 333 334 znegUp, 335 xnegUp, 336 zposUp, 337 xposUp, 338 339 //znegDown = zposUp, 340 //zposDown = znegUp, 341 //xposDown = xnegUp, 342 //xnegDown = xposUp, 343 } 344 345 enum SEGMENT_LENGTH_STRAIGHT = 8; 346 enum SEGMENT_LENGTH_DIAGONAL = 4*SQRT_2; 347 enum SEGMENT_LENGTH_SLOPE = sqrt(8.0*8.0 + 1*1); 348 349 float[] segmentLengths = [ 350 SEGMENT_LENGTH_STRAIGHT, 351 SEGMENT_LENGTH_STRAIGHT, 352 353 SEGMENT_LENGTH_DIAGONAL, 354 SEGMENT_LENGTH_DIAGONAL, 355 SEGMENT_LENGTH_DIAGONAL, 356 SEGMENT_LENGTH_DIAGONAL, 357 358 SEGMENT_LENGTH_SLOPE, 359 SEGMENT_LENGTH_SLOPE, 360 SEGMENT_LENGTH_SLOPE, 361 SEGMENT_LENGTH_SLOPE, 362 ]; 363 364 enum SLOPE_RAIL_BIT = 0b0100_0000; 365 ubyte[] railSegmentData = 366 [ 367 1, 368 2, 369 370 4, 371 8, 372 16, 373 32, 374 375 SLOPE_RAIL_BIT + 0, 376 SLOPE_RAIL_BIT + 1, 377 SLOPE_RAIL_BIT + 2, 378 SLOPE_RAIL_BIT + 3]; 379 380 // slopeUpToSide[data & 0b11] 381 CubeSide[4] slopeUpToSide = [ 382 CubeSide.zneg, 383 CubeSide.xneg, 384 CubeSide.zpos, 385 CubeSide.xpos]; 386 387 bool isSlopeUpSideBlock(RailData railData, ivec3 entityPos, out CubeSide sideToMesh) 388 { 389 sideToMesh = slopeUpToSide[railData.data & 0b11]; 390 switch(sideToMesh) 391 { 392 case CubeSide.zneg: return entityPos.z == 0; 393 case CubeSide.xneg: return entityPos.x == 0; 394 case CubeSide.zpos: return entityPos.z == Z_RAIL_SIZE.z - 1; 395 case CubeSide.xpos: return entityPos.x == X_RAIL_SIZE.x - 1; 396 default: assert(false); 397 } 398 } 399 400 enum Z_RAIL_SIZE = ivec3(4, 1, 8); 401 enum X_RAIL_SIZE = ivec3(8, 1, 4); 402 enum DIAGONAL_RAIL_SIZE = ivec3(6, 1, 6); 403 404 // [x, z] 405 ivec3[] railSegmentSizes = [ 406 Z_RAIL_SIZE, // zneg 407 X_RAIL_SIZE, // xpos 408 409 DIAGONAL_RAIL_SIZE, // xnegZneg 410 DIAGONAL_RAIL_SIZE, // xnegZpos 411 DIAGONAL_RAIL_SIZE, // xposZpos 412 DIAGONAL_RAIL_SIZE, // xposZneg 413 414 Z_RAIL_SIZE, // znegUp 415 X_RAIL_SIZE, // xnegUp 416 Z_RAIL_SIZE, // zposUp 417 X_RAIL_SIZE, // xposUp 418 ]; 419 420 enum Z_RAIL_OFFSET = ivec3(2, 0, 0); 421 enum X_RAIL_OFFSET = ivec3(0, 0, 2); 422 423 // [x, z] 424 ivec3[] railSegmentOffsets = [ 425 Z_RAIL_OFFSET, // zneg 426 X_RAIL_OFFSET, // xpos 427 428 ivec3(0, 0, 0), // xnegZneg 429 ivec3(0, 0, 2), // xnegZpos 430 ivec3(2, 0, 2), // xposZpos 431 ivec3(2, 0, 0), // xposZneg 432 433 Z_RAIL_OFFSET, // znegUp 434 X_RAIL_OFFSET, // xnegUp 435 Z_RAIL_OFFSET, // zposUp 436 X_RAIL_OFFSET, // xposUp 437 ]; 438 439 440 ubyte[] railSegmentMeshId = [0, 0, 1, 1, 1, 1, 2, 2, 2, 2]; 441 ubyte[] railSegmentMeshRotation = [0, 1, 0, 1, 2, 3, 0, 1, 2, 3]; 442 443 void rotateSegment(ref RailSegment segment) 444 { 445 ++segment; 446 if (segment > RailSegment.max) 447 segment = RailSegment.min; 448 } 449 450 bool isSegmentSolid(RailSegment segment, ivec3 blockTilePos) 451 { 452 import core.bitop : bt; 453 auto bitmapId = bt(cast(size_t*)&railSegmentBottomSolidityIndex, segment); 454 ubyte rotation = railSegmentMeshRotation[segment]; 455 // Size needs to be less by 1 for correct shift 456 ivec3 rotatedPos = rotatePointShiftOriginCW!ivec3(blockTilePos, ivec3(7,0,7), rotation); 457 // Invert bit order (63 - bit num) add bitnum id offset of 64 458 auto tileIndex = 63 - (rotatedPos.x + rotatedPos.z * RAIL_TILE_SIZE) + 64 * bitmapId; 459 return !!bt(cast(size_t*)&railBottomSolidityBitmaps, tileIndex); 460 } 461 462 ushort railSegmentBottomSolidityIndex = 0b0000_1111_00; 463 464 ulong[2] railBottomSolidityBitmaps = [ 465 mixin("0b"~ // !bit order is right to left! 466 "00111100"~ 467 "00111100"~ 468 "00111100"~ 469 "00111100"~ 470 "00111100"~ 471 "00111100"~ 472 "00111100"~ 473 "00111100"), 474 mixin("0b"~ 475 "00111000"~ 476 "01110000"~ 477 "11100000"~ 478 "11000000"~ 479 "10000000"~ 480 "00000000"~ 481 "00000000"~ 482 "00000000")]; 483 484 // Rail tile can have a number of these. I.e. straight, diagonal rails are segments. 485 struct SegmentInfo 486 { 487 // Sides of rail tile that this segment connects to 488 // 0 zneg, 1 xneg, 2 zpos, 3 xpos 489 FaceSide[2] sides; 490 bool[4] sideConnections; 491 // 0 - first connection (item at sides[0]), 1 - second connection (item at sides[1]). Non-connected sides also use 0. 492 // Only indicies stated in 'sides' are relevant 493 ubyte[4] sideIndicies; 494 } 495 496 // relative to 8x8 tile 497 vec3[] railTileConnectionPoints = [ 498 vec3(4, 0.5, 0), 499 vec3(0, 0.5, 4), 500 vec3(4, 0.5, 8), 501 vec3(8, 0.5, 4), 502 ]; 503 504 SegmentInfo[10] segmentInfos = [ 505 {cast(FaceSide[2])[0, 2], [true, false, true, false], [0, 0, 1, 0]}, // zneg, 506 {cast(FaceSide[2])[1, 3], [false, true, false, true ], [0, 0, 0, 1]}, // xpos, 507 508 {cast(FaceSide[2])[1, 0], [true, true, false, false], [1, 0, 0, 0]}, // xnegZneg, 509 {cast(FaceSide[2])[1, 2], [false, true, true, false], [0, 0, 1, 0]}, // xnegZpos, 510 {cast(FaceSide[2])[3, 2], [false, false, true, true ], [0, 0, 1, 0]}, // xposZpos, 511 {cast(FaceSide[2])[3, 0], [true, false, false, true ], [1, 0, 0, 0]}, // xposZneg, 512 513 {cast(FaceSide[2])[0, 2], [true, false, false, false], [0, 0, 0, 0]}, // znegUp, 514 {cast(FaceSide[2])[1, 3], [false, true, false, false], [0, 0, 0, 0]}, // xnegUp, 515 {cast(FaceSide[2])[2, 0], [false, false, true, false], [0, 0, 0, 0]}, // zposUp, 516 {cast(FaceSide[2])[3, 1], [false, false, false, true ], [0, 0, 0, 0]}, // xposUp, 517 ]; 518 519 struct SmoothConnections 520 { 521 ushort data; 522 bool isSmoothWith(RailSegment segment) 523 { 524 return true; // TODO 525 } 526 } 527 528 // Table which gives info on valid transitions between segments for wagons 529 SmoothConnections[] segmentSmoothConnectionTbl = [ 530 ]; 531 532 533 import voxelman.graphics; 534 535 void railDebugHandler(ref BlockEntityDebugContext context) 536 { 537 drawSolidityDebug(context.graphics.debugBatch, RailData(context.data), context.bwp); 538 } 539 540 void drawSolidityDebug(ref Batch b, RailData data, BlockWorldPos bwp) 541 { 542 ivec3 tilePos = railTilePos(bwp.xyz); 543 ivec3 blockTilePos = bwp.xyz - tilePos; 544 545 foreach(segment; data.getSegments) 546 { 547 foreach(z; 0..RAIL_TILE_SIZE) 548 foreach(x; 0..RAIL_TILE_SIZE) 549 { 550 auto blockPos = ivec3(x, 0, z); 551 if (isSegmentSolid(segment, blockPos)) 552 { 553 auto renderPos = tilePos + ivec3(x, segment, z); 554 enum cursorOffset = vec3(0.01, 0.01, 0.01); 555 b.putCube(vec3(renderPos) - cursorOffset + vec3(0.25,0.25,0.25), 556 vec3(0.5,0.5,0.5) + cursorOffset, Colors.black, true); 557 } 558 } 559 } 560 } 561 562 // Diagonal rail utils 563 564 enum RailOrientation 565 { 566 x, 567 xzOppSign, // xneg-zpos, xpos-zneg 568 z, 569 xzSameSign, //xneg-zneg, xpos-zpos 570 } 571 572 enum DiagonalRailSide 573 { 574 zpos, 575 zneg 576 } 577 578 ivec2 addDiagonalManhattan(ivec2 origin, int distance, RailOrientation orientation, DiagonalRailSide side) 579 { 580 bool topSide = side == DiagonalRailSide.zneg; 581 int odd = distance % 2 != 0; 582 583 switch(orientation) 584 { 585 case RailOrientation.xzSameSign: 586 ivec2 oddIncrement = topSide ? ivec2(0, -1) : ivec2(1, 0); 587 return origin + ivec2(1, -1) * cast(int)floor(distance/2.0f) + oddIncrement * odd; 588 589 case RailOrientation.xzOppSign: 590 ivec2 oddIncrement = topSide ? ivec2(1, 0) : ivec2(0, 1); 591 return origin + ivec2(1, 1) * cast(int)floor(distance/2.0f) + oddIncrement * odd; 592 593 default: assert(false); 594 } 595 } 596