Skip to main content

mraphics_control/
orbit_control.rs

1use std::f32::consts::PI;
2
3use mraphics_core::Camera;
4use nalgebra::{Point3, Rotation3, Vector3};
5use winit::event::{ElementState, MouseButton, MouseScrollDelta, WindowEvent};
6
7#[derive(Debug)]
8pub enum OrbitControlState {
9    Wait,
10    Rotate([f64; 2]),
11    Move([f64; 2]),
12}
13
14struct MouseState {
15    position: [f64; 2],
16}
17
18pub struct OrbitControl {
19    pub state: OrbitControlState,
20    mouse_state: MouseState,
21
22    pub center: Point3<f32>,
23    start_center: Point3<f32>,
24    target_center: Point3<f32>,
25
26    pub enable_zoom: bool,
27    pub radius: f32,
28    pub scale: f32,
29    pub zoom_speed: f32,
30
31    pub enable_rotate: bool,
32    pub theta: f32,
33    pub phi: f32,
34    pub phi_max: f32,
35    pub phi_min: f32,
36    pub delta_angle_max: f32,
37    pub rotate_speed: f32,
38    pub rotate_ease: f32,
39    start_phi: f32,
40    start_theta: f32,
41    target_phi: f32,
42    target_theta: f32,
43
44    pub enable_move: bool,
45    pub move_speed: f32,
46    pub move_ease: f32,
47}
48
49impl Default for OrbitControl {
50    fn default() -> Self {
51        Self {
52            state: OrbitControlState::Wait,
53            mouse_state: MouseState {
54                position: [0.0, 0.0],
55            },
56
57            center: Point3::origin(),
58            start_center: Point3::origin(),
59            target_center: Point3::origin(),
60
61            enable_zoom: true,
62            radius: 5.0,
63            scale: 1.0,
64            zoom_speed: 1.1,
65
66            enable_rotate: true,
67            theta: 0.0,
68            phi: 0.0,
69            phi_max: PI / 2.0,
70            phi_min: -PI / 2.0,
71            delta_angle_max: 0.1,
72            rotate_speed: 0.01,
73            rotate_ease: 0.01,
74            start_phi: 0.0,
75            start_theta: 0.0,
76            target_phi: 0.0,
77            target_theta: 0.0,
78
79            enable_move: true,
80            move_speed: 0.001,
81            move_ease: 0.15,
82        }
83    }
84}
85
86impl OrbitControl {
87    pub fn new() -> Self {
88        Self {
89            ..Default::default()
90        }
91    }
92
93    pub fn update<C: Camera>(&mut self, camera: &mut C) {
94        let delta_phi = ((self.target_phi - self.phi) * self.rotate_ease).min(self.delta_angle_max);
95        let delta_theta =
96            ((self.target_theta - self.theta) * self.rotate_ease).min(self.delta_angle_max);
97
98        self.phi = (self.phi + delta_phi).min(self.phi_max).max(self.phi_min);
99        self.theta += delta_theta;
100
101        self.center = self.center.lerp(&self.target_center, self.move_ease);
102
103        camera.set_center(&self.compute_camera_center());
104
105        camera.look_at(&self.center);
106    }
107
108    pub fn handle_window_event(&mut self, event: &WindowEvent) {
109        match event {
110            WindowEvent::MouseInput { state, button, .. } => match state {
111                ElementState::Released => self.state = OrbitControlState::Wait,
112                ElementState::Pressed => match button {
113                    MouseButton::Left => {
114                        self.start_phi = self.phi;
115                        self.start_theta = self.theta;
116                        self.state = OrbitControlState::Rotate(self.mouse_state.position);
117                    }
118                    MouseButton::Middle => {
119                        self.start_center.clone_from(&self.center);
120                        self.state = OrbitControlState::Move(self.mouse_state.position)
121                    }
122                    _ => {}
123                },
124            },
125            WindowEvent::MouseWheel { delta, .. } => match delta {
126                MouseScrollDelta::LineDelta(_, y) => {
127                    self.on_mouse_wheel(*y > 0.0);
128                }
129                MouseScrollDelta::PixelDelta(pos) => {
130                    self.on_mouse_wheel(pos.y > 0.0);
131                }
132            },
133            WindowEvent::CursorMoved { position, .. } => {
134                self.mouse_state.position = [position.x, position.y];
135                self.on_mouse_move([position.x, position.y]);
136            }
137            _ => {}
138        }
139    }
140
141    pub fn rotate(&mut self, delta_phi: f32, delta_theta: f32) {
142        self.target_phi = delta_phi + self.start_phi;
143        self.target_theta = delta_theta + self.start_theta;
144    }
145
146    pub fn zoom_to(&mut self, scale: f32) {
147        self.scale = scale;
148    }
149
150    pub fn shift(&mut self, delta_x: f32, delta_y: f32) {
151        let camera_center = self.compute_camera_center();
152        let z_axis = self.center.coords - &camera_center;
153        let mut x_axis = z_axis.cross(&Vector3::y());
154        let mut y_axis = x_axis.cross(&z_axis);
155
156        x_axis.set_magnitude(delta_x * self.radius * self.scale);
157        y_axis.set_magnitude(delta_y * self.radius * self.scale);
158
159        self.target_center = self.start_center + &x_axis + &y_axis;
160    }
161
162    fn on_mouse_move(&mut self, pos: [f64; 2]) {
163        if let OrbitControlState::Rotate(start_pos) = self.state {
164            if !self.enable_rotate {
165                return;
166            }
167
168            let delta_phi = self.rotate_speed * (start_pos[1] - pos[1]) as f32;
169            let delta_theta = self.rotate_speed * (pos[0] - start_pos[0]) as f32;
170            self.rotate(delta_phi, delta_theta);
171        }
172
173        if let OrbitControlState::Move(start_pos) = self.state {
174            if !self.enable_move {
175                return;
176            }
177
178            let delta_x = self.move_speed * (start_pos[0] - pos[0]) as f32;
179            let delta_y = self.move_speed * (pos[1] - start_pos[1]) as f32;
180            self.shift(delta_x, delta_y);
181        }
182    }
183
184    fn on_mouse_wheel(&mut self, positive: bool) {
185        if !self.enable_zoom {
186            return;
187        }
188
189        if positive {
190            self.zoom_to(self.scale / self.zoom_speed);
191        } else {
192            self.zoom_to(self.scale * self.zoom_speed);
193        }
194    }
195
196    fn compute_camera_center(&self) -> Vector3<f32> {
197        let rotation = Rotation3::from_euler_angles(self.phi, -self.theta, 0.0);
198        rotation.transform_vector(&Vector3::new(0.0, 0.0, self.radius * self.scale))
199            + &self.center.coords
200    }
201}