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