-
-
Notifications
You must be signed in to change notification settings - Fork 133
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement cast_ray_predicate to allow filtering the colliders with a …
…function (#297) As I was writing a xpbd backend for bevy_mod_picking, I found that a ray cast with a predicate was missing to implement the functionality efficiently. I was inspired by the predicate from bevy_rapier: https://github.com/dimforge/bevy_rapier/blob/c6bcce4695d596a7a9c8e91748d4dbb3d31f6d13/src/pipeline/query_filter.rs#L12 # Objective - Implement a ray cast function with the support for a predicate function to filter the colliders ## Solution - Added support for a predicate starting at SpatialQuery and down the pipeline - I added a small (and fun to play with) example to illustrate one possible use case - I chose not to change existing functionality. Therefore there is some code duplication in QueryPipelineAsCompositeShapeWithPredicate. This could be unified by introducing an Option in QueryPipelineAsCompositeShape, but that would create breaking changes at many places. --------- Co-authored-by: hendrikd <[email protected]> Co-authored-by: Joona Aalto <[email protected]>
- Loading branch information
1 parent
0776176
commit adb3a19
Showing
4 changed files
with
362 additions
and
2 deletions.
There are no files selected for viewing
This file contains 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,200 @@ | ||
#![allow(clippy::unnecessary_cast)] | ||
|
||
use bevy::{pbr::NotShadowReceiver, prelude::*}; | ||
use bevy_xpbd_3d::{math::*, prelude::*}; | ||
use examples_common_3d::XpbdExamplePlugin; | ||
|
||
fn main() { | ||
App::new() | ||
.add_plugins((DefaultPlugins, XpbdExamplePlugin)) | ||
.insert_resource(ClearColor(Color::rgb(0.05, 0.05, 0.1))) | ||
.insert_resource(Msaa::Sample4) | ||
.add_systems(Startup, setup) | ||
.add_systems(Update, (movement, reset_colors, raycast).chain()) | ||
.run(); | ||
} | ||
|
||
/// The acceleration used for movement. | ||
#[derive(Component)] | ||
struct MovementAcceleration(Scalar); | ||
|
||
#[derive(Component)] | ||
struct RayIndicator; | ||
|
||
/// If to be ignored by raycast | ||
#[derive(Component)] | ||
struct OutOfGlass(bool); | ||
|
||
const CUBE_COLOR: Color = Color::rgba(0.2, 0.7, 0.9, 1.0); | ||
const CUBE_COLOR_GLASS: Color = Color::rgba(0.2, 0.7, 0.9, 0.5); | ||
|
||
fn setup( | ||
mut commands: Commands, | ||
mut materials: ResMut<Assets<StandardMaterial>>, | ||
mut meshes: ResMut<Assets<Mesh>>, | ||
) { | ||
let cube_mesh = meshes.add(Mesh::from(shape::Cube { size: 1.0 })); | ||
|
||
// Ground | ||
commands.spawn(( | ||
PbrBundle { | ||
mesh: cube_mesh.clone(), | ||
material: materials.add(Color::rgb(0.7, 0.7, 0.8).into()), | ||
transform: Transform::from_xyz(0.0, -2.0, 0.0).with_scale(Vec3::new(100.0, 1.0, 100.0)), | ||
..default() | ||
}, | ||
RigidBody::Static, | ||
Collider::cuboid(1.0, 1.0, 1.0), | ||
)); | ||
|
||
let cube_size = 2.0; | ||
|
||
// Spawn cube stacks | ||
for x in -1..2 { | ||
for y in -1..2 { | ||
for z in -1..2 { | ||
let position = Vec3::new(x as f32, y as f32 + 5.0, z as f32) * (cube_size + 0.05); | ||
let material: StandardMaterial = if x == -1 { | ||
CUBE_COLOR_GLASS.into() | ||
} else { | ||
CUBE_COLOR.into() | ||
}; | ||
commands.spawn(( | ||
PbrBundle { | ||
mesh: cube_mesh.clone(), | ||
material: materials.add(material.clone()), | ||
transform: Transform::from_translation(position) | ||
.with_scale(Vec3::splat(cube_size as f32)), | ||
..default() | ||
}, | ||
RigidBody::Dynamic, | ||
Collider::cuboid(1.0, 1.0, 1.0), | ||
MovementAcceleration(10.0), | ||
OutOfGlass(x == -1), | ||
)); | ||
} | ||
} | ||
} | ||
|
||
// raycast indicator | ||
commands.spawn(( | ||
PbrBundle { | ||
mesh: cube_mesh.clone(), | ||
material: materials.add(Color::rgb(1.0, 0.0, 0.0).into()), | ||
transform: Transform::from_xyz(-500.0, 2.0, 0.0) | ||
.with_scale(Vec3::new(1000.0, 0.1, 0.1)), | ||
..default() | ||
}, | ||
RayIndicator, | ||
NotShadowReceiver, | ||
)); | ||
|
||
// Directional light | ||
commands.spawn(DirectionalLightBundle { | ||
directional_light: DirectionalLight { | ||
illuminance: 20_000.0, | ||
shadows_enabled: true, | ||
..default() | ||
}, | ||
transform: Transform::default().looking_at(Vec3::new(-1.0, -2.5, -1.5), Vec3::Y), | ||
..default() | ||
}); | ||
|
||
// Camera | ||
commands.spawn(Camera3dBundle { | ||
transform: Transform::from_translation(Vec3::new(0.0, 12.0, 40.0)) | ||
.looking_at(Vec3::Y * 5.0, Vec3::Y), | ||
..default() | ||
}); | ||
} | ||
|
||
fn movement( | ||
time: Res<Time>, | ||
keyboard_input: Res<Input<KeyCode>>, | ||
mut query: Query<(&MovementAcceleration, &mut LinearVelocity)>, | ||
) { | ||
// Precision is adjusted so that the example works with | ||
// both the `f32` and `f64` features. Otherwise you don't need this. | ||
let delta_time = time.delta_seconds_f64().adjust_precision(); | ||
|
||
for (movement_acceleration, mut linear_velocity) in &mut query { | ||
let up = keyboard_input.any_pressed([KeyCode::W, KeyCode::Up]); | ||
let down = keyboard_input.any_pressed([KeyCode::S, KeyCode::Down]); | ||
let left = keyboard_input.any_pressed([KeyCode::A, KeyCode::Left]); | ||
let right = keyboard_input.any_pressed([KeyCode::D, KeyCode::Right]); | ||
|
||
let horizontal = right as i8 - left as i8; | ||
let vertical = down as i8 - up as i8; | ||
let direction = | ||
Vector::new(horizontal as Scalar, 0.0, vertical as Scalar).normalize_or_zero(); | ||
|
||
// Move in input direction | ||
if direction != Vector::ZERO { | ||
linear_velocity.x += direction.x * movement_acceleration.0 * delta_time; | ||
linear_velocity.z += direction.z * movement_acceleration.0 * delta_time; | ||
} | ||
} | ||
} | ||
|
||
fn reset_colors( | ||
mut materials: ResMut<Assets<StandardMaterial>>, | ||
cubes: Query<(&Handle<StandardMaterial>, &OutOfGlass)>, | ||
) { | ||
for (material_handle, out_of_glass) in cubes.iter() { | ||
if let Some(material) = materials.get_mut(material_handle) { | ||
if out_of_glass.0 { | ||
material.base_color = CUBE_COLOR_GLASS; | ||
} else { | ||
material.base_color = CUBE_COLOR; | ||
} | ||
} | ||
} | ||
} | ||
|
||
fn raycast( | ||
query: SpatialQuery, | ||
mut materials: ResMut<Assets<StandardMaterial>>, | ||
cubes: Query<(&Handle<StandardMaterial>, &OutOfGlass)>, | ||
mut indicator_transform: Query<&mut Transform, With<RayIndicator>>, | ||
) { | ||
let origin = Vector { | ||
x: -200.0, | ||
y: 2.0, | ||
z: 0.0, | ||
}; | ||
let direction = Vector { | ||
x: 1.0, | ||
y: 0.0, | ||
z: 0.0, | ||
}; | ||
|
||
let mut ray_indicator_transform = indicator_transform.single_mut(); | ||
|
||
if let Some(ray_hit_data) = query.cast_ray_predicate( | ||
origin, | ||
direction, | ||
Scalar::MAX, | ||
true, | ||
SpatialQueryFilter::new(), | ||
&|entity| { | ||
if let Ok((_, out_of_glass)) = cubes.get(entity) { | ||
return !out_of_glass.0; // only look at cubes not out of glass | ||
} | ||
true // if the collider has no OutOfGlass component, then check it nevertheless | ||
}, | ||
) { | ||
// set color of hit object to red | ||
if let Ok((material_handle, _)) = cubes.get(ray_hit_data.entity) { | ||
if let Some(material) = materials.get_mut(material_handle) { | ||
material.base_color = Color::RED; | ||
} | ||
} | ||
|
||
// set length of ray indicator to look more like a laser | ||
let contact_point = (origin + direction * ray_hit_data.time_of_impact).x; | ||
let target_scale = 1000.0 + contact_point * 2.0; | ||
ray_indicator_transform.scale.x = target_scale as f32; | ||
} else { | ||
ray_indicator_transform.scale.x = 2000.0; | ||
} | ||
} |
This file contains 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 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 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