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