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