use crate::{
get_context,
math::Rect,
texture::RenderTarget,
window::{screen_height, screen_width},
};
use glam::{vec2, vec3, Mat4, Vec2, Vec3};
pub trait Camera {
fn matrix(&self) -> Mat4;
fn depth_enabled(&self) -> bool;
fn render_pass(&self) -> Option<miniquad::RenderPass>;
fn viewport(&self) -> Option<(i32, i32, i32, i32)>;
}
#[derive(Clone, Copy)]
pub struct Camera2D {
pub rotation: f32,
pub zoom: Vec2,
pub target: Vec2,
pub offset: Vec2,
pub render_target: Option<RenderTarget>,
pub viewport: Option<(i32, i32, i32, i32)>,
}
impl Camera2D {
pub fn from_display_rect(rect: Rect) -> Camera2D {
let target = vec2(rect.x + rect.w / 2., rect.y + rect.h / 2.);
Camera2D {
target,
zoom: vec2(1. / rect.w * 2., -1. / rect.h * 2.),
offset: vec2(0., 0.),
rotation: 0.,
render_target: None,
viewport: None,
}
}
}
impl Default for Camera2D {
fn default() -> Camera2D {
Camera2D {
zoom: vec2(1., 1.),
offset: vec2(0., 0.),
target: vec2(0., 0.),
rotation: 0.,
render_target: None,
viewport: None,
}
}
}
impl Camera for Camera2D {
fn matrix(&self) -> Mat4 {
let mat_origin = Mat4::from_translation(vec3(-self.target.x, -self.target.y, 0.0));
let mat_rotation = Mat4::from_axis_angle(vec3(0.0, 0.0, 1.0), self.rotation.to_radians());
let mat_scale = Mat4::from_scale(vec3(self.zoom.x, self.zoom.y, 1.0));
let mat_translation = Mat4::from_translation(vec3(self.offset.x, self.offset.y, 0.0));
mat_translation * ((mat_scale * mat_rotation) * mat_origin)
}
fn depth_enabled(&self) -> bool {
false
}
fn render_pass(&self) -> Option<miniquad::RenderPass> {
self.render_target.map(|rt| rt.render_pass)
}
fn viewport(&self) -> Option<(i32, i32, i32, i32)> {
self.viewport
}
}
impl Camera2D {
pub fn world_to_screen(&self, point: Vec2) -> Vec2 {
let mat = self.matrix();
let transform = mat.transform_point3(vec3(point.x, point.y, 0.));
vec2(
(transform.x / 2. + 0.5) * screen_width(),
(0.5 - transform.y / 2.) * screen_height(),
)
}
pub fn screen_to_world(&self, point: Vec2) -> Vec2 {
let point = vec2(
point.x / screen_width() * 2. - 1.,
1. - point.y / screen_height() * 2.,
);
let inv_mat = self.matrix().inverse();
let transform = inv_mat.transform_point3(vec3(point.x, point.y, 0.));
vec2(transform.x, transform.y)
}
}
#[derive(Debug, Clone, Copy)]
pub enum Projection {
Perspective,
Orthographics,
}
#[derive(Debug, Clone, Copy)]
pub struct Camera3D {
pub position: Vec3,
pub target: Vec3,
pub up: Vec3,
pub fovy: f32,
pub aspect: Option<f32>,
pub projection: Projection,
pub render_target: Option<RenderTarget>,
pub viewport: Option<(i32, i32, i32, i32)>,
}
impl Default for Camera3D {
fn default() -> Camera3D {
Camera3D {
position: vec3(0., -10., 0.),
target: vec3(0., 0., 0.),
aspect: None,
up: vec3(0., 0., 1.),
fovy: 45.,
projection: Projection::Perspective,
render_target: None,
viewport: None,
}
}
}
impl Camera3D {
const Z_NEAR: f32 = 0.01;
const Z_FAR: f32 = 10000.0;
}
impl Camera for Camera3D {
fn matrix(&self) -> Mat4 {
let aspect = self.aspect.unwrap_or(screen_width() / screen_height());
match self.projection {
Projection::Perspective => {
Mat4::perspective_rh_gl(self.fovy, aspect, Self::Z_NEAR, Self::Z_FAR)
* Mat4::look_at_rh(self.position, self.target, self.up)
}
Projection::Orthographics => {
let top = self.fovy / 2.0;
let right = top * aspect;
Mat4::orthographic_rh_gl(-right, right, -top, top, Self::Z_NEAR, Self::Z_FAR)
* Mat4::look_at_rh(self.position, self.target, self.up)
}
}
}
fn depth_enabled(&self) -> bool {
true
}
fn render_pass(&self) -> Option<miniquad::RenderPass> {
self.render_target.map(|rt| rt.render_pass)
}
fn viewport(&self) -> Option<(i32, i32, i32, i32)> {
self.viewport
}
}
pub fn set_camera(camera: &dyn Camera) {
let context = get_context();
context.perform_render_passes();
context.gl.render_pass(camera.render_pass());
context.gl.viewport(camera.viewport());
context.gl.depth_test(camera.depth_enabled());
context.camera_matrix = Some(camera.matrix());
}
pub fn set_default_camera() {
let context = get_context();
context.perform_render_passes();
context.gl.render_pass(None);
context.gl.depth_test(false);
context.camera_matrix = None;
}
pub(crate) struct CameraState {
render_pass: Option<miniquad::RenderPass>,
depth_test: bool,
matrix: Option<Mat4>,
}
pub fn push_camera_state() {
let context = get_context();
let camera_state = CameraState {
render_pass: context.gl.get_active_render_pass(),
depth_test: context.gl.is_depth_test_enabled(),
matrix: context.camera_matrix,
};
context.camera_stack.push(camera_state);
}
pub fn pop_camera_state() {
let context = get_context();
if let Some(camera_state) = context.camera_stack.pop() {
context.perform_render_passes();
context.gl.render_pass(camera_state.render_pass);
context.gl.depth_test(camera_state.depth_test);
context.camera_matrix = camera_state.matrix;
}
}