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