From 1ffd78811062af9d1be1d88e0acb631b05c5711e Mon Sep 17 00:00:00 2001 From: Rich Churcher Date: Sun, 8 Sep 2024 19:20:16 +1200 Subject: [PATCH 01/12] Add examples for orthographic and perspective zoom --- Cargo.toml | 23 ++++++++ examples/3d/orthographic_zoom.rs | 93 ++++++++++++++++++++++++++++++++ examples/3d/perspective_zoom.rs | 92 +++++++++++++++++++++++++++++++ 3 files changed, 208 insertions(+) create mode 100644 examples/3d/orthographic_zoom.rs create mode 100644 examples/3d/perspective_zoom.rs diff --git a/Cargo.toml b/Cargo.toml index 0a0ea25aab5a3..dabd07ee508a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -872,6 +872,17 @@ description = "Shows how to create a 3D orthographic view (for isometric-look in category = "3D Rendering" wasm = true +[[example]] +name = "orthographic_zoom" +path = "examples/3d/orthographic_zoom.rs" +doc-scrape-examples = true + +[package.metadata.example.orthographic_zoom] +name = "Orthographic Zoom" +description = "Shows how to zoom, pan, and rotate an orthographic projection camera." +category = "3D Rendering" +wasm = true + [[example]] name = "parenting" path = "examples/3d/parenting.rs" @@ -883,6 +894,18 @@ description = "Demonstrates parent->child relationships and relative transformat category = "3D Rendering" wasm = true + +[[example]] +name = "perspective_zoom" +path = "examples/3d/perspective_zoom.rs" +doc-scrape-examples = true + +[package.metadata.example.perspective_zoom] +name = "Perspective Zoom" +description = "Shows how to zoom, pan, and rotate a perspective projection camera." +category = "3D Rendering" +wasm = true + [[example]] name = "pbr" path = "examples/3d/pbr.rs" diff --git a/examples/3d/orthographic_zoom.rs b/examples/3d/orthographic_zoom.rs new file mode 100644 index 0000000000000..5d8a43d440bff --- /dev/null +++ b/examples/3d/orthographic_zoom.rs @@ -0,0 +1,93 @@ +//! Shows how to zoom and orbit an orthographic projection camera. + +use std::ops::Range; + +use bevy::{input::mouse::MouseWheel, prelude::*, render::camera::ScalingMode}; + +// Multiply mouse movements by these factors +const CAMERA_ORBIT_SPEED: f32 = 0.02; +const CAMERA_ZOOM_SPEED: f32 = 1.0; + +// Clamp fixed vertical scale to this range +const CAMERA_ZOOM_RANGE: Range = 5.0..50.0; + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_systems(Startup, setup) + .add_systems(Update, camera_controls) + .run(); +} + +/// Set up a simple 3D scene +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + // Camera3dBundle defaults to using a PerspectiveProjection + commands.spawn(Camera3dBundle { + transform: Transform::from_xyz(5.0, 5.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y), + ..default() + }); + + // Plane + commands.spawn(PbrBundle { + mesh: meshes.add(Plane3d::default().mesh().size(5.0, 5.0)), + material: materials.add(Color::srgb(0.3, 0.5, 0.3)), + ..default() + }); + + // Cube + commands.spawn(PbrBundle { + mesh: meshes.add(Cuboid::default()), + material: materials.add(Color::srgb(0.8, 0.7, 0.6)), + transform: Transform::from_xyz(1.5, 0.5, 1.5), + ..default() + }); + + // Light + commands.spawn(PointLightBundle { + transform: Transform::from_xyz(3.0, 8.0, 5.0), + ..default() + }); +} + +fn camera_controls( + mut camera: Query<(&mut Projection, &mut Transform), With>, + keyboard_input: Res>, + mut mouse_wheel_input: EventReader, +) { + let mut delta_orbit = 0.0; + if keyboard_input.pressed(KeyCode::KeyA) { + delta_orbit -= CAMERA_ORBIT_SPEED; + } + if keyboard_input.pressed(KeyCode::KeyD) { + delta_orbit += CAMERA_ORBIT_SPEED; + } + + let (mut projection, mut transform) = camera.single_mut(); + + if delta_orbit != 0.0 { + transform.translate_around(Vec3::ZERO, Quat::from_axis_angle(Vec3::Y, delta_orbit)); + transform.look_at(Vec3::ZERO, Vec3::Y); + } + + // Accumulate mouse wheel inputs for this tick + // TODO: going away in 0.15 with AccumulatedMouseScroll + let delta_zoom: f32 = mouse_wheel_input.read().map(|e| e.y).sum(); + if delta_zoom != 0.0 { + return; + } + + if let Projection::Orthographic(orthographic) = &mut *projection { + // Get the current scaling_mode value to allow clamping the new value to our zoom range. + let ScalingMode::FixedVertical(current) = orthographic.scaling_mode else { + return; + }; + // Set a new ScalingMode, clamped to a limited range. + let zoom_level = (current + CAMERA_ZOOM_SPEED * delta_zoom) + .clamp(CAMERA_ZOOM_RANGE.start, CAMERA_ZOOM_RANGE.end); + orthographic.scaling_mode = ScalingMode::FixedVertical(zoom_level); + } +} diff --git a/examples/3d/perspective_zoom.rs b/examples/3d/perspective_zoom.rs new file mode 100644 index 0000000000000..871a34a35db31 --- /dev/null +++ b/examples/3d/perspective_zoom.rs @@ -0,0 +1,92 @@ +//! Shows how to zoom and orbit a perspective projection camera. + +use std::ops::Range; + +use bevy::{input::mouse::MouseWheel, prelude::*}; + +// Multiply mouse movements by these factors +const CAMERA_ORBIT_SPEED: f32 = 0.02; +const CAMERA_ZOOM_SPEED: f32 = 0.5; + +// Clamp FOV to this range. Note that we can't adjust FOV to more than PI, which represents +// a 180 degree field. +const CAMERA_ZOOM_RANGE: Range = 0.5..3.0; + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_systems(Startup, setup) + .add_systems(Update, camera_controls) + .run(); +} + +/// Set up a simple 3D scene +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + // Camera3dBundle defaults to using a PerspectiveProjection + commands.spawn(Camera3dBundle { + transform: Transform::from_xyz(5.0, 5.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y), + ..default() + }); + + // Plane + commands.spawn(PbrBundle { + mesh: meshes.add(Plane3d::default().mesh().size(5.0, 5.0)), + material: materials.add(Color::srgb(0.3, 0.5, 0.3)), + ..default() + }); + + // Cube + commands.spawn(PbrBundle { + mesh: meshes.add(Cuboid::default()), + material: materials.add(Color::srgb(0.8, 0.7, 0.6)), + transform: Transform::from_xyz(1.5, 0.5, 1.5), + ..default() + }); + + // Light + commands.spawn(PointLightBundle { + transform: Transform::from_xyz(3.0, 8.0, 5.0), + ..default() + }); +} + +fn camera_controls( + mut camera: Query<(&mut Projection, &mut Transform), With>, + keyboard_input: Res>, + mut mouse_wheel_input: EventReader, +) { + let mut delta_orbit = 0.0; + if keyboard_input.pressed(KeyCode::KeyA) { + // Orbit left + delta_orbit -= CAMERA_ORBIT_SPEED; + } + if keyboard_input.pressed(KeyCode::KeyD) { + // Orbit right + delta_orbit += CAMERA_ORBIT_SPEED; + } + + let (mut projection, mut transform) = camera.single_mut(); + + if delta_orbit != 0.0 { + transform.translate_around(Vec3::ZERO, Quat::from_axis_angle(Vec3::Y, delta_orbit)); + transform.look_at(Vec3::ZERO, Vec3::Y); + } + + // Accumulate mouse wheel inputs for this tick + // TODO: going away in 0.15 with AccumulatedMouseScroll + let delta_zoom: f32 = mouse_wheel_input.read().map(|e| e.y).sum(); + if delta_zoom == 0.0 { + return; + } + + if let Projection::Perspective(perspective) = &mut *projection { + // Adjust the field of view, but keep it within our stated range + perspective.fov = (perspective.fov + CAMERA_ZOOM_SPEED * delta_zoom) + .clamp(CAMERA_ZOOM_RANGE.start, CAMERA_ZOOM_RANGE.end); + dbg!(perspective.fov); + } +} From 770b70fb023f577d7b994bf549627f13d378cb46 Mon Sep 17 00:00:00 2001 From: Rich Churcher Date: Sun, 8 Sep 2024 19:29:02 +1200 Subject: [PATCH 02/12] Fix descriptions --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index dabd07ee508a6..08540f6d2a3ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -879,7 +879,7 @@ doc-scrape-examples = true [package.metadata.example.orthographic_zoom] name = "Orthographic Zoom" -description = "Shows how to zoom, pan, and rotate an orthographic projection camera." +description = "Shows how to zoom and orbit an orthographic projection camera." category = "3D Rendering" wasm = true @@ -902,7 +902,7 @@ doc-scrape-examples = true [package.metadata.example.perspective_zoom] name = "Perspective Zoom" -description = "Shows how to zoom, pan, and rotate a perspective projection camera." +description = "Shows how to zoom and orbit a perspective projection camera." category = "3D Rendering" wasm = true From 6d3a8ac1b816ad98f787e9c45ee44d9e2fdcc004 Mon Sep 17 00:00:00 2001 From: Rich Churcher Date: Sun, 8 Sep 2024 19:31:25 +1200 Subject: [PATCH 03/12] Update example list --- examples/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/README.md b/examples/README.md index e2e3bbf426d40..48776abe81069 100644 --- a/examples/README.md +++ b/examples/README.md @@ -154,8 +154,10 @@ Example | Description [Meshlet](../examples/3d/meshlet.rs) | Meshlet rendering for dense high-poly scenes (experimental) [Motion Blur](../examples/3d/motion_blur.rs) | Demonstrates per-pixel motion blur [Orthographic View](../examples/3d/orthographic.rs) | Shows how to create a 3D orthographic view (for isometric-look in games or CAD applications) +[Orthographic Zoom](../examples/3d/orthographic_zoom.rs) | Shows how to zoom and orbit an orthographic projection camera. [Parallax Mapping](../examples/3d/parallax_mapping.rs) | Demonstrates use of a normal map and depth map for parallax mapping [Parenting](../examples/3d/parenting.rs) | Demonstrates parent->child relationships and relative transformations +[Perspective Zoom](../examples/3d/perspective_zoom.rs) | Shows how to zoom and orbit a perspective projection camera. [Physically Based Rendering](../examples/3d/pbr.rs) | Demonstrates use of Physically Based Rendering (PBR) properties [Reflection Probes](../examples/3d/reflection_probes.rs) | Demonstrates reflection probes [Render to Texture](../examples/3d/render_to_texture.rs) | Shows how to render to a texture, useful for mirrors, UI, or exporting images From 6e59e7616344767b40f0840c750dcfa26d43cfaf Mon Sep 17 00:00:00 2001 From: Rich Churcher Date: Sun, 8 Sep 2024 20:01:11 +1200 Subject: [PATCH 04/12] Add instructions --- examples/3d/orthographic_zoom.rs | 11 +++++++++-- examples/3d/perspective_zoom.rs | 4 +++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/examples/3d/orthographic_zoom.rs b/examples/3d/orthographic_zoom.rs index 5d8a43d440bff..07092a282054e 100644 --- a/examples/3d/orthographic_zoom.rs +++ b/examples/3d/orthographic_zoom.rs @@ -25,8 +25,15 @@ fn setup( mut meshes: ResMut>, mut materials: ResMut>, ) { - // Camera3dBundle defaults to using a PerspectiveProjection + // Find the middle of the zoom range + let initial_scale = (CAMERA_ZOOM_RANGE.end - CAMERA_ZOOM_RANGE.start) / 2.0; + commands.spawn(Camera3dBundle { + projection: OrthographicProjection { + scaling_mode: ScalingMode::FixedVertical(initial_scale), + ..default() + } + .into(), transform: Transform::from_xyz(5.0, 5.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y), ..default() }); @@ -76,7 +83,7 @@ fn camera_controls( // Accumulate mouse wheel inputs for this tick // TODO: going away in 0.15 with AccumulatedMouseScroll let delta_zoom: f32 = mouse_wheel_input.read().map(|e| e.y).sum(); - if delta_zoom != 0.0 { + if delta_zoom == 0.0 { return; } diff --git a/examples/3d/perspective_zoom.rs b/examples/3d/perspective_zoom.rs index 871a34a35db31..75caa82c71d40 100644 --- a/examples/3d/perspective_zoom.rs +++ b/examples/3d/perspective_zoom.rs @@ -52,6 +52,9 @@ fn setup( transform: Transform::from_xyz(3.0, 8.0, 5.0), ..default() }); + + info!("Zoom in and out with mouse wheel."); + info!("Orbit camera with A and D."); } fn camera_controls( @@ -87,6 +90,5 @@ fn camera_controls( // Adjust the field of view, but keep it within our stated range perspective.fov = (perspective.fov + CAMERA_ZOOM_SPEED * delta_zoom) .clamp(CAMERA_ZOOM_RANGE.start, CAMERA_ZOOM_RANGE.end); - dbg!(perspective.fov); } } From 5412d17880e807403e2f521c0a952b1d4948c9c9 Mon Sep 17 00:00:00 2001 From: Rich Churcher Date: Sun, 8 Sep 2024 20:05:14 +1200 Subject: [PATCH 05/12] Pad out the comments --- examples/3d/orthographic_zoom.rs | 6 ++++++ examples/3d/perspective_zoom.rs | 1 + 2 files changed, 7 insertions(+) diff --git a/examples/3d/orthographic_zoom.rs b/examples/3d/orthographic_zoom.rs index 07092a282054e..834ff88ae8c84 100644 --- a/examples/3d/orthographic_zoom.rs +++ b/examples/3d/orthographic_zoom.rs @@ -58,6 +58,9 @@ fn setup( transform: Transform::from_xyz(3.0, 8.0, 5.0), ..default() }); + + info!("Zoom in and out with mouse wheel."); + info!("Orbit camera with A and D."); } fn camera_controls( @@ -67,15 +70,18 @@ fn camera_controls( ) { let mut delta_orbit = 0.0; if keyboard_input.pressed(KeyCode::KeyA) { + // Orbit left delta_orbit -= CAMERA_ORBIT_SPEED; } if keyboard_input.pressed(KeyCode::KeyD) { + // Orbit right delta_orbit += CAMERA_ORBIT_SPEED; } let (mut projection, mut transform) = camera.single_mut(); if delta_orbit != 0.0 { + // Orbit the camera around a fixed point, facing its center. transform.translate_around(Vec3::ZERO, Quat::from_axis_angle(Vec3::Y, delta_orbit)); transform.look_at(Vec3::ZERO, Vec3::Y); } diff --git a/examples/3d/perspective_zoom.rs b/examples/3d/perspective_zoom.rs index 75caa82c71d40..9c5091f3465c8 100644 --- a/examples/3d/perspective_zoom.rs +++ b/examples/3d/perspective_zoom.rs @@ -75,6 +75,7 @@ fn camera_controls( let (mut projection, mut transform) = camera.single_mut(); if delta_orbit != 0.0 { + // Orbit the camera around a fixed point, facing its center. transform.translate_around(Vec3::ZERO, Quat::from_axis_angle(Vec3::Y, delta_orbit)); transform.look_at(Vec3::ZERO, Vec3::Y); } From c3f4e1c814a3ddff92751cc98368fbaaa0861568 Mon Sep 17 00:00:00 2001 From: Rich Churcher Date: Mon, 9 Sep 2024 13:22:21 +1200 Subject: [PATCH 06/12] Address PR feedback --- examples/3d/orthographic_zoom.rs | 92 +++++++++++++++++++++----------- examples/3d/perspective_zoom.rs | 82 +++++++++++++++++++--------- 2 files changed, 117 insertions(+), 57 deletions(-) diff --git a/examples/3d/orthographic_zoom.rs b/examples/3d/orthographic_zoom.rs index 834ff88ae8c84..8e4c9f5fba524 100644 --- a/examples/3d/orthographic_zoom.rs +++ b/examples/3d/orthographic_zoom.rs @@ -2,31 +2,41 @@ use std::ops::Range; -use bevy::{input::mouse::MouseWheel, prelude::*, render::camera::ScalingMode}; - -// Multiply mouse movements by these factors -const CAMERA_ORBIT_SPEED: f32 = 0.02; -const CAMERA_ZOOM_SPEED: f32 = 1.0; - -// Clamp fixed vertical scale to this range -const CAMERA_ZOOM_RANGE: Range = 5.0..50.0; +use bevy::{input::mouse::AccumulatedMouseScroll, prelude::*, render::camera::ScalingMode}; + +#[derive(Debug, Default, Reflect, Resource)] +struct CameraSettings { + // Multiply keyboard inputs by this factor + pub orbit_speed: f32, + // Clamp fixed vertical scale to this range + pub zoom_range: Range, + // Multiply mouse wheel movements by this factor + pub zoom_speed: f32, +} fn main() { App::new() .add_plugins(DefaultPlugins) - .add_systems(Startup, setup) + .init_resource::() + .add_systems(Startup, (setup, instructions)) .add_systems(Update, camera_controls) + .register_type::() .run(); } /// Set up a simple 3D scene fn setup( + mut camera_settings: ResMut, mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, ) { + camera_settings.orbit_speed = 0.02; + camera_settings.zoom_range = 5.0..50.0; + camera_settings.zoom_speed = 1.0; + // Find the middle of the zoom range - let initial_scale = (CAMERA_ZOOM_RANGE.end - CAMERA_ZOOM_RANGE.start) / 2.0; + let initial_scale = (camera_settings.zoom_range.start + camera_settings.zoom_range.end) / 2.0; commands.spawn(Camera3dBundle { projection: OrthographicProjection { @@ -58,24 +68,46 @@ fn setup( transform: Transform::from_xyz(3.0, 8.0, 5.0), ..default() }); +} - info!("Zoom in and out with mouse wheel."); - info!("Orbit camera with A and D."); +fn instructions(mut commands: Commands) { + commands + .spawn(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( + "A or D to orbit left or right", + TextStyle::default(), + )); + }); } fn camera_controls( mut camera: Query<(&mut Projection, &mut Transform), With>, + camera_settings: Res, keyboard_input: Res>, - mut mouse_wheel_input: EventReader, + mouse_wheel_input: Res, ) { let mut delta_orbit = 0.0; if keyboard_input.pressed(KeyCode::KeyA) { // Orbit left - delta_orbit -= CAMERA_ORBIT_SPEED; + delta_orbit -= camera_settings.orbit_speed; } if keyboard_input.pressed(KeyCode::KeyD) { // Orbit right - delta_orbit += CAMERA_ORBIT_SPEED; + delta_orbit += camera_settings.orbit_speed; } let (mut projection, mut transform) = camera.single_mut(); @@ -86,21 +118,19 @@ fn camera_controls( transform.look_at(Vec3::ZERO, Vec3::Y); } - // Accumulate mouse wheel inputs for this tick - // TODO: going away in 0.15 with AccumulatedMouseScroll - let delta_zoom: f32 = mouse_wheel_input.read().map(|e| e.y).sum(); - if delta_zoom == 0.0 { + let Projection::Orthographic(orthographic) = &mut *projection else { + panic!( + "This kind of scaling only works with cameras which have an orthographic projection." + ); + }; + // Get the current scaling_mode value to allow clamping the new value to our zoom range. + let ScalingMode::FixedVertical(current) = orthographic.scaling_mode else { return; - } - - if let Projection::Orthographic(orthographic) = &mut *projection { - // Get the current scaling_mode value to allow clamping the new value to our zoom range. - let ScalingMode::FixedVertical(current) = orthographic.scaling_mode else { - return; - }; - // Set a new ScalingMode, clamped to a limited range. - let zoom_level = (current + CAMERA_ZOOM_SPEED * delta_zoom) - .clamp(CAMERA_ZOOM_RANGE.start, CAMERA_ZOOM_RANGE.end); - orthographic.scaling_mode = ScalingMode::FixedVertical(zoom_level); - } + }; + // Set a new ScalingMode, clamped to a limited range. + let zoom_level = (current + camera_settings.zoom_speed * mouse_wheel_input.delta.y).clamp( + camera_settings.zoom_range.start, + camera_settings.zoom_range.end, + ); + orthographic.scaling_mode = ScalingMode::FixedVertical(zoom_level); } diff --git a/examples/3d/perspective_zoom.rs b/examples/3d/perspective_zoom.rs index 9c5091f3465c8..0a1825b849eeb 100644 --- a/examples/3d/perspective_zoom.rs +++ b/examples/3d/perspective_zoom.rs @@ -2,31 +2,41 @@ use std::ops::Range; -use bevy::{input::mouse::MouseWheel, prelude::*}; +use bevy::{input::mouse::AccumulatedMouseScroll, prelude::*}; -// Multiply mouse movements by these factors -const CAMERA_ORBIT_SPEED: f32 = 0.02; -const CAMERA_ZOOM_SPEED: f32 = 0.5; - -// Clamp FOV to this range. Note that we can't adjust FOV to more than PI, which represents -// a 180 degree field. -const CAMERA_ZOOM_RANGE: Range = 0.5..3.0; +#[derive(Debug, Default, Reflect, Resource)] +struct CameraSettings { + // Multiply keyboard inputs by this factor + pub orbit_speed: f32, + // Clamp fixed vertical scale to this range + pub zoom_range: Range, + // Multiply mouse wheel movements by this factor + pub zoom_speed: f32, +} fn main() { App::new() .add_plugins(DefaultPlugins) - .add_systems(Startup, setup) + .init_resource::() + .add_systems(Startup, (setup, instructions)) .add_systems(Update, camera_controls) + .register_type::() .run(); } /// Set up a simple 3D scene fn setup( + mut camera_settings: ResMut, mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, ) { - // Camera3dBundle defaults to using a PerspectiveProjection + camera_settings.orbit_speed = 0.02; + // Clamp FOV to this range. Note that we can't adjust FOV to more than PI, + // which represents a 180 degree field. + camera_settings.zoom_range = 0.5..3.0; + camera_settings.zoom_speed = 1.0; + commands.spawn(Camera3dBundle { transform: Transform::from_xyz(5.0, 5.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y), ..default() @@ -52,24 +62,46 @@ fn setup( transform: Transform::from_xyz(3.0, 8.0, 5.0), ..default() }); +} - info!("Zoom in and out with mouse wheel."); - info!("Orbit camera with A and D."); +fn instructions(mut commands: Commands) { + commands + .spawn(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( + "A or D to orbit left or right", + TextStyle::default(), + )); + }); } fn camera_controls( mut camera: Query<(&mut Projection, &mut Transform), With>, + camera_settings: Res, keyboard_input: Res>, - mut mouse_wheel_input: EventReader, + mouse_wheel_input: Res, ) { let mut delta_orbit = 0.0; if keyboard_input.pressed(KeyCode::KeyA) { // Orbit left - delta_orbit -= CAMERA_ORBIT_SPEED; + delta_orbit -= camera_settings.orbit_speed; } if keyboard_input.pressed(KeyCode::KeyD) { // Orbit right - delta_orbit += CAMERA_ORBIT_SPEED; + delta_orbit += camera_settings.orbit_speed; } let (mut projection, mut transform) = camera.single_mut(); @@ -80,16 +112,14 @@ fn camera_controls( transform.look_at(Vec3::ZERO, Vec3::Y); } - // Accumulate mouse wheel inputs for this tick - // TODO: going away in 0.15 with AccumulatedMouseScroll - let delta_zoom: f32 = mouse_wheel_input.read().map(|e| e.y).sum(); - if delta_zoom == 0.0 { - return; - } + let Projection::Perspective(perspective) = &mut *projection else { + panic!("This kind of scaling only works with cameras which have a perspective projection."); + }; - if let Projection::Perspective(perspective) = &mut *projection { - // Adjust the field of view, but keep it within our stated range - perspective.fov = (perspective.fov + CAMERA_ZOOM_SPEED * delta_zoom) - .clamp(CAMERA_ZOOM_RANGE.start, CAMERA_ZOOM_RANGE.end); - } + // Adjust the field of view, but keep it within our stated range + perspective.fov = (perspective.fov + camera_settings.zoom_speed * mouse_wheel_input.delta.y) + .clamp( + camera_settings.zoom_range.start, + camera_settings.zoom_range.end, + ); } From 90bf07b1c707b4c9f20c5f0e4d397c8d8d6f78a4 Mon Sep 17 00:00:00 2001 From: Rich Churcher Date: Mon, 9 Sep 2024 22:53:06 +1200 Subject: [PATCH 07/12] Apply (some of) PR feedback --- Cargo.toml | 35 ++-- examples/3d/orthographic_zoom.rs | 136 ---------------- examples/3d/perspective_zoom.rs | 125 --------------- examples/README.md | 3 +- examples/camera/projection_zoom.rs | 247 +++++++++++++++++++++++++++++ 5 files changed, 260 insertions(+), 286 deletions(-) delete mode 100644 examples/3d/orthographic_zoom.rs delete mode 100644 examples/3d/perspective_zoom.rs create mode 100644 examples/camera/projection_zoom.rs diff --git a/Cargo.toml b/Cargo.toml index 08540f6d2a3ad..46e126bc9de6e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -872,17 +872,6 @@ description = "Shows how to create a 3D orthographic view (for isometric-look in category = "3D Rendering" wasm = true -[[example]] -name = "orthographic_zoom" -path = "examples/3d/orthographic_zoom.rs" -doc-scrape-examples = true - -[package.metadata.example.orthographic_zoom] -name = "Orthographic Zoom" -description = "Shows how to zoom and orbit an orthographic projection camera." -category = "3D Rendering" -wasm = true - [[example]] name = "parenting" path = "examples/3d/parenting.rs" @@ -894,18 +883,6 @@ description = "Demonstrates parent->child relationships and relative transformat category = "3D Rendering" wasm = true - -[[example]] -name = "perspective_zoom" -path = "examples/3d/perspective_zoom.rs" -doc-scrape-examples = true - -[package.metadata.example.perspective_zoom] -name = "Perspective Zoom" -description = "Shows how to zoom and orbit a perspective projection camera." -category = "3D Rendering" -wasm = true - [[example]] name = "pbr" path = "examples/3d/pbr.rs" @@ -3267,6 +3244,18 @@ description = "A first-person camera that uses a world model and a view model wi category = "Camera" wasm = true +[[example]] +name = "projection_zoom" +path = "examples/camera/projection_zoom.rs" +doc-scrape-examples = true + +[package.metadata.example.projection_zoom] +name = "Orthographic Zoom" +description = "Shows how to zoom and orbit orthographic and perspective projection cameras." +category = "Camera" +wasm = true + + [package.metadata.example.fps_overlay] name = "FPS overlay" description = "Demonstrates FPS overlay" diff --git a/examples/3d/orthographic_zoom.rs b/examples/3d/orthographic_zoom.rs deleted file mode 100644 index 8e4c9f5fba524..0000000000000 --- a/examples/3d/orthographic_zoom.rs +++ /dev/null @@ -1,136 +0,0 @@ -//! Shows how to zoom and orbit an orthographic projection camera. - -use std::ops::Range; - -use bevy::{input::mouse::AccumulatedMouseScroll, prelude::*, render::camera::ScalingMode}; - -#[derive(Debug, Default, Reflect, Resource)] -struct CameraSettings { - // Multiply keyboard inputs by this factor - pub orbit_speed: f32, - // Clamp fixed vertical scale to this range - pub zoom_range: Range, - // Multiply mouse wheel movements by this factor - pub zoom_speed: f32, -} - -fn main() { - App::new() - .add_plugins(DefaultPlugins) - .init_resource::() - .add_systems(Startup, (setup, instructions)) - .add_systems(Update, camera_controls) - .register_type::() - .run(); -} - -/// Set up a simple 3D scene -fn setup( - mut camera_settings: ResMut, - mut commands: Commands, - mut meshes: ResMut>, - mut materials: ResMut>, -) { - camera_settings.orbit_speed = 0.02; - camera_settings.zoom_range = 5.0..50.0; - camera_settings.zoom_speed = 1.0; - - // Find the middle of the zoom range - let initial_scale = (camera_settings.zoom_range.start + camera_settings.zoom_range.end) / 2.0; - - commands.spawn(Camera3dBundle { - projection: OrthographicProjection { - scaling_mode: ScalingMode::FixedVertical(initial_scale), - ..default() - } - .into(), - transform: Transform::from_xyz(5.0, 5.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y), - ..default() - }); - - // Plane - commands.spawn(PbrBundle { - mesh: meshes.add(Plane3d::default().mesh().size(5.0, 5.0)), - material: materials.add(Color::srgb(0.3, 0.5, 0.3)), - ..default() - }); - - // Cube - commands.spawn(PbrBundle { - mesh: meshes.add(Cuboid::default()), - material: materials.add(Color::srgb(0.8, 0.7, 0.6)), - transform: Transform::from_xyz(1.5, 0.5, 1.5), - ..default() - }); - - // Light - commands.spawn(PointLightBundle { - transform: Transform::from_xyz(3.0, 8.0, 5.0), - ..default() - }); -} - -fn instructions(mut commands: Commands) { - commands - .spawn(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( - "A or D to orbit left or right", - TextStyle::default(), - )); - }); -} - -fn camera_controls( - mut camera: Query<(&mut Projection, &mut Transform), With>, - camera_settings: Res, - keyboard_input: Res>, - mouse_wheel_input: Res, -) { - let mut delta_orbit = 0.0; - if keyboard_input.pressed(KeyCode::KeyA) { - // Orbit left - delta_orbit -= camera_settings.orbit_speed; - } - if keyboard_input.pressed(KeyCode::KeyD) { - // Orbit right - delta_orbit += camera_settings.orbit_speed; - } - - let (mut projection, mut transform) = camera.single_mut(); - - if delta_orbit != 0.0 { - // Orbit the camera around a fixed point, facing its center. - transform.translate_around(Vec3::ZERO, Quat::from_axis_angle(Vec3::Y, delta_orbit)); - transform.look_at(Vec3::ZERO, Vec3::Y); - } - - let Projection::Orthographic(orthographic) = &mut *projection else { - panic!( - "This kind of scaling only works with cameras which have an orthographic projection." - ); - }; - // Get the current scaling_mode value to allow clamping the new value to our zoom range. - let ScalingMode::FixedVertical(current) = orthographic.scaling_mode else { - return; - }; - // Set a new ScalingMode, clamped to a limited range. - let zoom_level = (current + camera_settings.zoom_speed * mouse_wheel_input.delta.y).clamp( - camera_settings.zoom_range.start, - camera_settings.zoom_range.end, - ); - orthographic.scaling_mode = ScalingMode::FixedVertical(zoom_level); -} diff --git a/examples/3d/perspective_zoom.rs b/examples/3d/perspective_zoom.rs deleted file mode 100644 index 0a1825b849eeb..0000000000000 --- a/examples/3d/perspective_zoom.rs +++ /dev/null @@ -1,125 +0,0 @@ -//! Shows how to zoom and orbit a perspective projection camera. - -use std::ops::Range; - -use bevy::{input::mouse::AccumulatedMouseScroll, prelude::*}; - -#[derive(Debug, Default, Reflect, Resource)] -struct CameraSettings { - // Multiply keyboard inputs by this factor - pub orbit_speed: f32, - // Clamp fixed vertical scale to this range - pub zoom_range: Range, - // Multiply mouse wheel movements by this factor - pub zoom_speed: f32, -} - -fn main() { - App::new() - .add_plugins(DefaultPlugins) - .init_resource::() - .add_systems(Startup, (setup, instructions)) - .add_systems(Update, camera_controls) - .register_type::() - .run(); -} - -/// Set up a simple 3D scene -fn setup( - mut camera_settings: ResMut, - mut commands: Commands, - mut meshes: ResMut>, - mut materials: ResMut>, -) { - camera_settings.orbit_speed = 0.02; - // Clamp FOV to this range. Note that we can't adjust FOV to more than PI, - // which represents a 180 degree field. - camera_settings.zoom_range = 0.5..3.0; - camera_settings.zoom_speed = 1.0; - - commands.spawn(Camera3dBundle { - transform: Transform::from_xyz(5.0, 5.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y), - ..default() - }); - - // Plane - commands.spawn(PbrBundle { - mesh: meshes.add(Plane3d::default().mesh().size(5.0, 5.0)), - material: materials.add(Color::srgb(0.3, 0.5, 0.3)), - ..default() - }); - - // Cube - commands.spawn(PbrBundle { - mesh: meshes.add(Cuboid::default()), - material: materials.add(Color::srgb(0.8, 0.7, 0.6)), - transform: Transform::from_xyz(1.5, 0.5, 1.5), - ..default() - }); - - // Light - commands.spawn(PointLightBundle { - transform: Transform::from_xyz(3.0, 8.0, 5.0), - ..default() - }); -} - -fn instructions(mut commands: Commands) { - commands - .spawn(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( - "A or D to orbit left or right", - TextStyle::default(), - )); - }); -} - -fn camera_controls( - mut camera: Query<(&mut Projection, &mut Transform), With>, - camera_settings: Res, - keyboard_input: Res>, - mouse_wheel_input: Res, -) { - let mut delta_orbit = 0.0; - if keyboard_input.pressed(KeyCode::KeyA) { - // Orbit left - delta_orbit -= camera_settings.orbit_speed; - } - if keyboard_input.pressed(KeyCode::KeyD) { - // Orbit right - delta_orbit += camera_settings.orbit_speed; - } - - let (mut projection, mut transform) = camera.single_mut(); - - if delta_orbit != 0.0 { - // Orbit the camera around a fixed point, facing its center. - transform.translate_around(Vec3::ZERO, Quat::from_axis_angle(Vec3::Y, delta_orbit)); - transform.look_at(Vec3::ZERO, Vec3::Y); - } - - let Projection::Perspective(perspective) = &mut *projection else { - panic!("This kind of scaling only works with cameras which have a perspective projection."); - }; - - // Adjust the field of view, but keep it within our stated range - perspective.fov = (perspective.fov + camera_settings.zoom_speed * mouse_wheel_input.delta.y) - .clamp( - camera_settings.zoom_range.start, - camera_settings.zoom_range.end, - ); -} diff --git a/examples/README.md b/examples/README.md index 48776abe81069..194d70190af23 100644 --- a/examples/README.md +++ b/examples/README.md @@ -154,10 +154,8 @@ Example | Description [Meshlet](../examples/3d/meshlet.rs) | Meshlet rendering for dense high-poly scenes (experimental) [Motion Blur](../examples/3d/motion_blur.rs) | Demonstrates per-pixel motion blur [Orthographic View](../examples/3d/orthographic.rs) | Shows how to create a 3D orthographic view (for isometric-look in games or CAD applications) -[Orthographic Zoom](../examples/3d/orthographic_zoom.rs) | Shows how to zoom and orbit an orthographic projection camera. [Parallax Mapping](../examples/3d/parallax_mapping.rs) | Demonstrates use of a normal map and depth map for parallax mapping [Parenting](../examples/3d/parenting.rs) | Demonstrates parent->child relationships and relative transformations -[Perspective Zoom](../examples/3d/perspective_zoom.rs) | Shows how to zoom and orbit a perspective projection camera. [Physically Based Rendering](../examples/3d/pbr.rs) | Demonstrates use of Physically Based Rendering (PBR) properties [Reflection Probes](../examples/3d/reflection_probes.rs) | Demonstrates reflection probes [Render to Texture](../examples/3d/render_to_texture.rs) | Shows how to render to a texture, useful for mirrors, UI, or exporting images @@ -257,6 +255,7 @@ Example | Description --- | --- [2D top-down camera](../examples/camera/2d_top_down_camera.rs) | A 2D top-down camera smoothly following player movements [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) +[Orthographic Zoom](../examples/camera/projection_zoom.rs) | Shows how to zoom and orbit orthographic and perspective projection cameras. ## Dev tools diff --git a/examples/camera/projection_zoom.rs b/examples/camera/projection_zoom.rs new file mode 100644 index 0000000000000..431921e2db30b --- /dev/null +++ b/examples/camera/projection_zoom.rs @@ -0,0 +1,247 @@ +//! 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 PI, 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, + ), + ..default() + } + .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