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

Rapier context as a Component #545

Merged
merged 36 commits into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
d4ff57e
working rapierContext as component, but not ideal API.
Vrixyz Jun 25, 2024
3cb7676
systemparam exploration
Vrixyz Jun 25, 2024
a01395e
RapierContextEntityLink to know which RapierContext is referred to
Vrixyz Jun 25, 2024
4e7c36c
more rapierContext conf + broken events -> added a test which should …
Vrixyz Jun 25, 2024
4952ea5
fix events test
Vrixyz Jun 26, 2024
7a4ba5e
better support for multi world
Vrixyz Jun 26, 2024
24fd036
multiple worlds working + example
Vrixyz Jun 26, 2024
f900dac
progress multi world with change
Vrixyz Jun 27, 2024
d171fe4
cargo clippy + pr feedbacks
Vrixyz Jul 1, 2024
8dd49ee
less unwraps
Vrixyz Jul 2, 2024
2b262ea
ci without debug symbols
Vrixyz Jul 2, 2024
f849cd2
more tests + logs + better changelog
Vrixyz Jul 2, 2024
df3578e
more error logs in case of no rapier context
Vrixyz Jul 2, 2024
ac16f38
Merge remote-tracking branch 'upstream' into RapierContext_Component
Vrixyz Jul 4, 2024
941b52e
fix conflict
Vrixyz Jul 4, 2024
5669a8c
fix unwrap
Vrixyz Jul 4, 2024
0b9ca86
fix warning
Vrixyz Jul 4, 2024
f4643ef
address pr feedback
Vrixyz Jul 5, 2024
f8b296a
address pr feedback
Vrixyz Jul 8, 2024
0e46610
Merge branch 'master' into RapierContext_Component
Vrixyz Jul 8, 2024
75c2195
fixed compilation
Vrixyz Jul 8, 2024
ccd2be7
fix doc comment
Vrixyz Jul 8, 2024
9142888
fixed joints3 example
Vrixyz Jul 8, 2024
ba0cd76
experiment on stepping to test system ordering and consistent ecs state
Vrixyz Jul 9, 2024
6d10fe6
fix compilation + warning
Vrixyz Jul 10, 2024
d0b07a0
better error handling when rapier context is missing
Vrixyz Jul 10, 2024
2676c35
add a name for rapier context entity
Vrixyz Jul 10, 2024
40be1f1
Merge branch 'master' into RapierContext_Component
Vrixyz Jul 23, 2024
deb0a06
rename an init function
Vrixyz Jul 23, 2024
cf165bd
apply pr suggestion
Vrixyz Jul 23, 2024
aeab8c9
apply PR suggestions
Vrixyz Aug 12, 2024
07eefbb
Merge branch 'master' into RapierContext_Component
Vrixyz Aug 12, 2024
4cccde1
rename rapier context system params
Vrixyz Aug 12, 2024
cbfe138
fix clippy (+ random typo)
Vrixyz Aug 12, 2024
5dc5b2d
cargo fmt
Vrixyz Aug 12, 2024
d0961d0
Merge remote-tracking branch 'upstream' into RapierContext_Component
Vrixyz Sep 9, 2024
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
1 change: 1 addition & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ on:
env:
CARGO_TERM_COLOR: always
RUST_CACHE_KEY: rust-cache-20240701
CARGO_PROFILE_DEV_DEBUG: none
Vrixyz marked this conversation as resolved.
Show resolved Hide resolved

jobs:
check-fmt:
Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ and new features. Please have a look at the

- Update to rapier `0.21`.
- Update to nalgebra `0.33`.
- `RapierContext`, `RapierConfiguration` and `RenderToSimulationTime` are now a `Component`
- Rapier now supports multiple worlds, see example `multi_world3` for usage details.
Vrixyz marked this conversation as resolved.
Show resolved Hide resolved
- Migration guide:
- `ResMut<mut RapierContext>` -> `DefaultRapierContextAccessMut`
- `Res<RapierContext>` -> `DefaultRapierContextAccess`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just a suggestion. Bevy has these Read<> and Write<> structs. Perhaps, to make the API look more idiomatic the access structs could be named as follow?

Suggested change
- `ResMut<mut RapierContext>` -> `DefaultRapierContextAccessMut`
- `Res<RapierContext>` -> `DefaultRapierContextAccess`
- `ResMut<mut RapierContext>` -> `WriteDefaultRapierContext`
- `Res<RapierContext>` -> `ReadDefaultRapierContext`

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TL;DR: ok :)

