ngen 0.1.4

A very simple game engine using OpenGL
Documentation
use crate::{
    assets::Sprite,
    math::{Mat4, Vec2, Vec3, Vec4},
};

mod opengl;
pub(crate) use opengl::OpenGLRenderer;

#[repr(C)]
#[derive(Clone, Copy)]
pub struct Vertex {
    pub pos: Vec3,
    pub col: Vec4,
    pub uv: Vec2,
    pub texture_slot: f32,
}

impl Vertex {
    pub const fn new(pos: Vec3, col: Vec4) -> Self {
        Self {
            pos,
            col,
            uv: Vec2::new(1., 1.),
            texture_slot: 0.,
        }
    }
}

pub enum RenderCommand {
    RenderQuads {
        offset: usize,
        num_quads: usize,
        transform: Mat4,
    },

    ClearScreen {
        color: Vec4,
    },
}

pub struct RenderCommands {
    vert_buffer_id: u32,
    index_buffer_id: u32,
    white_texture_id: u32,

    vertices: Vec<Vertex>,
    textures: Vec<u32>,
    commands: Vec<RenderCommand>,
}

impl RenderCommands {
    fn new(
        vert_buffer_id: u32,
        index_buffer_id: u32,
        white_texture_id: u32,
        max_quads: usize,
    ) -> Self {
        let vertices = Vec::with_capacity(max_quads * 4);
        let commands = Vec::new();
        let textures = Vec::new();

        Self {
            vert_buffer_id,
            index_buffer_id,
            white_texture_id,

            vertices,
            textures,
            commands,
        }
    }

    fn reset(&mut self) {
        self.vertices.clear();
        self.commands.clear();
        self.textures.clear();
        self.textures.push(self.white_texture_id);
    }

    pub fn begin_render_layer(&mut self, transform: Mat4) -> RenderLayer<'_> {
        RenderLayer::new(self, transform)
    }

    pub fn with_render_layer<F: FnMut(&mut RenderLayer<'_>)>(&mut self, transform: Mat4, mut f: F) {
        let mut render_layer = self.begin_render_layer(transform);
        f(&mut render_layer);
        render_layer.end();
    }

    fn push(&mut self, command: RenderCommand) {
        self.commands.push(command);
    }
}

pub struct RenderLayer<'rc> {
    commands: &'rc mut RenderCommands,
    transform: Mat4,
    offset: usize,
    num_quads: usize,
}

impl<'rc> RenderLayer<'rc> {
    fn new(commands: &'rc mut RenderCommands, transform: Mat4) -> Self {
        let offset = commands.vertices.len();

        Self {
            commands,
            transform,
            offset,
            num_quads: 0,
        }
    }

    pub fn clear_screen(&mut self, color: Vec4) {
        self.commands.push(RenderCommand::ClearScreen { color });
    }

    pub fn push_quad(&mut self, pos: Vec2, dim: Vec2, color: Vec4) {
        self.push_tex_quad(pos, dim, color, self.commands.white_texture_id);
    }

    fn get_texture_slot(&mut self, texture_id: u32) -> f32 {
        if let Some(loc) = self.commands.textures.iter().position(|i| *i == texture_id) {
            loc as f32
        } else {
            let loc = self.commands.textures.len();
            self.commands.textures.push(texture_id);
            loc as f32
        }
    }

    pub fn push_tex_quad(&mut self, pos: Vec2, dim: Vec2, color: Vec4, texture_id: u32) {
        let Vec2 {
            x: half_width,
            y: half_height,
        } = dim * 0.5;

        let bottom_left = pos + Vec2::new(-half_width, -half_height);
        let bottom_right = pos + Vec2::new(half_width, -half_height);
        let top_right = pos + Vec2::new(half_width, half_height);
        let top_left = pos + Vec2::new(-half_width, half_height);

        let to_vec3 = |Vec2 { x, y }: Vec2| -> Vec3 { Vec3::new(x, y, 0.) };
        let texture_slot = self.get_texture_slot(texture_id);

        let mut bottom_left = Vertex::new(to_vec3(bottom_left), color);
        bottom_left.uv = Vec2::new(0., 1.);
        bottom_left.texture_slot = texture_slot;

        let mut bottom_right = Vertex::new(to_vec3(bottom_right), color);
        bottom_right.uv = Vec2::new(1., 1.);
        bottom_right.texture_slot = texture_slot;

        let mut top_right = Vertex::new(to_vec3(top_right), color);
        top_right.uv = Vec2::new(1., 0.);
        top_right.texture_slot = texture_slot;

        let mut top_left = Vertex::new(to_vec3(top_left), color);
        top_left.uv = Vec2::new(0., 0.);
        top_left.texture_slot = texture_slot;

        self.store_quad([bottom_left, bottom_right, top_right, top_left]);
    }

    pub fn push_sprite(&mut self, pos: Vec2, dim: Vec2, color: Vec4, sprite: Sprite) {
        let Vec2 {
            x: half_width,
            y: half_height,
        } = dim * 0.5;

        let bottom_left = pos + Vec2::new(-half_width, -half_height);
        let bottom_right = pos + Vec2::new(half_width, -half_height);
        let top_right = pos + Vec2::new(half_width, half_height);
        let top_left = pos + Vec2::new(-half_width, half_height);

        let to_vec3 = |Vec2 { x, y }: Vec2| -> Vec3 { Vec3::new(x, y, 0.) };
        let texture_slot = self.get_texture_slot(sprite.texture_id);

        let mut bottom_left = Vertex::new(to_vec3(bottom_left), color);
        bottom_left.uv = sprite.uv[3];
        bottom_left.texture_slot = texture_slot;

        let mut bottom_right = Vertex::new(to_vec3(bottom_right), color);
        bottom_right.uv = sprite.uv[2];
        bottom_right.texture_slot = texture_slot;

        let mut top_right = Vertex::new(to_vec3(top_right), color);
        top_right.uv = sprite.uv[1];
        top_right.texture_slot = texture_slot;

        let mut top_left = Vertex::new(to_vec3(top_left), color);
        top_left.uv = sprite.uv[0];
        top_left.texture_slot = texture_slot;

        self.store_quad([bottom_left, bottom_right, top_right, top_left]);
    }

    fn store_quad(&mut self, [v1, v2, v3, v4]: [Vertex; 4]) {
        self.num_quads += 1;
        self.commands.vertices.push(v1);
        self.commands.vertices.push(v2);
        self.commands.vertices.push(v3);
        self.commands.vertices.push(v4);
    }

    pub fn end(self) -> &'rc mut RenderCommands {
        let Self {
            offset,
            num_quads,
            transform,
            ..
        } = self;

        if num_quads > 0 {
            self.commands.push(RenderCommand::RenderQuads {
                offset,
                num_quads,
                transform,
            });
        }

        self.commands
    }
}