diff --git a/CHANGELOG.md b/CHANGELOG.md index be63227d2..49a5d0c25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,15 @@ NOTE: Subcrates have their own changelogs: [bevy-tnua-physics-integration-layer] nullified even with very high walk acceleration settings (see https://github.com/idanarye/bevy-tnua/issues/30) +### Changed +- Instead of fixating it to positive Y, Tnua now calculates the up direction to + be the reverse of the gravity direction (see see + https://github.com/idanarye/bevy-tnua/issues/40) +- [**BREAKING**] API changes: + - (only relevant for custom basis/actions) The `up_direction` of + `TnuaBasisContext` and `TnuaActionContext` is now a field instead of a + method. + ## 0.19.0 - 2024-07-05 ### Changed - Upgrade to Bevy 0.14. diff --git a/demos/src/ui/mod.rs b/demos/src/ui/mod.rs index 7621f2aea..d10ed19e9 100644 --- a/demos/src/ui/mod.rs +++ b/demos/src/ui/mod.rs @@ -16,6 +16,7 @@ use bevy::window::{PresentMode, PrimaryWindow}; use bevy_egui::{egui, EguiContexts, EguiPlugin}; #[allow(unused_imports)] use bevy_tnua::math::AsF32; +use bevy_tnua::math::{float_consts, Float, Vector2, Vector3}; #[cfg(feature = "egui")] use bevy_tnua::TnuaToggle; @@ -40,11 +41,16 @@ impl Default for DemoUi { } } +const GRAVITY_MAGNITUDE: Float = 9.81; + impl Plugin for DemoUi { fn build(&self, app: &mut App) { #[cfg(feature = "egui")] app.add_plugins(EguiPlugin); - app.insert_resource(DemoUiPhysicsBackendActive(true)); + app.insert_resource(DemoUiPhysicsBackendSettings { + active: true, + gravity: Vector3::NEG_Y * GRAVITY_MAGNITUDE, + }); app.configure_sets( Update, DemoInfoUpdateSystemSet.after(bevy_tnua::TnuaUserControlsSystemSet), @@ -98,7 +104,10 @@ impl Plugin for DemoUi { // NOTE: The demos are responsible for updating the physics backend #[derive(Resource)] -pub struct DemoUiPhysicsBackendActive(pub bool); +pub struct DemoUiPhysicsBackendSettings { + pub active: bool, + pub gravity: Vector3, +} #[derive(Component)] pub struct TrackedEntity(pub String); @@ -116,7 +125,7 @@ fn apply_selectors( #[allow(clippy::type_complexity)] fn ui_system( mut egui_context: EguiContexts, - mut physics_backend_active: ResMut, + mut physics_backend_settings: ResMut, mut query: Query<( Entity, &TrackedEntity, @@ -182,7 +191,19 @@ fn ui_system( ui.label("Dash with Shift (while moving in a direction)"); }); level_selection.show_in_ui(ui); - ui.checkbox(&mut physics_backend_active.0, "Physics Backend Enabled"); + ui.collapsing("Physics Backend", |ui| { + ui.checkbox(&mut physics_backend_settings.active, "Physics Enabled"); + let mut gravity_angle = physics_backend_settings.gravity.truncate().to_angle(); + ui.horizontal(|ui| { + ui.label("Gravity Angle:"); + if ui.add(egui::Slider::new(&mut gravity_angle, -float_consts::PI..=0.0)).changed() { + physics_backend_settings.gravity = Vector2::from_angle(gravity_angle).extend(0.0) * GRAVITY_MAGNITUDE; + } + if ui.button("Reset").clicked() { + physics_backend_settings.gravity = Vector3::NEG_Y * GRAVITY_MAGNITUDE; + } + }); + }); for ( entity, TrackedEntity(name), @@ -281,7 +302,7 @@ fn ui_system( } fn update_physics_active_from_ui( - setting_from_ui: Res, + setting_from_ui: Res, #[cfg(feature = "rapier2d")] mut config_rapier2d: Option< ResMut, >, @@ -291,34 +312,46 @@ fn update_physics_active_from_ui( #[cfg(feature = "avian2d")] mut physics_time_avian2d: Option< ResMut>, >, + #[cfg(feature = "avian2d")] mut gravity_avian2d: Option>, #[cfg(feature = "avian3d")] mut physics_time_avian3d: Option< ResMut>, >, + #[cfg(feature = "avian3d")] mut gravity_avian3d: Option>, ) { #[cfg(feature = "rapier2d")] if let Some(config) = config_rapier2d.as_mut() { - config.physics_pipeline_active = setting_from_ui.0; + config.physics_pipeline_active = setting_from_ui.active; + config.gravity = setting_from_ui.gravity.truncate(); } #[cfg(feature = "rapier3d")] if let Some(config) = config_rapier3d.as_mut() { - config.physics_pipeline_active = setting_from_ui.0; + config.physics_pipeline_active = setting_from_ui.active; + config.gravity = setting_from_ui.gravity; } #[cfg(feature = "avian2d")] if let Some(physics_time) = physics_time_avian2d.as_mut() { use avian2d::schedule::PhysicsTime; - if setting_from_ui.0 { + if setting_from_ui.active { physics_time.unpause(); } else { physics_time.pause(); } } + #[cfg(feature = "avian2d")] + if let Some(gravity) = gravity_avian2d.as_mut() { + gravity.0 = setting_from_ui.gravity.truncate(); + } #[cfg(feature = "avian3d")] if let Some(physics_time) = physics_time_avian3d.as_mut() { use avian3d::schedule::PhysicsTime; - if setting_from_ui.0 { + if setting_from_ui.active { physics_time.unpause(); } else { physics_time.pause(); } } + #[cfg(feature = "avian3d")] + if let Some(gravity) = gravity_avian3d.as_mut() { + gravity.0 = setting_from_ui.gravity; + } } diff --git a/src/basis_action_traits.rs b/src/basis_action_traits.rs index 04ef74014..9405765fd 100644 --- a/src/basis_action_traits.rs +++ b/src/basis_action_traits.rs @@ -16,13 +16,9 @@ pub struct TnuaBasisContext<'a> { /// A sensor that tracks the distance of the character's center from the ground. pub proximity_sensor: &'a TnuaProximitySensor, -} -impl TnuaBasisContext<'_> { /// The direction considered as "up". - pub fn up_direction(&self) -> Dir3 { - Dir3::Y - } + pub up_direction: Dir3, } /// The main movement command of a character. @@ -205,6 +201,9 @@ pub struct TnuaActionContext<'a> { /// A sensor that tracks the distance of the character's center from the ground. pub proximity_sensor: &'a TnuaProximitySensor, + /// The direction considered as "up". + pub up_direction: Dir3, + /// An accessor to the currently active basis. pub basis: &'a dyn DynamicBasis, } @@ -227,17 +226,13 @@ impl<'a> TnuaActionContext<'a> { frame_duration: self.frame_duration, tracker: self.tracker, proximity_sensor: self.proximity_sensor, + up_direction: self.up_direction, } } pub fn frame_duration_as_duration(&self) -> Duration { Duration::from_secs_f64(self.frame_duration.into()) } - - /// The direction considered as "up". - pub fn up_direction(&self) -> Dir3 { - Dir3::Y - } } /// Input for [`TnuaAction::apply`] that informs it about the long-term feeding of the input. diff --git a/src/builtins/crouch.rs b/src/builtins/crouch.rs index 8b89e6e4e..0752cb079 100644 --- a/src/builtins/crouch.rs +++ b/src/builtins/crouch.rs @@ -122,7 +122,7 @@ impl TnuaAction for TnuaBuiltinCrouch { let spring_force_boost = spring_force.calc_boost(ctx.frame_duration); let impulse_boost = self.impulse_boost(spring_offset); if spring_force_boost.length_squared() < impulse_boost.powi(2) { - TnuaVelChange::boost(impulse_boost * ctx.up_direction().adjust_precision()) + TnuaVelChange::boost(impulse_boost * ctx.up_direction.adjust_precision()) } else { spring_force } @@ -131,7 +131,7 @@ impl TnuaAction for TnuaBuiltinCrouch { let mut set_vel_change = |vel_change: TnuaVelChange| { motor .lin - .cancel_on_axis(ctx.up_direction().adjust_precision()); + .cancel_on_axis(ctx.up_direction.adjust_precision()); motor.lin += vel_change; }; diff --git a/src/builtins/dash.rs b/src/builtins/dash.rs index e44abb77c..4907c5ddc 100644 --- a/src/builtins/dash.rs +++ b/src/builtins/dash.rs @@ -139,7 +139,7 @@ impl TnuaAction for TnuaBuiltinDash { }; if 0.0 < desired_forward.length_squared() { - let up = ctx.up_direction(); + let up = ctx.up_direction; let up = up.adjust_precision(); let current_forward = ctx.tracker.rotation.mul_vec3(Vector3::NEG_Z); let rotation_along_up_axis = rotation_arc_around_axis( diff --git a/src/builtins/jump.rs b/src/builtins/jump.rs index 547301631..d306cf9ad 100644 --- a/src/builtins/jump.rs +++ b/src/builtins/jump.rs @@ -146,7 +146,7 @@ impl TnuaAction for TnuaBuiltinJump { lifecycle_status: TnuaActionLifecycleStatus, motor: &mut crate::TnuaMotor, ) -> TnuaActionLifecycleDirective { - let up = ctx.up_direction().adjust_precision(); + let up = ctx.up_direction.adjust_precision(); if lifecycle_status.just_started() { let mut calculator = SegmentedJumpInitialVelocityCalculator::new(self.height); diff --git a/src/builtins/knockback.rs b/src/builtins/knockback.rs index 2ac3b15cd..c2266ef32 100644 --- a/src/builtins/knockback.rs +++ b/src/builtins/knockback.rs @@ -149,25 +149,21 @@ impl TnuaAction for TnuaBuiltinKnockback { if let Some(force_forward) = self.force_forward { let current_forward = ctx.tracker.rotation.mul_vec3(Vector3::NEG_Z); let rotation_along_up_axis = rotation_arc_around_axis( - ctx.up_direction(), + ctx.up_direction, current_forward, force_forward.adjust_precision(), ) .unwrap_or(0.0); let desired_angvel = rotation_along_up_axis / ctx.frame_duration; - let existing_angvel = ctx - .tracker - .angvel - .dot(ctx.up_direction().adjust_precision()); + let existing_angvel = ctx.tracker.angvel.dot(ctx.up_direction.adjust_precision()); let torque_to_turn = desired_angvel - existing_angvel; motor .ang - .cancel_on_axis(ctx.up_direction().adjust_precision()); - motor.ang += - TnuaVelChange::boost(torque_to_turn * ctx.up_direction().adjust_precision()); + .cancel_on_axis(ctx.up_direction.adjust_precision()); + motor.ang += TnuaVelChange::boost(torque_to_turn * ctx.up_direction.adjust_precision()); } TnuaActionLifecycleDirective::StillActive diff --git a/src/builtins/walk.rs b/src/builtins/walk.rs index 99ef38b4a..87668367a 100644 --- a/src/builtins/walk.rs +++ b/src/builtins/walk.rs @@ -161,7 +161,7 @@ impl TnuaBasis for TnuaBuiltinWalk { state.effective_velocity = ctx.tracker.velocity - sensor_output.entity_linvel; let sideways_unnormalized = sensor_output .normal - .cross(*ctx.up_direction()) + .cross(*ctx.up_direction) .adjust_precision(); if sideways_unnormalized == Vector3::ZERO { climb_vectors = None; @@ -178,7 +178,7 @@ impl TnuaBasis for TnuaBuiltinWalk { slipping_vector = { let angle_with_floor = sensor_output .normal - .angle_between(*ctx.up_direction()) + .angle_between(*ctx.up_direction) .adjust_precision(); if angle_with_floor <= self.max_slope { None @@ -186,7 +186,7 @@ impl TnuaBasis for TnuaBuiltinWalk { Some( sensor_output .normal - .reject_from(*ctx.up_direction()) + .reject_from(*ctx.up_direction) .adjust_precision(), ) } @@ -231,7 +231,7 @@ impl TnuaBasis for TnuaBuiltinWalk { let velocity_on_plane = state .effective_velocity - .reject_from(ctx.up_direction().adjust_precision()); + .reject_from(ctx.up_direction.adjust_precision()); let desired_boost = self.desired_velocity - velocity_on_plane; @@ -252,7 +252,7 @@ impl TnuaBasis for TnuaBuiltinWalk { state.effective_velocity.dot(climb_vectors.direction) * climb_vectors .direction - .dot(ctx.up_direction().adjust_precision()) + .dot(ctx.up_direction.adjust_precision()) } else { 0.0 }; @@ -284,9 +284,7 @@ impl TnuaBasis for TnuaBuiltinWalk { break 'slipping_boost Vector3::ZERO; }; let vertical_velocity = if 0.0 <= state.vertical_velocity { - ctx.tracker - .gravity - .dot(ctx.up_direction().adjust_precision()) + ctx.tracker.gravity.dot(ctx.up_direction.adjust_precision()) * ctx.frame_duration } else { state.vertical_velocity @@ -333,7 +331,7 @@ impl TnuaBasis for TnuaBuiltinWalk { let spring_offset = self.float_height - sensor_output.proximity.adjust_precision(); state.standing_offset = - -spring_offset * ctx.up_direction().adjust_precision(); + -spring_offset * ctx.up_direction.adjust_precision(); break 'upward_impulse self.spring_force(state, &ctx, spring_offset); } else { state.airborne_timer = Some(Timer::from_seconds( @@ -354,8 +352,7 @@ impl TnuaBasis for TnuaBuiltinWalk { } if state.vertical_velocity <= 0.0 { break 'upward_impulse TnuaVelChange::acceleration( - -self.free_fall_extra_gravity - * ctx.up_direction().adjust_precision(), + -self.free_fall_extra_gravity * ctx.up_direction.adjust_precision(), ); } else { break 'upward_impulse TnuaVelChange::ZERO; @@ -372,18 +369,15 @@ impl TnuaBasis for TnuaBuiltinWalk { + motor.lin.boost + ctx.frame_duration * motor.lin.acceleration - impulse_to_offset; - state.running_velocity = new_velocity.reject_from(ctx.up_direction().adjust_precision()); + state.running_velocity = new_velocity.reject_from(ctx.up_direction.adjust_precision()); // Tilt let torque_to_fix_tilt = { - let tilted_up = ctx - .tracker - .rotation - .mul_vec3(ctx.up_direction().adjust_precision()); + let tilted_up = ctx.tracker.rotation.mul_vec3(Vector3::Y); let rotation_required_to_fix_tilt = - Quaternion::from_rotation_arc(tilted_up, ctx.up_direction().adjust_precision()); + Quaternion::from_rotation_arc(tilted_up, ctx.up_direction.adjust_precision()); let desired_angvel = (rotation_required_to_fix_tilt.xyz() / ctx.frame_duration) .clamp_length_max(self.tilt_offset_angvel); @@ -396,7 +390,7 @@ impl TnuaBasis for TnuaBuiltinWalk { let desired_angvel = if 0.0 < self.desired_forward.length_squared() { let current_forward = ctx.tracker.rotation.mul_vec3(Vector3::NEG_Z); let rotation_along_up_axis = - rotation_arc_around_axis(ctx.up_direction(), current_forward, self.desired_forward) + rotation_arc_around_axis(ctx.up_direction, current_forward, self.desired_forward) .unwrap_or(0.0); (rotation_along_up_axis / ctx.frame_duration) .clamp(-self.turning_angvel, self.turning_angvel) @@ -405,20 +399,17 @@ impl TnuaBasis for TnuaBuiltinWalk { }; // NOTE: This is the regular axis system so we used the configured up. - let existing_angvel = ctx - .tracker - .angvel - .dot(ctx.up_direction().adjust_precision()); + let existing_angvel = ctx.tracker.angvel.dot(ctx.up_direction.adjust_precision()); // This is the torque. Should it be clamped by an acceleration? From experimenting with // this I think it's meaningless and only causes bugs. let torque_to_turn = desired_angvel - existing_angvel; - let existing_turn_torque = torque_to_fix_tilt.dot(ctx.up_direction().adjust_precision()); + let existing_turn_torque = torque_to_fix_tilt.dot(ctx.up_direction.adjust_precision()); let torque_to_turn = torque_to_turn - existing_turn_torque; motor.ang = TnuaVelChange::boost( - torque_to_fix_tilt + torque_to_turn * ctx.up_direction().adjust_precision(), + torque_to_fix_tilt + torque_to_turn * ctx.up_direction.adjust_precision(), ); } @@ -477,7 +468,7 @@ impl TnuaBuiltinWalk { let relative_velocity = state .effective_velocity - .dot(ctx.up_direction().adjust_precision()) + .dot(ctx.up_direction.adjust_precision()) - state.vertical_velocity; let gravity_compensation = -ctx.tracker.gravity; @@ -485,9 +476,8 @@ impl TnuaBuiltinWalk { let dampening_boost = relative_velocity * self.spring_dampening; TnuaVelChange { - acceleration: ctx.up_direction().adjust_precision() * spring_force - + gravity_compensation, - boost: ctx.up_direction().adjust_precision() * -dampening_boost, + acceleration: ctx.up_direction.adjust_precision() * spring_force + gravity_compensation, + boost: ctx.up_direction.adjust_precision() * -dampening_boost, } } } diff --git a/src/controller.rs b/src/controller.rs index 8c39b8f34..35aadd979 100644 --- a/src/controller.rs +++ b/src/controller.rs @@ -398,12 +398,14 @@ fn apply_controller_system( } if let Some((_, basis)) = controller.current_basis.as_mut() { + let up_direction = Dir3::new(-tracker.gravity.f32()).unwrap_or(Dir3::Y); let basis = basis.as_mut(); basis.apply( TnuaBasisContext { frame_duration, tracker, proximity_sensor: sensor.as_ref(), + up_direction, }, motor.as_mut(), ); @@ -421,6 +423,7 @@ fn apply_controller_system( tracker, proximity_sensor, basis, + up_direction, }, being_fed_for, ); @@ -457,6 +460,7 @@ fn apply_controller_system( tracker, proximity_sensor, basis, + up_direction, }, lifecycle_status, motor.as_mut(), @@ -503,6 +507,7 @@ fn apply_controller_system( tracker, proximity_sensor, basis, + up_direction, }, TnuaActionLifecycleStatus::CancelledFrom, motor.as_mut(), @@ -569,6 +574,7 @@ fn apply_controller_system( tracker, proximity_sensor, basis, + up_direction, }, TnuaActionLifecycleStatus::Initiated, motor.as_mut(), @@ -588,6 +594,7 @@ fn apply_controller_system( }; sensor.cast_range = sensor_cast_range_for_basis.max(sensor_case_range_for_action); + sensor.cast_direction = -up_direction; } // Cycle actions_being_fed