From: Daniel Karbach Date: Mon, 14 Sep 2015 13:15:59 +0000 (+0200) Subject: grouped entity physics state into a struct X-Git-Url: https://git.localhorst.tv/?a=commitdiff_plain;h=cf5ce8220483bb062740eeaedde6474928fd5e0e;p=blank.git grouped entity physics state into a struct --- diff --git a/src/client/client.cpp b/src/client/client.cpp index bd43635..8a3d1c8 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -212,7 +212,7 @@ void MasterState::On(const Packet::Join &pack) { pack.ReadPlayerID(player_id); state.reset(new InteractiveState(*this, player_id)); - pack.ReadPlayer(*state->GetInterface().GetPlayer().entity); + pack.ReadPlayerState(state->GetInterface().GetPlayer().entity->GetState()); env.state.PopAfter(this); env.state.Push(state.get()); @@ -293,7 +293,7 @@ void MasterState::On(const Packet::EntityUpdate &pack) { } if (world_iter->ID() == entity_id) { if (UpdateEntity(entity_id, pack.Seq())) { - pack.ReadEntity(*world_iter, i); + pack.ReadEntityState(world_iter->GetState(), i); } } } diff --git a/src/net/Packet.hpp b/src/net/Packet.hpp index 4742a45..232d0be 100644 --- a/src/net/Packet.hpp +++ b/src/net/Packet.hpp @@ -10,6 +10,7 @@ namespace blank { class Entity; +class EntityState; struct Packet { @@ -85,7 +86,7 @@ struct Packet { void WritePlayer(const Entity &) noexcept; void ReadPlayerID(std::uint32_t &) const noexcept; - void ReadPlayer(Entity &) const noexcept; + void ReadPlayerState(EntityState &) const noexcept; void WriteWorldName(const std::string &) noexcept; void ReadWorldName(std::string &) const noexcept; }; @@ -100,7 +101,7 @@ struct Packet { static constexpr std::size_t MAX_LEN = 64; void WritePlayer(const Entity &) noexcept; - void ReadPlayer(Entity &) const noexcept; + void ReadPlayerState(EntityState &) const noexcept; }; struct SpawnEntity : public Payload { @@ -135,7 +136,7 @@ struct Packet { void WriteEntity(const Entity &, std::uint32_t) noexcept; void ReadEntityID(std::uint32_t &, std::uint32_t) const noexcept; - void ReadEntity(Entity &, std::uint32_t) const noexcept; + void ReadEntityState(EntityState &, std::uint32_t) const noexcept; }; diff --git a/src/net/net.cpp b/src/net/net.cpp index 18abb92..13ea4d3 100644 --- a/src/net/net.cpp +++ b/src/net/net.cpp @@ -8,6 +8,8 @@ #include "../app/init.hpp" #include "../model/CompositeModel.hpp" +#include "../world/Entity.hpp" +#include "../world/EntityState.hpp" #include "../world/World.hpp" #include @@ -334,7 +336,8 @@ void ClientConnection::On(const Packet::PlayerUpdate &pack) { player_update_timer.Reset(); if (pack_diff > 0 || overdue) { player_update_pack = pack.Seq(); - pack.ReadPlayer(Player()); + // TODO: do client input validation here + pack.ReadPlayerState(Player().GetState()); } } @@ -542,34 +545,15 @@ void Packet::Login::ReadPlayerName(string &name) const noexcept { void Packet::Join::WritePlayer(const Entity &player) noexcept { Write(player.ID(), 0); - Write(player.ChunkCoords(), 4); - Write(player.Position(), 16); - Write(player.Velocity(), 28); - Write(player.Orientation(), 40); - Write(player.AngularVelocity(), 56); + Write(player.GetState(), 4); } void Packet::Join::ReadPlayerID(uint32_t &id) const noexcept { Read(id, 0); } -void Packet::Join::ReadPlayer(Entity &player) const noexcept { - glm::ivec3 chunk_coords(0); - glm::vec3 pos; - glm::vec3 vel; - glm::quat rot; - glm::vec3 ang; - - Read(chunk_coords, 4); - Read(pos, 16); - Read(vel, 28); - Read(rot, 40); - Read(ang, 56); - - player.Position(chunk_coords, pos); - player.Velocity(vel); - player.Orientation(rot); - player.AngularVelocity(ang); +void Packet::Join::ReadPlayerState(EntityState &state) const noexcept { + Read(state, 4); } void Packet::Join::WriteWorldName(const string &name) noexcept { @@ -581,30 +565,11 @@ void Packet::Join::ReadWorldName(string &name) const noexcept { } void Packet::PlayerUpdate::WritePlayer(const Entity &player) noexcept { - Write(player.ChunkCoords(), 0); - Write(player.Position(), 12); - Write(player.Velocity(), 24); - Write(player.Orientation(), 36); - Write(player.AngularVelocity(), 52); + Write(player.GetState(), 0); } -void Packet::PlayerUpdate::ReadPlayer(Entity &player) const noexcept { - glm::ivec3 chunk_coords(0); - glm::vec3 pos; - glm::vec3 vel; - glm::quat rot; - glm::vec3 ang; - - Read(chunk_coords, 0); - Read(pos, 12); - Read(vel, 24); - Read(rot, 36); - Read(ang, 52); - - player.Position(chunk_coords, pos); - player.Velocity(vel); - player.Orientation(rot); - player.AngularVelocity(ang); +void Packet::PlayerUpdate::ReadPlayerState(EntityState &state) const noexcept { + Read(state, 0); } void Packet::SpawnEntity::WriteEntity(const Entity &e) noexcept { @@ -614,11 +579,7 @@ void Packet::SpawnEntity::WriteEntity(const Entity &e) noexcept { } else { Write(uint32_t(0), 4); } - Write(e.ChunkCoords(), 8); - Write(e.Position(), 20); - Write(e.Velocity(), 32); - Write(e.Orientation(), 44); - Write(e.AngularVelocity(), 60); + Write(e.GetState(), 8); Write(e.Bounds(), 72); uint32_t flags = 0; if (e.WorldCollidable()) { @@ -637,28 +598,17 @@ void Packet::SpawnEntity::ReadSkeletonID(uint32_t &id) const noexcept { } void Packet::SpawnEntity::ReadEntity(Entity &e) const noexcept { - glm::ivec3 chunk_coords(0); - glm::vec3 pos; - glm::vec3 vel; - glm::quat rot; - glm::vec3 ang; + EntityState state; AABB bounds; uint32_t flags = 0; string name; - Read(chunk_coords, 8); - Read(pos, 20); - Read(vel, 32); - Read(rot, 44); - Read(ang, 60); + Read(state, 8); Read(bounds, 72); Read(flags, 96); ReadString(name, 100, 32); - e.Position(chunk_coords, pos); - e.Velocity(vel); - e.Orientation(rot); - e.AngularVelocity(ang); + e.SetState(state); e.Bounds(bounds); e.WorldCollidable(flags & 1); e.Name(name); @@ -684,36 +634,16 @@ void Packet::EntityUpdate::WriteEntity(const Entity &entity, uint32_t num) noexc uint32_t off = 4 + (num * 64); Write(entity.ID(), off); - Write(entity.ChunkCoords(), off + 4); - Write(entity.Position(), off + 16); - Write(entity.Velocity(), off + 28); - Write(entity.Orientation(), off + 40); - Write(entity.AngularVelocity(), off + 56); + Write(entity.GetState(), off + 4); } void Packet::EntityUpdate::ReadEntityID(uint32_t &id, uint32_t num) const noexcept { Read(id, 4 + (num * 64)); } -void Packet::EntityUpdate::ReadEntity(Entity &entity, uint32_t num) const noexcept { +void Packet::EntityUpdate::ReadEntityState(EntityState &state, uint32_t num) const noexcept { uint32_t off = 4 + (num * 64); - - glm::ivec3 chunk_coords(0); - glm::vec3 pos; - glm::vec3 vel; - glm::quat rot; - glm::vec3 ang; - - Read(chunk_coords, off + 4); - Read(pos, off + 16); - Read(vel, off + 28); - Read(rot, off + 40); - Read(ang, off + 56); - - entity.Position(chunk_coords, pos); - entity.Velocity(vel); - entity.Orientation(rot); - entity.AngularVelocity(ang); + Read(state, off + 4); } diff --git a/src/world/Entity.cpp b/src/world/Entity.cpp deleted file mode 100644 index b909603..0000000 --- a/src/world/Entity.cpp +++ /dev/null @@ -1,118 +0,0 @@ -#include "Entity.hpp" - -#include "../model/Shape.hpp" - -#include -#include -#include - -namespace { - -blank::EntityModel::Buffer model_buffer; - -} - -namespace blank { - -Entity::Entity() noexcept -: model() -, id(-1) -, name("anonymous") -, bounds() -, velocity(0, 0, 0) -, chunk(0, 0, 0) -, angular_velocity(0.0f) -, ref_count(0) -, world_collision(false) -, dead(false) { - -} - - -void Entity::Position(const Chunk::Pos &c, const glm::vec3 &pos) noexcept { - chunk = c; - model.Position(pos); -} - -void Entity::Position(const glm::vec3 &pos) noexcept { - glm::vec3 position(pos); - while (position.x >= Chunk::width) { - position.x -= Chunk::width; - ++chunk.x; - } - while (position.x < 0) { - position.x += Chunk::width; - --chunk.x; - } - while (position.y >= Chunk::height) { - position.y -= Chunk::height; - ++chunk.y; - } - while (position.y < 0) { - position.y += Chunk::height; - --chunk.y; - } - while (position.z >= Chunk::depth) { - position.z -= Chunk::depth; - ++chunk.z; - } - while (position.z < 0) { - position.z += Chunk::depth; - --chunk.z; - } - model.Position(position); -} - -void Entity::Move(const glm::vec3 &delta) noexcept { - Position(Position() + delta); -} - -void Entity::Rotate(const glm::quat &delta) noexcept { - Orientation(delta * Orientation()); -} - -glm::mat4 Entity::ChunkTransform(const Chunk::Pos &chunk_offset) const noexcept { - const glm::vec3 translation = glm::vec3((chunk - chunk_offset) * Chunk::Extent()); - return glm::translate(translation); -} - -glm::mat4 Entity::Transform(const Chunk::Pos &chunk_offset) const noexcept { - const glm::vec3 translation = glm::vec3((chunk - chunk_offset) * Chunk::Extent()) + Position(); - glm::mat4 transform(toMat4(Orientation())); - transform[3].x = translation.x; - transform[3].y = translation.y; - transform[3].z = translation.z; - return transform; -} - -Ray Entity::Aim(const Chunk::Pos &chunk_offset) const noexcept { - glm::mat4 transform = Transform(chunk_offset); - glm::vec4 from = transform * glm::vec4(0.0f, 0.0f, 0.0f, 1.0f); - from /= from.w; - glm::vec4 to = transform * glm::vec4(0.0f, 0.0f, -1.0f, 1.0f); - to /= to.w; - return Ray{ glm::vec3(from), glm::normalize(glm::vec3(to - from)) }; -} - -namespace { - -glm::quat delta_rot(const glm::vec3 &av, float dt) { - glm::vec3 half(av * dt * 0.5f); - float mag = length(half); - if (mag > 0.0f) { - float smag = std::sin(mag) / mag; - return glm::quat(std::cos(mag), half * smag); - } else { - return glm::quat(1.0f, 0.0f, 0.0f, 0.0f); - } -} - -} - -void Entity::Update(int dt) noexcept { - float fdt = float(dt); - Move(velocity * fdt); - Rotate(delta_rot(angular_velocity, fdt)); -} - -} diff --git a/src/world/Entity.hpp b/src/world/Entity.hpp index c0d9a0b..dcfb418 100644 --- a/src/world/Entity.hpp +++ b/src/world/Entity.hpp @@ -2,6 +2,7 @@ #define BLANK_WORLD_ENTITY_HPP_ #include "Chunk.hpp" +#include "EntityState.hpp" #include "../model/CompositeInstance.hpp" #include "../model/geometry.hpp" @@ -36,35 +37,38 @@ public: bool WorldCollidable() const noexcept { return world_collision; } void WorldCollidable(bool b) noexcept { world_collision = b; } - const glm::vec3 &Velocity() const noexcept { return velocity; } - void Velocity(const glm::vec3 &v) noexcept { velocity = v; } + const glm::vec3 &Velocity() const noexcept { return state.velocity; } + void Velocity(const glm::vec3 &v) noexcept { state.velocity = v; } - const glm::vec3 &Position() const noexcept { return model.Position(); } - void Position(const Chunk::Pos &, const glm::vec3 &) noexcept; + const glm::vec3 &Position() const noexcept { return state.block_pos; } + void Position(const glm::ivec3 &, const glm::vec3 &) noexcept; void Position(const glm::vec3 &) noexcept; - void Move(const glm::vec3 &delta) noexcept; - const Chunk::Pos ChunkCoords() const noexcept { return chunk; } + const glm::ivec3 ChunkCoords() const noexcept { return state.chunk_pos; } glm::vec3 AbsolutePosition() const noexcept { - return glm::vec3(chunk * Chunk::Extent()) + Position(); + return state.AbsolutePosition(); } glm::vec3 AbsoluteDifference(const Entity &other) const noexcept { - return glm::vec3((chunk - other.chunk) * Chunk::Extent()) + Position() - other.Position(); + return state.Diff(other.state); } /// direction is rotation axis, magnitude is speed in rad/ms - const glm::vec3 &AngularVelocity() const noexcept { return angular_velocity; } - void AngularVelocity(const glm::vec3 &v) noexcept { angular_velocity = v; } + const glm::vec3 &AngularVelocity() const noexcept { return state.ang_vel; } + void AngularVelocity(const glm::vec3 &v) noexcept { state.ang_vel = v; } - const glm::quat &Orientation() const noexcept { return model.Orientation(); } - void Orientation(const glm::quat &o) noexcept { model.Orientation(o); } - void Rotate(const glm::quat &delta) noexcept; + const glm::quat &Orientation() const noexcept { return state.orient; } + void Orientation(const glm::quat &o) noexcept { state.orient = o; } - glm::mat4 ChunkTransform(const Chunk::Pos &chunk_offset) const noexcept; - glm::mat4 Transform(const Chunk::Pos &chunk_offset) const noexcept; + glm::mat4 Transform(const glm::ivec3 &reference) const noexcept { + return state.Transform(reference); + } Ray Aim(const Chunk::Pos &chunk_offset) const noexcept; + void SetState(const EntityState &s) noexcept { state = s; } + EntityState &GetState() noexcept { return state; } + const EntityState &GetState() const noexcept { return state; } + void Ref() noexcept { ++ref_count; } void UnRef() noexcept { --ref_count; } void Kill() noexcept { dead = true; } @@ -85,11 +89,7 @@ private: std::string name; AABB bounds; - - glm::vec3 velocity; - Chunk::Pos chunk; - - glm::vec3 angular_velocity; + EntityState state; int ref_count; diff --git a/src/world/EntityState.hpp b/src/world/EntityState.hpp new file mode 100644 index 0000000..8f74114 --- /dev/null +++ b/src/world/EntityState.hpp @@ -0,0 +1,50 @@ +#ifndef BLANK_WORLD_ENTITYSTATE_HPP_ +#define BLANK_WORLD_ENTITYSTATE_HPP_ + +#include "Chunk.hpp" + +#include +#include + + +namespace blank { + +struct EntityState { + + glm::ivec3 chunk_pos; + glm::vec3 block_pos; + glm::vec3 velocity; + + glm::quat orient; + glm::vec3 ang_vel; + + EntityState(); + + /// make sure block_pos is within chunk bounds + void AdjustPosition() noexcept; + + /// do an integration step of dt milliseconds + void Update(int dt) noexcept; + + /// get a position vector relative to the (0,0,0) chunk + glm::vec3 AbsolutePosition() const noexcept { + return glm::vec3(chunk_pos * Chunk::Extent()) + block_pos; + } + /// get a position vector relative to given reference chunk + glm::vec3 RelativePosition(const glm::ivec3 &reference) const noexcept { + return glm::vec3((chunk_pos - reference) * Chunk::Extent()) + block_pos; + } + + /// get the difference between this and the given position + glm::vec3 Diff(const EntityState &other) const noexcept { + return RelativePosition(other.chunk_pos) - other.block_pos; + } + + /// get entity state as a matrix tranform relative to given reference chunk + glm::mat4 Transform(const glm::ivec3 &reference) const noexcept; + +}; + +} + +#endif diff --git a/src/world/World.cpp b/src/world/World.cpp deleted file mode 100644 index 505cbbb..0000000 --- a/src/world/World.cpp +++ /dev/null @@ -1,321 +0,0 @@ -#include "World.hpp" - -#include "ChunkIndex.hpp" -#include "EntityCollision.hpp" -#include "WorldCollision.hpp" -#include "../app/Assets.hpp" -#include "../graphics/Format.hpp" -#include "../graphics/Viewport.hpp" - -#include -#include -#include -#include - - -namespace blank { - -World::World(const BlockTypeRegistry &types, const Config &config) -: config(config) -, block_type(types) -, chunks(types) -// TODO: set spawn base and extent from config -, spawn_index(chunks.MakeIndex(Chunk::Pos(0, 0, 0), 3)) -, players() -, entities() -, light_direction(config.light_direction) -, fog_density(config.fog_density) { - -} - -World::~World() { - chunks.UnregisterIndex(spawn_index); -} - - -Player World::AddPlayer(const std::string &name) { - for (Player &p : players) { - if (p.entity->Name() == name) { - return { nullptr, nullptr }; - } - } - Entity &entity = AddEntity(); - entity.Name(name); - // TODO: load from save file here - entity.Bounds({ { -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f } }); - entity.WorldCollidable(true); - entity.Position(config.spawn); - ChunkIndex *index = &chunks.MakeIndex(entity.ChunkCoords(), 6); - players.emplace_back(&entity, index); - return players.back(); -} - -Player World::AddPlayer(const std::string &name, std::uint32_t id) { - for (Player &p : players) { - if (p.entity->Name() == name) { - return { nullptr, nullptr }; - } - } - Entity *entity = AddEntity(id); - if (!entity) { - return { nullptr, nullptr }; - } - entity->Name(name); - // TODO: load from save file here - entity->Bounds({ { -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f } }); - entity->WorldCollidable(true); - entity->Position(config.spawn); - ChunkIndex *index = &chunks.MakeIndex(entity->ChunkCoords(), 6); - players.emplace_back(entity, index); - return players.back(); -} - -Entity &World::AddEntity() { - if (entities.empty()) { - entities.emplace_back(); - entities.back().ID(1); - return entities.back(); - } - if (entities.back().ID() < std::numeric_limits::max()) { - std::uint32_t id = entities.back().ID() + 1; - entities.emplace_back(); - entities.back().ID(id); - return entities.back(); - } - std::uint32_t id = 1; - auto position = entities.begin(); - auto end = entities.end(); - while (position != end && position->ID() == id) { - ++id; - ++position; - } - auto entity = entities.emplace(position); - entity->ID(id); - return *entity; -} - -Entity *World::AddEntity(std::uint32_t id) { - if (entities.empty() || entities.back().ID() < id) { - entities.emplace_back(); - entities.back().ID(id); - return &entities.back(); - } - - auto position = entities.begin(); - auto end = entities.end(); - while (position != end && position->ID() < id) { - ++position; - } - if (position != end && position->ID() == id) { - return nullptr; - } - auto entity = entities.emplace(position); - entity->ID(id); - return &*entity; -} - -Entity &World::ForceAddEntity(std::uint32_t id) { - if (entities.empty() || entities.back().ID() < id) { - entities.emplace_back(); - entities.back().ID(id); - return entities.back(); - } - - auto position = entities.begin(); - auto end = entities.end(); - while (position != end && position->ID() < id) { - ++position; - } - if (position != end && position->ID() == id) { - return *position; - } - auto entity = entities.emplace(position); - entity->ID(id); - return *entity; -} - - -namespace { - -struct Candidate { - Chunk *chunk; - float dist; -}; - -bool CandidateLess(const Candidate &a, const Candidate &b) { - return a.dist < b.dist; -} - -std::vector candidates; - -} - -bool World::Intersection( - const Ray &ray, - const glm::mat4 &M, - const Chunk::Pos &reference, - WorldCollision &coll -) { - candidates.clear(); - - for (Chunk &cur_chunk : chunks) { - float cur_dist; - if (cur_chunk.Intersection(ray, M * cur_chunk.Transform(reference), cur_dist)) { - candidates.push_back({ &cur_chunk, cur_dist }); - } - } - - if (candidates.empty()) return false; - - std::sort(candidates.begin(), candidates.end(), CandidateLess); - - coll.chunk = nullptr; - coll.block = -1; - coll.depth = std::numeric_limits::infinity(); - - for (Candidate &cand : candidates) { - if (cand.dist > coll.depth) continue; - WorldCollision cur_coll; - if (cand.chunk->Intersection(ray, M * cand.chunk->Transform(reference), cur_coll)) { - if (cur_coll.depth < coll.depth) { - coll = cur_coll; - } - } - } - - return coll.chunk; -} - -bool World::Intersection( - const Ray &ray, - const glm::mat4 &M, - const Entity &reference, - EntityCollision &coll -) { - coll.entity = nullptr; - coll.depth = std::numeric_limits::infinity(); - for (Entity &cur_entity : entities) { - if (&cur_entity == &reference) { - continue; - } - float cur_dist; - glm::vec3 cur_normal; - if (blank::Intersection(ray, cur_entity.Bounds(), M * cur_entity.Transform(reference.ChunkCoords()), &cur_dist, &cur_normal)) { - // TODO: fine grained check goes here? maybe? - if (cur_dist < coll.depth) { - coll.entity = &cur_entity; - coll.depth = cur_dist; - coll.normal = cur_normal; - } - } - } - - return coll.entity; -} - -bool World::Intersection(const Entity &e, std::vector &col) { - AABB box = e.Bounds(); - Chunk::Pos reference = e.ChunkCoords(); - glm::mat4 M = e.Transform(reference); - bool any = false; - for (Chunk &cur_chunk : chunks) { - if (manhattan_radius(cur_chunk.Position() - e.ChunkCoords()) > 1) { - // chunk is not one of the 3x3x3 surrounding the entity - // since there's no entity which can extent over 16 blocks, they can be skipped - continue; - } - if (cur_chunk.Intersection(box, M, cur_chunk.Transform(reference), col)) { - any = true; - } - } - return any; -} - - -namespace { - -std::vector col; - -} - -void World::Update(int dt) { - for (Entity &entity : entities) { - entity.Update(dt); - } - for (Entity &entity : entities) { - col.clear(); - if (entity.WorldCollidable() && Intersection(entity, col)) { - // entity collides with the world - Resolve(entity, col); - } - } - for (Player &player : players) { - player.chunks->Rebase(player.entity->ChunkCoords()); - } - for (auto iter = entities.begin(), end = entities.end(); iter != end;) { - if (iter->CanRemove()) { - iter = RemoveEntity(iter); - } else { - ++iter; - } - } -} - -void World::Resolve(Entity &e, std::vector &col) { - // determine displacement for each cardinal axis and move entity accordingly - glm::vec3 min_disp(0.0f); - glm::vec3 max_disp(0.0f); - for (const WorldCollision &c : col) { - if (!c.Blocks()) continue; - glm::vec3 local_disp(c.normal * c.depth); - // swap if neccessary (normal may point away from the entity) - if (dot(c.normal, e.Position() - c.BlockCoords()) < 0) { - local_disp *= -1; - } - min_disp = min(min_disp, local_disp); - max_disp = max(max_disp, local_disp); - } - // for each axis - // if only one direction is set, use that as the final - // if both directions are set, use average - glm::vec3 final_disp(0.0f); - for (int axis = 0; axis < 3; ++axis) { - if (std::abs(min_disp[axis]) > std::numeric_limits::epsilon()) { - if (std::abs(max_disp[axis]) > std::numeric_limits::epsilon()) { - final_disp[axis] = (min_disp[axis] + max_disp[axis]) * 0.5f; - } else { - final_disp[axis] = min_disp[axis]; - } - } else if (std::abs(max_disp[axis]) > std::numeric_limits::epsilon()) { - final_disp[axis] = max_disp[axis]; - } - } - e.Move(final_disp); -} - -World::EntityHandle World::RemoveEntity(EntityHandle &eh) { - // check for player - for (auto player = players.begin(), end = players.end(); player != end;) { - if (player->entity == &*eh) { - chunks.UnregisterIndex(*player->chunks); - player = players.erase(player); - end = players.end(); - } else { - ++player; - } - } - return entities.erase(eh); -} - - -void World::Render(Viewport &viewport) { - DirectionalLighting &entity_prog = viewport.EntityProgram(); - entity_prog.SetLightDirection(light_direction); - entity_prog.SetFogDensity(fog_density); - - for (Entity &entity : entities) { - entity.Render(entity.ChunkTransform(players[0].entity->ChunkCoords()), entity_prog); - } -} - -} diff --git a/src/world/world.cpp b/src/world/world.cpp new file mode 100644 index 0000000..a35f76a --- /dev/null +++ b/src/world/world.cpp @@ -0,0 +1,430 @@ +#include "Entity.hpp" +#include "EntityState.hpp" +#include "World.hpp" + +#include "ChunkIndex.hpp" +#include "EntityCollision.hpp" +#include "WorldCollision.hpp" +#include "../app/Assets.hpp" +#include "../graphics/Format.hpp" +#include "../graphics/Viewport.hpp" + +#include +#include +#include +#include +#include +#include + + +namespace blank { + +Entity::Entity() noexcept +: model() +, id(-1) +, name("anonymous") +, bounds() +, state() +, ref_count(0) +, world_collision(false) +, dead(false) { + +} + + +void Entity::Position(const glm::ivec3 &c, const glm::vec3 &b) noexcept { + state.chunk_pos = c; + state.block_pos = b; +} + +void Entity::Position(const glm::vec3 &pos) noexcept { + state.block_pos = pos; + state.AdjustPosition(); +} + +Ray Entity::Aim(const Chunk::Pos &chunk_offset) const noexcept { + glm::mat4 transform = Transform(chunk_offset); + glm::vec4 from = transform * glm::vec4(0.0f, 0.0f, 0.0f, 1.0f); + from /= from.w; + glm::vec4 to = transform * glm::vec4(0.0f, 0.0f, -1.0f, 1.0f); + to /= to.w; + return Ray{ glm::vec3(from), glm::normalize(glm::vec3(to - from)) }; +} + +namespace { + +glm::quat delta_rot(const glm::vec3 &av, float dt) { + glm::vec3 half(av * dt * 0.5f); + float mag = length(half); + if (mag > 0.0f) { + float smag = std::sin(mag) / mag; + return glm::quat(std::cos(mag), half * smag); + } else { + return glm::quat(1.0f, 0.0f, 0.0f, 0.0f); + } +} + +} + +void Entity::Update(int dt) noexcept { + state.Update(dt); +} + + +EntityState::EntityState() +: chunk_pos(0) +, block_pos(0.0f) +, velocity(0.0f) +, orient(1.0f, 0.0f, 0.0f, 0.0f) +, ang_vel(0.0f) { + +} + +void EntityState::Update(int dt) noexcept { + float fdt = float(dt); + block_pos += velocity * fdt; + orient = delta_rot(ang_vel, fdt) * orient; + AdjustPosition(); +} + +void EntityState::AdjustPosition() noexcept { + while (block_pos.x >= Chunk::width) { + block_pos.x -= Chunk::width; + ++chunk_pos.x; + } + while (block_pos.x < 0) { + block_pos.x += Chunk::width; + --chunk_pos.x; + } + while (block_pos.y >= Chunk::height) { + block_pos.y -= Chunk::height; + ++chunk_pos.y; + } + while (block_pos.y < 0) { + block_pos.y += Chunk::height; + --chunk_pos.y; + } + while (block_pos.z >= Chunk::depth) { + block_pos.z -= Chunk::depth; + ++chunk_pos.z; + } + while (block_pos.z < 0) { + block_pos.z += Chunk::depth; + --chunk_pos.z; + } +} + +glm::mat4 EntityState::Transform(const glm::ivec3 &reference) const noexcept { + const glm::vec3 translation = RelativePosition(reference); + glm::mat4 transform(toMat4(orient)); + transform[3].x = translation.x; + transform[3].y = translation.y; + transform[3].z = translation.z; + return transform; +} + + +World::World(const BlockTypeRegistry &types, const Config &config) +: config(config) +, block_type(types) +, chunks(types) +// TODO: set spawn base and extent from config +, spawn_index(chunks.MakeIndex(Chunk::Pos(0, 0, 0), 3)) +, players() +, entities() +, light_direction(config.light_direction) +, fog_density(config.fog_density) { + +} + +World::~World() { + chunks.UnregisterIndex(spawn_index); +} + + +Player World::AddPlayer(const std::string &name) { + for (Player &p : players) { + if (p.entity->Name() == name) { + return { nullptr, nullptr }; + } + } + Entity &entity = AddEntity(); + entity.Name(name); + // TODO: load from save file here + entity.Bounds({ { -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f } }); + entity.WorldCollidable(true); + entity.Position(config.spawn); + ChunkIndex *index = &chunks.MakeIndex(entity.ChunkCoords(), 6); + players.emplace_back(&entity, index); + return players.back(); +} + +Player World::AddPlayer(const std::string &name, std::uint32_t id) { + for (Player &p : players) { + if (p.entity->Name() == name) { + return { nullptr, nullptr }; + } + } + Entity *entity = AddEntity(id); + if (!entity) { + return { nullptr, nullptr }; + } + entity->Name(name); + // TODO: load from save file here + entity->Bounds({ { -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f } }); + entity->WorldCollidable(true); + entity->Position(config.spawn); + ChunkIndex *index = &chunks.MakeIndex(entity->ChunkCoords(), 6); + players.emplace_back(entity, index); + return players.back(); +} + +Entity &World::AddEntity() { + if (entities.empty()) { + entities.emplace_back(); + entities.back().ID(1); + return entities.back(); + } + if (entities.back().ID() < std::numeric_limits::max()) { + std::uint32_t id = entities.back().ID() + 1; + entities.emplace_back(); + entities.back().ID(id); + return entities.back(); + } + std::uint32_t id = 1; + auto position = entities.begin(); + auto end = entities.end(); + while (position != end && position->ID() == id) { + ++id; + ++position; + } + auto entity = entities.emplace(position); + entity->ID(id); + return *entity; +} + +Entity *World::AddEntity(std::uint32_t id) { + if (entities.empty() || entities.back().ID() < id) { + entities.emplace_back(); + entities.back().ID(id); + return &entities.back(); + } + + auto position = entities.begin(); + auto end = entities.end(); + while (position != end && position->ID() < id) { + ++position; + } + if (position != end && position->ID() == id) { + return nullptr; + } + auto entity = entities.emplace(position); + entity->ID(id); + return &*entity; +} + +Entity &World::ForceAddEntity(std::uint32_t id) { + if (entities.empty() || entities.back().ID() < id) { + entities.emplace_back(); + entities.back().ID(id); + return entities.back(); + } + + auto position = entities.begin(); + auto end = entities.end(); + while (position != end && position->ID() < id) { + ++position; + } + if (position != end && position->ID() == id) { + return *position; + } + auto entity = entities.emplace(position); + entity->ID(id); + return *entity; +} + + +namespace { + +struct Candidate { + Chunk *chunk; + float dist; +}; + +bool CandidateLess(const Candidate &a, const Candidate &b) { + return a.dist < b.dist; +} + +std::vector candidates; + +} + +bool World::Intersection( + const Ray &ray, + const glm::mat4 &M, + const Chunk::Pos &reference, + WorldCollision &coll +) { + candidates.clear(); + + for (Chunk &cur_chunk : chunks) { + float cur_dist; + if (cur_chunk.Intersection(ray, M * cur_chunk.Transform(reference), cur_dist)) { + candidates.push_back({ &cur_chunk, cur_dist }); + } + } + + if (candidates.empty()) return false; + + std::sort(candidates.begin(), candidates.end(), CandidateLess); + + coll.chunk = nullptr; + coll.block = -1; + coll.depth = std::numeric_limits::infinity(); + + for (Candidate &cand : candidates) { + if (cand.dist > coll.depth) continue; + WorldCollision cur_coll; + if (cand.chunk->Intersection(ray, M * cand.chunk->Transform(reference), cur_coll)) { + if (cur_coll.depth < coll.depth) { + coll = cur_coll; + } + } + } + + return coll.chunk; +} + +bool World::Intersection( + const Ray &ray, + const glm::mat4 &M, + const Entity &reference, + EntityCollision &coll +) { + coll.entity = nullptr; + coll.depth = std::numeric_limits::infinity(); + for (Entity &cur_entity : entities) { + if (&cur_entity == &reference) { + continue; + } + float cur_dist; + glm::vec3 cur_normal; + if (blank::Intersection(ray, cur_entity.Bounds(), M * cur_entity.Transform(reference.ChunkCoords()), &cur_dist, &cur_normal)) { + // TODO: fine grained check goes here? maybe? + if (cur_dist < coll.depth) { + coll.entity = &cur_entity; + coll.depth = cur_dist; + coll.normal = cur_normal; + } + } + } + + return coll.entity; +} + +bool World::Intersection(const Entity &e, std::vector &col) { + AABB box = e.Bounds(); + Chunk::Pos reference = e.ChunkCoords(); + glm::mat4 M = e.Transform(reference); + bool any = false; + for (Chunk &cur_chunk : chunks) { + if (manhattan_radius(cur_chunk.Position() - e.ChunkCoords()) > 1) { + // chunk is not one of the 3x3x3 surrounding the entity + // since there's no entity which can extent over 16 blocks, they can be skipped + continue; + } + if (cur_chunk.Intersection(box, M, cur_chunk.Transform(reference), col)) { + any = true; + } + } + return any; +} + + +namespace { + +std::vector col; + +} + +void World::Update(int dt) { + for (Entity &entity : entities) { + entity.Update(dt); + } + for (Entity &entity : entities) { + col.clear(); + if (entity.WorldCollidable() && Intersection(entity, col)) { + // entity collides with the world + Resolve(entity, col); + } + } + for (Player &player : players) { + player.chunks->Rebase(player.entity->ChunkCoords()); + } + for (auto iter = entities.begin(), end = entities.end(); iter != end;) { + if (iter->CanRemove()) { + iter = RemoveEntity(iter); + } else { + ++iter; + } + } +} + +void World::Resolve(Entity &e, std::vector &col) { + // determine displacement for each cardinal axis and move entity accordingly + glm::vec3 min_disp(0.0f); + glm::vec3 max_disp(0.0f); + for (const WorldCollision &c : col) { + if (!c.Blocks()) continue; + glm::vec3 local_disp(c.normal * c.depth); + // swap if neccessary (normal may point away from the entity) + if (dot(c.normal, e.Position() - c.BlockCoords()) < 0) { + local_disp *= -1; + } + min_disp = min(min_disp, local_disp); + max_disp = max(max_disp, local_disp); + } + // for each axis + // if only one direction is set, use that as the final + // if both directions are set, use average + glm::vec3 final_disp(0.0f); + for (int axis = 0; axis < 3; ++axis) { + if (std::abs(min_disp[axis]) > std::numeric_limits::epsilon()) { + if (std::abs(max_disp[axis]) > std::numeric_limits::epsilon()) { + final_disp[axis] = (min_disp[axis] + max_disp[axis]) * 0.5f; + } else { + final_disp[axis] = min_disp[axis]; + } + } else if (std::abs(max_disp[axis]) > std::numeric_limits::epsilon()) { + final_disp[axis] = max_disp[axis]; + } + } + e.Position(e.Position() + final_disp); +} + +World::EntityHandle World::RemoveEntity(EntityHandle &eh) { + // check for player + for (auto player = players.begin(), end = players.end(); player != end;) { + if (player->entity == &*eh) { + chunks.UnregisterIndex(*player->chunks); + player = players.erase(player); + end = players.end(); + } else { + ++player; + } + } + return entities.erase(eh); +} + + +void World::Render(Viewport &viewport) { + DirectionalLighting &entity_prog = viewport.EntityProgram(); + entity_prog.SetLightDirection(light_direction); + entity_prog.SetFogDensity(fog_density); + + for (Entity &entity : entities) { + entity.Render(entity.Transform(players[0].entity->ChunkCoords()), entity_prog); + } +} + +}