pub const fn vec3(x: f32, y: f32, z: f32) -> Vec3Expand description
Creates a 3-dimensional vector.
Examples found in repository?
More examples
examples/3d/anisotropy.rs (line 14)
14const CAMERA_INITIAL_POSITION: Vec3 = vec3(-0.4, 0.0, 0.0);
15
16/// The current settings of the app, as chosen by the user.
17#[derive(Resource)]
18struct AppStatus {
19 /// Which type of light is in the scene.
20 light_mode: LightMode,
21 /// Whether anisotropy is enabled.
22 anisotropy_enabled: bool,
23 /// Which mesh is visible
24 visible_scene: Scene,
25}
26
27/// Which type of light we're using: a directional light, a point light, or an
28/// environment map.
29#[derive(Clone, Copy, PartialEq, Default)]
30enum LightMode {
31 /// A rotating directional light.
32 #[default]
33 Directional,
34 /// A rotating point light.
35 Point,
36 /// An environment map (image-based lighting, including skybox).
37 EnvironmentMap,
38}
39
40/// A component that stores the version of the material with anisotropy and the
41/// version of the material without it.
42///
43/// This is placed on each mesh with a material. It exists so that the
44/// appropriate system can replace the materials when the user presses Enter to
45/// turn anisotropy on and off.
46#[derive(Component)]
47struct MaterialVariants {
48 /// The version of the material in the glTF file, with anisotropy.
49 anisotropic: Handle<StandardMaterial>,
50 /// The version of the material with anisotropy removed.
51 isotropic: Handle<StandardMaterial>,
52}
53
54#[derive(Default, Clone, Copy, PartialEq, Eq, Component)]
55enum Scene {
56 #[default]
57 BarnLamp,
58 Sphere,
59}
60
61impl Scene {
62 fn next(&self) -> Self {
63 match self {
64 Self::BarnLamp => Self::Sphere,
65 Self::Sphere => Self::BarnLamp,
66 }
67 }
68}
69
70impl Display for Scene {
71 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72 let scene_name = match self {
73 Self::BarnLamp => "Barn Lamp",
74 Self::Sphere => "Sphere",
75 };
76 write!(f, "{scene_name}")
77 }
78}
79
80/// The application entry point.
81fn main() {
82 App::new()
83 .init_resource::<AppStatus>()
84 .add_plugins(DefaultPlugins.set(WindowPlugin {
85 primary_window: Some(Window {
86 title: "Bevy Anisotropy Example".into(),
87 ..default()
88 }),
89 ..default()
90 }))
91 .add_systems(Startup, setup)
92 .add_systems(Update, create_material_variants)
93 .add_systems(Update, animate_light)
94 .add_systems(Update, rotate_camera)
95 .add_systems(Update, (handle_input, update_help_text).chain())
96 .run();
97}
98
99/// Creates the initial scene.
100fn setup(mut commands: Commands, asset_server: Res<AssetServer>, app_status: Res<AppStatus>) {
101 commands.spawn((
102 Camera3d::default(),
103 Transform::from_translation(CAMERA_INITIAL_POSITION).looking_at(Vec3::ZERO, Vec3::Y),
104 ));
105
106 spawn_directional_light(&mut commands);
107
108 commands.spawn((
109 SceneRoot(asset_server.load("models/AnisotropyBarnLamp/AnisotropyBarnLamp.gltf#Scene0")),
110 Transform::from_xyz(0.0, 0.07, -0.13),
111 Scene::BarnLamp,
112 ));
113
114 commands.spawn((
115 Mesh3d(
116 asset_server.add(
117 Mesh::from(Sphere::new(0.1))
118 .with_generated_tangents()
119 .unwrap(),
120 ),
121 ),
122 MeshMaterial3d(asset_server.add(StandardMaterial {
123 base_color: palettes::tailwind::GRAY_300.into(),
124 anisotropy_rotation: 0.5,
125 anisotropy_strength: 1.,
126 ..default()
127 })),
128 Scene::Sphere,
129 Visibility::Hidden,
130 ));
131
132 spawn_text(&mut commands, &app_status);
133}
134
135/// Spawns the help text.
136fn spawn_text(commands: &mut Commands, app_status: &AppStatus) {
137 commands.spawn((
138 app_status.create_help_text(),
139 Node {
140 position_type: PositionType::Absolute,
141 bottom: px(12),
142 left: px(12),
143 ..default()
144 },
145 ));
146}
147
148/// For each material, creates a version with the anisotropy removed.
149///
150/// This allows the user to press Enter to toggle anisotropy on and off.
151fn create_material_variants(
152 mut commands: Commands,
153 mut materials: ResMut<Assets<StandardMaterial>>,
154 new_meshes: Query<
155 (Entity, &MeshMaterial3d<StandardMaterial>),
156 (
157 Added<MeshMaterial3d<StandardMaterial>>,
158 Without<MaterialVariants>,
159 ),
160 >,
161) {
162 for (entity, anisotropic_material_handle) in new_meshes.iter() {
163 let Some(anisotropic_material) = materials.get(anisotropic_material_handle).cloned() else {
164 continue;
165 };
166
167 commands.entity(entity).insert(MaterialVariants {
168 anisotropic: anisotropic_material_handle.0.clone(),
169 isotropic: materials.add(StandardMaterial {
170 anisotropy_texture: None,
171 anisotropy_strength: 0.0,
172 anisotropy_rotation: 0.0,
173 ..anisotropic_material
174 }),
175 });
176 }
177}
178
179/// A system that animates the light every frame, if there is one.
180fn animate_light(
181 mut lights: Query<&mut Transform, Or<(With<DirectionalLight>, With<PointLight>)>>,
182 time: Res<Time>,
183) {
184 let now = time.elapsed_secs();
185 for mut transform in lights.iter_mut() {
186 transform.translation = vec3(ops::cos(now), 1.0, ops::sin(now)) * vec3(3.0, 4.0, 3.0);
187 transform.look_at(Vec3::ZERO, Vec3::Y);
188 }
189}examples/3d/mixed_lighting.rs (line 115)
115const INITIAL_SPHERE_POSITION: Vec3 = vec3(0.0, 0.5233223, 0.0);
116
117fn main() {
118 App::new()
119 .add_plugins(DefaultPlugins.set(WindowPlugin {
120 primary_window: Some(Window {
121 title: "Bevy Mixed Lighting Example".into(),
122 ..default()
123 }),
124 ..default()
125 }))
126 .add_plugins(MeshPickingPlugin)
127 .insert_resource(AmbientLight {
128 color: ClearColor::default().0,
129 brightness: 10000.0,
130 affects_lightmapped_meshes: true,
131 })
132 .init_resource::<AppStatus>()
133 .add_message::<WidgetClickEvent<LightingMode>>()
134 .add_message::<LightingModeChanged>()
135 .add_systems(Startup, setup)
136 .add_systems(Update, update_lightmaps)
137 .add_systems(Update, update_directional_light)
138 .add_systems(Update, make_sphere_nonpickable)
139 .add_systems(Update, update_radio_buttons)
140 .add_systems(Update, handle_lighting_mode_change)
141 .add_systems(Update, widgets::handle_ui_interactions::<LightingMode>)
142 .add_systems(Update, reset_sphere_position)
143 .add_systems(Update, move_sphere)
144 .add_systems(Update, adjust_help_text)
145 .run();
146}
147
148/// Creates the scene.
149fn setup(mut commands: Commands, asset_server: Res<AssetServer>, app_status: Res<AppStatus>) {
150 spawn_camera(&mut commands);
151 spawn_scene(&mut commands, &asset_server);
152 spawn_buttons(&mut commands);
153 spawn_help_text(&mut commands, &app_status);
154}
155
156/// Spawns the 3D camera.
157fn spawn_camera(commands: &mut Commands) {
158 commands
159 .spawn(Camera3d::default())
160 .insert(Transform::from_xyz(-0.7, 0.7, 1.0).looking_at(vec3(0.0, 0.3, 0.0), Vec3::Y));
161}
162
163/// Spawns the scene.
164///
165/// The scene is loaded from a glTF file.
166fn spawn_scene(commands: &mut Commands, asset_server: &AssetServer) {
167 commands
168 .spawn(SceneRoot(
169 asset_server.load(
170 GltfAssetLabel::Scene(0)
171 .from_asset("models/MixedLightingExample/MixedLightingExample.gltf"),
172 ),
173 ))
174 .observe(
175 |_: On<SceneInstanceReady>,
176 mut lighting_mode_changed_writer: MessageWriter<LightingModeChanged>| {
177 // When the scene loads, send a `LightingModeChanged` event so
178 // that we set up the lightmaps.
179 lighting_mode_changed_writer.write(LightingModeChanged);
180 },
181 );
182}
183
184/// Spawns the buttons that allow the user to change the lighting mode.
185fn spawn_buttons(commands: &mut Commands) {
186 commands.spawn((
187 widgets::main_ui_node(),
188 children![widgets::option_buttons(
189 "Lighting",
190 &[
191 (LightingMode::Baked, "Baked"),
192 (LightingMode::MixedDirect, "Mixed (Direct)"),
193 (LightingMode::MixedIndirect, "Mixed (Indirect)"),
194 (LightingMode::RealTime, "Real-Time"),
195 ],
196 )],
197 ));
198}
199
200/// Spawns the help text at the top of the window.
201fn spawn_help_text(commands: &mut Commands, app_status: &AppStatus) {
202 commands.spawn((
203 create_help_text(app_status),
204 Node {
205 position_type: PositionType::Absolute,
206 top: px(12),
207 left: px(12),
208 ..default()
209 },
210 HelpText,
211 ));
212}
213
214/// Adds lightmaps to and/or removes lightmaps from objects in the scene when
215/// the lighting mode changes.
216///
217/// This is also called right after the scene loads in order to set up the
218/// lightmaps.
219fn update_lightmaps(
220 mut commands: Commands,
221 asset_server: Res<AssetServer>,
222 mut materials: ResMut<Assets<StandardMaterial>>,
223 meshes: Query<(Entity, &GltfMeshName, &MeshMaterial3d<StandardMaterial>), With<Mesh3d>>,
224 mut lighting_mode_changed_reader: MessageReader<LightingModeChanged>,
225 app_status: Res<AppStatus>,
226) {
227 // Only run if the lighting mode changed. (Note that a change event is fired
228 // when the scene first loads.)
229 if lighting_mode_changed_reader.read().next().is_none() {
230 return;
231 }
232
233 // Select the lightmap to use, based on the lighting mode.
234 let lightmap: Option<Handle<Image>> = match app_status.lighting_mode {
235 LightingMode::Baked => {
236 Some(asset_server.load("lightmaps/MixedLightingExample-Baked.zstd.ktx2"))
237 }
238 LightingMode::MixedDirect => {
239 Some(asset_server.load("lightmaps/MixedLightingExample-MixedDirect.zstd.ktx2"))
240 }
241 LightingMode::MixedIndirect => {
242 Some(asset_server.load("lightmaps/MixedLightingExample-MixedIndirect.zstd.ktx2"))
243 }
244 LightingMode::RealTime => None,
245 };
246
247 'outer: for (entity, name, material) in &meshes {
248 // Add lightmaps to or remove lightmaps from the scenery objects in the
249 // scene (all objects but the sphere).
250 //
251 // Note that doing a linear search through the `LIGHTMAPS` array is
252 // inefficient, but we do it anyway in this example to improve clarity.
253 for (lightmap_name, uv_rect) in LIGHTMAPS {
254 if &**name != lightmap_name {
255 continue;
256 }
257
258 // Lightmap exposure defaults to zero, so we need to set it.
259 if let Some(ref mut material) = materials.get_mut(material) {
260 material.lightmap_exposure = LIGHTMAP_EXPOSURE;
261 }
262
263 // Add or remove the lightmap.
264 match lightmap {
265 Some(ref lightmap) => {
266 commands.entity(entity).insert(Lightmap {
267 image: (*lightmap).clone(),
268 uv_rect,
269 bicubic_sampling: false,
270 });
271 }
272 None => {
273 commands.entity(entity).remove::<Lightmap>();
274 }
275 }
276 continue 'outer;
277 }
278
279 // Add lightmaps to or remove lightmaps from the sphere.
280 if &**name == "Sphere" {
281 // Lightmap exposure defaults to zero, so we need to set it.
282 if let Some(ref mut material) = materials.get_mut(material) {
283 material.lightmap_exposure = LIGHTMAP_EXPOSURE;
284 }
285
286 // Add or remove the lightmap from the sphere. We only apply the
287 // lightmap in fully-baked mode.
288 match (&lightmap, app_status.lighting_mode) {
289 (Some(lightmap), LightingMode::Baked) => {
290 commands.entity(entity).insert(Lightmap {
291 image: (*lightmap).clone(),
292 uv_rect: SPHERE_UV_RECT,
293 bicubic_sampling: false,
294 });
295 }
296 _ => {
297 commands.entity(entity).remove::<Lightmap>();
298 }
299 }
300 }
301 }
302}
303
304/// Converts a uv rectangle from the OpenGL coordinate system (origin in the
305/// lower left) to the Vulkan coordinate system (origin in the upper left) that
306/// Bevy uses.
307///
308/// For this particular example, the baking tool happened to use the OpenGL
309/// coordinate system, so it was more convenient to do the conversion at compile
310/// time than to pre-calculate and hard-code the values.
311const fn uv_rect_opengl(gl_min: Vec2, size: Vec2) -> Rect {
312 let min = vec2(gl_min.x, 1.0 - gl_min.y - size.y);
313 Rect {
314 min,
315 max: vec2(min.x + size.x, min.y + size.y),
316 }
317}
318
319/// Ensures that clicking on the scene to move the sphere doesn't result in a
320/// hit on the sphere itself.
321fn make_sphere_nonpickable(
322 mut commands: Commands,
323 mut query: Query<(Entity, &Name), (With<Mesh3d>, Without<Pickable>)>,
324) {
325 for (sphere, name) in &mut query {
326 if &**name == "Sphere" {
327 commands.entity(sphere).insert(Pickable::IGNORE);
328 }
329 }
330}
331
332/// Updates the directional light settings as necessary when the lighting mode
333/// changes.
334fn update_directional_light(
335 mut lights: Query<&mut DirectionalLight>,
336 mut lighting_mode_changed_reader: MessageReader<LightingModeChanged>,
337 app_status: Res<AppStatus>,
338) {
339 // Only run if the lighting mode changed. (Note that a change event is fired
340 // when the scene first loads.)
341 if lighting_mode_changed_reader.read().next().is_none() {
342 return;
343 }
344
345 // Real-time direct light is used on the scenery if we're using mixed
346 // indirect or real-time mode.
347 let scenery_is_lit_in_real_time = matches!(
348 app_status.lighting_mode,
349 LightingMode::MixedIndirect | LightingMode::RealTime
350 );
351
352 for mut light in &mut lights {
353 light.affects_lightmapped_mesh_diffuse = scenery_is_lit_in_real_time;
354 // Don't bother enabling shadows if they won't show up on the scenery.
355 light.shadows_enabled = scenery_is_lit_in_real_time;
356 }
357}
358
359/// Updates the state of the selection widgets at the bottom of the window when
360/// the lighting mode changes.
361fn update_radio_buttons(
362 mut widgets: Query<
363 (
364 Entity,
365 Option<&mut BackgroundColor>,
366 Has<Text>,
367 &WidgetClickSender<LightingMode>,
368 ),
369 Or<(With<RadioButton>, With<RadioButtonText>)>,
370 >,
371 app_status: Res<AppStatus>,
372 mut writer: TextUiWriter,
373) {
374 for (entity, image, has_text, sender) in &mut widgets {
375 let selected = **sender == app_status.lighting_mode;
376
377 if let Some(mut bg_color) = image {
378 widgets::update_ui_radio_button(&mut bg_color, selected);
379 }
380 if has_text {
381 widgets::update_ui_radio_button_text(entity, &mut writer, selected);
382 }
383 }
384}
385
386/// Handles clicks on the widgets at the bottom of the screen and fires
387/// [`LightingModeChanged`] events.
388fn handle_lighting_mode_change(
389 mut widget_click_event_reader: MessageReader<WidgetClickEvent<LightingMode>>,
390 mut lighting_mode_changed_writer: MessageWriter<LightingModeChanged>,
391 mut app_status: ResMut<AppStatus>,
392) {
393 for event in widget_click_event_reader.read() {
394 app_status.lighting_mode = **event;
395 lighting_mode_changed_writer.write(LightingModeChanged);
396 }
397}
398
399/// Moves the sphere to its original position when the user selects the baked
400/// lighting mode.
401///
402/// As the light from the sphere is precomputed and depends on the sphere's
403/// original position, the sphere must be placed there in order for the lighting
404/// to be correct.
405fn reset_sphere_position(
406 mut objects: Query<(&Name, &mut Transform)>,
407 mut lighting_mode_changed_reader: MessageReader<LightingModeChanged>,
408 app_status: Res<AppStatus>,
409) {
410 // Only run if the lighting mode changed and if the lighting mode is
411 // `LightingMode::Baked`. (Note that a change event is fired when the scene
412 // first loads.)
413 if lighting_mode_changed_reader.read().next().is_none()
414 || app_status.lighting_mode != LightingMode::Baked
415 {
416 return;
417 }
418
419 for (name, mut transform) in &mut objects {
420 if &**name == "Sphere" {
421 transform.translation = INITIAL_SPHERE_POSITION;
422 break;
423 }
424 }
425}
426
427/// Updates the position of the sphere when the user clicks on a spot in the
428/// scene.
429///
430/// Note that the position of the sphere is locked in baked lighting mode.
431fn move_sphere(
432 mouse_button_input: Res<ButtonInput<MouseButton>>,
433 pointers: Query<&PointerInteraction>,
434 mut meshes: Query<(&GltfMeshName, &ChildOf), With<Mesh3d>>,
435 mut transforms: Query<&mut Transform>,
436 app_status: Res<AppStatus>,
437) {
438 // Only run when the left button is clicked and we're not in baked lighting
439 // mode.
440 if app_status.lighting_mode == LightingMode::Baked
441 || !mouse_button_input.pressed(MouseButton::Left)
442 {
443 return;
444 }
445
446 // Find the sphere.
447 let Some(child_of) = meshes
448 .iter_mut()
449 .filter_map(|(name, child_of)| {
450 if &**name == "Sphere" {
451 Some(child_of)
452 } else {
453 None
454 }
455 })
456 .next()
457 else {
458 return;
459 };
460
461 // Grab its transform.
462 let Ok(mut transform) = transforms.get_mut(child_of.parent()) else {
463 return;
464 };
465
466 // Set its transform to the appropriate position, as determined by the
467 // picking subsystem.
468 for interaction in pointers.iter() {
469 if let Some(&(
470 _,
471 HitData {
472 position: Some(position),
473 ..
474 },
475 )) = interaction.get_nearest_hit()
476 {
477 transform.translation = position + vec3(0.0, SPHERE_OFFSET, 0.0);
478 }
479 }
480}examples/shader_advanced/render_depth_to_texture.rs (line 251)
248fn spawn_main_camera(commands: &mut Commands) {
249 commands.spawn((
250 Camera3d::default(),
251 Transform::from_xyz(5.0, 2.0, 30.0).looking_at(vec3(5.0, 2.0, 0.0), Vec3::Y),
252 // Disable antialiasing just for simplicity's sake.
253 Msaa::Off,
254 ));
255}
256
257/// Spawns the instructional text at the top of the screen.
258fn spawn_instructions(commands: &mut Commands) {
259 commands.spawn((
260 Text::new("Use WASD to move the secondary camera"),
261 Node {
262 position_type: PositionType::Absolute,
263 top: Val::Px(12.0),
264 left: Val::Px(12.0),
265 ..Node::default()
266 },
267 ));
268}
269
270/// Spins the cube a bit every frame.
271fn rotate_cube(mut cubes: Query<&mut Transform, With<RotatingCube>>, time: Res<Time>) {
272 for mut transform in &mut cubes {
273 transform.rotate_x(1.5 * time.delta_secs());
274 transform.rotate_y(1.1 * time.delta_secs());
275 transform.rotate_z(-1.3 * time.delta_secs());
276 }
277}
278
279impl Material for ShowDepthTextureMaterial {
280 fn fragment_shader() -> ShaderRef {
281 SHADER_ASSET_PATH.into()
282 }
283}
284
285impl ViewNode for CopyDepthTextureNode {
286 type ViewQuery = (Read<ExtractedCamera>, Read<ViewDepthTexture>);
287
288 fn run<'w>(
289 &self,
290 _: &mut RenderGraphContext,
291 render_context: &mut RenderContext<'w>,
292 (camera, depth_texture): QueryItem<'w, '_, Self::ViewQuery>,
293 world: &'w World,
294 ) -> Result<(), NodeRunError> {
295 // Make sure we only run on the depth-only camera.
296 // We could make a marker component for that camera and extract it to
297 // the render world, but using `order` as a tag to tell the main camera
298 // and the depth-only camera apart works in a pinch.
299 if camera.order >= 0 {
300 return Ok(());
301 }
302
303 // Grab the texture we're going to copy to.
304 let demo_depth_texture = world.resource::<DemoDepthTexture>();
305 let image_assets = world.resource::<RenderAssets<GpuImage>>();
306 let Some(demo_depth_image) = image_assets.get(demo_depth_texture.0.id()) else {
307 return Ok(());
308 };
309
310 // Perform the copy.
311 render_context.add_command_buffer_generation_task(move |render_device| {
312 let mut command_encoder =
313 render_device.create_command_encoder(&CommandEncoderDescriptor {
314 label: Some("copy depth to demo texture command encoder"),
315 });
316 command_encoder.push_debug_group("copy depth to demo texture");
317
318 // Copy from the view's depth texture to the destination depth
319 // texture.
320 command_encoder.copy_texture_to_texture(
321 TexelCopyTextureInfo {
322 texture: &depth_texture.texture,
323 mip_level: 0,
324 origin: Origin3d::default(),
325 aspect: TextureAspect::DepthOnly,
326 },
327 TexelCopyTextureInfo {
328 texture: &demo_depth_image.texture,
329 mip_level: 0,
330 origin: Origin3d::default(),
331 aspect: TextureAspect::DepthOnly,
332 },
333 Extent3d {
334 width: DEPTH_TEXTURE_SIZE,
335 height: DEPTH_TEXTURE_SIZE,
336 depth_or_array_layers: 1,
337 },
338 );
339
340 command_encoder.pop_debug_group();
341 command_encoder.finish()
342 });
343
344 Ok(())
345 }
346}
347
348impl FromWorld for DemoDepthTexture {
349 fn from_world(world: &mut World) -> Self {
350 let mut images = world.resource_mut::<Assets<Image>>();
351
352 // Create a new 32-bit floating point depth texture.
353 let mut depth_image = Image::new_uninit(
354 Extent3d {
355 width: DEPTH_TEXTURE_SIZE,
356 height: DEPTH_TEXTURE_SIZE,
357 depth_or_array_layers: 1,
358 },
359 TextureDimension::D2,
360 TextureFormat::Depth32Float,
361 RenderAssetUsages::default(),
362 );
363
364 // Create a sampler. Note that this needs to specify a `compare`
365 // function in order to be compatible with depth textures.
366 depth_image.sampler = ImageSampler::Descriptor(ImageSamplerDescriptor {
367 label: Some("custom depth image sampler".to_owned()),
368 compare: Some(ImageCompareFunction::Always),
369 ..ImageSamplerDescriptor::default()
370 });
371
372 let depth_image_handle = images.add(depth_image);
373 DemoDepthTexture(depth_image_handle)
374 }
375}
376
377impl ExtractResource for DemoDepthTexture {
378 type Source = Self;
379
380 fn extract_resource(source: &Self::Source) -> Self {
381 // Share the `DemoDepthTexture` resource over to the render world so
382 // that our `CopyDepthTextureNode` can access it.
383 (*source).clone()
384 }
385}
386
387/// Draws an outline of the depth texture on the screen.
388fn draw_camera_gizmo(cameras: Query<(&Camera, &GlobalTransform)>, mut gizmos: Gizmos) {
389 for (camera, transform) in &cameras {
390 // As above, we use the order as a cheap tag to tell the depth texture
391 // apart from the main texture.
392 if camera.order >= 0 {
393 continue;
394 }
395
396 // Draw a cone representing the camera.
397 gizmos.primitive_3d(
398 &Cone {
399 radius: 1.0,
400 height: 3.0,
401 },
402 Isometry3d::new(
403 transform.translation(),
404 // We have to rotate here because `Cone` primitives are oriented
405 // along +Y and cameras point along +Z.
406 transform.rotation() * Quat::from_rotation_x(FRAC_PI_2),
407 ),
408 LIME,
409 );
410 }
411}
412
413/// Orbits the cube when WASD is pressed.
414fn move_camera(
415 mut cameras: Query<(&Camera, &mut Transform)>,
416 keyboard: Res<ButtonInput<KeyCode>>,
417 time: Res<Time>,
418) {
419 for (camera, mut transform) in &mut cameras {
420 // Only affect the depth camera.
421 if camera.order >= 0 {
422 continue;
423 }
424
425 // Convert the camera's position from Cartesian to spherical coordinates.
426 let mut spherical_coords = SphericalCoordinates::from_cartesian(transform.translation);
427
428 // Modify those spherical coordinates as appropriate.
429 let mut changed = false;
430 if keyboard.pressed(KeyCode::KeyW) {
431 spherical_coords.inclination -= time.delta_secs() * CAMERA_MOVEMENT_SPEED;
432 changed = true;
433 }
434 if keyboard.pressed(KeyCode::KeyS) {
435 spherical_coords.inclination += time.delta_secs() * CAMERA_MOVEMENT_SPEED;
436 changed = true;
437 }
438 if keyboard.pressed(KeyCode::KeyA) {
439 spherical_coords.azimuth += time.delta_secs() * CAMERA_MOVEMENT_SPEED;
440 changed = true;
441 }
442 if keyboard.pressed(KeyCode::KeyD) {
443 spherical_coords.azimuth -= time.delta_secs() * CAMERA_MOVEMENT_SPEED;
444 changed = true;
445 }
446
447 // If they were changed, convert from spherical coordinates back to
448 // Cartesian ones, and update the camera's transform.
449 if changed {
450 spherical_coords.inclination = spherical_coords.inclination.clamp(0.01, PI - 0.01);
451 transform.translation = spherical_coords.to_cartesian();
452 transform.look_at(Vec3::ZERO, Vec3::Y);
453 }
454 }
455}
456
457impl SphericalCoordinates {
458 /// [Converts] from Cartesian coordinates to spherical coordinates.
459 ///
460 /// [Converts]: https://en.wikipedia.org/wiki/Spherical_coordinate_system#Cartesian_coordinates
461 fn from_cartesian(p: Vec3) -> SphericalCoordinates {
462 let radius = p.length();
463 SphericalCoordinates {
464 radius,
465 inclination: acos(p.y / radius),
466 azimuth: atan2(p.z, p.x),
467 }
468 }
469
470 /// [Converts] from spherical coordinates to Cartesian coordinates.
471 ///
472 /// [Converts]: https://en.wikipedia.org/wiki/Spherical_coordinate_system#Cartesian_coordinates
473 fn to_cartesian(self) -> Vec3 {
474 let (sin_inclination, cos_inclination) = sin_cos(self.inclination);
475 let (sin_azimuth, cos_azimuth) = sin_cos(self.azimuth);
476 self.radius
477 * vec3(
478 sin_inclination * cos_azimuth,
479 cos_inclination,
480 sin_inclination * sin_azimuth,
481 )
482 }Additional examples can be found in:
- examples/3d/clearcoat.rs
- examples/3d/clustered_decals.rs
- examples/3d/pcss.rs
- examples/3d/irradiance_volumes.rs
- examples/3d/ssr.rs
- examples/3d/fog_volumes.rs
- examples/3d/mesh_ray_cast.rs
- examples/movement/smooth_follow.rs
- examples/shader_advanced/specialized_mesh_pipeline.rs
- examples/animation/eased_motion.rs
- examples/3d/light_textures.rs
- examples/3d/volumetric_fog.rs