aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main.rs48
-rw-r--r--src/transform.rs130
2 files changed, 149 insertions, 29 deletions
diff --git a/src/main.rs b/src/main.rs
index 1456684..284ee53 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,12 +1,11 @@
+pub mod transform;
+use transform::*;
+
use piston_window::grid::Grid;
use piston_window::rectangle::{Border, Rectangle};
use piston_window::*;
use sdl2_window::Sdl2Window;
-pub const MIN_PIXELS_PER_M: f64 = 0.5;
-pub const MAX_PIXELS_PER_M: f64 = 10_000.;
-pub const STANDARD_PIXELS_PER_M: f64 = 64.;
-
/// Helper function to turn two given points into a rectangle. The order of the two points is not
/// important, they are considered two endpoints of a diagonal and therefore identify the rectangle
/// unambiguously.
@@ -25,7 +24,7 @@ fn main() {
.expect("Could not initialise window");
// The amount of on-screen pixels used to represent a meter of actual terrain.
- let mut pixels_per_m = STANDARD_PIXELS_PER_M;
+ let mut transform = Transform::new();
/* Create a rectangle that is used to draw all rectangles that were created by the user. It has
* a thicc blacc border and white colour.
@@ -41,49 +40,40 @@ fn main() {
// Line used to draw the square grid.
let grid_line = Line::new([1., 1., 1., 0.3], 1.5);
- let mut mouse_pos = [0., 0.];
+ let mut mouse_pos_m = [0., 0.];
let mut events = Events::new(EventSettings::new().lazy(true));
while let Some(e) = events.next(&mut window) {
// Update the mouse cursor position
e.mouse_cursor(|pos| {
- mouse_pos = pos;
+ mouse_pos_m = transform.point_px_to_m(pos);
});
// The zoom factor is changed with the mouse wheel.
e.mouse_scroll(|[_, y]| {
- let scale_changed = if y < 0. && MAX_PIXELS_PER_M > pixels_per_m {
- pixels_per_m *= 1.2;
+ let scale_changed = if y < 0. && transform.try_zoom_in() {
true
- } else if y > 0. && MIN_PIXELS_PER_M < pixels_per_m {
- pixels_per_m /= 1.2;
+ } else if y > 0. && transform.try_zoom_out() {
true
} else {
false
};
- /* Make sure that the scale factors stay very close to the normal scale factors, even
- * when the user zooms in and out very often, at least when they zoom over the standard
- * zoom factor.
- */
- if pixels_per_m > STANDARD_PIXELS_PER_M - 5.
- && pixels_per_m < STANDARD_PIXELS_PER_M + 5.
- {
- pixels_per_m = STANDARD_PIXELS_PER_M;
- }
-
// Notify the user of the change if there was any
if scale_changed {
- println!("Changed scale to {} pixels per m.", pixels_per_m);
+ println!(
+ "Changed scale to {} pixels per m.",
+ transform.pixels_per_m()
+ );
}
});
// Handle drawing a rectangle or finishing the rectangle when clicking with the mouse.
if let Some(Button::Mouse(MouseButton::Left)) = e.press_args() {
if let Some(first_point) = starting_rect_point {
- rectangles.push(bounding_box(first_point, mouse_pos));
+ rectangles.push(bounding_box(first_point, mouse_pos_m));
starting_rect_point = None;
} else {
- starting_rect_point = Some(mouse_pos);
+ starting_rect_point = Some(mouse_pos_m);
}
}
// Abort drawing a rectangle when clicking with the right mouse button
@@ -102,9 +92,9 @@ fn main() {
*/
let win_size = window.draw_size();
let grid = Grid {
- cols: (win_size.width / pixels_per_m) as u32 + 1,
- rows: (win_size.height / pixels_per_m) as u32 + 1,
- units: pixels_per_m,
+ cols: (win_size.width / transform.pixels_per_m()) as u32 + 1,
+ rows: (win_size.height / transform.pixels_per_m()) as u32 + 1,
+ units: transform.pixels_per_m(),
};
window.draw_2d(&e, |c, g, _device| {
@@ -114,13 +104,13 @@ fn main() {
// Draw all rectangles that are part of the map
for &rect in &rectangles {
- render_rect.draw(rect, &c.draw_state, c.transform, g);
+ render_rect.draw(transform.rect_m_to_px(rect), &c.draw_state, c.transform, g);
}
// Draw the current rectangle that is being drawn, but not part of the map
if let Some(starting_rect_point) = starting_rect_point {
render_rect.draw(
- bounding_box(starting_rect_point, mouse_pos),
+ transform.rect_m_to_px(bounding_box(starting_rect_point, mouse_pos_m)),
&c.draw_state,
c.transform,
g,
diff --git a/src/transform.rs b/src/transform.rs
new file mode 100644
index 0000000..ef94029
--- /dev/null
+++ b/src/transform.rs
@@ -0,0 +1,130 @@
+//! 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
+ }
+ }
+
+ pub fn pixels_per_m(&self) -> f64 {
+ self.pixels_per_m
+ }
+ pub fn translation_px(&self) -> [f64; 2] {
+ self.translation_px
+ }
+}