Support Warning
WebGPU is currently only supported on Chrome starting with version 113, and only on desktop. If they don't work on your configuration, you can check the WebGL2 examples here.mesh_ray_cast.rs:
//! 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
let t = ops::cos((time.elapsed_secs() - 4.0).max(0.0) * LASER_SPEED) * PI;
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(),
));
}