#![warn(clippy::manual_assert)]
#![warn(clippy::semicolon_if_nothing_returned)]
#![allow(clippy::collapsible_else_if)]
#![allow(clippy::collapsible_if)]
#![allow(clippy::collapsible_match)]
#![allow(clippy::too_many_arguments)]
#![allow(clippy::type_complexity)]
use std::time::Duration;
use bevy::app::PluginGroupBuilder;
use bevy::ecs::schedule::{InternedSystemSet, ScheduleConfigs, ScheduleLabel};
use bevy::prelude::*;
use physx::prelude::*;
pub mod core;
pub mod plugins;
pub mod prelude;
pub mod types;
pub mod utils;
pub use physx;
pub use physx_sys;
use crate::prelude as bpx;
use crate::core::systems;
use crate::core::material::{DefaultMaterial, DefaultMaterialHandle};
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct PhysicsSchedule;
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, SystemSet)]
pub enum PhysicsSet {
First,
PropagateTransforms,
Sync,
Simulate,
SimulateFlush,
Create,
CreateFlush,
Last,
}
impl PhysicsSet {
pub fn sets(sync_first: bool) -> ScheduleConfigs<InternedSystemSet> {
if sync_first {
(
Self::First,
Self::PropagateTransforms,
Self::Sync,
Self::Simulate,
Self::SimulateFlush,
Self::Create,
Self::CreateFlush,
Self::Last,
).chain()
} else {
(
Self::First,
Self::PropagateTransforms,
Self::Simulate,
Self::SimulateFlush,
Self::Create,
Self::CreateFlush,
Self::Sync,
Self::Last,
).chain()
}
}
}
pub struct PhysicsPlugins;
impl PluginGroup for PhysicsPlugins {
fn build(self) -> PluginGroupBuilder {
let mut group = PluginGroupBuilder::start::<Self>();
group = group
.add(PhysicsCore::default())
.add(crate::plugins::articulation::ArticulationPlugin)
.add(crate::plugins::damping::DampingPlugin)
.add(crate::plugins::external_force::ExternalForcePlugin)
.add(crate::plugins::kinematic::KinematicPlugin)
.add(crate::plugins::mass_properties::MassPropertiesPlugin)
.add(crate::plugins::name::NamePlugin)
.add(crate::plugins::shape_filter_data::ShapeFilterDataPlugin)
.add(crate::plugins::shape_offsets::ShapeOffsetsPlugin)
.add(crate::plugins::sleep::SleepPlugin)
.add(crate::plugins::velocity::VelocityPlugin)
.add(crate::plugins::lock_flags::LockFlagsPlugin);
#[cfg(feature = "debug-render")] {
group = group.add(crate::plugins::debug_render::DebugRenderPlugin);
}
group
}
}
pub struct PhysicsCore {
pub foundation: bpx::FoundationDescriptor,
pub scene: bpx::SceneDescriptor,
pub timestep: TimestepMode,
pub default_material: DefaultMaterial,
pub sync_first: bool,
}
impl PhysicsCore {
pub fn new() -> Self {
Self::default()
}
pub fn with_timestep(mut self, timestep: TimestepMode) -> Self {
self.timestep = timestep;
self
}
pub fn with_gravity(mut self, gravity: Vec3) -> Self {
self.scene.gravity = gravity;
self
}
pub fn with_pvd(mut self) -> Self {
self.foundation.visual_debugger = true;
self
}
}
impl Default for PhysicsCore {
fn default() -> Self {
Self {
foundation: default(),
scene: default(),
timestep: default(),
default_material: DefaultMaterial {
static_friction: 0.5,
dynamic_friction: 0.5,
restitution: 0.6,
},
sync_first: true,
}
}
}
impl Plugin for PhysicsCore {
fn build(&self, app: &mut App) {
app.init_schedule(PhysicsSchedule);
if !app.is_plugin_added::<AssetPlugin>() {
app.add_plugins(AssetPlugin::default());
}
app.init_asset::<bpx::Geometry>();
app.init_asset::<bpx::Material>();
app.register_type::<PhysicsTime>();
app.insert_resource(PhysicsTime::new(self.timestep));
app.init_resource::<BevyTimeDelta>();
app.edit_schedule(PhysicsSchedule, |schedule| {
schedule.configure_sets(PhysicsSet::sets(self.sync_first));
});
app.add_systems(PhysicsSchedule, (
bevy::transform::systems::sync_simple_transforms,
).in_set(PhysicsSet::PropagateTransforms));
app.add_systems(PhysicsSchedule, (
systems::sync_transform_static,
systems::sync_transform_dynamic,
systems::sync_transform_articulation_links,
systems::sync_transform_nested_shapes,
).in_set(PhysicsSet::Sync));
app.add_systems(PhysicsSchedule, (
systems::scene_simulate,
).in_set(PhysicsSet::Simulate));
app.add_systems(PhysicsSchedule, (
ApplyDeferred,
).in_set(PhysicsSet::SimulateFlush));
app.add_systems(PhysicsSchedule, (
systems::create_rigid_actors,
).in_set(PhysicsSet::Create));
app.add_systems(PhysicsSchedule, (
ApplyDeferred,
).in_set(PhysicsSet::CreateFlush));
app.add_systems(PreUpdate, run_physics_schedule);
}
fn finish(&self, app: &mut App) {
let mut physics = bpx::Physics::new(&self.foundation);
let wake_sleep_callback = app.world_mut().remove_resource::<crate::plugins::sleep::WakeSleepCallback>();
let scene = bpx::Scene::new(&mut physics, &self.scene, wake_sleep_callback.map(|x| x.0));
app.insert_resource(scene);
let default_material = DefaultMaterialHandle(
app.world_mut().resource_mut::<Assets<bpx::Material>>()
.add(physics.create_material(0.5, 0.5, 0.6, ()).unwrap())
);
app.insert_resource(default_material);
app.insert_resource(physics);
}
}
#[derive(Debug, PartialEq, Clone, Copy, Reflect)]
#[reflect(Default)]
pub enum TimestepMode {
Fixed {
dt: f32,
substeps: usize,
},
Variable {
max_dt: f32,
time_scale: f32,
substeps: usize,
},
Interpolated {
dt: f32,
time_scale: f32,
substeps: usize,
},
Custom,
}
impl Default for TimestepMode {
fn default() -> Self {
Self::Interpolated { dt: 1. / 60., time_scale: 1., substeps: 1 }
}
}
#[derive(Debug, Default, Copy, Clone, Reflect)]
#[reflect(Default)]
pub struct PhysicsTimeInner {
pub timestep: TimestepMode,
pub overstep: Duration,
}
pub type PhysicsTime = Time<PhysicsTimeInner>;
pub trait PhysicsTimeExtensions {
fn new(timestep: TimestepMode) -> Self;
fn timestep(&self) -> TimestepMode;
fn set_timestep(&mut self, timestep: TimestepMode);
}
impl PhysicsTimeExtensions for PhysicsTime {
fn new(timestep: TimestepMode) -> Self {
Self::new_with(PhysicsTimeInner { timestep, overstep: Duration::default() })
}
#[inline]
fn timestep(&self) -> TimestepMode {
self.context().timestep
}
#[inline]
fn set_timestep(&mut self, timestep: TimestepMode) {
self.context_mut().timestep = timestep;
}
}
#[derive(Resource, Default)]
struct BevyTimeDelta(f32);
pub fn run_physics_schedule(world: &mut World) {
fn simulate(world: &mut World, delta: f32, substeps: usize) {
let dt = Duration::from_secs_f32(delta / substeps as f32);
for _ in 0..substeps {
let mut pxtime = world.resource_mut::<PhysicsTime>();
pxtime.advance_by(dt);
world.run_schedule(PhysicsSchedule);
}
}
match world.resource::<PhysicsTime>().timestep() {
TimestepMode::Fixed { dt, substeps } => {
let mut pxdelta = world.resource_mut::<BevyTimeDelta>();
pxdelta.0 = 0.;
simulate(world, dt, substeps);
}
TimestepMode::Variable { max_dt, time_scale, substeps } => {
let bevy_delta = world.resource::<Time>().delta_secs();
let mut pxdelta = world.resource_mut::<BevyTimeDelta>();
pxdelta.0 += bevy_delta * time_scale;
let dt = if pxdelta.0 > max_dt && max_dt > 0. {
max_dt
} else {
pxdelta.0
};
pxdelta.0 = 0.;
simulate(world, dt, substeps);
}
TimestepMode::Interpolated { dt, time_scale, substeps } => {
let bevy_delta = world.resource::<Time>().delta_secs();
let mut pxdelta = world.resource_mut::<BevyTimeDelta>();
pxdelta.0 += bevy_delta * time_scale;
if pxdelta.0 > dt && dt > 0. {
pxdelta.0 -= dt;
if pxdelta.0 > dt { pxdelta.0 = dt; }
simulate(world, dt, substeps);
}
}
TimestepMode::Custom => {
}
}
}