graphics/
input.rs

1//! Handles keyboard and mouse input, eg for moving the camera.
2
3use lin_alg::f32::{Quaternion, Vec3};
4// todo: remove Winit from this module if you can, and make it agnostic?
5use winit::event::{DeviceEvent, ElementState, MouseScrollDelta};
6use winit::keyboard::{KeyCode, PhysicalKey::Code};
7
8use crate::{
9    ScrollBehavior,
10    camera::Camera,
11    graphics::{FWD_VEC, RIGHT_VEC, UP_VEC},
12    types::InputSettings,
13};
14
15const EPS_MOUSE: f32 = 0.00001;
16
17#[derive(Default, Debug)]
18pub struct InputsCommanded {
19    pub fwd: bool,
20    pub back: bool,
21    pub left: bool,
22    pub right: bool,
23    pub up: bool,
24    pub down: bool,
25    pub roll_ccw: bool,
26    pub roll_cw: bool,
27    pub mouse_delta_x: f32,
28    pub mouse_delta_y: f32,
29    pub run: bool,
30    pub scroll_up: bool,
31    pub scroll_down: bool,
32    pub free_look: bool,
33    pub panning: bool, // todo: Implement A/R
34}
35
36impl InputsCommanded {
37    /// Return true if there are any inputs.
38    pub fn inputs_present(&self) -> bool {
39        // Note; We don't include `run` or `free_look` here, since they're modifiers.
40        self.fwd
41            || self.back
42            || self.left
43            || self.right
44            || self.up
45            || self.down
46            || self.roll_ccw
47            || self.roll_cw
48            || self.mouse_delta_x.abs() > EPS_MOUSE
49            || self.mouse_delta_y.abs() > EPS_MOUSE
50            || self.scroll_up
51            || self.scroll_down
52    }
53}
54
55/// Modifies the commanded inputs in place; triggered by a single input event.
56/// dt is in seconds.
57/// pub(crate) fn handle_event(event: DeviceEvent, cam: &mut Camera, input_settings: &InputSettings, dt: f32) {
58pub(crate) fn add_input_cmd(event: &DeviceEvent, inputs: &mut InputsCommanded) {
59    match event {
60        DeviceEvent::Key(key) => {
61            if key.state == ElementState::Pressed {
62                match key.physical_key {
63                    Code(key) => match key {
64                        KeyCode::KeyW => {
65                            inputs.fwd = true;
66                        }
67                        KeyCode::KeyS => {
68                            inputs.back = true;
69                        }
70                        KeyCode::KeyA => {
71                            inputs.left = true;
72                        }
73                        KeyCode::KeyD => {
74                            inputs.right = true;
75                        }
76                        KeyCode::Space => {
77                            inputs.up = true;
78                        }
79                        KeyCode::KeyC => {
80                            inputs.down = true;
81                        }
82                        KeyCode::KeyQ => {
83                            inputs.roll_ccw = true;
84                        }
85                        KeyCode::KeyE => {
86                            inputs.roll_cw = true;
87                        }
88                        KeyCode::ShiftLeft => {
89                            inputs.run = true;
90                        }
91                        _ => (),
92                    },
93                    _ => (),
94                }
95            } else if key.state == ElementState::Released {
96                // todo: DRY
97                match key.physical_key {
98                    Code(key) => match key {
99                        KeyCode::KeyW => {
100                            inputs.fwd = false;
101                        }
102                        KeyCode::KeyS => {
103                            inputs.back = false;
104                        }
105                        KeyCode::KeyA => {
106                            inputs.left = false;
107                        }
108                        KeyCode::KeyD => {
109                            inputs.right = false;
110                        }
111                        KeyCode::Space => {
112                            inputs.up = false;
113                        }
114                        KeyCode::KeyC => {
115                            inputs.down = false;
116                        }
117                        KeyCode::KeyQ => {
118                            inputs.roll_ccw = false;
119                        }
120                        KeyCode::KeyE => {
121                            inputs.roll_cw = false;
122                        }
123                        KeyCode::ShiftLeft => {
124                            inputs.run = false;
125                        }
126                        _ => (),
127                    },
128                    _ => (),
129                }
130            }
131        }
132        DeviceEvent::Button { button, state } => {
133            // todo: Experiment?
134            #[cfg(target_os = "linux")]
135            let left_click = 1;
136            #[cfg(not(target_os = "linux"))]
137            let left_click = 0;
138
139            // What happened: left click (event 0) triggered behavior of event 1.
140
141            if *button == left_click {
142                inputs.free_look = match state {
143                    ElementState::Pressed => true,
144                    ElementState::Released => false,
145                }
146            }
147        }
148        DeviceEvent::MouseMotion { delta } => {
149            inputs.mouse_delta_x += delta.0 as f32;
150            inputs.mouse_delta_y += delta.1 as f32;
151        }
152        // Move the camera forward and back on scroll.
153        DeviceEvent::MouseWheel { delta } => match delta {
154            MouseScrollDelta::PixelDelta(_) => (),
155            MouseScrollDelta::LineDelta(_x, y) => {
156                if *y > 0. {
157                    inputs.scroll_down = true;
158                } else {
159                    inputs.scroll_up = true;
160                }
161            }
162        },
163        _ => (),
164    }
165}
166
167fn handle_scroll(
168    cam: &mut Camera,
169    inputs: &mut InputsCommanded,
170    input_settings: &InputSettings,
171    dt: f32,
172    movement_vec: &mut Vec3,
173    rotation: &mut Quaternion,
174    cam_moved: &mut bool,
175    cam_rotated: &mut bool,
176) {
177    if inputs.scroll_down || inputs.scroll_up {
178        if let ScrollBehavior::MoveRoll {
179            move_amt,
180            rotate_amt,
181        } = input_settings.scroll_behavior
182        {
183            if inputs.free_look {
184                // Roll if left button down while scrolling
185                let fwd = cam.orientation.rotate_vec(FWD_VEC);
186
187                let mut rot_amt = -rotate_amt * dt;
188                if inputs.scroll_down {
189                    rot_amt *= -1.; // todo: Allow reversed behavior for arc cam?
190                }
191
192                *rotation = Quaternion::from_axis_angle(fwd, rot_amt);
193                *cam_rotated = true;
194            } else {
195                // Otherwise, move forward and backward.
196                let mut movement = Vec3::new(0., 0., move_amt);
197                if inputs.scroll_up {
198                    movement *= -1.;
199                }
200                *movement_vec += movement;
201
202                *cam_moved = true;
203            }
204        }
205
206        // Immediately send the "release" command; not on a Release event like keys.
207        inputs.scroll_down = false;
208        inputs.scroll_up = false;
209    }
210}
211
212/// Used internally for inputs, and externally, e.g. to command an arc rotation.
213pub fn arc_rotation(cam: &mut Camera, axis: Vec3, amt: f32, center: Vec3) {
214    let rotation = Quaternion::from_axis_angle(axis, amt);
215
216    cam.orientation = (rotation * cam.orientation).to_normalized();
217
218    let dist = (cam.position - center).magnitude();
219    // Update position based on the new orientation.
220    cam.position = center - cam.orientation.rotate_vec(FWD_VEC) * dist;
221}
222
223/// Adjust the camera orientation and position. Return if there was a change, so we know to update the buffer.
224/// For the free (6DOF first-person) camera.
225pub fn adjust_camera_free(
226    cam: &mut Camera,
227    inputs: &mut InputsCommanded,
228    input_settings: &InputSettings,
229    dt: f32,
230) -> bool {
231    let mut move_amt = input_settings.move_sens * dt;
232    let mut rotate_key_amt = input_settings.rotate_key_sens * dt;
233
234    let mut cam_moved = false;
235    let mut cam_rotated = false;
236
237    let mut movement_vec = Vec3::new_zero();
238    let mut rotation = Quaternion::new_identity();
239
240    if inputs.run {
241        move_amt *= input_settings.run_factor;
242        rotate_key_amt *= input_settings.run_factor;
243    }
244
245    if inputs.fwd {
246        movement_vec.z += move_amt;
247        cam_moved = true;
248    } else if inputs.back {
249        movement_vec.z -= move_amt;
250        cam_moved = true;
251    }
252
253    if inputs.right {
254        movement_vec.x += move_amt;
255        cam_moved = true;
256    } else if inputs.left {
257        movement_vec.x -= move_amt;
258        cam_moved = true;
259    }
260
261    if inputs.up {
262        movement_vec.y += move_amt;
263        cam_moved = true;
264    } else if inputs.down {
265        movement_vec.y -= move_amt;
266        cam_moved = true;
267    }
268
269    if inputs.roll_cw {
270        let fwd = cam.orientation.rotate_vec(FWD_VEC);
271        rotation = Quaternion::from_axis_angle(fwd, -rotate_key_amt);
272        cam_rotated = true;
273    } else if inputs.roll_ccw {
274        let fwd = cam.orientation.rotate_vec(FWD_VEC);
275        rotation = Quaternion::from_axis_angle(fwd, rotate_key_amt);
276        cam_rotated = true;
277    }
278
279    if inputs.free_look
280        && (inputs.mouse_delta_x.abs() > EPS_MOUSE || inputs.mouse_delta_y.abs() > EPS_MOUSE)
281    {
282        let rotate_amt = input_settings.rotate_sens * dt;
283        let up = cam.orientation.rotate_vec(-UP_VEC);
284        let right = cam.orientation.rotate_vec(-RIGHT_VEC);
285
286        rotation = Quaternion::from_axis_angle(up, -inputs.mouse_delta_x * rotate_amt)
287            * Quaternion::from_axis_angle(right, -inputs.mouse_delta_y * rotate_amt)
288            * rotation;
289
290        cam_rotated = true;
291    }
292
293    handle_scroll(
294        cam,
295        inputs,
296        input_settings,
297        dt,
298        &mut movement_vec,
299        &mut rotation,
300        &mut cam_moved,
301        &mut cam_rotated,
302    );
303
304    // todo: Handle middleclick + drag here too. Move `mol_dock`'s impl here.
305
306    if cam_rotated {
307        cam.orientation = (rotation * cam.orientation).to_normalized();
308    }
309
310    if cam_moved {
311        cam.position += cam.orientation.rotate_vec(movement_vec);
312    }
313
314    cam_moved || cam_rotated
315}
316
317/// Adjust the camera orientation and position. Return if there was a change, so we know to update the buffer.
318/// For the arc (orbital) camera.
319pub fn adjust_camera_arc(
320    cam: &mut Camera,
321    inputs: &mut InputsCommanded,
322    input_settings: &InputSettings,
323    center: Vec3,
324    dt: f32,
325) -> bool {
326    // How fast we rotate, derived from your input settings:
327    // Track if we actually moved/rotated:
328    let mut cam_moved = false;
329    let mut cam_rotated = false;
330
331    let mut movement_vec = Vec3::new_zero();
332    let mut rotation = Quaternion::new_identity();
333
334    // todo: Combine this fn with adjust_free, and accept ControlScheme as a param.
335
336    // Inverse of free
337    let rotate_key_amt = -input_settings.rotate_key_sens * dt;
338
339    if inputs.roll_cw {
340        let fwd = cam.orientation.rotate_vec(FWD_VEC);
341        rotation = Quaternion::from_axis_angle(fwd, -rotate_key_amt);
342        cam_rotated = true;
343    } else if inputs.roll_ccw {
344        let fwd = cam.orientation.rotate_vec(FWD_VEC);
345        rotation = Quaternion::from_axis_angle(fwd, rotate_key_amt);
346        cam_rotated = true;
347    }
348
349    let mut skip_move_vec = false;
350    // Only rotate if "free look" is active and the mouse moved enough:
351    if inputs.free_look
352        && (inputs.mouse_delta_x.abs() > EPS_MOUSE || inputs.mouse_delta_y.abs() > EPS_MOUSE)
353    {
354        let rotate_amt = input_settings.rotate_sens * dt;
355        let up = cam.orientation.rotate_vec(-UP_VEC);
356        let right = cam.orientation.rotate_vec(-RIGHT_VEC);
357
358        // Rotation logic: Equivalent to the free camera.
359        rotation = Quaternion::from_axis_angle(up, -inputs.mouse_delta_x * rotate_amt)
360            * Quaternion::from_axis_angle(right, -inputs.mouse_delta_y * rotate_amt);
361
362        // Distance between cam and center is invariant under this change.
363        skip_move_vec = true;
364
365        cam_moved = true;
366        cam_rotated = true;
367    }
368
369    handle_scroll(
370        cam,
371        inputs,
372        input_settings,
373        dt,
374        &mut movement_vec,
375        &mut rotation,
376        &mut cam_moved,
377        &mut cam_rotated,
378    );
379
380    // todo: Handle middleclick + drag here too. Move `mol_dock`'s impl here.
381
382    // note: We are not using our `arc_roation` fn here due to needing to handle both x and y. Hmm.
383    // arc_rotation(cam, axis, amt, center);
384
385    if cam_rotated {
386        cam.orientation = (rotation * cam.orientation).to_normalized();
387    }
388
389    if cam_moved && !skip_move_vec {
390        cam.position += cam.orientation.rotate_vec(movement_vec);
391    } else if cam_moved {
392        // todo: Bit odd to break this off from the above.
393        let dist = (cam.position - center).magnitude();
394        // Update position based on the new orientation.
395        cam.position = center - cam.orientation.rotate_vec(FWD_VEC) * dist;
396    }
397
398    cam_moved || cam_rotated
399}