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;