Split zoom/orbit into separate examples (#15135)
# Objective As previously discussed, split camera zoom and orbiting examples to keep things less cluttered. See discussion on #15092 for context.
This commit is contained in:
		
							parent
							
								
									75343ef584
								
							
						
					
					
						commit
						8e7ef64bb1
					
				
							
								
								
									
										13
									
								
								Cargo.toml
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								Cargo.toml
									
									
									
									
									
								
							| @ -3251,7 +3251,18 @@ doc-scrape-examples = true | |||||||
| 
 | 
 | ||||||
| [package.metadata.example.projection_zoom] | [package.metadata.example.projection_zoom] | ||||||
| name = "Projection Zoom" | name = "Projection Zoom" | ||||||
| description = "Shows how to zoom and orbit orthographic and perspective projection cameras." | description = "Shows how to zoom orthographic and perspective projection cameras." | ||||||
|  | category = "Camera" | ||||||
|  | wasm = true | ||||||
|  | 
 | ||||||
|  | [[example]] | ||||||
|  | name = "camera_orbit" | ||||||
|  | path = "examples/camera/camera_orbit.rs" | ||||||
|  | doc-scrape-examples = true | ||||||
|  | 
 | ||||||
|  | [package.metadata.example.camera_orbit] | ||||||
|  | name = "Camera Orbit" | ||||||
|  | description = "Shows how to orbit a static scene using pitch, yaw, and roll." | ||||||
| category = "Camera" | category = "Camera" | ||||||
| wasm = true | wasm = true | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -254,8 +254,9 @@ Example | Description | |||||||
| Example | Description | Example | Description | ||||||
| --- | --- | --- | --- | ||||||
| [2D top-down camera](../examples/camera/2d_top_down_camera.rs) | A 2D top-down camera smoothly following player movements | [2D top-down camera](../examples/camera/2d_top_down_camera.rs) | A 2D top-down camera smoothly following player movements | ||||||
|  | [Camera Orbit](../examples/camera/camera_orbit.rs) | Shows how to orbit a static scene using pitch, yaw, and roll. | ||||||
| [First person view model](../examples/camera/first_person_view_model.rs) | A first-person camera that uses a world model and a view model with different field of views (FOV) | [First person view model](../examples/camera/first_person_view_model.rs) | A first-person camera that uses a world model and a view model with different field of views (FOV) | ||||||
| [Projection Zoom](../examples/camera/projection_zoom.rs) | Shows how to zoom and orbit orthographic and perspective projection cameras. | [Projection Zoom](../examples/camera/projection_zoom.rs) | Shows how to zoom orthographic and perspective projection cameras. | ||||||
| 
 | 
 | ||||||
| ## Dev tools | ## Dev tools | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										163
									
								
								examples/camera/camera_orbit.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										163
									
								
								examples/camera/camera_orbit.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,163 @@ | |||||||
|  | //! Shows how to orbit camera around a static scene using pitch, yaw, and roll.
 | ||||||
|  | 
 | ||||||
|  | use std::{f32::consts::FRAC_PI_2, ops::Range}; | ||||||
|  | 
 | ||||||
|  | use bevy::prelude::*; | ||||||
|  | 
 | ||||||
|  | #[derive(Debug, Default, Resource)] | ||||||
|  | struct CameraSettings { | ||||||
|  |     pub orbit_distance: f32, | ||||||
|  |     // Multiply keyboard inputs by this factor
 | ||||||
|  |     pub orbit_speed: f32, | ||||||
|  |     // Clamp pitch to this range
 | ||||||
|  |     pub pitch_range: Range<f32>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn main() { | ||||||
|  |     App::new() | ||||||
|  |         .add_plugins(DefaultPlugins) | ||||||
|  |         .init_resource::<CameraSettings>() | ||||||
|  |         .add_systems(Startup, (setup, instructions)) | ||||||
|  |         .add_systems(Update, orbit) | ||||||
|  |         .run(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// Set up a simple 3D scene
 | ||||||
|  | fn setup( | ||||||
|  |     mut camera_settings: ResMut<CameraSettings>, | ||||||
|  |     mut commands: Commands, | ||||||
|  |     mut meshes: ResMut<Assets<Mesh>>, | ||||||
|  |     mut materials: ResMut<Assets<StandardMaterial>>, | ||||||
|  | ) { | ||||||
|  |     // Limiting pitch stops some unexpected rotation past 90° up or down.
 | ||||||
|  |     let pitch_limit = FRAC_PI_2 - 0.01; | ||||||
|  | 
 | ||||||
|  |     camera_settings.orbit_distance = 10.0; | ||||||
|  |     camera_settings.orbit_speed = 1.0; | ||||||
|  |     camera_settings.pitch_range = -pitch_limit..pitch_limit; | ||||||
|  | 
 | ||||||
|  |     commands.spawn(( | ||||||
|  |         Name::new("Camera"), | ||||||
|  |         Camera3dBundle { | ||||||
|  |             transform: Transform::from_xyz(5.0, 5.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y), | ||||||
|  |             ..default() | ||||||
|  |         }, | ||||||
|  |     )); | ||||||
|  | 
 | ||||||
|  |     commands.spawn(( | ||||||
|  |         Name::new("Plane"), | ||||||
|  |         PbrBundle { | ||||||
|  |             mesh: meshes.add(Plane3d::default().mesh().size(5.0, 5.0)), | ||||||
|  |             material: materials.add(StandardMaterial { | ||||||
|  |                 base_color: Color::srgb(0.3, 0.5, 0.3), | ||||||
|  |                 // Turning off culling keeps the plane visible when viewed from beneath.
 | ||||||
|  |                 cull_mode: None, | ||||||
|  |                 ..default() | ||||||
|  |             }), | ||||||
|  |             ..default() | ||||||
|  |         }, | ||||||
|  |     )); | ||||||
|  | 
 | ||||||
|  |     commands.spawn(( | ||||||
|  |         Name::new("Cube"), | ||||||
|  |         PbrBundle { | ||||||
|  |             mesh: meshes.add(Cuboid::default()), | ||||||
|  |             material: materials.add(Color::srgb(0.8, 0.7, 0.6)), | ||||||
|  |             transform: Transform::from_xyz(1.5, 0.51, 1.5), | ||||||
|  |             ..default() | ||||||
|  |         }, | ||||||
|  |     )); | ||||||
|  | 
 | ||||||
|  |     commands.spawn(( | ||||||
|  |         Name::new("Light"), | ||||||
|  |         PointLightBundle { | ||||||
|  |             transform: Transform::from_xyz(3.0, 8.0, 5.0), | ||||||
|  |             ..default() | ||||||
|  |         }, | ||||||
|  |     )); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn instructions(mut commands: Commands) { | ||||||
|  |     commands | ||||||
|  |         .spawn(( | ||||||
|  |             Name::new("Instructions"), | ||||||
|  |             NodeBundle { | ||||||
|  |                 style: Style { | ||||||
|  |                     align_items: AlignItems::Start, | ||||||
|  |                     flex_direction: FlexDirection::Column, | ||||||
|  |                     justify_content: JustifyContent::Start, | ||||||
|  |                     width: Val::Percent(100.), | ||||||
|  |                     ..default() | ||||||
|  |                 }, | ||||||
|  |                 ..default() | ||||||
|  |             }, | ||||||
|  |         )) | ||||||
|  |         .with_children(|parent| { | ||||||
|  |             parent.spawn(TextBundle::from_section( | ||||||
|  |                 "W or S: pitch", | ||||||
|  |                 TextStyle::default(), | ||||||
|  |             )); | ||||||
|  |             parent.spawn(TextBundle::from_section( | ||||||
|  |                 "A or D: yaw", | ||||||
|  |                 TextStyle::default(), | ||||||
|  |             )); | ||||||
|  |             parent.spawn(TextBundle::from_section( | ||||||
|  |                 "Q or E: roll", | ||||||
|  |                 TextStyle::default(), | ||||||
|  |             )); | ||||||
|  |         }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn orbit( | ||||||
|  |     mut camera: Query<&mut Transform, With<Camera>>, | ||||||
|  |     camera_settings: Res<CameraSettings>, | ||||||
|  |     keyboard_input: Res<ButtonInput<KeyCode>>, | ||||||
|  |     time: Res<Time>, | ||||||
|  | ) { | ||||||
|  |     let mut transform = camera.single_mut(); | ||||||
|  | 
 | ||||||
|  |     let mut delta_pitch = 0.0; | ||||||
|  |     let mut delta_roll = 0.0; | ||||||
|  |     let mut delta_yaw = 0.0; | ||||||
|  | 
 | ||||||
|  |     if keyboard_input.pressed(KeyCode::KeyW) { | ||||||
|  |         delta_pitch += camera_settings.orbit_speed; | ||||||
|  |     } | ||||||
|  |     if keyboard_input.pressed(KeyCode::KeyS) { | ||||||
|  |         delta_pitch -= camera_settings.orbit_speed; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if keyboard_input.pressed(KeyCode::KeyQ) { | ||||||
|  |         delta_roll -= camera_settings.orbit_speed; | ||||||
|  |     } | ||||||
|  |     if keyboard_input.pressed(KeyCode::KeyE) { | ||||||
|  |         delta_roll += camera_settings.orbit_speed; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if keyboard_input.pressed(KeyCode::KeyA) { | ||||||
|  |         delta_yaw -= camera_settings.orbit_speed; | ||||||
|  |     } | ||||||
|  |     if keyboard_input.pressed(KeyCode::KeyD) { | ||||||
|  |         delta_yaw += camera_settings.orbit_speed; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Incorporating the delta time between calls prevents this from being framerate-bound.
 | ||||||
|  |     delta_pitch *= time.delta_seconds(); | ||||||
|  |     delta_roll *= time.delta_seconds(); | ||||||
|  |     delta_yaw *= time.delta_seconds(); | ||||||
|  | 
 | ||||||
|  |     // Obtain the existing pitch, yaw, and roll values from the transform.
 | ||||||
|  |     let (yaw, pitch, roll) = transform.rotation.to_euler(EulerRot::YXZ); | ||||||
|  | 
 | ||||||
|  |     // Establish the new yaw and pitch, preventing the pitch value from exceeding our limits.
 | ||||||
|  |     let pitch = (pitch + delta_pitch).clamp( | ||||||
|  |         camera_settings.pitch_range.start, | ||||||
|  |         camera_settings.pitch_range.end, | ||||||
|  |     ); | ||||||
|  |     let roll = roll + delta_roll; | ||||||
|  |     let yaw = yaw + delta_yaw; | ||||||
|  |     transform.rotation = Quat::from_euler(EulerRot::YXZ, yaw, pitch, roll); | ||||||
|  | 
 | ||||||
|  |     // Adjust the translation to maintain the correct orientation toward the orbit target.
 | ||||||
|  |     transform.translation = Vec3::ZERO - transform.forward() * camera_settings.orbit_distance; | ||||||
|  | } | ||||||
| @ -1,17 +1,11 @@ | |||||||
| //! Shows how to zoom and orbit orthographic and perspective projection cameras.
 | //! Shows how to zoom orthographic and perspective projection cameras.
 | ||||||
| 
 | 
 | ||||||
| use std::{ | use std::{f32::consts::PI, ops::Range}; | ||||||
|     f32::consts::{FRAC_PI_2, PI}, |  | ||||||
|     ops::Range, |  | ||||||
| }; |  | ||||||
| 
 | 
 | ||||||
| use bevy::{input::mouse::AccumulatedMouseScroll, prelude::*, render::camera::ScalingMode}; | use bevy::{input::mouse::AccumulatedMouseScroll, prelude::*, render::camera::ScalingMode}; | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, Default, Resource)] | #[derive(Debug, Default, Resource)] | ||||||
| struct CameraSettings { | struct CameraSettings { | ||||||
|     pub orbit_distance: f32, |  | ||||||
|     // Multiply keyboard inputs by this factor
 |  | ||||||
|     pub orbit_speed: f32, |  | ||||||
|     // Clamp fixed vertical scale to this range
 |     // Clamp fixed vertical scale to this range
 | ||||||
|     pub orthographic_zoom_range: Range<f32>, |     pub orthographic_zoom_range: Range<f32>, | ||||||
|     // Multiply mouse wheel inputs by this factor
 |     // Multiply mouse wheel inputs by this factor
 | ||||||
| @ -20,8 +14,6 @@ struct CameraSettings { | |||||||
|     pub perspective_zoom_range: Range<f32>, |     pub perspective_zoom_range: Range<f32>, | ||||||
|     // Multiply mouse wheel inputs by this factor
 |     // Multiply mouse wheel inputs by this factor
 | ||||||
|     pub perspective_zoom_speed: f32, |     pub perspective_zoom_speed: f32, | ||||||
|     // Clamp pitch to this range
 |  | ||||||
|     pub pitch_range: Range<f32>, |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fn main() { | fn main() { | ||||||
| @ -29,12 +21,13 @@ fn main() { | |||||||
|         .add_plugins(DefaultPlugins) |         .add_plugins(DefaultPlugins) | ||||||
|         .init_resource::<CameraSettings>() |         .init_resource::<CameraSettings>() | ||||||
|         .add_systems(Startup, (setup, instructions)) |         .add_systems(Startup, (setup, instructions)) | ||||||
|         .add_systems(Update, (orbit, switch_projection, zoom)) |         .add_systems(Update, (switch_projection, zoom)) | ||||||
|         .run(); |         .run(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Set up a simple 3D scene
 | /// Set up a simple 3D scene
 | ||||||
| fn setup( | fn setup( | ||||||
|  |     asset_server: Res<AssetServer>, | ||||||
|     mut camera_settings: ResMut<CameraSettings>, |     mut camera_settings: ResMut<CameraSettings>, | ||||||
|     mut commands: Commands, |     mut commands: Commands, | ||||||
|     mut meshes: ResMut<Assets<Mesh>>, |     mut meshes: ResMut<Assets<Mesh>>, | ||||||
| @ -51,17 +44,11 @@ fn setup( | |||||||
|     let min_zoom = 5.0; |     let min_zoom = 5.0; | ||||||
|     let max_zoom = 150.0; |     let max_zoom = 150.0; | ||||||
| 
 | 
 | ||||||
|     // Limiting pitch stops some unexpected rotation past 90° up or down.
 |  | ||||||
|     let pitch_limit = FRAC_PI_2 - 0.01; |  | ||||||
| 
 |  | ||||||
|     camera_settings.orbit_distance = 10.0; |  | ||||||
|     camera_settings.orbit_speed = 1.0; |  | ||||||
|     camera_settings.orthographic_zoom_range = min_zoom..max_zoom; |     camera_settings.orthographic_zoom_range = min_zoom..max_zoom; | ||||||
|     camera_settings.orthographic_zoom_speed = 1.0; |     camera_settings.orthographic_zoom_speed = 1.0; | ||||||
|     camera_settings.perspective_zoom_range = min_fov..max_fov; |     camera_settings.perspective_zoom_range = min_fov..max_fov; | ||||||
|     // Changes in FOV are much more noticeable due to its limited range in radians
 |     // Changes in FOV are much more noticeable due to its limited range in radians
 | ||||||
|     camera_settings.perspective_zoom_speed = 0.05; |     camera_settings.perspective_zoom_speed = 0.05; | ||||||
|     camera_settings.pitch_range = -pitch_limit..pitch_limit; |  | ||||||
| 
 | 
 | ||||||
|     commands.spawn(( |     commands.spawn(( | ||||||
|         Name::new("Camera"), |         Name::new("Camera"), | ||||||
| @ -93,11 +80,13 @@ fn setup( | |||||||
|     )); |     )); | ||||||
| 
 | 
 | ||||||
|     commands.spawn(( |     commands.spawn(( | ||||||
|         Name::new("Cube"), |         Name::new("Fox"), | ||||||
|         PbrBundle { |         SceneBundle { | ||||||
|             mesh: meshes.add(Cuboid::default()), |             scene: asset_server | ||||||
|             material: materials.add(Color::srgb(0.8, 0.7, 0.6)), |                 .load(GltfAssetLabel::Scene(0).from_asset("models/animated/Fox.glb")), | ||||||
|             transform: Transform::from_xyz(1.5, 0.51, 1.5), |             // Note: the scale adjustment is purely an accident of our fox model, which renders
 | ||||||
|  |             // HUGE unless mitigated!
 | ||||||
|  |             transform: Transform::from_translation(Vec3::splat(0.0)).with_scale(Vec3::splat(0.025)), | ||||||
|             ..default() |             ..default() | ||||||
|         }, |         }, | ||||||
|     )); |     )); | ||||||
| @ -132,59 +121,12 @@ fn instructions(mut commands: Commands) { | |||||||
|                 TextStyle::default(), |                 TextStyle::default(), | ||||||
|             )); |             )); | ||||||
|             parent.spawn(TextBundle::from_section( |             parent.spawn(TextBundle::from_section( | ||||||
|                 "W or S: pitch", |                 "Space: switch between orthographic and perspective projections", | ||||||
|                 TextStyle::default(), |  | ||||||
|             )); |  | ||||||
|             parent.spawn(TextBundle::from_section( |  | ||||||
|                 "A or D: yaw", |  | ||||||
|                 TextStyle::default(), |                 TextStyle::default(), | ||||||
|             )); |             )); | ||||||
|         }); |         }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fn orbit( |  | ||||||
|     mut camera: Query<&mut Transform, With<Camera>>, |  | ||||||
|     camera_settings: Res<CameraSettings>, |  | ||||||
|     keyboard_input: Res<ButtonInput<KeyCode>>, |  | ||||||
|     time: Res<Time>, |  | ||||||
| ) { |  | ||||||
|     let mut transform = camera.single_mut(); |  | ||||||
| 
 |  | ||||||
|     let mut delta_pitch = 0.0; |  | ||||||
|     let mut delta_yaw = 0.0; |  | ||||||
| 
 |  | ||||||
|     if keyboard_input.pressed(KeyCode::KeyW) { |  | ||||||
|         delta_pitch += camera_settings.orbit_speed; |  | ||||||
|     } |  | ||||||
|     if keyboard_input.pressed(KeyCode::KeyA) { |  | ||||||
|         delta_yaw -= camera_settings.orbit_speed; |  | ||||||
|     } |  | ||||||
|     if keyboard_input.pressed(KeyCode::KeyS) { |  | ||||||
|         delta_pitch -= camera_settings.orbit_speed; |  | ||||||
|     } |  | ||||||
|     if keyboard_input.pressed(KeyCode::KeyD) { |  | ||||||
|         delta_yaw += camera_settings.orbit_speed; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // Incorporating the delta time between calls prevents this from being framerate-bound.
 |  | ||||||
|     delta_pitch *= time.delta_seconds(); |  | ||||||
|     delta_yaw *= time.delta_seconds(); |  | ||||||
| 
 |  | ||||||
|     // Obtain the existing pitch, yaw, and roll values from the transform.
 |  | ||||||
|     let (yaw, pitch, roll) = transform.rotation.to_euler(EulerRot::YXZ); |  | ||||||
| 
 |  | ||||||
|     // Establish the new yaw and pitch, preventing the pitch value from exceeding our limits.
 |  | ||||||
|     let pitch = (pitch + delta_pitch).clamp( |  | ||||||
|         camera_settings.pitch_range.start, |  | ||||||
|         camera_settings.pitch_range.end, |  | ||||||
|     ); |  | ||||||
|     let yaw = yaw + delta_yaw; |  | ||||||
|     transform.rotation = Quat::from_euler(EulerRot::YXZ, yaw, pitch, roll); |  | ||||||
| 
 |  | ||||||
|     // Adjust the translation to maintain the correct orientation toward the orbit target.
 |  | ||||||
|     transform.translation = Vec3::ZERO - transform.forward() * camera_settings.orbit_distance; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| fn switch_projection( | fn switch_projection( | ||||||
|     mut camera: Query<&mut Projection, With<Camera>>, |     mut camera: Query<&mut Projection, With<Camera>>, | ||||||
|     camera_settings: Res<CameraSettings>, |     camera_settings: Res<CameraSettings>, | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Rich Churcher
						Rich Churcher