From 458e0005690ea9d579588a0a12368fc2c2c9a93a Mon Sep 17 00:00:00 2001 From: untodesu Date: Tue, 1 Jul 2025 03:08:39 +0500 Subject: I hyper-focued on refactoring again - I put a cool-sounding "we are number one" remix on repeat and straight up grinded the entire repository to a better state until 03:09 AM. I guess I have something wrong in my brain that makes me do this shit --- game/shared/entity/CMakeLists.txt | 16 ++++ game/shared/entity/collision.cc | 170 ++++++++++++++++++++++++++++++++++++++ game/shared/entity/collision.hh | 25 ++++++ game/shared/entity/factory.cc | 36 ++++++++ game/shared/entity/factory.hh | 15 ++++ game/shared/entity/gravity.cc | 19 +++++ game/shared/entity/gravity.hh | 18 ++++ game/shared/entity/grounded.hh | 16 ++++ game/shared/entity/head.hh | 20 +++++ game/shared/entity/player.hh | 10 +++ game/shared/entity/stasis.cc | 19 +++++ game/shared/entity/stasis.hh | 20 +++++ game/shared/entity/transform.cc | 33 ++++++++ game/shared/entity/transform.hh | 34 ++++++++ game/shared/entity/velocity.cc | 18 ++++ game/shared/entity/velocity.hh | 23 ++++++ 16 files changed, 492 insertions(+) create mode 100644 game/shared/entity/CMakeLists.txt create mode 100644 game/shared/entity/collision.cc create mode 100644 game/shared/entity/collision.hh create mode 100644 game/shared/entity/factory.cc create mode 100644 game/shared/entity/factory.hh create mode 100644 game/shared/entity/gravity.cc create mode 100644 game/shared/entity/gravity.hh create mode 100644 game/shared/entity/grounded.hh create mode 100644 game/shared/entity/head.hh create mode 100644 game/shared/entity/player.hh create mode 100644 game/shared/entity/stasis.cc create mode 100644 game/shared/entity/stasis.hh create mode 100644 game/shared/entity/transform.cc create mode 100644 game/shared/entity/transform.hh create mode 100644 game/shared/entity/velocity.cc create mode 100644 game/shared/entity/velocity.hh (limited to 'game/shared/entity') diff --git a/game/shared/entity/CMakeLists.txt b/game/shared/entity/CMakeLists.txt new file mode 100644 index 0000000..36e45b6 --- /dev/null +++ b/game/shared/entity/CMakeLists.txt @@ -0,0 +1,16 @@ +target_sources(shared PRIVATE + "${CMAKE_CURRENT_LIST_DIR}/collision.cc" + "${CMAKE_CURRENT_LIST_DIR}/collision.hh" + "${CMAKE_CURRENT_LIST_DIR}/factory.cc" + "${CMAKE_CURRENT_LIST_DIR}/factory.hh" + "${CMAKE_CURRENT_LIST_DIR}/gravity.cc" + "${CMAKE_CURRENT_LIST_DIR}/gravity.hh" + "${CMAKE_CURRENT_LIST_DIR}/grounded.hh" + "${CMAKE_CURRENT_LIST_DIR}/head.hh" + "${CMAKE_CURRENT_LIST_DIR}/player.hh" + "${CMAKE_CURRENT_LIST_DIR}/stasis.cc" + "${CMAKE_CURRENT_LIST_DIR}/stasis.hh" + "${CMAKE_CURRENT_LIST_DIR}/transform.cc" + "${CMAKE_CURRENT_LIST_DIR}/transform.hh" + "${CMAKE_CURRENT_LIST_DIR}/velocity.cc" + "${CMAKE_CURRENT_LIST_DIR}/velocity.hh") diff --git a/game/shared/entity/collision.cc b/game/shared/entity/collision.cc new file mode 100644 index 0000000..190f6e7 --- /dev/null +++ b/game/shared/entity/collision.cc @@ -0,0 +1,170 @@ +#include "shared/pch.hh" + +#include "shared/entity/collision.hh" + +#include "core/math/constexpr.hh" + +#include "shared/entity/gravity.hh" +#include "shared/entity/grounded.hh" +#include "shared/entity/transform.hh" +#include "shared/entity/velocity.hh" +#include "shared/world/dimension.hh" +#include "shared/world/voxel_registry.hh" + +#include "shared/coord.hh" +#include "shared/globals.hh" + +static int vgrid_collide(const world::Dimension* dimension, int d, entity::Collision& collision, entity::Transform& transform, + entity::Velocity& velocity, world::voxel_surface& touch_surface) +{ + const auto move = globals::fixed_frametime * velocity.value[d]; + const auto move_sign = math::sign(move); + + const auto& ref_aabb = collision.aabb; + const auto current_aabb = ref_aabb.push(transform.local); + + auto next_aabb = math::AABB(current_aabb); + next_aabb.min[d] += move; + next_aabb.max[d] += move; + + local_pos lpos_min; + lpos_min.x = math::floor(next_aabb.min.x); + lpos_min.y = math::floor(next_aabb.min.y); + lpos_min.z = math::floor(next_aabb.min.z); + + local_pos lpos_max; + lpos_max.x = math::ceil(next_aabb.max.x); + lpos_max.y = math::ceil(next_aabb.max.y); + lpos_max.z = math::ceil(next_aabb.max.z); + + // Other axes + const int u = (d + 1) % 3; + const int v = (d + 2) % 3; + + local_pos::value_type ddir; + local_pos::value_type dmin; + local_pos::value_type dmax; + + if(move < 0.0f) { + ddir = local_pos::value_type(+1); + dmin = lpos_min[d]; + dmax = lpos_max[d]; + } else { + ddir = local_pos::value_type(-1); + dmin = lpos_max[d]; + dmax = lpos_min[d]; + } + + world::voxel_touch latch_touch = world::voxel_touch::NOTHING; + glm::fvec3 latch_values = glm::fvec3(0.0f, 0.0f, 0.0f); + world::voxel_surface latch_surface = world::voxel_surface::UNKNOWN; + math::AABB latch_vbox; + + for(auto i = dmin; i != dmax; i += ddir) { + for(auto j = lpos_min[u]; j < lpos_max[u]; ++j) + for(auto k = lpos_min[v]; k < lpos_max[v]; ++k) { + local_pos lpos; + lpos[d] = i; + lpos[u] = j; + lpos[v] = k; + + const auto vpos = coord::to_voxel(transform.chunk, lpos); + const auto info = world::voxel_registry::find(dimension->get_voxel(vpos)); + + if(info == nullptr) { + // Don't collide with something + // that we assume to be nothing + continue; + } + + math::AABB vbox; + vbox.min = glm::fvec3(lpos); + vbox.max = glm::fvec3(lpos) + 1.0f; + + if(!next_aabb.intersect(vbox)) { + // No intersection between the voxel + // and the entity's collision hull + continue; + } + + if(info->touch_type == world::voxel_touch::SOLID) { + // Solid touch type makes a collision + // response whenever it is encountered + velocity.value[d] = 0.0f; + touch_surface = info->surface; + return move_sign; + } + + // In case of other touch types, they + // are latched and the last ever touch + // type is then responded to + if(info->touch_type != world::voxel_touch::NOTHING) { + latch_touch = info->touch_type; + latch_values = info->touch_values; + latch_surface = info->surface; + latch_vbox = vbox; + continue; + } + } + } + + if(latch_touch != world::voxel_touch::NOTHING) { + if(latch_touch == world::voxel_touch::BOUNCE) { + const auto move_distance = math::abs(current_aabb.min[d] - next_aabb.min[d]); + const auto threshold = 2.0f * globals::fixed_frametime; + + if(move_distance > threshold) { + velocity.value[d] *= -latch_values[d]; + } else { + velocity.value[d] = 0.0f; + } + + touch_surface = latch_surface; + + return move_sign; + } + + if(latch_touch == world::voxel_touch::SINK) { + velocity.value[d] *= latch_values[d]; + touch_surface = latch_surface; + return move_sign; + } + } + + return 0; +} + +void entity::Collision::fixed_update(world::Dimension* dimension) +{ + // FIXME: this isn't particularly accurate considering + // some voxels might be passable and some other voxels + // might apply some slowing factor; what I might do in the + // future is to add a specific value to the voxel registry + // entries that would specify the amount of force we apply + // to prevent player movement inside a specific voxel, plus + // we shouldn't treat all voxels as full cubes if we want + // to support slabs, stairs and non-full liquid voxels in the future + + auto group = dimension->entities.group(entt::get); + + for(auto [entity, collision, transform, velocity] : group.each()) { + auto surface = world::voxel_surface::UNKNOWN; + auto vertical_move = vgrid_collide(dimension, 1, collision, transform, velocity, surface); + + if(dimension->entities.any_of(entity)) { + if(vertical_move == math::sign(dimension->get_gravity())) { + dimension->entities.emplace_or_replace(entity, entity::Grounded { surface }); + } else { + dimension->entities.remove(entity); + } + } else { + // The entity cannot be grounded because the component + // setup of said entity should not let it comprehend the + // concept of resting on the ground (it flies around) + dimension->entities.remove(entity); + } + + vgrid_collide(dimension, 0, collision, transform, velocity, surface); + vgrid_collide(dimension, 2, collision, transform, velocity, surface); + } +} diff --git a/game/shared/entity/collision.hh b/game/shared/entity/collision.hh new file mode 100644 index 0000000..536a066 --- /dev/null +++ b/game/shared/entity/collision.hh @@ -0,0 +1,25 @@ +#ifndef SHARED_ENTITY_COLLISION_HH +#define SHARED_ENTITY_COLLISION_HH 1 +#pragma once + +#include "core/math/aabb.hh" + +namespace world +{ +class Dimension; +} // namespace world + +namespace entity +{ +struct Collision final { + math::AABB aabb; + +public: + // NOTE: entity::Collision::fixed_update must be called + // before entity::Transform::fixed_update and entity::Velocity::fixed_update + // because both transform and velocity may be updated internally + static void fixed_update(world::Dimension* dimension); +}; +} // namespace entity + +#endif // SHARED_ENTITY_COLLISION_HH diff --git a/game/shared/entity/factory.cc b/game/shared/entity/factory.cc new file mode 100644 index 0000000..c98306a --- /dev/null +++ b/game/shared/entity/factory.cc @@ -0,0 +1,36 @@ +#include "shared/pch.hh" + +#include "shared/entity/factory.hh" + +#include "shared/entity/collision.hh" +#include "shared/entity/gravity.hh" +#include "shared/entity/head.hh" +#include "shared/entity/player.hh" +#include "shared/entity/transform.hh" +#include "shared/entity/velocity.hh" +#include "shared/world/dimension.hh" + +#include "shared/globals.hh" + +void entity::shared::create_player(world::Dimension* dimension, entt::entity entity) +{ + spdlog::debug("factory[{}]: assigning player components to {}", dimension->get_name(), static_cast(entity)); + + auto& collision = dimension->entities.emplace_or_replace(entity); + collision.aabb.min = glm::fvec3(-0.4f, -1.6f, -0.4f); + collision.aabb.max = glm::fvec3(+0.4f, +0.2f, +0.4f); + + auto& head = dimension->entities.emplace_or_replace(entity); + head.angles = glm::fvec3(0.0f, 0.0f, 0.0f); + head.offset = glm::fvec3(0.0f, 0.0f, 0.0f); + + dimension->entities.emplace_or_replace(entity); + + auto& transform = dimension->entities.emplace_or_replace(entity); + transform.chunk = chunk_pos(0, 2, 0); + transform.local = glm::fvec3(0.0f, 0.0f, 0.0f); + transform.angles = glm::fvec3(0.0f, 0.0f, 0.0f); + + auto& velocity = dimension->entities.emplace_or_replace(entity); + velocity.value = glm::fvec3(0.0f, 0.0f, 0.0f); +} diff --git a/game/shared/entity/factory.hh b/game/shared/entity/factory.hh new file mode 100644 index 0000000..c0e0716 --- /dev/null +++ b/game/shared/entity/factory.hh @@ -0,0 +1,15 @@ +#ifndef SHARED_ENTITY_FACTORY_HH +#define SHARED_ENTITY_FACTORY_HH 1 +#pragma once + +namespace world +{ +class Dimension; +} // namespace world + +namespace entity::shared +{ +void create_player(world::Dimension* dimension, entt::entity entity); +} // namespace entity::shared + +#endif // SHARED_ENTITY_FACTORY_HH diff --git a/game/shared/entity/gravity.cc b/game/shared/entity/gravity.cc new file mode 100644 index 0000000..f5dd145 --- /dev/null +++ b/game/shared/entity/gravity.cc @@ -0,0 +1,19 @@ +#include "shared/pch.hh" + +#include "shared/entity/gravity.hh" + +#include "shared/entity/stasis.hh" +#include "shared/entity/velocity.hh" +#include "shared/world/dimension.hh" + +#include "shared/globals.hh" + +void entity::Gravity::fixed_update(world::Dimension* dimension) +{ + auto fixed_acceleration = globals::fixed_frametime * dimension->get_gravity(); + auto group = dimension->entities.group(entt::get, entt::exclude); + + for(auto [entity, velocity] : group.each()) { + velocity.value.y += fixed_acceleration; + } +} diff --git a/game/shared/entity/gravity.hh b/game/shared/entity/gravity.hh new file mode 100644 index 0000000..064f92d --- /dev/null +++ b/game/shared/entity/gravity.hh @@ -0,0 +1,18 @@ +#ifndef SHARED_ENTITY_GRAVITY_HH +#define SHARED_ENTITY_GRAVITY_HH 1 +#pragma once + +namespace world +{ +class Dimension; +} // namespace world + +namespace entity +{ +struct Gravity final { +public: + static void fixed_update(world::Dimension* dimension); +}; +} // namespace entity + +#endif // SHARED_ENTITY_GRAVITY_HH diff --git a/game/shared/entity/grounded.hh b/game/shared/entity/grounded.hh new file mode 100644 index 0000000..22340db --- /dev/null +++ b/game/shared/entity/grounded.hh @@ -0,0 +1,16 @@ +#ifndef SHARED_ENTITY_GROUNDED +#define SHARED_ENTITY_GROUNDED 1 +#pragma once + +#include "shared/world/voxel_registry.hh" + +namespace entity +{ +// Assigned to entities which are grounded +// according to the collision and gravity system +struct Grounded final { + world::voxel_surface surface; +}; +} // namespace entity + +#endif // SHARED_ENTITY_GROUNDED diff --git a/game/shared/entity/head.hh b/game/shared/entity/head.hh new file mode 100644 index 0000000..1437207 --- /dev/null +++ b/game/shared/entity/head.hh @@ -0,0 +1,20 @@ +#ifndef SHARED_ENTITY_HEAD_HH +#define SHARED_ENTITY_HEAD_HH 1 +#pragma once + +namespace entity +{ +struct Head { + glm::fvec3 angles; + glm::fvec3 offset; +}; +} // namespace entity + +namespace entity::client +{ +// Client-side only - interpolated and previous head +struct HeadIntr final : public Head {}; +struct HeadPrev final : public Head {}; +} // namespace entity::client + +#endif // SHARED_ENTITY_HEAD_HH diff --git a/game/shared/entity/player.hh b/game/shared/entity/player.hh new file mode 100644 index 0000000..3c8be32 --- /dev/null +++ b/game/shared/entity/player.hh @@ -0,0 +1,10 @@ +#ifndef SHARED_ENTITY_PLAYER_HH +#define SHARED_ENTITY_PLAYER_HH 1 +#pragma once + +namespace entity +{ +struct Player final {}; +} // namespace entity + +#endif // SHARED_ENTITY_PLAYER_HH diff --git a/game/shared/entity/stasis.cc b/game/shared/entity/stasis.cc new file mode 100644 index 0000000..10e2e0c --- /dev/null +++ b/game/shared/entity/stasis.cc @@ -0,0 +1,19 @@ +#include "shared/pch.hh" + +#include "shared/entity/stasis.hh" + +#include "shared/entity/transform.hh" +#include "shared/world/dimension.hh" + +void entity::Stasis::fixed_update(world::Dimension* dimension) +{ + auto view = dimension->entities.view(); + + for(auto [entity, transform] : view.each()) { + if(dimension->find_chunk(transform.chunk)) { + dimension->entities.remove(entity); + } else { + dimension->entities.emplace_or_replace(entity); + } + } +} diff --git a/game/shared/entity/stasis.hh b/game/shared/entity/stasis.hh new file mode 100644 index 0000000..27c0131 --- /dev/null +++ b/game/shared/entity/stasis.hh @@ -0,0 +1,20 @@ +#ifndef SHARED_ENTITY_STASIS_HH +#define SHARED_ENTITY_STASIS_HH 1 +#pragma once + +namespace world +{ +class Dimension; +} // namespace world + +namespace entity +{ +// Attached to entities with transform values +// out of bounds in a specific dimension +struct Stasis final { +public: + static void fixed_update(world::Dimension* dimension); +}; +} // namespace entity + +#endif // SHARED_ENTITY_STASIS_HH diff --git a/game/shared/entity/transform.cc b/game/shared/entity/transform.cc new file mode 100644 index 0000000..379ddd5 --- /dev/null +++ b/game/shared/entity/transform.cc @@ -0,0 +1,33 @@ +#include "shared/pch.hh" + +#include "shared/entity/transform.hh" + +#include "shared/world/dimension.hh" + +#include "shared/const.hh" + +constexpr inline static void update_component(unsigned int dim, entity::Transform& component) +{ + if(component.local[dim] >= CHUNK_SIZE) { + component.local[dim] -= CHUNK_SIZE; + component.chunk[dim] += 1; + return; + } + + if(component.local[dim] < 0.0f) { + component.local[dim] += CHUNK_SIZE; + component.chunk[dim] -= 1; + return; + } +} + +void entity::Transform::fixed_update(world::Dimension* dimension) +{ + auto view = dimension->entities.view(); + + for(auto [entity, transform] : view.each()) { + update_component(0U, transform); + update_component(1U, transform); + update_component(2U, transform); + } +} diff --git a/game/shared/entity/transform.hh b/game/shared/entity/transform.hh new file mode 100644 index 0000000..b1fdcf4 --- /dev/null +++ b/game/shared/entity/transform.hh @@ -0,0 +1,34 @@ +#ifndef SHARED_ENTITY_TRANSFORM_HH +#define SHARED_ENTITY_TRANSFORM_HH 1 +#pragma once + +#include "shared/types.hh" + +namespace world +{ +class Dimension; +} // namespace world + +namespace entity +{ +struct Transform { + chunk_pos chunk; + glm::fvec3 local; + glm::fvec3 angles; + +public: + // Updates entity::Transform values so that + // the local translation field is always within + // local coodrinates; [floating-point precision] + static void fixed_update(world::Dimension* dimension); +}; +} // namespace entity + +namespace entity::client +{ +// Client-side only - interpolated and previous transform +struct TransformIntr final : public Transform {}; +struct TransformPrev final : public Transform {}; +} // namespace entity::client + +#endif // SHARED_ENTITY_TRANSFORM_HH diff --git a/game/shared/entity/velocity.cc b/game/shared/entity/velocity.cc new file mode 100644 index 0000000..9e40688 --- /dev/null +++ b/game/shared/entity/velocity.cc @@ -0,0 +1,18 @@ +#include "shared/pch.hh" + +#include "shared/entity/velocity.hh" + +#include "shared/entity/stasis.hh" +#include "shared/entity/transform.hh" +#include "shared/world/dimension.hh" + +#include "shared/globals.hh" + +void entity::Velocity::fixed_update(world::Dimension* dimension) +{ + auto group = dimension->entities.group(entt::get, entt::exclude); + + for(auto [entity, velocity, transform] : group.each()) { + transform.local += velocity.value * globals::fixed_frametime; + } +} diff --git a/game/shared/entity/velocity.hh b/game/shared/entity/velocity.hh new file mode 100644 index 0000000..5d4ec03 --- /dev/null +++ b/game/shared/entity/velocity.hh @@ -0,0 +1,23 @@ +#ifndef SHARED_ENTITY_VELOCITY_HH +#define SHARED_ENTITY_VELOCITY_HH 1 +#pragma once + +namespace world +{ +class Dimension; +} // namespace world + +namespace entity +{ +struct Velocity final { + glm::fvec3 value; + +public: + // Updates entities entity::Transform values + // according to velocities multiplied by fixed_frametime. + // NOTE: This system was previously called inertial + static void fixed_update(world::Dimension* dimension); +}; +} // namespace entity + +#endif // SHARED_VELOCITY_HH -- cgit