]> git.localhorst.tv Git - blank.git/blob - src/world/World.cpp
adjust player index if entity is removed
[blank.git] / src / world / World.cpp
1 #include "World.hpp"
2
3 #include "EntityCollision.hpp"
4 #include "WorldCollision.hpp"
5 #include "../app/Assets.hpp"
6 #include "../graphics/Format.hpp"
7 #include "../graphics/Viewport.hpp"
8
9 #include <algorithm>
10 #include <limits>
11 #include <glm/gtx/io.hpp>
12 #include <glm/gtx/transform.hpp>
13
14
15 namespace blank {
16
17 World::World(const BlockTypeRegistry &types, const Config &config, const WorldSave &save)
18 : config(config)
19 , block_type(types)
20 , generate(config.gen)
21 , chunks(config.load, types, generate, save)
22 , players()
23 , entities()
24 , light_direction(config.light_direction)
25 , fog_density(config.fog_density) {
26         generate.Space(0);
27         generate.Light(13);
28         generate.Solids({ 1, 4, 7, 10 });
29 }
30
31
32 Entity *World::AddPlayer(const std::string &name) {
33         for (Entity *e : players) {
34                 if (e->Name() == name) {
35                         return nullptr;
36                 }
37         }
38         Entity &player = AddEntity();
39         player.Name(name);
40         // TODO: load from save file here
41         player.Bounds({ { -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f } });
42         player.WorldCollidable(true);
43         player.Position(config.spawn);
44         players.push_back(&player);
45         chunks.QueueSurrounding(player.ChunkCoords());
46         return &player;
47 }
48
49 Entity *World::AddPlayer(const std::string &name, std::uint32_t id) {
50         for (Entity *e : players) {
51                 if (e->Name() == name) {
52                         return nullptr;
53                 }
54         }
55         Entity *player = AddEntity(id);
56         if (!player) {
57                 return nullptr;
58         }
59         player->Name(name);
60         // TODO: load from save file here
61         player->Bounds({ { -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f } });
62         player->WorldCollidable(true);
63         player->Position(config.spawn);
64         players.push_back(player);
65         chunks.QueueSurrounding(player->ChunkCoords());
66         return player;
67 }
68
69 Entity &World::AddEntity() {
70         if (entities.empty()) {
71                 entities.emplace_back();
72                 entities.back().ID(1);
73                 return entities.back();
74         }
75         if (entities.back().ID() < std::numeric_limits<std::uint32_t>::max()) {
76                 std::uint32_t id = entities.back().ID() + 1;
77                 entities.emplace_back();
78                 entities.back().ID(id);
79                 return entities.back();
80         }
81         std::uint32_t id = 1;
82         auto position = entities.begin();
83         auto end = entities.end();
84         while (position != end && position->ID() == id) {
85                 ++id;
86                 ++position;
87         }
88         auto entity = entities.emplace(position);
89         entity->ID(id);
90         return *entity;
91 }
92
93 Entity *World::AddEntity(std::uint32_t id) {
94         if (entities.empty() || entities.back().ID() < id) {
95                 entities.emplace_back();
96                 entities.back().ID(id);
97                 return &entities.back();
98         }
99
100         auto position = entities.begin();
101         auto end = entities.end();
102         while (position != end && position->ID() < id) {
103                 ++position;
104         }
105         if (position != end && position->ID() == id) {
106                 return nullptr;
107         }
108         auto entity = entities.emplace(position);
109         entity->ID(id);
110         return &*entity;
111 }
112
113
114 namespace {
115
116 struct Candidate {
117         Chunk *chunk;
118         float dist;
119 };
120
121 bool CandidateLess(const Candidate &a, const Candidate &b) {
122         return a.dist < b.dist;
123 }
124
125 std::vector<Candidate> candidates;
126
127 }
128
129 bool World::Intersection(
130         const Ray &ray,
131         const glm::mat4 &M,
132         const Chunk::Pos &reference,
133         WorldCollision &coll
134 ) {
135         candidates.clear();
136
137         for (Chunk &cur_chunk : chunks.Loaded()) {
138                 float cur_dist;
139                 if (cur_chunk.Intersection(ray, M * cur_chunk.Transform(reference), cur_dist)) {
140                         candidates.push_back({ &cur_chunk, cur_dist });
141                 }
142         }
143
144         if (candidates.empty()) return false;
145
146         std::sort(candidates.begin(), candidates.end(), CandidateLess);
147
148         coll.chunk = nullptr;
149         coll.block = -1;
150         coll.depth = std::numeric_limits<float>::infinity();
151
152         for (Candidate &cand : candidates) {
153                 if (cand.dist > coll.depth) continue;
154                 WorldCollision cur_coll;
155                 if (cand.chunk->Intersection(ray, M * cand.chunk->Transform(reference), cur_coll)) {
156                         if (cur_coll.depth < coll.depth) {
157                                 coll = cur_coll;
158                         }
159                 }
160         }
161
162         return coll.chunk;
163 }
164
165 bool World::Intersection(
166         const Ray &ray,
167         const glm::mat4 &M,
168         const Entity &reference,
169         EntityCollision &coll
170 ) {
171         coll.entity = nullptr;
172         coll.depth = std::numeric_limits<float>::infinity();
173         for (Entity &cur_entity : entities) {
174                 if (&cur_entity == &reference) {
175                         continue;
176                 }
177                 float cur_dist;
178                 glm::vec3 cur_normal;
179                 if (blank::Intersection(ray, cur_entity.Bounds(), M * cur_entity.Transform(reference.ChunkCoords()), &cur_dist, &cur_normal)) {
180                         // TODO: fine grained check goes here? maybe?
181                         if (cur_dist < coll.depth) {
182                                 coll.entity = &cur_entity;
183                                 coll.depth = cur_dist;
184                                 coll.normal = cur_normal;
185                         }
186                 }
187         }
188
189         return coll.entity;
190 }
191
192 bool World::Intersection(const Entity &e, std::vector<WorldCollision> &col) {
193         AABB box = e.Bounds();
194         Chunk::Pos reference = e.ChunkCoords();
195         glm::mat4 M = e.Transform(reference);
196         bool any = false;
197         for (Chunk &cur_chunk : chunks.Loaded()) {
198                 if (manhattan_radius(cur_chunk.Position() - e.ChunkCoords()) > 1) {
199                         // chunk is not one of the 3x3x3 surrounding the entity
200                         // since there's no entity which can extent over 16 blocks, they can be skipped
201                         continue;
202                 }
203                 if (cur_chunk.Intersection(box, M, cur_chunk.Transform(reference), col)) {
204                         any = true;
205                 }
206         }
207         return any;
208 }
209
210
211 namespace {
212
213 std::vector<WorldCollision> col;
214
215 }
216
217 void World::Update(int dt) {
218         for (Entity &entity : entities) {
219                 entity.Update(dt);
220         }
221         for (Entity &entity : entities) {
222                 col.clear();
223                 if (entity.WorldCollidable() && Intersection(entity, col)) {
224                         // entity collides with the world
225                         Resolve(entity, col);
226                 }
227         }
228         for (auto iter = entities.begin(), end = entities.end(); iter != end;) {
229                 if (iter->CanRemove()) {
230                         iter = RemoveEntity(iter);
231                 } else {
232                         ++iter;
233                 }
234         }
235         // TODO: make flexible
236         chunks.Rebase(players[0]->ChunkCoords());
237         chunks.Update(dt);
238 }
239
240 void World::Resolve(Entity &e, std::vector<WorldCollision> &col) {
241         // determine displacement for each cardinal axis and move entity accordingly
242         glm::vec3 min_disp(0.0f);
243         glm::vec3 max_disp(0.0f);
244         for (const WorldCollision &c : col) {
245                 if (!c.Blocks()) continue;
246                 glm::vec3 local_disp(c.normal * c.depth);
247                 // swap if neccessary (normal may point away from the entity)
248                 if (dot(c.normal, e.Position() - c.BlockCoords()) < 0) {
249                         local_disp *= -1;
250                 }
251                 min_disp = min(min_disp, local_disp);
252                 max_disp = max(max_disp, local_disp);
253         }
254         // for each axis
255         // if only one direction is set, use that as the final
256         // if both directions are set, use average
257         glm::vec3 final_disp(0.0f);
258         for (int axis = 0; axis < 3; ++axis) {
259                 if (std::abs(min_disp[axis]) > std::numeric_limits<float>::epsilon()) {
260                         if (std::abs(max_disp[axis]) > std::numeric_limits<float>::epsilon()) {
261                                 final_disp[axis] = (min_disp[axis] + max_disp[axis]) * 0.5f;
262                         } else {
263                                 final_disp[axis] = min_disp[axis];
264                         }
265                 } else if (std::abs(max_disp[axis]) > std::numeric_limits<float>::epsilon()) {
266                         final_disp[axis] = max_disp[axis];
267                 }
268         }
269         e.Move(final_disp);
270 }
271
272 World::EntityHandle World::RemoveEntity(EntityHandle &eh) {
273         // check for player
274         auto player = std::find(players.begin(), players.end(), &*eh);
275         if (player != players.end()) {
276                 players.erase(player);
277         }
278         return entities.erase(eh);
279 }
280
281
282 void World::Render(Viewport &viewport) {
283         DirectionalLighting &entity_prog = viewport.EntityProgram();
284         entity_prog.SetLightDirection(light_direction);
285         entity_prog.SetFogDensity(fog_density);
286
287         for (Entity &entity : entities) {
288                 entity.Render(entity.ChunkTransform(players[0]->ChunkCoords()), entity_prog);
289         }
290 }
291
292 }