]> git.localhorst.tv Git - blank.git/blob - src/world/world.cpp
centralize entity controllers
[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         EntityDerivative out;
511         out.position = next.velocity;
512         out.velocity = CalculateForce(entity, next); // by mass = 1kg
513         return out;
514 }
515
516 glm::vec3 World::CalculateForce(
517         const Entity &entity,
518         const EntityState &state
519 ) {
520         return ControlForce(entity, state) + CollisionForce(entity, state) + Gravity(entity, state);
521 }
522
523 glm::vec3 World::ControlForce(
524         const Entity &entity,
525         const EntityState &state
526 ) {
527         return entity.ControlForce(state);
528 }
529
530 namespace {
531
532 std::vector<WorldCollision> col;
533
534 }
535
536 glm::vec3 World::CollisionForce(
537         const Entity &entity,
538         const EntityState &state
539 ) {
540         col.clear();
541         if (entity.WorldCollidable() && Intersection(entity, state, col)) {
542                 // determine displacement for each cardinal axis and move entity accordingly
543                 glm::vec3 min_pen(0.0f);
544                 glm::vec3 max_pen(0.0f);
545                 for (const WorldCollision &c : col) {
546                         if (!c.Blocks()) continue;
547                         glm::vec3 local_pen(c.normal * c.depth);
548                         // swap if neccessary (normal may point away from the entity)
549                         if (dot(c.normal, state.RelativePosition(c.ChunkPos()) - c.BlockCoords()) > 0) {
550                                 local_pen *= -1;
551                         }
552                         min_pen = min(min_pen, local_pen);
553                         max_pen = max(max_pen, local_pen);
554                 }
555                 glm::vec3 correction(0.0f);
556                 // only apply correction for axes where penetration is only in one direction
557                 for (std::size_t i = 0; i < 3; ++i) {
558                         if (min_pen[i] < -std::numeric_limits<float>::epsilon()) {
559                                 if (max_pen[i] < std::numeric_limits<float>::epsilon()) {
560                                         correction[i] = -min_pen[i];
561                                 }
562                         } else {
563                                 correction[i] = -max_pen[i];
564                         }
565                 }
566                 // correction may be zero in which case normalize() returns NaNs
567                 if (dot(correction, correction) < std::numeric_limits<float>::epsilon()) {
568                         return glm::vec3(0.0f);
569                 }
570                 glm::vec3 normal(normalize(correction));
571                 glm::vec3 normal_velocity(normal * dot(state.velocity, normal));
572                 // apply force proportional to penetration
573                 // use velocity projected onto normal as damper
574                 constexpr float k = 1000.0f; // spring constant
575                 constexpr float b = 10.0f; // damper constant
576                 const glm::vec3 x(-correction); // endpoint displacement from equilibrium in m
577                 const glm::vec3 v(normal_velocity); // relative velocity between endpoints in m/s
578                 return (((-k) * x) - (b * v)); // times 1kg/s, in kg*m/s²
579         } else {
580                 return glm::vec3(0.0f);
581         }
582 }
583
584 glm::vec3 World::Gravity(
585         const Entity &entity,
586         const EntityState &state
587 ) {
588         return glm::vec3(0.0f);
589 }
590
591 World::EntityHandle World::RemoveEntity(EntityHandle &eh) {
592         // check for player
593         for (auto player = players.begin(), end = players.end(); player != end;) {
594                 if (&player->GetEntity() == &*eh) {
595                         chunks.UnregisterIndex(player->GetChunks());
596                         player = players.erase(player);
597                         end = players.end();
598                 } else {
599                         ++player;
600                 }
601         }
602         return entities.erase(eh);
603 }
604
605
606 void World::Render(Viewport &viewport) {
607         DirectionalLighting &entity_prog = viewport.EntityProgram();
608         entity_prog.SetLightDirection(light_direction);
609         entity_prog.SetFogDensity(fog_density);
610
611         for (Entity &entity : entities) {
612                 entity.Render(entity.Transform(players.front().GetEntity().ChunkCoords()), entity_prog);
613         }
614 }
615
616 }