]> git.localhorst.tv Git - blank.git/blob - src/chunk.cpp
recycle chunks flagged for deletion
[blank.git] / src / chunk.cpp
1 #include "chunk.hpp"
2
3 #include "generator.hpp"
4
5 #include <limits>
6 #include <glm/gtx/transform.hpp>
7
8
9 namespace blank {
10
11 Chunk::Chunk(const BlockTypeRegistry &types)
12 : types(&types)
13 , blocks()
14 , model()
15 , position(0, 0, 0)
16 , dirty(false) {
17
18 }
19
20 Chunk::Chunk(Chunk &&other)
21 : types(other.types)
22 , blocks(std::move(other.blocks))
23 , model(std::move(other.model))
24 , dirty(other.dirty) {
25
26 }
27
28 Chunk &Chunk::operator =(Chunk &&other) {
29         types = other.types;
30         blocks = std::move(other.blocks);
31         model = std::move(other.model);
32         dirty = other.dirty;
33         return *this;
34 }
35
36
37 void Chunk::Allocate() {
38         blocks.resize(Size());
39 }
40
41
42 void Chunk::Draw() {
43         if (dirty) {
44                 Update();
45         }
46         model.Draw();
47 }
48
49
50 bool Chunk::Intersection(
51         const Ray &ray,
52         const glm::mat4 &M,
53         int *blkid,
54         float *dist,
55         glm::vec3 *normal) const {
56         { // rough check
57                 if (!blank::Intersection(ray, Bounds(), M)) {
58                         return false;
59                 }
60         }
61
62         if (!blkid && !dist && !normal) {
63                 return true;
64         }
65
66         // TODO: should be possible to heavily optimize this
67         int id = 0;
68         int closest_id = -1;
69         float closest_dist = std::numeric_limits<float>::infinity();
70         glm::vec3 closest_normal(0, 1, 0);
71         for (int z = 0; z < Depth(); ++z) {
72                 for (int y = 0; y < Height(); ++y) {
73                         for (int x = 0; x < Width(); ++x, ++id) {
74                                 if (!Type(blocks[id]).visible) {
75                                         continue;
76                                 }
77                                 float cur_dist;
78                                 glm::vec3 cur_norm;
79                                 Block::Pos pos(float(x) + 0.5f, float(y) + 0.5f, float(z) + 0.5f);
80                                 if (Type(blocks[id]).shape->Intersects(ray, glm::translate(M, pos), cur_dist, cur_norm)) {
81                                         if (cur_dist < closest_dist) {
82                                                 closest_id = id;
83                                                 closest_dist = cur_dist;
84                                                 closest_normal = cur_norm;
85                                         }
86                                 }
87                         }
88                 }
89         }
90
91         if (closest_id < 0) {
92                 return false;
93         }
94
95         if (blkid) {
96                 *blkid = closest_id;
97         }
98         if (dist) {
99                 *dist = closest_dist;
100         }
101         if (normal) {
102                 *normal = closest_normal;
103         }
104         return true;
105 }
106
107 void Chunk::Position(const Pos &pos) {
108         position = pos;
109 }
110
111 glm::mat4 Chunk::Transform(const Pos &offset) const {
112         return glm::translate((position - offset) * Extent());
113 }
114
115
116 void Chunk::CheckUpdate() {
117         if (dirty) {
118                 Update();
119         }
120         model.CheckUpdate();
121 }
122
123 void Chunk::Update() {
124         int vtx_count = 0, idx_count = 0;
125         for (const auto &block : blocks) {
126                 const Shape *shape = Type(block).shape;
127                 vtx_count += shape->VertexCount();
128                 idx_count += shape->VertexIndexCount();
129         }
130         model.Clear();
131         model.Reserve(vtx_count, idx_count);
132
133         Model::Index vtx_counter = 0;
134         for (size_t i = 0; i < Size(); ++i) {
135                 const BlockType &type = Type(blocks[i]);
136                 type.FillModel(model, ToCoords(i), vtx_counter);
137                 vtx_counter += type.shape->VertexCount();
138         }
139
140         model.Invalidate();
141         dirty = false;
142 }
143
144
145 ChunkLoader::ChunkLoader(const BlockTypeRegistry &reg, const Generator &gen)
146 : base(0, 0, 0)
147 , reg(reg)
148 , gen(gen)
149 , loaded()
150 , to_generate()
151 , to_free()
152 , load_dist(4)
153 , unload_dist(5) {
154
155 }
156
157 namespace {
158
159 struct ChunkLess {
160
161         explicit ChunkLess(const Chunk::Pos &base)
162         : base(base) { }
163
164         bool operator ()(const Chunk::Pos &a, const Chunk::Pos &b) const {
165                 Chunk::Pos da(base - a);
166                 Chunk::Pos db(base - b);
167                 return
168                         da.x * da.x + da.y * da.y + da.z * da.z <
169                         db.x * db.x + db.y * db.y + db.z * db.z;
170         }
171
172         Chunk::Pos base;
173
174 };
175
176 }
177
178 void ChunkLoader::Generate(const Chunk::Pos &from, const Chunk::Pos &to) {
179         for (int z = from.z; z < to.z; ++z) {
180                 for (int y = from.y; y < to.y; ++y) {
181                         for (int x = from.x; x < to.x; ++x) {
182                                 Chunk::Pos pos(x, y, z);
183                                 if (Known(pos)) {
184                                         continue;
185                                 } else if (x == 0 && y == 0 && z == 0) {
186                                         loaded.emplace_back(reg);
187                                         loaded.back().Position(pos);
188                                         gen(loaded.back());
189                                 } else {
190                                         to_generate.emplace_back(pos);
191                                 }
192                         }
193                 }
194         }
195         to_generate.sort(ChunkLess(base));
196 }
197
198 Chunk *ChunkLoader::Loaded(const Chunk::Pos &pos) {
199         for (Chunk &chunk : loaded) {
200                 if (chunk.Position() == pos) {
201                         return &chunk;
202                 }
203         }
204         return nullptr;
205 }
206
207 bool ChunkLoader::Queued(const Chunk::Pos &pos) {
208         for (const Chunk::Pos &chunk : to_generate) {
209                 if (chunk == pos) {
210                         return true;
211                 }
212         }
213         return nullptr;
214 }
215
216 bool ChunkLoader::Known(const Chunk::Pos &pos) {
217         if (Loaded(pos)) return true;
218         return Queued(pos);
219 }
220
221 Chunk &ChunkLoader::ForceLoad(const Chunk::Pos &pos) {
222         Chunk *chunk = Loaded(pos);
223         if (chunk) {
224                 return *chunk;
225         }
226
227         for (auto iter(to_generate.begin()), end(to_generate.end()); iter != end; ++iter) {
228                 if (*iter == pos) {
229                         to_generate.erase(iter);
230                         break;
231                 }
232         }
233
234         loaded.emplace_back(reg);
235         loaded.back().Position(pos);
236         gen(loaded.back());
237         return loaded.back();
238 }
239
240 void ChunkLoader::Rebase(const Chunk::Pos &new_base) {
241         if (new_base == base) {
242                 return;
243         }
244         base = new_base;
245
246         // unload far away chunks
247         for (auto iter(loaded.begin()), end(loaded.end()); iter != end;) {
248                 if (std::abs(base.x - iter->Position().x) > unload_dist
249                                 || std::abs(base.y - iter->Position().y) > unload_dist
250                                 || std::abs(base.z - iter->Position().z) > unload_dist) {
251                         auto saved = iter;
252                         ++iter;
253                         to_free.splice(to_free.end(), loaded, saved);
254                 } else {
255                         ++iter;
256                 }
257         }
258         // abort far away queued chunks
259         for (auto iter(to_generate.begin()), end(to_generate.end()); iter != end;) {
260                 if (std::abs(base.x - iter->x) > unload_dist
261                                 || std::abs(base.y - iter->y) > unload_dist
262                                 || std::abs(base.z - iter->z) > unload_dist) {
263                         iter = to_generate.erase(iter);
264                 } else {
265                         ++iter;
266                 }
267         }
268         // add missing new chunks
269         const Chunk::Pos offset(load_dist, load_dist, load_dist);
270         Generate(base - offset, base + offset);
271 }
272
273 void ChunkLoader::Update() {
274         bool reused = false;
275         if (!to_generate.empty()) {
276                 Chunk::Pos pos(to_generate.front());
277
278                 for (auto iter(to_free.begin()), end(to_free.end()); iter != end; ++iter) {
279                         if (iter->Position() == pos) {
280                                 loaded.splice(loaded.end(), to_free, iter);
281                                 reused = true;
282                                 break;
283                         }
284                 }
285
286                 if (!reused) {
287                         if (to_free.empty()) {
288                                 loaded.emplace_back(reg);
289                         } else {
290                                 loaded.splice(loaded.end(), to_free, to_free.begin());
291                                 reused = true;
292                         }
293                         loaded.back().Position(pos);
294                         gen(loaded.back());
295                 }
296                 to_generate.pop_front();
297         }
298
299         if (!reused && !to_free.empty()) {
300                 to_free.pop_front();
301         }
302 }
303
304 }