smooth_bevy_cameras/controllers/
orbit.rs1use crate::{LookAngles, LookTransform, LookTransformBundle, Smoother};
2
3use bevy::{
4 app::prelude::*,
5 ecs::{bundle::Bundle, prelude::*},
6 input::{
7 mouse::{MouseMotion, MouseScrollUnit, MouseWheel},
8 prelude::*,
9 },
10 math::prelude::*,
11 prelude::ReflectDefault,
12 reflect::Reflect,
13 time::Time,
14 transform::components::Transform,
15};
16
17#[derive(Default)]
18pub struct OrbitCameraPlugin {
19 pub override_input_system: bool,
20}
21
22impl OrbitCameraPlugin {
23 pub fn new(override_input_system: bool) -> Self {
24 Self {
25 override_input_system,
26 }
27 }
28}
29
30impl Plugin for OrbitCameraPlugin {
31 fn build(&self, app: &mut App) {
32 let app = app
33 .add_systems(PreUpdate, on_controller_enabled_changed)
34 .add_systems(Update, control_system)
35 .add_event::<ControlEvent>();
36
37 if !self.override_input_system {
38 app.add_systems(Update, default_input_map);
39 }
40 }
41}
42
43#[derive(Bundle)]
44pub struct OrbitCameraBundle {
45 controller: OrbitCameraController,
46 look_transform: LookTransformBundle,
47 transform: Transform,
48}
49
50impl OrbitCameraBundle {
51 pub fn new(controller: OrbitCameraController, eye: Vec3, target: Vec3, up: Vec3) -> Self {
52 let transform = Transform::from_translation(eye).looking_at(target, up);
54
55 Self {
56 controller,
57 look_transform: LookTransformBundle {
58 transform: LookTransform::new(eye, target, up),
59 smoother: Smoother::new(controller.smoothing_weight),
60 },
61 transform,
62 }
63 }
64}
65
66#[derive(Clone, Component, Copy, Debug, Reflect)]
68#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
69#[reflect(Component, Default, Debug)]
70pub struct OrbitCameraController {
71 pub enabled: bool,
72 pub mouse_rotate_sensitivity: Vec2,
73 pub mouse_translate_sensitivity: Vec2,
74 pub mouse_wheel_zoom_sensitivity: f32,
75 pub pixels_per_line: f32,
76 pub smoothing_weight: f32,
77}
78
79impl Default for OrbitCameraController {
80 fn default() -> Self {
81 Self {
82 mouse_rotate_sensitivity: Vec2::splat(0.08),
83 mouse_translate_sensitivity: Vec2::splat(0.1),
84 mouse_wheel_zoom_sensitivity: 0.2,
85 smoothing_weight: 0.8,
86 enabled: true,
87 pixels_per_line: 53.0,
88 }
89 }
90}
91
92#[derive(Event)]
93pub enum ControlEvent {
94 Orbit(Vec2),
95 TranslateTarget(Vec2),
96 Zoom(f32),
97}
98
99define_on_controller_enabled_changed!(OrbitCameraController);
100
101pub fn default_input_map(
102 mut events: EventWriter<ControlEvent>,
103 mut mouse_wheel_reader: EventReader<MouseWheel>,
104 mut mouse_motion_events: EventReader<MouseMotion>,
105 mouse_buttons: Res<ButtonInput<MouseButton>>,
106 keyboard: Res<ButtonInput<KeyCode>>,
107 controllers: Query<&OrbitCameraController>,
108) {
109 let controller = if let Some(controller) = controllers.iter().find(|c| c.enabled) {
111 controller
112 } else {
113 return;
114 };
115 let OrbitCameraController {
116 mouse_rotate_sensitivity,
117 mouse_translate_sensitivity,
118 mouse_wheel_zoom_sensitivity,
119 pixels_per_line,
120 ..
121 } = *controller;
122
123 let mut cursor_delta = Vec2::ZERO;
124 for event in mouse_motion_events.read() {
125 cursor_delta += event.delta;
126 }
127
128 if keyboard.pressed(KeyCode::ControlLeft) {
129 events.write(ControlEvent::Orbit(mouse_rotate_sensitivity * cursor_delta));
130 }
131
132 if mouse_buttons.pressed(MouseButton::Right) {
133 events.write(ControlEvent::TranslateTarget(
134 mouse_translate_sensitivity * cursor_delta,
135 ));
136 }
137
138 let mut scalar = 1.0;
139 for event in mouse_wheel_reader.read() {
140 let scroll_amount = match event.unit {
142 MouseScrollUnit::Line => event.y,
143 MouseScrollUnit::Pixel => event.y / pixels_per_line,
144 };
145 scalar *= 1.0 - scroll_amount * mouse_wheel_zoom_sensitivity;
146 }
147 events.write(ControlEvent::Zoom(scalar));
148}
149
150pub fn control_system(
151 time: Res<Time>,
152 mut events: EventReader<ControlEvent>,
153 mut cameras: Query<(&OrbitCameraController, &mut LookTransform, &Transform)>,
154) {
155 let (mut transform, scene_transform) =
157 if let Some((_, transform, scene_transform)) = cameras.iter_mut().find(|c| c.0.enabled) {
158 (transform, scene_transform)
159 } else {
160 return;
161 };
162
163 let mut look_angles = LookAngles::from_vector(-transform.look_direction().unwrap());
164 let mut radius_scalar = 1.0;
165 let radius = transform.radius();
166
167 let dt = time.delta_secs();
168 for event in events.read() {
169 match event {
170 ControlEvent::Orbit(delta) => {
171 look_angles.add_yaw(dt * -delta.x);
172 look_angles.add_pitch(dt * delta.y);
173 }
174 ControlEvent::TranslateTarget(delta) => {
175 let right_dir = scene_transform.rotation * -Vec3::X;
176 let up_dir = scene_transform.rotation * Vec3::Y;
177 transform.target += dt * delta.x * right_dir + dt * delta.y * up_dir;
178 }
179 ControlEvent::Zoom(scalar) => {
180 radius_scalar *= scalar;
181 }
182 }
183 }
184
185 look_angles.assert_not_looking_up();
186
187 let new_radius = (radius_scalar * radius).min(1000000.0).max(0.001);
188 transform.eye = transform.target + new_radius * look_angles.unit_vector();
189}