From 7b061aee2b4c15d242bb9f18101d6b9ea776c5cd Mon Sep 17 00:00:00 2001 From: Mel Date: Thu, 1 Feb 2024 23:56:57 +0100 Subject: Walking movement --- CMakeLists.txt | 1 + src/Common/Casts.hpp | 3 ++ src/Entities/Player.cpp | 116 +++++++++++++++++++++++++++++++++++++++--------- src/Entities/Player.hpp | 21 ++++++++- src/Math/AABB.cpp | 1 + src/Math/AABB.hpp | 2 + 6 files changed, 123 insertions(+), 21 deletions(-) create mode 100644 src/Common/Casts.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5227274..e98c87c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -62,6 +62,7 @@ add_executable(meowcraft src/Math/Ray.hpp src/Common/Lambda.hpp src/Math/Functions.hpp + src/Common/Casts.hpp ) target_link_libraries(meowcraft glfw GLEW::GLEW) diff --git a/src/Common/Casts.hpp b/src/Common/Casts.hpp new file mode 100644 index 0000000..c2c7088 --- /dev/null +++ b/src/Common/Casts.hpp @@ -0,0 +1,3 @@ +#pragma once + +#define TO(type, value) (static_cast(value)) \ No newline at end of file diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp index f87f2f9..66f08c6 100644 --- a/src/Entities/Player.cpp +++ b/src/Entities/Player.cpp @@ -1,30 +1,26 @@ #include "Player.hpp" +#include "../Common/Casts.hpp" #include namespace MC::Entities { void Player::update(const Time& time, GFX::Window& window, GFX::Camera& camera, World::World& world) { - auto origin = m_transform.position(); - auto r = window.mouse_delta(); - - auto key = [&](Int k) -> Real { return window.key(k, GLFW_PRESS); }; + auto const input_direction = directional_input(window); - Real x = key(GLFW_KEY_D) - key(GLFW_KEY_A); - Real y = key(GLFW_KEY_SPACE) - key(GLFW_KEY_LEFT_SHIFT); - Real z = key(GLFW_KEY_S) - key(GLFW_KEY_W); - Real boost = key(GLFW_KEY_LEFT_CONTROL) * 75.0f; + auto destination = movement(window, time, input_direction); + auto const [position, blocked_axes] = process_collisions(world, m_transform.position(), destination); - auto move_speed = (10.0f + boost) * time.delta(); - auto rotation_speed = 0.1f; + destination = position; + m_on_ground = blocked_axes.y().negative; - auto direction = m_transform.right() * x + Vec3(0, y, 0) + m_transform.forward() * z; - auto destination = origin + direction * move_speed; - - destination = process_collisions(world, origin, destination); + for (UInt axis = 0; axis < 3; axis++) { + if (blocked_axes[axis].positive) m_velocity[axis] = std::max(m_velocity[axis], 0.0); + if (blocked_axes[axis].negative) m_velocity[axis] = std::min(m_velocity[axis], 0.0); + } move_to(destination); - rotate({r.y() * rotation_speed, r.x() * rotation_speed, 0.0f}); + rotate(rotational_input(window)); update_camera_position(camera); } @@ -50,10 +46,57 @@ AABB Player::bounds() const { return bounding_box_for_position(m_transform.position()); } +Position::World Player::movement(GFX::Window& window, const Time& time, Vec3 input_direction) { + m_velocity = m_flying + ? flying_velocity(window, time, input_direction) + : walking_velocity(window, time, input_direction); + + return m_transform.position() + m_velocity; +} + +Vec3 Player::walking_velocity(GFX::Window& window, const Time& time, Vec3 input_direction) { + constexpr auto base_move_speed = 8.0; + constexpr auto gravity = 0.6; + + auto walking_direction = m_transform.right() * input_direction.x() + m_transform.forward() * input_direction.z(); + walking_direction.y() = 0; + if (!walking_direction.is_zero()) walking_direction = walking_direction.normalize(); + + auto const walking_velocity = walking_direction * base_move_speed * time.delta(); + + auto vertical_velocity = 0.0; + if (m_on_ground) { + if (window.key(GLFW_KEY_SPACE, GLFW_PRESS)) { + vertical_velocity = 0.16; + m_on_ground = false; + } + } else { + // TODO: This integration depends on frame delta. + vertical_velocity = m_velocity.y() - gravity * time.delta(); + } + + return { + walking_velocity.x(), + vertical_velocity, + walking_velocity.z(), + }; +} + +Vec3 Player::flying_velocity(GFX::Window& window, const Time& time, Vec3 input_direction) { + constexpr auto base_move_speed = 10.0; + + Real const boost = TO(Real, window.key(GLFW_KEY_LEFT_CONTROL, GLFW_PRESS)) * 75.0; + + auto const [x, y, z] = input_direction.elements; + auto const direction = m_transform.right() * x + Vec3(0, y, 0) + m_transform.forward() * z; + + return direction * (base_move_speed + boost) * time.delta(); +} + #define STUCK_THRESHOLD 100 -Position::World Player::process_collisions(World::World& world, Position::World from, Position::World to) { - if (from.mostly_equal(to)) return to; +Player::ProcessCollisionsResult Player::process_collisions(World::World& world, Position::World from, Position::World to) { + if (from.mostly_equal(to)) return {to, {}}; auto current_box = bounding_box_for_position(from); @@ -70,6 +113,8 @@ Position::World Player::process_collisions(World::World& world, Position::World // If there are no more responses, we have no collisions, // and the player can move freely to the proposed position. + Vector<3, BlockedAxis> blocked_axes; + struct Response { AABB::CollisionResponse response; Real distance_from_entity_squared; @@ -91,7 +136,7 @@ Position::World Player::process_collisions(World::World& world, Position::World } } - if (responses.empty()) return to; + if (responses.empty()) return {to, blocked_axes}; std::sort(responses.begin(), responses.end(), [=](const Response& a, const Response& b) -> bool { return std::tie(a.magnitude_squared, a.distance_from_entity_squared) < std::tie(b.magnitude_squared, b.distance_from_entity_squared); @@ -100,12 +145,26 @@ Position::World Player::process_collisions(World::World& world, Position::World // TODO: This applies the entire response, even though ideally we'd apply it in two parts, // since technically the total velocity is a diagonal of the two components. // This should only be a marginal issue, though. - to = from + responses[0].response.v_to_collision + responses[0].response.v_slide; + auto response = responses[0].response; + to = from + response.v_to_collision + response.v_slide; + + + auto check_axis = [&](Real axis_normal, BlockedAxis& axis) -> Bool { + if (axis_normal < -0.5) { axis.negative = true; return true; } + if (axis_normal > 0.5) { axis.positive = true; return true; } + return false; + }; + + check_axis(response.normal.x(), blocked_axes.x()) || + check_axis(response.normal.y(), blocked_axes.y()) || + check_axis(response.normal.z(), blocked_axes.z()); + responses.clear(); } // We got stuck, don't move. - return from; + // Also, if we're stuck, we're also presumably touching the ground on any axis. + return {from, {BlockedAxis{true, true}, BlockedAxis{true, true}, BlockedAxis{true, true}}}; } std::vector Player::terrain_collision_domain( @@ -152,6 +211,23 @@ void Player::update_camera_position(GFX::Camera& camera) { camera.set_angles(m_transform.rotation()); } +Vec3 Player::directional_input(GFX::Window& window) { + auto key = [&](Int k) -> Real { return window.key(k, GLFW_PRESS); }; + + Real x = key(GLFW_KEY_D) - key(GLFW_KEY_A); + Real y = key(GLFW_KEY_SPACE) - key(GLFW_KEY_LEFT_SHIFT); + Real z = key(GLFW_KEY_S) - key(GLFW_KEY_W); + + return {x, y, z}; +} + +Rotation Player::rotational_input(GFX::Window& window) { + constexpr auto base_rotation_speed = 0.1f; + + auto r = window.mouse_delta(); + return {r.y() * base_rotation_speed, r.x() * base_rotation_speed, 0.0f}; +} + AABB Player::bounding_box_for_position(Position::World position) { Vec3 box_start = { position.x() - s_bounds.max.x() / 2, diff --git a/src/Entities/Player.hpp b/src/Entities/Player.hpp index 95e3d64..5d70559 100644 --- a/src/Entities/Player.hpp +++ b/src/Entities/Player.hpp @@ -25,16 +25,35 @@ public: AABB bounds() const; private: - static Position::World process_collisions(World::World& world, Position::World from, Position::World to); + struct BlockedAxis { + Bool positive, negative; + }; + + struct ProcessCollisionsResult { + Position::World position; + Vector<3, BlockedAxis> blocked_axes; + }; + static ProcessCollisionsResult process_collisions(World::World& world, Position::World from, Position::World to); static std::vector terrain_collision_domain(Position::World from, Position::World to, World::World& world); + Position::World movement(GFX::Window& window, const Time& time, Vec3 input_direction); + Vec3 walking_velocity(GFX::Window& window, const Time& time, Vec3 input_direction); + Vec3 flying_velocity(GFX::Window& window, const Time& time, Vec3 input_direction); + void update_camera_position(GFX::Camera& camera); + static Vec3 directional_input(GFX::Window& window); + static Rotation rotational_input(GFX::Window& window); + // Creates a bounding box where `position` is at the center of the bottom face. static AABB bounding_box_for_position(Position::World position); // Returns position of the center of the bottom face of `box`. static Position::World position_for_bounding_box(AABB box); + Bool m_on_ground = false; + Bool m_flying = false; + + Vec3 m_velocity{}; Transform m_transform; static inline AABB s_bounds{{0.35, 1.8, 0.35}}; }; diff --git a/src/Math/AABB.cpp b/src/Math/AABB.cpp index b16960e..7f2c05d 100644 --- a/src/Math/AABB.cpp +++ b/src/Math/AABB.cpp @@ -29,5 +29,6 @@ AABB::CollisionResponse AABB::collision_response(Vector<3> v, AABB against) cons return { .v_to_collision = raycast.point - origin, .v_slide = projected_velocity, + .normal = raycast.normal, }; } diff --git a/src/Math/AABB.hpp b/src/Math/AABB.hpp index b50ba56..b298126 100644 --- a/src/Math/AABB.hpp +++ b/src/Math/AABB.hpp @@ -83,6 +83,8 @@ struct AABB { // Velocity sliding along the collision plane. // Projection of the fraction of the original velocity that was blocked by collision, onto the collision plane. Vec3 v_slide; + // Normal of the collision plane. + Vec3 normal; }; CollisionResponse collision_response(Vector<3> v, AABB against) const; -- cgit 1.4.1