viewport/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#![deny(missing_docs)]

//! A library for storing viewport information

extern crate float;

use float::{Float, FromPrimitive};

/// Stores viewport information.
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Viewport {
    /// Viewport in pixels.
    /// ```[x, y, width, height]``` where ```(x, y)``` is lower left corner.
    pub rect: [i32; 4],
    /// The size of frame buffer in pixels.
    pub draw_size: [u32; 2],
    /// The size of window in points.
    pub window_size: [f64; 2],
}

impl Viewport {
    /// Computes absolute transform for 2D graphics,
    /// which uses a row major 2x3 matrix.
    ////
    /// The origin is in the upper left corner of the viewport rectangle.
    /// The x-axis points to the right, and the y-axis points down.
    /// The units are in points (window coordinates).
    ///
    /// It is assumed that the underlying coordinate system is normalized
    /// with the origin in the center, such that ```(-1.0, 1.0)``` in the
    /// underlying coordinate system corresponds to the
    /// upper left corner of the viewport.
    #[must_use]
    pub fn abs_transform<T: Float>(&self) -> [[T; 3]; 2] {
        let (dw, dh) = (f64::from(self.draw_size[0]), f64::from(self.draw_size[1]));
        let sx = 2.0 * (dw / self.window_size[0]) / f64::from(self.rect[2]);
        let sy = -2.0 * (dh / self.window_size[1]) / f64::from(self.rect[3]);
        let f = |x| FromPrimitive::from_f64(x);
        [
            [f(sx), f(0.0), f(-1.0)],
            [f(0.0), f(sy), f(1.0)]
        ]
    }
}

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

    #[test]
    fn abs() {
        let transform = |mat: [[f64; 3]; 2], (px, py)| -> (f64, f64) {
            (mat[0][0] * px + mat[0][1] * py + mat[0][2],
            mat[1][0] * px + mat[1][1] * py + mat[1][2])
         };

        let viewport = Viewport {
            rect: [0, 0, 100, 200],
            draw_size: [100, 200],
            window_size: [100.0, 200.0],
        };
        let abs = viewport.abs_transform();
        assert_eq!(abs, [
                [0.02, 0.0, -1.0],
                [0.0, -0.01, 1.0]
            ]);
        assert_eq!(transform(abs, (0.0, 0.0)), (-1.0, 1.0));
        assert_eq!(transform(abs, (50.0, 100.0)), (0.0, 0.0));
        assert_eq!(transform(abs, (100.0, 200.0)), (1.0, -1.0));

        let viewport = Viewport {
            rect: [10, 10, 80, 80],
            draw_size: [100, 100],
            window_size: [50.0, 50.0],
        };
        let abs = viewport.abs_transform();
        assert_eq!(abs, [
                [0.05, 0.0, -1.0],
                [0.0, -0.05, 1.0]
            ]);
        assert_eq!(transform(abs, (0.0, 0.0)), (-1.0, 1.0));
        // The viewport size is 80 pixels, 40 points, therefore center is 20.
        assert_eq!(transform(abs, (20.0, 20.0)), (0.0, 0.0));
        assert_eq!(transform(abs, (40.0, 40.0)), (1.0, -1.0));
    }
}