mirage-engine 0.1.1

Mirage, an immediate-mode 3D engine for simple games on desktop and the browser
Documentation
//! A texture's cell grid, and the part of a texture one draw samples.

use crate::math::{UVec2, Vec2, Vec4};

/// A texture's cells, laid out in a grid.
///
/// Required if you want one texture to hold more than one frame of a sprite;
/// [`Sheet::cell`] selects one of them.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Sheet {
    columns: u32,
    rows: u32,
}

impl Sheet {
    /// A grid `size.x` columns across and `size.y` rows down; a side of zero
    /// counts as one.
    pub fn new(size: UVec2) -> Self {
        // A grid with no cells would divide by zero; counts are what a game
        // steps an animation with, so the API takes plain ones over a
        // non-zero type.
        Self {
            columns: size.x.max(1),
            rows: size.y.max(1),
        }
    }

    /// The cell `index` selects, counted row by row from the top left; an
    /// index past the last cell wraps around the grid.
    pub fn cell(&self, index: u32) -> Frame {
        self.cell_at(UVec2::new(index % self.columns, index / self.columns))
    }

    /// The cell at `at.x` and `at.y`; each wraps around its own side of the
    /// grid.
    pub fn cell_at(&self, at: UVec2) -> Frame {
        let size = Vec2::new(1.0 / self.columns as f32, 1.0 / self.rows as f32);
        let cell = Vec2::new((at.x % self.columns) as f32, (at.y % self.rows) as f32);

        Frame {
            min: cell * size,
            max: (cell + Vec2::ONE) * size,
        }
    }
}

/// The part of a texture a draw samples.
///
/// The part a frame covers is its window; a draw samples through it. The
/// whole texture by default; [`Sheet::cell`] and [`Frame::rect`] select a
/// part of one.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Frame {
    min: Vec2,
    max: Vec2,
}

impl Frame {
    /// The part between `min` and `max`, in `0..1` texture coordinates.
    ///
    /// A window past those coordinates repeats the texture: a `max.x` of
    /// `3.0` lays three copies of it across the draw.
    pub fn rect(min: Vec2, max: Vec2) -> Self {
        Self { min, max }
    }

    /// The same window with its `x` edges the other way round, so what a
    /// draw samples through it is mirrored.
    pub fn mirrored(self) -> Self {
        Self {
            min: Vec2::new(self.max.x, self.min.y),
            max: Vec2::new(self.min.x, self.max.y),
        }
    }

    /// The lane the shader reads it from: the part's start, then its extent
    /// of the texture.
    pub(crate) fn lane(self) -> Vec4 {
        let size = self.max - self.min;

        Vec4::new(self.min.x, self.min.y, size.x, size.y)
    }
}

impl Default for Frame {
    /// The whole texture.
    fn default() -> Self {
        Self {
            min: Vec2::ZERO,
            max: Vec2::ONE,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn the_whole_texture_is_what_a_draw_samples_until_it_asks_for_a_part() {
        assert_eq!(Frame::default().lane(), Vec4::new(0.0, 0.0, 1.0, 1.0));
        assert_eq!(
            Frame::rect(Vec2::new(0.25, 0.5), Vec2::new(0.75, 1.0)).lane(),
            Vec4::new(0.25, 0.5, 0.5, 0.5)
        );
    }

    #[test]
    fn the_cells_of_a_grid_are_counted_row_by_row_from_the_top_left() {
        let sheet = Sheet::new(UVec2::new(4, 2));
        let cell = |index| sheet.cell(index).lane();

        assert_eq!(cell(0), Vec4::new(0.0, 0.0, 0.25, 0.5));
        assert_eq!(cell(3), Vec4::new(0.75, 0.0, 0.25, 0.5));
        assert_eq!(cell(4), Vec4::new(0.0, 0.5, 0.25, 0.5));
    }

    #[test]
    fn a_cell_past_the_last_wraps_around_the_grid() {
        let sheet = Sheet::new(UVec2::new(4, 2));

        assert_eq!(sheet.cell(8), sheet.cell(0));
        assert_eq!(sheet.cell(11), sheet.cell(3));
        assert_eq!(sheet.cell(u32::MAX), sheet.cell(u32::MAX % 8));
    }

    #[test]
    fn a_cell_named_by_its_two_axes_is_the_one_the_count_reaches() {
        let sheet = Sheet::new(UVec2::new(4, 2));

        assert_eq!(sheet.cell_at(UVec2::new(0, 0)), sheet.cell(0));
        assert_eq!(sheet.cell_at(UVec2::new(3, 0)), sheet.cell(3));
        assert_eq!(sheet.cell_at(UVec2::new(1, 1)), sheet.cell(5));
    }

    #[test]
    fn a_column_or_row_past_the_grid_wraps_around_its_own_side() {
        let sheet = Sheet::new(UVec2::new(4, 2));

        assert_eq!(
            sheet.cell_at(UVec2::new(4, 1)),
            sheet.cell_at(UVec2::new(0, 1))
        );
        assert_eq!(
            sheet.cell_at(UVec2::new(2, 2)),
            sheet.cell_at(UVec2::new(2, 0))
        );
        assert_eq!(
            sheet.cell_at(UVec2::new(u32::MAX, u32::MAX)),
            sheet.cell_at(UVec2::new(u32::MAX % 4, u32::MAX % 2))
        );
    }

    #[test]
    fn a_grid_with_a_side_of_nothing_still_names_the_whole_texture() {
        for sheet in [
            Sheet::new(UVec2::new(0, 0)),
            Sheet::new(UVec2::new(0, 1)),
            Sheet::new(UVec2::new(1, 0)),
        ] {
            assert_eq!(sheet.cell(0), Frame::default());
            assert_eq!(sheet.cell(7), Frame::default());
        }
    }

    #[test]
    fn a_mirrored_window_names_the_same_two_edges_the_other_way_round() {
        let min = Vec2::new(0.25, 0.5);
        let max = Vec2::new(0.75, 1.0);
        let window = Frame::rect(min, max);

        assert_eq!(
            window.mirrored(),
            Frame::rect(Vec2::new(max.x, min.y), Vec2::new(min.x, max.y))
        );
        assert_eq!(window.mirrored().mirrored(), window, "and twice is itself");
    }

    #[test]
    fn a_mirrored_cell_starts_where_the_cell_it_came_from_ends() {
        let sheet = Sheet::new(UVec2::new(3, 2));

        for index in 0..6 {
            let cell = sheet.cell(index);
            let [x, y, width, height] = cell.lane().to_array();

            assert_eq!(
                cell.mirrored().lane(),
                Vec4::new(x + width, y, -width, height)
            );
            assert_eq!(cell.mirrored().mirrored(), cell, "and twice is the cell");
        }
    }

    #[test]
    fn the_cells_of_a_grid_cover_it_and_nothing_twice() {
        let sheet = Sheet::new(UVec2::new(2, 2));
        let cells: Vec<Vec4> = (0..4).map(|index| sheet.cell(index).lane()).collect();

        assert_eq!(
            cells,
            vec![
                Vec4::new(0.0, 0.0, 0.5, 0.5),
                Vec4::new(0.5, 0.0, 0.5, 0.5),
                Vec4::new(0.0, 0.5, 0.5, 0.5),
                Vec4::new(0.5, 0.5, 0.5, 0.5),
            ]
        );
    }
}