Add mesh picking backend and `MeshRayCast` system parameter (#15800)
# Objective
Closes #15545.
`bevy_picking` supports UI and sprite picking, but not mesh picking.
Being able to pick meshes would be extremely useful for various games,
tools, and our own examples, as well as scene editors and inspectors.
So, we need a mesh picking backend!
Luckily,
[`bevy_mod_picking`](https://github.com/aevyrie/bevy_mod_picking) (which
`bevy_picking` is based on) by @aevyrie already has a [backend for
it](https://github.com/aevyrie/bevy_mod_picking/blob/74f0c3c0fbc8048632ba46fd8f14e26aaea9c76c/backends/bevy_picking_raycast/src/lib.rs)
using [`bevy_mod_raycast`](https://github.com/aevyrie/bevy_mod_raycast).
As a side product of adding mesh picking, we also get support for
performing ray casts on meshes!
## Solution
Upstream a large chunk of the immediate-mode ray casting functionality
from `bevy_mod_raycast`, and add a mesh picking backend based on
`bevy_mod_picking`. Huge thanks to @aevyrie who did all the hard work on
these incredible crates!
All meshes are pickable by default. Picking can be disabled for
individual entities by adding `PickingBehavior::IGNORE`, like normal.
Or, if you want mesh picking to be entirely opt-in, you can set
`MeshPickingBackendSettings::require_markers` to `true` and add a
`RayCastPickable` component to the desired camera and target entities.
You can also use the new `MeshRayCast` system parameter to cast rays
into the world manually:
```rust
fn ray_cast_system(mut ray_cast: MeshRayCast, foo_query: Query<(), With<Foo>>) {
let ray = Ray3d::new(Vec3::ZERO, Dir3::X);
// Only ray cast against entities with the `Foo` component.
let filter = |entity| foo_query.contains(entity);
// Never early-exit. Note that you can change behavior per-entity.
let early_exit_test = |_entity| false;
// Ignore the visibility of entities. This allows ray casting hidden entities.
let visibility = RayCastVisibility::Any;
let settings = RayCastSettings::default()
.with_filter(&filter)
.with_early_exit_test(&early_exit_test)
.with_visibility(visibility);
// Cast the ray with the settings, returning a list of intersections.
let hits = ray_cast.cast_ray(ray, &settings);
}
```
This is largely a direct port, but I did make several changes to match
our APIs better, remove things we don't need or that I think are
unnecessary, and do some general improvements to code quality and
documentation.
### Changes Relative to `bevy_mod_raycast` and `bevy_mod_picking`
- Every `Raycast` and "raycast" has been renamed to `RayCast` and "ray
cast" (similar reasoning as the "Naming" section in #15724)
- `Raycast` system param has been renamed to `MeshRayCast` to avoid
naming conflicts and to be explicit that it is not for colliders
- `RaycastBackend` has been renamed to `MeshPickingBackend`
- `RayCastVisibility` variants are now `Any`, `Visible`, and
`VisibleInView` instead of `Ignore`, `MustBeVisible`, and
`MustBeVisibleAndInView`
- `NoBackfaceCulling` has been renamed to `RayCastBackfaces`, to avoid
implying that it affects the rendering of backfaces for meshes (it
doesn't)
- `SimplifiedMesh` and `RayCastBackfaces` live near other ray casting
API types, not in their own 10 LoC module
- All intersection logic and types are in the same `intersections`
module, not split across several modules
- Some intersection types have been renamed to be clearer and more
consistent
- `IntersectionData` -> `RayMeshHit`
- `RayHit` -> `RayTriangleHit`
- General documentation and code quality improvements
### Removed / Not Ported
- Removed unused ray helpers and types, like `PrimitiveIntersection`
- Removed getters on intersection types, and made their properties
public
- There is no `2d` feature, and `Raycast::mesh_query` and
`Raycast::mesh2d_query` have been merged into `MeshRayCast::mesh_query`,
which handles both 2D and 3D
- I assume this existed previously because `Mesh2dHandle` used to be in
`bevy_sprite`. Now both the 2D and 3D mesh are in `bevy_render`.
- There is no `debug` feature or ray debug rendering
- There is no deferred API (`RaycastSource`)
- There is no `CursorRayPlugin` (the picking backend handles this)
### Note for Reviewers
In case it's helpful, the [first
commit](https://github.com/bevyengine/bevy/commit/281638ef10ebd25f783bee2533509fdf756f7ab4)
here is essentially a one-to-one port. The rest of the commits are
primarily refactoring and cleaning things up in the ways listed earlier,
as well as changes to the module structure.
It may also be useful to compare the original [picking
backend](https://github.com/aevyrie/bevy_mod_picking/blob/74f0c3c0fbc8048632ba46fd8f14e26aaea9c76c/backends/bevy_picking_raycast/src/lib.rs)
and [`bevy_mod_raycast`](https://github.com/aevyrie/bevy_mod_raycast) to
this PR. Feel free to mention if there are any changes that I should
revert or something I should not include in this PR.
## Testing
I tested mesh picking and relevant components in some examples, for both
2D and 3D meshes, and added a new `mesh_picking` example. I also
~~stole~~ ported over the [ray-mesh intersection
benchmark](https://github.com/aevyrie/bevy_mod_raycast/blob/dbc5ef32fe48997a1a7eeec7434d9dd8b829e52e/benches/ray_mesh_intersection.rs)
from `bevy_mod_raycast`.
---
## Showcase
Below is a version of the `2d_shapes` example modified to demonstrate 2D
mesh picking. This is not included in this PR.
https://github.com/user-attachments/assets/7742528c-8630-4c00-bacd-81576ac432bf
And below is the new `mesh_picking` example:
https://github.com/user-attachments/assets/b65c7a5a-fa3a-4c2d-8bbd-e7a2c772986e
There is also a really cool new `mesh_ray_cast` example ported over from
`bevy_mod_raycast`:
https://github.com/user-attachments/assets/3c5eb6c0-bd94-4fb0-bec6-8a85668a06c9
---------
Co-authored-by: Aevyrie <aevyrie@gmail.com>
Co-authored-by: Trent <2771466+tbillington@users.noreply.github.com>
Co-authored-by: François Mockers <mockersf@gmail.com>
2024-10-13 17:24:19 +00:00
|
|
|
//! Demonstrates how to use the [`MeshRayCast`] system parameter to chain multiple ray casts
|
|
|
|
//! and bounce off of surfaces.
|
|
|
|
|
|
|
|
use std::f32::consts::{FRAC_PI_2, PI};
|
|
|
|
|
|
|
|
use bevy::{
|
|
|
|
color::palettes::css,
|
|
|
|
core_pipeline::{bloom::Bloom, tonemapping::Tonemapping},
|
|
|
|
math::vec3,
|
|
|
|
picking::backend::ray::RayMap,
|
|
|
|
prelude::*,
|
|
|
|
};
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
App::new()
|
|
|
|
.add_plugins(DefaultPlugins)
|
|
|
|
.add_systems(Startup, setup)
|
|
|
|
.add_systems(Update, bouncing_raycast)
|
|
|
|
.insert_resource(ClearColor(Color::BLACK))
|
|
|
|
.run();
|
|
|
|
}
|
|
|
|
|
|
|
|
const MAX_BOUNCES: usize = 64;
|
|
|
|
const LASER_SPEED: f32 = 0.03;
|
|
|
|
|
|
|
|
fn bouncing_raycast(
|
|
|
|
mut ray_cast: MeshRayCast,
|
|
|
|
mut gizmos: Gizmos,
|
|
|
|
time: Res<Time>,
|
|
|
|
// The ray map stores rays cast by the cursor
|
|
|
|
ray_map: Res<RayMap>,
|
|
|
|
) {
|
|
|
|
// Cast an automatically moving ray and bounce it off of surfaces
|
2024-10-16 21:09:32 +00:00
|
|
|
let t = ops::cos((time.elapsed_secs() - 4.0).max(0.0) * LASER_SPEED) * PI;
|
Add mesh picking backend and `MeshRayCast` system parameter (#15800)
# Objective
Closes #15545.
`bevy_picking` supports UI and sprite picking, but not mesh picking.
Being able to pick meshes would be extremely useful for various games,
tools, and our own examples, as well as scene editors and inspectors.
So, we need a mesh picking backend!
Luckily,
[`bevy_mod_picking`](https://github.com/aevyrie/bevy_mod_picking) (which
`bevy_picking` is based on) by @aevyrie already has a [backend for
it](https://github.com/aevyrie/bevy_mod_picking/blob/74f0c3c0fbc8048632ba46fd8f14e26aaea9c76c/backends/bevy_picking_raycast/src/lib.rs)
using [`bevy_mod_raycast`](https://github.com/aevyrie/bevy_mod_raycast).
As a side product of adding mesh picking, we also get support for
performing ray casts on meshes!
## Solution
Upstream a large chunk of the immediate-mode ray casting functionality
from `bevy_mod_raycast`, and add a mesh picking backend based on
`bevy_mod_picking`. Huge thanks to @aevyrie who did all the hard work on
these incredible crates!
All meshes are pickable by default. Picking can be disabled for
individual entities by adding `PickingBehavior::IGNORE`, like normal.
Or, if you want mesh picking to be entirely opt-in, you can set
`MeshPickingBackendSettings::require_markers` to `true` and add a
`RayCastPickable` component to the desired camera and target entities.
You can also use the new `MeshRayCast` system parameter to cast rays
into the world manually:
```rust
fn ray_cast_system(mut ray_cast: MeshRayCast, foo_query: Query<(), With<Foo>>) {
let ray = Ray3d::new(Vec3::ZERO, Dir3::X);
// Only ray cast against entities with the `Foo` component.
let filter = |entity| foo_query.contains(entity);
// Never early-exit. Note that you can change behavior per-entity.
let early_exit_test = |_entity| false;
// Ignore the visibility of entities. This allows ray casting hidden entities.
let visibility = RayCastVisibility::Any;
let settings = RayCastSettings::default()
.with_filter(&filter)
.with_early_exit_test(&early_exit_test)
.with_visibility(visibility);
// Cast the ray with the settings, returning a list of intersections.
let hits = ray_cast.cast_ray(ray, &settings);
}
```
This is largely a direct port, but I did make several changes to match
our APIs better, remove things we don't need or that I think are
unnecessary, and do some general improvements to code quality and
documentation.
### Changes Relative to `bevy_mod_raycast` and `bevy_mod_picking`
- Every `Raycast` and "raycast" has been renamed to `RayCast` and "ray
cast" (similar reasoning as the "Naming" section in #15724)
- `Raycast` system param has been renamed to `MeshRayCast` to avoid
naming conflicts and to be explicit that it is not for colliders
- `RaycastBackend` has been renamed to `MeshPickingBackend`
- `RayCastVisibility` variants are now `Any`, `Visible`, and
`VisibleInView` instead of `Ignore`, `MustBeVisible`, and
`MustBeVisibleAndInView`
- `NoBackfaceCulling` has been renamed to `RayCastBackfaces`, to avoid
implying that it affects the rendering of backfaces for meshes (it
doesn't)
- `SimplifiedMesh` and `RayCastBackfaces` live near other ray casting
API types, not in their own 10 LoC module
- All intersection logic and types are in the same `intersections`
module, not split across several modules
- Some intersection types have been renamed to be clearer and more
consistent
- `IntersectionData` -> `RayMeshHit`
- `RayHit` -> `RayTriangleHit`
- General documentation and code quality improvements
### Removed / Not Ported
- Removed unused ray helpers and types, like `PrimitiveIntersection`
- Removed getters on intersection types, and made their properties
public
- There is no `2d` feature, and `Raycast::mesh_query` and
`Raycast::mesh2d_query` have been merged into `MeshRayCast::mesh_query`,
which handles both 2D and 3D
- I assume this existed previously because `Mesh2dHandle` used to be in
`bevy_sprite`. Now both the 2D and 3D mesh are in `bevy_render`.
- There is no `debug` feature or ray debug rendering
- There is no deferred API (`RaycastSource`)
- There is no `CursorRayPlugin` (the picking backend handles this)
### Note for Reviewers
In case it's helpful, the [first
commit](https://github.com/bevyengine/bevy/commit/281638ef10ebd25f783bee2533509fdf756f7ab4)
here is essentially a one-to-one port. The rest of the commits are
primarily refactoring and cleaning things up in the ways listed earlier,
as well as changes to the module structure.
It may also be useful to compare the original [picking
backend](https://github.com/aevyrie/bevy_mod_picking/blob/74f0c3c0fbc8048632ba46fd8f14e26aaea9c76c/backends/bevy_picking_raycast/src/lib.rs)
and [`bevy_mod_raycast`](https://github.com/aevyrie/bevy_mod_raycast) to
this PR. Feel free to mention if there are any changes that I should
revert or something I should not include in this PR.
## Testing
I tested mesh picking and relevant components in some examples, for both
2D and 3D meshes, and added a new `mesh_picking` example. I also
~~stole~~ ported over the [ray-mesh intersection
benchmark](https://github.com/aevyrie/bevy_mod_raycast/blob/dbc5ef32fe48997a1a7eeec7434d9dd8b829e52e/benches/ray_mesh_intersection.rs)
from `bevy_mod_raycast`.
---
## Showcase
Below is a version of the `2d_shapes` example modified to demonstrate 2D
mesh picking. This is not included in this PR.
https://github.com/user-attachments/assets/7742528c-8630-4c00-bacd-81576ac432bf
And below is the new `mesh_picking` example:
https://github.com/user-attachments/assets/b65c7a5a-fa3a-4c2d-8bbd-e7a2c772986e
There is also a really cool new `mesh_ray_cast` example ported over from
`bevy_mod_raycast`:
https://github.com/user-attachments/assets/3c5eb6c0-bd94-4fb0-bec6-8a85668a06c9
---------
Co-authored-by: Aevyrie <aevyrie@gmail.com>
Co-authored-by: Trent <2771466+tbillington@users.noreply.github.com>
Co-authored-by: François Mockers <mockersf@gmail.com>
2024-10-13 17:24:19 +00:00
|
|
|
let ray_pos = Vec3::new(ops::sin(t), ops::cos(3.0 * t) * 0.5, ops::cos(t)) * 0.5;
|
|
|
|
let ray_dir = Dir3::new(-ray_pos).unwrap();
|
|
|
|
let ray = Ray3d::new(ray_pos, ray_dir);
|
|
|
|
gizmos.sphere(ray_pos, 0.1, Color::WHITE);
|
|
|
|
bounce_ray(ray, &mut ray_cast, &mut gizmos, Color::from(css::RED));
|
|
|
|
|
|
|
|
// Cast a ray from the cursor and bounce it off of surfaces
|
|
|
|
for (_, ray) in ray_map.iter() {
|
|
|
|
bounce_ray(*ray, &mut ray_cast, &mut gizmos, Color::from(css::GREEN));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Bounces a ray off of surfaces `MAX_BOUNCES` times.
|
|
|
|
fn bounce_ray(mut ray: Ray3d, ray_cast: &mut MeshRayCast, gizmos: &mut Gizmos, color: Color) {
|
|
|
|
let mut intersections = Vec::with_capacity(MAX_BOUNCES + 1);
|
|
|
|
intersections.push((ray.origin, Color::srgb(30.0, 0.0, 0.0)));
|
|
|
|
|
|
|
|
for i in 0..MAX_BOUNCES {
|
|
|
|
// Cast the ray and get the first hit
|
|
|
|
let Some((_, hit)) = ray_cast.cast_ray(ray, &RayCastSettings::default()).first() else {
|
|
|
|
break;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Draw the point of intersection and add it to the list
|
|
|
|
let brightness = 1.0 + 10.0 * (1.0 - i as f32 / MAX_BOUNCES as f32);
|
|
|
|
intersections.push((hit.point, Color::BLACK.mix(&color, brightness)));
|
|
|
|
gizmos.sphere(hit.point, 0.005, Color::BLACK.mix(&color, brightness * 2.0));
|
|
|
|
|
|
|
|
// Reflect the ray off of the surface
|
|
|
|
ray.direction = Dir3::new(ray.direction.reflect(hit.normal)).unwrap();
|
|
|
|
ray.origin = hit.point + ray.direction * 1e-6;
|
|
|
|
}
|
|
|
|
gizmos.linestrip_gradient(intersections);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set up a simple 3D scene
|
|
|
|
fn setup(
|
|
|
|
mut commands: Commands,
|
|
|
|
mut meshes: ResMut<Assets<Mesh>>,
|
|
|
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
|
|
|
) {
|
|
|
|
// Make a box of planes facing inward so the laser gets trapped inside
|
|
|
|
let plane_mesh = meshes.add(Plane3d::default());
|
|
|
|
let plane_material = materials.add(Color::from(css::GRAY).with_alpha(0.01));
|
|
|
|
let create_plane = move |translation, rotation| {
|
|
|
|
(
|
|
|
|
Transform::from_translation(translation)
|
|
|
|
.with_rotation(Quat::from_scaled_axis(rotation)),
|
|
|
|
Mesh3d(plane_mesh.clone()),
|
|
|
|
MeshMaterial3d(plane_material.clone()),
|
|
|
|
)
|
|
|
|
};
|
|
|
|
|
|
|
|
commands.spawn(create_plane(vec3(0.0, 0.5, 0.0), Vec3::X * PI));
|
|
|
|
commands.spawn(create_plane(vec3(0.0, -0.5, 0.0), Vec3::ZERO));
|
|
|
|
commands.spawn(create_plane(vec3(0.5, 0.0, 0.0), Vec3::Z * FRAC_PI_2));
|
|
|
|
commands.spawn(create_plane(vec3(-0.5, 0.0, 0.0), Vec3::Z * -FRAC_PI_2));
|
|
|
|
commands.spawn(create_plane(vec3(0.0, 0.0, 0.5), Vec3::X * -FRAC_PI_2));
|
|
|
|
commands.spawn(create_plane(vec3(0.0, 0.0, -0.5), Vec3::X * FRAC_PI_2));
|
|
|
|
|
|
|
|
// Light
|
|
|
|
commands.spawn((
|
|
|
|
DirectionalLight::default(),
|
|
|
|
Transform::from_rotation(Quat::from_euler(EulerRot::XYZ, -0.1, 0.2, 0.0)),
|
|
|
|
));
|
|
|
|
|
|
|
|
// Camera
|
|
|
|
commands.spawn((
|
|
|
|
Camera3d::default(),
|
|
|
|
Camera {
|
|
|
|
hdr: true,
|
|
|
|
..default()
|
|
|
|
},
|
|
|
|
Transform::from_xyz(1.5, 1.5, 1.5).looking_at(Vec3::ZERO, Vec3::Y),
|
|
|
|
Tonemapping::TonyMcMapface,
|
|
|
|
Bloom::default(),
|
|
|
|
));
|
|
|
|
}
|