]> git.localhorst.tv Git - blank.git/blob - src/world/world.cpp
ee5bf02c191ffcf3be293d0a4acda9b31289f751
[blank.git] / src / world / world.cpp
1 #include "Entity.hpp"
2 #include "EntityController.hpp"
3 #include "EntityDerivative.hpp"
4 #include "EntityState.hpp"
5 #include "Player.hpp"
6 #include "World.hpp"
7
8 #include "ChunkIndex.hpp"
9 #include "EntityCollision.hpp"
10 #include "WorldCollision.hpp"
11 #include "../app/Assets.hpp"
12 #include "../graphics/Format.hpp"
13 #include "../graphics/Viewport.hpp"
14
15 #include <algorithm>
16 #include <cmath>
17 #include <iostream>
18 #include <limits>
19 #include <glm/gtx/io.hpp>
20 #include <glm/gtx/quaternion.hpp>
21 #include <glm/gtx/transform.hpp>
22
23
24 namespace blank {
25
26 Entity::Entity() noexcept
27 : ctrl(nullptr)
28 , model()
29 , id(-1)
30 , name("anonymous")
31 , bounds()
32 , state()
33 , max_vel(5.0f)
34 , max_force(25.0f)
35 , ref_count(0)
36 , world_collision(false)
37 , dead(false)
38 , owns_controller(false) {
39
40 }
41
42 Entity::~Entity() noexcept {
43         UnsetController();
44 }
45
46 Entity::Entity(const Entity &other) noexcept
47 : ctrl(other.ctrl)
48 , model(other.model)
49 , id(-1)
50 , name(other.name)
51 , bounds(other.bounds)
52 , state(other.state)
53 , max_vel(other.max_vel)
54 , max_force(other.max_force)
55 , ref_count(0)
56 , world_collision(other.world_collision)
57 , dead(other.dead)
58 , owns_controller(false) {
59
60 }
61
62 void Entity::SetController(EntityController *c) noexcept {
63         UnsetController();
64         ctrl = c;
65         owns_controller = true;
66 }
67
68 void Entity::SetController(EntityController &c) noexcept {
69         UnsetController();
70         ctrl = &c;
71         owns_controller = false;
72 }
73
74 void Entity::UnsetController() noexcept {
75         if (ctrl && owns_controller) {
76                 delete ctrl;
77         }
78         ctrl = nullptr;
79 }
80
81 glm::vec3 Entity::ControlForce(const EntityState &s) const noexcept {
82         if (HasController()) {
83                 return GetController().ControlForce(s);
84         } else {
85                 return -s.velocity;
86         }
87 }
88
89 void Entity::Position(const glm::ivec3 &c, const glm::vec3 &b) noexcept {
90         state.chunk_pos = c;
91         state.block_pos = b;
92 }
93
94 void Entity::Position(const glm::vec3 &pos) noexcept {
95         state.block_pos = pos;
96         state.AdjustPosition();
97 }
98
99 void Entity::TurnHead(float dp, float dy) noexcept {
100         SetHead(state.pitch + dp, state.yaw + dy);
101 }
102
103 void Entity::SetHead(float p, float y) noexcept {
104         state.pitch = p;
105         state.yaw = y;
106         // TODO: I feel like this could be delayed
107         UpdateModel();
108 }
109
110 glm::mat4 Entity::Transform(const glm::ivec3 &reference) const noexcept {
111         return state.Transform(reference);
112 }
113
114 glm::mat4 Entity::ViewTransform(const glm::ivec3 &reference) const noexcept {
115         glm::mat4 transform = Transform(reference);
116         if (model) {
117                 transform *= model.EyesTransform();
118         }
119         return transform;
120 }
121
122 Ray Entity::Aim(const Chunk::Pos &chunk_offset) const noexcept {
123         glm::mat4 transform = ViewTransform(chunk_offset);
124         glm::vec4 from = transform * glm::vec4(0.0f, 0.0f, 0.0f, 1.0f);
125         from /= from.w;
126         glm::vec4 to = transform * glm::vec4(0.0f, 0.0f, -1.0f, 1.0f);
127         to /= to.w;
128         return Ray{ glm::vec3(from), glm::normalize(glm::vec3(to - from)) };
129 }
130
131 void Entity::UpdateModel() noexcept {
132         state.AdjustHeading();
133         if (model) {
134                 Part::State &body_state = model.BodyState();
135                 Part::State &eyes_state = model.EyesState();
136                 if (&body_state != &eyes_state) {
137                         body_state.orientation = glm::quat(glm::vec3(0.0f, state.yaw, 0.0f));
138                         eyes_state.orientation = glm::quat(glm::vec3(state.pitch, 0.0f, 0.0f));
139                 } else {
140                         eyes_state.orientation = glm::quat(glm::vec3(state.pitch, state.yaw, 0.0f));
141                 }
142         }
143 }
144
145 void Entity::Update(float dt) {
146         if (HasController()) {
147                 GetController().Update(*this, dt);
148         }
149 }
150
151
152 EntityController::~EntityController() {
153
154 }
155
156
157 EntityState::EntityState()
158 : chunk_pos(0)
159 , block_pos(0.0f)
160 , velocity(0.0f)
161 , orient(1.0f, 0.0f, 0.0f, 0.0f)
162 , pitch(0.0f)
163 , yaw(0.0f) {
164
165 }
166
167 void EntityState::AdjustPosition() noexcept {
168         while (block_pos.x >= Chunk::width) {
169                 block_pos.x -= Chunk::width;
170                 ++chunk_pos.x;
171         }
172         while (block_pos.x < 0) {
173                 block_pos.x += Chunk::width;
174                 --chunk_pos.x;
175         }
176         while (block_pos.y >= Chunk::height) {
177                 block_pos.y -= Chunk::height;
178                 ++chunk_pos.y;
179         }
180         while (block_pos.y < 0) {
181                 block_pos.y += Chunk::height;
182                 --chunk_pos.y;
183         }
184         while (block_pos.z >= Chunk::depth) {
185                 block_pos.z -= Chunk::depth;
186                 ++chunk_pos.z;
187         }
188         while (block_pos.z < 0) {
189                 block_pos.z += Chunk::depth;
190                 --chunk_pos.z;
191         }
192 }
193
194 void EntityState::AdjustHeading() noexcept {
195         while (pitch > PI / 2) {
196                 pitch = PI / 2;
197         }
198         while (pitch < -PI / 2) {
199                 pitch = -PI / 2;
200         }
201         while (yaw > PI) {
202                 yaw -= PI * 2;
203         }
204         while (yaw < -PI) {
205                 yaw += PI * 2;
206         }
207 }
208
209 glm::mat4 EntityState::Transform(const glm::ivec3 &reference) const noexcept {
210         const glm::vec3 translation = RelativePosition(reference);
211         glm::mat4 transform(toMat4(orient));
212         transform[3].x = translation.x;
213         transform[3].y = translation.y;
214         transform[3].z = translation.z;
215         return transform;
216 }
217
218
219 Player::Player(Entity &e, ChunkIndex &c)
220 : entity(e)
221 , chunks(c)
222 , inv_slot(0) {
223
224 }
225
226 Player::~Player() {
227
228 }
229
230 bool Player::SuitableSpawn(BlockLookup &spawn_block) const noexcept {
231         if (!spawn_block || spawn_block.GetType().collide_block) {
232                 return false;
233         }
234
235         BlockLookup head_block(spawn_block.Next(Block::FACE_UP));
236         if (!head_block || head_block.GetType().collide_block) {
237                 return false;
238         }
239
240         return true;
241 }
242
243 void Player::Update(int dt) {
244         chunks.Rebase(entity.ChunkCoords());
245 }
246
247
248 World::World(const BlockTypeRegistry &types, const Config &config)
249 : config(config)
250 , block_type(types)
251 , chunks(types)
252 , players()
253 , entities()
254 , light_direction(config.light_direction)
255 , fog_density(config.fog_density) {
256
257 }
258
259 World::~World() {
260
261 }
262
263
264 Player *World::AddPlayer(const std::string &name) {
265         for (Player &p : players) {
266                 if (p.Name() == name) {
267                         return nullptr;
268                 }
269         }
270         Entity &entity = AddEntity();
271         entity.Name(name);
272         entity.Bounds({ { -0.4f, -0.9f, -0.4f }, { 0.4f, 0.9f, 0.4f } });
273         entity.WorldCollidable(true);
274         ChunkIndex &index = chunks.MakeIndex(entity.ChunkCoords(), 6);
275         players.emplace_back(entity, index);
276         return &players.back();
277 }
278
279 Player *World::AddPlayer(const std::string &name, std::uint32_t id) {
280         for (Player &p : players) {
281                 if (p.Name() == name) {
282                         return nullptr;
283                 }
284         }
285         Entity *entity = AddEntity(id);
286         if (!entity) {
287                 return nullptr;
288         }
289         entity->Name(name);
290         entity->Bounds({ { -0.4f, -0.9f, -0.4f }, { 0.4f, 0.9f, 0.4f } });
291         entity->WorldCollidable(true);
292         ChunkIndex &index = chunks.MakeIndex(entity->ChunkCoords(), 6);
293         players.emplace_back(*entity, index);
294         return &players.back();
295 }
296
297 Entity &World::AddEntity() {
298         if (entities.empty()) {
299                 entities.emplace_back();
300                 entities.back().ID(1);
301                 return entities.back();
302         }
303         if (entities.back().ID() < std::numeric_limits<std::uint32_t>::max()) {
304                 std::uint32_t id = entities.back().ID() + 1;
305                 entities.emplace_back();
306                 entities.back().ID(id);
307                 return entities.back();
308         }
309         std::uint32_t id = 1;
310         auto position = entities.begin();
311         auto end = entities.end();
312         while (position != end && position->ID() == id) {
313                 ++id;
314                 ++position;
315         }
316         auto entity = entities.emplace(position);
317         entity->ID(id);
318         return *entity;
319 }
320
321 Entity *World::AddEntity(std::uint32_t id) {
322         if (entities.empty() || entities.back().ID() < id) {
323                 entities.emplace_back();
324                 entities.back().ID(id);
325                 return &entities.back();
326         }
327
328         auto position = entities.begin();
329         auto end = entities.end();
330         while (position != end && position->ID() < id) {
331                 ++position;
332         }
333         if (position != end && position->ID() == id) {
334                 return nullptr;
335         }
336         auto entity = entities.emplace(position);
337         entity->ID(id);
338         return &*entity;
339 }
340
341 Entity &World::ForceAddEntity(std::uint32_t id) {
342         if (entities.empty() || entities.back().ID() < id) {
343                 entities.emplace_back();
344                 entities.back().ID(id);
345                 return entities.back();
346         }
347
348         auto position = entities.begin();
349         auto end = entities.end();
350         while (position != end && position->ID() < id) {
351                 ++position;
352         }
353         if (position != end && position->ID() == id) {
354                 return *position;
355         }
356         auto entity = entities.emplace(position);
357         entity->ID(id);
358         return *entity;
359 }
360
361
362 namespace {
363
364 struct Candidate {
365         Chunk *chunk;
366         float dist;
367 };
368
369 bool CandidateLess(const Candidate &a, const Candidate &b) {
370         return a.dist < b.dist;
371 }
372
373 std::vector<Candidate> candidates;
374
375 }
376
377 bool World::Intersection(
378         const Ray &ray,
379         const glm::mat4 &M,
380         const Chunk::Pos &reference,
381         WorldCollision &coll
382 ) {
383         candidates.clear();
384
385         for (Chunk &cur_chunk : chunks) {
386                 float cur_dist;
387                 if (cur_chunk.Intersection(ray, M * cur_chunk.Transform(reference), cur_dist)) {
388                         candidates.push_back({ &cur_chunk, cur_dist });
389                 }
390         }
391
392         if (candidates.empty()) return false;
393
394         std::sort(candidates.begin(), candidates.end(), CandidateLess);
395
396         coll.chunk = nullptr;
397         coll.block = -1;
398         coll.depth = std::numeric_limits<float>::infinity();
399
400         for (Candidate &cand : candidates) {
401                 if (cand.dist > coll.depth) continue;
402                 WorldCollision cur_coll;
403                 if (cand.chunk->Intersection(ray, M * cand.chunk->Transform(reference), cur_coll)) {
404                         if (cur_coll.depth < coll.depth) {
405                                 coll = cur_coll;
406                         }
407                 }
408         }
409
410         return coll.chunk;
411 }
412
413 bool World::Intersection(
414         const Ray &ray,
415         const glm::mat4 &M,
416         const Entity &reference,
417         EntityCollision &coll
418 ) {
419         coll.entity = nullptr;
420         coll.depth = std::numeric_limits<float>::infinity();
421         for (Entity &cur_entity : entities) {
422                 if (&cur_entity == &reference) {
423                         continue;
424                 }
425                 float cur_dist;
426                 glm::vec3 cur_normal;
427                 if (blank::Intersection(ray, cur_entity.Bounds(), M * cur_entity.Transform(reference.ChunkCoords()), &cur_dist, &cur_normal)) {
428                         // TODO: fine grained check goes here? maybe?
429                         if (cur_dist < coll.depth) {
430                                 coll.entity = &cur_entity;
431                                 coll.depth = cur_dist;
432                                 coll.normal = cur_normal;
433                         }
434                 }
435         }
436
437         return coll.entity;
438 }
439
440 bool World::Intersection(const Entity &e, const EntityState &s, std::vector<WorldCollision> &col) {
441         AABB box = e.Bounds();
442         Chunk::Pos reference = s.chunk_pos;
443         glm::mat4 M = s.Transform(reference);
444         bool any = false;
445         for (Chunk &cur_chunk : chunks) {
446                 if (manhattan_radius(cur_chunk.Position() - reference) > 1) {
447                         // chunk is not one of the 3x3x3 surrounding the entity
448                         // since there's no entity which can extent over 16 blocks, they can be skipped
449                         continue;
450                 }
451                 if (cur_chunk.Intersection(box, M, cur_chunk.Transform(reference), col)) {
452                         any = true;
453                 }
454         }
455         return any;
456 }
457
458
459 void World::Update(int dt) {
460         float fdt(dt * 0.001f);
461         for (Entity &entity : entities) {
462                 entity.Update(fdt);
463         }
464         for (Entity &entity : entities) {
465                 Update(entity, fdt);
466         }
467         for (Player &player : players) {
468                 player.Update(dt);
469         }
470         for (auto iter = entities.begin(), end = entities.end(); iter != end;) {
471                 if (iter->CanRemove()) {
472                         iter = RemoveEntity(iter);
473                 } else {
474                         ++iter;
475                 }
476         }
477 }
478
479 void World::Update(Entity &entity, float dt) {
480         EntityState state(entity.GetState());
481
482         EntityDerivative a(CalculateStep(entity, state, 0.0f, EntityDerivative()));
483         EntityDerivative b(CalculateStep(entity, state, dt * 0.5f, a));
484         EntityDerivative c(CalculateStep(entity, state, dt * 0.5f, b));
485         EntityDerivative d(CalculateStep(entity, state, dt, c));
486
487         EntityDerivative f;
488         constexpr float sixth = 1.0f / 6.0f;
489         f.position = sixth * ((a.position + 2.0f * (b.position + c.position)) + d.position);
490         f.velocity = sixth * ((a.velocity + 2.0f * (b.velocity + c.velocity)) + d.velocity);
491
492         state.block_pos += f.position * dt;
493         state.velocity += f.velocity * dt;
494         state.AdjustPosition();
495
496         entity.SetState(state);
497 }
498
499 EntityDerivative World::CalculateStep(
500         const Entity &entity,
501         const EntityState &cur,
502         float dt,
503         const EntityDerivative &delta
504 ) {
505         EntityState next(cur);
506         next.block_pos += delta.position * dt;
507         next.velocity += delta.velocity * dt;
508         next.AdjustPosition();
509
510         if (dot(next.velocity, next.velocity) > entity.MaxVelocity() * entity.MaxVelocity()) {
511                 next.velocity = normalize(next.velocity) * entity.MaxVelocity();
512         }
513
514         EntityDerivative out;
515         out.position = next.velocity;
516         out.velocity = CalculateForce(entity, next); // by mass = 1kg
517         return out;
518 }
519
520 glm::vec3 World::CalculateForce(
521         const Entity &entity,
522         const EntityState &state
523 ) {
524         glm::vec3 force(ControlForce(entity, state) + CollisionForce(entity, state) + Gravity(entity, state));
525         if (dot(force, force) > entity.MaxControlForce() * entity.MaxControlForce()) {
526                 return normalize(force) * entity.MaxControlForce();
527         } else {
528                 return force;
529         }
530 }
531
532 glm::vec3 World::ControlForce(
533         const Entity &entity,
534         const EntityState &state
535 ) {
536         return entity.ControlForce(state);
537 }
538
539 namespace {
540
541 std::vector<WorldCollision> col;
542
543 }
544
545 glm::vec3 World::CollisionForce(
546         const Entity &entity,
547         const EntityState &state
548 ) {
549         col.clear();
550         if (entity.WorldCollidable() && Intersection(entity, state, col)) {
551                 // determine displacement for each cardinal axis and move entity accordingly
552                 glm::vec3 min_pen(0.0f);
553                 glm::vec3 max_pen(0.0f);
554                 for (const WorldCollision &c : col) {
555                         if (!c.Blocks()) continue;
556                         glm::vec3 local_pen(c.normal * c.depth);
557                         // swap if neccessary (normal may point away from the entity)
558                         if (dot(c.normal, state.RelativePosition(c.ChunkPos()) - c.BlockCoords()) > 0) {
559                                 local_pen *= -1;
560                         }
561                         min_pen = min(min_pen, local_pen);
562                         max_pen = max(max_pen, local_pen);
563                 }
564                 glm::vec3 correction(0.0f);
565                 // only apply correction for axes where penetration is only in one direction
566                 for (std::size_t i = 0; i < 3; ++i) {
567                         if (min_pen[i] < -std::numeric_limits<float>::epsilon()) {
568                                 if (max_pen[i] < std::numeric_limits<float>::epsilon()) {
569                                         correction[i] = -min_pen[i];
570                                 }
571                         } else {
572                                 correction[i] = -max_pen[i];
573                         }
574                 }
575                 // correction may be zero in which case normalize() returns NaNs
576                 if (dot(correction, correction) < std::numeric_limits<float>::epsilon()) {
577                         return glm::vec3(0.0f);
578                 }
579                 glm::vec3 normal(normalize(correction));
580                 glm::vec3 normal_velocity(normal * dot(state.velocity, normal));
581                 // apply force proportional to penetration
582                 // use velocity projected onto normal as damper
583                 constexpr float k = 1000.0f; // spring constant
584                 constexpr float b = 10.0f; // damper constant
585                 const glm::vec3 x(-correction); // endpoint displacement from equilibrium in m
586                 const glm::vec3 v(normal_velocity); // relative velocity between endpoints in m/s
587                 return (((-k) * x) - (b * v)); // times 1kg/s, in kg*m/s²
588         } else {
589                 return glm::vec3(0.0f);
590         }
591 }
592
593 glm::vec3 World::Gravity(
594         const Entity &entity,
595         const EntityState &state
596 ) {
597         return glm::vec3(0.0f);
598 }
599
600 World::EntityHandle World::RemoveEntity(EntityHandle &eh) {
601         // check for player
602         for (auto player = players.begin(), end = players.end(); player != end;) {
603                 if (&player->GetEntity() == &*eh) {
604                         chunks.UnregisterIndex(player->GetChunks());
605                         player = players.erase(player);
606                         end = players.end();
607                 } else {
608                         ++player;
609                 }
610         }
611         return entities.erase(eh);
612 }
613
614
615 void World::Render(Viewport &viewport) {
616         DirectionalLighting &entity_prog = viewport.EntityProgram();
617         entity_prog.SetLightDirection(light_direction);
618         entity_prog.SetFogDensity(fog_density);
619
620         for (Entity &entity : entities) {
621                 entity.Render(entity.Transform(players.front().GetEntity().ChunkCoords()), entity_prog);
622         }
623 }
624
625 }