Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split zoom/orbit into separate examples #15135

Merged
merged 3 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3251,7 +3251,18 @@ doc-scrape-examples = true

[package.metadata.example.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"
wasm = true

Expand Down
3 changes: 2 additions & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,8 +254,9 @@ Example | Description
Example | Description
--- | ---
[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)
[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

Expand Down
163 changes: 163 additions & 0 deletions examples/camera/camera_orbit.rs
Original file line number Diff line number Diff line change
@@ -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;
}
Comment on lines +123 to +142
Copy link
Member

@janhohenheim janhohenheim Sep 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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;
}
if keyboard_input.pressed(KeyCode::KeyW) {
delta_pitch += 1.0;
}
if keyboard_input.pressed(KeyCode::KeyS) {
delta_pitch -= 1.0;
}
if keyboard_input.pressed(KeyCode::KeyQ) {
delta_roll -= 1.0;
}
if keyboard_input.pressed(KeyCode::KeyE) {
delta_roll += 1.0;
}
if keyboard_input.pressed(KeyCode::KeyA) {
delta_yaw -= 1.0;
}
if keyboard_input.pressed(KeyCode::KeyD) {
delta_yaw += 1.0;
}

See comment below in the code or above (for some reason) when looking at the GitHub discussion


// 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();
Comment on lines +145 to +147
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
delta_pitch *= time.delta_seconds();
delta_roll *= time.delta_seconds();
delta_yaw *= time.delta_seconds();
let speed = camera_settings.orbit_speed * time.delta_seconds();
delta_pitch *= speed;
delta_roll *= speed;
delta_yaw *= speed;

Bit easier to understand where the numbers come from this way :)


// 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;
Comment on lines +161 to +162
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Adjust the translation to maintain the correct orientation toward the orbit target.
transform.translation = Vec3::ZERO - transform.forward() * camera_settings.orbit_distance;
// Adjust the translation to maintain the correct orientation toward the orbit target.
let target = Vec3::ZERO;
transform.translation = target - transform.forward() * camera_settings.orbit_distance;

That should make it a bit more obvious how to customize this code

}
82 changes: 12 additions & 70 deletions examples/camera/projection_zoom.rs
Original file line number Diff line number Diff line change
@@ -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::{
f32::consts::{FRAC_PI_2, PI},
ops::Range,
};
use std::{f32::consts::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<f32>,
// Multiply mouse wheel inputs by this factor
Expand All @@ -20,21 +14,20 @@ struct CameraSettings {
pub perspective_zoom_range: Range<f32>,
// Multiply mouse wheel inputs by this factor
pub perspective_zoom_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, switch_projection, zoom))
.add_systems(Update, (switch_projection, zoom))
.run();
}

/// Set up a simple 3D scene
fn setup(
asset_server: Res<AssetServer>,
mut camera_settings: ResMut<CameraSettings>,
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
Expand All @@ -51,17 +44,11 @@ fn setup(
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"),
Expand Down Expand Up @@ -93,11 +80,13 @@ fn setup(
));

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),
Name::new("Fox"),
SceneBundle {
scene: asset_server
.load(GltfAssetLabel::Scene(0).from_asset("models/animated/Fox.glb")),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL of this syntax 👀 Is this better than assert_server.load("models/animated/Fox.glb#Scene0")?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessarily! Yanked straight from one of our other examples. Not sure if "better", possibly exposes to editor/LSP help? Since it's not just a string...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like it better because it's harder to typo.

// 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()
},
));
Expand Down Expand Up @@ -132,59 +121,12 @@ fn instructions(mut commands: Commands) {
TextStyle::default(),
));
parent.spawn(TextBundle::from_section(
"W or S: pitch",
TextStyle::default(),
));
parent.spawn(TextBundle::from_section(
"A or D: yaw",
"Space: switch between orthographic and perspective projections",
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(
mut camera: Query<&mut Projection, With<Camera>>,
camera_settings: Res<CameraSettings>,
Expand Down