dunge 0.1.9

Simple and portable 3d render library
Documentation
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
    }
}

/// The camera view.
#[derive(Clone, Copy)]
pub struct View<P = Perspective> {
    /// Eye 3d point.
    pub eye: [f32; 3],

    /// Look at 3d point.
    pub look: [f32; 3],

    /// Camera projection.
    /// Can be a [`Perspective`] or [`Orthographic`].
    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()
    }
}

/// Perspective projection.
#[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)
    }
}

/// Orthographic projection.
#[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)
    }
}