Skip to main content

mirage_engine/mesh/
sheet.rs

1//! A texture's cell grid, and the part of a texture one draw samples.
2
3use crate::math::{UVec2, Vec2, Vec4};
4
5/// A texture's cells, laid out in a grid.
6///
7/// Required if you want one texture to hold more than one frame of a sprite;
8/// [`Sheet::cell`] selects one of them.
9#[derive(Clone, Copy, Debug, Eq, PartialEq)]
10pub struct Sheet {
11    columns: u32,
12    rows: u32,
13}
14
15impl Sheet {
16    /// A grid `size.x` columns across and `size.y` rows down; a side of zero
17    /// counts as one.
18    pub fn new(size: UVec2) -> Self {
19        // A grid with no cells would divide by zero; counts are what a game
20        // steps an animation with, so the API takes plain ones over a
21        // non-zero type.
22        Self {
23            columns: size.x.max(1),
24            rows: size.y.max(1),
25        }
26    }
27
28    /// The cell `index` selects, counted row by row from the top left; an
29    /// index past the last cell wraps around the grid.
30    pub fn cell(&self, index: u32) -> Frame {
31        self.cell_at(UVec2::new(index % self.columns, index / self.columns))
32    }
33
34    /// The cell at `at.x` and `at.y`; each wraps around its own side of the
35    /// grid.
36    pub fn cell_at(&self, at: UVec2) -> Frame {
37        let size = Vec2::new(1.0 / self.columns as f32, 1.0 / self.rows as f32);
38        let cell = Vec2::new((at.x % self.columns) as f32, (at.y % self.rows) as f32);
39
40        Frame {
41            min: cell * size,
42            max: (cell + Vec2::ONE) * size,
43        }
44    }
45}
46
47/// The part of a texture a draw samples.
48///
49/// The part a frame covers is its window; a draw samples through it. The
50/// whole texture by default; [`Sheet::cell`] and [`Frame::rect`] select a
51/// part of one.
52#[derive(Clone, Copy, Debug, PartialEq)]
53pub struct Frame {
54    min: Vec2,
55    max: Vec2,
56}
57
58impl Frame {
59    /// The part between `min` and `max`, in `0..1` texture coordinates.
60    ///
61    /// A window past those coordinates repeats the texture: a `max.x` of
62    /// `3.0` lays three copies of it across the draw.
63    pub fn rect(min: Vec2, max: Vec2) -> Self {
64        Self { min, max }
65    }
66
67    /// The same window with its `x` edges the other way round, so what a
68    /// draw samples through it is mirrored.
69    pub fn mirrored(self) -> Self {
70        Self {
71            min: Vec2::new(self.max.x, self.min.y),
72            max: Vec2::new(self.min.x, self.max.y),
73        }
74    }
75
76    /// The lane the shader reads it from: the part's start, then its extent
77    /// of the texture.
78    pub(crate) fn lane(self) -> Vec4 {
79        let size = self.max - self.min;
80
81        Vec4::new(self.min.x, self.min.y, size.x, size.y)
82    }
83}
84
85impl Default for Frame {
86    /// The whole texture.
87    fn default() -> Self {
88        Self {
89            min: Vec2::ZERO,
90            max: Vec2::ONE,
91        }
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn the_whole_texture_is_what_a_draw_samples_until_it_asks_for_a_part() {
101        assert_eq!(Frame::default().lane(), Vec4::new(0.0, 0.0, 1.0, 1.0));
102        assert_eq!(
103            Frame::rect(Vec2::new(0.25, 0.5), Vec2::new(0.75, 1.0)).lane(),
104            Vec4::new(0.25, 0.5, 0.5, 0.5)
105        );
106    }
107
108    #[test]
109    fn the_cells_of_a_grid_are_counted_row_by_row_from_the_top_left() {
110        let sheet = Sheet::new(UVec2::new(4, 2));
111        let cell = |index| sheet.cell(index).lane();
112
113        assert_eq!(cell(0), Vec4::new(0.0, 0.0, 0.25, 0.5));
114        assert_eq!(cell(3), Vec4::new(0.75, 0.0, 0.25, 0.5));
115        assert_eq!(cell(4), Vec4::new(0.0, 0.5, 0.25, 0.5));
116    }
117
118    #[test]
119    fn a_cell_past_the_last_wraps_around_the_grid() {
120        let sheet = Sheet::new(UVec2::new(4, 2));
121
122        assert_eq!(sheet.cell(8), sheet.cell(0));
123        assert_eq!(sheet.cell(11), sheet.cell(3));
124        assert_eq!(sheet.cell(u32::MAX), sheet.cell(u32::MAX % 8));
125    }
126
127    #[test]
128    fn a_cell_named_by_its_two_axes_is_the_one_the_count_reaches() {
129        let sheet = Sheet::new(UVec2::new(4, 2));
130
131        assert_eq!(sheet.cell_at(UVec2::new(0, 0)), sheet.cell(0));
132        assert_eq!(sheet.cell_at(UVec2::new(3, 0)), sheet.cell(3));
133        assert_eq!(sheet.cell_at(UVec2::new(1, 1)), sheet.cell(5));
134    }
135
136    #[test]
137    fn a_column_or_row_past_the_grid_wraps_around_its_own_side() {
138        let sheet = Sheet::new(UVec2::new(4, 2));
139
140        assert_eq!(
141            sheet.cell_at(UVec2::new(4, 1)),
142            sheet.cell_at(UVec2::new(0, 1))
143        );
144        assert_eq!(
145            sheet.cell_at(UVec2::new(2, 2)),
146            sheet.cell_at(UVec2::new(2, 0))
147        );
148        assert_eq!(
149            sheet.cell_at(UVec2::new(u32::MAX, u32::MAX)),
150            sheet.cell_at(UVec2::new(u32::MAX % 4, u32::MAX % 2))
151        );
152    }
153
154    #[test]
155    fn a_grid_with_a_side_of_nothing_still_names_the_whole_texture() {
156        for sheet in [
157            Sheet::new(UVec2::new(0, 0)),
158            Sheet::new(UVec2::new(0, 1)),
159            Sheet::new(UVec2::new(1, 0)),
160        ] {
161            assert_eq!(sheet.cell(0), Frame::default());
162            assert_eq!(sheet.cell(7), Frame::default());
163        }
164    }
165
166    #[test]
167    fn a_mirrored_window_names_the_same_two_edges_the_other_way_round() {
168        let min = Vec2::new(0.25, 0.5);
169        let max = Vec2::new(0.75, 1.0);
170        let window = Frame::rect(min, max);
171
172        assert_eq!(
173            window.mirrored(),
174            Frame::rect(Vec2::new(max.x, min.y), Vec2::new(min.x, max.y))
175        );
176        assert_eq!(window.mirrored().mirrored(), window, "and twice is itself");
177    }
178
179    #[test]
180    fn a_mirrored_cell_starts_where_the_cell_it_came_from_ends() {
181        let sheet = Sheet::new(UVec2::new(3, 2));
182
183        for index in 0..6 {
184            let cell = sheet.cell(index);
185            let [x, y, width, height] = cell.lane().to_array();
186
187            assert_eq!(
188                cell.mirrored().lane(),
189                Vec4::new(x + width, y, -width, height)
190            );
191            assert_eq!(cell.mirrored().mirrored(), cell, "and twice is the cell");
192        }
193    }
194
195    #[test]
196    fn the_cells_of_a_grid_cover_it_and_nothing_twice() {
197        let sheet = Sheet::new(UVec2::new(2, 2));
198        let cells: Vec<Vec4> = (0..4).map(|index| sheet.cell(index).lane()).collect();
199
200        assert_eq!(
201            cells,
202            vec![
203                Vec4::new(0.0, 0.0, 0.5, 0.5),
204                Vec4::new(0.5, 0.0, 0.5, 0.5),
205                Vec4::new(0.0, 0.5, 0.5, 0.5),
206                Vec4::new(0.5, 0.5, 0.5, 0.5),
207            ]
208        );
209    }
210}