1 module dminer.core.blocks; 2 3 import dminer.core.minetypes; 4 import dminer.core.world; 5 import dlangui.graphics.scene.mesh; 6 7 immutable string BLOCK_TEXTURE_FILENAME = "blocks"; 8 immutable int BLOCK_TEXTURE_DX = 1024; 9 immutable int BLOCK_TEXTURE_DY = 1024; 10 immutable int BLOCK_SPRITE_SIZE = 16; 11 immutable int BLOCK_SPRITE_STEP = 16; 12 immutable int BLOCK_SPRITE_OFFSET = 0; 13 immutable int BLOCK_TEXTURE_SPRITES_PER_LINE = 1024/16; 14 immutable int VERTEX_COMPONENTS = 12; 15 16 enum BlockVisibility { 17 INVISIBLE, 18 OPAQUE, // completely opaque (cells covered by this block are invisible) 19 OPAQUE_SEPARATE_TX, 20 HALF_OPAQUE, // partially paque, cells covered by this block can be visible, render as normal block 21 HALF_OPAQUE_SEPARATE_TX, 22 HALF_TRANSPARENT, // should be rendered last (semi transparent texture) 23 } 24 25 class BlockDef { 26 public: 27 cell_t id; 28 string name; 29 BlockVisibility visibility = BlockVisibility.INVISIBLE; 30 int txIndex; 31 this() { 32 } 33 this(cell_t blockId, string blockName, BlockVisibility v, int tx) { 34 id = blockId; 35 name = blockName; 36 visibility = v; 37 txIndex = tx; 38 } 39 ~this() { 40 } 41 // blocks behind this block can be visible 42 @property bool canPass() { 43 return visibility == BlockVisibility.INVISIBLE 44 || visibility == BlockVisibility.HALF_OPAQUE 45 || visibility == BlockVisibility.HALF_OPAQUE_SEPARATE_TX 46 || visibility == BlockVisibility.HALF_TRANSPARENT; 47 } 48 // block is fully opaque (all blocks behind are invisible) 49 @property bool isOpaque() { 50 return visibility == BlockVisibility.OPAQUE 51 || visibility == BlockVisibility.OPAQUE_SEPARATE_TX; 52 } 53 // block is visible 54 @property bool isVisible() { 55 return visibility != BlockVisibility.INVISIBLE; 56 } 57 58 @property bool terrainSmoothing() { 59 return false; 60 } 61 62 /// add cube face 63 protected void addFace(Vector3d pos, Dir face, Mesh mesh, int textureIndex) { 64 ushort startVertexIndex = cast(ushort)mesh.vertexCount; 65 float[VERTEX_COMPONENTS * 4] vptr; 66 ushort[6] iptr; 67 createFaceMesh(vptr.ptr, face, pos.x, pos.y, pos.z, textureIndex); 68 for (int i = 0; i < 6; i++) 69 iptr[i] = cast(ushort)(startVertexIndex + face_indexes[i]); 70 mesh.addVertexes(vptr); 71 mesh.addPart(PrimitiveType.triangles, iptr); 72 } 73 74 /// create cube face 75 void createFace(World world, ref Position camPosition, Vector3d pos, Dir face, Mesh mesh) { 76 addFace(pos, face, mesh, txIndex); 77 } 78 /// create faces 79 void createFaces(World world, ref Position camPosition, Vector3d pos, int visibleFaces, Mesh mesh) { 80 for (int i = 0; i < 6; i++) 81 if (visibleFaces & (1 << i)) 82 createFace(world, camPosition, pos, cast(Dir)i, mesh); 83 } 84 } 85 86 87 // pos, normal, color, tx 88 89 90 /* North, z=-1 91 Y^ 92 0 | 1 93 X<-----x----- 94 3 | 2 95 */ 96 97 private immutable float CCC = 0.5; // cell cube coordinates 98 private immutable float TC0 = 0.0; 99 private immutable float TC1 = 0.99; 100 101 __gshared static const float[VERTEX_COMPONENTS * 4] face_vertices_north = 102 [ 103 CCC, CCC, -CCC, 0.0, 0.0, -1.0, 1.0, 1.0, 1.0, 1.0, TC0, TC0, 104 -CCC, CCC, -CCC, 0.0, 0.0, -1.0, 1.0, 1.0, 1.0, 1.0, TC1, TC0, 105 -CCC, -CCC, -CCC, 0.0, 0.0, -1.0, 1.0, 1.0, 1.0, 1.0, TC1, TC1, 106 CCC, -CCC, -CCC, 0.0, 0.0, -1.0, 1.0, 1.0, 1.0, 1.0, TC0, TC1, 107 ]; 108 109 /* South, z=1 110 Y^ 111 0 | 1 112 -----x----->X 113 3 | 2 114 */ 115 116 __gshared static const float[VERTEX_COMPONENTS * 4] face_vertices_south = 117 [ 118 -CCC, CCC, CCC, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, TC0, TC0, 119 CCC, CCC, CCC, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, TC1, TC0, 120 CCC, -CCC, CCC, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, TC1, TC1, 121 -CCC, -CCC, CCC, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, TC0, TC1, 122 ]; 123 124 /* West, x=-1 125 Y^ 126 0 | 1 127 -----x----->Z 128 3 | 2 129 */ 130 131 __gshared static const float[VERTEX_COMPONENTS * 4] face_vertices_west = 132 [ 133 -CCC, CCC, -CCC, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, TC0, TC0, 134 -CCC, CCC, CCC, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, TC1, TC0, 135 -CCC, -CCC, CCC, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, TC1, TC1, 136 -CCC, -CCC, -CCC, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, TC0, TC1 137 ]; 138 139 /* East, x=1 140 Y^ 141 0 | 1 142 Z<-----x----- 143 3 | 2 144 */ 145 146 __gshared static const float[VERTEX_COMPONENTS * 4] face_vertices_east = 147 [ 148 CCC, CCC, CCC, -1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, TC0, TC0, 149 CCC, CCC, -CCC, -1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, TC1, TC0, 150 CCC, -CCC, -CCC, -1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, TC1, TC1, 151 CCC, -CCC, CCC, -1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, TC0, TC1, 152 ]; 153 154 /* Up, y=1 155 156 0 | 1 157 -----x----->X 158 3 | 2 159 Zv 160 */ 161 162 __gshared static const float[VERTEX_COMPONENTS * 4] face_vertices_up = 163 [ 164 -CCC, CCC, -CCC, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, TC0, TC0, 165 CCC, CCC, -CCC, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, TC1, TC0, 166 CCC, CCC, CCC, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, TC1, TC1, 167 -CCC, CCC, CCC, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, TC0, TC1, 168 ]; 169 170 /* Down, y=-1 171 0 | 1 172 X<-----x----- 173 3 | 2 174 Zv 175 */ 176 177 __gshared static const float[VERTEX_COMPONENTS * 4] face_vertices_down = 178 [ 179 CCC, -CCC,-CCC, 0.0, -1.0, 0.0, 1.0, 1.0, 1.0, 1.0, TC0, TC0, 180 -CCC, -CCC,-CCC, 0.0, -1.0, 0.0, 1.0, 1.0, 1.0, 1.0, TC1, TC0, 181 -CCC, -CCC, CCC, 0.0, -1.0, 0.0, 1.0, 1.0, 1.0, 1.0, TC1, TC1, 182 CCC, -CCC, CCC, 0.0, -1.0, 0.0, 1.0, 1.0, 1.0, 1.0, TC0, TC1, 183 ]; 184 185 __gshared static const ushort[6] face_indexes = 186 [ 187 2, 1, 0, 0, 3, 2 // CCW 188 ]; 189 190 __gshared static const ushort[6] face_indexes_back = 191 [ 192 0, 2, 1, 2, 3, 1 193 ]; 194 195 static void fillFaceMesh(float * data, const float * src, float x0, float y0, float z0, int tileX, int tileY) { 196 for (int i = 0; i < 4; i++) { 197 const float * srcvertex = src + i * VERTEX_COMPONENTS; 198 float * dstvertex = data + i * VERTEX_COMPONENTS; 199 for (int j = 0; j < VERTEX_COMPONENTS; j++) { 200 float v = srcvertex[j]; 201 switch (j) { 202 case 0: // x 203 v += x0; 204 break; 205 case 1: // y 206 v += y0; 207 break; 208 case 2: // z 209 v += z0; 210 break; 211 case 10: // tx.u 212 v = ((tileX + v * BLOCK_SPRITE_SIZE)) / cast(float)BLOCK_TEXTURE_DX; 213 break; 214 case 11: // tx.v 215 //v = (BLOCK_TEXTURE_DY - (tileY + v * BLOCK_SPRITE_SIZE)) / cast(float)BLOCK_TEXTURE_DY; 216 v = ((tileY + v * BLOCK_SPRITE_SIZE)) / cast(float)BLOCK_TEXTURE_DY; 217 break; 218 default: 219 break; 220 } 221 dstvertex[j] = v; 222 } 223 } 224 } 225 226 static void createFaceMesh(float * data, Dir face, float x0, float y0, float z0, int tileIndex) { 227 228 int tileX = (tileIndex % BLOCK_TEXTURE_SPRITES_PER_LINE) * BLOCK_SPRITE_STEP + BLOCK_SPRITE_OFFSET; 229 int tileY = (tileIndex / BLOCK_TEXTURE_SPRITES_PER_LINE) * BLOCK_SPRITE_STEP + BLOCK_SPRITE_OFFSET; 230 // data is 11 comp * 4 vert floats 231 switch (face) with(Dir) { 232 default: 233 case NORTH: 234 fillFaceMesh(data, face_vertices_north.ptr, x0, y0, z0, tileX, tileY); 235 break; 236 case SOUTH: 237 fillFaceMesh(data, face_vertices_south.ptr, x0, y0, z0, tileX, tileY); 238 break; 239 case WEST: 240 fillFaceMesh(data, face_vertices_west.ptr, x0, y0, z0, tileX, tileY); 241 break; 242 case EAST: 243 fillFaceMesh(data, face_vertices_east.ptr, x0, y0, z0, tileX, tileY); 244 break; 245 case UP: 246 fillFaceMesh(data, face_vertices_up.ptr, x0, y0, z0, tileX, tileY); 247 break; 248 case DOWN: 249 fillFaceMesh(data, face_vertices_down.ptr, x0, y0, z0, tileX, tileY); 250 break; 251 } 252 } 253 254 255 256 // block type definitions 257 __gshared BlockDef[256] BLOCK_DEFS; 258 // faster check for block->canPass() 259 __gshared bool[256] BLOCK_TYPE_CAN_PASS; 260 // faster check for block->isOpaque() 261 __gshared bool[256] BLOCK_TYPE_OPAQUE; 262 // faster check for block->isVisible() 263 __gshared bool[256] BLOCK_TYPE_VISIBLE; 264 // faster check for block->isVisible() 265 __gshared bool[256] BLOCK_TERRAIN_SMOOTHING; 266 267 /// registers new block type 268 void registerBlockType(BlockDef def) { 269 if (BLOCK_DEFS[def.id]) { 270 if (BLOCK_DEFS[def.id] is def) 271 return; 272 destroy(BLOCK_DEFS[def.id]); 273 } 274 BLOCK_DEFS[def.id] = def; 275 // init property shortcuts 276 BLOCK_TYPE_CAN_PASS[def.id] = def.canPass; 277 BLOCK_TYPE_OPAQUE[def.id] = def.isOpaque; 278 BLOCK_TYPE_VISIBLE[def.id] = def.isVisible; 279 BLOCK_TERRAIN_SMOOTHING[def.id] = def.terrainSmoothing; 280 } 281 282 enum BlockImage : int { 283 stone, 284 grass_top, 285 grass_side, 286 grass_top_footsteps, 287 dirt, 288 bedrock, 289 sand, 290 gravel, 291 sandstone, 292 clay, 293 cobblestone, 294 cobblestone_mossy, 295 brick, 296 stonebrick, 297 red_sand, 298 299 face_test=64, 300 } 301 302 enum BlockId : cell_t { 303 air, // 0 304 gray_brick, 305 brick, 306 bedrock, 307 clay, 308 cobblestone, 309 gravel, 310 red_sand, 311 sand, 312 dirt, 313 grass, 314 face_test 315 } 316 317 /// init block types array 318 __gshared static this() { 319 import std.string; 320 for (int i = 0; i < 256; i++) { 321 if (!BLOCK_DEFS[i]) { 322 registerBlockType(new BlockDef(cast(cell_t)i, "undef%d".format(i), BlockVisibility.INVISIBLE, 0)); 323 } 324 } 325 BLOCK_TYPE_CAN_PASS[BOUND_SKY] = false; 326 BLOCK_TYPE_VISIBLE[BOUND_SKY] = false; 327 BLOCK_TYPE_CAN_PASS[BOUND_BOTTOM] = false; 328 BLOCK_TYPE_VISIBLE[BOUND_BOTTOM] = true; 329 330 // empty cell 331 registerBlockType(new BlockDef(BlockId.air, "air", BlockVisibility.INVISIBLE, 0)); 332 // standard block types 333 registerBlockType(new BlockDef(BlockId.gray_brick, "gray_brick", BlockVisibility.OPAQUE, BlockImage.stonebrick)); 334 registerBlockType(new BlockDef(BlockId.brick, "brick", BlockVisibility.OPAQUE, BlockImage.brick)); 335 registerBlockType(new BlockDef(BlockId.bedrock, "bedrock", BlockVisibility.OPAQUE, BlockImage.bedrock)); 336 registerBlockType(new BlockDef(BlockId.clay, "clay", BlockVisibility.OPAQUE, BlockImage.clay)); 337 registerBlockType(new BlockDef(BlockId.cobblestone, "cobblestone", BlockVisibility.OPAQUE, BlockImage.cobblestone)); 338 registerBlockType(new BlockDef(BlockId.gravel, "gravel", BlockVisibility.OPAQUE, BlockImage.gravel)); 339 registerBlockType(new BlockDef(BlockId.red_sand, "red_sand", BlockVisibility.OPAQUE, BlockImage.red_sand)); 340 registerBlockType(new BlockDef(BlockId.sand, "sand", BlockVisibility.OPAQUE, BlockImage.sand)); 341 registerBlockType(new BlockDef(BlockId.dirt, "dirt", BlockVisibility.OPAQUE, BlockImage.dirt)); 342 registerBlockType(new CustomTopBlock(BlockId.grass, "grass", BlockVisibility.OPAQUE, BlockImage.dirt, BlockImage.grass_top, BlockImage.grass_side)); 343 344 345 // for face texture test 346 registerBlockType(new BlockDef(BlockId.face_test, "face_test", BlockVisibility.OPAQUE, BlockImage.face_test)); 347 348 //registerBlockType(new BlockDef(50, "box", BlockVisibility.HALF_OPAQUE, 50)); 349 350 //registerBlockType(new TerrainBlock(100, "terrain_bedrock", 2)); 351 //registerBlockType(new TerrainBlock(101, "terrain_clay", 3)); 352 //registerBlockType(new TerrainBlock(102, "terrain_cobblestone", 4)); 353 //registerBlockType(new TerrainBlock(103, "terrain_gravel", 5)); 354 //registerBlockType(new TerrainBlock(104, "terrain_red_sand", 6)); 355 //registerBlockType(new TerrainBlock(105, "terrain_sand", 7)); 356 357 } 358 359 class CustomTopBlock : BlockDef { 360 public: 361 int topTxIndex; 362 int sideTxIndex; 363 this(cell_t blockId, string blockName, BlockVisibility v, int tx, int topTx, int sideTx) { 364 super(blockId, blockName, BlockVisibility.OPAQUE, tx); 365 topTxIndex = topTx; 366 sideTxIndex = sideTx; 367 } 368 ~this() { 369 } 370 371 /// create cube face 372 override void createFace(World world, ref Position camPosition, Vector3d pos, Dir face, Mesh mesh) { 373 // checking cell above 374 cell_t blockAbove = world.getCell(pos.x, pos.y + 1, pos.z); 375 int tx = txIndex; 376 if (BLOCK_TYPE_CAN_PASS[blockAbove]) { 377 if (face == Dir.UP) { 378 tx = topTxIndex; 379 } else if (face != Dir.DOWN) { 380 tx = sideTxIndex; 381 } 382 } 383 addFace(pos, face, mesh, tx); 384 } 385 } 386