//! This models contains structs that help move the camera in a user-friendly way. const EPSILON: f32 = 0.001; use glium::glutin::{ Event, WindowEvent, ElementState, MouseButton, MouseScrollDelta, }; use math::vector::{Vector2, Vector3}; use renderer::camera::Camera; use model::Model; /// 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); } /// 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: f32, /// The phi angle of the position of the camera in spheric coordinates. phi: f32, /// The distance between the camera and the origin. distance: f32, /// The sensitiviy of the rotation of the mouse. sensitivity: f32, /// The center of the object. center: Vector3, } impl OrbitControls { /// Creates a new orbit controls, and initializes the camera. pub fn new(center: Vector3, distance: f32, 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 f32, center.y() as f32, center.z() as f32), distance as f32, camera ) } } impl Controls for OrbitControls { fn manage_event(&mut self, event: &Event, camera: &mut Camera) { match *event { Event::WindowEvent { event: WindowEvent::MouseInput { button: MouseButton::Left, state, .. }, .. } => { self.pressed = state == ElementState::Pressed; }, Event::WindowEvent { event: WindowEvent::MouseWheel { delta: MouseScrollDelta::LineDelta(_, y), .. }, .. } => { self.distance -= y; *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: (x, y), .. }, .. } => { let current_position = Vector2::new(x as f32, y as f32); if self.pressed { let difference = (current_position - self.mouse_position) / self.sensitivity; self.theta += difference.x(); self.phi += difference.y(); use std::f32::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; }, _ => (), } } }