projection_zoom/
projection_zoom.rs1use std::{f32::consts::PI, ops::Range};
4
5use bevy::{camera::ScalingMode, input::mouse::AccumulatedMouseScroll, prelude::*};
6
7#[derive(Debug, Resource)]
8struct CameraSettings {
9 pub orthographic_viewport_height: f32,
11 pub orthographic_zoom_range: Range<f32>,
13 pub orthographic_zoom_speed: f32,
15 pub perspective_zoom_range: Range<f32>,
17 pub perspective_zoom_speed: f32,
19}
20
21fn main() {
22 App::new()
23 .add_plugins(DefaultPlugins)
24 .insert_resource(CameraSettings {
25 orthographic_viewport_height: 5.,
26 orthographic_zoom_range: 0.1..10.0,
29 orthographic_zoom_speed: 0.2,
31 perspective_zoom_range: (PI / 5.)..(PI - 0.2),
34 perspective_zoom_speed: 0.05,
36 })
37 .add_systems(Startup, (setup, instructions))
38 .add_systems(Update, (switch_projection, zoom))
39 .run();
40}
41
42fn setup(
44 asset_server: Res<AssetServer>,
45 camera_settings: Res<CameraSettings>,
46 mut commands: Commands,
47 mut meshes: ResMut<Assets<Mesh>>,
48 mut materials: ResMut<Assets<StandardMaterial>>,
49) {
50 commands.spawn((
51 Name::new("Camera"),
52 Camera3d::default(),
53 Projection::from(OrthographicProjection {
54 scaling_mode: ScalingMode::FixedVertical {
57 viewport_height: camera_settings.orthographic_viewport_height,
58 },
59 scale: 1.,
62 ..OrthographicProjection::default_3d()
63 }),
64 Transform::from_xyz(5.0, 5.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
65 ));
66
67 commands.spawn((
68 Name::new("Plane"),
69 Mesh3d(meshes.add(Plane3d::default().mesh().size(5.0, 5.0))),
70 MeshMaterial3d(materials.add(StandardMaterial {
71 base_color: Color::srgb(0.3, 0.5, 0.3),
72 cull_mode: None,
74 ..default()
75 })),
76 ));
77
78 commands.spawn((
79 Name::new("Fox"),
80 SceneRoot(
81 asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/animated/Fox.glb")),
82 ),
83 Transform::from_translation(Vec3::splat(0.0)).with_scale(Vec3::splat(0.025)),
86 ));
87
88 commands.spawn((
89 Name::new("Light"),
90 PointLight::default(),
91 Transform::from_xyz(3.0, 8.0, 5.0),
92 ));
93}
94
95fn instructions(mut commands: Commands) {
96 commands.spawn((
97 Name::new("Instructions"),
98 Text::new(
99 "Scroll mouse wheel to zoom in/out\n\
100 Space: switch between orthographic and perspective projections",
101 ),
102 Node {
103 position_type: PositionType::Absolute,
104 top: px(12),
105 left: px(12),
106 ..default()
107 },
108 ));
109}
110
111fn switch_projection(
112 mut camera: Single<&mut Projection, With<Camera>>,
113 camera_settings: Res<CameraSettings>,
114 keyboard_input: Res<ButtonInput<KeyCode>>,
115) {
116 if keyboard_input.just_pressed(KeyCode::Space) {
117 **camera = match **camera {
119 Projection::Orthographic(_) => Projection::Perspective(PerspectiveProjection {
120 fov: camera_settings.perspective_zoom_range.start,
121 ..default()
122 }),
123 Projection::Perspective(_) => Projection::Orthographic(OrthographicProjection {
124 scaling_mode: ScalingMode::FixedVertical {
125 viewport_height: camera_settings.orthographic_viewport_height,
126 },
127 ..OrthographicProjection::default_3d()
128 }),
129 _ => return,
130 }
131 }
132}
133
134fn zoom(
135 camera: Single<&mut Projection, With<Camera>>,
136 camera_settings: Res<CameraSettings>,
137 mouse_wheel_input: Res<AccumulatedMouseScroll>,
138) {
139 match *camera.into_inner() {
142 Projection::Orthographic(ref mut orthographic) => {
143 let delta_zoom = -mouse_wheel_input.delta.y * camera_settings.orthographic_zoom_speed;
145 let multiplicative_zoom = 1. + delta_zoom;
150
151 orthographic.scale = (orthographic.scale * multiplicative_zoom).clamp(
152 camera_settings.orthographic_zoom_range.start,
153 camera_settings.orthographic_zoom_range.end,
154 );
155 }
156 Projection::Perspective(ref mut perspective) => {
157 let delta_zoom = -mouse_wheel_input.delta.y * camera_settings.perspective_zoom_speed;
159
160 perspective.fov = (perspective.fov + delta_zoom).clamp(
162 camera_settings.perspective_zoom_range.start,
163 camera_settings.perspective_zoom_range.end,
164 );
165 }
166 _ => (),
167 }
168}