diff options
| author | Arne Dußin | 2021-01-27 14:01:50 +0100 |
|---|---|---|
| committer | Arne Dußin | 2021-02-02 22:16:15 +0100 |
| commit | f92e9f6f07b1e3834c2ca58ce3510734819d08e4 (patch) | |
| tree | 20e3d3afce342a56ae98f6c20491482ccd2b5c6b /src/client/transform.rs | |
| parent | c60a6d07efb120724b308e29e8e70f27c87c952d (diff) | |
| download | graf_karto-f92e9f6f07b1e3834c2ca58ce3510734819d08e4.tar.gz graf_karto-f92e9f6f07b1e3834c2ca58ce3510734819d08e4.zip | |
Rework graf karto to fit the client/server structure
Diffstat (limited to 'src/client/transform.rs')
| -rw-r--r-- | src/client/transform.rs | 142 |
1 files changed, 142 insertions, 0 deletions
diff --git a/src/client/transform.rs b/src/client/transform.rs new file mode 100644 index 0000000..147956c --- /dev/null +++ b/src/client/transform.rs @@ -0,0 +1,142 @@ +//! 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: f64 = 64.; +const MIN_PIXELS_PER_M: f64 = 5.; +const MAX_PIXELS_PER_M: f64 = 10_000.; + +/// A rigid 2D transformation. Since the translation must often be accessed directly and so far there +/// was no huge need for fancy transformation, this currently does not use any matrix transformations. +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: Vec2<f64>, +} + +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<f64>) -> Vec2<f64> { + // 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<f64>) -> Vec2<f64> { + // 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: 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: &Rect<f64>) -> Rect<f64> { + 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<f64>) -> Rect<f64> { + 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<f64>, factor: f64) -> 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 f64 + } else if desired_px_per_m > MAX_PIXELS_PER_M { + MAX_PIXELS_PER_M as u32 as f64 + } else { + desired_px_per_m as u32 as f64 + }; + + /* 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<f64>) { + self.translation_px += *by; + } + + /// Get the current scale with the number of pixels (as a real number) that makes up a meter of + /// actual map space. + pub fn pixels_per_m(&self) -> f64 { + self.pixels_per_m + } + /// Get the current translation of the map on the screen. This is purely in pixels, rather than + /// meters, since a translation in meters does not make sense, because all map items have an + /// absolute position and the translation is merely used to see where on the screen anything + /// should be drawn. + pub fn translation_px(&self) -> &Vec2<f64> { + &self.translation_px + } +} + +impl Default for Transform { + fn default() -> Self { + Self::new() + } +} |
