diff --git a/CHANGELOG.md b/CHANGELOG.md index b553961e..e6edae44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,13 +4,16 @@ ### Changed -- `RapierContext` is now a `Component` - - Rapier now supports multiple worlds. - - Migration guide: +- `RapierContext`, `RapierConfiguration` and `RenderToSimulationTime` are now a `Component` + - Rapier now supports multiple worlds, see example `multi_world3` for usage details. + - Migration guide: - `ResMut` -> `DefaultRapierContextAccessMut` - `Res` -> `DefaultRapierContextAccess` - - `ResMut` -> `DefaultRapierConfigurationMut` - - `Res` -> `DefaultRapierConfiguration` + - Access to `RapierConfiguration` and `RenderToSimulationTime` should query for it +on the responsible entity owning the `RenderContext`. + - If you are building a library on top of `bevy_rapier` and would want to support multiple worlds too, +you can check out the details of [#545](https://github.com/dimforge/bevy_rapier/pull/545) +to get more context and information. ## v0.27.0-rc.1 (18 June 2024) diff --git a/src/plugin/configuration.rs b/src/plugin/configuration.rs index b9e0e2e6..e57d8fb8 100644 --- a/src/plugin/configuration.rs +++ b/src/plugin/configuration.rs @@ -83,17 +83,6 @@ pub struct RapierConfiguration { pub force_update_from_transform_changes: bool, } -/* -impl FromWorld for RapierConfiguration { - fn from_world(world: &mut World) -> Self { - let length_unit = world - .get_resource::() - .map(|ctxt| ctxt.integration_parameters.length_unit) - .unwrap_or(1.0); - Self::new(length_unit) - } -}*/ - impl RapierConfiguration { /// Configures rapier with the specified length unit. /// diff --git a/src/plugin/context/mod.rs b/src/plugin/context/mod.rs index 62b7b1ed..a796f4f9 100644 --- a/src/plugin/context/mod.rs +++ b/src/plugin/context/mod.rs @@ -360,6 +360,8 @@ impl RapierContext { } } if let Some(mut event_queue) = event_queue { + // SAFETY: event_queue and its inner locksare only accessed from + // within `self.pipeline.step` called above, so we can unwrap here safely. self.collision_events_to_send = std::mem::take(event_queue.collision_events.get_mut().unwrap()); self.contact_force_events_to_send = diff --git a/src/plugin/context/systemparams/rapier_context_access.rs b/src/plugin/context/systemparams/rapier_context_access.rs index 6d8b9de8..9710c617 100644 --- a/src/plugin/context/systemparams/rapier_context_access.rs +++ b/src/plugin/context/systemparams/rapier_context_access.rs @@ -93,3 +93,20 @@ impl<'w, 's> RapierContextAccessMut<'w, 's> { .into_inner() } } + +pub fn try_get_default_context( + default_context_access: &Query>, +) -> Option { + let context_entity = match default_context_access.iter().next() { + Some(it) => it, + None => { + log::error!( + "No entity with `DefaultRapierContext` found.\ + Please add a default `RapierContext` or a `RapierContextEntityLink`\ + on the new rapier-managed entity." + ); + return None; + } + }; + Some(context_entity) +} diff --git a/src/plugin/systems/collider.rs b/src/plugin/systems/collider.rs index e581adde..0a992a15 100644 --- a/src/plugin/systems/collider.rs +++ b/src/plugin/systems/collider.rs @@ -1,8 +1,10 @@ use crate::dynamics::ReadMassProperties; use crate::geometry::Collider; -use crate::plugin::context::systemparams::RapierEntity; +use crate::plugin::context::systemparams::{try_get_default_context, RapierEntity}; use crate::plugin::context::RapierContextEntityLink; -use crate::plugin::{RapierConfiguration, RapierContext, RapierContextAccessMut}; +use crate::plugin::{ + DefaultRapierContext, RapierConfiguration, RapierContext, RapierContextAccessMut, +}; use crate::prelude::{ ActiveCollisionTypes, ActiveEvents, ActiveHooks, ColliderDisabled, ColliderMassProperties, ColliderScale, CollidingEntities, CollisionEvent, CollisionGroups, ContactForceEventThreshold, @@ -314,6 +316,7 @@ pub fn init_colliders( mut commands: Commands, config: Query<&RapierConfiguration>, mut context: Query<(Entity, &mut RapierContext)>, + default_context: Query>, colliders: Query<(ColliderComponents, Option<&GlobalTransform>), Without>, mut rigid_body_mprops: Query<&mut ReadMassProperties>, parent_query: Query<&Parent>, @@ -342,7 +345,7 @@ pub fn init_colliders( let context_entity = context_link.map_or_else( || { dbg!("unknown rapierContext for object, setting to default"); - let context_entity = context.iter().next().unwrap().0; + let context_entity = try_get_default_context(&default_context).unwrap(); commands .entity(entity) .insert(RapierContextEntityLink(context_entity)); diff --git a/src/plugin/systems/joint.rs b/src/plugin/systems/joint.rs index 5538bc1e..e62be675 100644 --- a/src/plugin/systems/joint.rs +++ b/src/plugin/systems/joint.rs @@ -2,7 +2,9 @@ use crate::dynamics::ImpulseJoint; use crate::dynamics::MultibodyJoint; use crate::dynamics::RapierImpulseJointHandle; use crate::dynamics::RapierMultibodyJointHandle; +use crate::plugin::context::systemparams::try_get_default_context; use crate::plugin::context::RapierContextEntityLink; +use crate::plugin::DefaultRapierContext; use crate::plugin::RapierContext; use crate::plugin::RapierContextAccessMut; use bevy::prelude::*; @@ -11,6 +13,7 @@ use bevy::prelude::*; pub fn init_joints( mut commands: Commands, mut context: Query<(Entity, &mut RapierContext)>, + default_context: Query>, impulse_joints: Query< (Entity, Option<&RapierContextEntityLink>, &ImpulseJoint), Without, @@ -24,7 +27,7 @@ pub fn init_joints( for (entity, entity_context_link, joint) in impulse_joints.iter() { let context_entity = entity_context_link.map_or_else( || { - let context_entity = context.iter().next().unwrap().0; + let context_entity = try_get_default_context(&default_context).unwrap(); commands .entity(entity) .insert(RapierContextEntityLink(context_entity)); diff --git a/src/plugin/systems/multiple_rapier_contexts.rs b/src/plugin/systems/multiple_rapier_contexts.rs index 19d6259f..5fe84f94 100644 --- a/src/plugin/systems/multiple_rapier_contexts.rs +++ b/src/plugin/systems/multiple_rapier_contexts.rs @@ -117,3 +117,105 @@ fn bubble_down_world_change( ); }); } + +#[cfg(test)] +mod test { + use crate::plugin::systems::tests::HeadlessRenderPlugin; + use crate::plugin::{ + NoUserData, PhysicsSet, RapierContext, RapierContextEntityLink, RapierPhysicsPlugin, + }; + use crate::prelude::{ActiveEvents, Collider, ContactForceEventThreshold, RigidBody, Sensor}; + use bevy::prelude::*; + use bevy::time::{TimePlugin, TimeUpdateStrategy}; + use rapier::math::Real; + + #[test] + pub fn multi_world_hierarchy_update() { + let mut app = App::new(); + app.add_plugins(( + HeadlessRenderPlugin, + TransformPlugin, + TimePlugin, + RapierPhysicsPlugin::::default(), + )) + .add_systems( + PostUpdate, + setup_physics + .run_if(run_once()) + .before(PhysicsSet::SyncBackend), + ); + // Simulates 60 updates per seconds + app.insert_resource(TimeUpdateStrategy::ManualDuration( + std::time::Duration::from_secs_f32(1f32 / 60f32), + )); + app.update(); + // Verify all rapier entities have a `RapierContextEntityLink`. + let mut world = app.world_mut(); + let mut query = world.query_filtered::>>(); + for entity in query.iter(&world) { + world + .get::(entity) + .expect(&format!("no link to rapier context entity from {entity}.")); + } + // Verify link is correctly updated for children. + let new_rapier_context = world.spawn(RapierContext::default()).id(); + // FIXME: We need to wait 1 frame when creating a world. + // Ideally we should be able to order the systems so that we don't have to wait. + app.update(); + let mut world = app.world_mut(); + let mut query = world.query_filtered::<&mut RapierContextEntityLink, With>>(); + let mut link_parent = query.get_single_mut(&mut world).unwrap(); + link_parent.0 = new_rapier_context; + app.update(); + let mut world = app.world_mut(); + let mut query = world.query_filtered::<&RapierContextEntityLink, With>>(); + let link_child = query.get_single_mut(&mut world).unwrap(); + assert_eq!(link_child.0, new_rapier_context); + return; + + #[derive(Component)] + pub struct Marker; + + #[cfg(feature = "dim3")] + fn cuboid(hx: Real, hy: Real, hz: Real) -> Collider { + Collider::cuboid(hx, hy, hz) + } + #[cfg(feature = "dim2")] + fn cuboid(hx: Real, hy: Real, _hz: Real) -> Collider { + Collider::cuboid(hx, hy) + } + pub fn setup_physics(mut commands: Commands) { + commands.spawn(( + TransformBundle::from(Transform::from_xyz(0.0, -1.2, 0.0)), + cuboid(4.0, 1.0, 1.0), + Marker::<'R'>, + )); + + commands.spawn(( + TransformBundle::from(Transform::from_xyz(0.0, 5.0, 0.0)), + cuboid(4.0, 1.5, 1.0), + Sensor, + Marker::<'R'>, + )); + + commands + .spawn(( + TransformBundle::from(Transform::from_xyz(0.0, 13.0, 0.0)), + RigidBody::Dynamic, + cuboid(0.5, 0.5, 0.5), + ActiveEvents::COLLISION_EVENTS, + ContactForceEventThreshold(30.0), + Marker::<'P'>, + Marker::<'R'>, + )) + .with_children(|child_builder| { + child_builder.spawn(( + TransformBundle::from(Transform::from_xyz(0.0, -1.2, 0.0)), + cuboid(4.0, 1.0, 1.0), + Marker::<'C'>, + Marker::<'R'>, + )); + }); + } + } +} diff --git a/src/plugin/systems/rigid_body.rs b/src/plugin/systems/rigid_body.rs index bf28c156..c8662824 100644 --- a/src/plugin/systems/rigid_body.rs +++ b/src/plugin/systems/rigid_body.rs @@ -1,4 +1,5 @@ use crate::dynamics::RapierRigidBodyHandle; +use crate::plugin::context::systemparams::try_get_default_context; use crate::plugin::context::RapierContextEntityLink; use crate::plugin::{configuration::TimestepMode, RapierConfiguration, RapierContext}; use crate::{dynamics::RigidBody, plugin::configuration::SimulationToRenderTime}; @@ -603,17 +604,7 @@ pub fn init_rigid_bodies( // Get rapier context from RapierContextEntityLink or insert its default value. let context_entity = entity_context_link.map_or_else( || { - let context_entity = match default_context_access.iter().next() { - Some(it) => it, - None => { - log::error!( - "No entity with `DefaultRapierContext` found.\ - Please add a default `RapierContext` or a `RapierContextEntityLink`\ - on the new rapier-managed entity." - ); - return None; - } - }; + let context_entity = try_get_default_context(&default_context_access)?; commands .entity(entity) .insert(RapierContextEntityLink(context_entity)); @@ -640,6 +631,7 @@ pub fn init_rigid_bodies( } } } + /// This applies the initial impulse given to a rigid-body when it is created. /// /// This cannot be done inside `init_rigid_bodies` because impulses require the rigid-body diff --git a/src/plugin/systems/writeback.rs b/src/plugin/systems/writeback.rs index f40a1e53..a14c5b66 100644 --- a/src/plugin/systems/writeback.rs +++ b/src/plugin/systems/writeback.rs @@ -18,10 +18,6 @@ pub fn writeback_mass_properties( for entity in mass_modified.read() { let link = link.get(entity.0).unwrap(); let config = config.get(link.0).unwrap(); - // FIXME: I think this should still run even if the physics pipeline is not enabled: - // - if we re-enable the pipeline, we'll have missed that event - // - it's not a heavy computation - // - it shouldn't happen too often anyway (only at initialization or when a user add/removes child colliders / changes their mass) if config.physics_pipeline_active { let context = context.get(link.0).unwrap();