1use std::f32::consts::PI;
5
6use bevy::{
7 camera::{Exposure, PhysicalCameraParameters},
8 color::palettes::css::*,
9 light::CascadeShadowConfigBuilder,
10 prelude::*,
11};
12
13fn main() {
14 App::new()
15 .add_plugins(DefaultPlugins)
16 .insert_resource(Parameters(PhysicalCameraParameters {
17 aperture_f_stops: 1.0,
18 shutter_speed_s: 1.0 / 125.0,
19 sensitivity_iso: 100.0,
20 sensor_height: 0.01866,
21 }))
22 .add_systems(Startup, setup)
23 .add_systems(
24 Update,
25 (
26 update_exposure,
27 toggle_ambient_light,
28 movement,
29 animate_light_direction,
30 ),
31 )
32 .run();
33}
34
35#[derive(Resource, Default, Deref, DerefMut)]
36struct Parameters(PhysicalCameraParameters);
37
38#[derive(Component)]
39struct Movable;
40
41fn setup(
43 parameters: Res<Parameters>,
44 mut commands: Commands,
45 mut meshes: ResMut<Assets<Mesh>>,
46 mut materials: ResMut<Assets<StandardMaterial>>,
47 asset_server: Res<AssetServer>,
48) {
49 commands.spawn((
51 Mesh3d(meshes.add(Plane3d::default().mesh().size(10.0, 10.0))),
52 MeshMaterial3d(materials.add(StandardMaterial {
53 base_color: Color::WHITE,
54 perceptual_roughness: 1.0,
55 ..default()
56 })),
57 ));
58
59 let mut transform = Transform::from_xyz(2.5, 2.5, 0.0);
61 transform.rotate_z(PI / 2.);
62 commands.spawn((
63 Mesh3d(meshes.add(Cuboid::new(5.0, 0.15, 5.0))),
64 MeshMaterial3d(materials.add(StandardMaterial {
65 base_color: INDIGO.into(),
66 perceptual_roughness: 1.0,
67 ..default()
68 })),
69 transform,
70 ));
71 let mut transform = Transform::from_xyz(0.0, 2.5, -2.5);
73 transform.rotate_x(PI / 2.);
74 commands.spawn((
75 Mesh3d(meshes.add(Cuboid::new(5.0, 0.15, 5.0))),
76 MeshMaterial3d(materials.add(StandardMaterial {
77 base_color: INDIGO.into(),
78 perceptual_roughness: 1.0,
79 ..default()
80 })),
81 transform,
82 ));
83
84 let mut transform = Transform::from_xyz(-2.2, 0.5, 1.0);
86 transform.rotate_y(PI / 8.);
87 commands.spawn((
88 Mesh3d(meshes.add(Rectangle::new(2.0, 0.5))),
89 MeshMaterial3d(materials.add(StandardMaterial {
90 base_color_texture: Some(asset_server.load("branding/bevy_logo_light.png")),
91 perceptual_roughness: 1.0,
92 alpha_mode: AlphaMode::Mask(0.5),
93 cull_mode: None,
94 ..default()
95 })),
96 transform,
97 Movable,
98 ));
99
100 commands.spawn((
102 Mesh3d(meshes.add(Cuboid::default())),
103 MeshMaterial3d(materials.add(StandardMaterial {
104 base_color: DEEP_PINK.into(),
105 ..default()
106 })),
107 Transform::from_xyz(0.0, 0.5, 0.0),
108 Movable,
109 ));
110 commands.spawn((
112 Mesh3d(meshes.add(Sphere::new(0.5).mesh().uv(32, 18))),
113 MeshMaterial3d(materials.add(StandardMaterial {
114 base_color: LIMEGREEN.into(),
115 ..default()
116 })),
117 Transform::from_xyz(1.5, 1.0, 1.5),
118 Movable,
119 ));
120
121 commands.insert_resource(AmbientLight {
124 color: ORANGE_RED.into(),
125 brightness: 200.0,
126 ..default()
127 });
128
129 commands.spawn((
131 PointLight {
132 intensity: 100_000.0,
133 color: RED.into(),
134 shadows_enabled: true,
135 ..default()
136 },
137 Transform::from_xyz(1.0, 2.0, 0.0),
138 children![(
139 Mesh3d(meshes.add(Sphere::new(0.1).mesh().uv(32, 18))),
140 MeshMaterial3d(materials.add(StandardMaterial {
141 base_color: RED.into(),
142 emissive: LinearRgba::new(4.0, 0.0, 0.0, 0.0),
143 ..default()
144 })),
145 )],
146 ));
147
148 commands.spawn((
150 SpotLight {
151 intensity: 100_000.0,
152 color: LIME.into(),
153 shadows_enabled: true,
154 inner_angle: 0.6,
155 outer_angle: 0.8,
156 ..default()
157 },
158 Transform::from_xyz(-1.0, 2.0, 0.0).looking_at(Vec3::new(-1.0, 0.0, 0.0), Vec3::Z),
159 children![(
160 Mesh3d(meshes.add(Capsule3d::new(0.1, 0.125))),
161 MeshMaterial3d(materials.add(StandardMaterial {
162 base_color: LIME.into(),
163 emissive: LinearRgba::new(0.0, 4.0, 0.0, 0.0),
164 ..default()
165 })),
166 Transform::from_rotation(Quat::from_rotation_x(PI / 2.0)),
167 )],
168 ));
169
170 commands.spawn((
172 PointLight {
173 intensity: 100_000.0,
174 color: BLUE.into(),
175 shadows_enabled: true,
176 ..default()
177 },
178 Transform::from_xyz(0.0, 4.0, 0.0),
179 children![(
180 Mesh3d(meshes.add(Sphere::new(0.1).mesh().uv(32, 18))),
181 MeshMaterial3d(materials.add(StandardMaterial {
182 base_color: BLUE.into(),
183 emissive: LinearRgba::new(0.0, 0.0, 713.0, 0.0),
184 ..default()
185 })),
186 )],
187 ));
188
189 commands.spawn((
191 DirectionalLight {
192 illuminance: light_consts::lux::OVERCAST_DAY,
193 shadows_enabled: true,
194 ..default()
195 },
196 Transform {
197 translation: Vec3::new(0.0, 2.0, 0.0),
198 rotation: Quat::from_rotation_x(-PI / 4.),
199 ..default()
200 },
201 CascadeShadowConfigBuilder {
205 first_cascade_far_bound: 4.0,
206 maximum_distance: 10.0,
207 ..default()
208 }
209 .build(),
210 ));
211
212 commands.spawn((
215 Text::default(),
216 Node {
217 position_type: PositionType::Absolute,
218 top: px(12),
219 left: px(12),
220 ..default()
221 },
222 children![
223 TextSpan::new("Ambient light is on\n"),
224 TextSpan(format!("Aperture: f/{:.0}\n", parameters.aperture_f_stops,)),
225 TextSpan(format!(
226 "Shutter speed: 1/{:.0}s\n",
227 1.0 / parameters.shutter_speed_s
228 )),
229 TextSpan(format!(
230 "Sensitivity: ISO {:.0}\n",
231 parameters.sensitivity_iso
232 )),
233 TextSpan::new("\n\n"),
234 TextSpan::new("Controls\n"),
235 TextSpan::new("---------------\n"),
236 TextSpan::new("Arrow keys - Move objects\n"),
237 TextSpan::new("Space - Toggle ambient light\n"),
238 TextSpan::new("1/2 - Decrease/Increase aperture\n"),
239 TextSpan::new("3/4 - Decrease/Increase shutter speed\n"),
240 TextSpan::new("5/6 - Decrease/Increase sensitivity\n"),
241 TextSpan::new("R - Reset exposure"),
242 ],
243 ));
244
245 commands.spawn((
247 Camera3d::default(),
248 Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
249 Exposure::from_physical_camera(**parameters),
250 ));
251}
252
253fn update_exposure(
254 key_input: Res<ButtonInput<KeyCode>>,
255 mut parameters: ResMut<Parameters>,
256 mut exposure: Single<&mut Exposure>,
257 text: Single<Entity, With<Text>>,
258 mut writer: TextUiWriter,
259) {
260 let entity = *text;
262 if key_input.just_pressed(KeyCode::Digit2) {
263 parameters.aperture_f_stops *= 2.0;
264 } else if key_input.just_pressed(KeyCode::Digit1) {
265 parameters.aperture_f_stops *= 0.5;
266 }
267 if key_input.just_pressed(KeyCode::Digit4) {
268 parameters.shutter_speed_s *= 2.0;
269 } else if key_input.just_pressed(KeyCode::Digit3) {
270 parameters.shutter_speed_s *= 0.5;
271 }
272 if key_input.just_pressed(KeyCode::Digit6) {
273 parameters.sensitivity_iso += 100.0;
274 } else if key_input.just_pressed(KeyCode::Digit5) {
275 parameters.sensitivity_iso -= 100.0;
276 }
277 if key_input.just_pressed(KeyCode::KeyR) {
278 *parameters = Parameters::default();
279 }
280
281 *writer.text(entity, 2) = format!("Aperture: f/{:.0}\n", parameters.aperture_f_stops);
282 *writer.text(entity, 3) = format!(
283 "Shutter speed: 1/{:.0}s\n",
284 1.0 / parameters.shutter_speed_s
285 );
286 *writer.text(entity, 4) = format!("Sensitivity: ISO {:.0}\n", parameters.sensitivity_iso);
287
288 **exposure = Exposure::from_physical_camera(**parameters);
289}
290
291fn toggle_ambient_light(
292 key_input: Res<ButtonInput<KeyCode>>,
293 mut ambient_light: ResMut<AmbientLight>,
294 text: Single<Entity, With<Text>>,
295 mut writer: TextUiWriter,
296) {
297 if key_input.just_pressed(KeyCode::Space) {
298 if ambient_light.brightness > 1. {
299 ambient_light.brightness = 0.;
300 } else {
301 ambient_light.brightness = 200.;
302 }
303
304 let entity = *text;
305 let ambient_light_state_text: &str = match ambient_light.brightness {
306 0. => "off",
307 _ => "on",
308 };
309 *writer.text(entity, 1) = format!("Ambient light is {ambient_light_state_text}\n");
310 }
311}
312
313fn animate_light_direction(
314 time: Res<Time>,
315 mut query: Query<&mut Transform, With<DirectionalLight>>,
316) {
317 for mut transform in &mut query {
318 transform.rotate_y(time.delta_secs() * 0.5);
319 }
320}
321
322fn movement(
323 input: Res<ButtonInput<KeyCode>>,
324 time: Res<Time>,
325 mut query: Query<&mut Transform, With<Movable>>,
326) {
327 for mut transform in &mut query {
328 let mut direction = Vec3::ZERO;
329 if input.pressed(KeyCode::ArrowUp) {
330 direction.y += 1.0;
331 }
332 if input.pressed(KeyCode::ArrowDown) {
333 direction.y -= 1.0;
334 }
335 if input.pressed(KeyCode::ArrowLeft) {
336 direction.x -= 1.0;
337 }
338 if input.pressed(KeyCode::ArrowRight) {
339 direction.x += 1.0;
340 }
341
342 transform.translation += time.delta_secs() * 2.0 * direction;
343 }
344}