//! 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 } }