use crate::prelude::*;
use bevy::{
ecs::{intern::Interned, query::QueryData, schedule::ScheduleLabel},
prelude::*,
};
use dynamics::solver::SolverDiagnostics;
use super::solver::solver_body::SolverBody;
pub struct IntegratorPlugin {
schedule: Interned<dyn ScheduleLabel>,
}
impl IntegratorPlugin {
pub fn new(schedule: impl ScheduleLabel) -> Self {
Self {
schedule: schedule.intern(),
}
}
}
impl Default for IntegratorPlugin {
fn default() -> Self {
Self::new(SubstepSchedule)
}
}
impl Plugin for IntegratorPlugin {
fn build(&self, app: &mut App) {
app.register_required_components::<SolverBody, VelocityIntegrationData>();
app.init_resource::<Gravity>();
app.configure_sets(
PhysicsSchedule,
(
IntegrationSystems::UpdateVelocityIncrements
.in_set(SolverSystems::PreSubstep)
.before(IntegrationSystems::Velocity),
IntegrationSystems::ClearVelocityIncrements
.in_set(SolverSystems::PostSubstep)
.after(IntegrationSystems::Velocity),
),
);
app.add_systems(
PhysicsSchedule,
(
pre_process_velocity_increments
.in_set(IntegrationSystems::UpdateVelocityIncrements),
clear_velocity_increments.in_set(IntegrationSystems::ClearVelocityIncrements),
),
);
app.configure_sets(
self.schedule.intern(),
(IntegrationSystems::Velocity, IntegrationSystems::Position).chain(),
);
app.add_systems(
self.schedule.intern(),
(
(integrate_velocities, clamp_velocities)
.chain()
.in_set(IntegrationSystems::Velocity),
integrate_positions.in_set(IntegrationSystems::Position),
),
);
}
}
#[derive(SystemSet, Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum IntegrationSystems {
UpdateVelocityIncrements,
Velocity,
Position,
ClearVelocityIncrements,
}
#[deprecated(since = "0.4.0", note = "Renamed to `IntegrationSystems`")]
pub type IntegrationSet = IntegrationSystems;
#[cfg_attr(feature = "2d", doc = "use avian2d::prelude::*;")]
#[cfg_attr(feature = "3d", doc = "use avian3d::prelude::*;")]
#[cfg_attr(
feature = "2d",
doc = " .insert_resource(Gravity(Vec2::NEG_Y * 100.0))"
)]
#[cfg_attr(
feature = "3d",
doc = " .insert_resource(Gravity(Vec3::NEG_Y * 19.6))"
)]
#[derive(Reflect, Resource, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
#[reflect(Debug, Resource)]
pub struct Gravity(pub Vector);
impl Default for Gravity {
fn default() -> Self {
Self(Vector::Y * -9.81)
}
}
impl Gravity {
pub const ZERO: Gravity = Gravity(Vector::ZERO);
}
#[derive(Component, Debug, Default, Reflect)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
#[reflect(Component, Debug, Default)]
pub struct CustomVelocityIntegration;
#[derive(Component, Debug, Default, Reflect)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
#[reflect(Component, Debug, Default)]
pub struct CustomPositionIntegration;
#[derive(Component, Debug, Default, PartialEq, Reflect)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
#[reflect(Component, Debug, Default, PartialEq)]
pub struct VelocityIntegrationData {
pub linear_increment: Vector,
pub angular_increment: AngularVector,
pub linear_damping_rhs: Scalar,
pub angular_damping_rhs: Scalar,
}
impl VelocityIntegrationData {
pub fn apply_linear_acceleration(&mut self, acceleration: Vector) {
self.linear_increment += acceleration;
}
pub fn apply_angular_acceleration(&mut self, acceleration: AngularVector) {
self.angular_increment += acceleration;
}
pub fn update_linear_damping_rhs(&mut self, damping_coefficient: Scalar, delta_secs: Scalar) {
self.linear_damping_rhs = 1.0 / (1.0 + delta_secs * damping_coefficient);
}
pub fn update_angular_damping_rhs(&mut self, damping_coefficient: Scalar, delta_secs: Scalar) {
self.angular_damping_rhs = 1.0 / (1.0 + delta_secs * damping_coefficient);
}
}
pub fn pre_process_velocity_increments(
mut bodies: Query<(
&RigidBody,
&mut VelocityIntegrationData,
Option<&LinearDamping>,
Option<&AngularDamping>,
Option<&GravityScale>,
Option<&LockedAxes>,
)>,
gravity: Res<Gravity>,
time: Res<Time<Substeps>>,
mut diagnostics: ResMut<SolverDiagnostics>,
) {
let start = crate::utils::Instant::now();
let delta_secs = time.delta_secs_f64() as Scalar;
bodies.par_iter_mut().for_each(
|(rb, mut integration, lin_damping, ang_damping, gravity_scale, locked_axes)| {
if !rb.is_dynamic() {
return;
}
let locked_axes = locked_axes.map_or(LockedAxes::default(), |locked_axes| *locked_axes);
let lin_damping = lin_damping.map_or(0.0, |damping| damping.0);
let ang_damping = ang_damping.map_or(0.0, |damping| damping.0);
integration.update_linear_damping_rhs(lin_damping, delta_secs);
integration.update_angular_damping_rhs(ang_damping, delta_secs);
integration.linear_increment += gravity.0 * gravity_scale.map_or(1.0, |scale| scale.0);
integration.linear_increment = locked_axes.apply_to_vec(integration.linear_increment);
integration.angular_increment =
locked_axes.apply_to_angular_velocity(integration.angular_increment);
integration.linear_increment *= delta_secs;
integration.angular_increment *= delta_secs;
},
);
diagnostics.update_velocity_increments += start.elapsed();
}
fn clear_velocity_increments(
mut bodies: Query<&mut VelocityIntegrationData, With<SolverBody>>,
mut diagnostics: ResMut<SolverDiagnostics>,
) {
let start = crate::utils::Instant::now();
bodies.par_iter_mut().for_each(|mut integration| {
integration.linear_increment = Vector::ZERO;
integration.angular_increment = AngularVector::ZERO;
});
diagnostics.update_velocity_increments += start.elapsed();
}
#[derive(QueryData)]
#[query_data(mutable)]
#[doc(hidden)]
pub struct VelocityIntegrationQuery {
solver_body: &'static mut SolverBody,
integration: &'static mut VelocityIntegrationData,
#[cfg(feature = "3d")]
angular_inertia: &'static ComputedAngularInertia,
#[cfg(feature = "3d")]
rotation: &'static Rotation,
}
pub fn integrate_velocities(
mut bodies: Query<
VelocityIntegrationQuery,
(RigidBodyActiveFilter, Without<CustomVelocityIntegration>),
>,
mut diagnostics: ResMut<SolverDiagnostics>,
#[cfg(feature = "3d")] time: Res<Time>,
) {
let start = crate::utils::Instant::now();
#[cfg(feature = "3d")]
let delta_secs = time.delta_secs_f64() as Scalar;
bodies.par_iter_mut().for_each(|mut body| {
if body.solver_body.flags.is_kinematic() {
return;
}
body.solver_body.linear_velocity *= body.integration.linear_damping_rhs;
body.solver_body.angular_velocity *= body.integration.angular_damping_rhs;
body.solver_body.linear_velocity += body.integration.linear_increment;
body.solver_body.angular_velocity += body.integration.angular_increment;
#[cfg(feature = "3d")]
{
if body.solver_body.is_gyroscopic() {
let rotation = body.solver_body.delta_rotation.0 * body.rotation.0;
solve_gyroscopic_torque(
&mut body.solver_body.angular_velocity,
rotation,
body.angular_inertia,
delta_secs,
);
}
}
});
diagnostics.integrate_velocities += start.elapsed();
}
#[cfg(feature = "3d")]
#[inline]
pub fn solve_gyroscopic_torque(
ang_vel: &mut Vector,
rotation: Quaternion,
local_inertia: &ComputedAngularInertia,
delta_secs: Scalar,
) {
let local_ang_vel = rotation.inverse() * *ang_vel;
let local_momentum = local_inertia.tensor() * local_ang_vel;
let mut new_local_momentum = local_momentum - delta_secs * local_ang_vel.cross(local_momentum);
let new_local_momentum_length_squared = new_local_momentum.length_squared();
if new_local_momentum_length_squared == 0.0 {
*ang_vel = Vector::ZERO;
return;
}
new_local_momentum *=
(local_momentum.length_squared() / new_local_momentum_length_squared).sqrt();
let local_inverse_inertia = local_inertia.inverse();
*ang_vel = rotation * (local_inverse_inertia * new_local_momentum);
}
fn clamp_velocities(
mut bodies: ParamSet<(
Query<(&mut SolverBody, &MaxLinearSpeed)>,
Query<(&mut SolverBody, &MaxAngularSpeed)>,
)>,
mut diagnostics: ResMut<SolverDiagnostics>,
) {
let start = crate::utils::Instant::now();
bodies.p0().iter_mut().for_each(|(mut body, max_speed)| {
let linear_speed_squared = body.linear_velocity.length_squared();
if linear_speed_squared > max_speed.0 * max_speed.0 {
body.linear_velocity *= max_speed.0 / linear_speed_squared.sqrt();
}
});
bodies.p1().iter_mut().for_each(|(mut body, max_speed)| {
#[cfg(feature = "2d")]
if body.angular_velocity.abs() > max_speed.0 {
body.angular_velocity = max_speed.copysign(body.angular_velocity);
}
#[cfg(feature = "3d")]
{
let angular_speed_squared = body.angular_velocity.length_squared();
if angular_speed_squared > max_speed.0 * max_speed.0 {
body.angular_velocity *= max_speed.0 / angular_speed_squared.sqrt();
}
}
});
diagnostics.integrate_velocities += start.elapsed();
}
pub fn integrate_positions(
mut solver_bodies: Query<&mut SolverBody, Without<CustomPositionIntegration>>,
time: Res<Time>,
mut diagnostics: ResMut<SolverDiagnostics>,
) {
let start = crate::utils::Instant::now();
let delta_secs = time.delta_seconds_adjusted();
solver_bodies.par_iter_mut().for_each(|body| {
let SolverBody {
linear_velocity,
angular_velocity,
delta_position,
delta_rotation,
..
} = body.into_inner();
*delta_position += *linear_velocity * delta_secs;
#[cfg(feature = "2d")]
{
*delta_rotation = Rotation::radians(*angular_velocity * delta_secs) * *delta_rotation;
}
#[cfg(feature = "3d")]
{
delta_rotation.0 =
Quaternion::from_scaled_axis(*angular_velocity * delta_secs) * delta_rotation.0;
}
});
diagnostics.integrate_positions += start.elapsed();
}
#[cfg(test)]
mod tests {
use core::time::Duration;
use approx::assert_relative_eq;
use crate::prelude::*;
use bevy::{mesh::MeshPlugin, prelude::*, time::TimeUpdateStrategy};
fn create_app() -> App {
let mut app = App::new();
app.add_plugins((
MinimalPlugins,
PhysicsPlugins::default(),
TransformPlugin,
#[cfg(feature = "bevy_scene")]
AssetPlugin::default(),
#[cfg(feature = "bevy_scene")]
bevy::scene::ScenePlugin,
MeshPlugin,
));
app
}
#[test]
fn semi_implicit_euler() {
let mut app = create_app();
app.insert_resource(SubstepCount(1));
app.finish();
let body_entity = app
.world_mut()
.spawn((
RigidBody::Dynamic,
#[cfg(feature = "2d")]
{
(
MassPropertiesBundle::from_shape(&Rectangle::from_length(1.0), 1.0),
AngularVelocity(2.0),
)
},
#[cfg(feature = "3d")]
{
(
MassPropertiesBundle::from_shape(&Cuboid::from_length(1.0), 1.0),
AngularVelocity(Vector::Z * 2.0),
)
},
))
.id();
app.insert_resource(Time::from_hz(10.0));
app.insert_resource(TimeUpdateStrategy::ManualDuration(Duration::from_secs_f64(
1.0 / 10.0,
)));
app.update();
for _ in 0..100 {
app.update();
}
let entity_ref = app.world_mut().entity(body_entity);
let position = entity_ref.get::<Position>().unwrap().0;
let rotation = *entity_ref.get::<Rotation>().unwrap();
let linear_velocity = entity_ref.get::<LinearVelocity>().unwrap().0;
let angular_velocity = entity_ref.get::<AngularVelocity>().unwrap().0;
assert_relative_eq!(position, Vector::NEG_Y * 490.5, epsilon = 10.0);
#[cfg(feature = "2d")]
assert_relative_eq!(
rotation.as_radians(),
Rotation::radians(20.0).as_radians(),
epsilon = 0.00001
);
#[cfg(feature = "3d")]
assert_relative_eq!(
rotation.0,
Quaternion::from_rotation_z(20.0),
epsilon = 0.01
);
assert_relative_eq!(linear_velocity, Vector::NEG_Y * 98.1, epsilon = 0.0001);
#[cfg(feature = "2d")]
assert_relative_eq!(angular_velocity, 2.0, epsilon = 0.00001);
#[cfg(feature = "3d")]
assert_relative_eq!(angular_velocity, Vector::Z * 2.0, epsilon = 0.00001);
}
}