]> git.localhorst.tv Git - blank.git/blob - src/world/World.cpp
allow multiple meshes per entity
[blank.git] / src / world / World.cpp
1 #include "World.hpp"
2
3 #include "WorldCollision.hpp"
4 #include "../app/Assets.hpp"
5 #include "../graphics/Format.hpp"
6 #include "../graphics/Viewport.hpp"
7
8 #include <limits>
9 #include <glm/gtx/io.hpp>
10 #include <glm/gtx/transform.hpp>
11
12
13 namespace blank {
14
15 World::World(const Assets &assets, const Config &config, const WorldSave &save)
16 : blockType()
17 , blockShape({{ -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f }})
18 , stairShape({{ -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f }}, { 0.0f, 0.0f })
19 , slabShape({{ -0.5f, -0.5f, -0.5f }, { 0.5f, 0.0f, 0.5f }})
20 , block_tex()
21 , generate(config.gen)
22 , chunks(config.load, blockType, generate, save)
23 , player()
24 , entities()
25 , light_direction(config.light_direction)
26 , fog_density(config.fog_density) {
27         BlockType::Faces block_fill = {  true,  true,  true,  true,  true,  true };
28         BlockType::Faces slab_fill  = { false,  true, false, false, false, false };
29         BlockType::Faces stair_fill = { false,  true, false, false, false,  true };
30
31         block_tex.Bind();
32         block_tex.Reserve(16, 16, 4, Format());
33         assets.LoadTexture("debug", block_tex, 0);
34         assets.LoadTexture("rock-1", block_tex, 1);
35         assets.LoadTexture("rock-2", block_tex, 2);
36         assets.LoadTexture("rock-3", block_tex, 3);
37         block_tex.FilterNearest();
38
39         { // white block
40                 BlockType type(true, { 1.0f, 1.0f, 1.0f }, &blockShape);
41                 type.texture = 1;
42                 type.label = "White Block";
43                 type.block_light = true;
44                 type.collision = true;
45                 type.collide_block = true;
46                 type.fill = block_fill;
47                 blockType.Add(type);
48         }
49         { // white slab
50                 BlockType type(true, { 1.0f, 1.0f, 1.0f }, &slabShape);
51                 type.texture = 1;
52                 type.label = "White Slab";
53                 type.block_light = true;
54                 type.collision = true;
55                 type.collide_block = true;
56                 type.fill = slab_fill;
57                 blockType.Add(type);
58         }
59         { // white stair
60                 BlockType type(true, { 1.0f, 1.0f, 1.0f }, &stairShape);
61                 type.texture = 1;
62                 type.label = "White Stair";
63                 type.block_light = true;
64                 type.collision = true;
65                 type.collide_block = true;
66                 type.fill = stair_fill;
67                 blockType.Add(type);
68         }
69
70         { // red block
71                 BlockType type(true, { 1.0f, 0.0f, 0.0f }, &blockShape);
72                 type.texture = 3;
73                 type.label = "Red Block";
74                 type.block_light = true;
75                 type.collision = true;
76                 type.collide_block = true;
77                 type.fill = block_fill;
78                 blockType.Add(type);
79         }
80         { // red slab
81                 BlockType type(true, { 1.0f, 0.0f, 0.0f }, &slabShape);
82                 type.texture = 3;
83                 type.label = "Red Slab";
84                 type.block_light = true;
85                 type.collision = true;
86                 type.collide_block = true;
87                 type.fill = slab_fill;
88                 blockType.Add(type);
89         }
90         { // red stair
91                 BlockType type(true, { 1.0f, 0.0f, 0.0f }, &stairShape);
92                 type.texture = 3;
93                 type.label = "Red Stair";
94                 type.block_light = true;
95                 type.collision = true;
96                 type.collide_block = true;
97                 type.fill = stair_fill;
98                 blockType.Add(type);
99         }
100
101         { // green block
102                 BlockType type(true, { 0.0f, 1.0f, 0.0f }, &blockShape);
103                 type.texture = 1;
104                 type.label = "Green Block";
105                 type.block_light = true;
106                 type.collision = true;
107                 type.collide_block = true;
108                 type.fill = block_fill;
109                 blockType.Add(type);
110         }
111         { // green slab
112                 BlockType type(true, { 0.0f, 1.0f, 0.0f }, &slabShape);
113                 type.texture = 1;
114                 type.label = "Green Slab";
115                 type.block_light = true;
116                 type.collision = true;
117                 type.collide_block = true;
118                 type.fill = slab_fill;
119                 blockType.Add(type);
120         }
121         { // green stair
122                 BlockType type(true, { 0.0f, 1.0f, 0.0f }, &stairShape);
123                 type.texture = 1;
124                 type.label = "Green Stair";
125                 type.block_light = true;
126                 type.collision = true;
127                 type.collide_block = true;
128                 type.fill = stair_fill;
129                 blockType.Add(type);
130         }
131
132         { // blue block
133                 BlockType type(true, { 0.0f, 0.0f, 1.0f }, &blockShape);
134                 type.texture = 3;
135                 type.label = "Blue Block";
136                 type.block_light = true;
137                 type.collision = true;
138                 type.collide_block = true;
139                 type.fill = block_fill;
140                 blockType.Add(type);
141         }
142         { // blue slab
143                 BlockType type(true, { 0.0f, 0.0f, 1.0f }, &slabShape);
144                 type.texture = 3;
145                 type.label = "Blue Slab";
146                 type.block_light = true;
147                 type.collision = true;
148                 type.collide_block = true;
149                 type.fill = slab_fill;
150                 blockType.Add(type);
151         }
152         { // blue stair
153                 BlockType type(true, { 0.0f, 0.0f, 1.0f }, &stairShape);
154                 type.texture = 3;
155                 type.label = "Blue Stair";
156                 type.block_light = true;
157                 type.collision = true;
158                 type.collide_block = true;
159                 type.fill = stair_fill;
160                 blockType.Add(type);
161         }
162
163         { // glowing yellow block
164                 BlockType type(true, { 1.0f, 1.0f, 0.0f }, &blockShape);
165                 type.texture = 2;
166                 type.label = "Light";
167                 type.luminosity = 15;
168                 type.block_light = true;
169                 type.collision = true;
170                 type.collide_block = true;
171                 type.fill = block_fill;
172                 blockType.Add(type);
173         }
174
175         { // the mysterious debug cube
176                 BlockType type(true, { 1.0f, 1.0f, 1.0f }, &blockShape);
177                 type.texture = 0;
178                 type.label = "Debug Cube";
179                 type.luminosity = 0;
180                 type.block_light = true;
181                 type.collision = true;
182                 type.collide_block = true;
183                 type.fill = block_fill;
184                 blockType.Add(type);
185         }
186
187         generate.Space(0);
188         generate.Light(13);
189         generate.Solids({ 1, 4, 7, 10 });
190
191         player = &AddEntity();
192         player->Name("player");
193         player->Bounds({ { -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f } });
194         player->WorldCollidable(true);
195         player->Position(config.spawn);
196
197         chunks.QueueSurrounding(player->ChunkCoords());
198 }
199
200
201 namespace {
202
203 struct Candidate {
204         Chunk *chunk;
205         float dist;
206 };
207
208 std::vector<Candidate> candidates;
209
210 }
211
212 bool World::Intersection(
213         const Ray &ray,
214         const glm::mat4 &M,
215         Chunk *&chunk,
216         int &blkid,
217         float &dist,
218         glm::vec3 &normal
219 ) {
220         candidates.clear();
221
222         for (Chunk &cur_chunk : chunks.Loaded()) {
223                 float cur_dist;
224                 if (cur_chunk.Intersection(ray, M * cur_chunk.Transform(player->ChunkCoords()), cur_dist)) {
225                         candidates.push_back({ &cur_chunk, cur_dist });
226                 }
227         }
228
229         if (candidates.empty()) return false;
230
231         chunk = nullptr;
232         dist = std::numeric_limits<float>::infinity();
233         blkid = -1;
234
235         for (Candidate &cand : candidates) {
236                 if (cand.dist > dist) continue;
237                 int cur_blkid;
238                 float cur_dist;
239                 glm::vec3 cur_normal;
240                 if (cand.chunk->Intersection(ray, M * cand.chunk->Transform(player->ChunkCoords()), cur_blkid, cur_dist, cur_normal)) {
241                         if (cur_dist < dist) {
242                                 chunk = cand.chunk;
243                                 blkid = cur_blkid;
244                                 dist = cur_dist;
245                                 normal = cur_normal;
246                         }
247                 }
248         }
249
250         return chunk;
251 }
252
253 bool World::Intersection(const Entity &e, std::vector<WorldCollision> &col) {
254         AABB box = e.Bounds();
255         glm::mat4 M = e.Transform(player->ChunkCoords());
256         bool any = false;
257         // TODO: this only needs to check the chunks surrounding the entity's chunk position
258         //       need find out if that is quicker than the rough chunk bounds test
259         for (Chunk &cur_chunk : chunks.Loaded()) {
260                 if (cur_chunk.Intersection(box, M, cur_chunk.Transform(player->ChunkCoords()), col)) {
261                         any = true;
262                 }
263         }
264         return any;
265 }
266
267
268 Chunk &World::PlayerChunk() {
269         return chunks.ForceLoad(player->ChunkCoords());
270 }
271
272 Chunk &World::Next(const Chunk &to, const glm::ivec3 &dir) {
273         const Chunk::Pos tgt_pos = to.Position() + dir;
274         return chunks.ForceLoad(tgt_pos);
275 }
276
277
278 namespace {
279
280 std::vector<WorldCollision> col;
281
282 }
283
284 void World::Update(int dt) {
285         for (Entity &entity : entities) {
286                 entity.Update(dt);
287         }
288         for (Entity &entity : entities) {
289                 col.clear();
290                 if (entity.WorldCollidable() && Intersection(entity, col)) {
291                         // entity collides with the world
292                         Resolve(entity, col);
293                 }
294         }
295         for (auto iter = entities.begin(), end = entities.end(); iter != end;) {
296                 if (iter->CanRemove()) {
297                         iter = entities.erase(iter);
298                 } else {
299                         ++iter;
300                 }
301         }
302         chunks.Rebase(player->ChunkCoords());
303         chunks.Update(dt);
304 }
305
306 void World::Resolve(Entity &e, std::vector<WorldCollision> &col) {
307         // determine displacement for each cardinal axis and move entity accordingly
308         glm::vec3 min_disp(0.0f);
309         glm::vec3 max_disp(0.0f);
310         for (const WorldCollision &c : col) {
311                 if (!c.Blocks()) continue;
312                 glm::vec3 local_disp(c.normal * c.depth);
313                 // swap if neccessary (normal may point away from the entity)
314                 if (dot(c.normal, e.Position() - c.BlockCoords()) < 0) {
315                         local_disp *= -1;
316                 }
317                 min_disp = min(min_disp, local_disp);
318                 max_disp = max(max_disp, local_disp);
319         }
320         // for each axis
321         // if only one direction is set, use that as the final
322         // if both directions are set, use average
323         glm::vec3 final_disp(0.0f);
324         for (int axis = 0; axis < 3; ++axis) {
325                 if (std::abs(min_disp[axis]) > std::numeric_limits<float>::epsilon()) {
326                         if (std::abs(max_disp[axis]) > std::numeric_limits<float>::epsilon()) {
327                                 final_disp[axis] = (min_disp[axis] + max_disp[axis]) * 0.5f;
328                         } else {
329                                 final_disp[axis] = min_disp[axis];
330                         }
331                 } else if (std::abs(max_disp[axis]) > std::numeric_limits<float>::epsilon()) {
332                         final_disp[axis] = max_disp[axis];
333                 }
334         }
335         e.Move(final_disp);
336 }
337
338
339 void World::Render(Viewport &viewport) {
340         viewport.WorldPosition(player->Transform(player->ChunkCoords()));
341
342         BlockLighting &chunk_prog = viewport.ChunkProgram();
343         chunk_prog.SetTexture(block_tex);
344         chunk_prog.SetFogDensity(fog_density);
345
346         for (Chunk &chunk : chunks.Loaded()) {
347                 glm::mat4 m(chunk.Transform(player->ChunkCoords()));
348                 chunk_prog.SetM(m);
349                 glm::mat4 mvp(chunk_prog.GetVP() * m);
350                 if (!CullTest(Chunk::Bounds(), mvp)) {
351                         chunk.Draw();
352                 }
353         }
354
355         DirectionalLighting &entity_prog = viewport.EntityProgram();
356         entity_prog.SetLightDirection(light_direction);
357         entity_prog.SetFogDensity(fog_density);
358
359         for (Entity &entity : entities) {
360                 entity.Render(entity.ChunkTransform(player->ChunkCoords()), entity_prog);
361         }
362 }
363
364 }