//! Shows how to zoom and orbit orthographic and perspective projection cameras. use std::{ f32::consts::{FRAC_PI_2, PI}, ops::Range, }; use bevy::{input::mouse::AccumulatedMouseScroll, prelude::*, render::camera::ScalingMode}; #[derive(Debug, Default, Resource)] struct CameraSettings { pub orbit_distance: f32, // Multiply keyboard inputs by this factor pub orbit_speed: f32, // Clamp fixed vertical scale to this range pub orthographic_zoom_range: Range, // Multiply mouse wheel inputs by this factor pub orthographic_zoom_speed: f32, // Clamp field of view to this range pub perspective_zoom_range: Range, // Multiply mouse wheel inputs by this factor pub perspective_zoom_speed: f32, // Clamp pitch to this range pub pitch_range: Range, } fn main() { App::new() .add_plugins(DefaultPlugins) .init_resource::() .add_systems(Startup, (setup, instructions)) .add_systems(Update, (orbit, switch_projection, zoom)) .run(); } /// Set up a simple 3D scene fn setup( mut camera_settings: ResMut, mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, ) { // Perspective projections use field of view, expressed in radians. We would // normally not set it to more than π, which represents a 180° FOV. let min_fov = PI / 5.; let max_fov = PI - 0.2; // In orthographic projections, we specify sizes in world units. The below values // are very roughly similar to the above FOV settings, in terms of how "far away" // the subject will appear when used with FixedVertical scaling mode. let min_zoom = 5.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_speed = 1.0; camera_settings.perspective_zoom_range = min_fov..max_fov; // Changes in FOV are much more noticeable due to its limited range in radians camera_settings.perspective_zoom_speed = 0.05; camera_settings.pitch_range = -pitch_limit..pitch_limit; commands.spawn(( Name::new("Camera"), Camera3dBundle { projection: OrthographicProjection { scaling_mode: ScalingMode::FixedVertical( camera_settings.orthographic_zoom_range.start, ), ..OrthographicProjection::default_3d() } .into(), 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( "Scroll mouse wheel to zoom in/out", TextStyle::default(), )); parent.spawn(TextBundle::from_section( "W or S: pitch", TextStyle::default(), )); parent.spawn(TextBundle::from_section( "A or D: yaw", TextStyle::default(), )); }); } fn orbit( mut camera: Query<&mut Transform, With>, camera_settings: Res, keyboard_input: Res>, time: Res