pub(crate) use self::proj::{IntoProjection, Projection};
use {
crate::{
layout::Plain,
shader,
shader_data::CameraUniform,
transform::{IntoQuat, Quat},
},
glam::{Mat4, Vec3},
std::cell::Cell,
wgpu::{BindGroup, BindGroupLayout, Buffer, Device, Queue},
};
mod proj {
use super::{Orthographic, Perspective};
#[derive(Clone, Copy)]
pub enum Projection {
Perspective(Perspective),
Orthographic(Orthographic),
}
pub trait IntoProjection {
fn into_projection(self) -> Projection;
}
}
pub(crate) struct Camera {
view: View<Projection>,
size: Cell<Option<(u32, u32)>>,
buffer: Buffer,
bind_group: BindGroup,
}
impl Camera {
pub fn new(device: &Device, layout: &BindGroupLayout) -> Self {
use wgpu::{
util::{BufferInitDescriptor, DeviceExt},
*,
};
let uniform = CameraUniform::default();
let buffer = device.create_buffer_init(&BufferInitDescriptor {
label: Some("camera buffer"),
contents: uniform.as_bytes(),
usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
});
let bind_group = device.create_bind_group(&BindGroupDescriptor {
layout,
entries: &[BindGroupEntry {
binding: shader::CAMERA_BINDING,
resource: buffer.as_entire_binding(),
}],
label: Some("camera bind group"),
});
Self {
view: View::<Orthographic>::default().into_projection_view(),
size: Cell::new(None),
buffer,
bind_group,
}
}
pub fn set_view(&mut self, view: View<Projection>) {
self.view = view;
self.size.set(None);
}
pub fn resize(&self, size @ (width, height): (u32, u32), queue: &Queue) {
if self
.size
.get()
.map(|(w, h)| width == w && height == h)
.unwrap_or_default()
{
return;
}
self.size.set(Some(size));
let mat = self.view.build_mat((width as f32, height as f32));
let uniform = CameraUniform::new(mat.to_cols_array_2d());
queue.write_buffer(&self.buffer, 0, uniform.as_bytes());
}
pub fn bind_group(&self) -> &BindGroup {
&self.bind_group
}
}
#[derive(Clone, Copy)]
pub struct View<P = Perspective> {
pub eye: [f32; 3],
pub look: [f32; 3],
pub proj: P,
}
impl<P> View<P> {
pub fn into_projection_view(self) -> View<Projection>
where
P: IntoProjection,
{
View {
eye: self.eye,
look: self.look,
proj: self.proj.into_projection(),
}
}
pub fn rotation_quat(&self) -> Quat {
let mat = Mat4::look_at_rh(Vec3::from(self.eye), Vec3::from(self.look), Vec3::Y);
let (_, rot, _) = mat.to_scale_rotation_translation();
Quat(rot.to_array())
}
}
impl View<Projection> {
fn build_mat(&self, (width, height): (f32, f32)) -> Mat4 {
let proj = match self.proj {
Projection::Perspective(Perspective { fovy, znear, zfar }) => {
Mat4::perspective_rh(fovy, width / height, znear, zfar)
}
Projection::Orthographic(Orthographic {
width_factor,
height_factor,
near,
far,
}) => {
let wh = width * width_factor * 0.5;
let left = -wh;
let right = wh;
let hh = height * height_factor * 0.5;
let bottom = -hh;
let top = hh;
Mat4::orthographic_rh(left, right, bottom, top, near, far)
}
};
let view = Mat4::look_at_rh(self.eye.into(), self.look.into(), Vec3::Y);
proj * view
}
}
impl<P> Default for View<P>
where
P: Default,
{
fn default() -> Self {
Self {
eye: [0., 0., 1.],
look: [0.; 3],
proj: P::default(),
}
}
}
impl<P> IntoQuat for View<P> {
fn into_quat(self) -> Quat {
self.rotation_quat()
}
}
#[derive(Clone, Copy)]
pub struct Perspective {
pub fovy: f32,
pub znear: f32,
pub zfar: f32,
}
impl Default for Perspective {
fn default() -> Self {
Self {
fovy: 1.6,
znear: 0.1,
zfar: 100.,
}
}
}
impl IntoProjection for Perspective {
fn into_projection(self) -> Projection {
Projection::Perspective(self)
}
}
#[derive(Clone, Copy)]
pub struct Orthographic {
pub width_factor: f32,
pub height_factor: f32,
pub near: f32,
pub far: f32,
}
impl Default for Orthographic {
fn default() -> Self {
Self {
width_factor: 1.,
height_factor: 1.,
near: -100.,
far: 100.,
}
}
}
impl IntoProjection for Orthographic {
fn into_projection(self) -> Projection {
Projection::Orthographic(self)
}
}