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