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