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;