diff --git a/README.md b/README.md index eddd11c1..94e595fa 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Below are some of the current features of Bevy XPBD. - Collision layers - Material properties like restitution and friction - Linear and angular velocity damping for simulating drag -- External forces and torque +- External forces, torque and impulses - Gravity and gravity scale for specific entities - Joints - Built-in constraints and support for custom constraints diff --git a/src/components/forces.rs b/src/components/forces.rs new file mode 100644 index 00000000..4e4eeb36 --- /dev/null +++ b/src/components/forces.rs @@ -0,0 +1,527 @@ +use crate::prelude::*; +use bevy::prelude::*; +use derive_more::From; +use std::ops::{Deref, DerefMut}; + +#[cfg(feature = "2d")] +pub(crate) type Torque = Scalar; + +#[cfg(feature = "3d")] +pub(crate) type Torque = Vector; + +pub(crate) trait FloatZero { + const ZERO: Self; +} + +impl FloatZero for Scalar { + const ZERO: Self = 0.0; +} + +/// An external force applied continuously to a dynamic [rigid body](RigidBody) during the integration step. +/// +/// By default, the force persists across frames. You can clear the force manually using +/// [`clear`](#method.clear) or set `persistent` to false. +/// +/// ## Example +/// +/// ``` +/// use bevy::prelude::*; +/// # #[cfg(feature = "2d")] +/// # use bevy_xpbd_2d::prelude::*; +/// # #[cfg(feature = "3d")] +/// use bevy_xpbd_3d::prelude::*; +/// +/// # #[cfg(all(feature = "3d", feature = "f32"))] +/// fn setup(mut commands: Commands) { +/// // Apply a force every physics frame. +/// commands.spawn((RigidBody::Dynamic, ExternalForce::new(Vec3::Y))); +/// +/// // Apply an initial force and automatically clear it every physics frame. +/// commands.spawn(( +/// RigidBody::Dynamic, +/// ExternalForce::new(Vec3::Y).with_persistence(false), +/// )); +/// +/// // Apply multiple forces. +/// let mut force = ExternalForce::default(); +/// force.apply_force(Vec3::Y).apply_force(Vec3::X); +/// commands.spawn((RigidBody::Dynamic, force)); +/// +/// // Apply a force at a specific point relative to the given center of mass, also applying a torque. +/// // In this case, the torque would cause the body to rotate counterclockwise. +/// let mut force = ExternalForce::default(); +/// force.apply_force_at_point(Vec3::Y, Vec3::X, Vec3::ZERO); +/// commands.spawn((RigidBody::Dynamic, force)); +/// } +/// ``` +#[derive(Reflect, Clone, Copy, Component, Debug, PartialEq, From)] +#[reflect(Component)] +pub struct ExternalForce { + /// The total external force that will be applied. + force: Vector, + /// True if the force persists across frames, and false if the force is automatically cleared every physics frame. + /// + /// If you clear the force manually, use the [`clear`](#method.clear) method. This will clear the force and + /// the torque that is applied when the force is not applied at the center of mass. + pub persistent: bool, + /// The torque caused by forces applied at certain points using [`apply_force_at_point`](#method.apply_force_at_point). + torque: Torque, +} + +impl Deref for ExternalForce { + type Target = Vector; + + fn deref(&self) -> &Self::Target { + &self.force + } +} + +impl DerefMut for ExternalForce { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.force + } +} + +impl Default for ExternalForce { + fn default() -> Self { + Self { + force: Vector::ZERO, + persistent: true, + torque: Torque::ZERO, + } + } +} + +impl ExternalForce { + /// Zero external force. + pub const ZERO: Self = Self { + force: Vector::ZERO, + persistent: true, + torque: Torque::ZERO, + }; + + /// Creates a new [`ExternalForce`] component with a given `force`. + pub fn new(force: Vector) -> Self { + Self { force, ..default() } + } + + /// Sets the force. Note that the torque caused by any forces will not be reset. + pub fn set_force(&mut self, force: Vector) -> &mut Self { + **self = force; + self + } + + /// Adds the given `force` to the force that will be applied. + pub fn apply_force(&mut self, force: Vector) -> &mut Self { + **self += force; + self + } + + /// Applies the given `force` at a local `point`, which will also cause torque to be applied. + pub fn apply_force_at_point( + &mut self, + force: Vector, + point: Vector, + center_of_mass: Vector, + ) -> &mut Self { + **self += force; + #[cfg(feature = "2d")] + { + self.torque += (point - center_of_mass).perp_dot(force); + } + #[cfg(feature = "3d")] + { + self.torque += (point - center_of_mass).cross(force); + } + self + } + + /// Returns the force. + pub fn force(&self) -> Vector { + self.force + } + + /// Returns the torque caused by forces applied at certain points using + /// [`apply_force_at_point`](#method.apply_force_at_point). + pub fn torque(&self) -> Torque { + self.torque + } + + /// Determines if the force is persistent or if it should be automatically cleared every physics frame. + #[doc(alias = "clear_automatically")] + pub fn with_persistence(mut self, is_persistent: bool) -> Self { + self.persistent = is_persistent; + self + } + + /// Sets the force and the potential torque caused by the force to zero. + pub fn clear(&mut self) { + self.force = Vector::ZERO; + self.torque = Torque::ZERO; + } +} + +/// An external torque applied continuously to a dynamic [rigid body](RigidBody) during the integration step. +/// +/// By default, the torque persists across frames. You can clear the torque manually using +/// [`clear`](#method.clear) or set `persistent` to false. +/// +/// ## Example +/// +/// ``` +/// use bevy::prelude::*; +/// # #[cfg(feature = "2d")] +/// # use bevy_xpbd_2d::prelude::*; +/// # #[cfg(feature = "3d")] +/// use bevy_xpbd_3d::prelude::*; +/// +/// # #[cfg(all(feature = "3d", feature = "f32"))] +/// fn setup(mut commands: Commands) { +/// // Apply a torque every physics frame. +/// commands.spawn((RigidBody::Dynamic, ExternalTorque::new(Vec3::Y))); +/// +/// // Apply an initial torque and automatically clear it every physics frame. +/// commands.spawn(( +/// RigidBody::Dynamic, +/// ExternalTorque::new(Vec3::Y).with_persistence(false), +/// )); +/// +/// // Apply multiple torques. +/// let mut torque = ExternalTorque::default(); +/// torque.apply_torque(Vec3::Y).apply_torque(Vec3::X); +/// commands.spawn((RigidBody::Dynamic, torque)); +/// } +/// ``` +#[derive(Reflect, Clone, Copy, Component, Debug, PartialEq, From)] +#[reflect(Component)] +pub struct ExternalTorque { + /// The total external torque that will be applied. + torque: Torque, + /// True if the torque persists across frames, and false if the torque is automatically cleared every physics frame. + pub persistent: bool, +} + +impl Deref for ExternalTorque { + type Target = Torque; + + fn deref(&self) -> &Self::Target { + &self.torque + } +} + +impl DerefMut for ExternalTorque { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.torque + } +} + +impl Default for ExternalTorque { + fn default() -> Self { + Self { + torque: Torque::ZERO, + persistent: true, + } + } +} + +impl ExternalTorque { + /// Zero external torque. + pub const ZERO: Self = Self { + torque: Torque::ZERO, + persistent: true, + }; + + /// Creates a new [`ExternalTorque`] component with a given `torque`. + pub fn new(torque: Torque) -> Self { + Self { + torque, + ..default() + } + } + + /// Sets the torque. + pub fn set_torque(&mut self, torque: Torque) -> &mut Self { + **self = torque; + self + } + + /// Adds the given `torque` to the torque that will be applied. + pub fn apply_torque(&mut self, torque: Torque) -> &mut Self { + **self += torque; + self + } + + /// Determines if the torque is persistent or if it should be automatically cleared every physics frame. + #[doc(alias = "clear_automatically")] + pub fn with_persistence(mut self, is_persistent: bool) -> Self { + self.persistent = is_persistent; + self + } + + /// Returns the torque. + pub fn torque(&self) -> Torque { + self.torque + } + + /// Sets the torque to zero. + pub fn clear(&mut self) { + self.torque = Torque::ZERO; + } +} + +/// An external impulse applied instantly to a dynamic [rigid body](RigidBody) during the integration step. +/// +/// By default, the impulse is cleared every frame. You can set `persistent` to true in order to persist +/// the impulse across frames. +/// +/// ## Example +/// +/// ``` +/// use bevy::prelude::*; +/// # #[cfg(feature = "2d")] +/// # use bevy_xpbd_2d::prelude::*; +/// # #[cfg(feature = "3d")] +/// use bevy_xpbd_3d::prelude::*; +/// +/// # #[cfg(all(feature = "3d", feature = "f32"))] +/// fn setup(mut commands: Commands) { +/// // Apply an impulse. +/// commands.spawn((RigidBody::Dynamic, ExternalImpulse::new(Vec3::Y))); +/// +/// // Apply an impulse every physics frame. +/// commands.spawn(( +/// RigidBody::Dynamic, +/// ExternalImpulse::new(Vec3::Y).with_persistence(true), +/// )); +/// +/// // Apply multiple impulses. +/// let mut impulse = ExternalImpulse::default(); +/// impulse.apply_impulse(Vec3::Y).apply_impulse(Vec3::X); +/// commands.spawn((RigidBody::Dynamic, impulse)); +/// +/// // Apply an impulse at a specific point relative to the given center of mass, also applying an angular impulse. +/// // In this case, the angular impulse would cause the body to rotate counterclockwise. +/// let mut impulse = ExternalImpulse::default(); +/// impulse.apply_impulse_at_point(Vec3::Y, Vec3::X, Vec3::ZERO); +/// commands.spawn((RigidBody::Dynamic, impulse)); +/// } +/// ``` +#[derive(Reflect, Clone, Copy, Component, Debug, PartialEq, From)] +#[reflect(Component)] +pub struct ExternalImpulse { + /// The total external impulse that will be applied. + impulse: Vector, + /// True if the impulse persists across frames, and false if the impulse is automatically cleared every physics frame. + /// + /// If you clear the impulse manually, use the [`clear`](#method.clear) method. This will clear the impulse and + /// the angular impulse that is applied when the impulse is not applied at the center of mass. + pub persistent: bool, + /// The angular impulse caused by impulses applied at certain points using [`apply_impulse_at_point`](#method.apply_impulse_at_point). + angular_impulse: Torque, +} + +impl Deref for ExternalImpulse { + type Target = Vector; + + fn deref(&self) -> &Self::Target { + &self.impulse + } +} + +impl DerefMut for ExternalImpulse { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.impulse + } +} + +impl Default for ExternalImpulse { + fn default() -> Self { + Self { + impulse: Vector::ZERO, + persistent: false, + angular_impulse: Torque::ZERO, + } + } +} + +impl ExternalImpulse { + /// Zero external impulse. + pub const ZERO: Self = Self { + impulse: Vector::ZERO, + persistent: false, + angular_impulse: Torque::ZERO, + }; + + /// Creates a new [`ExternalImpulse`] component with a given `impulse`. + pub fn new(impulse: Vector) -> Self { + Self { + impulse, + ..default() + } + } + + /// Sets the impulse. Note that the angular impulse caused by any impulses will not be reset. + pub fn set_impulse(&mut self, impulse: Vector) -> &mut Self { + **self = impulse; + self + } + + /// Adds the given `impulse` to the impulse that will be applied. + pub fn apply_impulse(&mut self, impulse: Vector) -> &mut Self { + **self += impulse; + self + } + + /// Applies the given `impulse` at a local `point`, which will also cause an angular impulse to be applied. + pub fn apply_impulse_at_point( + &mut self, + impulse: Vector, + point: Vector, + center_of_mass: Vector, + ) -> &mut Self { + **self += impulse; + #[cfg(feature = "2d")] + { + self.angular_impulse += (point - center_of_mass).perp_dot(impulse); + } + #[cfg(feature = "3d")] + { + self.angular_impulse += (point - center_of_mass).cross(impulse); + } + self + } + + /// Returns the impulse. + pub fn impulse(&self) -> Vector { + self.impulse + } + + /// Returns the angular impulse caused by impulses applied at certain points using + /// [`apply_impulse_at_point`](#method.apply_impulse_at_point). + pub fn angular_impulse(&self) -> Torque { + self.angular_impulse + } + + /// Determines if the impulse is persistent or if it should be automatically cleared every physics frame. + #[doc(alias = "clear_automatically")] + pub fn with_persistence(mut self, is_persistent: bool) -> Self { + self.persistent = is_persistent; + self + } + + /// Sets the impulse and the potential angular impulse caused by the impulse to zero. + pub fn clear(&mut self) { + self.impulse = Vector::ZERO; + self.angular_impulse = Torque::ZERO; + } +} + +/// An external angular impulse applied to a dynamic [rigid body](RigidBody) during the integration step. +/// +/// By default, the angular impulse is cleared every frame. You can set `persistent` to true in order to persist +/// the impulse across frames. +/// +/// ## Example +/// +/// ``` +/// use bevy::prelude::*; +/// # #[cfg(feature = "2d")] +/// # use bevy_xpbd_2d::prelude::*; +/// # #[cfg(feature = "3d")] +/// use bevy_xpbd_3d::prelude::*; +/// +/// # #[cfg(all(feature = "3d", feature = "f32"))] +/// fn setup(mut commands: Commands) { +/// // Apply an angular impulse. +/// commands.spawn((RigidBody::Dynamic, ExternalAngularImpulse::new(Vec3::Y))); +/// +/// // Apply an angular impulse every physics frame. +/// commands.spawn(( +/// RigidBody::Dynamic, +/// ExternalAngularImpulse::new(Vec3::Y).with_persistence(false), +/// )); +/// +/// // Apply multiple angular impulses. +/// let mut angular_impulse = ExternalAngularImpulse::default(); +/// angular_impulse.apply_impulse(Vec3::Y).apply_impulse(Vec3::X); +/// commands.spawn((RigidBody::Dynamic, angular_impulse)); +/// } +/// ``` +#[derive(Reflect, Clone, Copy, Component, Debug, PartialEq, From)] +#[reflect(Component)] +#[doc(alias = "ExternalTorqueImpulse")] +pub struct ExternalAngularImpulse { + /// The total external angular impulse that will be applied. + impulse: Torque, + /// True if the angular impulse persists across frames, and false if + /// the angular impulse is automatically cleared every physics frame. + pub persistent: bool, +} + +impl Deref for ExternalAngularImpulse { + type Target = Torque; + + fn deref(&self) -> &Self::Target { + &self.impulse + } +} + +impl DerefMut for ExternalAngularImpulse { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.impulse + } +} + +impl Default for ExternalAngularImpulse { + fn default() -> Self { + Self { + impulse: Torque::ZERO, + persistent: false, + } + } +} + +impl ExternalAngularImpulse { + /// Zero external angular impulse. + pub const ZERO: Self = Self { + impulse: Torque::ZERO, + persistent: false, + }; + + /// Creates a new [`ExternalAngularImpulse`] component with a given `impulse`. + pub fn new(impulse: Torque) -> Self { + Self { + impulse, + ..default() + } + } + + /// Sets the angular impulse. + pub fn set_impulse(&mut self, impulse: Torque) -> &mut Self { + **self = impulse; + self + } + + /// Adds the given `impulse` to the angular impulse that will be applied. + pub fn apply_impulse(&mut self, impulse: Torque) -> &mut Self { + **self += impulse; + self + } + + /// Determines if the angular impulse is persistent or if it should be automatically cleared every physics frame. + #[doc(alias = "clear_automatically")] + pub fn with_persistence(mut self, is_persistent: bool) -> Self { + self.persistent = is_persistent; + self + } + + /// Returns the angular impulse. + pub fn impulse(&self) -> Torque { + self.impulse + } + + /// Sets the angular impulse to zero. + pub fn clear(&mut self) { + self.impulse = Torque::ZERO; + } +} diff --git a/src/components/mod.rs b/src/components/mod.rs index fc755777..1b23bf70 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -1,15 +1,15 @@ //! Components used for rigid bodies, colliders and mass properties. mod collider; +mod forces; mod layers; mod locked_axes; mod mass_properties; mod rotation; mod world_queries; -use std::ops::{Deref, DerefMut}; - pub use collider::*; +pub use forces::*; pub use layers::*; pub use locked_axes::*; pub use mass_properties::*; @@ -225,272 +225,6 @@ pub(crate) struct PreSolveAngularVelocity(pub Scalar); #[reflect(Component)] pub(crate) struct PreSolveAngularVelocity(pub Vector); -/// An external force applied to a dynamic [rigid body](RigidBody) during the integration step. -/// -/// By default, the force persists across frames. You can clear the force manually using -/// [`clear`](#method.clear) or set `persistent` to false. -/// -/// ## Example -/// -/// ``` -/// use bevy::prelude::*; -/// # #[cfg(feature = "2d")] -/// # use bevy_xpbd_2d::prelude::*; -/// # #[cfg(feature = "3d")] -/// use bevy_xpbd_3d::prelude::*; -/// -/// # #[cfg(all(feature = "3d", feature = "f32"))] -/// fn setup(mut commands: Commands) { -/// // Apply a force every physics frame. -/// commands.spawn((RigidBody::Dynamic, ExternalForce::new(Vec3::Y))); -/// -/// // Apply an initial force and automatically clear it every physics frame. -/// commands.spawn(( -/// RigidBody::Dynamic, -/// ExternalForce::new(Vec3::Y).with_persistence(false), -/// )); -/// -/// // Apply multiple forces. -/// let mut force = ExternalForce::default(); -/// force.apply_force(Vec3::Y).apply_force(Vec3::X); -/// commands.spawn((RigidBody::Dynamic, force)); -/// -/// // Apply a force at a specific point relative to the given center of mass, also applying a torque. -/// // In this case, the torque would cause the body to rotate counterclockwise. -/// let mut force = ExternalForce::default(); -/// force.apply_force_at_point(Vec3::Y, Vec3::X, Vec3::ZERO); -/// commands.spawn((RigidBody::Dynamic, force)); -/// } -/// ``` -#[derive(Reflect, Clone, Copy, Component, Debug, PartialEq, From)] -#[reflect(Component)] -pub struct ExternalForce { - /// The total external force that will be applied. - force: Vector, - /// True if the force persists across frames, and false if the force is automatically cleared every physics frame. - /// - /// If you clear the force manually, use the [`clear`](#method.clear) method. This will clear the force and - /// the torque that is applied when the force is not applied at the center of mass. - pub persistent: bool, - /// The torque caused by forces applied at certain points using [`apply_force_at_point`](#method.apply_force_At_point). - torque: Torque, -} - -impl Deref for ExternalForce { - type Target = Vector; - - fn deref(&self) -> &Self::Target { - &self.force - } -} - -impl DerefMut for ExternalForce { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.force - } -} - -impl Default for ExternalForce { - fn default() -> Self { - Self { - force: Vector::ZERO, - persistent: true, - torque: Torque::ZERO, - } - } -} - -impl ExternalForce { - /// Zero external force. - pub const ZERO: Self = Self { - force: Vector::ZERO, - persistent: true, - torque: Torque::ZERO, - }; - - /// Creates a new [`ExternalForce`] component with a given `force`. - pub fn new(force: Vector) -> Self { - Self { force, ..default() } - } - - /// Sets the force. Note that the torque caused by any forces will not be reset. - pub fn set_force(&mut self, force: Vector) -> &mut Self { - **self = force; - self - } - - /// Adds the given `force` to the force that will be applied. - pub fn apply_force(&mut self, force: Vector) -> &mut Self { - **self += force; - self - } - - /// Applies the given `force` at a local `point`, which will also cause torque to be applied. - pub fn apply_force_at_point( - &mut self, - force: Vector, - point: Vector, - center_of_mass: Vector, - ) -> &mut Self { - **self += force; - #[cfg(feature = "2d")] - { - self.torque += (point - center_of_mass).perp_dot(force); - } - #[cfg(feature = "3d")] - { - self.torque += (point - center_of_mass).cross(force); - } - self - } - - /// Returns the force. - pub fn force(&self) -> Vector { - self.force - } - - /// Returns the torque caused by forces applied at certain points using - /// [`apply_force_at_point`](#method.apply_force_At_point). - pub fn torque(&self) -> Torque { - self.torque - } - - /// Determines if the force is persistent or if it should be automatically cleared every physics frame. - #[doc(alias = "clear_automatically")] - pub fn with_persistence(mut self, is_persistent: bool) -> Self { - self.persistent = is_persistent; - self - } - - /// Sets the force and the potential torque caused by the force to zero. - pub fn clear(&mut self) { - self.force = Vector::ZERO; - self.torque = Torque::ZERO; - } -} - -#[cfg(feature = "2d")] -pub(crate) type Torque = Scalar; - -#[cfg(feature = "3d")] -pub(crate) type Torque = Vector; - -pub(crate) trait FloatZero { - const ZERO: Self; -} - -impl FloatZero for Scalar { - const ZERO: Self = 0.0; -} - -/// An external torque applied to a dynamic [rigid body](RigidBody) during the integration step. -/// -/// By default, the torque persists across frames. You can clear the torque manually using -/// [`clear`](#method.clear) or set `persistent` to false. -/// -/// ## Example -/// -/// ``` -/// use bevy::prelude::*; -/// # #[cfg(feature = "2d")] -/// # use bevy_xpbd_2d::prelude::*; -/// # #[cfg(feature = "3d")] -/// use bevy_xpbd_3d::prelude::*; -/// -/// # #[cfg(all(feature = "3d", feature = "f32"))] -/// fn setup(mut commands: Commands) { -/// // Apply a torque every physics frame. -/// commands.spawn((RigidBody::Dynamic, ExternalTorque::new(Vec3::Y))); -/// -/// // Apply an initial torque and automatically clear it every physics frame. -/// commands.spawn(( -/// RigidBody::Dynamic, -/// ExternalTorque::new(Vec3::Y).with_persistence(false), -/// )); -/// -/// // Apply multiple torques. -/// let mut torque = ExternalTorque::default(); -/// torque.apply_torque(Vec3::Y).apply_torque(Vec3::X); -/// commands.spawn((RigidBody::Dynamic, torque)); -/// } -/// ``` -#[derive(Reflect, Clone, Copy, Component, Debug, PartialEq, From)] -#[reflect(Component)] -pub struct ExternalTorque { - /// The total external torque that will be applied. - torque: Torque, - /// True if the torque persists across frames, and false if the torque is automatically cleared every physics frame. - pub persistent: bool, -} - -impl Deref for ExternalTorque { - type Target = Torque; - - fn deref(&self) -> &Self::Target { - &self.torque - } -} - -impl DerefMut for ExternalTorque { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.torque - } -} - -impl Default for ExternalTorque { - fn default() -> Self { - Self { - torque: Torque::ZERO, - persistent: true, - } - } -} - -impl ExternalTorque { - /// Zero external torque. - pub const ZERO: Self = Self { - torque: Torque::ZERO, - persistent: true, - }; - - /// Creates a new [`ExternalTorque`] component with a given `torque`. - pub fn new(torque: Torque) -> Self { - Self { - torque, - ..default() - } - } - - /// Sets the torque. - pub fn set_torque(&mut self, torque: Torque) -> &mut Self { - **self = torque; - self - } - - /// Adds the given `torque` to the torque that will be applied. - pub fn apply_torque(&mut self, torque: Torque) -> &mut Self { - **self += torque; - self - } - - /// Determines if the torque is persistent or if it should be automatically cleared every physics frame. - #[doc(alias = "clear_automatically")] - pub fn with_persistence(mut self, is_persistent: bool) -> Self { - self.persistent = is_persistent; - self - } - - /// Returns the torque. - pub fn torque(&self) -> Torque { - self.torque - } - - /// Sets the torque to zero. - pub fn clear(&mut self) { - self.torque = Torque::ZERO; - } -} - /// Controls how [gravity](Gravity) affects a specific [rigid body](RigidBody). /// /// A gravity scale of `0.0` will disable gravity, while `2.0` will double the gravity. diff --git a/src/lib.rs b/src/lib.rs index 052fb031..f8d2c5d9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,7 +28,8 @@ //! - Material properties like [restitution](Restitution) and [friction](Friction) //! - [Linear damping](LinearDamping) and [angular damping](AngularDamping) for simulating drag //! - [Gravity] and [gravity scale](GravityScale) -//! - External [forces](ExternalForce) and [torque](ExternalTorque) +//! - External [forces](ExternalForce), [torque](ExternalTorque), [impulses](ExternalImpulse) and +//! [angular impulses](ExternalAngularImpulse) //! - [Locking](LockedAxes) translational and rotational axes //! - [Joints](joints) //! - Built-in [constraints] and support for [custom constraints](constraints#custom-constraints) diff --git a/src/plugins/integrator.rs b/src/plugins/integrator.rs index 5c7700cc..c0c87c99 100644 --- a/src/plugins/integrator.rs +++ b/src/plugins/integrator.rs @@ -23,7 +23,12 @@ impl Plugin for IntegratorPlugin { app.get_schedule_mut(PhysicsSchedule) .expect("add PhysicsSchedule first") .add_systems( - clear_external_force_and_torque + apply_impulses + .after(PhysicsStepSet::BroadPhase) + .before(PhysicsStepSet::Substeps), + ) + .add_systems( + clear_forces_and_impulses .after(PhysicsStepSet::Substeps) .before(PhysicsStepSet::Sleeping), ); @@ -80,11 +85,12 @@ fn integrate_pos( lin_vel.0 *= 1.0 / (1.0 + sub_dt.0 * damping.0); } - let mass = locked_axes.apply_to_vec(Vector::splat(mass.0)); + let effective_mass = locked_axes.apply_to_vec(Vector::splat(mass.0)); let effective_inv_mass = locked_axes.apply_to_vec(Vector::splat(inv_mass.0)); // Apply forces - let gravitation_force = mass * gravity.0 * gravity_scale.map_or(1.0, |scale| scale.0); + let gravitation_force = + effective_mass * gravity.0 * gravity_scale.map_or(1.0, |scale| scale.0); let external_forces = gravitation_force + external_force.force(); lin_vel.0 += sub_dt.0 * external_forces * effective_inv_mass; } @@ -207,18 +213,71 @@ fn integrate_rot( } } -type ForceComponents = (&'static mut ExternalForce, &'static mut ExternalTorque); -type ForceComponentsChanged = Or<(Changed, Changed)>; +type ImpulseQueryComponents = ( + &'static RigidBody, + &'static mut ExternalImpulse, + &'static mut ExternalAngularImpulse, + &'static mut LinearVelocity, + &'static mut AngularVelocity, + &'static Rotation, + &'static InverseMass, + &'static InverseInertia, + Option<&'static LockedAxes>, +); + +fn apply_impulses(mut bodies: Query>) { + for ( + rb, + impulse, + ang_impulse, + mut lin_vel, + mut ang_vel, + rotation, + inv_mass, + inv_inertia, + locked_axes, + ) in &mut bodies + { + if !rb.is_dynamic() { + continue; + } + + let locked_axes = locked_axes.map_or(LockedAxes::default(), |locked_axes| *locked_axes); + + let effective_inv_mass = locked_axes.apply_to_vec(Vector::splat(inv_mass.0)); + let effective_inv_inertia = locked_axes.apply_to_rotation(inv_inertia.rotated(rotation).0); + + lin_vel.0 += impulse.impulse() * effective_inv_mass; + ang_vel.0 += effective_inv_inertia * (ang_impulse.impulse() + impulse.angular_impulse()); + } +} + +type ForceComponents = ( + &'static mut ExternalForce, + &'static mut ExternalTorque, + &'static mut ExternalImpulse, + &'static mut ExternalAngularImpulse, +); +type ForceComponentsChanged = Or<( + Changed, + Changed, + Changed, + Changed, +)>; -fn clear_external_force_and_torque(mut forces: Query) { - for (mut force, mut torque) in &mut forces { - // Clear external force if it's not persistent +fn clear_forces_and_impulses(mut forces: Query) { + for (mut force, mut torque, mut impulse, mut angular_ímpulse) in &mut forces { if !force.persistent { force.clear(); } - // Clear external torque if it's not persistent if !torque.persistent { torque.clear(); } + if !impulse.persistent { + impulse.clear(); + } + if !angular_ímpulse.persistent { + angular_ímpulse.clear(); + } } } diff --git a/src/plugins/prepare.rs b/src/plugins/prepare.rs index 1955c2bf..6c1de7cf 100644 --- a/src/plugins/prepare.rs +++ b/src/plugins/prepare.rs @@ -68,6 +68,8 @@ type RigidBodyComponents = ( Option<&'static AngularVelocity>, Option<&'static ExternalForce>, Option<&'static ExternalTorque>, + Option<&'static ExternalImpulse>, + Option<&'static ExternalAngularImpulse>, Option<&'static Restitution>, Option<&'static Friction>, Option<&'static TimeSleeping>, @@ -91,6 +93,8 @@ fn init_rigid_bodies( ang_vel, force, torque, + impulse, + angular_impulse, restitution, friction, time_sleeping, @@ -164,6 +168,12 @@ fn init_rigid_bodies( if torque.is_none() { body.insert(ExternalTorque::default()); } + if impulse.is_none() { + body.insert(ExternalImpulse::default()); + } + if angular_impulse.is_none() { + body.insert(ExternalAngularImpulse::default()); + } if restitution.is_none() { body.insert(Restitution::default()); } diff --git a/src/plugins/setup.rs b/src/plugins/setup.rs index 85d0942c..713e7303 100644 --- a/src/plugins/setup.rs +++ b/src/plugins/setup.rs @@ -92,6 +92,8 @@ impl Plugin for PhysicsSetupPlugin { .register_type::() .register_type::() .register_type::() + .register_type::() + .register_type::() .register_type::() .register_type::() .register_type::() diff --git a/src/plugins/sleeping.rs b/src/plugins/sleeping.rs index ff44abb8..d3f544be 100644 --- a/src/plugins/sleeping.rs +++ b/src/plugins/sleeping.rs @@ -88,6 +88,8 @@ type BodyWokeUpFilter = Or<( Changed, Changed, Changed, + Changed, + Changed, Changed, )>;