aboutsummaryrefslogtreecommitdiff
path: root/src/shared/movement.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/shared/movement.cc')
-rw-r--r--src/shared/movement.cc490
1 files changed, 0 insertions, 490 deletions
diff --git a/src/shared/movement.cc b/src/shared/movement.cc
deleted file mode 100644
index 985062c..0000000
--- a/src/shared/movement.cc
+++ /dev/null
@@ -1,490 +0,0 @@
-#include "shared/movement.hh"
-
-namespace shared {
-namespace movement {
-
-// Move a player into the correct neighbour chunk if they go over a border.
-static void shift_chunk(shared::player& player) noexcept {
- const bool g_x = (player.local_pos.x >= shared::world::chunk::WIDTH);
- const bool l_x = (player.local_pos.x < 0.0f);
- const bool g_z = (player.local_pos.z >= shared::world::chunk::WIDTH);
- const bool l_z = (player.local_pos.z < 0.0f);
-
- player.chunk_pos.x += g_x - l_x;
- player.local_pos.x += shared::world::chunk::WIDTH * (l_x - g_x);
- player.chunk_pos.z += g_z - l_z;
- player.local_pos.z += shared::world::chunk::WIDTH * (l_z - g_z);
-}
-
-constexpr float epsilon = 0.001f; // counteract arithmetic errors
-constexpr float epsilon2 = epsilon + 2 * epsilon * epsilon;
-
-bool intersect_aabbs(const aabb& a, const aabb& b) noexcept {
- if (a.max.x < b.min.x || a.min.x > b.max.x) {
- return false;
- }
- if (a.max.y < b.min.y || a.min.y > b.max.y) {
- return false;
- }
- if (a.max.z < b.min.z || a.min.z > b.max.z) {
- return false;
- }
- return true;
-}
-
-static glm::vec3 plane_to_normal(const int plane) {
- glm::vec3 normal{0.0f, 0.0f, 0.0f};
- normal[std::abs(plane) - 1] = std::signbit(plane) ? -1.0f : 1.0f;
- return normal;
-}
-
-std::optional<ray_aabb_ret> intersect_ray_aabb(const line& line,
- const aabb& aabb) noexcept {
- float tmin = -std::numeric_limits<float>::max();
- float tmax = std::numeric_limits<float>::max();
-
- int p = 0;
- for (int i = 0; i < 3; ++i) {
- if (std::abs(line.dir[i]) < epsilon) {
-
- // Ray is parallel to slab, no hit if origin not within slab.
- if (line.origin[i] < aabb.min[i] || line.origin[i] > aabb.max[i]) {
- return std::nullopt;
- }
-
- } else {
- // Intersection t value of ray with near and far plane of slab.
- const float ood = 1.0f / line.dir[i];
- float t1 = (aabb.min[i] - line.origin[i]) * ood;
- float t2 = (aabb.max[i] - line.origin[i]) * ood;
-
- if (t1 > t2) {
- std::swap(t1, t2);
- }
- auto old = tmin;
- tmin = std::max(tmin, t1);
- if (tmin != old) {
- p = (i + 1);
- }
- tmax = std::min(tmax, t2);
- if (tmin > tmax) {
- return std::nullopt;
- }
- }
- }
-
- if (tmin <= 0.0f) {
- return std::nullopt;
- }
-
- return ray_aabb_ret{.position = line.origin + line.dir * tmin,
- .time = tmin,
- .normal = plane_to_normal(p)};
-}
-
-std::optional<moving_aabb_ret>
-intersect_moving_aabbs(const moving_aabb& a, const moving_aabb& b) noexcept {
-
- if (intersect_aabbs(a.aabb, b.aabb)) {
- return std::nullopt;
- }
-
- const glm::vec3 velocity = b.velocity - a.velocity;
- float tfirst = 0.0f;
- float tlast = 10.0f;
- int p = 0;
-
- for (int i = 0; i < 3; ++i) {
- if (velocity[i] < 0.0f) {
- if (b.aabb.max[i] < a.aabb.min[i]) {
- return std::nullopt;
- }
- if (a.aabb.max[i] < b.aabb.min[i]) {
- const auto old = tfirst;
- tfirst = std::max((a.aabb.max[i] - b.aabb.min[i]) / velocity[i],
- tfirst);
- if (tfirst != old) {
- p = (i + 1);
- }
- }
- if (b.aabb.max[i] > a.aabb.min[i]) {
- tlast = std::min((a.aabb.min[i] - b.aabb.max[i]) / velocity[i],
- tlast);
- }
- } else if (velocity[i] > 0.0f) {
- if (b.aabb.min[i] > a.aabb.max[i]) {
- return std::nullopt;
- }
- if (b.aabb.max[i] < a.aabb.min[i]) {
- const auto old = tfirst;
- tfirst = std::max((a.aabb.min[i] - b.aabb.max[i]) / velocity[i],
- tfirst);
- if (tfirst != old) {
- p = -(i + 1);
- }
- }
- if (a.aabb.max[i] > b.aabb.min[i]) {
- tlast = std::min((a.aabb.max[i] - b.aabb.min[i]) / velocity[i],
- tlast);
- }
- } else {
- if (b.aabb.max[i] < a.aabb.min[i] ||
- b.aabb.min[i] > a.aabb.max[i]) {
- return std::nullopt;
- }
- }
- if (tfirst > tlast) {
- return std::nullopt;
- }
- }
- /*
- if (tfirst < 0.0f || tfirst > 1.0f) {
- return std::nullopt;
- }
- */
-
- return moving_aabb_ret{.time = tfirst, .normal = plane_to_normal(p)};
-}
-
-static std::optional<shared::world::block>
-get_ground(const aabb& player_aabb, const std::vector<blockinfo> blockinfos,
- bool (*delimit)(const enum shared::world::block::type) =
- shared::world::block::is_tangible) noexcept {
-
- const moving_aabb player_moving_aabb{.aabb = player_aabb,
- .velocity = {0.0f, -1.0f, 0.0f}};
- for (const auto& block_aabb : blockinfos) {
-
- if (!delimit(block_aabb.block)) {
- continue;
- }
-
- const struct moving_aabb block_moving_aabb {
- .aabb = block_aabb.aabb, .velocity = { 0.0f, 0.0f, 0.0f }
- };
-
- if (const auto intersect =
- intersect_moving_aabbs(player_moving_aabb, block_moving_aabb);
- intersect.has_value()) {
-
- if (intersect->time >= 1.0f) {
- continue;
- }
-
- if (intersect->time <= epsilon2) {
- return block_aabb.block;
- }
- }
- }
- return std::nullopt;
-}
-
-// Only returns ground if we can collide with it.
-static std::optional<shared::world::block>
-get_collidable_ground(const aabb& player_aabb,
- const std::vector<blockinfo> blockinfo) noexcept {
-
- return get_ground(player_aabb, blockinfo,
- shared::world::block::is_collidable);
-}
-
-// Recursively resolve collisions against blocks, up to n times.
-static void resolve_collisions(shared::player& player, aabb player_aabb,
- const std::vector<blockinfo>& blockinfos,
- const float deltatime,
- const int n = 3) noexcept {
-
- const moving_aabb player_moving_aabb{
- .aabb = player_aabb, .velocity = player.velocity * deltatime};
-
- std::optional<std::pair<moving_aabb_ret, shared::world::block>> collision;
- for (const auto& block_aabb : blockinfos) {
- if (!shared::world::block::is_collidable(block_aabb.block)) {
- continue;
- }
-
- const struct moving_aabb block_moving_aabb {
- .aabb = block_aabb.aabb, .velocity = { 0.0f, 0.0f, 0.0f }
- };
- const auto intersect =
- intersect_moving_aabbs(player_moving_aabb, block_moving_aabb);
-
- if (!intersect.has_value() || intersect->time > 1.0f) {
- continue;
- }
-
- // Update collision if it doesn't exist or if this one is closer.
- if (!collision.has_value() || intersect->time < collision->first.time) {
-
- collision = std::make_pair(intersect.value(), block_aabb.block);
- }
- }
-
- // No more collisions :)
- if (!collision.has_value()) {
- player.local_pos += player.velocity * deltatime;
- return;
- }
-
- const glm::vec3 collision_pos =
- collision->first.time * player.velocity * deltatime;
- const glm::vec3 offset = collision->first.normal * epsilon;
- const float backoff = glm::dot(player.velocity, collision->first.normal);
-
-#ifndef NDEBUG
- // clang-format off
- shared::print::debug("Collision with n = " + std::to_string(n) + "\n");
- shared::print::message(" Unresolved player position: {" + std::to_string(player.local_pos.x) + ", " + std::to_string(player.local_pos.y) + ", " + std::to_string(player.local_pos.z) + "}\n", false);
- shared::print::message(" Unresolved player velocity: {" + std::to_string(player.velocity.x) + ", " + std::to_string(player.velocity.y) + ", " + std::to_string(player.velocity.z) + "}\n", false);
- shared::print::message(" Collision delta vector: {" + std::to_string(collision_pos.x) + ", " + std::to_string(collision_pos.y) + ", " + std::to_string(collision_pos.z) + "}\n", false);
- shared::print::message(" Backoff: " + std::to_string(backoff) + "\n", false);
- shared::print::message(" On ground before collision: " + std::to_string(get_collidable_ground(player_aabb, blockinfos).has_value()) + "\n", false);
- shared::print::message(" Collision normal: {" + std::to_string(collision->first.normal.x) + ", " + std::to_string(collision->first.normal.y) + ", " + std::to_string(collision->first.normal.z) + "}\n", false);
- // clang-format on
-#endif
-
- // Quake engine's reflect velocity.
- for (int i = 0; i < 3; ++i) {
- player.velocity[i] -= collision->first.normal[i] * backoff;
- if (std::abs(player.velocity[i]) <= epsilon) {
- player.velocity[i] = 0.0f;
- }
- if (std::abs(collision_pos[i]) <= epsilon2) {
-
-#ifndef NDEBUG
- shared::print::message(
- " Ignored axis: " + std::to_string(i) + "\n", false);
-#endif
-
- continue;
- }
- player.local_pos[i] += collision_pos[i] - offset[i];
- player_aabb.min[i] += collision_pos[i] - offset[i];
- player_aabb.max[i] += collision_pos[i] - offset[i];
- }
-
-#ifndef NDEBUG
- // clang-format off
- shared::print::message(" Resolved player position: {" + std::to_string(player.local_pos.x) + ", " + std::to_string(player.local_pos.y) + ", " + std::to_string(player.local_pos.z) + "}\n", false);
- shared::print::message(" Resolved player velocity: {" + std::to_string(player.velocity.x) + ", " + std::to_string(player.velocity.y) + ", " + std::to_string(player.velocity.z) + "}\n", false);
- shared::print::message(" On ground after collision: " + std::to_string(get_collidable_ground(player_aabb, blockinfos).has_value()) + "\n", false);
- // clang-format on
-#endif
-
- if (n <= 0) {
- return;
- }
- resolve_collisions(player, player_aabb, blockinfos,
- deltatime - collision->first.time * deltatime, n - 1);
-}
-
-// Returns the highest friction value of a block that intersects an aabb.
-static std::optional<float>
-get_intersect_friction(const aabb& aabb,
- const std::vector<blockinfo>& blockinfos) noexcept {
- std::optional<float> greatest_friction;
- for (const auto& block_aabb : blockinfos) {
- if (!intersect_aabbs(aabb, block_aabb.aabb)) {
- continue;
- }
- const float friction =
- shared::world::block::get_friction(block_aabb.block);
-
- if (!greatest_friction.has_value() ||
- friction > greatest_friction.value()) {
-
- greatest_friction = friction;
- }
- }
-
- return greatest_friction;
-}
-
-// In air is slightly more complicated becase of edge cases involving water.
-static bool is_in_air(
- const aabb& aabb, const std::vector<blockinfo>& blockinfos,
- const std::optional<shared::world::block>& collidable_ground) noexcept {
- if (!std::all_of(std::begin(blockinfos), std::end(blockinfos),
- [&aabb](const auto& ba) {
- if (!intersect_aabbs(aabb, ba.aabb)) {
- return true;
- }
- return !shared::world::block::is_tangible(ba.block);
- })) {
- return false;
- }
- if (collidable_ground.has_value()) {
- return false;
- }
- return true;
-}
-
-static bool is_in_liquid(const aabb& aabb,
- const std::vector<blockinfo>& blockinfos) noexcept {
- return std::any_of(std::begin(blockinfos), std::end(blockinfos),
- [&aabb](const auto& ba) {
- if (!intersect_aabbs(aabb, ba.aabb)) {
- return false;
- }
- return shared::world::block::is_liquid(ba.block);
- });
-}
-
-static bool
-can_jump(const std::optional<shared::world::block>& collidable_ground,
- const bool in_liquid) noexcept {
- if (collidable_ground.has_value()) {
- return true;
- }
- if (in_liquid) {
- return true;
- }
- return false;
-}
-
-// Move state is a cache of expensive operations that we use more than once.
-struct move_state {
- std::optional<shared::world::block> ground;
- std::optional<shared::world::block> collidable_ground;
- bool is_in_air;
- bool is_in_liquid;
- bool can_jump;
-};
-static move_state
-make_move_state(const aabb& player_aabb,
- const std::vector<blockinfo>& blockinfos) noexcept {
-
- const auto get_ground_ret = get_ground(player_aabb, blockinfos);
- const auto get_collidable_ground_ret =
- get_collidable_ground(player_aabb, blockinfos);
- const bool is_in_air_ret =
- is_in_air(player_aabb, blockinfos, get_collidable_ground_ret);
- const bool is_in_liquid_ret = is_in_liquid(player_aabb, blockinfos);
- const bool can_jump_ret =
- can_jump(get_collidable_ground_ret, is_in_liquid_ret);
-
- return {.ground = get_ground_ret,
- .collidable_ground = get_collidable_ground_ret,
- .is_in_air = is_in_air_ret,
- .is_in_liquid = is_in_liquid_ret,
- .can_jump = can_jump_ret};
-}
-
-static float get_jump_magnitude(const shared::player& player, const aabb& aabb,
- const std::vector<blockinfo>& blockinfos,
- const struct move_state& move_state) noexcept {
- if (!move_state.can_jump) {
- return 0.0f;
- }
- if (move_state.is_in_liquid) {
- const struct aabb jump_aabb = {
- .min = glm::vec3{0.0f, aabb.min.y + aabb.max.y / 5.0f, 0.0f},
- .max = aabb.max};
- if (player.velocity.y > 0.0f &&
- is_in_air(jump_aabb, blockinfos,
- get_collidable_ground(jump_aabb, blockinfos))) {
- return 1.0f;
- }
- return 0.5f;
- }
- if (player.velocity.y < 0.0f) {
- return 0.0f;
- }
- return 8.0f;
-}
-
-void move(shared::player& player, const std::vector<blockinfo>& blockinfos,
- const float deltatime) noexcept {
-
- constexpr glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f);
- const glm::vec3 front = shared::math::angle_to_dir(player.viewangles);
- const glm::vec3 right = glm::normalize(glm::cross(front, up));
-
- constexpr aabb player_aabb = {.min = {-shared::player::HALFWIDTH + epsilon,
- 0.0f + epsilon,
- -shared::player::HALFWIDTH + epsilon},
- .max = {shared::player::HALFWIDTH - epsilon,
- shared::player::HEIGHT - epsilon,
- shared::player::HALFWIDTH - epsilon}};
-
- const auto move_state = make_move_state(player_aabb, blockinfos);
-
- // Some of velocity we want to add or remove should be scaled by our
- // tickrate (eg, walking), while others such as jumping should not.
-
- const glm::vec3 scaled_acceleration = [&]() -> glm::vec3 {
- glm::vec3 acceleration = {0.0f, 0.0f, 0.0f};
- if (player.commands & shared::player::mask::forward) {
- acceleration += front;
- }
- if (player.commands & shared::player::mask::left) {
- acceleration -= right;
- }
- if (player.commands & shared::player::mask::backward) {
- acceleration -= front;
- }
- if (player.commands & shared::player::mask::right) {
- acceleration += right;
- }
- acceleration.y = 0.0f;
- if (acceleration != glm::vec3{}) {
- acceleration = glm::normalize(acceleration);
- }
- acceleration *= 1.75f;
- if (player.commands & shared::player::mask::sprint) {
- acceleration *= 1.25f;
- }
-
- // Increase movement when on the ground - heavily reduced with friction.
- if (!move_state.is_in_air) {
- acceleration *= 30.0f;
- }
-
- // Gravity.
- if (!move_state.collidable_ground.has_value()) {
- acceleration -= 25.0f * up;
- }
-
- return acceleration;
- }();
-
- const glm::vec3 constant_acceleration = [&]() -> glm::vec3 {
- glm::vec3 acceleration = {0.0f, 0.0f, 0.0f};
- // Jumping - we have to adjust our magnitude based on our environment.
- // Technically swimming is just lots of small jumps.
- if (player.commands & shared::player::mask::jump) {
- acceleration += get_jump_magnitude(player, player_aabb, blockinfos,
- move_state) *
- up;
- }
- return acceleration;
- }();
-
- // Our deceleration is the max friction of what we're in and what we're on.
- const glm::vec3 scaled_deceleration = [&]() -> glm::vec3 {
- const auto drag = get_intersect_friction(player_aabb, blockinfos);
-
- float max_friction = 0.0f;
- if (move_state.collidable_ground.has_value()) {
- const float friction = shared::world::block::get_friction(
- move_state.collidable_ground.value());
- if (drag.has_value()) {
- max_friction = std::max(friction, drag.value());
- }
- } else if (drag.has_value()) {
- max_friction = drag.value();
- }
-
- return player.velocity * max_friction;
- }();
-
- player.velocity += (scaled_acceleration - scaled_deceleration) * deltatime;
- player.velocity += constant_acceleration;
-
- resolve_collisions(player, player_aabb, blockinfos, deltatime);
-
- shift_chunk(player);
-}
-
-} // namespace movement
-} // namespace shared