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/// [`TnuaPipelineSystems::Sensors`](crate::TnuaPipelineSystems::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#[derive(Component)]
63#[relationship(relationship_target = TnuaSensorsSet)]
64pub struct TnuaSensorOf(pub Entity);
65
66#[derive(Component)]
67#[relationship_target(relationship = TnuaSensorOf)]
68pub struct TnuaSensorsSet(Vec<Entity>);
69
70/// Distance from another collider in a certain direction, and information on that collider.
71///
72/// The physics backend is responsible for updating this component from the physics engine during
73/// [`TnuaPipelineSystems::Sensors`](crate::TnuaPipelineSystems::Sensors), usually by casting a ray
74/// or a shape in the `cast_direction`.
75#[derive(Component, Debug)]
76pub struct TnuaProximitySensor {
77    /// The cast origin in the entity's coord system.
78    pub cast_origin: Vector3,
79    /// The direction in world coord system (unmodified by the entity's transform)
80    pub cast_direction: Dir3,
81    /// Rotation for the sensor shape. Ignored if there is no sensor shape.
82    pub cast_shape_rotation: Quaternion,
83    /// The maximum detection range.
84    pub cast_range: Float,
85    pub output: Option<TnuaProximitySensorOutput>,
86}
87
88impl Default for TnuaProximitySensor {
89    fn default() -> Self {
90        Self {
91            cast_origin: Vector3::ZERO,
92            cast_direction: Dir3::NEG_Y,
93            cast_shape_rotation: Quaternion::IDENTITY,
94            cast_range: 0.0,
95            output: None,
96        }
97    }
98}
99
100/// Information from [`TnuaProximitySensor`] that have detected another collider.
101#[derive(Debug, Clone)]
102pub struct TnuaProximitySensorOutput {
103    /// The entity of the collider detected by the ray.
104    pub entity: Entity,
105    /// The distance to the collider from [`cast_origin`](TnuaProximitySensor::cast_origin) along the
106    /// [`cast_direction`](TnuaProximitySensor::cast_direction).
107    pub proximity: Float,
108    /// The normal from the detected collider's surface where the ray hits.
109    pub normal: Dir3,
110    /// The velocity of the detected entity,
111    pub entity_linvel: Vector3,
112    /// The angular velocity of the detected entity, given as the rotation axis multiplied by the
113    /// rotation speed in radians per second. Can be extracted from a quaternion using
114    /// [`Quaternion::xyz`].
115    pub entity_angvel: Vector3,
116}
117
118/// Represents a change to velocity (linear or angular)
119#[derive(Debug, Clone)]
120pub struct TnuaVelChange {
121    // The part of the velocity change that gets multiplied by the frame duration.
122    //
123    // In Rapier, this is applied using `ExternalForce` so that the simulation will apply in
124    // smoothly over time and won't be sensitive to frame rate.
125    pub acceleration: Vector3,
126    // The part of the velocity change that gets added to the velocity as-is.
127    //
128    // In Rapier, this is added directly to the `Velocity` component.
129    pub boost: Vector3,
130}
131
132impl TnuaVelChange {
133    pub const ZERO: Self = Self {
134        acceleration: Vector3::ZERO,
135        boost: Vector3::ZERO,
136    };
137
138    pub fn acceleration(acceleration: Vector3) -> Self {
139        Self {
140            acceleration,
141            boost: Vector3::ZERO,
142        }
143    }
144
145    pub fn boost(boost: Vector3) -> Self {
146        Self {
147            acceleration: Vector3::ZERO,
148            boost,
149        }
150    }
151
152    pub fn clear(&mut self) {
153        self.acceleration = Vector3::ZERO;
154        self.boost = Vector3::ZERO;
155    }
156
157    pub fn project_onto(&self, rhs: Vector3) -> Self {
158        Self {
159            acceleration: self.acceleration.project_onto(rhs),
160            boost: self.boost.project_onto(rhs),
161        }
162    }
163
164    pub fn project_onto_normalized(&self, rhs: Vector3) -> Self {
165        Self {
166            acceleration: self.acceleration.project_onto_normalized(rhs),
167            boost: self.boost.project_onto_normalized(rhs),
168        }
169    }
170
171    pub fn project_onto_dir(&self, rhs: Dir3) -> Self {
172        self.project_onto_normalized(rhs.adjust_precision())
173    }
174
175    pub fn cancel_on_axis(&mut self, axis: Vector3) {
176        self.acceleration = self.acceleration.reject_from(axis);
177        self.boost = self.boost.reject_from(axis);
178    }
179
180    pub fn calc_boost(&self, frame_duration: Float) -> Vector3 {
181        self.acceleration * frame_duration + self.boost
182    }
183
184    pub fn calc_mean_boost(&self, frame_duration: Float) -> Vector3 {
185        0.5 * self.acceleration * frame_duration + self.boost
186    }
187
188    pub fn calc_acceleration(&self, frame_duration: Float) -> Vector3 {
189        self.acceleration + self.boost / frame_duration
190    }
191
192    pub fn apply_boost_limit(
193        &mut self,
194        frame_duration: Float,
195        component_direction: Dir3,
196        component_limit: Float,
197    ) {
198        let regular_boost = self.calc_boost(frame_duration);
199        let regular = regular_boost.dot(component_direction.adjust_precision());
200        let to_cut = regular - component_limit;
201        if to_cut <= 0.0 {
202            return;
203        }
204        let boost_part = self.boost.dot(component_direction.adjust_precision());
205        if to_cut <= boost_part {
206            // Can do the entire cut by just reducing the boost
207            self.boost -= to_cut * component_direction.adjust_precision();
208            return;
209        }
210        // Even nullifying the boost is not enough, and we don't want to
211        // reverse it, so we're going to cut the acceleration as well.
212        self.boost = self
213            .boost
214            .reject_from(component_direction.adjust_precision());
215        let to_cut_from_acceleration = to_cut - boost_part;
216        let acceleration_to_cut = to_cut_from_acceleration / frame_duration;
217        self.acceleration -= acceleration_to_cut * component_direction.adjust_precision();
218    }
219}
220
221impl Default for TnuaVelChange {
222    fn default() -> Self {
223        Self::ZERO
224    }
225}
226
227impl Add<TnuaVelChange> for TnuaVelChange {
228    type Output = TnuaVelChange;
229
230    fn add(self, rhs: TnuaVelChange) -> Self::Output {
231        Self::Output {
232            acceleration: self.acceleration + rhs.acceleration,
233            boost: self.boost + rhs.boost,
234        }
235    }
236}
237
238impl AddAssign for TnuaVelChange {
239    fn add_assign(&mut self, rhs: Self) {
240        self.acceleration += rhs.acceleration;
241        self.boost += rhs.boost;
242    }
243}
244
245/// Instructions on how to move forces to the rigid body.
246///
247/// The physics backend is responsible for reading this component during
248/// [`TnuaPipelineSystems::Sensors`](crate::TnuaPipelineSystems::Sensors) and apply the forces to the
249/// rigid body.
250///
251/// This documentation uses the term "forces", but in fact these numbers ignore mass and are
252/// applied directly to the velocity.
253#[derive(Component, Default, Debug)]
254pub struct TnuaMotor {
255    /// How much velocity to add to the rigid body in the current frame.
256    pub lin: TnuaVelChange,
257
258    /// How much angular velocity to add to the rigid body in the current frame, given as the
259    /// rotation axis multiplied by the rotation speed in radians per second. Can be extracted from
260    /// a quaternion using [`Quaternion::xyz`].
261    pub ang: TnuaVelChange,
262}
263
264/// An addon for [`TnuaProximitySensor`] that allows it to detect [`TnuaGhostPlatform`] colliders.
265///
266/// Tnua will register all the ghost platforms encountered by the proximity sensor inside this
267/// component, so that other systems may pick one to override the [sensor
268/// output](TnuaProximitySensor::output)
269///
270/// See <https://github.com/idanarye/bevy-tnua/wiki/Jump-fall-Through-Platforms>
271///
272/// See `TnuaSimpleFallThroughPlatformsHelper`.
273#[derive(Component, Default, Debug)]
274pub struct TnuaGhostSensor(pub Vec<TnuaProximitySensorOutput>);
275
276impl TnuaGhostSensor {
277    pub fn iter(&self) -> impl Iterator<Item = &TnuaProximitySensorOutput> {
278        self.0.iter()
279    }
280}
281
282/// A marker for jump/fall-through platforms.
283///
284/// Ghost platforms must also have their solver groups (**not** collision groups) set to exclude
285/// the character's collider. In order to sense them the player character's sensor must also use
286/// [`TnuaGhostSensor`].
287///
288/// See <https://github.com/idanarye/bevy-tnua/wiki/Jump-fall-Through-Platforms>
289///
290/// See `TnuaSimpleFallThroughPlatformsHelper`.
291#[derive(Component, Default, Debug)]
292pub struct TnuaGhostPlatform;
293
294/// Change the gravity for a Tnua-controlled character.
295#[derive(Component, Debug)]
296pub struct TnuaGravity(pub Vector3);
297
298/// Marker component for colliders which Tnua should not treat as platform.
299///
300/// This means that the ray/shape cast ignores these hits.
301#[derive(Component, Default, Debug)]
302pub struct TnuaNotPlatform;