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}