377 lines
11 KiB
Rust
377 lines
11 KiB
Rust
//! 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,
|
|
KeyboardInput,
|
|
VirtualKeyCode,
|
|
MouseCursor,
|
|
};
|
|
|
|
use math::vector::{Vector2, Vector3};
|
|
use camera::Camera;
|
|
use renderer::Renderer;
|
|
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, 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<f32>,
|
|
|
|
/// 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<f32>,
|
|
}
|
|
|
|
impl OrbitControls {
|
|
|
|
/// Creates a new orbit controls, and initializes the camera.
|
|
pub fn new(center: Vector3<f32>, 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, _: &Renderer) {
|
|
match *event {
|
|
|
|
Event::WindowEvent {
|
|
event: WindowEvent::MouseInput {
|
|
button: MouseButton::Left,
|
|
state, ..
|
|
}, ..
|
|
} => {
|
|
self.pressed = state == ElementState::Pressed;
|
|
},
|
|
|
|
Event::WindowEvent {
|
|
event: WindowEvent::Resized(width, height), ..
|
|
} => {
|
|
camera.aspect_ratio = width as f32 / height as f32;
|
|
},
|
|
|
|
Event::WindowEvent {
|
|
event: WindowEvent::MouseWheel {
|
|
delta: MouseScrollDelta::LineDelta(_, y), ..
|
|
}, ..
|
|
} => {
|
|
self.distance -= y / 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: (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;
|
|
},
|
|
|
|
_ => (),
|
|
}
|
|
}
|
|
|
|
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: f32,
|
|
|
|
/// Phi angle of the spheric coordinates of the direction of the camera.
|
|
phi: f32,
|
|
|
|
/// Current position of the camera.
|
|
position: Vector3<f32>,
|
|
|
|
/// Vector indicating the direction of the camera.
|
|
forward: Vector3<f32>,
|
|
|
|
/// Vector indicating the left of the camera.
|
|
left: Vector3<f32>,
|
|
|
|
/// Speed of the camera.
|
|
speed: f32,
|
|
|
|
/// Sensitivity of the mouse.
|
|
sensitivity: f32,
|
|
|
|
/// 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(width, height), ..
|
|
} => {
|
|
camera.aspect_ratio = width as f32 / height as f32;
|
|
},
|
|
|
|
// 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: (x, y), ..
|
|
}, ..
|
|
} => {
|
|
|
|
let size = renderer.gl_window().window().get_inner_size().unwrap();
|
|
let center = Vector2::new(size.0 as f32 / 2.0, size.1 as f32 / 2.0);
|
|
let current_position = Vector2::new(x as f32, y as f32);
|
|
let difference = (current_position - center) / 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);
|
|
|
|
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(size.0 as i32 / 2, size.1 as i32/ 2)
|
|
.unwrap();
|
|
|
|
},
|
|
|
|
|
|
_ => (),
|
|
|
|
}
|
|
|
|
self.update_camera(camera);
|
|
}
|
|
|
|
fn update(&mut self, camera: &mut Camera, renderer: &Renderer) {
|
|
|
|
renderer.gl_window().window().set_cursor(MouseCursor::NoneCursor);
|
|
|
|
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);
|
|
}
|
|
}
|