aboutsummaryrefslogtreecommitdiff
path: root/src/client/transform.rs
diff options
context:
space:
mode:
authorArne Dußin2021-01-27 14:01:50 +0100
committerArne Dußin2021-02-02 22:16:15 +0100
commitf92e9f6f07b1e3834c2ca58ce3510734819d08e4 (patch)
tree20e3d3afce342a56ae98f6c20491482ccd2b5c6b /src/client/transform.rs
parentc60a6d07efb120724b308e29e8e70f27c87c952d (diff)
downloadgraf_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.rs142
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()
+ }
+}