Skip to main content

polytope_playground/
strip.rs

1use loam::render::Viewport;
2
3use crate::catalog::SHAPE_CATALOG;
4
5pub(crate) const MIN_CELLS: usize = 3;
6
7pub(crate) const MAX_CELLS: usize = 21;
8
9pub(crate) const MIN_T_EXTENT: f32 = 0.1;
10
11pub(crate) const MAX_T_EXTENT: f32 = 10.0;
12
13const DEFAULT_T_EXTENT: f32 = 3.0;
14
15const DEFAULT_SUBJECT: usize = 3;
16
17#[derive(Clone, Copy, Debug, PartialEq)]
18pub(crate) struct Strip {
19    pub(crate) on: bool,
20    pub(crate) w: bool,
21    pub(crate) t: bool,
22    pub(crate) swap_axes: bool,
23    pub(crate) count_w: usize,
24    pub(crate) count_t: usize,
25    pub(crate) t_extent: f32,
26    pub(crate) subject: usize,
27}
28
29impl Default for Strip {
30    fn default() -> Self {
31        Self {
32            on: false,
33            w: true,
34            t: false,
35            swap_axes: false,
36            count_w: 11,
37            count_t: 5,
38            t_extent: DEFAULT_T_EXTENT,
39            subject: DEFAULT_SUBJECT,
40        }
41    }
42}
43
44#[derive(Clone, Copy, Debug, PartialEq)]
45pub(crate) struct Cell {
46    pub(crate) viewport: Viewport,
47    pub(crate) w: f32,
48    pub(crate) t: f32,
49}
50
51impl Strip {
52    pub(crate) fn subject(&self) -> usize {
53        self.subject.min(SHAPE_CATALOG.len() - 1)
54    }
55
56    pub(crate) fn grid(&self) -> (usize, usize, bool) {
57        match (self.w, self.t) {
58            (true, true) if self.swap_axes => (self.count_t, self.count_w, false),
59            (true, true) => (self.count_w, self.count_t, true),
60            (true, false) => (self.count_w, 1, true),
61            (false, true) => (1, self.count_t, true),
62            (false, false) => (1, 1, true),
63        }
64    }
65
66    pub(crate) fn cells(&self, frame: [u32; 2], slice: f32, w_extent: f32, out: &mut Vec<Cell>) {
67        out.clear();
68        let (cols, rows, w_on_cols) = self.grid();
69        if cols == 0 || rows == 0 {
70            return;
71        }
72        for (col, column) in Viewport::full(frame)
73            .split_horizontal(cols as u32)
74            .enumerate()
75        {
76            for (row, viewport) in column.split_vertical(rows as u32).enumerate() {
77                let (w_index, w_count, t_index, t_count) = if w_on_cols {
78                    (col, cols, row, rows)
79                } else {
80                    (row, rows, col, cols)
81                };
82                let across = if !self.w || w_count <= 1 {
83                    0.5
84                } else {
85                    w_index as f32 / (w_count - 1) as f32
86                };
87                let along = if !self.t || t_count <= 1 {
88                    0.0
89                } else {
90                    t_index as f32 / (t_count - 1) as f32 * self.t_extent
91                };
92                out.push(Cell {
93                    viewport,
94                    w: slice + (across * 2.0 - 1.0) * w_extent,
95                    t: along,
96                });
97            }
98        }
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    const FRAME: [u32; 2] = [1280, 720];
107
108    #[test]
109    fn the_grid_tiles_the_frame_once_per_cell_and_spans_the_w_extent() {
110        const EXTENT: f32 = 0.35;
111        const SLICE: f32 = 0.1;
112        let strip = Strip {
113            on: true,
114            w: true,
115            t: true,
116            count_w: 5,
117            count_t: 3,
118            t_extent: 2.0,
119            ..Strip::default()
120        };
121        let mut cells = Vec::new();
122        strip.cells(FRAME, SLICE, EXTENT, &mut cells);
123        assert_eq!(cells.len(), 15, "a 5 by 3 grid has fifteen cells");
124
125        let covered: u64 = cells
126            .iter()
127            .map(|cell| u64::from(cell.viewport.width) * u64::from(cell.viewport.height))
128            .sum();
129        assert_eq!(
130            covered,
131            u64::from(FRAME[0]) * u64::from(FRAME[1]),
132            "the cells leave a gap or overlap"
133        );
134
135        assert!((cells[0].w - (SLICE - EXTENT)).abs() < 1e-6);
136        assert!((cells[14].w - (SLICE + EXTENT)).abs() < 1e-6);
137        assert!(
138            (cells[0].t - 0.0).abs() < 1e-6 && (cells[2].t - 2.0).abs() < 1e-6,
139            "the t fan spans the extent down each column"
140        );
141    }
142
143    #[test]
144    fn one_axis_off_leaves_a_single_row_or_column_at_the_slider() {
145        let mut cells = Vec::new();
146        let across = Strip {
147            on: true,
148            w: true,
149            t: false,
150            count_w: 4,
151            ..Strip::default()
152        };
153        across.cells(FRAME, 0.0, 1.0, &mut cells);
154        assert_eq!(cells.len(), 4);
155        assert!(cells.iter().all(|cell| cell.t == 0.0));
156
157        let forward = Strip {
158            on: true,
159            w: false,
160            t: true,
161            count_t: 4,
162            ..Strip::default()
163        };
164        forward.cells(FRAME, 0.25, 1.0, &mut cells);
165        assert_eq!(cells.len(), 4);
166        assert!(
167            cells.iter().all(|cell| (cell.w - 0.25).abs() < 1e-6),
168            "with w off every cell cuts at the slider's own w"
169        );
170    }
171}