From 48c425a193cb13012eb9303df56ac04b9d683ed4 Mon Sep 17 00:00:00 2001 From: Arne Dußin Date: Fri, 30 Oct 2020 22:32:28 +0100 Subject: Rewrite project to use raylib instead of piston Sorry piston.. I really tried liking you, but I just couldn't :/ It's not you, it's me. What am I saying? It's you, sorry not sorry. --- src/grid.rs | 38 +++++++++++++ src/infinite_grid.rs | 63 --------------------- src/main.rs | 148 +++++++++++--------------------------------------- src/math.rs | 18 ++++++ src/tool/mod.rs | 16 ++++++ src/tool/room_tool.rs | 81 +++++++++++++++++++++++++++ src/transform.rs | 76 ++++++++++++-------------- 7 files changed, 220 insertions(+), 220 deletions(-) create mode 100644 src/grid.rs delete mode 100644 src/infinite_grid.rs create mode 100644 src/math.rs create mode 100644 src/tool/mod.rs create mode 100644 src/tool/room_tool.rs (limited to 'src') diff --git a/src/grid.rs b/src/grid.rs new file mode 100644 index 0000000..1ec49a3 --- /dev/null +++ b/src/grid.rs @@ -0,0 +1,38 @@ +use crate::transform::Transform; +use raylib::drawing::RaylibDraw; +use raylib::ffi::Color; + +pub const LINE_COLOUR: Color = Color { + r: 255, + g: 255, + b: 255, + a: 75, +}; + +/// Draw an infinite grid that can be moved around on the screen and zoomed in and out of. +pub fn draw_grid(rld: &mut D, screen_width: i32, screen_height: i32, transform: &Transform) +where + D: RaylibDraw, +{ + /* Calculate the actual screen offset of the grid, by modulo-ing the translation of the + * transform. + */ + let translation_x_px: i32 = + transform.translation_px().x as i32 % transform.pixels_per_m() as i32; + let translation_y_px: i32 = + transform.translation_px().y as i32 % transform.pixels_per_m() as i32; + + // Draw the row lines. + let mut line_y: f32 = translation_y_px as f32; + while line_y <= screen_height as f32 { + rld.draw_line(0, line_y as i32, screen_width, line_y as i32, LINE_COLOUR); + line_y += transform.pixels_per_m(); + } + + // Draw the column lines. + let mut line_x: f32 = translation_x_px as f32; + while line_x <= screen_width as f32 { + rld.draw_line(line_x as i32, 0, line_x as i32, screen_height, LINE_COLOUR); + line_x += transform.pixels_per_m(); + } +} diff --git a/src/infinite_grid.rs b/src/infinite_grid.rs deleted file mode 100644 index 163c2b2..0000000 --- a/src/infinite_grid.rs +++ /dev/null @@ -1,63 +0,0 @@ -//! Grid that can be moved and scaled, and which gives the appearance of being infinite, while -//! staying easily renderable. - -use crate::transform::Transform; -use piston_window::grid::Grid; -use piston_window::line::{Line, Shape}; -use piston_window::{Context, Graphics, Size, Transformed}; - -/// The line used to draw the grids lines -const GRID_LINE: Line = Line { - color: [1., 1., 1., 0.3], - radius: 1.2, - shape: Shape::Square, -}; - -pub struct InfiniteGrid(Grid); - -impl InfiniteGrid { - pub fn new(transform: &Transform, draw_size: Size) -> InfiniteGrid { - /* Create a grid with two extra columns and rows per draw_size, so when the user moves the - * grid up and down they can be seen, before the grid position is reset to create the - * infinitness illusion. - * The +3 instead of the +2 is necessary, since the amount needed may be underestimated - * because of rounding errors (I think) - */ - InfiniteGrid(Grid { - cols: (draw_size.width / transform.pixels_per_m()) as u32 + 3, - rows: (draw_size.height / transform.pixels_per_m()) as u32 + 3, - units: transform.pixels_per_m(), - }) - } - - // TODO: Yes, this is ugly af - pub fn on_scale_change(&mut self, transform: &Transform, draw_size: Size) { - self.0.cols = (draw_size.width / transform.pixels_per_m()) as u32 + 3; - self.0.rows = (draw_size.height / transform.pixels_per_m()) as u32 + 3; - self.0.units = transform.pixels_per_m() - } - - pub fn draw(&self, map_trans: &Transform, context: &Context, g: &mut G) - where - G: Graphics, - { - /* Since mouse movement is actually always provided as whole pixels, this is fine. - * the - cell_size (i.e. pixels_per_m) is provided, so that the grid is centered in the - * screen,and not at position 0.x, 0.x, which would mean there would be no row at the top - * and/or no column to the left. - */ - let actual_trans_px = [ - (map_trans.translation_px()[0] as i64 % self.0.units as i64) as f64 - - map_trans.pixels_per_m(), - (map_trans.translation_px()[1] as i64 % self.0.units as i64) as f64 - - map_trans.pixels_per_m(), - ]; - - self.0.draw( - &GRID_LINE, - &context.draw_state, - context.trans_pos(actual_trans_px).transform, - g, - ); - } -} diff --git a/src/main.rs b/src/main.rs index 4969261..3f36123 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,132 +1,48 @@ -pub mod infinite_grid; +pub mod grid; +pub mod math; +pub mod tool; pub mod transform; -use infinite_grid::*; -use transform::*; +pub use transform::Transform; -use piston_window::grid::Grid; -use piston_window::rectangle::{Border, Rectangle}; -use piston_window::*; -use sdl2_window::Sdl2Window; - -/// 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. -fn bounding_box(p0: [f64; 2], p1: [f64; 2]) -> [f64; 4] { - let min_x = p0[0].min(p1[0]); - let min_y = p0[1].min(p1[1]); - let max_x = p0[0].max(p1[0]); - let max_y = p0[1].max(p1[1]); - - [min_x, min_y, max_x - min_x, max_y - min_y] -} +use raylib::prelude::*; +use tool::{RoomTool, Tool}; fn main() { - let mut window: PistonWindow = WindowSettings::new("Hello there!", [1000, 1000]) - .build() - .expect("Could not initialise window"); - - // The amount of on-screen pixels used to represent a meter of actual terrain. - let mut transform = Transform::new(); - let mut grid = InfiniteGrid::new(&transform, window.draw_size()); - - /* 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. - */ - let render_rect = Rectangle::new([0.7, 0.7, 0.7, 1.]).border(Border { - color: [0.5, 0.5, 0.5, 1.], - radius: 2., - }); - // The point the user has clicked. This is where they want the rectangle to start. - let mut starting_rect_point: Option<[f64; 2]> = None; - let mut rectangles = Vec::new(); - - let mut mouse_pos_px = [0., 0.]; - let mut mouse_pos_m = [0., 0.]; - let mut canvas_follows_mouse = false; - - let mut events = Events::new(EventSettings::new().lazy(true)); - while let Some(e) = events.next(&mut window) { - // Update the mouse cursor position and possibly the canvas position too, in case the user - // is currently dragging the canvas. - e.mouse_cursor(|new_pos| { - if canvas_follows_mouse { - let move_by = math::sub(new_pos, mouse_pos_px); - transform.move_by_px(move_by); - } + let (mut rl, thread) = raylib::init().resizable().title("Hello there!").build(); - mouse_pos_px = new_pos; - mouse_pos_m = transform.point_px_to_m(new_pos); - }); + let mut current_tool = RoomTool::new(); - // The zoom factor is changed with the mouse wheel. - e.mouse_scroll(|[_, y]| { - let scale_changed = if y < 0. && transform.try_zoom_in() { - true - } else if y > 0. && transform.try_zoom_out() { - true - } else { - false - }; - - // Notify the user of the change if there was any - if scale_changed { - grid.on_scale_change(&transform, window.draw_size()); - println!( - "Changed scale to {} pixels per m.", - transform.pixels_per_m() - ); - } - }); - - // Handle the movement of the canvas when pressing the middle mouse button. - if let Some(Button::Mouse(MouseButton::Middle)) = e.press_args() { - canvas_follows_mouse = true; - println!("Canvas now follows the mouse"); - } - if let Some(Button::Mouse(MouseButton::Middle)) = e.release_args() { - canvas_follows_mouse = false; - println!("Canvas no longer follows the mouse"); - } - - // 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_m)); - starting_rect_point = None; - } else { - starting_rect_point = Some(mouse_pos_m); - } - } - // Abort drawing a rectangle when clicking with the right mouse button - if let Some(Button::Mouse(MouseButton::Right)) = e.press_args() { - starting_rect_point = None; + let mut transform = Transform::new(); + let mut last_mouse_pos = rl.get_mouse_position(); + while !rl.window_should_close() { + let screen_width = rl.get_screen_width(); + let screen_height = rl.get_screen_height(); + + // Move the canvas together with the mouse + if rl.is_mouse_button_down(MouseButton::MOUSE_MIDDLE_BUTTON) { + transform.move_by_px(rl.get_mouse_position() - last_mouse_pos); } - // Close the window when the user presses escape - if let Some(Button::Keyboard(Key::Escape)) = e.press_args() { - window.set_should_close(true); + // Handle scrolling of the canvas + if rl.get_mouse_wheel_move() > 0 { + transform.try_zoom_in(); + } else if rl.get_mouse_wheel_move() < 0 { + transform.try_zoom_out(); } - window.draw_2d(&e, |c, g, _device| { - clear([0.4, 0.2, 0., 1.], g); + current_tool.update(&rl, &transform); - grid.draw(&transform, &c, g); + // Update the last mouse position + last_mouse_pos = rl.get_mouse_position(); - // Draw all rectangles that are part of the map - for &rect in &rectangles { - render_rect.draw(transform.rect_m_to_px(rect), &c.draw_state, c.transform, g); - } + // Drawing section + { + let mut d = rl.begin_drawing(&thread); + d.clear_background(Color::BLACK); + grid::draw_grid(&mut d, screen_width, screen_height, &transform); - // 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( - transform.rect_m_to_px(bounding_box(starting_rect_point, mouse_pos_m)), - &c.draw_state, - c.transform, - g, - ); - } - }); + current_tool.draw(&mut d, &transform); + } } } diff --git a/src/math.rs b/src/math.rs new file mode 100644 index 0000000..c4e7ac0 --- /dev/null +++ b/src/math.rs @@ -0,0 +1,18 @@ +use raylib::math::{Rectangle, Vector2}; + +/// Function to calculate the bounding rectangle that is between two vectors. The order of the +/// vectors is irrelevent for this. As long as they are diagonally opposite of each other, this +/// function will work. +pub fn bounding_rect(pos1: Vector2, pos2: Vector2) -> Rectangle { + let min_x = pos1.x.min(pos2.x); + let min_y = pos1.y.min(pos2.y); + let max_x = pos1.x.max(pos2.x); + let max_y = pos1.y.max(pos2.y); + + Rectangle { + x: min_x, + y: min_y, + width: max_x - min_x, + height: max_y - min_y, + } +} diff --git a/src/tool/mod.rs b/src/tool/mod.rs new file mode 100644 index 0000000..e0d4f1e --- /dev/null +++ b/src/tool/mod.rs @@ -0,0 +1,16 @@ +pub mod room_tool; +pub use room_tool::RoomTool; + +use crate::transform::Transform; +use raylib::core::drawing::RaylibDraw; +use raylib::RaylibHandle; + +pub trait Tool { + fn update(&mut self, rl: &RaylibHandle, transform: &Transform); + + fn draw(&self, _rld: &mut D, _transform: &Transform) + where + D: RaylibDraw, + { + } +} diff --git a/src/tool/room_tool.rs b/src/tool/room_tool.rs new file mode 100644 index 0000000..16ff5ce --- /dev/null +++ b/src/tool/room_tool.rs @@ -0,0 +1,81 @@ +use super::Tool; +use crate::math; +use crate::transform::Transform; +use raylib::core::drawing::RaylibDraw; +use raylib::ffi::{Color, MouseButton}; +use raylib::math::{Rectangle, Vector2}; +use raylib::RaylibHandle; + +pub struct RoomTool { + /// Vector of all Rectangles representing rooms that have already been drawn. + room_rects: Vec, + /// The rectangle that is currently being drawn by the user. Once it is finished, it will be + /// pushed into the room_rects. + unfinished_rect: Option<(Vector2, Vector2)>, +} + +impl RoomTool { + /// Create a new room tool where no rooms have been drawn yet. + pub fn new() -> Self { + Self { + room_rects: Vec::new(), + unfinished_rect: None, + } + } +} + +impl Tool for RoomTool { + fn update(&mut self, rl: &RaylibHandle, transform: &Transform) { + let mouse_pos_m = transform.point_px_to_m(rl.get_mouse_position()); + // Update the currently drawn rectangle, if it exists + if let Some((_, ref mut pos2)) = &mut self.unfinished_rect { + *pos2 = mouse_pos_m; + } + + // Start or finish drawing the currently unfinished rectangle + if rl.is_mouse_button_pressed(MouseButton::MOUSE_LEFT_BUTTON) { + if let Some((pos1, pos2)) = self.unfinished_rect { + self.room_rects.push(math::bounding_rect(pos1, pos2)); + self.unfinished_rect = None; + } else { + self.unfinished_rect = Some((mouse_pos_m, mouse_pos_m)) + } + } + + // Abort drawing the room (if any) in case the right mouse button was pressed. + if rl.is_mouse_button_pressed(MouseButton::MOUSE_RIGHT_BUTTON) { + self.unfinished_rect = None; + } + } + + fn draw(&self, rld: &mut D, transform: &Transform) + where + D: RaylibDraw, + { + // Draw all finished rectangles. + for &rect in &self.room_rects { + rld.draw_rectangle_rec( + transform.rect_m_to_px(rect), + Color { + r: 50, + g: 50, + b: 50, + a: 255, + }, + ); + } + + // Do the same for the unfinished rectangle + if let Some((pos1, pos2)) = self.unfinished_rect { + rld.draw_rectangle_rec( + transform.rect_m_to_px(math::bounding_rect(pos1, pos2)), + Color { + r: 70, + g: 100, + b: 70, + a: 255, + }, + ); + } + } +} diff --git a/src/transform.rs b/src/transform.rs index a91d7da..fa24636 100644 --- a/src/transform.rs +++ b/src/transform.rs @@ -1,19 +1,19 @@ //! Transformation module //! -//! Useful to turn on-screen coordinates into the measurements in the "real" world the map -//! describes and the other way around. +//! Useful to turn on-screen coordinates into measurements of the "real" world the map describes +//! and the other way around. -use piston_window::math; +use raylib::prelude::*; -const STANDARD_PIXELS_PER_M: f64 = 64.; -const MIN_PIXELS_PER_M: f64 = 0.5; -const MAX_PIXELS_PER_M: f64 = 10_000.; +const STANDARD_PIXELS_PER_M: f32 = 64.; +const MIN_PIXELS_PER_M: f32 = 0.5; +const MAX_PIXELS_PER_M: f32 = 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, + pixels_per_m: f32, /// The vector the entire on-screen map is moved by in pixels - translation_px: [f64; 2], + translation_px: Vector2, } impl Transform { @@ -21,66 +21,60 @@ impl Transform { pub fn new() -> Self { Self { pixels_per_m: STANDARD_PIXELS_PER_M, - translation_px: [0., 0.], + translation_px: Vector2::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: [f64; 2]) -> [f64; 2] { + pub fn point_m_to_px(&self, point: Vector2) -> Vector2 { // 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, - ) + (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] { + pub fn point_px_to_m(&self, point: Vector2) -> Vector2 { // 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, - ) + (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 { + pub fn length_m_to_px(&self, length: f32) -> f32 { 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 { + pub fn length_px_to_m(&self, length: f32) -> f32 { 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]), - ] + pub fn rect_m_to_px(&self, rect: Rectangle) -> Rectangle { + let left_upper = self.point_m_to_px(Vector2::new(rect.x, rect.y)); + Rectangle::new( + left_upper.x, + left_upper.y, + self.length_m_to_px(rect.width), + self.length_m_to_px(rect.height), + ) } /// 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]), - ] + pub fn rect_px_to_m(&self, rect: Rectangle) -> Rectangle { + let left_upper = self.point_px_to_m(Vector2::new(rect.x, rect.y)); + Rectangle::new( + left_upper.x, + left_upper.y, + self.length_px_to_m(rect.width), + self.length_px_to_m(rect.height), + ) } /* Helper function to make sure the standard zoom factor is always exact. This helps @@ -122,14 +116,14 @@ impl Transform { } /// 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 move_by_px(&mut self, by: Vector2) { + self.translation_px += by; } - pub fn pixels_per_m(&self) -> f64 { + pub fn pixels_per_m(&self) -> f32 { self.pixels_per_m } - pub fn translation_px(&self) -> [f64; 2] { + pub fn translation_px(&self) -> Vector2 { self.translation_px } } -- cgit v1.2.3-70-g09d2