use math_utils as math;
use math_utils::vek;
use math_utils::num_traits as num;
use crate::graphics;
#[derive(Clone, Debug, PartialEq)]
pub struct Camera2d {
position : math::Point2 <f32>,
yaw : math::Rad <f32>,
orientation : math::Rotation2 <f32>,
transform_mat_world_to_view : math::Matrix4 <f32>,
viewport_width : u16,
viewport_height : u16,
zoom : f32,
ortho : vek::FrustumPlanes <f32>,
projection_mat_ortho : math::Matrix4 <f32>
}
impl Camera2d {
pub fn new (viewport_width : u16, viewport_height : u16) -> Self {
use math::Point;
use num::One;
let position = math::Point2::origin();
let yaw = math::Rad (0.0);
let orientation = math::Rotation2::one();
let transform_mat_world_to_view =
transform_mat_world_to_view (position, orientation);
let zoom = 1.0;
let ortho = Self::ortho_from_viewport_zoom (
viewport_width, viewport_height, zoom);
let projection_mat_ortho = graphics::projection_mat_orthographic (&ortho);
Camera2d {
position,
yaw,
orientation,
transform_mat_world_to_view,
viewport_width,
viewport_height,
zoom,
ortho,
projection_mat_ortho
}
}
pub fn yaw (&self) -> math::Rad <f32> {
self.yaw
}
pub fn position (&self) -> math::Point2 <f32> {
self.position
}
pub fn orientation (&self) -> math::Rotation2 <f32> {
self.orientation
}
pub fn viewport_width (&self) -> u16 {
self.viewport_width
}
pub fn viewport_height (&self) -> u16 {
self.viewport_height
}
pub fn transform_mat_world_to_view (&self) -> math::Matrix4 <f32> {
self.transform_mat_world_to_view
}
pub fn zoom (&self) -> f32 {
self.zoom
}
pub fn ortho (&self) -> vek::FrustumPlanes <f32> {
self.ortho
}
pub fn projection_mat_ortho (&self) -> math::Matrix4 <f32> {
self.projection_mat_ortho
}
pub fn screen_to_world (&self, screen_coord : math::Point2 <f32>)
-> math::Point2 <f32>
{
let screen_dimensions = [self.viewport_width, self.viewport_height].into();
let ndc_2d_coord =
graphics::screen_2d_to_ndc_2d (screen_dimensions, screen_coord);
let ndc_coord = ndc_2d_coord.0.with_z (0.0).with_w (1.0);
let view_coord = self.projection_mat_ortho().transposed() * ndc_coord;
let world_coord =
self.transform_mat_world_to_view().transposed() * view_coord;
world_coord.xy().into()
}
pub fn world_to_screen (&self, world_coord : math::Point2 <f32>)
-> math::Point2 <f32>
{
let screen_dimensions = [self.viewport_width, self.viewport_height].into();
let world_4d_coord = world_coord.0.with_z (0.0).with_w (1.0);
let view_coord = self.transform_mat_world_to_view() * world_4d_coord;
let clip_coord = self.projection_mat_ortho() * view_coord;
let ndc_coord = clip_coord.xy().into();
graphics::ndc_2d_to_screen_2d (screen_dimensions, ndc_coord)
}
pub fn set_viewport_dimensions (&mut self,
viewport_width : u16, viewport_height : u16
) {
assert!(0 < viewport_width);
assert!(0 < viewport_height);
self.viewport_width = viewport_width;
self.viewport_height = viewport_height;
self.compute_ortho();
}
pub fn set_position (&mut self, position : math::Point2 <f32>) {
if self.position != position {
self.position = position;
self.compute_transform();
}
}
pub fn set_zoom (&mut self, zoom : f32) {
assert!(0.0 < zoom);
if self.zoom != zoom {
self.zoom = zoom;
self.compute_ortho();
}
}
pub fn rotate (&mut self, delta_yaw : math::Rad <f32>) {
use std::f32::consts::PI;
use num::Zero;
use math::Angle;
if !delta_yaw.is_zero() {
self.yaw += delta_yaw;
if self.yaw < math::Rad (-PI) {
self.yaw += math::Rad::full_turn();
} else if self.yaw > math::Rad (PI) {
self.yaw -= math::Rad::full_turn();
}
self.compute_orientation();
}
}
pub fn move_local (&mut self, delta_x : f32, delta_y : f32) {
self.position += (delta_x * (*self.orientation).cols.x)
+ (delta_y * (*self.orientation).cols.y);
self.compute_transform();
}
pub fn move_origin_to_bottom_left (&mut self) {
self.position = [
(self.viewport_width / 2) as f32,
(self.viewport_height / 2) as f32
].into();
self.compute_transform();
}
pub fn scale_zoom (&mut self, scale : f32) {
assert!(0.0 < scale);
self.zoom *= scale;
self.compute_ortho();
}
#[inline]
pub fn view_ortho_mats (&self) -> ([[f32; 4]; 4], [[f32; 4]; 4]) {
( self.transform_mat_world_to_view.into_col_arrays(),
self.projection_mat_ortho.into_col_arrays()
)
}
#[inline]
fn compute_orientation (&mut self) {
self.orientation = math::Rotation2::from_angle (self.yaw);
self.compute_transform();
}
#[inline]
fn compute_transform (&mut self) {
self.transform_mat_world_to_view =
transform_mat_world_to_view (self.position, self.orientation);
}
fn compute_ortho (&mut self) {
self.ortho = Self::ortho_from_viewport_zoom (
self.viewport_width, self.viewport_height, self.zoom);
self.compute_projection();
}
#[inline]
fn compute_projection (&mut self) {
self.projection_mat_ortho =
graphics::projection_mat_orthographic (&self.ortho);
}
pub (crate) fn ortho_from_viewport_zoom (
viewport_width : u16, viewport_height : u16, zoom : f32
) -> vek::FrustumPlanes <f32> {
let half_scaled_width = 0.5 * (
(viewport_width - viewport_width % 2) as f32 / zoom);
let half_scaled_height = 0.5 * (
(viewport_height - viewport_height % 2) as f32 / zoom);
vek::FrustumPlanes {
left: -half_scaled_width,
right: half_scaled_width,
bottom: -half_scaled_height,
top: half_scaled_height,
near: -1.0,
far: 1.0
}
}
}
pub fn transform_mat_world_to_view (
view_position : math::Point2 <f32>,
view_orientation : math::Rotation2 <f32>
) -> math::Matrix4 <f32> {
let eye = view_position.0.with_z (0.0).into();
let center = eye - math::Vector3::unit_z();
let up = view_orientation.cols.y.with_z (0.0);
math::Matrix4::<f32>::look_at_rh (eye, center, up)
}