Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use SystemParam for physics hooks #323

Merged
merged 3 commits into from
Feb 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## Unreleased
### Modified
- The `PhysicsHooksWithQuery` trait has been renamed to by the `BevyPhysicsHooks`.
- Bevy resources and queries accessed by the physics hook are now specified by the implementor of `BevyPhysicsHooks`
which must derive Bevy’s `SystemParam` trait. This implies that the physics hook’s `filter_contact_pair` (and
all its other methods) no longer take the Bevy `Query` as argument. Queries and resources are accessed through
`self`.

## 0.20.0 (15 Jan. 2023)
### Added
- Add the `RigidBodyDisabled` and `ColliderDisabled` component that can be inserted to disable a rigid-body
Expand Down
26 changes: 11 additions & 15 deletions bevy_rapier2d/examples/contact_filter2.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use bevy::prelude::*;
use bevy::{ecs::system::SystemParam, prelude::*};
use bevy_rapier2d::prelude::*;

#[derive(PartialEq, Eq, Clone, Copy, Component)]
Expand All @@ -11,15 +11,15 @@ enum CustomFilterTag {
// same user_data value.
// Note that using collision groups would be a more efficient way of doing
// this, but we use custom filters instead for demonstration purpose.
struct SameUserDataFilter;
impl<'a> PhysicsHooksWithQuery<&'a CustomFilterTag> for SameUserDataFilter {
fn filter_contact_pair(
&self,
context: PairFilterContextView,
tags: &Query<&'a CustomFilterTag>,
) -> Option<SolverFlags> {
if tags.get(context.collider1()).ok().copied()
== tags.get(context.collider2()).ok().copied()
#[derive(SystemParam)]
struct SameUserDataFilter<'w, 's> {
tags: Query<'w, 's, &'static CustomFilterTag>,
}

impl BevyPhysicsHooks for SameUserDataFilter<'_, '_> {
fn filter_contact_pair(&self, context: PairFilterContextView) -> Option<SolverFlags> {
if self.tags.get(context.collider1()).ok().copied()
== self.tags.get(context.collider2()).ok().copied()
{
Some(SolverFlags::COMPUTE_IMPULSES)
} else {
Expand All @@ -36,7 +36,7 @@ fn main() {
0xFF as f32 / 255.0,
)))
.add_plugins(DefaultPlugins)
.add_plugin(RapierPhysicsPlugin::<&CustomFilterTag>::pixels_per_meter(
.add_plugin(RapierPhysicsPlugin::<SameUserDataFilter>::pixels_per_meter(
100.0,
))
.add_plugin(RapierDebugRenderPlugin::default())
Expand All @@ -56,10 +56,6 @@ pub fn setup_physics(mut commands: Commands) {
/*
* Ground
*/
commands.insert_resource(PhysicsHooksWithQueryResource(Box::new(
SameUserDataFilter {},
)));

let ground_size = 100.0;

commands.spawn((
Expand Down
26 changes: 11 additions & 15 deletions bevy_rapier3d/examples/contact_filter3.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use bevy::prelude::*;
use bevy::{ecs::system::SystemParam, prelude::*};
use bevy_rapier3d::prelude::*;

#[derive(PartialEq, Eq, Clone, Copy, Component)]
Expand All @@ -11,15 +11,15 @@ enum CustomFilterTag {
// same user_data value.
// Note that using collision groups would be a more efficient way of doing
// this, but we use custom filters instead for demonstration purpose.
struct SameUserDataFilter;
impl<'a> PhysicsHooksWithQuery<&'a CustomFilterTag> for SameUserDataFilter {
fn filter_contact_pair(
&self,
context: PairFilterContextView,
tags: &Query<&'a CustomFilterTag>,
) -> Option<SolverFlags> {
if tags.get(context.collider1()).ok().copied()
== tags.get(context.collider2()).ok().copied()
#[derive(SystemParam)]
struct SameUserDataFilter<'w, 's> {
tags: Query<'w, 's, &'static CustomFilterTag>,
}

impl BevyPhysicsHooks for SameUserDataFilter<'_, '_> {
fn filter_contact_pair(&self, context: PairFilterContextView) -> Option<SolverFlags> {
if self.tags.get(context.collider1()).ok().copied()
== self.tags.get(context.collider2()).ok().copied()
{
Some(SolverFlags::COMPUTE_IMPULSES)
} else {
Expand All @@ -36,7 +36,7 @@ fn main() {
0xFF as f32 / 255.0,
)))
.add_plugins(DefaultPlugins)
.add_plugin(RapierPhysicsPlugin::<&CustomFilterTag>::default())
.add_plugin(RapierPhysicsPlugin::<SameUserDataFilter>::default())
.add_plugin(RapierDebugRenderPlugin::default())
.add_startup_system(setup_graphics)
.add_startup_system(setup_physics)
Expand All @@ -55,10 +55,6 @@ pub fn setup_physics(mut commands: Commands) {
/*
* Ground
*/
commands.insert_resource(PhysicsHooksWithQueryResource(Box::new(
SameUserDataFilter {},
)));

let ground_size = 10.0;

commands.spawn((
Expand Down
5 changes: 2 additions & 3 deletions src/pipeline/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
pub(crate) use self::events::EventQueue;
pub use self::events::{CollisionEvent, ContactForceEvent};
pub(crate) use self::physics_hooks::PhysicsHooksWithQueryInstance;
pub(crate) use self::physics_hooks::BevyPhysicsHooksAdapter;
pub use self::physics_hooks::{
ContactModificationContextView, PairFilterContextView, PhysicsHooksWithQuery,
PhysicsHooksWithQueryResource,
BevyPhysicsHooks, ContactModificationContextView, PairFilterContextView,
};
pub use query_filter::{QueryFilter, QueryFilterFlags};

Expand Down
93 changes: 42 additions & 51 deletions src/pipeline/physics_hooks.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
use bevy::ecs::query::WorldQuery;
use bevy::prelude::*;
use rapier::geometry::SolverFlags;
use rapier::pipeline::{ContactModificationContext, PairFilterContext, PhysicsHooks};
use bevy::{
ecs::system::{SystemParam, SystemParamFetch, SystemParamItem},
prelude::*,
};
use rapier::{
pipeline::{ContactModificationContext, PairFilterContext},
prelude::{PhysicsHooks, SolverFlags},
};

/// Read-only access to the properties of a collision pair filter context.
pub struct PairFilterContextView<'a> {
Expand Down Expand Up @@ -80,7 +84,7 @@ impl<'a, 'b> ContactModificationContextView<'a, 'b> {
}

/// User-defined functions called by the physics engines during one timestep in order to customize its behavior.
pub trait PhysicsHooksWithQuery<UserData: WorldQuery>: Send + Sync {
pub trait BevyPhysicsHooks: SystemParam + Send + Sync {
/// Applies the contact pair filter.
///
/// Note that this method will only be called if at least one of the colliders
Expand All @@ -105,11 +109,7 @@ pub trait PhysicsHooksWithQuery<UserData: WorldQuery>: Send + Sync {
/// will be taken into account by the constraints solver. If this returns
/// `Some(SolverFlags::empty())` then the constraints solver will ignore these
/// contacts.
fn filter_contact_pair(
&self,
_context: PairFilterContextView,
_user_data: &Query<UserData>,
) -> Option<SolverFlags> {
fn filter_contact_pair(&self, _context: PairFilterContextView) -> Option<SolverFlags> {
None
}

Expand All @@ -133,11 +133,7 @@ pub trait PhysicsHooksWithQuery<UserData: WorldQuery>: Send + Sync {
/// not compute any intersection information for it.
/// If this return `true` then the narrow-phase will compute intersection
/// information for this pair.
fn filter_intersection_pair(
&self,
_context: PairFilterContextView,
_user_data: &Query<UserData>,
) -> bool {
fn filter_intersection_pair(&self, _context: PairFilterContextView) -> bool {
false
}

Expand Down Expand Up @@ -166,68 +162,63 @@ pub trait PhysicsHooksWithQuery<UserData: WorldQuery>: Send + Sync {
/// as 0 and can be modified in `context.user_data`.
///
/// The world-space contact normal can be modified in `context.normal`.
fn modify_solver_contacts(
&self,
_context: ContactModificationContextView,
_user_data: &Query<UserData>,
) {
}
fn modify_solver_contacts(&self, _context: ContactModificationContextView) {}
}

impl<T, UserData> PhysicsHooksWithQuery<UserData> for T
impl<T> BevyPhysicsHooks for T
where
T: PhysicsHooks + Send + Sync,
UserData: WorldQuery,
T: 'static + PhysicsHooks + SystemParam + Send + Sync,
for<'w, 's> T::Fetch: SystemParamFetch<'w, 's, Item = T>,
{
fn filter_contact_pair(
&self,
context: PairFilterContextView,
_: &Query<UserData>,
) -> Option<SolverFlags> {
fn filter_contact_pair(&self, context: PairFilterContextView) -> Option<SolverFlags> {
PhysicsHooks::filter_contact_pair(self, context.raw)
}

fn filter_intersection_pair(
&self,
context: PairFilterContextView,
_: &Query<UserData>,
) -> bool {
fn filter_intersection_pair(&self, context: PairFilterContextView) -> bool {
PhysicsHooks::filter_intersection_pair(self, context.raw)
}

fn modify_solver_contacts(&self, context: ContactModificationContextView, _: &Query<UserData>) {
fn modify_solver_contacts(&self, context: ContactModificationContextView) {
PhysicsHooks::modify_solver_contacts(self, context.raw)
}
}

/// Resource containing the user-defined physics hooks.
#[derive(Resource)]
pub struct PhysicsHooksWithQueryResource<UserData: WorldQuery>(
pub Box<dyn PhysicsHooksWithQuery<UserData>>,
);
/// Adapts a type implementing `BevyPhysicsHooks` so that it implements `PhysicsHooks`.
pub(crate) struct BevyPhysicsHooksAdapter<'w, 's, Hooks>
where
Hooks: 'static + BevyPhysicsHooks,
for<'w1, 's1> SystemParamItem<'w1, 's1, Hooks>: BevyPhysicsHooks,
{
hooks: SystemParamItem<'w, 's, Hooks>,
}

pub(crate) struct PhysicsHooksWithQueryInstance<'world, 'state, 'b, UserData: WorldQuery> {
// pub commands: Commands<'world, 'state>,
pub user_data: Query<'world, 'state, UserData>,
pub hooks: &'b dyn PhysicsHooksWithQuery<UserData>,
impl<'w, 's, Hooks> BevyPhysicsHooksAdapter<'w, 's, Hooks>
where
Hooks: 'static + BevyPhysicsHooks,
for<'w1, 's1> SystemParamItem<'w1, 's1, Hooks>: BevyPhysicsHooks,
{
pub(crate) fn new(hooks: SystemParamItem<'w, 's, Hooks>) -> Self {
Self { hooks }
}
}

impl<UserData: WorldQuery> PhysicsHooks for PhysicsHooksWithQueryInstance<'_, '_, '_, UserData> {
impl<'w, 's, Hooks> PhysicsHooks for BevyPhysicsHooksAdapter<'w, 's, Hooks>
where
Hooks: 'static + BevyPhysicsHooks,
for<'w1, 's1> SystemParamItem<'w1, 's1, Hooks>: BevyPhysicsHooks,
{
fn filter_contact_pair(&self, context: &PairFilterContext) -> Option<SolverFlags> {
let context_view = PairFilterContextView { raw: context };
self.hooks
.filter_contact_pair(context_view, &self.user_data)
self.hooks.filter_contact_pair(context_view)
}

fn filter_intersection_pair(&self, context: &PairFilterContext) -> bool {
let context_view = PairFilterContextView { raw: context };
self.hooks
.filter_intersection_pair(context_view, &self.user_data)
self.hooks.filter_intersection_pair(context_view)
}

fn modify_solver_contacts(&self, context: &mut ContactModificationContext) {
let context_view = ContactModificationContextView { raw: context };
self.hooks
.modify_solver_contacts(context_view, &self.user_data)
self.hooks.modify_solver_contacts(context_view)
}
}
39 changes: 18 additions & 21 deletions src/plugin/plugin.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use crate::pipeline::{CollisionEvent, ContactForceEvent, PhysicsHooksWithQueryResource};
use crate::pipeline::{CollisionEvent, ContactForceEvent};
use crate::plugin::configuration::SimulationToRenderTime;
use crate::plugin::{systems, RapierConfiguration, RapierContext};
use crate::prelude::*;
use bevy::ecs::{event::Events, query::WorldQuery};
use bevy::ecs::event::Events;
use bevy::ecs::system::SystemParamItem;
use bevy::prelude::*;
use std::marker::PhantomData;

Expand All @@ -13,13 +14,17 @@ pub type NoUserData = ();
//
// This will automatically setup all the resources needed to run a physics simulation with the
// Rapier physics engine.
pub struct RapierPhysicsPlugin<PhysicsHooksData = ()> {
pub struct RapierPhysicsPlugin<PhysicsHooks = ()> {
physics_scale: f32,
default_system_setup: bool,
_phantom: PhantomData<PhysicsHooksData>,
_phantom: PhantomData<PhysicsHooks>,
}

impl<PhysicsHooksData: 'static + WorldQuery + Send + Sync> RapierPhysicsPlugin<PhysicsHooksData> {
impl<PhysicsHooks> RapierPhysicsPlugin<PhysicsHooks>
where
PhysicsHooks: 'static + BevyPhysicsHooks,
for<'w, 's> SystemParamItem<'w, 's, PhysicsHooks>: BevyPhysicsHooks,
{
/// Specifies a scale ratio between the physics world and the bevy transforms.
///
/// This affects the size of every elements in the physics engine, by multiplying
Expand Down Expand Up @@ -112,14 +117,14 @@ impl<PhysicsHooksData: 'static + WorldQuery + Send + Sync> RapierPhysicsPlugin<P
}
}
PhysicsStages::StepSimulation => SystemSet::new()
.with_system(systems::step_simulation::<PhysicsHooksData>)
.with_system(systems::step_simulation::<PhysicsHooks>)
.with_system(
Events::<CollisionEvent>::update_system
.before(systems::step_simulation::<PhysicsHooksData>),
.before(systems::step_simulation::<PhysicsHooks>),
)
.with_system(
Events::<ContactForceEvent>::update_system
.before(systems::step_simulation::<PhysicsHooksData>),
.before(systems::step_simulation::<PhysicsHooks>),
),
PhysicsStages::Writeback => SystemSet::new()
.with_system(systems::update_colliding_entities)
Expand All @@ -129,7 +134,7 @@ impl<PhysicsHooksData: 'static + WorldQuery + Send + Sync> RapierPhysicsPlugin<P
}
}

impl<PhysicsHooksData> Default for RapierPhysicsPlugin<PhysicsHooksData> {
impl<PhysicsHooksSystemParam> Default for RapierPhysicsPlugin<PhysicsHooksSystemParam> {
fn default() -> Self {
Self {
physics_scale: 1.0,
Expand Down Expand Up @@ -163,8 +168,10 @@ pub enum PhysicsStages {
DetectDespawn,
}

impl<PhysicsHooksData: 'static + WorldQuery + Send + Sync> Plugin
for RapierPhysicsPlugin<PhysicsHooksData>
impl<PhysicsHooks> Plugin for RapierPhysicsPlugin<PhysicsHooks>
where
PhysicsHooks: 'static + BevyPhysicsHooks,
for<'w, 's> SystemParamItem<'w, 's, PhysicsHooks>: BevyPhysicsHooks,
{
fn build(&self, app: &mut App) {
// Register components as reflectable.
Expand Down Expand Up @@ -232,15 +239,5 @@ impl<PhysicsHooksData: 'static + WorldQuery + Send + Sync> Plugin
.with_system_set(Self::get_systems(PhysicsStages::DetectDespawn)),
);
}

if app
.world
.get_resource::<PhysicsHooksWithQueryResource<PhysicsHooksData>>()
.is_none()
{
app.insert_resource(PhysicsHooksWithQueryResource::<PhysicsHooksData>(Box::new(
(),
)));
}
}
}
Loading