bevy_tnua_physics_integration_layer/
data_for_backends.rs

1use std::ops::{Add, AddAssign};
2
3use crate::math::{AdjustPrecision, Float, Quaternion, Vector3};
4use bevy::prelude::*;
5
6pub use crate::obstacle_radar::TnuaObstacleRadar;
7
8/// Allows disabling Tnua for a specific entity.
9///
10/// This can be used to let some other system  temporarily take control over a character.
11///
12/// This component is not mandatory - if omitted, Tnua will just assume it is enabled for that
13/// entity.
14#[derive(Component, Default, Debug, PartialEq, Eq, Clone, Copy)]
15pub enum TnuaToggle {
16    /// Do not update the sensors, and do not apply forces from the motor.
17    ///
18    /// The controller system will also not run and won't update the motor components not the state
19    /// stored in the `TnuaController` component. They will retain their last value from before
20    /// `TnuaToggle::Disabled` was set.
21    Disabled,
22    /// Update the sensors, but do not apply forces from the motor.
23    ///
24    /// The platformer controller system will still run and still update the motor components and
25    /// state stored in the `TnuaController` component. only the system that applies the motor
26    /// forces will be disabled.
27    SenseOnly,
28    #[default]
29    /// The backend behaves normally - it updates the sensors and applies forces from the motor.
30    Enabled,
31}
32
33/// Newtonian state of the rigid body.
34///
35/// Tnua takes the position and rotation of the rigid body from its `GlobalTransform`, but things
36/// like velocity are dependent on the physics engine. The physics backend is responsible for
37/// updating this component from the physics engine during
38/// [`TnuaPipelineStages::Sensors`](crate::TnuaPipelineStages::Sensors).
39#[derive(Component, Debug)]
40pub struct TnuaRigidBodyTracker {
41    pub translation: Vector3,
42    pub rotation: Quaternion,
43    pub velocity: Vector3,
44    /// Angular velocity as the rotation axis multiplied by the rotation speed in radians per
45    /// second. Can be extracted from a quaternion using [`Quaternion::xyz`].
46    pub angvel: Vector3,
47    pub gravity: Vector3,
48}
49
50impl Default for TnuaRigidBodyTracker {
51    fn default() -> Self {
52        Self {
53            translation: Vector3::ZERO,
54            rotation: Quaternion::IDENTITY,
55            velocity: Vector3::ZERO,
56            angvel: Vector3::ZERO,
57            gravity: Vector3::ZERO,
58        }
59    }
60}
61
62/// Distance from another collider in a certain direction, and information on that collider.
63///
64/// The physics backend is responsible for updating this component from the physics engine during
65/// [`TnuaPipelineStages::Sensors`](crate::TnuaPipelineStages::Sensors), usually by casting a ray
66/// or a shape in the `cast_direction`.
67#[derive(Component, Debug)]
68pub struct TnuaProximitySensor {
69    /// The cast origin in the entity's coord system.
70    pub cast_origin: Vector3,
71    /// The direction in world coord system (unmodified by the entity's transform)
72    pub cast_direction: Dir3,
73    /// Rotation for the sensor shape. Ignored if there is no sensor shape.
74    pub cast_shape_rotation: Quaternion,
75    /// The maximum detection range.
76    pub cast_range: Float,
77    pub output: Option<TnuaProximitySensorOutput>,
78}
79
80impl Default for TnuaProximitySensor {
81    fn default() -> Self {
82        Self {
83            cast_origin: Vector3::ZERO,
84            cast_direction: Dir3::NEG_Y,
85            cast_shape_rotation: Quaternion::IDENTITY,
86            cast_range: 0.0,
87            output: None,
88        }
89    }
90}
91
92/// Information from [`TnuaProximitySensor`] that have detected another collider.
93#[derive(Debug, Clone)]
94pub struct TnuaProximitySensorOutput {
95    /// The entity of the collider detected by the ray.
96    pub entity: Entity,
97    /// The distance to the collider from [`cast_origin`](TnuaProximitySensor::cast_origin) along the
98    /// [`cast_direction`](TnuaProximitySensor::cast_direction).
99    pub proximity: Float,
100    /// The normal from the detected collider's surface where the ray hits.
101    pub normal: Dir3,
102    /// The velocity of the detected entity,
103    pub entity_linvel: Vector3,
104    /// The angular velocity of the detected entity, given as the rotation axis multiplied by the
105    /// rotation speed in radians per second. Can be extracted from a quaternion using
106    /// [`Quaternion::xyz`].
107    pub entity_angvel: Vector3,
108}
109
110/// Represents a change to velocity (linear or angular)
111#[derive(Debug, Clone)]
112pub struct TnuaVelChange {
113    // The part of the velocity change that gets multiplied by the frame duration.
114    //
115    // In Rapier, this is applied using `ExternalForce` so that the simulation will apply in
116    // smoothly over time and won't be sensitive to frame rate.
117    pub acceleration: Vector3,
118    // The part of the velocity change that gets added to the velocity as-is.
119    //
120    // In Rapier, this is added directly to the `Velocity` component.
121    pub boost: Vector3,
122}
123
124impl TnuaVelChange {
125    pub const ZERO: Self = Self {
126        acceleration: Vector3::ZERO,
127        boost: Vector3::ZERO,
128    };
129
130    pub fn acceleration(acceleration: Vector3) -> Self {
131        Self {
132            acceleration,
133            boost: Vector3::ZERO,
134        }
135    }
136
137    pub fn boost(boost: Vector3) -> Self {
138        Self {
139            acceleration: Vector3::ZERO,
140            boost,
141        }
142    }
143
144    pub fn clear(&mut self) {
145        self.acceleration = Vector3::ZERO;
146        self.boost = Vector3::ZERO;
147    }
148
149    pub fn project_onto(&self, rhs: Vector3) -> Self {
150        Self {
151            acceleration: self.acceleration.project_onto(rhs),
152            boost: self.boost.project_onto(rhs),
153        }
154    }
155
156    pub fn project_onto_normalized(&self, rhs: Vector3) -> Self {
157        Self {
158            acceleration: self.acceleration.project_onto_normalized(rhs),
159            boost: self.boost.project_onto_normalized(rhs),
160        }
161    }
162
163    pub fn project_onto_dir(&self, rhs: Dir3) -> Self {
164        self.project_onto_normalized(rhs.adjust_precision())
165    }
166
167    pub fn cancel_on_axis(&mut self, axis: Vector3) {
168        self.acceleration = self.acceleration.reject_from(axis);
169        self.boost = self.boost.reject_from(axis);
170    }
171
172    pub fn calc_boost(&self, frame_duration: Float) -> Vector3 {
173        self.acceleration * frame_duration + self.boost
174    }
175
176    pub fn calc_mean_boost(&self, frame_duration: Float) -> Vector3 {
177        0.5 * self.acceleration * frame_duration + self.boost
178    }
179
180    pub fn calc_acceleration(&self, frame_duration: Float) -> Vector3 {
181        self.acceleration + self.boost / frame_duration
182    }
183
184    pub fn apply_boost_limit(
185        &mut self,
186        frame_duration: Float,
187        component_direction: Dir3,
188        component_limit: Float,
189    ) {
190        let regular_boost = self.calc_boost(frame_duration);
191        let regular = regular_boost.dot(component_direction.adjust_precision());
192        let to_cut = regular - component_limit;
193        if to_cut <= 0.0 {
194            return;
195        }
196        let boost_part = self.boost.dot(component_direction.adjust_precision());
197        if to_cut <= boost_part {
198            // Can do the entire cut by just reducing the boost
199            self.boost -= to_cut * component_direction.adjust_precision();
200            return;
201        }
202        // Even nullifying the boost is not enough, and we don't want to
203        // reverse it, so we're going to cut the acceleration as well.
204        self.boost = self
205            .boost
206            .reject_from(component_direction.adjust_precision());
207        let to_cut_from_acceleration = to_cut - boost_part;
208        let acceleration_to_cut = to_cut_from_acceleration / frame_duration;
209        self.acceleration -= acceleration_to_cut * component_direction.adjust_precision();
210    }
211}
212
213impl Default for TnuaVelChange {
214    fn default() -> Self {
215        Self::ZERO
216    }
217}
218
219impl Add<TnuaVelChange> for TnuaVelChange {
220    type Output = TnuaVelChange;
221
222    fn add(self, rhs: TnuaVelChange) -> Self::Output {
223        Self::Output {
224            acceleration: self.acceleration + rhs.acceleration,
225            boost: self.boost + rhs.boost,
226        }
227    }
228}
229
230impl AddAssign for TnuaVelChange {
231    fn add_assign(&mut self, rhs: Self) {
232        self.acceleration += rhs.acceleration;
233        self.boost += rhs.boost;
234    }
235}
236
237/// Instructions on how to move forces to the rigid body.
238///
239/// The physics backend is responsible for reading this component during
240/// [`TnuaPipelineStages::Sensors`](crate::TnuaPipelineStages::Sensors) and apply the forces to the
241/// rigid body.
242///
243/// This documentation uses the term "forces", but in fact these numbers ignore mass and are
244/// applied directly to the velocity.
245#[derive(Component, Default, Debug)]
246pub struct TnuaMotor {
247    /// How much velocity to add to the rigid body in the current frame.
248    pub lin: TnuaVelChange,
249
250    /// How much angular velocity to add to the rigid body in the current frame, given as the
251    /// rotation axis multiplied by the rotation speed in radians per second. Can be extracted from
252    /// a quaternion using [`Quaternion::xyz`].
253    pub ang: TnuaVelChange,
254}
255
256/// An addon for [`TnuaProximitySensor`] that allows it to detect [`TnuaGhostPlatform`] colliders.
257///
258/// Tnua will register all the ghost platforms encountered by the proximity sensor inside this
259/// component, so that other systems may pick one to override the [sensor
260/// output](TnuaProximitySensor::output)
261///
262/// See <https://github.com/idanarye/bevy-tnua/wiki/Jump-fall-Through-Platforms>
263///
264/// See `TnuaSimpleFallThroughPlatformsHelper`.
265#[derive(Component, Default, Debug)]
266pub struct TnuaGhostSensor(pub Vec<TnuaProximitySensorOutput>);
267
268impl TnuaGhostSensor {
269    pub fn iter(&self) -> impl Iterator<Item = &TnuaProximitySensorOutput> {
270        self.0.iter()
271    }
272}
273
274/// A marker for jump/fall-through platforms.
275///
276/// Ghost platforms must also have their solver groups (**not** collision groups) set to exclude
277/// the character's collider. In order to sense them the player character's sensor must also use
278/// [`TnuaGhostSensor`].
279///
280/// See <https://github.com/idanarye/bevy-tnua/wiki/Jump-fall-Through-Platforms>
281///
282/// See `TnuaSimpleFallThroughPlatformsHelper`.
283#[derive(Component, Default, Debug)]
284pub struct TnuaGhostPlatform;
285
286/// Change the gravity for a Tnua-controlled character.
287#[derive(Component, Debug)]
288pub struct TnuaGravity(pub Vector3);
289
290/// Marker component for colliders which Tnua should not treat as platform.
291///
292/// This means that the ray/shape cast ignores these hits.
293#[derive(Component, Default, Debug)]
294pub struct TnuaNotPlatform;