use crate::{
ecs::{ Mut, Const },
services::{ Frame, Globals, Input, Renderer, Window },
renderer::{ UniformBuffer },
};
use dotrix_math::{ Mat4, Point3, Vec3, perspective, Rad };
use std::f32::consts::PI;
const ROTATE_SPEED: f32 = PI / 10.0;
const ZOOM_SPEED: f32 = 10.0;
#[derive(Default)]
pub struct ProjView {
pub uniform: UniformBuffer,
}
pub struct Camera {
pub distance: f32,
pub y_angle: f32,
pub xz_angle: f32,
pub target: Point3,
pub view: Option<Mat4>,
pub fov: f32,
pub near_plane: f32,
pub far_plane: f32,
pub proj: Option<Mat4>,
}
impl Camera {
pub fn new() -> Self {
let distance = 15.0;
let y_angle = -PI / 2.0;
let xz_angle = PI / 4.0;
let target = Point3::new(0.0, 5.0, 0.0);
let fov = 1.1;
let near_plane = 0.0625;
let far_plane = 524288.06;
Self {
distance,
y_angle,
xz_angle,
target,
view: None,
fov,
near_plane,
far_plane,
proj: None
}
}
pub fn view_matrix_static(&self) -> Mat4 {
let mut view_static = self.view.expect("View matrix must be set");
view_static.w.x = 0.0;
view_static.w.y = 0.0;
view_static.w.z = 0.0;
view_static
}
pub fn position(&self) -> Vec3 {
let dy = self.distance * self.xz_angle.sin();
let dxz = self.distance * self.xz_angle.cos();
let dx = dxz * self.y_angle.cos();
let dz = dxz * self.y_angle.sin();
Vec3::new(self.target.x + dx, self.target.y + dy, self.target.z + dz)
}
pub fn view_matrix(&self) -> Mat4 {
let dy = self.distance * self.xz_angle.sin();
let dxz = self.distance * self.xz_angle.cos();
let dx = dxz * self.y_angle.cos();
let dz = dxz * self.y_angle.sin();
let position = Point3::new(self.target.x + dx, self.target.y + dy, self.target.z + dz);
let (position, target) = if self.distance > 0.0 {
(position, self.target)
} else {
(self.target, position)
};
Mat4::look_at(position, target, Vec3::unit_y())
}
pub fn view(&self) -> &Mat4 {
self.view.as_ref().expect("View matrix must be set")
}
pub fn proj(&self) -> &Mat4 {
self.proj.as_ref().expect("Projection matrix must be set")
}
}
impl Default for Camera {
fn default() -> Self {
Self::new()
}
}
pub fn startup(mut globals: Mut<Globals>) {
let proj_view = ProjView::default();
globals.set(proj_view);
}
pub fn bind(
mut globals: Mut<Globals>,
mut camera: Mut<Camera>,
renderer: Const<Renderer>,
window: Const<Window>
) {
if camera.proj.is_none() {
camera.proj = Some(perspective(
Rad(camera.fov),
window.aspect_ratio(),
camera.near_plane,
camera.far_plane
));
}
camera.view = Some(camera.view_matrix());
if let Some(proj_view) = globals.get_mut::<ProjView>() {
let matrix = camera.proj.as_ref().unwrap() * camera.view.as_ref().unwrap();
let matrix_raw = AsRef::<[f32; 16]>::as_ref(&matrix);
renderer.load_uniform_buffer(&mut proj_view.uniform, bytemuck::cast_slice(matrix_raw));
}
}
pub fn resize(mut camera: Mut<Camera>, window: Const<Window>) {
camera.proj = Some(perspective(
Rad(camera.fov),
window.aspect_ratio(),
camera.near_plane,
camera.far_plane
));
}
pub fn control(mut camera: Mut<Camera>, input: Const<Input>, frame: Const<Frame>) {
let time_delta = frame.delta().as_secs_f32();
let mouse_delta = input.mouse_delta();
let mouse_scroll = input.mouse_scroll();
let distance = camera.distance - ZOOM_SPEED * mouse_scroll * time_delta;
camera.distance = if distance > -1.0 { distance } else { -1.0 };
camera.y_angle += mouse_delta.x * ROTATE_SPEED * time_delta;
let xz_angle = camera.xz_angle + mouse_delta.y * ROTATE_SPEED * time_delta;
let half_pi = PI / 2.0;
camera.xz_angle = if xz_angle >= half_pi {
half_pi - 0.01
} else if xz_angle <= -half_pi {
-half_pi + 0.01
} else {
xz_angle
};
}