From c1cb941b590766b993efcab36277f15cd2bb9ac5 Mon Sep 17 00:00:00 2001 From: Hypernoot Date: Fri, 30 May 2025 21:40:24 +0200 Subject: [PATCH 1/5] Wind: implement something like air drag formula --- src/object/wind.cpp | 29 +++++++++++++++++++++++++---- src/object/wind.hpp | 5 ++++- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/src/object/wind.cpp b/src/object/wind.cpp index 24fa32a6297..671ed6433a1 100644 --- a/src/object/wind.cpp +++ b/src/object/wind.cpp @@ -28,6 +28,7 @@ #include "object/sprite_particle.hpp" #include "sprite/sprite.hpp" #include "sprite/sprite_manager.hpp" +#include "supertux/physic.hpp" #include "supertux/sector.hpp" #include "util/reader_mapping.hpp" #include "video/drawing_context.hpp" @@ -132,6 +133,25 @@ Wind::draw(DrawingContext& context) } } +Vector +Wind::get_induced_acceleration(const MovingObject* other, const Physic& physic) +{ + const float coef = 1/28.8; + const Vector rel_speed = speed - physic.get_velocity(); + if (rel_speed.x == 0 && rel_speed.y == 0) { + return rel_speed; // no relative movement = no drag = no force = no acceleration + } + const float v2 = glm::length2(rel_speed); + const float v = std::sqrt(v2); + // "length" of the object in the direction of the wind + const float l = other->get_bbox().get_width() * std::fabs(rel_speed.x / v) + + other->get_bbox().get_height() * std::fabs(rel_speed.y / v); + // induced acceleration + const float a = coef * v2 / l; + return a * (rel_speed / v); +} + + HitResponse Wind::collision(MovingObject& other, const CollisionHit& ) { @@ -141,7 +161,8 @@ Wind::collision(MovingObject& other, const CollisionHit& ) if (player && affects_player) { player->override_velocity(); - if (!player->on_ground()) + player->add_velocity(get_induced_acceleration(player, player->get_physic()) * dt_sec, speed); + /*if (!player->on_ground()) { player->add_velocity(speed * acceleration * dt_sec, speed); } @@ -156,19 +177,19 @@ Wind::collision(MovingObject& other, const CollisionHit& ) // When on ground, get blown slightly differently, but the max speed is less than it would be otherwise seen as we take "friction" into account player->add_velocity((Vector(speed.x, 0) * 0.1f) * (acceleration+1), (Vector(speed.x, speed.y) * 0.5f)); } - } + }*/ } auto badguy = dynamic_cast(&other); if (badguy && affects_badguys && badguy->can_be_affected_by_wind()) { - badguy->add_wind_velocity(speed * acceleration * dt_sec, speed); + badguy->add_wind_velocity(get_induced_acceleration(badguy, badguy->get_physic()) * dt_sec, speed); } auto rock = dynamic_cast(&other); if (rock && affects_objects) { - rock->add_wind_velocity(speed * acceleration * dt_sec, speed); + rock->add_wind_velocity(get_induced_acceleration(rock, rock->get_physic()) * dt_sec, speed); } return ABORT_MOVE; diff --git a/src/object/wind.hpp b/src/object/wind.hpp index 920ca8b7034..1fa2e33da7e 100644 --- a/src/object/wind.hpp +++ b/src/object/wind.hpp @@ -21,6 +21,7 @@ #include "video/layer.hpp" +class Physic; class ReaderMapping; /** Defines an area that will gently push Players in one direction */ @@ -74,7 +75,7 @@ class Wind final : public MovingObject int m_layer; bool blowing; /**< true if wind is currently switched on */ Vector speed; - float acceleration; + float acceleration; // legacy value Vector new_size; float dt_sec; /**< stores last dt_sec gotten at update() */ @@ -84,6 +85,8 @@ class Wind final : public MovingObject bool affects_player; /**< whether the wind can affect the player: useful for cinematic wind */ bool fancy_wind; bool particles_enabled; + + Vector get_induced_acceleration(const MovingObject* other, const Physic& physic); private: Wind(const Wind&) = delete; From a8db067719ad288dd4f34ab6741ddca3204eadcc Mon Sep 17 00:00:00 2001 From: Hypernoot Date: Fri, 30 May 2025 22:28:54 +0200 Subject: [PATCH 2/5] Player: fix add_velocity [ci-skip] --- src/object/player.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/object/player.cpp b/src/object/player.cpp index 328e8573b2a..22e82a71f15 100644 --- a/src/object/player.cpp +++ b/src/object/player.cpp @@ -2585,6 +2585,13 @@ Player::add_velocity(const Vector& velocity) void Player::add_velocity(const Vector& velocity, const Vector& end_speed) { + const Vector new_speed = m_physic.get_velocity() + velocity; + if (glm::dot(velocity, end_speed - new_speed) < 0) { // Do we overshot the end_speed? + m_physic.set_velocity(end_speed); + } else { + m_physic.set_velocity(new_speed); + } + /* if (end_speed.x > 0) m_physic.set_velocity_x(std::min(m_physic.get_velocity_x() + velocity.x, end_speed.x)); if (end_speed.x < 0) @@ -2593,6 +2600,7 @@ Player::add_velocity(const Vector& velocity, const Vector& end_speed) m_physic.set_velocity_y(std::min(m_physic.get_velocity_y() + velocity.y, end_speed.y)); if (end_speed.y < 0) m_physic.set_velocity_y(std::max(m_physic.get_velocity_y() + velocity.y, end_speed.y)); + */ } void From bbaacec6e4f34424113e94600ee9e9f84fa0162f Mon Sep 17 00:00:00 2001 From: Hypernoot Date: Sat, 31 May 2025 10:18:03 +0200 Subject: [PATCH 3/5] Physic: use vector form and add functions to accelerate with overshot prevention --- src/supertux/physic.cpp | 54 +++++++++++++++++++++++++++++++++-------- src/supertux/physic.hpp | 39 +++++++++++++++-------------- 2 files changed, 65 insertions(+), 28 deletions(-) diff --git a/src/supertux/physic.cpp b/src/supertux/physic.cpp index a5e58bce29a..7c16ecd400b 100644 --- a/src/supertux/physic.cpp +++ b/src/supertux/physic.cpp @@ -20,8 +20,8 @@ #include "supertux/sector.hpp" Physic::Physic() : - ax(0), ay(0), - vx(0), vy(0), + a(0, 0), + v(0, 0), gravity_enabled_flag(true), gravity_modifier(1.0f) { @@ -30,22 +30,20 @@ Physic::Physic() : void Physic::reset() { - ax = ay = vx = vy = 0; + a.x = a.y = v.x = v.y = 0; gravity_enabled_flag = true; } void Physic::set_velocity(const Vector& vector) { - vx = vector.x; - vy = vector.y; + v = vector; } void Physic::set_acceleration(const Vector& vector) { - ax = vector.x; - ay = vector.y; + a = vector; } Vector @@ -56,11 +54,47 @@ Physic::get_movement(float dt_sec) // Semi-implicit Euler integration // with constant acceleration, this will result in a position delta of // v t + .5 a t (t+dt_sec) at total time t - vx += ax * dt_sec; - vy += (ay + grav) * dt_sec; - Vector result(vx * dt_sec, vy * dt_sec); + v.x += a.x * dt_sec; + v.y += (a.y + grav) * dt_sec; + Vector result(v.x * dt_sec, v.y * dt_sec); return result; } +void +Physic::accelerate(const Vector& acceleration, float dt_sec, const Vector& target_velocity) +{ + const Vector v_contr = acceleration * dt_sec; + const Vector new_v = v + v_contr; + if (glm::dot(v_contr, target_velocity - new_v) < 0) { + v = target_velocity; // overshot detected, trimming velocity + } else { + v = new_v; + } +} + +void +Physic::accelerate_x(float acceleration, float dt_sec, float target_velocity) +{ + const float v_contr = acceleration * dt_sec; + const float new_v = v.x + v_contr; + if (v_contr * (target_velocity - new_v) < 0) { + v.x = target_velocity; + } else { + v.x = new_v; + } +} + +void +Physic::accelerate_y(float acceleration, float dt_sec, float target_velocity) +{ + const float v_contr = acceleration * dt_sec; + const float new_v = v.y + v_contr; + if (v_contr * (target_velocity - new_v) < 0) { + v.y = target_velocity; + } else { + v.y = new_v; + } +} + /* EOF */ diff --git a/src/supertux/physic.hpp b/src/supertux/physic.hpp index 83e65d977a3..981388a8748 100644 --- a/src/supertux/physic.hpp +++ b/src/supertux/physic.hpp @@ -35,21 +35,20 @@ class Physic final /// Sets velocity to a fixed value. inline void set_velocity(float nvx, float nvy) { - vx = nvx; - vy = nvy; + v = Vector(nvx, nvy); } void set_velocity(const Vector& vector); - inline void set_velocity_x(float nvx) { vx = nvx; } - inline void set_velocity_y(float nvy) { vy = nvy; } + inline void set_velocity_x(float nvx) { v.x = nvx; } + inline void set_velocity_y(float nvy) { v.y = nvy; } /// Velocity inversion. - void inverse_velocity_x() { vx = -vx; } - void inverse_velocity_y() { vy = -vy; } + void inverse_velocity_x() { v.x = -v.x; } + void inverse_velocity_y() { v.y = -v.y; } - inline Vector get_velocity() const { return Vector(vx, vy); } - inline float get_velocity_x() const { return vx; } - inline float get_velocity_y() const { return vy; } + inline Vector get_velocity() const { return v; } + inline float get_velocity_x() const { return v.x; } + inline float get_velocity_y() const { return v.y; } /// Set acceleration. /** Sets acceleration applied to the object. (Note that gravity is @@ -57,17 +56,16 @@ class Physic final */ inline void set_acceleration(float nax, float nay) { - ax = nax; - ay = nay; + a = Vector(nax, nay); } void set_acceleration(const Vector& vector); - inline void set_acceleration_x(float nax) { ax = nax; } - inline void set_acceleration_y(float nay) { ay = nay; } + inline void set_acceleration_x(float nax) { a.x = nax; } + inline void set_acceleration_y(float nay) { a.y = nay; } - inline Vector get_acceleration() const { return Vector(ax, ay); } - inline float get_acceleration_x() const { return ax; } - inline float get_acceleration_y() const { return ay; } + inline Vector get_acceleration() const { return a; } + inline float get_acceleration_x() const { return a.x; } + inline float get_acceleration_y() const { return a.y; } /// Enables or disables handling of gravity. inline void enable_gravity(bool enable) { gravity_enabled_flag = enable; } @@ -79,13 +77,18 @@ class Physic final inline float get_gravity_modifier() const { return gravity_modifier; } Vector get_movement(float dt_sec); + + // Accelerate the object but prevent overshooting the target velocity + void accelerate(const Vector& acceleration, float dt_sec, const Vector& target_velocity); + void accelerate_x(float acceleration, float dt_sec, float target_velocity); + void accelerate_y(float acceleration, float dt_sec, float target_velocity); private: /** horizontal and vertical acceleration */ - float ax, ay; + Vector a; /** horizontal and vertical velocity */ - float vx, vy; + Vector v; /** should we respect gravity in our calculations? */ bool gravity_enabled_flag; From dcc71b49f02b23ee2d835ec853cd2190570c57fe Mon Sep 17 00:00:00 2001 From: Hypernoot Date: Sat, 31 May 2025 10:19:40 +0200 Subject: [PATCH 4/5] Wind: Influence physics directly --- src/object/wind.cpp | 21 +++++++++++++++------ src/object/wind.hpp | 5 ++++- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/object/wind.cpp b/src/object/wind.cpp index 671ed6433a1..ec8fee5bae5 100644 --- a/src/object/wind.cpp +++ b/src/object/wind.cpp @@ -134,7 +134,7 @@ Wind::draw(DrawingContext& context) } Vector -Wind::get_induced_acceleration(const MovingObject* other, const Physic& physic) +Wind::get_induced_acceleration(const Rectf& bbox, const Physic& physic) const { const float coef = 1/28.8; const Vector rel_speed = speed - physic.get_velocity(); @@ -144,13 +144,19 @@ Wind::get_induced_acceleration(const MovingObject* other, const Physic& physic) const float v2 = glm::length2(rel_speed); const float v = std::sqrt(v2); // "length" of the object in the direction of the wind - const float l = other->get_bbox().get_width() * std::fabs(rel_speed.x / v) - + other->get_bbox().get_height() * std::fabs(rel_speed.y / v); + const float l = bbox.get_width() * std::fabs(rel_speed.x / v) + + bbox.get_height() * std::fabs(rel_speed.y / v); // induced acceleration const float a = coef * v2 / l; return a * (rel_speed / v); } +void +Wind::accelerate_object(const MovingObject* other, Physic& physic) +{ + const Vector acceleration = get_induced_acceleration(other->get_bbox(), physic); + physic.accelerate(acceleration, dt_sec, speed); +} HitResponse Wind::collision(MovingObject& other, const CollisionHit& ) @@ -161,7 +167,8 @@ Wind::collision(MovingObject& other, const CollisionHit& ) if (player && affects_player) { player->override_velocity(); - player->add_velocity(get_induced_acceleration(player, player->get_physic()) * dt_sec, speed); + //player->add_velocity(get_induced_acceleration(player, player->get_physic()) * dt_sec, speed); + accelerate_object(player, player->get_physic()); /*if (!player->on_ground()) { player->add_velocity(speed * acceleration * dt_sec, speed); @@ -183,13 +190,15 @@ Wind::collision(MovingObject& other, const CollisionHit& ) auto badguy = dynamic_cast(&other); if (badguy && affects_badguys && badguy->can_be_affected_by_wind()) { - badguy->add_wind_velocity(get_induced_acceleration(badguy, badguy->get_physic()) * dt_sec, speed); + //badguy->add_wind_velocity(get_induced_acceleration(badguy, badguy->get_physic()) * dt_sec, speed); + accelerate_object(badguy, badguy->get_physic()); } auto rock = dynamic_cast(&other); if (rock && affects_objects) { - rock->add_wind_velocity(get_induced_acceleration(rock, rock->get_physic()) * dt_sec, speed); + //rock->add_wind_velocity(get_induced_acceleration(rock, rock->get_physic()) * dt_sec, speed); + accelerate_object(rock, rock->get_physic()); } return ABORT_MOVE; diff --git a/src/object/wind.hpp b/src/object/wind.hpp index 1fa2e33da7e..e83059dd897 100644 --- a/src/object/wind.hpp +++ b/src/object/wind.hpp @@ -71,6 +71,9 @@ class Wind final : public MovingObject */ virtual int get_layer() const override { return m_layer; } + // Returns induced acceleration onto an object, approximated from maximal speeds and accelerations + Vector get_induced_acceleration(const Rectf& bbox, const Physic& physic) const; + private: int m_layer; bool blowing; /**< true if wind is currently switched on */ @@ -86,7 +89,7 @@ class Wind final : public MovingObject bool fancy_wind; bool particles_enabled; - Vector get_induced_acceleration(const MovingObject* other, const Physic& physic); + void accelerate_object(const MovingObject* other, Physic& physic); private: Wind(const Wind&) = delete; From e910511d0df77ba08bfafdb7dfad0a18d91b2a4b Mon Sep 17 00:00:00 2001 From: Hypernoot Date: Sat, 31 May 2025 10:20:09 +0200 Subject: [PATCH 5/5] Player: tweak ground friction --- src/object/player.cpp | 46 +++++++++++++++++++------------------------ src/object/player.hpp | 10 +++++----- 2 files changed, 25 insertions(+), 31 deletions(-) diff --git a/src/object/player.cpp b/src/object/player.cpp index 22e82a71f15..64952d41928 100644 --- a/src/object/player.cpp +++ b/src/object/player.cpp @@ -101,6 +101,7 @@ const float OVERSPEED_DECELERATION = 100; /** multiplied by WALK_ACCELERATION to give friction */ const float NORMAL_FRICTION_MULTIPLIER = 1.5f; +const float STATIC_FRICTION_MULTIPLIER = 2.0f; /** multiplied by WALK_ACCELERATION to give friction */ const float ICE_FRICTION_MULTIPLIER = 0.1f; const float ICE_ACCELERATION_MULTIPLIER = 0.25f; @@ -629,7 +630,7 @@ Player::update(float dt_sec) } if (!m_dying && !m_deactivated) - handle_input(); + handle_input(dt_sec); /* // handle_input() calls apply_friction() when Tux is not walking, so we'll have to do this ourselves @@ -956,7 +957,7 @@ Player::update(float dt_sec) } void -Player::slide() +Player::slide(float dt_sec) { if (m_swimming || m_water_jump || m_stone) { @@ -1005,7 +1006,7 @@ Player::slide() if (m_floor_normal.y == 0.f && m_can_jump) { if (!m_slidejumping && !m_jumping) { - apply_friction(); + apply_friction(dt_sec); } } else @@ -1158,30 +1159,31 @@ Player::swim(float pointx, float pointy, bool boost) } void -Player::apply_friction() +Player::apply_friction(float dt_sec) { bool is_on_ground = on_ground(); float velx = m_physic.get_velocity_x(); - if (is_on_ground && (fabsf(velx) < (m_stone ? 5.f : WALK_SPEED))) { + /*if (is_on_ground && (fabsf(velx) < (m_stone ? 5.f : WALK_SPEED))) { m_physic.set_velocity_x(0); m_physic.set_acceleration_x(0); return; - } + }*/ float friction = WALK_ACCELERATION_X; if (m_on_ice && is_on_ground) //we need this or else sliding on ice will cause Tux to go on for a very long time friction *= (ICE_FRICTION_MULTIPLIER*(m_sliding ? 4.f : m_stone ? 5.f : 1.f)); else friction *= (NORMAL_FRICTION_MULTIPLIER*(m_sliding ? 0.8f : m_stone ? 0.4f : 1.f)); + if (is_on_ground && (fabsf(velx) < (m_stone ? 5.f : WALK_SPEED))) friction *= STATIC_FRICTION_MULTIPLIER; if (velx < 0) { - m_physic.set_acceleration_x(friction); + m_physic.accelerate_x(friction, dt_sec, 0.0f); } else if (velx > 0) { - m_physic.set_acceleration_x(-friction); + m_physic.accelerate_x(-friction, dt_sec, 0.0f); } // no friction for physic.get_velocity_x() == 0 } void -Player::handle_horizontal_input() +Player::handle_horizontal_input(float dt_sec) { float vx = m_physic.get_velocity_x(); float vy = m_physic.get_velocity_y(); @@ -1295,7 +1297,7 @@ Player::handle_horizontal_input() // we get slower when not pressing any keys if (dirsign == 0) { - apply_friction(); + apply_friction(dt_sec); } } @@ -1538,7 +1540,7 @@ Player::handle_vertical_input() } void -Player::handle_input() +Player::handle_input(float dt_sec) { // Display the player's ID on top of them at the beginning of the level/sector // and persist the number until the player moves, because players will be @@ -1560,7 +1562,7 @@ Player::handle_input() return; } if (m_stone) { - handle_input_rolling(); + handle_input_rolling(dt_sec); return; } if (m_swimming) { @@ -1607,7 +1609,7 @@ Player::handle_input() m_peekingY = Direction::DOWN; /* Handle horizontal movement: */ - if (!m_backflipping && !m_stone && !m_swimming && !m_sliding) handle_horizontal_input(); + if (!m_backflipping && !m_stone && !m_swimming && !m_sliding) handle_horizontal_input(dt_sec); /* Jump/jumping? */ if (on_ground()) @@ -1654,7 +1656,7 @@ Player::handle_input() } if (m_stone) - apply_friction(); + apply_friction(dt_sec); /* Duck or Standup! */ if (((m_controller->pressed(Control::DOWN) && !m_growing && !m_stone) || ((m_duck || m_wants_buttjump || m_crawl) && m_controller->hold(Control::DOWN))) && @@ -1745,7 +1747,7 @@ Player::handle_input() if (m_sliding) { adjust_height(DUCKED_TUX_HEIGHT); - slide(); + slide(dt_sec); } else if (!m_sliding && (m_coyote_timer.started()) && !m_skidding_timer.started() && (m_floor_normal.y != 0 || (m_controller->hold(Control::LEFT) || m_controller->hold(Control::RIGHT))) @@ -1754,7 +1756,7 @@ Player::handle_input() { sideways_push(m_dir == Direction::LEFT ? -100.f : 100.f); adjust_height(DUCKED_TUX_HEIGHT); - slide(); + slide(dt_sec); } } @@ -2585,13 +2587,6 @@ Player::add_velocity(const Vector& velocity) void Player::add_velocity(const Vector& velocity, const Vector& end_speed) { - const Vector new_speed = m_physic.get_velocity() + velocity; - if (glm::dot(velocity, end_speed - new_speed) < 0) { // Do we overshot the end_speed? - m_physic.set_velocity(end_speed); - } else { - m_physic.set_velocity(new_speed); - } - /* if (end_speed.x > 0) m_physic.set_velocity_x(std::min(m_physic.get_velocity_x() + velocity.x, end_speed.x)); if (end_speed.x < 0) @@ -2600,7 +2595,6 @@ Player::add_velocity(const Vector& velocity, const Vector& end_speed) m_physic.set_velocity_y(std::min(m_physic.get_velocity_y() + velocity.y, end_speed.y)); if (end_speed.y < 0) m_physic.set_velocity_y(std::max(m_physic.get_velocity_y() + velocity.y, end_speed.y)); - */ } void @@ -2763,7 +2757,7 @@ Player::handle_input_climbing() } void -Player::handle_input_rolling() +Player::handle_input_rolling(float dt_sec) { // handle exiting if (m_stone) @@ -2842,7 +2836,7 @@ Player::handle_input_rolling() m_physic.set_acceleration_x(ax + sx); } else { - apply_friction(); + apply_friction(dt_sec); } } } diff --git a/src/object/player.hpp b/src/object/player.hpp index 04653bc97a1..f488d69f9e4 100644 --- a/src/object/player.hpp +++ b/src/object/player.hpp @@ -461,14 +461,14 @@ class Player final : public MovingObject bool track_state() const override { return false; } private: - void handle_input(); + void handle_input(float dt_sec); void handle_input_ghost(); /**< input handling while in ghost mode */ void handle_input_climbing(); /**< input handling while climbing */ - void handle_input_rolling(); + void handle_input_rolling(float dt_sec); void handle_input_swimming(); - void handle_horizontal_input(); + void handle_horizontal_input(float dt_sec); void handle_vertical_input(); /** Set Tux's position, reset state and velocity. */ @@ -477,13 +477,13 @@ class Player final : public MovingObject void do_jump_apex(); void early_jump_apex(); - void slide(); + void slide(float dt_sec); void swim(float pointx, float pointy, bool boost); BonusType string_to_bonus(const std::string& bonus) const; /** slows Tux down a little, based on where he's standing */ - void apply_friction(); + void apply_friction(float dt_sec); void check_bounds();