bevy_tnua/util/
command_impl_helpers.rs

1use bevy::prelude::*;
2
3use bevy_tnua_physics_integration_layer::data_for_backends::TnuaVelChange;
4use bevy_tnua_physics_integration_layer::math::{AdjustPrecision, Float, Quaternion, Vector3};
5
6use crate::TnuaBasisContext;
7use crate::basis_action_traits::{TnuaActionContext, TnuaBasis};
8
9/// Helper trait for implementing basis and actions.
10///
11/// Methods of this trait typically return a [`TnuaVelChange`] that can be added to
12/// [`motor.lin`](crate::TnuaMotor::lin) or [`motor.ang`](crate::TnuaMotor::ang).
13pub trait MotionHelper {
14    fn frame_duration(&self) -> Float;
15    fn up_direction(&self) -> Dir3;
16    fn gravity(&self) -> Vector3;
17    fn position(&self) -> Vector3;
18    fn velocity(&self) -> Vector3;
19    fn rotation(&self) -> Quaternion;
20    fn angvel(&self) -> Vector3;
21
22    /// A force for cancelling out the gravity.
23    fn negate_gravity(&self) -> TnuaVelChange {
24        TnuaVelChange::acceleration(-self.gravity())
25    }
26
27    /// A force for setting the velocity to the desired velocity, with acceleration constraints.
28    ///
29    /// The `dimlim` parameter can be used to only set the velocity on certain axis/plane.
30    fn adjust_velocity(
31        &self,
32        target: Vector3,
33        acceleration: Float,
34        dimlim: impl Fn(Vector3) -> Vector3,
35    ) -> TnuaVelChange {
36        let delta = dimlim(target - self.velocity());
37        let allowed_this_frame = acceleration * self.frame_duration();
38        if delta.length_squared() <= allowed_this_frame.powi(2) {
39            TnuaVelChange::boost(delta)
40        } else {
41            TnuaVelChange::acceleration(delta.normalize_or_zero() * acceleration)
42        }
43    }
44
45    /// A force for setting the vertical velocity to the desired velocity, with acceleration constraints.
46    fn adjust_vertical_velocity(&self, target: Float, acceleration: Float) -> TnuaVelChange {
47        let up_vector = self.up_direction().adjust_precision();
48        self.adjust_velocity(up_vector * target, acceleration, |v| {
49            v.project_onto_normalized(up_vector)
50        })
51    }
52
53    /// A force for setting the horizontal velocity to the desired velocity, with acceleration constraints.
54    fn adjust_horizontal_velocity(&self, target: Vector3, acceleration: Float) -> TnuaVelChange {
55        self.adjust_velocity(target, acceleration, |v| {
56            v.reject_from_normalized(self.up_direction().adjust_precision())
57        })
58    }
59
60    /// Calculate the rotation around `up_direction` required to rotate the character from the
61    /// current forward to `desired_forward`.
62    fn turn_to_direction(&self, desired_forward: Dir3, up_direction: Dir3) -> TnuaVelChange {
63        let up_vector = up_direction.adjust_precision();
64        let current_forward = self.rotation().mul_vec3(Vector3::NEG_Z);
65        let rotation_along_up_axis = crate::util::rotation_arc_around_axis(
66            up_direction,
67            current_forward,
68            desired_forward.adjust_precision(),
69        )
70        .unwrap_or(0.0);
71        let desired_angvel = rotation_along_up_axis / self.frame_duration();
72        let existing_angvel = self.angvel().dot(up_vector);
73        let torque_to_turn = desired_angvel - existing_angvel;
74        TnuaVelChange::boost(torque_to_turn * up_vector)
75    }
76
77    /// A force for stopping the character right before reaching a certain point.
78    ///
79    /// Please note that unlike most [`MotionHelper`] methods that "stack" on impulses and forces
80    /// already applied, this one needs to be able to "cancel" the previous changes (on the
81    /// relevant axis, at least) which is why it has a `current_vel_change` argument. Due to that,
82    /// this must be the **last** velchange applied to the motor (at least, to it's
83    /// [`lin`](crate::TnuaMotor::lin) field, on the axis of `direction`)
84    ///
85    /// `direction` is the direction _from_ the character _toward_ that point. So, for example, in
86    /// order to stop a character climbing a ladder when reaching the top of the ladder, `stop_at`
87    /// needs to be the coordinates of the top of the ladder while `direction` needs to be
88    /// `Dir3::Y` (the UP direction)
89    ///
90    /// Velocities perpendicular to the `direction` will not be affected.
91    fn hard_stop(
92        &self,
93        direction: Dir3,
94        stop_at: Vector3,
95        current_vel_change: &TnuaVelChange,
96    ) -> TnuaVelChange {
97        let expected_velocity =
98            self.velocity() + current_vel_change.calc_mean_boost(self.frame_duration());
99        let expected_velocity_in_direction = expected_velocity.dot(direction.adjust_precision());
100        if expected_velocity_in_direction <= 0.0 {
101            return TnuaVelChange::default();
102        }
103        let distance_in_direction = (stop_at - self.position()).dot(direction.adjust_precision());
104        let max_allowed_velocity = distance_in_direction / self.frame_duration();
105        let velocity_to_cut = expected_velocity_in_direction - max_allowed_velocity;
106        if 0.0 < velocity_to_cut {
107            TnuaVelChange::boost(velocity_to_cut * -direction.adjust_precision())
108        } else {
109            TnuaVelChange::default()
110        }
111    }
112}
113
114impl MotionHelper for TnuaBasisContext<'_> {
115    fn frame_duration(&self) -> Float {
116        self.frame_duration
117    }
118
119    fn up_direction(&self) -> Dir3 {
120        self.up_direction
121    }
122
123    fn gravity(&self) -> Vector3 {
124        self.tracker.gravity
125    }
126
127    fn position(&self) -> Vector3 {
128        self.tracker.translation
129    }
130
131    fn velocity(&self) -> Vector3 {
132        self.tracker.velocity
133    }
134
135    fn rotation(&self) -> Quaternion {
136        self.tracker.rotation
137    }
138
139    fn angvel(&self) -> Vector3 {
140        self.tracker.angvel
141    }
142}
143
144impl<B: TnuaBasis> MotionHelper for TnuaActionContext<'_, B> {
145    fn frame_duration(&self) -> Float {
146        self.frame_duration
147    }
148
149    fn up_direction(&self) -> Dir3 {
150        self.up_direction
151    }
152
153    fn gravity(&self) -> Vector3 {
154        self.tracker.gravity
155    }
156
157    fn position(&self) -> Vector3 {
158        self.tracker.translation
159    }
160
161    fn velocity(&self) -> Vector3 {
162        self.tracker.velocity
163    }
164
165    fn rotation(&self) -> Quaternion {
166        self.tracker.rotation
167    }
168
169    fn angvel(&self) -> Vector3 {
170        self.tracker.angvel
171    }
172}