-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Add mesh picking backend and MeshRayCast
system parameter
#15800
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
Merged
Merged
Changes from all commits
Commits
Show all changes
33 commits
Select commit
Hold shift + click to select a range
281638e
Initial port
Jondolf 9b73b44
Rename to bevy_picking_mesh and add to default plugins
Jondolf 85360b5
Merge branch 'main' into mesh-picking
Jondolf 6ccc539
Clean up and rename "raycast" to "ray cast" everywhere
Jondolf 74cda85
Clean up, refactor module structure, and rename types
Jondolf 171f032
Add basic mesh picking example
Jondolf efe26d9
Merge branch 'main' into mesh-picking
Jondolf 4b728dc
Move to `bevy_picking` crate
Jondolf 7b4a2e7
Fix doc things and name
Jondolf 3526cfa
Clean up and improve docs
Jondolf 3af0565
Fix scaling issue and clean up
Jondolf 0ce79a5
Add back backface marker component `RayCastBackfaces`
Jondolf 3d544c6
Add benchmark
Jondolf 673d75f
Improve example
Jondolf 4495be8
Make Aevyrie a co-author :)
Jondolf 96b3a5f
Fix missing examples in docs
Jondolf 8ded91a
Merge branch 'main' into mesh-picking
Jondolf 9c0b4c6
Huh, this exists too... Fix missing feature in docs
Jondolf eb744fb
Fix example
Jondolf e7fca35
Add sick bouncing laser example by Aevyrie
Jondolf 7ae5c7b
Fix imports in doc examples
Jondolf a9f4b79
Fix bench
Jondolf 0c865f3
I keep forgetting this :/
Jondolf cdb3520
Make the `mesh_ray_cast` example clearer
Jondolf dcfdd01
Merge branch 'main' into mesh-picking
Jondolf 402d3c9
Fix bench for real for real
Jondolf 0909366
Use `chunks_exact` and change attribute panics to errors
Jondolf 8c51e67
Yeet `IntoUsize` and use slice instead of vec
Jondolf 390465f
Improve docs
Jondolf 86a2ea4
Make hit data docs more consistent
Jondolf 4e504b3
Optimize ray-AABB intersection
Jondolf 7aaa6f2
Merge branch 'main' into mesh-picking
Jondolf 379b710
Merge branch 'main' into mesh-picking
mockersf File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
use bevy_math::{Dir3, Mat4, Ray3d, Vec3}; | ||
use bevy_picking::{mesh_picking::ray_cast, prelude::*}; | ||
use criterion::{black_box, criterion_group, criterion_main, Criterion}; | ||
|
||
fn ptoxznorm(p: u32, size: u32) -> (f32, f32) { | ||
let ij = (p / (size), p % (size)); | ||
(ij.0 as f32 / size as f32, ij.1 as f32 / size as f32) | ||
} | ||
|
||
struct SimpleMesh { | ||
positions: Vec<[f32; 3]>, | ||
normals: Vec<[f32; 3]>, | ||
indices: Vec<u32>, | ||
} | ||
|
||
fn mesh_creation(vertices_per_side: u32) -> SimpleMesh { | ||
let mut positions = Vec::new(); | ||
let mut normals = Vec::new(); | ||
for p in 0..vertices_per_side.pow(2) { | ||
let xz = ptoxznorm(p, vertices_per_side); | ||
positions.push([xz.0 - 0.5, 0.0, xz.1 - 0.5]); | ||
normals.push([0.0, 1.0, 0.0]); | ||
} | ||
|
||
let mut indices = vec![]; | ||
for p in 0..vertices_per_side.pow(2) { | ||
if p % (vertices_per_side) != vertices_per_side - 1 | ||
&& p / (vertices_per_side) != vertices_per_side - 1 | ||
{ | ||
indices.extend_from_slice(&[p, p + 1, p + vertices_per_side]); | ||
indices.extend_from_slice(&[p + vertices_per_side, p + 1, p + vertices_per_side + 1]); | ||
} | ||
} | ||
|
||
SimpleMesh { | ||
positions, | ||
normals, | ||
indices, | ||
} | ||
} | ||
|
||
fn ray_mesh_intersection(c: &mut Criterion) { | ||
let mut group = c.benchmark_group("ray_mesh_intersection"); | ||
group.warm_up_time(std::time::Duration::from_millis(500)); | ||
|
||
for vertices_per_side in [10_u32, 100, 1000] { | ||
group.bench_function(format!("{}_vertices", vertices_per_side.pow(2)), |b| { | ||
let ray = Ray3d::new(Vec3::new(0.0, 1.0, 0.0), Dir3::NEG_Y); | ||
let mesh_to_world = Mat4::IDENTITY; | ||
let mesh = mesh_creation(vertices_per_side); | ||
|
||
b.iter(|| { | ||
black_box(ray_cast::ray_mesh_intersection( | ||
ray, | ||
&mesh_to_world, | ||
&mesh.positions, | ||
Some(&mesh.normals), | ||
Some(&mesh.indices), | ||
ray_cast::Backfaces::Cull, | ||
)); | ||
}); | ||
}); | ||
} | ||
} | ||
|
||
fn ray_mesh_intersection_no_cull(c: &mut Criterion) { | ||
let mut group = c.benchmark_group("ray_mesh_intersection_no_cull"); | ||
group.warm_up_time(std::time::Duration::from_millis(500)); | ||
|
||
for vertices_per_side in [10_u32, 100, 1000] { | ||
group.bench_function(format!("{}_vertices", vertices_per_side.pow(2)), |b| { | ||
let ray = Ray3d::new(Vec3::new(0.0, 1.0, 0.0), Dir3::NEG_Y); | ||
let mesh_to_world = Mat4::IDENTITY; | ||
let mesh = mesh_creation(vertices_per_side); | ||
|
||
b.iter(|| { | ||
black_box(ray_cast::ray_mesh_intersection( | ||
ray, | ||
&mesh_to_world, | ||
&mesh.positions, | ||
Some(&mesh.normals), | ||
Some(&mesh.indices), | ||
ray_cast::Backfaces::Include, | ||
)); | ||
}); | ||
}); | ||
} | ||
} | ||
|
||
fn ray_mesh_intersection_no_intersection(c: &mut Criterion) { | ||
let mut group = c.benchmark_group("ray_mesh_intersection_no_intersection"); | ||
group.warm_up_time(std::time::Duration::from_millis(500)); | ||
|
||
for vertices_per_side in [10_u32, 100, 1000] { | ||
group.bench_function(format!("{}_vertices", (vertices_per_side).pow(2)), |b| { | ||
let ray = Ray3d::new(Vec3::new(0.0, 1.0, 0.0), Dir3::X); | ||
let mesh_to_world = Mat4::IDENTITY; | ||
let mesh = mesh_creation(vertices_per_side); | ||
|
||
b.iter(|| { | ||
black_box(ray_cast::ray_mesh_intersection( | ||
ray, | ||
&mesh_to_world, | ||
&mesh.positions, | ||
Some(&mesh.normals), | ||
Some(&mesh.indices), | ||
ray_cast::Backfaces::Cull, | ||
)); | ||
}); | ||
}); | ||
} | ||
} | ||
|
||
criterion_group!( | ||
benches, | ||
ray_mesh_intersection, | ||
ray_mesh_intersection_no_cull, | ||
ray_mesh_intersection_no_intersection | ||
); | ||
criterion_main!(benches); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
//! A [mesh ray casting](ray_cast) backend for [`bevy_picking`](crate). | ||
//! | ||
//! By default, all meshes are pickable. Picking can be disabled for individual entities | ||
//! by adding [`PickingBehavior::IGNORE`]. | ||
//! | ||
//! To make mesh picking entirely opt-in, set [`MeshPickingBackendSettings::require_markers`] | ||
//! to `true` and add a [`RayCastPickable`] component to the desired camera and target entities. | ||
//! | ||
//! To manually perform mesh ray casts independent of picking, use the [`MeshRayCast`] system parameter. | ||
|
||
pub mod ray_cast; | ||
|
||
use crate::{ | ||
backend::{ray::RayMap, HitData, PointerHits}, | ||
prelude::*, | ||
PickSet, | ||
}; | ||
use bevy_app::prelude::*; | ||
use bevy_ecs::prelude::*; | ||
use bevy_reflect::prelude::*; | ||
use bevy_render::{prelude::*, view::RenderLayers}; | ||
use ray_cast::{MeshRayCast, RayCastSettings, RayCastVisibility, SimplifiedMesh}; | ||
|
||
/// Runtime settings for the [`MeshPickingBackend`]. | ||
#[derive(Resource, Reflect)] | ||
#[reflect(Resource, Default)] | ||
pub struct MeshPickingBackendSettings { | ||
/// When set to `true` ray casting will only happen between cameras and entities marked with | ||
/// [`RayCastPickable`]. `false` by default. | ||
/// | ||
/// This setting is provided to give you fine-grained control over which cameras and entities | ||
/// should be used by the mesh picking backend at runtime. | ||
pub require_markers: bool, | ||
|
||
/// Determines how mesh picking should consider [`Visibility`]. When set to [`RayCastVisibility::Any`], | ||
/// ray casts can be performed against both visible and hidden entities. | ||
/// | ||
/// Defaults to [`RayCastVisibility::VisibleInView`], only performing picking against visible entities | ||
/// that are in the view of a camera. | ||
pub ray_cast_visibility: RayCastVisibility, | ||
} | ||
|
||
impl Default for MeshPickingBackendSettings { | ||
fn default() -> Self { | ||
Self { | ||
require_markers: false, | ||
ray_cast_visibility: RayCastVisibility::VisibleInView, | ||
} | ||
} | ||
} | ||
|
||
/// An optional component that marks cameras and target entities that should be used in the [`MeshPickingBackend`]. | ||
/// Only needed if [`MeshPickingBackendSettings::require_markers`] is set to `true`, and ignored otherwise. | ||
#[derive(Debug, Clone, Default, Component, Reflect)] | ||
#[reflect(Component, Default)] | ||
pub struct RayCastPickable; | ||
|
||
/// Adds the mesh picking backend to your app. | ||
#[derive(Clone, Default)] | ||
pub struct MeshPickingBackend; | ||
|
||
impl Plugin for MeshPickingBackend { | ||
fn build(&self, app: &mut App) { | ||
app.init_resource::<MeshPickingBackendSettings>() | ||
.register_type::<(RayCastPickable, MeshPickingBackendSettings, SimplifiedMesh)>() | ||
.add_systems(PreUpdate, update_hits.in_set(PickSet::Backend)); | ||
} | ||
} | ||
|
||
/// Casts rays into the scene using [`MeshPickingBackendSettings`] and sends [`PointerHits`] events. | ||
#[allow(clippy::too_many_arguments)] | ||
pub fn update_hits( | ||
backend_settings: Res<MeshPickingBackendSettings>, | ||
ray_map: Res<RayMap>, | ||
picking_cameras: Query<(&Camera, Option<&RayCastPickable>, Option<&RenderLayers>)>, | ||
pickables: Query<&PickingBehavior>, | ||
marked_targets: Query<&RayCastPickable>, | ||
layers: Query<&RenderLayers>, | ||
mut ray_cast: MeshRayCast, | ||
mut output: EventWriter<PointerHits>, | ||
) { | ||
for (&ray_id, &ray) in ray_map.map().iter() { | ||
let Ok((camera, cam_pickable, cam_layers)) = picking_cameras.get(ray_id.camera) else { | ||
continue; | ||
}; | ||
if backend_settings.require_markers && cam_pickable.is_none() { | ||
continue; | ||
} | ||
|
||
let cam_layers = cam_layers.to_owned().unwrap_or_default(); | ||
|
||
let settings = RayCastSettings { | ||
visibility: backend_settings.ray_cast_visibility, | ||
filter: &|entity| { | ||
let marker_requirement = | ||
!backend_settings.require_markers || marked_targets.get(entity).is_ok(); | ||
|
||
// Other entities missing render layers are on the default layer 0 | ||
let entity_layers = layers.get(entity).cloned().unwrap_or_default(); | ||
let render_layers_match = cam_layers.intersects(&entity_layers); | ||
|
||
let is_pickable = pickables | ||
.get(entity) | ||
.map(|p| p.is_hoverable) | ||
.unwrap_or(true); | ||
|
||
marker_requirement && render_layers_match && is_pickable | ||
}, | ||
early_exit_test: &|entity_hit| { | ||
pickables | ||
.get(entity_hit) | ||
.is_ok_and(|pickable| pickable.should_block_lower) | ||
}, | ||
}; | ||
let picks = ray_cast | ||
.cast_ray(ray, &settings) | ||
.iter() | ||
.map(|(entity, hit)| { | ||
let hit_data = HitData::new( | ||
ray_id.camera, | ||
hit.distance, | ||
Some(hit.point), | ||
Some(hit.normal), | ||
); | ||
(*entity, hit_data) | ||
}) | ||
.collect::<Vec<_>>(); | ||
let order = camera.order as f32; | ||
if !picks.is_empty() { | ||
output.send(PointerHits::new(ray_id.pointer, picks, order)); | ||
} | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.