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;