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