1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
use std::ops::{Add, AddAssign};

use crate::math::{Float, Quaternion, Vector3};
use bevy::prelude::*;

/// Allows disabling Tnua for a specific entity.
///
/// This can be used to let some other system  temporarily take control over a character.
///
/// This component is not mandatory - if omitted, Tnua will just assume it is enabled for that
/// entity.
#[derive(Component, Default, Debug, PartialEq, Eq, Clone, Copy)]
pub enum TnuaToggle {
    /// Do not update the sensors, and do not apply forces from the motor.
    ///
    /// The controller system will also not run and won't update the motor components not the state
    /// stored in the `TnuaController` component. They will retain their last value from before
    /// `TnuaToggle::Disabled` was set.
    Disabled,
    /// Update the sensors, but do not apply forces from the motor.
    ///
    /// The platformer controller system will still run and still update the motor components and
    /// state stored in the `TnuaController` component. only the system that applies the motor
    /// forces will be disabled.
    SenseOnly,
    #[default]
    /// The backend behaves normally - it updates the sensors and applies forces from the motor.
    Enabled,
}

/// Newtonian state of the rigid body.
///
/// Tnua takes the position and rotation of the rigid body from its `GlobalTransform`, but things
/// like velocity are dependent on the physics engine. The physics backend is responsible for
/// updating this component from the physics engine during
/// [`TnuaPipelineStages::Sensors`](crate::TnuaPipelineStages::Sensors).
#[derive(Component, Debug)]
pub struct TnuaRigidBodyTracker {
    pub translation: Vector3,
    pub rotation: Quaternion,
    pub velocity: Vector3,
    /// Angular velocity as the rotation axis multiplied by the rotation speed in radians per
    /// second. Can be extracted from a quaternion using [`Quaternion::xyz`].
    pub angvel: Vector3,
    pub gravity: Vector3,
}

impl Default for TnuaRigidBodyTracker {
    fn default() -> Self {
        Self {
            translation: Vector3::ZERO,
            rotation: Quaternion::IDENTITY,
            velocity: Vector3::ZERO,
            angvel: Vector3::ZERO,
            gravity: Vector3::ZERO,
        }
    }
}

/// Distance from another collider in a certain direction, and information on that collider.
///
/// The physics backend is responsible for updating this component from the physics engine during
/// [`TnuaPipelineStages::Sensors`](crate::TnuaPipelineStages::Sensors), usually by casting a ray
/// or a shape in the `cast_direction`.
#[derive(Component, Debug)]
pub struct TnuaProximitySensor {
    /// The cast origin in the entity's coord system.
    pub cast_origin: Vector3,
    /// The direction in world coord system (unmodified by the entity's transform)
    pub cast_direction: Dir3,
    /// Tnua will update this field according to its need. The backend only needs to read it.
    pub cast_range: Float,
    pub output: Option<TnuaProximitySensorOutput>,

    /// Used to prevent collision with obstacles the character squeezed into sideways.
    ///
    /// This is used to prevent <https://github.com/idanarye/bevy-tnua/issues/14>. When casting,
    /// Tnua checks if the entity the ray(/shape)cast hits is also in contact with the owner
    /// collider. If so, Tnua compares the contact normal with the cast direction.
    ///
    /// For legitimate hits, these two directions should be opposite. If Tnua casts downwards and
    /// hits the actual floor, the normal of the contact with it should point upward. Opposite
    /// directions means the dot product is closer to `-1.0`.
    ///
    /// Illigitimage hits hits would have perpendicular directions - hitting a wall (sideways) when
    /// casting downwards - which should give a dot product closer to `0.0`.
    ///
    /// This field is compared to the dot product to determine if the hit is valid or not, and can
    /// usually be left at the default value of `-0.5`.
    ///
    /// Positive dot products should not happen (hitting the ceiling?), but it's trivial to
    /// consider them as invalid.
    pub intersection_match_prevention_cutoff: Float,
}

impl Default for TnuaProximitySensor {
    fn default() -> Self {
        Self {
            cast_origin: Vector3::ZERO,
            cast_direction: Dir3::NEG_Y,
            cast_range: 0.0,
            output: None,
            intersection_match_prevention_cutoff: -0.5,
        }
    }
}

