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 crate::math::{Rect, Vec2};
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: 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),
)
}
/* 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: 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
}
}
|