From 53d376eaeef991850d35318b147f75c8f103319d Mon Sep 17 00:00:00 2001 From: Arne Dußin Date: Sun, 27 Dec 2020 21:54:31 +0100 Subject: Change to polygongraph instead of polygon in roomtool The polygon room tool used a convoluted process for determining what the user actually wants to draw. I have changed to the polygon graph instead, which makes the checks easier and restricts the user a bit less. In the process however I found a serious problem with my handling float, so everything needed to change to margin compares (which I of course should have done in the beginning. Guys, take the warning seriously and don't ignore it for ten years like I did. It will come back to haunt you.. apparently) instead of direct equality. --- src/tool/deletion_tool.rs | 3 +- src/tool/mod.rs | 12 +-- src/tool/polygon_room_tool.rs | 223 ++++++++++++++++-------------------------- src/tool/rect_room_tool.rs | 1 + src/tool/selection_tool.rs | 3 +- src/tool/wall_tool.rs | 1 + 6 files changed, 95 insertions(+), 148 deletions(-) (limited to 'src/tool') diff --git a/src/tool/deletion_tool.rs b/src/tool/deletion_tool.rs index afd916a..da2090b 100644 --- a/src/tool/deletion_tool.rs +++ b/src/tool/deletion_tool.rs @@ -9,7 +9,7 @@ use super::Tool; use crate::colours::DEFAULT_COLOURS; use crate::map::Map; -use crate::math::{Rect, Surface, Vec2}; +use crate::math::{ExactSurface, Rect, Vec2}; use crate::transform::Transform; use raylib::core::drawing::{RaylibDraw, RaylibDrawHandle}; @@ -21,6 +21,7 @@ pub struct DeletionTool { impl DeletionTool { /// Create a new deletion tool, there should only be one deletion tool and it should be created /// by the editor. + #[allow(clippy::new_without_default)] pub fn new() -> Self { Self { deletion_rect: None, diff --git a/src/tool/mod.rs b/src/tool/mod.rs index 70534ac..4130244 100644 --- a/src/tool/mod.rs +++ b/src/tool/mod.rs @@ -31,17 +31,17 @@ use raylib::core::drawing::RaylibDrawHandle; /// The types of tools available in graf karto. For information about the tool itself, please see the /// referenced Tool's documentation. pub enum ToolType { - /// See [super::RectRoomTool] for information on this tool. + /// See [RectRoomTool] for information on this tool. RectRoomTool, - /// See [super::PolygonRoomTool] for information on this tool. + /// See [PolygonRoomTool] for information on this tool. PolygonRoomTool, - /// See [super::WallTool] for information on this tool. + /// See [WallTool] for information on this tool. WallTool, - /// See [super::IconTool] for information on this tool. + /// See [IconTool] for information on this tool. IconTool, - /// See [super::DeletionTool] for information on this tool. + /// See [DeletionTool] for information on this tool. DeletionTool, - /// See [super::SelectionTool] for information on this tool. + /// See [SelectionTool] for information on this tool. SelectionTool, /// Not a real tool but used to know how many tools are available. New tools must be added /// above this variant. diff --git a/src/tool/polygon_room_tool.rs b/src/tool/polygon_room_tool.rs index 33daaf5..d181669 100644 --- a/src/tool/polygon_room_tool.rs +++ b/src/tool/polygon_room_tool.rs @@ -3,189 +3,132 @@ use super::Tool; use crate::colours::DEFAULT_COLOURS; use crate::map::Map; -use crate::math::{self, Polygon, PolygonError, Vec2}; +use crate::math::{self, PolygonGraph, Vec2}; use crate::transform::Transform; +use crate::FLOAT_MARGIN; use raylib::core::drawing::{RaylibDraw, RaylibDrawHandle}; -struct UnfinishedPolygon { - pub corners: Vec>, - pub working_corner: Vec2, -} - /// The tool itself. pub struct PolygonRoomTool { - unfinished_polygon: Option, -} - -impl UnfinishedPolygon { - // Check the validity of the already completed corners only - pub fn check_validity_completed(&self) -> Result<(), PolygonError> { - Polygon::check_validity(&self.corners) - } - - // Check the validity of the already completed corners, but with the working corner wedged - // between the last and first corner (making it the new last corner). - pub fn check_validity_all(&self) -> Result<(), PolygonError> { - // TODO: Is this possible without changing the mutability of self and without reallocation? - let mut corners = self.corners.clone(); - corners.push(self.working_corner); - - Polygon::check_validity(&corners) - } - - pub fn try_into_completed(&mut self) -> Option> { - match self.check_validity_completed() { - Ok(()) => Some(Polygon::new_unchecked(self.corners.drain(..).collect())), - Err(e) => { - warn!("Cannot turn placed corners into a polygon: {}", e); - None - } - } - } - - pub fn try_into_all(&mut self) -> Option> { - match self.check_validity_all() { - Ok(()) => { - self.corners.push(self.working_corner); - Some(Polygon::new_unchecked(self.corners.drain(..).collect())) - } - Err(e) => { - warn!( - "Cannot turn corners with newest corner into a polygon: {}", - e - ); - None - } - } - } - - pub fn try_push_working(&mut self) -> Result<(), PolygonError> { - assert!(!self.corners.is_empty()); - - if self.corners.len() == 1 { - return if self.corners[0] != self.working_corner { - self.corners.push(self.working_corner); - Ok(()) - } else { - Err(PolygonError::VertexDoubling(self.corners[0], 0)) - }; - } - - self.check_validity_all()?; - self.corners.push(self.working_corner); - - Ok(()) - } + unfinished_room: Option<(PolygonGraph, Vec2)>, + last_mouse_pos_m: Vec2, } impl PolygonRoomTool { /// Create a new polygon room tool. There should be only one instance and it should be created /// in the editor. + #[allow(clippy::new_without_default)] pub fn new() -> Self { Self { - unfinished_polygon: None, + unfinished_room: None, + last_mouse_pos_m: Vec2::new(0., 0.), + } + } + + /* Helper function to try and finish the currently drawn polygon. If successful, it will add it + * to the map, clear the currently drawn polygon and return bool. Otherwise it will leave the + * unfinished polygon as is and return false without pushing anything. + */ + fn try_push(&mut self, map: &mut Map) -> bool { + if self.unfinished_room.is_none() { + return false; + } + + match self + .unfinished_room + .as_ref() + .unwrap() + .0 + .clone() + .bounding_polygon(FLOAT_MARGIN) + { + Some(polygon) => { + map.push_polygon_room(polygon); + self.unfinished_room = None; + true + } + None => false, } } } impl Tool for PolygonRoomTool { fn deactivate(&mut self) { - self.unfinished_polygon = None; + self.unfinished_room = None; } fn update(&mut self, _map: &Map, mouse_pos_m: &Vec2) { - // Update the position of the node that would be placed into the polygon next. - if let Some(ref mut polygon) = &mut self.unfinished_polygon { - polygon.working_corner = *mouse_pos_m; - } + // Update the last mouse position that was seen for later use. + self.last_mouse_pos_m = *mouse_pos_m; } fn draw(&self, rld: &mut RaylibDrawHandle, transform: &Transform) { - if let Some(polygon) = &self.unfinished_polygon { - // The first corner is guaranteed to be set, so we can at least draw a line. - if polygon.corners.len() == 1 { - rld.draw_line_ex( - transform.point_m_to_px(&polygon.corners[0]), - transform.point_m_to_px(&polygon.working_corner), + if let Some((graph, last_node)) = &self.unfinished_room { + /* To turn the graph into a polygon, we need a copy, might as well do + * it now, so we can add the working corner to it. + */ + let mut graph = graph.clone(); + + // Add the current mouse position as the next position if possible. + graph.add_edge(&last_node, &self.last_mouse_pos_m); + + if graph.num_nodes() <= 1 { + // Only able to draw a point + rld.draw_circle_v( + transform.point_m_to_px(&self.last_mouse_pos_m), transform.length_m_to_px(0.1) as f32, DEFAULT_COLOURS.room_selected, ); - } else if polygon.corners.len() == 2 { - // We have three valid corners, so we can draw a triangle. - rld.draw_triangle( - transform.point_m_to_px(&polygon.corners[0]), - transform.point_m_to_px(&polygon.corners[1]), - transform.point_m_to_px(&polygon.working_corner), - DEFAULT_COLOURS.room_selected, - ) + } else if let Some(polygon) = graph.clone().bounding_polygon(FLOAT_MARGIN) { + let triangles = math::triangulate(polygon, FLOAT_MARGIN); + for triangle in triangles { + let triangle: [Vec2; 3] = triangle.into(); + rld.draw_triangle( + transform.point_m_to_px(&triangle[0]), + transform.point_m_to_px(&triangle[1]), + transform.point_m_to_px(&triangle[2]), + DEFAULT_COLOURS.room_selected, + ) + } } else { - // A proper polygon can be drawn. - if polygon.check_validity_all().is_ok() { - let mut corners = polygon.corners.clone(); - corners.push(polygon.working_corner); - let polygon = Polygon::new_unchecked(corners); - let triangles = math::triangulate(polygon); - for triangle in triangles { - let triangle: [Vec2; 3] = triangle.into(); - rld.draw_triangle( - transform.point_m_to_px(&triangle[0]), - transform.point_m_to_px(&triangle[1]), - transform.point_m_to_px(&triangle[2]), - DEFAULT_COLOURS.room_selected, - ) - } - } else if polygon.check_validity_completed().is_ok() { - let polygon = Polygon::new_unchecked(polygon.corners.clone()); - let triangles = math::triangulate(polygon); - for triangle in triangles { - let triangle: [Vec2; 3] = triangle.into(); - rld.draw_triangle( - transform.point_m_to_px(&triangle[0]), - transform.point_m_to_px(&triangle[1]), - transform.point_m_to_px(&triangle[2]), - DEFAULT_COLOURS.room_selected, - ) - } + // For some reason the polygon creation failed. Draw lines for the edges instead. + for edge in graph.edge_iter() { + rld.draw_line_ex( + transform.point_m_to_px(&edge.start), + transform.point_m_to_px(&edge.end), + transform.length_m_to_px(0.1) as f32, + DEFAULT_COLOURS.room_selected, + ); } } } } fn place_single(&mut self, map: &mut Map, mouse_pos_m: &Vec2) { - if let Some(ref mut polygon) = &mut self.unfinished_polygon { - if polygon.working_corner == polygon.corners[0] { - /* The working corner will be ignored, since it would double the vertex at the - * polygon starting position. - */ - if let Some(polygon) = polygon.try_into_completed() { - map.push_polygon_room(polygon); - self.unfinished_polygon = None; - } - } else { - // Check if we can add the corner to the polygon without ruining it. - if let Err(e) = polygon.try_push_working() { - error!("Cannot add corner to polygon: {}", e); - } + if let Some((ref mut graph, ref mut last_placed)) = &mut self.unfinished_room { + // If the corner already exists in the polygon, try to finish and push it after adding the + // next edge. + let try_finish = graph.has_node(&mouse_pos_m); + + // Add an edge from the last corner to the currently active position if possible. + if graph.add_edge(last_placed, &mouse_pos_m) { + *last_placed = *mouse_pos_m; + } + + if try_finish { + self.try_push(map); } } else { // Start a new unfinished polygon - self.unfinished_polygon = Some(UnfinishedPolygon { - corners: vec![*mouse_pos_m], - working_corner: *mouse_pos_m, - }); + self.unfinished_room = Some((PolygonGraph::new(), *mouse_pos_m)); } } fn finish(&mut self, map: &mut Map) { - if let Some(ref mut polygon) = self.unfinished_polygon { - if let Some(polygon) = polygon.try_into_completed() { - map.push_polygon_room(polygon); - self.unfinished_polygon = None; - } - } + self.try_push(map); } fn abort(&mut self) { - self.unfinished_polygon = None; + self.unfinished_room = None; } } diff --git a/src/tool/rect_room_tool.rs b/src/tool/rect_room_tool.rs index 7cb85bb..9445787 100644 --- a/src/tool/rect_room_tool.rs +++ b/src/tool/rect_room_tool.rs @@ -19,6 +19,7 @@ pub struct RectRoomTool { impl RectRoomTool { /// Create a new room tool where no rooms have been drawn yet. Should be created only once per /// program instance and by the editor. + #[allow(clippy::new_without_default)] pub fn new() -> Self { Self { unfinished_rect: None, diff --git a/src/tool/selection_tool.rs b/src/tool/selection_tool.rs index c790734..4850a28 100644 --- a/src/tool/selection_tool.rs +++ b/src/tool/selection_tool.rs @@ -9,7 +9,7 @@ use super::Tool; use crate::colours::DEFAULT_COLOURS; use crate::map::Map; -use crate::math::{Rect, Surface, Vec2}; +use crate::math::{ExactSurface, Rect, Vec2}; use crate::transform::Transform; use raylib::core::drawing::{RaylibDraw, RaylibDrawHandle}; @@ -21,6 +21,7 @@ pub struct SelectionTool { impl SelectionTool { /// Create a new selection tool. There should be only one such tool per program instance and it /// should be created in the editor. + #[allow(clippy::new_without_default)] pub fn new() -> Self { Self { selection_rect: None, diff --git a/src/tool/wall_tool.rs b/src/tool/wall_tool.rs index 123171c..e79d815 100644 --- a/src/tool/wall_tool.rs +++ b/src/tool/wall_tool.rs @@ -16,6 +16,7 @@ pub struct WallTool { impl WallTool { /// Create a new wall tool. There should only be one wall tool per program instance, which should /// be created inside of the editor. + #[allow(clippy::new_without_default)] pub fn new() -> Self { Self { unfinished_wall: None, -- cgit v1.2.3-70-g09d2