/// Information from [`TnuaProximitySensor`] that have detected another collider.
#[derive(Debug, Clone)]
pub struct TnuaProximitySensorOutput {
    /// The entity of the collider detected by the ray.
    pub entity: Entity,
    /// The distance to the collider from [`cast_origin`](TnuaProximitySensor::cast_origin) along the
    /// [`cast_direction`](TnuaProximitySensor::cast_direction).
    pub proximity: Float,
    /// The normal from the detected collider's surface where the ray hits.
    pub normal: Dir3,
    /// The velocity of the detected entity,
    pub entity_linvel: Vector3,
    /// The angular velocity of the detected entity, given as the rotation axis multiplied by the
    /// rotation speed in radians per second. Can be extracted from a quaternion using
    /// [`Quaternion::xyz`].
    pub entity_angvel: Vector3,
}

/// Represents a change to velocity (linear or angular)
#[derive(Debug, Clone)]
pub struct TnuaVelChange {
    // The part of the velocity change that gets multiplied by the frame duration.
    //
    // In Rapier, this is applied using `ExternalForce` so that the simulation will apply in
    // smoothly over time and won't be sensitive to frame rate.
    pub acceleration: Vector3,
    // The part of the velocity change that gets added to the velocity as-is.
    //
    // In Rapier, this is added directly to the `Velocity` component.
    pub boost: Vector3,
}

impl TnuaVelChange {
    pub const ZERO: Self = Self {
        acceleration: Vector3::ZERO,
        boost: Vector3::ZERO,
    };

    pub fn acceleration(acceleration: Vector3) -> Self {
        Self {
            acceleration,
            boost: Vector3::ZERO,
        }
    }

    pub fn boost(boost: Vector3) -> Self {
        Self {
            acceleration: Vector3::ZERO,
            boost,
        }
    }

    pub fn cancel_on_axis(&mut self, axis: Vector3) {
        self.acceleration = self.acceleration.reject_from(axis);
        self.boost = self.boost.reject_from(axis);
    }

    pub fn calc_boost(&self, frame_duration: Float) -> Vector3 {
        self.acceleration * frame_duration + self.boost
    }
}

impl Default for TnuaVelChange {
    fn default() -> Self {
        Self::ZERO
    }
}

impl Add<TnuaVelChange> for TnuaVelChange {
    type Output = TnuaVelChange;

    fn add(self, rhs: TnuaVelChange) -> Self::Output {
        Self::Output {
            acceleration: self.acceleration + rhs.acceleration,
            boost: self.boost + rhs.boost,
        }
    }
}

impl AddAssign for TnuaVelChange {
    fn add_assign(&mut self, rhs: Self) {
        self.acceleration += rhs.acceleration;
        self.boost += rhs.boost;
    }
}

/// Instructions on how to move forces to the rigid body.
///
/// The physics backend is responsible for reading this component during
/// [`TnuaPipelineStages::Sensors`](crate::TnuaPipelineStages::Sensors) and apply the forces to the
/// rigid body.
///
/// This documentation uses the term "forces", but in fact these numbers ignore mass and are
/// applied directly to the velocity.
#[derive(Component, Default, Debug)]
pub struct TnuaMotor {
    /// How much velocity to add to the rigid body in the current frame.
    pub lin: TnuaVelChange,

    /// How much angular velocity to add to the rigid body in the current frame, given as the
    /// rotation axis multiplied by the rotation speed in radians per second. Can be extracted from
    /// a quaternion using [`Quaternion::xyz`].
    pub ang: TnuaVelChange,
}

/// An addon for [`TnuaProximitySensor`] that allows it to detect [`TnuaGhostPlatform`] colliders.
///
/// Tnua will register all the ghost platforms encountered by the proximity sensor inside this
/// component, so that other systems may pick one to override the [sensor
/// output](TnuaProximitySensor::output)
///
/// See <https://github.com/idanarye/bevy-tnua/wiki/Jump-fall-Through-Platforms>
///
/// See `TnuaSimpleFallThroughPlatformsHelper`.
#[derive(Component, Default, Debug)]
pub struct TnuaGhostSensor(pub Vec<TnuaProximitySensorOutput>);

impl TnuaGhostSensor {
    pub fn iter(&self) -> impl Iterator<Item = &TnuaProximitySensorOutput> {
        self.0.iter()
    }
}

/// A marker for jump/fall-through platforms.
///
/// Ghost platforms must also have their solver groups (**not** collision groups) set to exclude
/// the character's collider. In order to sense them the player character's sensor must also use
/// [`TnuaGhostSensor`].
///
/// See <https://github.com/idanarye/bevy-tnua/wiki/Jump-fall-Through-Platforms>
///
/// See `TnuaSimpleFallThroughPlatformsHelper`.
#[derive(Component, Default, Debug)]
pub struct TnuaGhostPlatform;