I tend to prefer suffixes to prefix, so when you're searching for a term, the alphabetical order comes better grouped.

That said, my initial naming does a bad job at putting Default first. For this reason, I might prefer a RapierContextDefaultRead ; but that would probably mean renaming DefaultRapierContext to RapierContextDefault, which might sound weird.

For the idiomatic way, I wanted to keep close to Res / ResMut, which is very idiomatic. I believe Read/Write is used for individual components, which is not exaclty the case here. I'm not sure I've seen it in SystemParam. But I'm not sure.

In any way, I don't feel too opinionated about that, so I'll just implement your suggestion 😅.

- 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,
Vrixyz marked this conversation as resolved.
Show resolved Hide resolved
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)

Expand Down
3 changes: 2 additions & 1 deletion bevy_rapier2d/examples/player_movement2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ fn main() {
#[derive(Component)]
pub struct Player(f32);

pub fn spawn_player(mut commands: Commands, mut rapier_config: ResMut<RapierConfiguration>) {
pub fn spawn_player(mut commands: Commands, mut rapier_config: Query<&mut RapierConfiguration>) {
let mut rapier_config = rapier_config.single_mut();
// Set gravity to 0.0 and spawn camera.
rapier_config.gravity = Vec2::ZERO;
commands.spawn(Camera2dBundle::default());
Expand Down
4 changes: 3 additions & 1 deletion bevy_rapier2d/examples/testbed2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,9 @@ fn main() {
OnExit(Examples::PlayerMovement2),
(
cleanup,
|mut rapier_config: ResMut<RapierConfiguration>, ctxt: Res<RapierContext>| {
|mut rapier_config: Query<&mut RapierConfiguration>,
ctxt: DefaultRapierContextAccess| {
let mut rapier_config = rapier_config.single_mut();
rapier_config.gravity =
RapierConfiguration::new(ctxt.integration_parameters.length_unit).gravity;
},
Expand Down
120 changes: 120 additions & 0 deletions bevy_rapier3d/examples/multi_world3.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
use bevy::{input::common_conditions::input_just_pressed, prelude::*};
use bevy_rapier3d::prelude::*;

const N_WORLDS: usize = 2;

fn main() {
App::new()
.insert_resource(ClearColor(Color::srgb(
0xF9 as f32 / 255.0,
0xF9 as f32 / 255.0,
0xFF as f32 / 255.0,
)))
.add_plugins((
DefaultPlugins,
RapierPhysicsPlugin::<NoUserData>::default()
.with_default_world(RapierContextInitialization::NoAutomaticRapierContext),
Vrixyz marked this conversation as resolved.
Show resolved Hide resolved
RapierDebugRenderPlugin::default(),
))
.add_systems(
Startup,
((create_worlds, setup_physics).chain(), setup_graphics),
)
.add_systems(Update, move_platforms)
.add_systems(
Update,
change_world.run_if(input_just_pressed(KeyCode::KeyC)),
)
.run();
}

fn create_worlds(mut commands: Commands) {
for i in 0..N_WORLDS {
let mut world = commands.spawn((RapierContext::default(), WorldId(i)));
Vrixyz marked this conversation as resolved.
Show resolved Hide resolved
if i == 0 {
world.insert(DefaultRapierContext);
}
}
}

fn setup_graphics(mut commands: Commands) {
commands.spawn(Camera3dBundle {
transform: Transform::from_xyz(0.0, 3.0, -10.0)
.looking_at(Vec3::new(0.0, 0.0, 0.0), Vec3::Y),
..Default::default()
});
}

#[derive(Component)]
pub struct WorldId(pub usize);

#[derive(Component)]
struct Platform {
starting_y: f32,
}

fn move_platforms(time: Res<Time>, mut query: Query<(&mut Transform, &Platform)>) {
for (mut transform, platform) in query.iter_mut() {
transform.translation.y = platform.starting_y + -time.elapsed_seconds().sin();
}
}

/// Demonstrates how easy it is to move one entity to another world.
fn change_world(
query_context: Query<Entity, With<DefaultRapierContext>>,
mut query_links: Query<(Entity, &mut RapierContextEntityLink)>,
) {
let default_context = query_context.single();
for (e, mut link) in query_links.iter_mut() {
if link.0 == default_context {
continue;
}
link.0 = default_context;
println!("changing world of {} for world {}", e, link.0);
}
Vrixyz marked this conversation as resolved.
Show resolved Hide resolved
}

pub fn setup_physics(
context: Query<(Entity, &WorldId), With<RapierContext>>,
mut commands: Commands,
) {
for (context_entity, id) in context.iter() {
let id = id.0;

let color = [
Hsla::hsl(220.0, 1.0, 0.3),
Hsla::hsl(180.0, 1.0, 0.3),
Hsla::hsl(260.0, 1.0, 0.7),
][id % 3];

/*
* Ground
*/
let ground_size = 5.1;
let ground_height = 0.1;

let starting_y = (id as f32) * -0.5 - ground_height;

let mut platforms = commands.spawn((
TransformBundle::from(Transform::from_xyz(0.0, starting_y, 0.0)),
Collider::cuboid(ground_size, ground_height, ground_size),
ColliderDebugColor(color),
RapierContextEntityLink(context_entity),
));
if id == 1 {
platforms.insert(Platform { starting_y });
}

/*
* Create the cube
*/

commands.spawn((
TransformBundle::from(Transform::from_xyz(0.0, 1.0 + id as f32 * 5.0, 0.0)),
RigidBody::Dynamic,
Collider::cuboid(0.5, 0.5, 0.5),
ColliderDebugColor(color),
RapierContextEntityLink(context_entity),
));
}
}
2 changes: 1 addition & 1 deletion bevy_rapier3d/examples/ray_casting3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ pub fn setup_physics(mut commands: Commands) {
pub fn cast_ray(
mut commands: Commands,
windows: Query<&Window, With<PrimaryWindow>>,
rapier_context: Res<RapierContext>,
rapier_context: DefaultRapierContextAccessMut,
Vrixyz marked this conversation as resolved.
Show resolved Hide resolved
cameras: Query<(&Camera, &GlobalTransform)>,
) {
let window = windows.single();
Expand Down
41 changes: 20 additions & 21 deletions src/pipeline/events.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::math::{Real, Vect};
use bevy::prelude::{Entity, Event, EventWriter};
use bevy::prelude::{Entity, Event};
use rapier::dynamics::RigidBodySet;
use rapier::geometry::{
ColliderHandle, ColliderSet, CollisionEvent as RapierCollisionEvent, CollisionEventFlags,
Expand Down Expand Up @@ -58,8 +58,8 @@ pub(crate) struct EventQueue<'a> {
// Used ot retrieve the entity of colliders that have been removed from the simulation
// since the last physics step.
pub deleted_colliders: &'a HashMap<ColliderHandle, Entity>,
pub collision_events: RwLock<EventWriter<'a, CollisionEvent>>,
pub contact_force_events: RwLock<EventWriter<'a, ContactForceEvent>>,
pub collision_events: RwLock<Vec<CollisionEvent>>,
pub contact_force_events: RwLock<Vec<ContactForceEvent>>,
}

impl<'a> EventQueue<'a> {
Expand Down Expand Up @@ -94,7 +94,7 @@ impl<'a> EventHandler for EventQueue<'a> {
};

if let Ok(mut events) = self.collision_events.write() {
events.send(event);
events.push(event);
}
}

Expand All @@ -118,14 +118,14 @@ impl<'a> EventHandler for EventQueue<'a> {
};

if let Ok(mut events) = self.contact_force_events.write() {
events.send(event);
events.push(event);
}
}
}

#[cfg(test)]
mod test {
use bevy::time::{TimePlugin, TimeUpdateStrategy};
use bevy::time::TimePlugin;
use systems::tests::HeadlessRenderPlugin;

use crate::{plugin::*, prelude::*};
Expand All @@ -149,15 +149,13 @@ mod test {
pub struct EventsSaver<E: Event> {
pub events: Vec<E>,
}

impl<E: Event> Default for EventsSaver<E> {
fn default() -> Self {
Self {
events: Default::default(),
}
}
}

pub fn save_events<E: Event + Clone>(
mut events: EventReader<E>,
mut saver: ResMut<EventsSaver<E>>,
Expand All @@ -166,32 +164,33 @@ mod test {
saver.events.push(event.clone());
}
}

fn run_test(app: &mut App) {
app.add_systems(PostUpdate, save_events::<CollisionEvent>)
.add_systems(PostUpdate, save_events::<ContactForceEvent>)
.init_resource::<EventsSaver<CollisionEvent>>()
.init_resource::<EventsSaver<ContactForceEvent>>();

// Simulates 60 updates per seconds
app.insert_resource(TimeUpdateStrategy::ManualDuration(
std::time::Duration::from_secs_f32(1f32 / 60f32),
));
// 2 seconds should be plenty of time for the cube to fall on the
// lowest collider.
for _ in 0..120 {
// while app.plugins_state() == bevy::app::PluginsState::Adding {
// #[cfg(not(target_arch = "wasm32"))]
// bevy::tasks::tick_global_task_pools_on_main_thread();
// }
// app.finish();
// app.cleanup();
Vrixyz marked this conversation as resolved.
Show resolved Hide resolved
let mut time = app.world_mut().get_resource_mut::<Time<Virtual>>().unwrap();
time.set_relative_speed(1000f32);
for _ in 0..300 {
// FIXME: advance by set durations to avoid being at the mercy of the CPU.
app.update();
}
let saved_collisions = app
.world()
.get_resource::<EventsSaver<CollisionEvent>>()
.unwrap();
assert_eq!(saved_collisions.events.len(), 3);
assert!(saved_collisions.events.len() > 0);
let saved_contact_forces = app
.world()
.get_resource::<EventsSaver<ContactForceEvent>>()
.get_resource::<EventsSaver<CollisionEvent>>()
.unwrap();
assert_eq!(saved_contact_forces.events.len(), 1);
assert!(saved_contact_forces.events.len() > 0);
}

/// Adapted from events example
Expand Down Expand Up @@ -226,7 +225,7 @@ mod test {
TransformBundle::from(Transform::from_xyz(0.0, 13.0, 0.0)),
RigidBody::Dynamic,
cuboid(0.5, 0.5, 0.5),
ActiveEvents::COLLISION_EVENTS | ActiveEvents::CONTACT_FORCE_EVENTS,
ActiveEvents::COLLISION_EVENTS,
ContactForceEventThreshold(30.0),
));
}
Expand Down
53 changes: 24 additions & 29 deletions src/plugin/configuration.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
use bevy::prelude::{FromWorld, Resource, World};
use bevy::{
prelude::{Component, Resource},
reflect::Reflect,
};

use crate::math::{Real, Vect};
use crate::plugin::RapierContext;

/// Difference between simulation and rendering time
#[derive(Resource, Default)]
pub struct SimulationToRenderTime {
/// Difference between simulation and rendering time
pub diff: f32,
}

/// The different ways of adjusting the timestep length.
#[derive(Copy, Clone, Debug, PartialEq)]
/// The different ways of adjusting the timestep length each frame.
#[derive(Copy, Clone, Debug, PartialEq, Resource)]
pub enum TimestepMode {
/// Use a fixed timestep: the physics simulation will be advanced by the fixed value
/// `dt` seconds at each Bevy tick by performing `substeps` of length `dt / substeps`.
Expand Down Expand Up @@ -50,7 +45,24 @@ pub enum TimestepMode {
},
}

#[derive(Resource, Copy, Clone, Debug)]
/// Difference between simulation and rendering time
#[derive(Component, Default, Reflect)]
pub struct SimulationToRenderTime {
/// Difference between simulation and rendering time
pub diff: f32,
}

impl Default for TimestepMode {
fn default() -> Self {
TimestepMode::Variable {
max_dt: 1.0 / 60.0,
time_scale: 1.0,
substeps: 1,
}
}
}

#[derive(Component, Copy, Clone, Debug, Reflect)]
/// A resource for specifying configuration information for the physics simulation
pub struct RapierConfiguration {
/// Specifying the gravity of the physics simulation.
Expand All @@ -59,8 +71,6 @@ pub struct RapierConfiguration {
pub physics_pipeline_active: bool,
/// Specifies if the query pipeline is active and update the query pipeline.
pub query_pipeline_active: bool,
/// Specifies the way the timestep length should be adjusted at each frame.
pub timestep_mode: TimestepMode,
/// Specifies the number of subdivisions along each axes a shape should be subdivided
/// if its scaled representation cannot be represented with the same shape type.
///
Expand All @@ -73,16 +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::<RapierContext>()
.map(|ctxt| ctxt.integration_parameters.length_unit)
.unwrap_or(1.0);
Self::new(length_unit)
}
}

impl RapierConfiguration {
/// Configures rapier with the specified length unit.
///
Expand All @@ -95,11 +95,6 @@ impl RapierConfiguration {
gravity: Vect::Y * -9.81 * length_unit,
physics_pipeline_active: true,
query_pipeline_active: true,
timestep_mode: TimestepMode::Variable {
max_dt: 1.0 / 60.0,
time_scale: 1.0,
substeps: 1,
},
scaled_shape_subdivision: 10,
force_update_from_transform_changes: false,
}
Expand Down
Loading