use glutin::Window;
use nalgebra::{Isometry3, Perspective3, Point2};
use num::traits::FloatConst;
use event::{Event, React};
use framework::WindowView;
use math::{FMatrix4, FPoint2, FPoint3, FRay3, FScalar, FVector3, UScalar};
lazy_static! {
static ref UP: FVector3 = FVector3::y();
}
pub trait AspectRatio {
fn dimensions(&self) -> (u32, u32);
fn aspect_ratio(&self) -> f32 {
let (width, height) = self.dimensions();
width as f32 / height as f32
}
}
impl AspectRatio for Window {
fn dimensions(&self) -> (UScalar, UScalar) {
self.get_inner_size_pixels().unwrap_or((1, 1))
}
}
pub struct Projection {
pub fov: FScalar,
pub near: FScalar,
pub far: FScalar,
}
impl Projection {
pub fn new(fov: FScalar, near: FScalar, far: FScalar) -> Self {
Projection {
fov: fov,
near: near,
far: far,
}
}
}
impl Default for Projection {
fn default() -> Self {
Projection::new(FScalar::FRAC_PI_2(), 0.1, 100.0)
}
}
impl From<Perspective3<FScalar>> for Projection {
fn from(perspective: Perspective3<FScalar>) -> Self {
Projection::new(perspective.fovy(), perspective.znear(), perspective.zfar())
}
}
pub struct Camera {
projection: Perspective3<FScalar>,
view: Isometry3<FScalar>,
}
impl Camera {
pub fn new(window: &WindowView, projection: &Projection) -> Self {
Camera {
projection: Perspective3::new(
window.aspect_ratio(),
projection.fov,
projection.near,
projection.far,
),
view: Isometry3::look_at_rh(&FPoint3::origin(), &FPoint3::new(0.0, 0.0, -1.0), &UP),
}
}
pub fn look_at(&mut self, from: &FPoint3, to: &FPoint3) {
self.view = Isometry3::look_at_rh(from, to, &UP);
}
pub fn cast_ray(&self, window: &WindowView, point: &Point2<i32>) -> FRay3 {
let (width, height) = window.dimensions();
let point = FPoint2::new(
((2.0 * point.x as FScalar) / width as FScalar) - 1.0,
1.0 - ((2.0 * point.y as FScalar) / height as FScalar),
);
let near = self.projection
.unproject_point(&FPoint3::new(point.x, point.y, -1.0));
let far = self.projection
.unproject_point(&FPoint3::new(point.x, point.y, 1.0));
let inverse = self.view.inverse();
FRay3::new(inverse * near, inverse * (far - near))
}
pub fn transform(&self) -> FMatrix4 {
self.projection.as_matrix() * self.view.to_homogeneous()
}
}
impl React for Camera {
fn react(&mut self, event: &Event) {
if let Event::Resized(width, height) = *event {
let ratio = (width as f32) / (height as f32);
self.projection = Perspective3::new(
ratio,
self.projection.fovy(),
self.projection.znear(),
self.projection.zfar(),
);
}
}
}