From 00df86e63f05ac910c3a4605a8fcf11da8a6b73a Mon Sep 17 00:00:00 2001 From: Sara Date: Fri, 17 May 2024 12:53:50 +0200 Subject: [PATCH] feat: added oversteer, understeer and handbraking Implementation is still messy so some cleanup is needed --- src/car_physics.cpp | 174 +++++++++++++++++++++++++++++++++++++++++--- src/car_physics.hpp | 51 ++++++++++++- src/car_player.cpp | 2 +- src/car_player.hpp | 4 +- 4 files changed, 212 insertions(+), 19 deletions(-) diff --git a/src/car_physics.cpp b/src/car_physics.cpp index 5421f7b..3352171 100644 --- a/src/car_physics.cpp +++ b/src/car_physics.cpp @@ -1,4 +1,6 @@ #include "car_physics.hpp" +#include "godot_cpp/classes/collision_object3d.hpp" +#include "godot_cpp/variant/utility_functions.hpp" #include "utils/godot_macros.h" #include @@ -6,35 +8,166 @@ namespace godot { void CarPhysics::_bind_methods() { #define CLASSNAME CarPhysics GDPROPERTY_HINTED(oversteer_curve, Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "Curve"); + GDPROPERTY(oversteer_curve_x_scale, Variant::FLOAT); GDPROPERTY_HINTED(understeer_curve, Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "Curve"); + GDPROPERTY(understeer_curve_x_scale, Variant::FLOAT); } void CarPhysics::_enter_tree() { this->set_use_custom_integrator(true); + this->front_wheels = this->get_node("FrontWheels"); + this->back_wheels = this->get_node("BackWheels"); + this->set_contact_monitor(true); + this->set_max_contacts_reported(128); + + GDGAMEONLY(); + this->connect("body_shape_entered", callable_mp(this, &CarPhysics::on_body_shape_entered)); + this->connect("body_shape_exited", callable_mp(this, &CarPhysics::on_body_shape_exited)); +} + +void CarPhysics::_physics_process(double delta_time) { + this->process_oversteer(delta_time); + this->process_understeer(delta_time); + UtilityFunctions::print("true target speed: ", this->get_true_target_speed()); + UtilityFunctions::print("speed: ", this->local_velocity.z); + UtilityFunctions::print("grounded: ", this->grounded_objects.size()); + UtilityFunctions::print("brakes: ", this->brake); + UtilityFunctions::print("acceleration: ", this->get_current_acceleration()); + UtilityFunctions::print("-----------------------------------------------------------"); +} + +void CarPhysics::process_oversteer(double delta_time) { + float const target = this->current_steering == 0.f + ? 0.f + : (this->evaluate_oversteer_curve(this->local_velocity.z) + (this->brake ? 1.f : 0.f)) * Math::sign(this->current_steering); + if(Math::abs(target) >= Math::abs(this->current_oversteer) && Math::sign(target) == Math::sign(this->current_oversteer)) + this->current_oversteer = target; + else + this->current_oversteer = Math::move_toward(this->current_oversteer, target, float(delta_time * 100.0)); + UtilityFunctions::print("current oversteer: ", this->current_oversteer, " target: ", target); +} + +void CarPhysics::process_understeer(double delta_time) { + float const target = this->evaluate_understeer_curve(this->local_velocity.z); + if(target >= this->current_understeer) + this->current_understeer = target; + else + this->current_understeer = Math::move_toward(this->current_understeer, target, (float)delta_time); + UtilityFunctions::print("current understeer: ", this->current_understeer, " target: ", target); } void CarPhysics::_integrate_forces(PhysicsDirectBodyState3D *state) { - Basis const basis = this->get_global_basis(); + if(this->is_grounded()) { + this->integrate_engine_acceleration(state); + this->integrate_steering(state); + this->integrate_oversteer(state); + } + this->last_velocity = state->get_linear_velocity(); + state->set_linear_velocity( + (this->is_grounded() + ? (this->local_to_world_velocity() + Vector3{0, float(-9.8f * state->get_step()), 0}) + : (this->get_linear_velocity() + Vector3{0, float(-9.8f * state->get_step()), 0}))); +} - this->local_velocity.z = Math::move_toward(this->local_velocity.z, this->target_speed, float(ACCELERATION * state->get_step())); +void CarPhysics::integrate_steering(PhysicsDirectBodyState3D *state) { + float target_x_velocity = 0.f; if(this->local_velocity.z != 0.f) { - state->set_angular_velocity(basis.get_column(1) * -this->current_steering * Math::sign(this->local_velocity.z)); - this->local_velocity.x = -this->current_steering * 0.1f; + Vector3 const steering_angle_vel = this->get_global_basis().get_column(1) * ( + this->current_steering * Math::sign(this->local_velocity.z + this->current_oversteer * 2.f)); + state->set_angular_velocity(steering_angle_vel * (1.0f-this->current_understeer)); + target_x_velocity = this->current_steering * 0.1f; } else { state->set_angular_velocity(Vector3(0,0,0)); - this->local_velocity.x = 0; } + this->local_velocity.x = Math::move_toward(this->local_velocity.x, target_x_velocity, float(20.f * (1.f-this->current_oversteer) * state->get_step())); +} + +void CarPhysics::integrate_engine_acceleration(PhysicsDirectBodyState3D *state) { + Vector3 const jolt = state->get_linear_velocity() - this->last_velocity; + float const z_jolt = jolt.dot(this->get_global_basis().get_column(2)); + + if(Math::abs(this->local_velocity.z) > 1.f && Math::abs(z_jolt) >= Math::abs(this->local_velocity.z) / 2.f && Math::sign(z_jolt) != Math::sign(this->local_velocity.z)) { + this->local_velocity.z = 0.f; + } + + this->local_velocity.z = Math::move_toward( + this->local_velocity.z, + this->get_true_target_speed(), + float(this->get_current_acceleration() * state->get_step())); +} + +void CarPhysics::integrate_oversteer(PhysicsDirectBodyState3D *state) { + this->local_velocity.x = Math::clamp( + this->local_velocity.x - this->local_velocity.z * this->current_oversteer * 0.05f, + -20.f, + +20.f + ); +} + +void CarPhysics::on_body_shape_entered(RID body_rid, Node *body, int body_shape_index, int local_shape_index) { + CollisionShape3D *shape = Object::cast_to(this->shape_owner_get_owner(this->shape_find_owner(local_shape_index))); + bool const was_grounded = this->is_grounded(); + if(shape == this->front_wheels || shape == this->back_wheels) + this->grounded_objects.insert(body); + if(!was_grounded && this->is_grounded()) { + this->local_velocity = this->world_to_local_velocity(); + } +} + +void CarPhysics::on_body_shape_exited(RID body_rid, Node *body, int body_shape_index, int local_shape_index) { + if(this->grounded_objects.has(body)) + this->grounded_objects.erase(body); +} + +Vector3 CarPhysics::local_to_world_velocity() const { + Basis const basis = this->get_global_basis(); Vector3 world_velocity{ this->local_velocity.x * basis.get_column(0) + this->local_velocity.y * basis.get_column(1) + this->local_velocity.z * basis.get_column(2) }; - world_velocity.y = 0; - if(state->get_linear_velocity().y > this->gravity_velocity && state->get_linear_velocity().y <= 0.f) - this->gravity_velocity = 0.f; - else - this->gravity_velocity -= 15.f * state->get_step(); - state->set_linear_velocity(world_velocity + Vector3{0, this->gravity_velocity, 0}); + return world_velocity; +} + +Vector3 CarPhysics::world_to_local_velocity() const { + Basis const basis = this->get_global_basis(); + Vector3 const velocity = this->get_linear_velocity(); + return Vector3{ + basis.get_column(0).dot(velocity), + 0.f, + basis.get_column(2).dot(velocity) + }; +} + +float CarPhysics::evaluate_oversteer_curve(float speed) const { + return this->current_steering != 0.f + ? this->oversteer_curve->sample(speed / this->oversteer_curve_x_scale) + : 0.f; +} + +float CarPhysics::evaluate_understeer_curve(float speed) const { + return this->current_steering != 0.f + ? this->understeer_curve->sample(speed / this->understeer_curve_x_scale) + : 0.f; +} + +float CarPhysics::get_true_target_speed() const { + return (this->brake ? 0.f + : (this->target_speed > 0 + ? this->target_speed - (Math::abs(this->current_oversteer) * 5.f + this->current_understeer * 3.f) + : this->target_speed)); +} + +bool CarPhysics::is_grounded() const { + return !this->grounded_objects.is_empty(); +} + +float CarPhysics::get_current_acceleration() const { + return Math::abs(this->brake + ? HANDBRAKE + : ((Math::abs(this->get_true_target_speed()) >= Math::abs(this->local_velocity.z)) + ? (ACCELERATION * (1.f-Math::abs(this->current_oversteer))) + : (CarPhysics::ENGINE_BRAKING * (1.f-Math::abs(this->current_oversteer * 0.25f))))); } void CarPhysics::set_target_speed(float target) { @@ -69,6 +202,14 @@ Ref CarPhysics::get_oversteer_curve() const { return this->oversteer_curve; } +void CarPhysics::set_oversteer_curve_x_scale(float scale) { + this->oversteer_curve_x_scale = scale; +} + +float CarPhysics::get_oversteer_curve_x_scale() const { + return this->oversteer_curve_x_scale; +} + void CarPhysics::set_understeer_curve(Ref curve) { this->understeer_curve = curve; } @@ -77,8 +218,17 @@ Ref CarPhysics::get_understeer_curve() const { return this->understeer_curve; } +void CarPhysics::set_understeer_curve_x_scale(float scale) { + this->understeer_curve_x_scale = scale; +} + +float CarPhysics::get_understeer_curve_x_scale() const { + return this->understeer_curve_x_scale; +} + const float CarPhysics::ACCELERATION{20.f}; -const float CarPhysics::BRAKE_FORCE{5.f}; +const float CarPhysics::ENGINE_BRAKING{30.f}; +const float CarPhysics::HANDBRAKE{40.f}; const float CarPhysics::UNDERSTEER_SPEED_MUL{0.f}; const float CarPhysics::OVERSTEER_SPEED_MUL{0.f}; } diff --git a/src/car_physics.hpp b/src/car_physics.hpp index 197ca44..7dd6018 100644 --- a/src/car_physics.hpp +++ b/src/car_physics.hpp @@ -1,6 +1,9 @@ #ifndef CAR_PHYSICS_HPP #define CAR_PHYSICS_HPP +#include "godot_cpp/classes/collision_shape3d.hpp" +#include "godot_cpp/templates/hash_set.hpp" +#include "godot_cpp/variant/rid.hpp" #include #include #include @@ -11,8 +14,32 @@ class CarPhysics : public RigidBody3D { static void _bind_methods(); public: virtual void _enter_tree() override; - virtual void _integrate_forces(PhysicsDirectBodyState3D *state) override; + virtual void _physics_process(double delta_time) override; + void process_oversteer(double delta_time); + void process_understeer(double delta_time); + virtual void _integrate_forces(PhysicsDirectBodyState3D *state) override; +protected: + // Integration functions, state can be gotten via the _integrate_forces method. + // Only intended to be called from the _integrate_forces methdo + void integrate_steering(PhysicsDirectBodyState3D *state); + void integrate_engine_acceleration(PhysicsDirectBodyState3D *state); + void integrate_oversteer(PhysicsDirectBodyState3D *state); + void on_body_shape_entered(RID body_rid, Node *node, int body_shape_index, int local_shape_index); + void on_body_shape_exited(RID body_rid, Node *node, int body_shape_index, int local_shape_index); + // convert the local_velocity to world coordinates + Vector3 local_to_world_velocity() const; + Vector3 world_to_local_velocity() const; + // evaluate the oversteer curve with a speed, taking *_x_scale into account + float evaluate_oversteer_curve(float speed) const; + // evaluate the understeer curve with a speed, taking *_x_scale into account + float evaluate_understeer_curve(float speed) const; + + // returns the maximum speed modified by under/oversteer + float get_true_target_speed() const; + bool is_grounded() const; + float get_current_acceleration() const; +public: void set_target_speed(float target); float get_target_speed() const; void set_current_steering(float steering); @@ -21,19 +48,35 @@ public: bool get_brake() const; void set_oversteer_curve(Ref curve); Ref get_oversteer_curve() const; + void set_oversteer_curve_x_scale(float scale); + float get_oversteer_curve_x_scale() const; void set_understeer_curve(Ref curve); Ref get_understeer_curve() const; + void set_understeer_curve_x_scale(float scale); + float get_understeer_curve_x_scale() const; private: + Vector3 last_velocity{0.f, 0.f, 0.f}; + Vector3 local_velocity{0.f, 0.f, 0.f}; + + HashSet grounded_objects{}; + float target_speed{0.f}; float current_steering{0.f}; - Vector3 local_velocity{}; - float gravity_velocity{-1.f}; bool brake{false}; Ref oversteer_curve{}; + float oversteer_curve_x_scale{50.f}; Ref understeer_curve{}; + float understeer_curve_x_scale{50.f}; + + CollisionShape3D *front_wheels{nullptr}; + CollisionShape3D *back_wheels{nullptr}; + + float current_understeer{0.f}; + float current_oversteer{0.f}; static const float ACCELERATION; - static const float BRAKE_FORCE; + static const float ENGINE_BRAKING; + static const float HANDBRAKE; static const float UNDERSTEER_SPEED_MUL; static const float OVERSTEER_SPEED_MUL; }; diff --git a/src/car_player.cpp b/src/car_player.cpp index c8f6f22..f6fc875 100644 --- a/src/car_player.cpp +++ b/src/car_player.cpp @@ -9,7 +9,7 @@ void CarPlayer::_bind_methods() { } void CarPlayer::setup_player_input(PlayerInput *input) { - input->listen_to(PlayerInput::Listener("steer_right", "steer_left", callable_mp(this, &CarPlayer::on_steer))); + input->listen_to(PlayerInput::Listener("steer_left", "steer_right", callable_mp(this, &CarPlayer::on_steer))); input->listen_to(PlayerInput::Listener("accelerate", callable_mp(this, &CarPlayer::on_accelerate))); input->listen_to(PlayerInput::Listener("brake", callable_mp(this, &CarPlayer::on_brake))); } diff --git a/src/car_player.hpp b/src/car_player.hpp index a453107..73e7d25 100644 --- a/src/car_player.hpp +++ b/src/car_player.hpp @@ -21,9 +21,9 @@ public: void on_brake(Ref event, float value); void on_accelerate(Ref event, float value); private: - const float max_speed{34.f}; + const float max_speed{40.f}; const float max_speed_reverse{10.f}; - const float steering_factor{1.f}; + const float steering_factor{2.f}; }; };