Skip to main content

loam_render/
lattice.rs

1/// Pixel rectangle, origin top-left, +y down.
2#[derive(Copy, Clone, Debug, PartialEq, Eq)]
3pub struct Viewport {
4    pub x: u32,
5    pub y: u32,
6    pub width: u32,
7    pub height: u32,
8}
9
10impl Viewport {
11    pub fn full(framebuffer: [u32; 2]) -> Self {
12        Self {
13            x: 0,
14            y: 0,
15            width: framebuffer[0],
16            height: framebuffer[1],
17        }
18    }
19
20    /// Empty when the panel covers the framebuffer.
21    pub fn right_of_left_panel(panel_width: u32, framebuffer: [u32; 2]) -> Self {
22        let panel = panel_width.min(framebuffer[0]);
23        Self {
24            x: panel,
25            y: 0,
26            width: framebuffer[0] - panel,
27            height: framebuffer[1],
28        }
29    }
30
31    pub fn apply(&self, rp: &mut wgpu::RenderPass<'_>) {
32        rp.set_viewport(
33            self.x as f32,
34            self.y as f32,
35            self.width as f32,
36            self.height as f32,
37            0.0,
38            1.0,
39        );
40    }
41
42    pub fn resolution_f32(&self) -> [f32; 2] {
43        [self.width as f32, self.height as f32]
44    }
45
46    /// The last cell absorbs the rounding remainder; empty when `n == 0`.
47    pub fn split_horizontal(&self, n: u32) -> impl Iterator<Item = Viewport> {
48        let viewport = *self;
49        (0..n).map(move |i| {
50            let width = viewport.width / n;
51            Viewport {
52                x: viewport.x + i * width,
53                width: if i + 1 == n {
54                    viewport.width - i * width
55                } else {
56                    width
57                },
58                ..viewport
59            }
60        })
61    }
62
63    pub fn split_vertical(&self, n: u32) -> impl Iterator<Item = Viewport> {
64        let viewport = *self;
65        (0..n).map(move |i| {
66            let height = viewport.height / n;
67            Viewport {
68                y: viewport.y + i * height,
69                height: if i + 1 == n {
70                    viewport.height - i * height
71                } else {
72                    height
73                },
74                ..viewport
75            }
76        })
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn right_of_left_panel_clamps_when_panel_exceeds_framebuffer() {
86        let v = Viewport::right_of_left_panel(2000, [1280, 720]);
87        assert_eq!(v.x, 1280);
88        assert_eq!(v.width, 0);
89    }
90
91    #[test]
92    fn split_horizontal_tiles_without_gaps() {
93        let v = Viewport {
94            x: 100,
95            y: 50,
96            width: 17,
97            height: 30,
98        };
99        let cells: Vec<_> = v.split_horizontal(5).collect();
100        assert_eq!(cells.len(), 5);
101        assert_eq!(cells.first().unwrap().x, 100);
102        assert_eq!(
103            cells.last().unwrap().x + cells.last().unwrap().width,
104            100 + 17
105        );
106        for win in cells.windows(2) {
107            assert_eq!(win[0].x + win[0].width, win[1].x);
108        }
109        assert_eq!(cells[0].width, 3);
110        assert_eq!(cells[4].width, 5);
111    }
112
113    #[test]
114    fn split_horizontal_n_zero_returns_empty() {
115        let v = Viewport::full([100, 50]);
116        assert!(v.split_horizontal(0).next().is_none());
117    }
118}