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