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