Skip to main content

spotlight/
spotlight.rs

1//! Illustrates spot lights.
2
3use std::f32::consts::*;
4
5use bevy::{
6    camera::Hdr,
7    color::palettes::basic::{MAROON, RED},
8    light::NotShadowCaster,
9    math::ops,
10    prelude::*,
11};
12use chacha20::ChaCha8Rng;
13use rand::{RngExt, SeedableRng};
14
15const INSTRUCTIONS: &str = "\
16Controls
17--------
18Horizontal Movement: WASD
19Vertical Movement: Space and Shift
20Rotate Camera: Left and Right Arrows";
21
22fn main() {
23    App::new()
24        .insert_resource(GlobalAmbientLight {
25            brightness: 20.0,
26            ..default()
27        })
28        .add_plugins(DefaultPlugins)
29        .add_systems(Startup, setup)
30        .add_systems(Update, (light_sway, movement, rotation))
31        .run();
32}
33
34#[derive(Component)]
35struct Movable;
36
37/// set up a simple 3D scene
38fn setup(
39    mut commands: Commands,
40    mut meshes: ResMut<Assets<Mesh>>,
41    mut materials: ResMut<Assets<StandardMaterial>>,
42) {
43    // ground plane
44    commands.spawn((
45        Mesh3d(meshes.add(Plane3d::default().mesh().size(100.0, 100.0))),
46        MeshMaterial3d(materials.add(Color::WHITE)),
47        Movable,
48    ));
49
50    // cubes
51
52    // We're seeding the PRNG here to make this example deterministic for testing purposes.
53    // This isn't strictly required in practical use unless you need your app to be deterministic.
54    let mut rng = ChaCha8Rng::seed_from_u64(19878367467713);
55    let cube_mesh = meshes.add(Cuboid::new(0.5, 0.5, 0.5));
56    let blue = materials.add(Color::srgb_u8(124, 144, 255));
57
58    commands.spawn_batch(
59        std::iter::repeat_with(move || {
60            let x = rng.random_range(-5.0..5.0);
61            let y = rng.random_range(0.0..3.0);
62            let z = rng.random_range(-5.0..5.0);
63
64            (
65                Mesh3d(cube_mesh.clone()),
66                MeshMaterial3d(blue.clone()),
67                Transform::from_xyz(x, y, z),
68                Movable,
69            )
70        })
71        .take(40),
72    );
73
74    let sphere_mesh = meshes.add(Sphere::new(0.05).mesh().uv(32, 18));
75    let sphere_mesh_direction = meshes.add(Sphere::new(0.1).mesh().uv(32, 18));
76    let red_emissive = materials.add(StandardMaterial {
77        base_color: RED.into(),
78        emissive: LinearRgba::new(1.0, 0.0, 0.0, 0.0),
79        ..default()
80    });
81    let maroon_emissive = materials.add(StandardMaterial {
82        base_color: MAROON.into(),
83        emissive: LinearRgba::new(0.369, 0.0, 0.0, 0.0),
84        ..default()
85    });
86
87    for x in 0..4 {
88        for z in 0..4 {
89            let x = x as f32 - 2.0;
90            let z = z as f32 - 2.0;
91            // red spot_light
92            commands.spawn((
93                SpotLight {
94                    intensity: 40_000.0, // lumens
95                    color: Color::WHITE,
96                    shadow_maps_enabled: true,
97                    inner_angle: PI / 4.0 * 0.85,
98                    outer_angle: PI / 4.0,
99                    ..default()
100                },
101                Transform::from_xyz(1.0 + x, 2.0, z)
102                    .looking_at(Vec3::new(1.0 + x, 0.0, z), Vec3::X),
103                children![
104                    (
105                        Mesh3d(sphere_mesh.clone()),
106                        MeshMaterial3d(red_emissive.clone()),
107                    ),
108                    (
109                        Mesh3d(sphere_mesh_direction.clone()),
110                        MeshMaterial3d(maroon_emissive.clone()),
111                        Transform::from_translation(Vec3::Z * -0.1),
112                        NotShadowCaster,
113                    )
114                ],
115            ));
116        }
117    }
118
119    // camera
120    commands.spawn((
121        Camera3d::default(),
122        Hdr,
123        Transform::from_xyz(-4.0, 5.0, 10.0).looking_at(Vec3::ZERO, Vec3::Y),
124    ));
125
126    commands.spawn((
127        Text::new(INSTRUCTIONS),
128        Node {
129            position_type: PositionType::Absolute,
130            top: px(12),
131            left: px(12),
132            ..default()
133        },
134    ));
135}
136
137fn light_sway(time: Res<Time>, mut query: Query<(&mut Transform, &mut SpotLight)>) {
138    for (mut transform, mut angles) in query.iter_mut() {
139        transform.rotation = Quat::from_euler(
140            EulerRot::XYZ,
141            -FRAC_PI_2 + ops::sin(time.elapsed_secs() * 0.67 * 3.0) * 0.5,
142            ops::sin(time.elapsed_secs() * 3.0) * 0.5,
143            0.0,
144        );
145        let angle = (ops::sin(time.elapsed_secs() * 1.2) + 1.0) * (FRAC_PI_4 - 0.1);
146        angles.inner_angle = angle * 0.8;
147        angles.outer_angle = angle;
148    }
149}
150
151fn movement(
152    input: Res<ButtonInput<KeyCode>>,
153    time: Res<Time>,
154    mut query: Query<&mut Transform, With<Movable>>,
155) {
156    // Calculate translation to move the cubes and ground plane
157    let mut translation = Vec3::ZERO;
158
159    // Horizontal forward and backward movement
160    if input.pressed(KeyCode::KeyW) {
161        translation.z += 1.0;
162    } else if input.pressed(KeyCode::KeyS) {
163        translation.z -= 1.0;
164    }
165
166    // Horizontal left and right movement
167    if input.pressed(KeyCode::KeyA) {
168        translation.x += 1.0;
169    } else if input.pressed(KeyCode::KeyD) {
170        translation.x -= 1.0;
171    }
172
173    // Vertical movement
174    if input.pressed(KeyCode::ShiftLeft) {
175        translation.y += 1.0;
176    } else if input.pressed(KeyCode::Space) {
177        translation.y -= 1.0;
178    }
179
180    translation *= 2.0 * time.delta_secs();
181
182    // Apply translation
183    for mut transform in &mut query {
184        transform.translation += translation;
185    }
186}
187
188fn rotation(
189    mut transform: Single<&mut Transform, With<Camera>>,
190    input: Res<ButtonInput<KeyCode>>,
191    time: Res<Time>,
192) {
193    let delta = time.delta_secs();
194
195    if input.pressed(KeyCode::ArrowLeft) {
196        transform.rotate_around(Vec3::ZERO, Quat::from_rotation_y(delta));
197    } else if input.pressed(KeyCode::ArrowRight) {
198        transform.rotate_around(Vec3::ZERO, Quat::from_rotation_y(-delta));
199    }
200}