diff options
| author | Arne Dußin | 2020-10-29 02:40:39 +0100 |
|---|---|---|
| committer | Arne Dußin | 2020-10-29 02:40:39 +0100 |
| commit | 771f94fb2383176bcd3faced3edb7daa044065eb (patch) | |
| tree | 3da49330f82d2cd8ff564a4d868508f6a62d7fe5 /src | |
| parent | 8237855202d58e28e912a83e612397b4300a3f3b (diff) | |
| download | graf_karto-771f94fb2383176bcd3faced3edb7daa044065eb.tar.gz graf_karto-771f94fb2383176bcd3faced3edb7daa044065eb.zip | |
Make rectangles zoom together with the grid
Diffstat (limited to 'src')
| -rw-r--r-- | src/main.rs | 48 | ||||
| -rw-r--r-- | src/transform.rs | 130 |
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 + } +} |
