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
130
131
132
133
134
135
|
//! Transformation module
//!
//! Useful to turn on-screen coordinates into the measurements in the "real" world the map
//! describes and the other way around.
use piston_window::math;
const STANDARD_PIXELS_PER_M: f64 = 64.;
const MIN_PIXELS_PER_M: f64 = 0.5;
const MAX_PIXELS_PER_M: f64 = 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: f64,
/// The vector the entire on-screen map is moved by in pixels
translation_px: [f64; 2],
}
impl Transform {
/// Create a new standard transformation for the map.
pub fn new() -> Self {
Self {
pixels_per_m: STANDARD_PIXELS_PER_M,
translation_px: [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: [f64; 2]) -> [f64; 2] {
// Start by converting the absolute position in meters into the absolute position in
// pixels, then add the translation of the screen.
math::add(
math::mul_scalar(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: [f64; 2]) -> [f64; 2] {
// Start by subtracting the pixel translation and afterwards convert these absolute pixel
// measurements into meters.
math::mul_scalar(
math::sub(point, self.translation_px),
1. / self.pixels_per_m,
)
}
/// Convert a length given in meters into a length in pixels
#[inline]
pub fn length_m_to_px(&self, length: f64) -> f64 {
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: f64) -> f64 {
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: [f64; 4]) -> [f64; 4] {
let left_upper = self.point_m_to_px([rect[0], rect[1]]);
[
left_upper[0],
left_upper[1],
self.length_m_to_px(rect[2]),
self.length_m_to_px(rect[3]),
]
}
/// Convert a rectangle which has measurements in pixels into one of meters
#[inline]
pub fn rect_px_to_m(&self, rect: [f64; 4]) -> [f64; 4] {
let left_upper = self.point_px_to_m([rect[0], rect[1]]);
[
left_upper[0],
left_upper[1],
self.length_px_to_m(rect[2]),
self.length_px_to_m(rect[3]),
]
}
/* 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: [f64; 2]) {
self.translation_px = math::add(self.translation_px, by);
}
pub fn pixels_per_m(&self) -> f64 {
self.pixels_per_m
}
pub fn translation_px(&self) -> [f64; 2] {
self.translation_px
}
}
|