Reputation: 6979
So, I'm using Bevy (0.11) the Rapier (0.22) physics engine to have a bunch of blocks falling on each other, here is how I spawn the blocks:
commands.spawn((
Collider::cuboid(dim, dim),
MaterialMesh2dBundle {
mesh: meshes. Add(shape::Box::new(dim2, dim2, dim2).into()).into(),
material: materials.add(ColorMaterial::from(random_colour())),
transform: Transform::from_xyz(
left + (dim2 * i as f32) + 1.0,
half_height - dim,
0.0,
),
..default()
},
RigidBody::Dynamic,
Restitution::coefficient(0.5),
ColliderMassProperties::Mass(1000.0), // Increase the mass to stop the glitching (a bit)
GravityScale(10.0),
));
I want to be able to interact with them via mouse click/touch. I'm trying to use the Point projection to see if the mouse is over something like so
fn mouse_button_events(
mut mousebtn_evr: EventReader<MouseButtonInput>,
q_windows: Query<&Window, With<PrimaryWindow>>,
rapier_context: Res<RapierContext>,
) {
use bevy::input::ButtonState;
for ev in mousebtn_evr.iter() {
match ev.state {
ButtonState::Pressed => {
println!("Mouse button press: {:?}", ev.button);
let point = q_windows.single().cursor_position().unwrap();
let filter = QueryFilter::default();
rapier_context.intersections_with_point(point, filter, |entity| {
// Callback called on each collider with a shape containing the point.
println!("The entity {:?} contains the point.", entity);
// Return `false` instead if we want to stop searching for other colliders containing this point.
true
});
}
ButtonState::Released => {
println!("Mouse button release: {:?}", ev.button);
}
}
}
}
But it's not printing the entity id when something is clicked. Are the screen coordinates the same as the Rapier space coordinates? What am I don't wrong here?
Upvotes: 1
Views: 1481
Reputation: 11
Hey a forum question I can actually answer! In case anyone is looking for a good way to do this, here's a function I wrote that returns an entity and a hit location under the cursor, in 3d, from the perspective of a moveable camera:
fn get_hit_under_cursor (
camera_query: &Query<(&Camera, &GlobalTransform), With<GameCamera>>,
window_query: &Query<&Window, With<PrimaryWindow>>,
rapier_context: Res<RapierContext>,
) -> Option<(Entity, Vec3)> {
let window = window_query.single();
let Some(cursor_position) = window.cursor_position() else {
return None;
};
let (camera, camera_global_transform) = camera_query.single();
let Some(ray) = camera.viewport_to_world(camera_global_transform, cursor_position) else {
return None;
};
if let Some((entity, toi)) = rapier_context.cast_ray(
ray.origin, *ray.direction, MAX_TOI, true, QueryFilter::default(),
) {
let hit_point = ray.origin + *ray.direction * toi;
return Some((entity, hit_point));
};
return None;
}
I call this function inside of some of my systems in response to mouse input. It doesn't require you to convert any coordinates yourself, and it works in Bevy 0.13 with Rapier3d 0.26.
You do have to pass it the queries by reference. I'm not sure if that's best practice or not, I've only been working with Bevy for a couple weeks.
Upvotes: 1
Reputation: 472
You need to convert the cursor position into a world position through the camera. The code you posted in the answer will only work if you don't move the camera from the initial position. A more general solution would be:
let (camera, camera_transform) = camera_q.single();
let Some(mouse_position) = windows.single().cursor_position()
.and_then(|cursor| camera.viewport_to_world_2d(camera_transform, cursor))
else {
return
};
with
camera_q: Query<(&Camera, &GlobalTransform)>,
Upvotes: 1
Reputation: 6979
Seems I was spot on about the different coordinate systems. Mouse events are in screen coordinates which have their origin in the top left while Rapier has it's origin in the center of the screen. So I just needed a conversion function...
fn screen_to_rapier_coords(point: Vec2, screen: Vec2) -> Vec2 {
// Conver between screen coordinates with origin at the top left
// to rapier coordinates with origin at the center
let x = point.x - screen.x / 2.0;
let y = screen.y / 2.0 - point.y;
Vec2::new(x, y)
}
Upvotes: 0