aboutsummaryrefslogtreecommitdiff
path: root/src/transform.rs
blob: fa24636ad38549d61911cee046f64c8273596245 (plain) (blame)
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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
//! Transformation module
//!
//! Useful to turn on-screen coordinates into measurements of the "real" world the map describes
//! and the other way around.

use raylib::prelude::*;

const STANDARD_PIXELS_PER_M: f32 = 64.;
const MIN_PIXELS_PER_M: f32 = 0.5;
const MAX_PIXELS_PER_M: f32 = 10_000.;

pub struct Transform {
    /// The (not necessarily natural) number of pixels per m, i.e. the current scale of the map
    pixels_per_m: f32,
    /// The vector the entire on-screen map is moved by in pixels
    translation_px: Vector2,
}

impl Transform {
    /// Create a new standard transformation for the map.
    pub fn new() -> Self {
        Self {
            pixels_per_m: STANDARD_PIXELS_PER_M,
            translation_px: Vector2::new(0., 0.),
        }
    }

    /// Convert a point that is given in meters into the corresponding point in pixels.
    #[inline]
    pub fn point_m_to_px(&self, point: Vector2) -> Vector2 {
        // Start by converting the absolute position in meters into the absolute position in
        // pixels, then add the translation of the screen.
        (point * self.pixels_per_m) + self.translation_px
    }

    /// Convert an on-screen point into an absolute point with values in meters.
    #[inline]
    pub fn point_px_to_m(&self, point: Vector2) -> Vector2 {
        // Start by subtracting the pixel translation and afterwards convert these absolute pixel
        // measurements into meters.
        (point - self.translation_px) / self.pixels_per_m
    }

    /// Convert a length given in meters into a length in pixels
    #[inline]
    pub fn length_m_to_px(&self, length: f32) -> f32 {
        length * self.pixels_per_m
    }

    /// Convert a length given in pixels into a length in meters
    #[inline]
    pub fn length_px_to_m(&self, length: f32) -> f32 {
        length / self.pixels_per_m
    }

    /// Convert a rectangle which has measurements in meters into one of pixels
    #[inline]
    pub fn rect_m_to_px(&self, rect: Rectangle) -> Rectangle {
        let left_upper = self.point_m_to_px(Vector2::new(rect.x, rect.y));
        Rectangle::new(
            left_upper.x,
            left_upper.y,
            self.length_m_to_px(rect.width),
            self.length_m_to_px(rect.height),
        )
    }

    /// Convert a rectangle which has measurements in pixels into one of meters
    #[inline]
    pub fn rect_px_to_m(&self, rect: Rectangle) -> Rectangle {
        let left_upper = self.point_px_to_m(Vector2::new(rect.x, rect.y));
        Rectangle::new(
            left_upper.x,
            left_upper.y,
            self.length_px_to_m(rect.width),
            self.length_px_to_m(rect.height),
        )
    }

    /* Helper function to make sure the standard zoom factor is always exact. This helps
     * normalising zoom levels even when the user zooms in and out extremely often, assuming they
     * pass the standard zoom factor.
     */
    fn normalise_zoom(&mut self) {
        if self.pixels_per_m > STANDARD_PIXELS_PER_M - 5.
            && self.pixels_per_m < STANDARD_PIXELS_PER_M + 5.
        {
            self.pixels_per_m = STANDARD_PIXELS_PER_M;
        }
    }

    /// Attempts to zoom in a step and return true.
    /// If the maximum zoom is reached, this function changes nothing and returns false.
    pub fn try_zoom_in(&mut self) -> bool {
        // TODO: Zoom in on mouse pointer position
        if self.pixels_per_m < MAX_PIXELS_PER_M {
            self.pixels_per_m *= 1.2;
            self.normalise_zoom();
            true
        } else {
            false
        }
    }

    /// Attempts to zoom out a step and return true.
    /// If the minimum zoom is reached, this function changes nothing and returns false.
    pub fn try_zoom_out(&mut self) -> bool {
        // TODO: Zoom out at mouse pointer position
        if self.pixels_per_m > MIN_PIXELS_PER_M {
            self.pixels_per_m /= 1.2;
            self.normalise_zoom();
            true
        } else {
            false
        }
    }

    /// Move the canvas by the vector in pixels.
    pub fn move_by_px(&mut self, by: Vector2) {
        self.translation_px += by;
    }

    pub fn pixels_per_m(&self) -> f32 {
        self.pixels_per_m
    }
    pub fn translation_px(&self) -> Vector2 {
        self.translation_px
    }
}