//! This models contains structs that help move the camera in a user-friendly way. const EPSILON: f64 = 0.001; use glium::glutin::dpi::{PhysicalPosition, PhysicalSize}; use glium::glutin::event::{ ElementState, Event, KeyboardInput, MouseButton, MouseScrollDelta, VirtualKeyCode, WindowEvent, }; use camera::Camera; use math::vector::{Vector2, Vector3}; use model::Model; use renderer::Renderer; /// The trait that all controls should implement. pub trait Controls { /// Modifies the camera depending on the event. fn manage_event(&mut self, event: &Event<()>, camera: &mut Camera, renderer: &Renderer); /// Updates the camera depending on time. fn update(&mut self, camera: &mut Camera, renderer: &Renderer); } /// An orbit controls allowing to orbit around an object. /// /// Only object centered are supported. pub struct OrbitControls { /// The last position of the mouse. mouse_position: Vector2, /// Wether the left click of the mouse is pressed or not. pressed: bool, /// The theta angle of the position of the camera in spheric coordinates. theta: f64, /// The phi angle of the position of the camera in spheric coordinates. phi: f64, /// The distance between the camera and the origin. distance: f64, /// The sensitiviy of the rotation of the mouse. sensitivity: f64, /// The center of the object. center: Vector3, } impl OrbitControls { /// Creates a new orbit controls, and initializes the camera. pub fn new(center: Vector3, distance: f64, camera: &mut Camera) -> OrbitControls { let controls = OrbitControls { mouse_position: Vector2::new(0.0, 0.0), pressed: false, theta: 0.0, phi: 0.0, distance: distance, sensitivity: 200.0, center: center, }; *camera.position.x_mut() = controls.distance * controls.theta.cos(); *camera.position.y_mut() = 0.0; *camera.position.z_mut() = controls.distance * controls.phi.sin(); camera.position += controls.center; camera.target = controls.center; controls } /// Creates orbit controls that are mode to rotate around a model. pub fn around(model: &Model, camera: &mut Camera) -> OrbitControls { // Compute bounding box let bounding_box = model.bounding_box(); let center = (bounding_box.min() + bounding_box.max()) / 2.0; let distance = (bounding_box.max() - bounding_box.min()).norm(); OrbitControls::new( Vector3::new(center.x() as f64, center.y() as f64, center.z() as f64), distance as f64, camera, ) } } impl Controls for OrbitControls { fn manage_event(&mut self, event: &Event<()>, camera: &mut Camera, _: &Renderer) { match *event { Event::WindowEvent { event: WindowEvent::MouseInput { button: MouseButton::Left, state, .. }, .. } => { self.pressed = state == ElementState::Pressed; } Event::WindowEvent { event: WindowEvent::Resized(PhysicalSize { width, height }), .. } => { camera.aspect_ratio = width as f64 / height as f64; } Event::WindowEvent { event: WindowEvent::MouseWheel { delta: MouseScrollDelta::LineDelta(_, y), .. }, .. } => { self.distance -= y as f64 / self.sensitivity; *camera.position.x_mut() = self.distance * self.phi.cos() * self.theta.cos(); *camera.position.y_mut() = self.distance * self.phi.sin(); *camera.position.z_mut() = self.distance * self.phi.cos() * self.theta.sin(); camera.position += self.center; camera.target = self.center; } Event::WindowEvent { event: WindowEvent::CursorMoved { position: PhysicalPosition { x, y }, .. }, .. } => { let current_position = Vector2::new(x as f64, y as f64); if self.pressed { let difference = (current_position - self.mouse_position) / self.sensitivity; self.theta += difference.x(); self.phi += difference.y(); use std::f64::consts::PI; self.phi = self.phi.max(-PI / 2.0 + EPSILON); self.phi = self.phi.min(PI / 2.0 - EPSILON); *camera.position.x_mut() = self.distance * self.phi.cos() * self.theta.cos(); *camera.position.y_mut() = self.distance * self.phi.sin(); *camera.position.z_mut() = self.distance * self.phi.cos() * self.theta.sin(); camera.position += self.center; camera.target = self.center; } // Record new position self.mouse_position = current_position; } _ => (), } } fn update(&mut self, _: &mut Camera, _: &Renderer) {} } /// First person controls, just like in video games. pub struct FirstPersonControls { /// Theta angle of the spheric coordinates of the direction of the camera. theta: f64, /// Phi angle of the spheric coordinates of the direction of the camera. phi: f64, /// Current position of the camera. position: Vector3, /// Vector indicating the direction of the camera. forward: Vector3, /// Vector indicating the left of the camera. left: Vector3, /// Speed of the camera. speed: f64, /// Sensitivity of the mouse. sensitivity: f64, /// Wether the forward button is pressed or not. forward_pressed: bool, /// Wether the backward button is pressed or not. backward_pressed: bool, /// Wether the left button is pressed or not. left_pressed: bool, /// Wether the right button is pressed or not. right_pressed: bool, /// Wether the boost is active or not. boost: bool, } impl FirstPersonControls { /// Creates a new default first person controls. pub fn new() -> FirstPersonControls { FirstPersonControls { theta: 0.0, phi: 0.0, position: Vector3::new(0.0, 0.0, 0.0), forward: Vector3::new(0.0, 0.0, 1.0), left: Vector3::new(1.0, 0.0, 0.0), speed: 0.001, forward_pressed: false, backward_pressed: false, left_pressed: false, right_pressed: false, boost: false, sensitivity: 500.0, } } /// Updates the camera according to the state of the controls. pub fn update_camera(&self, camera: &mut Camera) { camera.position = self.position; camera.target = self.position + self.forward; } } impl Controls for FirstPersonControls { fn manage_event(&mut self, event: &Event<()>, camera: &mut Camera, renderer: &Renderer) { match *event { // On resize window Event::WindowEvent { event: WindowEvent::Resized(PhysicalSize { width, height }), .. } => { camera.aspect_ratio = width as f64 / height as f64; } // On Z pressed Event::WindowEvent { event: WindowEvent::KeyboardInput { input: KeyboardInput { virtual_keycode: Some(VirtualKeyCode::Z), state, .. }, .. }, .. } => { self.forward_pressed = state == ElementState::Pressed; } // On S pressed Event::WindowEvent { event: WindowEvent::KeyboardInput { input: KeyboardInput { virtual_keycode: Some(VirtualKeyCode::S), state, .. }, .. }, .. } => { self.backward_pressed = state == ElementState::Pressed; } // On Q pressed Event::WindowEvent { event: WindowEvent::KeyboardInput { input: KeyboardInput { virtual_keycode: Some(VirtualKeyCode::Q), state, .. }, .. }, .. } => { self.left_pressed = state == ElementState::Pressed; } // On D pressed Event::WindowEvent { event: WindowEvent::KeyboardInput { input: KeyboardInput { virtual_keycode: Some(VirtualKeyCode::D), state, .. }, .. }, .. } => { self.right_pressed = state == ElementState::Pressed; } // On Space pressed Event::WindowEvent { event: WindowEvent::KeyboardInput { input: KeyboardInput { virtual_keycode: Some(VirtualKeyCode::Space), state, .. }, .. }, .. } => { self.boost = state == ElementState::Pressed; } // On mouse move Event::WindowEvent { event: WindowEvent::CursorMoved { position: PhysicalPosition { x, y }, .. }, .. } => { let size = renderer.gl_window().window().inner_size(); let center = Vector2::new(size.width as f64 / 2.0, size.height as f64 / 2.0); let current_position = Vector2::new(x as f64, y as f64); let difference = (current_position - center) / self.sensitivity; self.theta += difference.x(); self.phi -= difference.y(); use std::f64::consts::PI; self.phi = self.phi.max(-PI / 2.0 + EPSILON); self.phi = self.phi.min(PI / 2.0 - EPSILON); self.forward = Vector3::new( self.phi.cos() * self.theta.cos(), self.phi.sin(), self.phi.cos() * self.theta.sin(), ); self.left = Vector3::new(0.0, 1.0, 0.0) .cross_product(self.forward) .normalized(); // Move the cursor back to the center renderer .gl_window() .window() .set_cursor_position(PhysicalPosition::new( size.width as f64 / 2.0, size.height as f64 / 2.0, )) .unwrap(); } _ => (), } self.update_camera(camera); } fn update(&mut self, camera: &mut Camera, renderer: &Renderer) { renderer.gl_window().window().set_cursor_visible(false); let mut speed = Vector3::new(0.0, 0.0, 0.0); if self.forward_pressed { speed += self.forward * self.speed; } if self.backward_pressed { speed -= self.forward * self.speed; } if self.left_pressed { speed += self.left * self.speed; } if self.right_pressed { speed -= self.left * self.speed; } if self.boost { speed *= 10.0; } self.position += speed; self.update_camera(camera); } }