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 11 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-20240617
CARGO_PROFILE_DEV_DEBUG: none
Vrixyz marked this conversation as resolved.
Show resolved Hide resolved

jobs:
check-fmt:
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Changelog

## Unreleased

### Changed

- `RapierContext` is now a `Component`
- Rapier now supports multiple worlds.
- 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 😅.

- `ResMut<RapierConfiguration>` -> `DefaultRapierConfigurationMut`
- `Res<RapierConfiguration>` -> `DefaultRapierConfiguration`

## v0.27.0-rc.1 (18 June 2024)

**This is an update to Rapier 0.20 which includes several stability improvements
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
119 changes: 114 additions & 5 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 @@ -55,8 +55,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 @@ -91,7 +91,7 @@ impl<'a> EventHandler for EventQueue<'a> {
};

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

Expand All @@ -115,7 +115,116 @@ 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;
use systems::tests::HeadlessRenderPlugin;

use crate::{plugin::*, prelude::*};

#[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)
}

#[test]
pub fn events_received() {
return main();

use bevy::prelude::*;

#[derive(Resource, Reflect)]
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>>,
) {
for event in events.read() {
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>>();
// 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!(saved_collisions.events.len() > 0);
let saved_contact_forces = app
.world()
.get_resource::<EventsSaver<CollisionEvent>>()
.unwrap();
assert!(saved_contact_forces.events.len() > 0);
}

/// Adapted from events example
fn main() {
let mut app = App::new();
app.add_plugins((
HeadlessRenderPlugin,
TransformPlugin,
TimePlugin,
RapierPhysicsPlugin::<NoUserData>::default(),
))
.add_systems(Startup, setup_physics);
run_test(&mut app);
}

pub fn setup_physics(mut commands: Commands) {
/*
* Ground
*/
commands.spawn((
TransformBundle::from(Transform::from_xyz(0.0, -1.2, 0.0)),
cuboid(4.0, 1.0, 1.0),
));

commands.spawn((
TransformBundle::from(Transform::from_xyz(0.0, 5.0, 0.0)),
cuboid(4.0, 1.5, 1.0),
Sensor,
));

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),
));
}
}
}
Loading