aboutsummaryrefslogtreecommitdiff
path: root/src/transform.rs
blob: 2e9ea0b9d199b8a89aa6308d455e644366959e41 (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
//! Transformation module
//!
//! Useful to turn on-screen coordinates into measurements of the "real" world the map describes
//! and the other way around.

use crate::math::{Rect, Vec2};

const STANDARD_PIXELS_PER_M: f32 = 64.;
const MIN_PIXELS_PER_M: f32 = 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: Vec2<f32>,
}

impl Transform {
    /// Create a new standard transformation for the map.
    pub fn new() -> Self {
        Self {
            pixels_per_m: STANDARD_PIXELS_PER_M,
            translation_px: Vec2::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: Vec2<f32>) -> Vec2<f32> {
        // 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: Vec2<f32>) -> Vec2<f32> {
        // 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: Rect<f32>) -> Rect<f32> {
        let left_upper = self.point_m_to_px(Vec2::new(rect.x, rect.y));
        Rect::new(
            left_upper.x,
            left_upper.y,
            self.length_m_to_px(rect.w),
            self.length_m_to_px(rect.h),
        )
    }

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

    /// Attempts to zoom the pixels per meter by the amount of factor.
    ///
    /// # Arguments
    /// `factor`: A number greater than one means zooming in, a number less than one means zooming out. What happens when you try to
    /// zoom with a negative factor you'll have to figure out yourself.
    /// `mouse_pos_px`: Position of the mouse cursor, this time not in meters, but in screen
    /// pixels. This will be used to tether zoom on that point.
    pub fn try_zoom(&mut self, mouse_pos_px: Vec2<f32>, factor: f32) -> bool {
        // Abort zooming when the scale would not be in the min-max-bounds anymore.
        let desired_px_per_m = self.pixels_per_m * factor;
        if (factor < 1. && desired_px_per_m <= MIN_PIXELS_PER_M)
            || (factor > 1. && desired_px_per_m >= MAX_PIXELS_PER_M)
        {
            return false;
        }

        // Save the absolute mouse position in meters for tethering later
        let mouse_pos_m = self.point_px_to_m(mouse_pos_px);

        // Make sure the desired scale stays within the bounds and in whole numbers
        let desired_px_per_m = if desired_px_per_m < MIN_PIXELS_PER_M {
            MIN_PIXELS_PER_M as u32 as f32
        } else if desired_px_per_m > MAX_PIXELS_PER_M {
            MAX_PIXELS_PER_M as u32 as f32
        } else {
            desired_px_per_m as u32 as f32
        };

        /* Adjust to the desired scale and bring the map back to its desired position according to
         * the mouse pointer position.
         */
        self.pixels_per_m = desired_px_per_m;
        self.translation_px += mouse_pos_px - self.point_m_to_px(mouse_pos_m);

        true
    }

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

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