gamekit 0.1.0

A set of helpers for making games with notan
Documentation
use notan::math::{vec2, vec3, Mat3, Mat4, Vec2};

#[derive(Default, Clone, Copy, PartialEq)]
pub enum CameraMode {
    #[default]
    Basic,
    Fill(Vec2),
    AspectFit(Vec2),
    AspectFill(Vec2),
}

#[derive(Default, Clone, Copy, PartialEq)]
pub enum CameraStyle {
    #[default]
    LockOn,
    Platformer,
    TopDown,
    TopDownTight,
}

pub struct Camera {
    dirty_projection: bool,
    dirty_transform: bool,
    position: Vec2,
    rotation: f32,
    scale: Vec2,
    size: Vec2,
    work_size: Vec2,

    projection: Mat4,
    ratio: Vec2,
    transform: Mat3,

    mode: CameraMode,
    style: CameraStyle,
}

impl Default for Camera {
    fn default() -> Self {
        Self {
            size: vec2(1.0, 1.0),
            work_size: vec2(1.0, 1.0),
            position: vec2(0.0, 0.0),
            scale: vec2(1.0, 1.0),
            rotation: 0.0,
            dirty_projection: true,
            dirty_transform: true,
            projection: Mat4::IDENTITY,
            ratio: vec2(1.0, 1.0),
            transform: Mat3::IDENTITY,
            mode: CameraMode::Basic,
            style: CameraStyle::LockOn,
        }
    }
}

impl Camera {
    pub fn new(size: Vec2) -> Self {
        let work_size = size;
        Self {
            size,
            work_size,
            ..Default::default()
        }
    }

    pub fn set_mode(&mut self, mode: CameraMode) {
        if self.mode != mode {
            self.mode = mode;
            self.dirty_projection = true;
        }
    }

    pub fn mode(&self) -> CameraMode {
        self.mode
    }

    pub fn set_style(&mut self, style: CameraStyle) {
        if self.style != style {
            self.style = style;
            self.dirty_projection = true;
        }
    }

    pub fn style(&self) -> CameraStyle {
        self.style
    }

    pub fn set_size(&mut self, x: f32, y: f32) {
        let size = vec2(x, y);
        if self.size != size {
            self.size = size;
            self.dirty_projection = true;
        }
    }

    pub fn size(&self) -> Vec2 {
        self.size
    }

    pub fn set_position(&mut self, x: f32, y: f32) {
        let pos = vec2(x, y);
        if self.position != pos {
            self.position = pos;
            self.dirty_projection = true;
        }
    }

    pub fn position(&self) -> Vec2 {
        self.position
    }

    pub fn set_rotation(&mut self, angle: f32) {
        if self.rotation != angle {
            self.rotation = angle;
            self.dirty_projection = true;
        }
    }

    pub fn rotation(&self) -> f32 {
        self.rotation
    }

    pub fn set_scale(&mut self, x: f32, y: f32) {
        let scale = vec2(x, y);
        if self.scale != scale {
            self.scale = scale;
            self.dirty_projection = true;
        }
    }

    pub fn scale(&self) -> Vec2 {
        self.scale
    }

    pub fn set_zoom(&mut self, scale: f32) {
        self.set_scale(scale, scale);
    }

    pub fn zoom(&self) -> f32 {
        self.scale.x
    }

    pub fn transform(&self) -> Mat3 {
        self.transform
    }

    pub fn projection(&self) -> Mat4 {
        self.projection
    }

    pub fn update(&mut self) {
        if !self.dirty_projection {
            return;
        }

        // dirty_projection
        // dirty_transform

        self.dirty_projection = false;
        self.calculate_projection();
        self.calculate_transform();
    }

    fn calculate_projection(&mut self) {
        match self.mode {
            CameraMode::Basic => self.calculate_ortho_projection(),
            CameraMode::Fill(work_size) => self.calculate_fill_projection(work_size),
            CameraMode::AspectFit(work_size) => self.calculate_aspect_fit_projection(work_size),
            CameraMode::AspectFill(work_size) => self.calculate_aspect_fill_projection(work_size),
        }
    }

    fn calculate_transform(&mut self) {
        let pos = self.position - self.work_size * 0.5 / self.scale;
        let translate = Mat3::from_translation(pos * -1.0);
        let scale = Mat3::from_scale(self.scale);
        self.transform = scale * translate;
    }

    fn calculate_ortho_projection(&mut self) {
        let (projection, ratio) = calculate_ortho_projection(self.size);
        self.projection = projection;
        self.ratio = ratio;
    }

    fn calculate_fill_projection(&mut self, work_size: Vec2) {
        let (projection, ratio) = calculate_fill_projection(self.size, work_size);
        self.projection = projection;
        self.ratio = ratio;
    }

    fn calculate_aspect_fit_projection(&mut self, work_size: Vec2) {
        let (projection, ratio) = calculate_aspect_fit_projection(self.size, work_size);
        self.projection = projection;
        self.ratio = ratio;
    }

    fn calculate_aspect_fill_projection(&mut self, work_size: Vec2) {
        let (projection, ratio) = calculate_aspect_fill_projection(self.size, work_size);
        self.projection = projection;
        self.ratio = ratio;
    }
}

fn calculate_ortho_projection(win_size: Vec2) -> (Mat4, Vec2) {
    let projection = Mat4::orthographic_rh_gl(0.0, win_size.x, win_size.y, 0.0, -1.0, 1.0);
    (projection, vec2(1.0, 1.0))
}

fn calculate_fill_projection(win_size: Vec2, work_size: Vec2) -> (Mat4, Vec2) {
    let ratio = vec2(win_size.x / work_size.x, win_size.y / work_size.y);
    (Mat4::IDENTITY, ratio)
}

fn calculate_aspect_fit_projection(win_size: Vec2, work_size: Vec2) -> (Mat4, Vec2) {
    let ratio = (win_size.x / work_size.x).min(win_size.y / work_size.y);
    let ratio = Vec2::splat(ratio);
    let scale = Mat4::from_scale(vec3(ratio.x, ratio.y, 1.0));
    let pos = (win_size - work_size * ratio) * 0.5;
    let position = vec3(pos.x, pos.y, 1.0);
    let translation = Mat4::from_translation(position);
    let projection = Mat4::orthographic_rh_gl(0.0, win_size.x, win_size.y, 0.0, -1.0, 1.0);
    let final_projection = projection * translation * scale;
    (final_projection, ratio)
}

fn calculate_aspect_fill_projection(win_size: Vec2, work_size: Vec2) -> (Mat4, Vec2) {
    let ratio = (win_size.x / work_size.x).max(win_size.y / work_size.y);
    let ratio = Vec2::splat(ratio);
    (Mat4::IDENTITY, ratio)
}

// This returns a projection that keeps the aspect ratio while scaling
// and fitting the content in our window
// It also returns the ratio in case we need it to calculate positions
// or manually scale something
// fn calc_projection(win_size: Vec2, work_size: Vec2) -> (Mat4, f32) {
//     let ratio = (win_size.x / work_size.x).min(win_size.y / work_size.y);

//     let projection = Mat4::orthographic_rh_gl(0.0, win_size.x, win_size.y, 0.0, -1.0, 1.0);
//     let scale = Mat4::from_scale(vec3(ratio, ratio, 1.0));
//     let position = vec3(
//         (win_size.x - work_size.x * ratio) * 0.5,
//         (win_size.y - work_size.y * ratio) * 0.5,
//         1.0,
//     );
//     let translation = Mat4::from_translation(position);
//     (projection * translation * scale, ratio)
// }