docs/bevy/examples/3d/mesh_ray_cast.rs 3.9 K · 108 lines · raw · history

1 //! Demonstrates how to use the [`MeshRayCast`] system parameter to chain multiple ray casts
2 //! and bounce off of surfaces.
3
4 use std::f32::consts::{FRAC_PI_2, PI};
5
6 use bevy::{
7 color::palettes::css, core_pipeline::tonemapping::Tonemapping, math::vec3,
8 picking::backend::ray::RayMap, post_process::bloom::Bloom, prelude::*,
9 };
10
11 fn main() {
12 App::new()
13 .add_plugins(DefaultPlugins)
14 .add_systems(Startup, setup)
15 .add_systems(Update, bouncing_raycast)
16 .insert_resource(ClearColor(Color::BLACK))
17 .run();
18 }
19
20 const MAX_BOUNCES: usize = 64;
21 const LASER_SPEED: f32 = 0.03;
22
23 fn bouncing_raycast(
24 mut ray_cast: MeshRayCast,
25 mut gizmos: Gizmos,
26 time: Res<Time>,
27 // The ray map stores rays cast by the cursor
28 ray_map: Res<RayMap>,
29 ) {
30 // Cast an automatically moving ray and bounce it off of surfaces
31 let t = ops::cos((time.elapsed_secs() - 4.0).max(0.0) * LASER_SPEED) * PI;
32 let ray_pos = Vec3::new(ops::sin(t), ops::cos(3.0 * t) * 0.5, ops::cos(t)) * 0.5;
33 let ray_dir = Dir3::new(-ray_pos).unwrap();
34 let ray = Ray3d::new(ray_pos, ray_dir);
35 gizmos.sphere(ray_pos, 0.1, Color::WHITE);
36 bounce_ray(ray, &mut ray_cast, &mut gizmos, Color::from(css::RED));
37
38 // Cast a ray from the cursor and bounce it off of surfaces
39 for (_, ray) in ray_map.iter() {
40 bounce_ray(*ray, &mut ray_cast, &mut gizmos, Color::from(css::GREEN));
41 }
42 }
43
44 // Bounces a ray off of surfaces `MAX_BOUNCES` times.
45 fn bounce_ray(mut ray: Ray3d, ray_cast: &mut MeshRayCast, gizmos: &mut Gizmos, color: Color) {
46 let mut intersections = Vec::with_capacity(MAX_BOUNCES + 1);
47 intersections.push((ray.origin, Color::srgb(30.0, 0.0, 0.0)));
48
49 for i in 0..MAX_BOUNCES {
50 // Cast the ray and get the first hit
51 let Some((_, hit)) = ray_cast
52 .cast_ray(ray, &MeshRayCastSettings::default())
53 .first()
54 else {
55 break;
56 };
57
58 // Draw the point of intersection and add it to the list
59 let brightness = 1.0 + 10.0 * (1.0 - i as f32 / MAX_BOUNCES as f32);
60 intersections.push((hit.point, Color::BLACK.mix(&color, brightness)));
61 gizmos.sphere(hit.point, 0.005, Color::BLACK.mix(&color, brightness * 2.0));
62
63 // Reflect the ray off of the surface
64 ray.direction = Dir3::new(ray.direction.reflect(hit.normal)).unwrap();
65 ray.origin = hit.point + ray.direction * 1e-6;
66 }
67 gizmos.linestrip_gradient(intersections);
68 }
69
70 // Set up a simple 3D scene
71 fn setup(
72 mut commands: Commands,
73 mut meshes: ResMut<Assets<Mesh>>,
74 mut materials: ResMut<Assets<StandardMaterial>>,
75 ) {
76 // Make a box of planes facing inward so the laser gets trapped inside
77 let plane_mesh = meshes.add(Plane3d::default());
78 let plane_material = materials.add(Color::from(css::GRAY).with_alpha(0.01));
79 let create_plane = move |translation, rotation| {
80 (
81 Transform::from_translation(translation)
82 .with_rotation(Quat::from_scaled_axis(rotation)),
83 Mesh3d(plane_mesh.clone()),
84 MeshMaterial3d(plane_material.clone()),
85 )
86 };
87
88 commands.spawn(create_plane(vec3(0.0, 0.5, 0.0), Vec3::X * PI));
89 commands.spawn(create_plane(vec3(0.0, -0.5, 0.0), Vec3::ZERO));
90 commands.spawn(create_plane(vec3(0.5, 0.0, 0.0), Vec3::Z * FRAC_PI_2));
91 commands.spawn(create_plane(vec3(-0.5, 0.0, 0.0), Vec3::Z * -FRAC_PI_2));
92 commands.spawn(create_plane(vec3(0.0, 0.0, 0.5), Vec3::X * -FRAC_PI_2));
93 commands.spawn(create_plane(vec3(0.0, 0.0, -0.5), Vec3::X * FRAC_PI_2));
94
95 // Light
96 commands.spawn((
97 DirectionalLight::default(),
98 Transform::from_rotation(Quat::from_euler(EulerRot::XYZ, -0.1, 0.2, 0.0)),
99 ));
100
101 // Camera
102 commands.spawn((
103 Camera3d::default(),
104 Transform::from_xyz(1.5, 1.5, 1.5).looking_at(Vec3::ZERO, Vec3::Y),
105 Tonemapping::TonyMcMapface,
106 Bloom::default(),
107 ));
108 }