]> git.localhorst.tv Git - blank.git/blob - src/world/world.cpp
e4fb968ddfbb8014004a2b7a6a7647be53b2c2a5
[blank.git] / src / world / world.cpp
1 #include "Entity.hpp"
2 #include "EntityState.hpp"
3 #include "Player.hpp"
4 #include "World.hpp"
5
6 #include "ChunkIndex.hpp"
7 #include "EntityCollision.hpp"
8 #include "WorldCollision.hpp"
9 #include "../app/Assets.hpp"
10 #include "../graphics/Format.hpp"
11 #include "../graphics/Viewport.hpp"
12
13 #include <algorithm>
14 #include <cmath>
15 #include <limits>
16 #include <glm/gtx/io.hpp>
17 #include <glm/gtx/quaternion.hpp>
18 #include <glm/gtx/transform.hpp>
19
20
21 namespace blank {
22
23 Entity::Entity() noexcept
24 : model()
25 , id(-1)
26 , name("anonymous")
27 , bounds()
28 , state()
29 , ref_count(0)
30 , world_collision(false)
31 , dead(false) {
32
33 }
34
35
36 void Entity::Position(const glm::ivec3 &c, const glm::vec3 &b) noexcept {
37         state.chunk_pos = c;
38         state.block_pos = b;
39 }
40
41 void Entity::Position(const glm::vec3 &pos) noexcept {
42         state.block_pos = pos;
43         state.AdjustPosition();
44 }
45
46 Ray Entity::Aim(const Chunk::Pos &chunk_offset) const noexcept {
47         glm::mat4 transform = Transform(chunk_offset);
48         glm::vec4 from = transform * glm::vec4(0.0f, 0.0f, 0.0f, 1.0f);
49         from /= from.w;
50         glm::vec4 to = transform * glm::vec4(0.0f, 0.0f, -1.0f, 1.0f);
51         to /= to.w;
52         return Ray{ glm::vec3(from), glm::normalize(glm::vec3(to - from)) };
53 }
54
55 namespace {
56
57 glm::quat delta_rot(const glm::vec3 &av, float dt) {
58         glm::vec3 half(av * dt * 0.5f);
59         float mag = length(half);
60         if (mag > 0.0f) {
61                 float smag = std::sin(mag) / mag;
62                 return glm::quat(std::cos(mag), half * smag);
63         } else {
64                 return glm::quat(1.0f, 0.0f, 0.0f, 0.0f);
65         }
66 }
67
68 }
69
70 void Entity::Update(int dt) noexcept {
71         state.Update(dt);
72 }
73
74
75 EntityState::EntityState()
76 : chunk_pos(0)
77 , block_pos(0.0f)
78 , velocity(0.0f)
79 , orient(1.0f, 0.0f, 0.0f, 0.0f)
80 , ang_vel(0.0f) {
81
82 }
83
84 void EntityState::Update(int dt) noexcept {
85         float fdt = float(dt);
86         block_pos += velocity * fdt;
87         orient = delta_rot(ang_vel, fdt) * orient;
88         AdjustPosition();
89 }
90
91 void EntityState::AdjustPosition() noexcept {
92         while (block_pos.x >= Chunk::width) {
93                 block_pos.x -= Chunk::width;
94                 ++chunk_pos.x;
95         }
96         while (block_pos.x < 0) {
97                 block_pos.x += Chunk::width;
98                 --chunk_pos.x;
99         }
100         while (block_pos.y >= Chunk::height) {
101                 block_pos.y -= Chunk::height;
102                 ++chunk_pos.y;
103         }
104         while (block_pos.y < 0) {
105                 block_pos.y += Chunk::height;
106                 --chunk_pos.y;
107         }
108         while (block_pos.z >= Chunk::depth) {
109                 block_pos.z -= Chunk::depth;
110                 ++chunk_pos.z;
111         }
112         while (block_pos.z < 0) {
113                 block_pos.z += Chunk::depth;
114                 --chunk_pos.z;
115         }
116 }
117
118 glm::mat4 EntityState::Transform(const glm::ivec3 &reference) const noexcept {
119         const glm::vec3 translation = RelativePosition(reference);
120         glm::mat4 transform(toMat4(orient));
121         transform[3].x = translation.x;
122         transform[3].y = translation.y;
123         transform[3].z = translation.z;
124         return transform;
125 }
126
127
128 Player::Player(Entity &e, ChunkIndex &c)
129 : entity(e)
130 , chunks(c)
131 , inv_slot(0) {
132
133 }
134
135 Player::~Player() {
136
137 }
138
139 bool Player::SuitableSpawn(BlockLookup &spawn_block) const noexcept {
140         if (!spawn_block || spawn_block.GetType().collide_block) {
141                 return false;
142         }
143
144         BlockLookup head_block(spawn_block.Next(Block::FACE_UP));
145         if (!head_block || head_block.GetType().collide_block) {
146                 return false;
147         }
148
149         return true;
150 }
151
152 void Player::Update(int dt) {
153         chunks.Rebase(entity.ChunkCoords());
154 }
155
156
157 World::World(const BlockTypeRegistry &types, const Config &config)
158 : config(config)
159 , block_type(types)
160 , chunks(types)
161 , players()
162 , entities()
163 , light_direction(config.light_direction)
164 , fog_density(config.fog_density) {
165
166 }
167
168 World::~World() {
169
170 }
171
172
173 Player *World::AddPlayer(const std::string &name) {
174         for (Player &p : players) {
175                 if (p.Name() == name) {
176                         return nullptr;
177                 }
178         }
179         Entity &entity = AddEntity();
180         entity.Name(name);
181         entity.Bounds({ { -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f } });
182         entity.WorldCollidable(true);
183         ChunkIndex &index = chunks.MakeIndex(entity.ChunkCoords(), 6);
184         players.emplace_back(entity, index);
185         return &players.back();
186 }
187
188 Player *World::AddPlayer(const std::string &name, std::uint32_t id) {
189         for (Player &p : players) {
190                 if (p.Name() == name) {
191                         return nullptr;
192                 }
193         }
194         Entity *entity = AddEntity(id);
195         if (!entity) {
196                 return nullptr;
197         }
198         entity->Name(name);
199         entity->Bounds({ { -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f } });
200         entity->WorldCollidable(true);
201         ChunkIndex &index = chunks.MakeIndex(entity->ChunkCoords(), 6);
202         players.emplace_back(*entity, index);
203         return &players.back();
204 }
205
206 Entity &World::AddEntity() {
207         if (entities.empty()) {
208                 entities.emplace_back();
209                 entities.back().ID(1);
210                 return entities.back();
211         }
212         if (entities.back().ID() < std::numeric_limits<std::uint32_t>::max()) {
213                 std::uint32_t id = entities.back().ID() + 1;
214                 entities.emplace_back();
215                 entities.back().ID(id);
216                 return entities.back();
217         }
218         std::uint32_t id = 1;
219         auto position = entities.begin();
220         auto end = entities.end();
221         while (position != end && position->ID() == id) {
222                 ++id;
223                 ++position;
224         }
225         auto entity = entities.emplace(position);
226         entity->ID(id);
227         return *entity;
228 }
229
230 Entity *World::AddEntity(std::uint32_t id) {
231         if (entities.empty() || entities.back().ID() < id) {
232                 entities.emplace_back();
233                 entities.back().ID(id);
234                 return &entities.back();
235         }
236
237         auto position = entities.begin();
238         auto end = entities.end();
239         while (position != end && position->ID() < id) {
240                 ++position;
241         }
242         if (position != end && position->ID() == id) {
243                 return nullptr;
244         }
245         auto entity = entities.emplace(position);
246         entity->ID(id);
247         return &*entity;
248 }
249
250 Entity &World::ForceAddEntity(std::uint32_t id) {
251         if (entities.empty() || entities.back().ID() < id) {
252                 entities.emplace_back();
253                 entities.back().ID(id);
254                 return entities.back();
255         }
256
257         auto position = entities.begin();
258         auto end = entities.end();
259         while (position != end && position->ID() < id) {
260                 ++position;
261         }
262         if (position != end && position->ID() == id) {
263                 return *position;
264         }
265         auto entity = entities.emplace(position);
266         entity->ID(id);
267         return *entity;
268 }
269
270
271 namespace {
272
273 struct Candidate {
274         Chunk *chunk;
275         float dist;
276 };
277
278 bool CandidateLess(const Candidate &a, const Candidate &b) {
279         return a.dist < b.dist;
280 }
281
282 std::vector<Candidate> candidates;
283
284 }
285
286 bool World::Intersection(
287         const Ray &ray,
288         const glm::mat4 &M,
289         const Chunk::Pos &reference,
290         WorldCollision &coll
291 ) {
292         candidates.clear();
293
294         for (Chunk &cur_chunk : chunks) {
295                 float cur_dist;
296                 if (cur_chunk.Intersection(ray, M * cur_chunk.Transform(reference), cur_dist)) {
297                         candidates.push_back({ &cur_chunk, cur_dist });
298                 }
299         }
300
301         if (candidates.empty()) return false;
302
303         std::sort(candidates.begin(), candidates.end(), CandidateLess);
304
305         coll.chunk = nullptr;
306         coll.block = -1;
307         coll.depth = std::numeric_limits<float>::infinity();
308
309         for (Candidate &cand : candidates) {
310                 if (cand.dist > coll.depth) continue;
311                 WorldCollision cur_coll;
312                 if (cand.chunk->Intersection(ray, M * cand.chunk->Transform(reference), cur_coll)) {
313                         if (cur_coll.depth < coll.depth) {
314                                 coll = cur_coll;
315                         }
316                 }
317         }
318
319         return coll.chunk;
320 }
321
322 bool World::Intersection(
323         const Ray &ray,
324         const glm::mat4 &M,
325         const Entity &reference,
326         EntityCollision &coll
327 ) {
328         coll.entity = nullptr;
329         coll.depth = std::numeric_limits<float>::infinity();
330         for (Entity &cur_entity : entities) {
331                 if (&cur_entity == &reference) {
332                         continue;
333                 }
334                 float cur_dist;
335                 glm::vec3 cur_normal;
336                 if (blank::Intersection(ray, cur_entity.Bounds(), M * cur_entity.Transform(reference.ChunkCoords()), &cur_dist, &cur_normal)) {
337                         // TODO: fine grained check goes here? maybe?
338                         if (cur_dist < coll.depth) {
339                                 coll.entity = &cur_entity;
340                                 coll.depth = cur_dist;
341                                 coll.normal = cur_normal;
342                         }
343                 }
344         }
345
346         return coll.entity;
347 }
348
349 bool World::Intersection(const Entity &e, std::vector<WorldCollision> &col) {
350         AABB box = e.Bounds();
351         Chunk::Pos reference = e.ChunkCoords();
352         glm::mat4 M = e.Transform(reference);
353         bool any = false;
354         for (Chunk &cur_chunk : chunks) {
355                 if (manhattan_radius(cur_chunk.Position() - e.ChunkCoords()) > 1) {
356                         // chunk is not one of the 3x3x3 surrounding the entity
357                         // since there's no entity which can extent over 16 blocks, they can be skipped
358                         continue;
359                 }
360                 if (cur_chunk.Intersection(box, M, cur_chunk.Transform(reference), col)) {
361                         any = true;
362                 }
363         }
364         return any;
365 }
366
367
368 namespace {
369
370 std::vector<WorldCollision> col;
371
372 }
373
374 void World::Update(int dt) {
375         for (Entity &entity : entities) {
376                 entity.Update(dt);
377         }
378         for (Entity &entity : entities) {
379                 col.clear();
380                 if (entity.WorldCollidable() && Intersection(entity, col)) {
381                         // entity collides with the world
382                         Resolve(entity, col);
383                 }
384         }
385         for (Player &player : players) {
386                 player.Update(dt);
387         }
388         for (auto iter = entities.begin(), end = entities.end(); iter != end;) {
389                 if (iter->CanRemove()) {
390                         iter = RemoveEntity(iter);
391                 } else {
392                         ++iter;
393                 }
394         }
395 }
396
397 void World::Resolve(Entity &e, std::vector<WorldCollision> &col) {
398         // determine displacement for each cardinal axis and move entity accordingly
399         glm::vec3 min_disp(0.0f);
400         glm::vec3 max_disp(0.0f);
401         for (const WorldCollision &c : col) {
402                 if (!c.Blocks()) continue;
403                 glm::vec3 local_disp(c.normal * c.depth);
404                 // swap if neccessary (normal may point away from the entity)
405                 if (dot(c.normal, e.Position() - c.BlockCoords()) < 0) {
406                         local_disp *= -1;
407                 }
408                 min_disp = min(min_disp, local_disp);
409                 max_disp = max(max_disp, local_disp);
410         }
411         // for each axis
412         // if only one direction is set, use that as the final
413         // if both directions are set, use average
414         glm::vec3 final_disp(0.0f);
415         for (int axis = 0; axis < 3; ++axis) {
416                 if (std::abs(min_disp[axis]) > std::numeric_limits<float>::epsilon()) {
417                         if (std::abs(max_disp[axis]) > std::numeric_limits<float>::epsilon()) {
418                                 final_disp[axis] = (min_disp[axis] + max_disp[axis]) * 0.5f;
419                         } else {
420                                 final_disp[axis] = min_disp[axis];
421                         }
422                 } else if (std::abs(max_disp[axis]) > std::numeric_limits<float>::epsilon()) {
423                         final_disp[axis] = max_disp[axis];
424                 }
425         }
426         e.Position(e.Position() + final_disp);
427 }
428
429 World::EntityHandle World::RemoveEntity(EntityHandle &eh) {
430         // check for player
431         for (auto player = players.begin(), end = players.end(); player != end;) {
432                 if (&player->GetEntity() == &*eh) {
433                         chunks.UnregisterIndex(player->GetChunks());
434                         player = players.erase(player);
435                         end = players.end();
436                 } else {
437                         ++player;
438                 }
439         }
440         return entities.erase(eh);
441 }
442
443
444 void World::Render(Viewport &viewport) {
445         DirectionalLighting &entity_prog = viewport.EntityProgram();
446         entity_prog.SetLightDirection(light_direction);
447         entity_prog.SetFogDensity(fog_density);
448
449         for (Entity &entity : entities) {
450                 entity.Render(entity.Transform(players.front().GetEntity().ChunkCoords()), entity_prog);
451         }
452 }
453
454 }