use crate::pipeline::{CollisionEvent, ContactForceEvent};
use crate::plugin::configuration::SimulationToRenderTime;
use crate::plugin::{systems, RapierConfiguration, RapierContext};
use crate::prelude::*;
use bevy::{
ecs::{
event::{event_update_system, Events},
schedule::{ScheduleLabel, SystemConfigs},
system::SystemParamItem,
},
utils::intern::Interned,
};
use bevy::{prelude::*, transform::TransformSystem};
use rapier::dynamics::IntegrationParameters;
use std::marker::PhantomData;
pub type NoUserData = ();
pub struct RapierPhysicsPlugin<PhysicsHooks = ()> {
schedule: Interned<dyn ScheduleLabel>,
length_unit: f32,
default_system_setup: bool,
_phantom: PhantomData<PhysicsHooks>,
}
impl<PhysicsHooks> RapierPhysicsPlugin<PhysicsHooks>
where
PhysicsHooks: 'static + BevyPhysicsHooks,
for<'w, 's> SystemParamItem<'w, 's, PhysicsHooks>: BevyPhysicsHooks,
{
pub fn with_length_unit(mut self, length_unit: f32) -> Self {
self.length_unit = length_unit;
self
}
pub fn with_default_system_setup(mut self, default_system_setup: bool) -> Self {
self.default_system_setup = default_system_setup;
self
}
#[cfg(feature = "dim2")]
pub fn pixels_per_meter(pixels_per_meter: f32) -> Self {
Self {
length_unit: pixels_per_meter,
default_system_setup: true,
..default()
}
}
pub fn in_fixed_schedule(self) -> Self {
self.in_schedule(FixedUpdate)
}
pub fn in_schedule(mut self, schedule: impl ScheduleLabel) -> Self {
self.schedule = schedule.intern();
self
}
pub fn get_systems(set: PhysicsSet) -> SystemConfigs {
match set {
PhysicsSet::SyncBackend => (
systems::update_character_controls,
(
bevy::transform::systems::sync_simple_transforms,
bevy::transform::systems::propagate_transforms,
)
.chain()
.in_set(RapierTransformPropagateSet),
#[cfg(all(feature = "dim3", feature = "async-collider"))]
systems::init_async_scene_colliders,
#[cfg(all(feature = "dim3", feature = "async-collider"))]
systems::init_async_colliders,
systems::init_rigid_bodies,
systems::init_colliders,
systems::init_joints,
systems::sync_removals,
apply_deferred,
systems::apply_scale,
systems::apply_collider_user_changes,
systems::apply_rigid_body_user_changes,
systems::apply_joint_user_changes,
systems::apply_initial_rigid_body_impulses,
)
.chain()
.into_configs(),
PhysicsSet::StepSimulation => (
systems::step_simulation::<PhysicsHooks>,
event_update_system::<CollisionEvent>
.before(systems::step_simulation::<PhysicsHooks>),
event_update_system::<ContactForceEvent>
.before(systems::step_simulation::<PhysicsHooks>),
)
.into_configs(),
PhysicsSet::Writeback => (
systems::update_colliding_entities,
systems::writeback_rigid_bodies,
systems::writeback_mass_properties,
event_update_system::<MassModifiedEvent>.after(systems::writeback_mass_properties),
)
.into_configs(),
}
}
}
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
pub struct RapierTransformPropagateSet;
impl<PhysicsHooksSystemParam> Default for RapierPhysicsPlugin<PhysicsHooksSystemParam> {
fn default() -> Self {
Self {
schedule: PostUpdate.intern(),
length_unit: 1.0,
default_system_setup: true,
_phantom: PhantomData,
}
}
}
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
pub enum PhysicsSet {
SyncBackend,
StepSimulation,
Writeback,
}
impl<PhysicsHooks> Plugin for RapierPhysicsPlugin<PhysicsHooks>
where
PhysicsHooks: 'static + BevyPhysicsHooks,
for<'w, 's> SystemParamItem<'w, 's, PhysicsHooks>: BevyPhysicsHooks,
{
fn build(&self, app: &mut App) {
app.register_type::<RigidBody>()
.register_type::<Velocity>()
.register_type::<AdditionalMassProperties>()
.register_type::<MassProperties>()
.register_type::<LockedAxes>()
.register_type::<ExternalForce>()
.register_type::<ExternalImpulse>()
.register_type::<Sleeping>()
.register_type::<Damping>()
.register_type::<Dominance>()
.register_type::<Ccd>()
.register_type::<SoftCcd>()
.register_type::<GravityScale>()
.register_type::<CollidingEntities>()
.register_type::<Sensor>()
.register_type::<Friction>()
.register_type::<Restitution>()
.register_type::<CollisionGroups>()
.register_type::<SolverGroups>()
.register_type::<ContactForceEventThreshold>()
.register_type::<ContactSkin>()
.register_type::<Group>();
app.insert_resource(SimulationToRenderTime::default())
.insert_resource(RapierContext {
integration_parameters: IntegrationParameters {
length_unit: self.length_unit,
..Default::default()
},
..Default::default()
})
.insert_resource(Events::<CollisionEvent>::default())
.insert_resource(Events::<ContactForceEvent>::default())
.insert_resource(Events::<MassModifiedEvent>::default());
app.init_resource::<RapierConfiguration>();
if self.default_system_setup {
app.configure_sets(
self.schedule,
(
PhysicsSet::SyncBackend,
PhysicsSet::StepSimulation,
PhysicsSet::Writeback,
)
.chain()
.before(TransformSystem::TransformPropagate),
);
app.add_systems(PostUpdate, (systems::sync_removals,));
app.add_systems(
self.schedule,
(
Self::get_systems(PhysicsSet::SyncBackend).in_set(PhysicsSet::SyncBackend),
Self::get_systems(PhysicsSet::StepSimulation)
.in_set(PhysicsSet::StepSimulation),
Self::get_systems(PhysicsSet::Writeback).in_set(PhysicsSet::Writeback),
),
);
if self.schedule.as_dyn_eq().dyn_eq(FixedUpdate.as_dyn_eq()) {
let config = app.world.resource::<RapierConfiguration>();
match config.timestep_mode {
TimestepMode::Fixed { .. } => {}
mode => {
warn!("TimestepMode is set to `{:?}`, it is recommended to use `TimestepMode::Fixed` if you have the physics in `FixedUpdate`", mode);
}
}
}
}
}
}