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