bevy_orbit_controls/
lib.rs

1//! An orbit controls plugin for bevy.
2//!
3//! # Usage
4//!
5//! Register the `OrbitCameraPlugin`, and add the `OrbitCamera` struct to the
6//! entity containing the camera.
7//!
8//! For example, within the startup system:
9//!
10//! ```ignore
11//! commands
12//!     .spawn(Camera3dBundle {
13//!         transform: Transform::from_translation(Vec3::new(-3.0, 3.0, 5.0))
14//!             .looking_at(Vec3::default(), Vec3::unit_y()),
15//!         ..Default::default()
16//!     })
17//!     .with(OrbitCamera::default());
18//! ```
19//!
20//! To control the camera, use dragging (left button) to rotate and the mouse
21//! wheel to zoom.
22
23use bevy::input::mouse::MouseMotion;
24use bevy::input::mouse::MouseScrollUnit::{Line, Pixel};
25use bevy::input::mouse::MouseWheel;
26use bevy::prelude::*;
27use bevy::render::camera::Camera;
28
29const LINE_TO_PIXEL_RATIO: f32 = 0.1;
30
31pub struct OrbitCamera {
32    pub x: f32,
33    pub y: f32,
34    pub distance: f32,
35    pub center: Vec3,
36    pub rotate_sensitivity: f32,
37    pub zoom_sensitivity: f32,
38}
39
40impl Default for OrbitCamera {
41    fn default() -> Self {
42        OrbitCamera {
43            x: 0.0,
44            y: 0.0,
45            distance: 5.0,
46            center: Vec3::ZERO,
47            rotate_sensitivity: 1.0,
48            zoom_sensitivity: 0.8,
49        }
50    }
51}
52
53impl OrbitCamera {
54    pub fn new(dist: f32, center: Vec3) -> OrbitCamera {
55        OrbitCamera {
56            x: 0.0,
57            y: 0.0,
58            distance: dist,
59            center,
60            rotate_sensitivity: 1.0,
61            zoom_sensitivity: 0.8,
62        }
63    }
64}
65
66pub struct OrbitCameraPlugin;
67impl OrbitCameraPlugin {
68    fn mouse_motion_system(
69        time: Res<Time>,
70        mut mouse_motion_events: EventReader<MouseMotion>,
71        mouse_button_input: Res<Input<MouseButton>>,
72        mut query: Query<(&mut OrbitCamera, &mut Transform, &mut Camera)>,
73    ) {
74        let mut delta = Vec2::ZERO;
75        for event in mouse_motion_events.iter() {
76            delta += event.delta;
77        }
78        for (mut camera, mut transform, _) in query.iter_mut() {
79            if mouse_button_input.pressed(MouseButton::Left) {
80                camera.x -= delta.x * camera.rotate_sensitivity * time.delta_seconds();
81                camera.y -= delta.y * camera.rotate_sensitivity * time.delta_seconds();
82
83                camera.y = camera.y.max(0.01).min(3.13);
84
85                let rot = Quat::from_axis_angle(Vec3::Y, camera.x)
86                    * Quat::from_axis_angle(-Vec3::X, camera.y);
87                transform.translation =
88                    (rot * Vec3::new(0.0, 1.0, 0.0)) * camera.distance + camera.center;
89                transform.look_at(camera.center, Vec3::Y);
90            }
91        }
92    }
93
94    fn zoom_system(
95        mut mouse_wheel_events: EventReader<MouseWheel>,
96        mut query: Query<(&mut OrbitCamera, &mut Transform, &mut Camera)>,
97    ) {
98        let mut total = 0.0;
99        for event in mouse_wheel_events.iter() {
100            total += event.y
101                * match event.unit {
102                    Line => 1.0,
103                    Pixel => LINE_TO_PIXEL_RATIO,
104                };
105        }
106        for (mut camera, mut transform, _) in query.iter_mut() {
107            camera.distance *= camera.zoom_sensitivity.powf(total);
108            let translation = &mut transform.translation;
109            *translation =
110                (*translation - camera.center).normalize() * camera.distance + camera.center;
111        }
112    }
113}
114impl Plugin for OrbitCameraPlugin {
115    fn build(&self, app: &mut AppBuilder) {
116        app
117            .add_system(Self::mouse_motion_system.system())
118            .add_system(Self::zoom_system.system());
119    }
120}