1 module dminer.core.world;
2
3 import dminer.core.minetypes;
4 import dminer.core.blocks;
5 import dminer.core.chunk;
6
7 version (Android) {
8 const int MAX_VIEW_DISTANCE = 60;
9 } else {
10 const int MAX_VIEW_DISTANCE = 250;
11 }
12
13
14 class World {
15
16 this() {
17 _camPosition = Position(Vector3d(0, 13, 0), Vector3d(0, 0, 1));
18 }
19 ~this() {
20 clear();
21 }
22 void clear() {
23 for(int index = 0; index < _chunkStacks.length; index++) {
24 if (_chunkStacks[index]) {
25 destroy(_chunkStacks[index]);
26 _chunkStacks[index] = null;
27 }
28 }
29 }
30 @property final ref Position camPosition() { return _camPosition; }
31
32
33 protected ChunkStack*[CHUNKS_X * 2 * CHUNKS_Z * 2] _chunkStacks;
34
35 //pragma(msg, "stack pointers array size, Kb:");
36 //pragma(msg, _chunkStacks.sizeof / 1024);
37 final cell_t getCell(int x, int y, int z) {
38 int chunkx = (x >> 3) + CHUNKS_X;
39 int chunkz = (z >> 3) + CHUNKS_Z;
40 if ((chunkx & (~CHUNKS_X_MASK)) || (chunkz & (~CHUNKS_Z_MASK)))
41 return 0; // out of bounds x,z
42 int index = chunkx + (chunkz << (CHUNKS_BITS_X + 1));
43 if (ChunkStack * stack = _chunkStacks[index]) {
44 int chunkY = (y >> 3);
45 if (SmallChunk * chunk = stack.get(chunkY))
46 return chunk.getCell(x, y, z);
47 }
48 return 0;
49 }
50 /// get chunk stack for cell by world cell coordinates x, z
51 ChunkStack * getCellChunkStack(int x, int z) {
52 int chunkx = (x >> 3) + CHUNKS_X;
53 int chunkz = (z >> 3) + CHUNKS_Z;
54 if ((chunkx & (~CHUNKS_X_MASK)) || (chunkz & (~CHUNKS_Z_MASK)))
55 return null; // out of bounds x,z
56 int index = chunkx + (chunkz << (CHUNKS_BITS_X + 1));
57 return _chunkStacks[index];
58 }
59 /// get chunk by chunkx = x / 8 + CHUNKS_X, chunky = y / 8, chunkz = z / 8 + CHUNKS_Z
60 SmallChunk * getChunk(int chunkx, int chunky, int chunkz) {
61 if ((chunkx & (~CHUNKS_X_MASK)) || (chunkz & (~CHUNKS_Z_MASK)))
62 return null; // out of bounds x,z
63 int index = chunkx + (chunkz << (CHUNKS_BITS_X + 1));
64 if (ChunkStack * stack = _chunkStacks[index])
65 return stack.get(chunky);
66 return null;
67 }
68 /// get chunk for cell by world cell coordinates x, y, z
69 SmallChunk * getCellChunk(int x, int y, int z) {
70 int chunkx = (x >> 3) + CHUNKS_X;
71 int chunkz = (z >> 3) + CHUNKS_Z;
72 if ((chunkx & (~CHUNKS_X_MASK)) || (chunkz & (~CHUNKS_Z_MASK)))
73 return null; // out of bounds x,z
74 int index = chunkx + (chunkz << (CHUNKS_BITS_X + 1));
75 int chunky = (y >> 3);
76 if (ChunkStack * stack = _chunkStacks[index])
77 return stack.get(chunky);
78 return null;
79 }
80
81 private void updateNearChunks(SmallChunk * thisChunk, int x, int y, int z) {
82 // UP
83 SmallChunk * chunkAbove = getCellChunk(x, y + 8, z);
84 thisChunk.nearChunks[Dir.UP] = chunkAbove;
85 if (chunkAbove)
86 chunkAbove.nearChunks[Dir.DOWN] = thisChunk;
87 // DOWN
88 SmallChunk * chunkBelow = getCellChunk(x, y - 8, z);
89 thisChunk.nearChunks[Dir.DOWN] = chunkBelow;
90 if (chunkBelow)
91 chunkBelow.nearChunks[Dir.UP] = thisChunk;
92 // WEST
93 SmallChunk * chunkWest = getCellChunk(x - 8, y, z);
94 thisChunk.nearChunks[Dir.WEST] = chunkWest;
95 if (chunkWest)
96 chunkWest.nearChunks[Dir.EAST] = thisChunk;
97 // EAST
98 SmallChunk * chunkEast = getCellChunk(x + 8, y, z);
99 thisChunk.nearChunks[Dir.EAST] = chunkEast;
100 if (chunkEast)
101 chunkEast.nearChunks[Dir.WEST] = thisChunk;
102 // NORTH
103 SmallChunk * chunkNorth = getCellChunk(x, y, z - 8);
104 thisChunk.nearChunks[Dir.NORTH] = chunkNorth;
105 if (chunkNorth)
106 chunkNorth.nearChunks[Dir.SOUTH] = thisChunk;
107 // SOUTH
108 SmallChunk * chunkSouth = getCellChunk(x, y, z + 8);
109 thisChunk.nearChunks[Dir.SOUTH] = chunkSouth;
110 if (chunkSouth)
111 chunkSouth.nearChunks[Dir.NORTH] = thisChunk;
112 }
113
114 final void setCell(int x, int y, int z, cell_t value) {
115 int chunkx = (x >> 3) + CHUNKS_X;
116 int chunkz = (z >> 3) + CHUNKS_Z;
117 if ((chunkx & (~CHUNKS_X_MASK)) || (chunkz & (~CHUNKS_Z_MASK)))
118 return; // out of bounds x,z
119 int index = chunkx + (chunkz << (CHUNKS_BITS_X + 1));
120 ChunkStack * stack = _chunkStacks[index];
121 SmallChunk * chunk;
122 if (stack) {
123 int chunkY = (y >> 3);
124 chunk = stack.get(chunkY);
125 if (chunk)
126 chunk.setCell(x, y, z, value);
127 else {
128 // create chunk
129 if (!value)
130 return; // don't create chunk for 0
131 chunk = SmallChunk.alloc(x, y, z);
132 stack.set(chunkY, chunk);
133 chunk.setCell(x, y, z, value);
134 updateNearChunks(chunk, x, y, z);
135 }
136 } else {
137 if (!value)
138 return; // don't create chunk for 0
139 stack = new ChunkStack();
140 _chunkStacks[index] = stack;
141 int chunkY = (y >> 3);
142 chunk = SmallChunk.alloc(x, y, z);
143 stack.set(chunkY, chunk);
144 chunk.setCell(x, y, z, value);
145 updateNearChunks(chunk, x, y, z);
146 }
147 }
148
149
150
151
152 void setCellRange(Vector3d pos, Vector3d sz, cell_t value) {
153 for (int x = 0; x < sz.x; x++)
154 for (int y = 0; y < sz.y; y++)
155 for (int z = 0; z < sz.z; z++)
156 setCell(pos.x + x, pos.y + y, pos.z + z, value);
157 }
158
159 final bool isOpaque(int x, int y, int z) {
160 cell_t cell = getCell(x, y, z);
161 return BLOCK_TYPE_OPAQUE.ptr[cell] && cell != BOUND_SKY;
162 }
163
164 bool canPass(Vector3d pos) {
165 return canPass(Vector3d(pos.x - 2, pos.y - 3, pos.z - 2), Vector3d(4, 5, 4));
166 }
167
168 bool canPass(Vector3d pos, Vector3d size) {
169 for (int x = 0; x <= size.x; x++)
170 for (int z = 0; z <= size.z; z++)
171 for (int y = 0; y < size.y; y++) {
172 if (isOpaque(pos.x + x, pos.y + y, pos.z + z))
173 return false;
174 }
175 return true;
176 }
177
178 /// get max Y position of non-empty cell in region (x +- size, z +- size)
179 int regionHeight(int x, int z, int size) {
180 int top = -1;
181 int delta = size + 1;
182 for (int dx = x - delta; dx <= x + delta; dx += 8) {
183 for (int dz = z - delta; dz <= z + delta; dz += 8) {
184 if (ChunkStack * stack = getCellChunkStack(x, z)) {
185 if (top < stack.topNonEmptyY)
186 top = stack.topNonEmptyY;
187 }
188 }
189 }
190 return top;
191 }
192
193 private void visitChunk(ChunkVisitor visitor, Vector3d pos) {
194 SmallChunk * chunk = getCellChunk(pos.x, pos.y, pos.z);
195 if (chunk && chunk.hasVisibleFaces)
196 visitor.visit(this, chunk);
197 }
198
199 /// visit visible chunks, starting from specified position
200 void visitVisibleChunks(ChunkVisitor visitor, Vector3d pos, int maxDistance) {
201 int chunkDist = (maxDistance + 7) >> 3;
202 visitChunk(visitor, pos);
203 for (int dist = 1; dist <= chunkDist; dist++) {
204 int d = dist << 3;
205 visitChunk(visitor, Vector3d(pos.x - d, pos.y, pos.z));
206 visitChunk(visitor, Vector3d(pos.x + d, pos.y, pos.z));
207 visitChunk(visitor, Vector3d(pos.x, pos.y - d, pos.z));
208 visitChunk(visitor, Vector3d(pos.x, pos.y + d, pos.z));
209 visitChunk(visitor, Vector3d(pos.x, pos.y, pos.z - d));
210 visitChunk(visitor, Vector3d(pos.x, pos.y, pos.z + d));
211 for (int i = 1; i <= dist; i++) {
212 }
213 }
214 }
215
216 private:
217
218 Position _camPosition;
219 int maxVisibleRange = MAX_VIEW_DISTANCE;
220 }
221
222 struct VisitorCell {
223 SmallChunk * chunk;
224 ulong[6] accessible;
225 bool visited;
226 int dirFlags;
227 }
228
229 struct ChunkDiamondVisitor {
230 World world;
231 ChunkVisitor visitor;
232 Vector3d pos;
233 Array3d!VisitorCell cells;
234 int maxDist;
235 Vector3dArray oldcells;
236 Vector3dArray newcells;
237 void init(World world, int distance, ChunkVisitor visitor) {
238 this.world = world;
239 this.maxDist = (distance + 7) / 8;
240 cells.reset(maxDist);
241 this.visitor = visitor;
242 }
243 void visitCell(VisitorCell * oldCell, int x, int y, int z, Dir direction) {
244 if (x < -maxDist || x > maxDist || y < -maxDist || y > maxDist || z < -maxDist || z > maxDist)
245 return; // out of bounds
246 auto cell = cells.ptr(x, y, z);
247 if (!cell.visited) {
248 cell.chunk = world.getCellChunk(pos.x + (x << 3), pos.y + (y << 3), pos.z + (z << 3));
249 cell.visited = true;
250 newcells.append(Vector3d(x, y, z));
251 }
252 cell.dirFlags |= (1 << direction);
253 }
254 void visitChunks(Vector3d pos) {
255 this.pos = pos;
256 cells.reset(maxDist);
257 //cells[1,2,3] = VisitorCell.init;
258 newcells.clear();
259 //oldcells.append(Vector3d(0, 0, 0));
260 visitCell(null, 0,0,0, Dir.NORTH);
261 visitCell(null, 0,0,0, Dir.SOUTH);
262 visitCell(null, 0,0,0, Dir.WEST);
263 visitCell(null, 0,0,0, Dir.EAST);
264 visitCell(null, 0,0,0, Dir.UP);
265 visitCell(null, 0,0,0, Dir.DOWN);
266 newcells.swap(oldcells);
267 // call visitor for this newly visited cells
268 for (int i = 0; i < oldcells.length; i++) {
269 Vector3d pt = oldcells[i];
270 auto cell = cells.ptr(pt.x, pt.y, pt.z);
271 if (cell.chunk)
272 visitor.visit(world, cell.chunk);
273 }
274 for (int dist = 0; dist < maxDist * 2; dist++) {
275 if (oldcells.length == 0)
276 break;
277 newcells.clear();
278 for (int i = 0; i < oldcells.length; i++) {
279 Vector3d pt = oldcells[i];
280 auto oldcell = cells.ptr(pt.x, pt.y, pt.z);
281 if (pt.x < 0) {
282 visitCell(oldcell, pt.x - 1, pt.y, pt.z, Dir.WEST);
283 } else if (pt.x > 0) {
284 visitCell(oldcell, pt.x + 1, pt.y, pt.z, Dir.EAST);
285 } else {
286 visitCell(oldcell, pt.x - 1, pt.y, pt.z, Dir.WEST);
287 visitCell(oldcell, pt.x + 1, pt.y, pt.z, Dir.EAST);
288 }
289 if (pt.y < 0) {
290 visitCell(oldcell, pt.x, pt.y - 1, pt.z, Dir.DOWN);
291 } else if (pt.y > 0) {
292 visitCell(oldcell, pt.x, pt.y + 1, pt.z, Dir.UP);
293 } else {
294 visitCell(oldcell, pt.x, pt.y - 1, pt.z, Dir.DOWN);
295 visitCell(oldcell, pt.x, pt.y + 1, pt.z, Dir.UP);
296 }
297 if (pt.z < 0) {
298 visitCell(oldcell, pt.x, pt.y, pt.z - 1, Dir.WEST);
299 } else if (pt.z > 0) {
300 visitCell(oldcell, pt.x, pt.y, pt.z + 1, Dir.EAST);
301 } else {
302 visitCell(oldcell, pt.x, pt.y, pt.z - 1, Dir.WEST);
303 visitCell(oldcell, pt.x, pt.y, pt.z + 1, Dir.EAST);
304 }
305 }
306 newcells.swap(oldcells);
307 // call visitor for this newly visited cells
308 for (int i = 0; i < oldcells.length; i++) {
309 Vector3d pt = oldcells[i];
310 auto cell = cells.ptr(pt.x, pt.y, pt.z);
311 if (cell.chunk)
312 visitor.visit(world, cell.chunk);
313 }
314 }
315 }
316 }
317