]> git.localhorst.tv Git - blank.git/blob - src/chunk.cpp
split chunk loader from world
[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 &a, const Chunk &b) const {
165                 Chunk::Pos da(base - a.Position());
166                 Chunk::Pos db(base - b.Position());
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(reg);
191                                         to_generate.back().Position(pos);
192                                 }
193                         }
194                 }
195         }
196         to_generate.sort(ChunkLess(base));
197 }
198
199 Chunk *ChunkLoader::Loaded(const Chunk::Pos &pos) {
200         for (Chunk &chunk : loaded) {
201                 if (chunk.Position() == pos) {
202                         return &chunk;
203                 }
204         }
205         return nullptr;
206 }
207
208 Chunk *ChunkLoader::Queued(const Chunk::Pos &pos) {
209         for (Chunk &chunk : to_generate) {
210                 if (chunk.Position() == pos) {
211                         return &chunk;
212                 }
213         }
214         return nullptr;
215 }
216
217 Chunk *ChunkLoader::Known(const Chunk::Pos &pos) {
218         Chunk *chunk = Loaded(pos);
219         if (chunk) return chunk;
220
221         return Queued(pos);
222 }
223
224 Chunk &ChunkLoader::ForceLoad(const Chunk::Pos &pos) {
225         Chunk *chunk = Loaded(pos);
226         if (chunk) {
227                 return *chunk;
228         }
229
230         chunk = Queued(pos);
231         if (chunk) {
232                 gen(*chunk);
233                 return *chunk;
234         }
235
236         loaded.emplace_back(reg);
237         loaded.back().Position(pos);
238         gen(loaded.back());
239         return loaded.back();
240 }
241
242 void ChunkLoader::Rebase(const Chunk::Pos &new_base) {
243         if (new_base == base) {
244                 return;
245         }
246         base = new_base;
247
248         // unload far away chunks
249         for (auto iter(loaded.begin()), end(loaded.end()); iter != end;) {
250                 if (std::abs(base.x - iter->Position().x) > unload_dist
251                                 || std::abs(base.y - iter->Position().y) > unload_dist
252                                 || std::abs(base.z - iter->Position().z) > unload_dist) {
253                         auto saved = iter;
254                         ++iter;
255                         to_free.splice(to_free.end(), loaded, saved);
256                 } else {
257                         ++iter;
258                 }
259         }
260         // abort far away queued chunks
261         for (auto iter(to_generate.begin()), end(to_generate.end()); iter != end;) {
262                 if (std::abs(base.x - iter->Position().x) > unload_dist
263                                 || std::abs(base.y - iter->Position().y) > unload_dist
264                                 || std::abs(base.z - iter->Position().z) > unload_dist) {
265                         iter = to_generate.erase(iter);
266                 } else {
267                         ++iter;
268                 }
269         }
270         // add missing new chunks
271         const Chunk::Pos offset(load_dist, load_dist, load_dist);
272         Generate(base - offset, base + offset);
273 }
274
275 void ChunkLoader::Update() {
276         if (!to_generate.empty()) {
277                 gen(to_generate.front());
278                 loaded.splice(loaded.end(), to_generate, to_generate.begin());
279         }
280
281         if (!to_free.empty()) {
282                 to_free.pop_front();
283         }
284 }
285
286 }