aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorArne Dußin2021-01-06 22:56:37 +0100
committerArne Dußin2021-01-06 22:56:37 +0100
commitfa1afb6be3ba2d521eb0791edc0bb8e631a85327 (patch)
treee0a365444784efaaeb1eea6373b34559b6d57fbc /src
parent1c81d7c70fe891e6ded49d49d6a09f04ce74dd6e (diff)
parent30b23db9e86fdf72a4e7de72213df274ce19123e (diff)
downloadgraf_karto-fa1afb6be3ba2d521eb0791edc0bb8e631a85327.tar.gz
graf_karto-fa1afb6be3ba2d521eb0791edc0bb8e631a85327.zip
Merge branch 'master' into snapping
Diffstat (limited to 'src')
-rw-r--r--src/cli/cmd/edit.rs35
-rw-r--r--src/cli/cmd/mod.rs59
-rw-r--r--src/cli/cmd/read.rs38
-rw-r--r--src/cli/cmd/save.rs42
-rw-r--r--src/cli/mod.rs104
-rw-r--r--src/colours.rs16
-rw-r--r--src/editor.rs14
-rw-r--r--src/gui/dimension_indicator.rs20
-rw-r--r--src/gui/tool_sidebar.rs5
-rw-r--r--src/main.rs16
-rw-r--r--src/map/data.rs33
-rw-r--r--src/map/mod.rs49
-rw-r--r--src/map/polygon_room.rs14
-rw-r--r--src/map/rect_room.rs2
-rw-r--r--src/math/line_segment.rs46
-rw-r--r--src/math/polygon/mod.rs194
-rw-r--r--src/math/polygon/polygon_graph.rs67
-rw-r--r--src/math/polygon/triangulate.rs59
-rw-r--r--src/math/rect.rs7
-rw-r--r--src/math/surface.rs55
-rw-r--r--src/math/triangle.rs58
-rw-r--r--src/tool/deletion_tool.rs3
-rw-r--r--src/tool/mod.rs12
-rw-r--r--src/tool/polygon_room_tool.rs223
-rw-r--r--src/tool/rect_room_tool.rs1
-rw-r--r--src/tool/selection_tool.rs3
-rw-r--r--src/tool/wall_tool.rs1
27 files changed, 826 insertions, 350 deletions
diff --git a/src/cli/cmd/edit.rs b/src/cli/cmd/edit.rs
new file mode 100644
index 0000000..797edc6
--- /dev/null
+++ b/src/cli/cmd/edit.rs
@@ -0,0 +1,35 @@
+//! Replace the contents of the currently edited map with contents from a file.
+
+use super::Command;
+use super::{CmdParseError, FromArgs};
+use crate::map::MapData;
+use crate::Editor;
+use std::path::PathBuf;
+
+pub struct Edit {
+ file: PathBuf,
+}
+
+impl FromArgs for Edit {
+ fn from_args(args: &[&str]) -> Result<Self, CmdParseError> {
+ if args.len() != 1 {
+ return Err(CmdParseError::WrongNumberOfArgs(args.len(), 1..=1));
+ }
+
+ Ok(Self {
+ file: PathBuf::from(args[0]),
+ })
+ }
+}
+
+impl Command for Edit {
+ fn process(&self, editor: &mut Editor) -> Result<String, String> {
+ let data = match MapData::load_from_file(&self.file) {
+ Ok(data) => data,
+ Err(err) => return Err(format!("Unable to read file: {:?}", &self.file)),
+ };
+
+ editor.map_mut().set_data(data);
+ Ok(format!("Map data from {:?} loaded.", &self.file))
+ }
+}
diff --git a/src/cli/cmd/mod.rs b/src/cli/cmd/mod.rs
new file mode 100644
index 0000000..42e865a
--- /dev/null
+++ b/src/cli/cmd/mod.rs
@@ -0,0 +1,59 @@
+//! The commands that can be performed in the CLI
+
+pub mod edit;
+pub mod read;
+pub mod save;
+
+pub use edit::*;
+pub use read::*;
+pub use save::*;
+
+use crate::Editor;
+use std::ops::RangeInclusive;
+
+/// Errors that can occur when parsing a command. This is for syntax checking, the
+/// semantics are checked when trying to execute the command.
+#[allow(missing_docs)]
+#[derive(thiserror::Error, Debug)]
+pub enum CmdParseError {
+ #[error("no command specified")]
+ StringEmpty,
+ #[error("the command {0} is unknown")]
+ NoSuchCmd(String),
+ #[error("wrong number of arguments. Expected in range {1:?}, but received {0}")]
+ WrongNumberOfArgs(usize, RangeInclusive<usize>),
+ #[error("{0} cannot be converted into a {1}, which is required")]
+ InvalidArgType(String, &'static str),
+}
+
+/// Attempts to parse a command from the given string. If it is unsuccessful, it returns a
+/// [CmdParseError].
+pub fn parse_command(string: &str) -> Result<Box<dyn Command>, CmdParseError> {
+ if string.is_empty() {
+ return Err(CmdParseError::StringEmpty);
+ }
+
+ let parts: Vec<&str> = string.split_whitespace().collect();
+ match parts[0] {
+ "w" => Ok(Box::new(Save::from_args(&parts[1..])?)),
+ "e" => Ok(Box::new(Edit::from_args(&parts[1..])?)),
+ "r" => Ok(Box::new(Read::from_args(&parts[1..])?)),
+ other => Err(CmdParseError::NoSuchCmd(other.to_owned())),
+ }
+}
+
+/// Indicates that this entity (command) can be created from arguments. Make sure to check what is
+/// expected, to pass the arguments to the correct command.
+pub trait FromArgs: Sized {
+ /// Creates a new instance from the arguments provided. If for whatever reason the syntax of the
+ /// given arguments is correct an [ArgParseError] is returned.
+ fn from_args(args: &[&str]) -> Result<Self, CmdParseError>;
+}
+
+/// A common trait for all commands.
+pub trait Command {
+ /// Process this command on the provided context. Returns either a string with the output of the
+ /// command when everything went right with it, or an error string explaining what went wrong,
+ /// which can be displayed to the user.
+ fn process(&self, editor: &mut Editor) -> Result<String, String>;
+}
diff --git a/src/cli/cmd/read.rs b/src/cli/cmd/read.rs
new file mode 100644
index 0000000..4ac671c
--- /dev/null
+++ b/src/cli/cmd/read.rs
@@ -0,0 +1,38 @@
+//! Read the contents of a file and add it to the currently edited map.
+
+use super::Command;
+use super::{CmdParseError, FromArgs};
+use crate::map::MapData;
+use crate::Editor;
+use std::path::PathBuf;
+
+pub struct Read {
+ file: PathBuf,
+}
+
+impl FromArgs for Read {
+ fn from_args(args: &[&str]) -> Result<Self, CmdParseError> {
+ if args.len() != 1 {
+ return Err(CmdParseError::WrongNumberOfArgs(args.len(), 1..=1));
+ }
+
+ Ok(Self {
+ file: PathBuf::from(args[0]),
+ })
+ }
+}
+
+impl Command for Read {
+ fn process(&self, editor: &mut Editor) -> Result<String, String> {
+ let data = match MapData::load_from_file(&self.file) {
+ Ok(data) => data,
+ Err(err) => return Err(format!("Unable to read file: {:?}", &self.file)),
+ };
+
+ editor.map_mut().add_data(data);
+ Ok(format!(
+ "Map data from {:?} read and added to the current buffer.",
+ &self.file
+ ))
+ }
+}
diff --git a/src/cli/cmd/save.rs b/src/cli/cmd/save.rs
new file mode 100644
index 0000000..2c022cf
--- /dev/null
+++ b/src/cli/cmd/save.rs
@@ -0,0 +1,42 @@
+//! Save the contents of the map to disk
+
+use super::Command;
+use super::{CmdParseError, FromArgs};
+use crate::map::MapData;
+use crate::Editor;
+use std::path::PathBuf;
+
+/// The save command can take any destination in the filesystem the user can write to. Processing
+/// will then save the map contents to that destination, overwriting anything that may be there.
+pub struct Save {
+ destination: PathBuf,
+}
+
+impl FromArgs for Save {
+ fn from_args(args: &[&str]) -> Result<Self, CmdParseError> {
+ if args.len() != 1 {
+ return Err(CmdParseError::WrongNumberOfArgs(args.len(), 1..=1));
+ }
+
+ Ok(Self {
+ destination: PathBuf::from(args[0]),
+ })
+ }
+}
+
+impl Command for Save {
+ fn process(&self, editor: &mut Editor) -> Result<String, String> {
+ let data = MapData::extract_data(editor.map());
+
+ match data.write_to_file(&self.destination) {
+ Ok(_) => Ok(format!(
+ "Successfully wrote contents to `{:?}`",
+ &self.destination
+ )),
+ Err(e) => Err(format!(
+ "Unable to write to `{:?}`. Error: {:?}",
+ &self.destination, e
+ )),
+ }
+ }
+}
diff --git a/src/cli/mod.rs b/src/cli/mod.rs
new file mode 100644
index 0000000..e96070f
--- /dev/null
+++ b/src/cli/mod.rs
@@ -0,0 +1,104 @@
+//! In-window Command line interface. Used for operations that are just easier than with GUI.
+//!
+//! Sometimes it is nice to have a GUI, for instance when a selection has to be made, things have to
+//! be moved etc., however for operations like saving/loading and exporting, no such thing has to be
+//! done and the GUI is really just slowing you down (at least in my opinion). For these operations,
+//! it is much better to simply have a command do that specific thing. It is also much easier to
+//! implement a new command, so features can be tested more quickly. For some things, there should
+//! still be a GUI option. With the example of saving/loading, it is much easier to find some hidden
+//! folder in a GUI, so that is definitely a consideration for the future.
+
+pub mod cmd;
+pub use self::cmd::*;
+
+use crate::colours::DEFAULT_COLOURS;
+use crate::math::Vec2;
+use crate::Editor;
+use raylib::drawing::{RaylibDraw, RaylibDrawHandle};
+use raylib::ffi::KeyboardKey;
+use raylib::RaylibHandle;
+
+/// The command line interface. Should be created only once per program instance.
+pub struct CLI {
+ text: String,
+ active: bool,
+}
+
+impl CLI {
+ /// Create a CLI for this instance
+ pub fn new() -> Self {
+ Self {
+ text: String::new(),
+ active: false,
+ }
+ }
+
+ /// Activates the CLI, which will now capture keyboard input and execute commands accordingly.
+ pub fn activate(&mut self) {
+ if !self.active {
+ self.text = ";".to_owned();
+ self.active = true;
+ }
+ }
+
+ /// Handle input for the command line and perform any commands the user may want to run.
+ pub fn update(&mut self, rl: &mut RaylibHandle, editor: &mut Editor) {
+ /* Check if the CLI is currently active. If not and it should not be activated according to
+ * keyboard input, there is nothing to do.
+ */
+ if !self.active {
+ if rl.is_key_pressed(KeyboardKey::KEY_SEMICOLON) {
+ // Don't write the keypress again.
+ rl.get_key_pressed();
+ self.activate();
+ } else {
+ return;
+ }
+ }
+
+ // The CLI is currently active. Handle input to it.
+ if let Some(key) = rl.get_key_pressed_number() {
+ self.text.push(key as u8 as char);
+ } else if rl.is_key_pressed(KeyboardKey::KEY_BACKSPACE) {
+ self.text.pop();
+ } else if rl.is_key_pressed(KeyboardKey::KEY_ESCAPE) {
+ self.text.clear();
+ }
+
+ // When the text is empty, there is also no command marker, so set as inactive and leave.
+ if self.text.is_empty() {
+ self.active = false;
+ return;
+ }
+
+ // Perform the entered command, when the enter-key is pressed.
+ if rl.is_key_pressed(KeyboardKey::KEY_ENTER) {
+ self.active = false;
+ match cmd::parse_command(&self.text[1..]) {
+ Ok(cmd) => match cmd.process(editor) {
+ Ok(res) => self.text = format!("SUCCESS: {}", res),
+ Err(err) => self.text = format!("ERROR: {}", err),
+ },
+ Err(err) => self.text = format!("SYNTAX ERROR: {}", err),
+ }
+ }
+ }
+
+ /// Draw the command line at the bottom of the window.
+ pub fn draw(&self, rld: &mut RaylibDrawHandle) {
+ let pos = Vec2::new(150., rld.get_screen_height() as f32 - 25.);
+
+ rld.draw_rectangle_v(
+ pos,
+ Vec2::new(rld.get_screen_width() as f32 - pos.x, 25.),
+ DEFAULT_COLOURS.cli_background,
+ );
+ rld.draw_text(
+ &self.text,
+ 155,
+ rld.get_screen_height() - 22,
+ 20,
+ DEFAULT_COLOURS.cli_foreground,
+ );
+ }
+}
diff --git a/src/colours.rs b/src/colours.rs
index d381266..d7c728c 100644
--- a/src/colours.rs
+++ b/src/colours.rs
@@ -39,6 +39,10 @@ pub struct Colours {
/// The colour used for drawing the lines of the grid which divides the map into chunks of evenly
/// spaced cells.
pub grid_lines: Color,
+ /// Color used to draw the background of the Command Line Interface
+ pub cli_background: Color,
+ /// Color used to draw the normal text of the Command Line Interface
+ pub cli_foreground: Color,
}
impl Colours {
@@ -137,6 +141,18 @@ impl Colours {
b: 255,
a: 75,
},
+ cli_background: Color {
+ r: 100,
+ g: 100,
+ b: 100,
+ a: 150,
+ },
+ cli_foreground: Color {
+ r: 255,
+ g: 255,
+ b: 255,
+ a: 200,
+ },
}
}
}
diff --git a/src/editor.rs b/src/editor.rs
index 8e025dc..2cf2e41 100644
--- a/src/editor.rs
+++ b/src/editor.rs
@@ -127,20 +127,6 @@ impl Editor {
}
}
- /*
- TODO: reintroduce saving and loading
- // Handle saving and loading the editor contents to the swap file
- if rl.is_key_pressed(KeyboardKey::KEY_S) {
- self.map_data
- .write_file("swap.ron")
- .expect("Unable to write buffer file");
- } else if rl.is_key_pressed(KeyboardKey::KEY_L) {
- self.map_data
- .load_file("swap.ron")
- .expect("Unable to read buffer file");
- }
- */
-
let mouse_pos_m = transform.point_px_to_m(&rl.get_mouse_position().into());
let snapped_mouse_pos = snapper.snap(mouse_pos_m);
diff --git a/src/gui/dimension_indicator.rs b/src/gui/dimension_indicator.rs
index e8848fe..57f5bcc 100644
--- a/src/gui/dimension_indicator.rs
+++ b/src/gui/dimension_indicator.rs
@@ -41,22 +41,28 @@ impl Default for State {
}
}
+impl Default for DimensionIndicator {
+ fn default() -> Self {
+ Self {
+ state: State::default(),
+ bounds: Rect::new(0., 0., 0., 0.),
+ }
+ }
+}
+
impl DimensionIndicator {
/// Create a new dimension indicator. While it is possible to have multiple instances, this is
/// not generally recommended, since they will need to be managed carefully or otherwise steal
/// keystrokes from each other.
pub fn new() -> Self {
- Self {
- state: State::default(),
- bounds: Rect::new(0., 0., 0., 0.),
- }
+ Self::default()
}
/// Update whatever is selected on the map according to the dimension indicator rules and rulers.
pub fn update(&mut self, map: &mut Map, rl: &mut RaylibHandle) {
- match &self.state {
- &State::Watching => self.update_watching(map, rl),
- &State::Ruling { .. } => self.update_ruling(map, rl),
+ match self.state {
+ State::Watching => self.update_watching(map, rl),
+ State::Ruling { .. } => self.update_ruling(map, rl),
};
}
diff --git a/src/gui/tool_sidebar.rs b/src/gui/tool_sidebar.rs
index e6b8867..78041e7 100644
--- a/src/gui/tool_sidebar.rs
+++ b/src/gui/tool_sidebar.rs
@@ -3,7 +3,7 @@
// TODO: Currently, the keyboard shortcuts for tools are handled by the editor, but a lot speaks for
// them being handled by the ToolSidebar instead.
-use crate::math::{Rect, Surface, Vec2};
+use crate::math::{ExactSurface, Rect, Vec2};
use crate::tool::ToolType;
use crate::Editor;
use raylib::core::texture::Texture2D;
@@ -30,6 +30,9 @@ impl ToolSidebar {
}
fn panel_rect(screen_height: u16) -> Rect<f32> {
+ /* The width is currently hardcoded as 104, which is
+ * 64 (button-size) + 20 left gap + 20 right gap
+ */
Rect::new(0., 0., 104., screen_height as f32)
}
diff --git a/src/main.rs b/src/main.rs
index 345477b..105bb44 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -22,6 +22,7 @@
extern crate log;
pub mod button;
+pub mod cli;
pub mod colours;
pub mod config;
pub mod editor;
@@ -35,8 +36,10 @@ pub mod tool;
pub mod transform;
pub mod transformable;
+use cli::CLI;
use config::Config;
use editor::Editor;
+use float_cmp::F64Margin;
use gui::{DimensionIndicator, ToolSidebar};
use raylib::prelude::*;
use snapping::Snapper;
@@ -49,6 +52,14 @@ pub const GUI_STYLE: &str = "assets/style/cyber.rgs";
/// Location of the graf karto configuration options file.
pub const CONFIG_FILE: &str = "config.ron";
+/// The acceptable error that is used throughout the project for two floats to be considered equal.
+/// If it is set too low, the editor might not work properly, if set too high, the granularity may
+/// become too low for certain purposes.
+pub const FLOAT_MARGIN: F64Margin = F64Margin {
+ epsilon: 10000. * f64::EPSILON,
+ ulps: 0,
+};
+
fn main() {
pretty_env_logger::init();
@@ -89,6 +100,7 @@ fn main() {
let mut dimension_indicator = DimensionIndicator::new();
let tool_sidebar = ToolSidebar::new(&mut rl, &thread);
let mut snapper = Snapper::default();
+ let mut cli = CLI::new();
let mut transform = Transform::new();
let mut last_mouse_pos = rl.get_mouse_position();
@@ -113,9 +125,9 @@ fn main() {
);
}
+ cli.update(&mut rl, &mut editor);
dimension_indicator.update(editor.map_mut(), &mut rl);
snapper.update(&mut rl);
-
editor.update(
&mut rl,
&transform,
@@ -133,9 +145,9 @@ fn main() {
editor.draw_tools(&mut d, &transform);
tool_sidebar.draw(screen_height as u16, &mut d, &mut editor);
snapper.draw(&mut d);
-
gui::position_indicator_draw(&mut d, last_mouse_pos.into(), &transform, &snapper);
dimension_indicator.draw(&mut d, &transform);
+ cli.draw(&mut d);
}
}
}
diff --git a/src/map/data.rs b/src/map/data.rs
index 1031d3c..f7ec484 100644
--- a/src/map/data.rs
+++ b/src/map/data.rs
@@ -1,6 +1,6 @@
//! Module containing the raw map data version of the map.
-use super::{IconData, PolygonRoomData, RectRoomData, WallData};
+use super::{IconData, Map, PolygonRoomData, RectRoomData, WallData};
use ron::de::from_reader;
use ron::ser::{to_string_pretty, PrettyConfig};
use serde::{Deserialize, Serialize};
@@ -35,8 +35,37 @@ impl MapData {
}
}
+ /// Creates a data struct from the Map. It is important to note, that this data element is not
+ /// bound to the Map in any way after this, so changing anything won't change anything in the map.
+ /// It is useful however to for instance serialize this map without extra rendering information
+ /// included.
+ pub fn extract_data(map: &Map) -> Self {
+ Self {
+ rect_rooms: map
+ .rect_rooms()
+ .iter()
+ .map(|r| (r as &RectRoomData).clone())
+ .collect(),
+ polygon_rooms: map
+ .polygon_rooms()
+ .iter()
+ .map(|p| (p as &PolygonRoomData).clone())
+ .collect(),
+ walls: map
+ .walls()
+ .iter()
+ .map(|w| (w as &WallData).clone())
+ .collect(),
+ icons: map
+ .icons()
+ .iter()
+ .map(|i| (i as &IconData).clone())
+ .collect(),
+ }
+ }
+
/// Load the map data from a file. Fails if the file does not exist or cannot be correctly parsed.
- pub fn load_from_file<P: AsRef<Path>>(&mut self, path: P) -> io::Result<Self> {
+ pub fn load_from_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
let file = File::open(&path)?;
let data: Self = match from_reader(file) {
Ok(data) => data,
diff --git a/src/map/mod.rs b/src/map/mod.rs
index 88a7e6c..70f65b3 100644
--- a/src/map/mod.rs
+++ b/src/map/mod.rs
@@ -146,4 +146,53 @@ impl Map {
.chain(self.walls.iter_mut().map(|w| w as &mut dyn Mappable))
.chain(self.icons.iter_mut().map(|i| i as &mut dyn Mappable))
}
+
+ /// Get the rectangular rooms of this map.
+ pub fn rect_rooms(&self) -> &Vec<RectRoom> {
+ &self.rect_rooms
+ }
+
+ /// Get the polygon rooms of this map.
+ pub fn polygon_rooms(&self) -> &Vec<PolygonRoom> {
+ &self.polygon_rooms
+ }
+
+ /// Get the walls of this map.
+ pub fn walls(&self) -> &Vec<Wall> {
+ &self.walls
+ }
+
+ /// Get the icons of this map.
+ pub fn icons(&self) -> &Vec<Icon> {
+ &self.icons
+ }
+
+ /// Replace the internal map data with the data provided. (Load and replace)
+ pub fn set_data(&mut self, data: MapData) {
+ // Remove all data.
+ self.icons.clear();
+ self.polygon_rooms.clear();
+ self.rect_rooms.clear();
+ self.walls.clear();
+
+ // Add all data from the map data.
+ self.add_data(data);
+ }
+
+ /// Add the data provided to the current data on the map. All elements will remain, with the
+ /// additional elements being pushed also.
+ pub fn add_data(&mut self, data: MapData) {
+ for i in data.icons {
+ self.push_icon(Icon::from_data(i, self.icon_renderer.clone()))
+ }
+ for p in data.polygon_rooms {
+ self.push_polygon_room(p);
+ }
+ for r in data.rect_rooms {
+ self.push_rect_room(r);
+ }
+ for w in data.walls {
+ self.push_wall(w);
+ }
+ }
}
diff --git a/src/map/polygon_room.rs b/src/map/polygon_room.rs
index fd4122e..2a29436 100644
--- a/src/map/polygon_room.rs
+++ b/src/map/polygon_room.rs
@@ -6,8 +6,10 @@ use crate::colours::DEFAULT_COLOURS;
use crate::math::{self, Polygon, Rect, Triangle};
use crate::transform::Transform;
use crate::transformable::NonRigidTransformable;
+use crate::FLOAT_MARGIN;
use nalgebra::{Matrix3, Point2};
use raylib::drawing::{RaylibDraw, RaylibDrawHandle};
+use std::ops::Deref;
/// Data type for the Polygon room.
pub type PolygonRoomData = Polygon<f64>;
@@ -25,7 +27,7 @@ impl PolygonRoom {
pub fn from_data(data: PolygonRoomData) -> Self {
Self {
data: data.clone(),
- triangulated: math::triangulate(data),
+ triangulated: math::triangulate(data, FLOAT_MARGIN),
selected: false,
}
}
@@ -33,7 +35,7 @@ impl PolygonRoom {
// When the internal polygon changes, it must be retriangulated to be drawn on the screen
// properly, so this function must be called any time that happens.
fn retriangulate(&mut self) {
- self.triangulated = math::triangulate(self.data.clone());
+ self.triangulated = math::triangulate(self.data.clone(), FLOAT_MARGIN);
}
}
@@ -85,3 +87,11 @@ impl NonRigidTransformable for PolygonRoom {
self.retriangulate();
}
}
+
+impl Deref for PolygonRoom {
+ type Target = PolygonRoomData;
+
+ fn deref(&self) -> &Self::Target {
+ &self.data
+ }
+}
diff --git a/src/map/rect_room.rs b/src/map/rect_room.rs
index 6ed3ed6..ae10327 100644
--- a/src/map/rect_room.rs
+++ b/src/map/rect_room.rs
@@ -49,7 +49,7 @@ impl Mappable for RectRoom {
}
fn bounding_rect(&self) -> Rect<f64> {
- self.data.clone()
+ self.data
}
}
diff --git a/src/math/line_segment.rs b/src/math/line_segment.rs
index 204cf0c..738f56a 100644
--- a/src/math/line_segment.rs
+++ b/src/math/line_segment.rs
@@ -1,8 +1,9 @@
//! A line segment is like a line, but with a start and an end, with the line only being between
//! those two.
-use super::{Rect, Surface, TripletOrientation, Vec2};
+use super::{ExactSurface, Rect, TripletOrientation, Vec2};
use alga::general::{ClosedDiv, ClosedMul, ClosedSub};
+use float_cmp::ApproxEq;
use nalgebra::{RealField, Scalar};
use num_traits::Zero;
use serde::{Deserialize, Serialize};
@@ -37,9 +38,9 @@ impl<T: Scalar + Copy> LineSegment<T> {
/// Checks if two segments intersect. This is much more efficient than trying to find the exact
/// point of intersection, so it should be used if it is not strictly necessary to know it.
- pub fn intersect(ls1: &LineSegment<T>, ls2: &LineSegment<T>) -> bool
+ pub fn intersect<M: Copy>(ls1: &LineSegment<T>, ls2: &LineSegment<T>, margin: M) -> bool
where
- T: Scalar + Copy + ClosedSub + ClosedMul + PartialOrd + Zero,
+ T: Scalar + Copy + ClosedSub + ClosedMul + PartialOrd + Zero + ApproxEq<Margin = M>,
{
/* This algorithm works by checking the triplet orientation of the first lines points with the
* first point of the second line. After that it does the same for the second point of the
@@ -56,10 +57,14 @@ impl<T: Scalar + Copy> LineSegment<T> {
*/
// Cache the necessary orientations.
- let ls1_ls2start_orientation = super::triplet_orientation(ls1.start, ls1.end, ls2.start);
- let ls1_ls2end_orientation = super::triplet_orientation(ls1.start, ls1.end, ls2.end);
- let ls2_ls1start_orientation = super::triplet_orientation(ls2.start, ls2.end, ls1.start);
- let ls2_ls1end_orientation = super::triplet_orientation(ls2.start, ls2.end, ls1.end);
+ let ls1_ls2start_orientation =
+ super::triplet_orientation(ls1.start, ls1.end, ls2.start, margin);
+ let ls1_ls2end_orientation =
+ super::triplet_orientation(ls1.start, ls1.end, ls2.end, margin);
+ let ls2_ls1start_orientation =
+ super::triplet_orientation(ls2.start, ls2.end, ls1.start, margin);
+ let ls2_ls1end_orientation =
+ super::triplet_orientation(ls2.start, ls2.end, ls1.end, margin);
// Check for the first case that wase described (general case).
if ls1_ls2start_orientation != ls1_ls2end_orientation
@@ -100,9 +105,14 @@ impl<T: Scalar + Copy> LineSegment<T> {
/// Try to find the the point where the two line segments intersect. If they do not intersect,
/// `None` is returned. If the lines are parallel and intersect (at least part of a line is on
/// a part of the other line), inside that region is returned.
- pub fn intersection(line_a: &LineSegment<T>, line_b: &LineSegment<T>) -> Option<Vec2<T>>
+ pub fn intersection<M>(
+ line_a: &LineSegment<T>,
+ line_b: &LineSegment<T>,
+ margin: M,
+ ) -> Option<Vec2<T>>
where
- T: ClosedSub + ClosedMul + ClosedDiv + Zero + RealField,
+ T: ClosedSub + ClosedMul + ClosedDiv + Zero + RealField + ApproxEq<Margin = M>,
+ M: Copy,
{
// Calculate the differences of each line value from starting point to end point
// coordinate.
@@ -115,8 +125,10 @@ impl<T: Scalar + Copy> LineSegment<T> {
let d = (dax * dby) - (day * dbx);
if d == T::zero() {
// The two line segments are parallel, check if one may be on the other.
- if super::triplet_orientation(line_a.start, line_a.end, line_b.start) == TripletOrientation::Collinear
- && super::triplet_orientation(line_a.start, line_a.end, line_b.end) == TripletOrientation::Collinear
+ if super::triplet_orientation(line_a.start, line_a.end, line_b.start, margin)
+ == TripletOrientation::Collinear
+ && super::triplet_orientation(line_a.start, line_a.end, line_b.end, margin)
+ == TripletOrientation::Collinear
{
if line_a.contains_collinear(line_b.start) {
Some(line_b.start)
@@ -156,16 +168,16 @@ impl<T: Scalar + Copy> LineSegment<T> {
/// Find all segments, into which this LineSegment would be splitted, when the points provided
/// would cut the segment. The points must be on the line, otherwise this does not make sense.
/// Also, no segments of length zero (start point = end point) will be created.
- pub fn segments(&self, split_points: &[Vec2<T>]) -> Vec<Vec2<T>>
+ pub fn segments<M>(&self, split_points: &[Vec2<T>], margin: M) -> Vec<Vec2<T>>
where
- T: ClosedSub + ClosedMul + PartialOrd + Zero,
+ T: ClosedSub + ClosedMul + PartialOrd + Zero + ApproxEq<Margin = M>,
+ M: Copy,
{
// Make sure all segments are collinear with the segment and actually on it
assert_eq!(
- split_points
- .iter()
- .find(|&p| super::triplet_orientation(self.start, self.end, *p)
- != TripletOrientation::Collinear),
+ split_points.iter().find(|&p| super::triplet_orientation(
+ self.start, self.end, *p, margin
+ ) != TripletOrientation::Collinear),
None
);
assert_eq!(
diff --git a/src/math/polygon/mod.rs b/src/math/polygon/mod.rs
index 1577f63..bc145ed 100644
--- a/src/math/polygon/mod.rs
+++ b/src/math/polygon/mod.rs
@@ -6,12 +6,12 @@ pub mod triangulate;
pub use polygon_graph::*;
pub use triangulate::*;
-use super::{LineSegment, Rect, Surface, TripletOrientation, Vec2};
+use super::{ExactSurface, LineSegment, Rect, Surface, TripletOrientation, Vec2};
use crate::math;
-use nalgebra::{ClosedDiv, ClosedMul, ClosedSub, RealField, Scalar};
-use num_traits::Zero;
+use float_cmp::ApproxEq;
+use nalgebra::{RealField, Scalar};
use serde::{Deserialize, Serialize};
-use std::ops::Neg;
+use std::fmt::Debug;
use thiserror::Error;
/// Describes errors that can happen when handling polygons, especially on creation.
@@ -41,13 +41,14 @@ impl<T: Scalar + Copy> Polygon<T> {
/// be reversed to be left-turning.
/// Checks if the corners make a valid polygon before creating it. If the check fails, an error
/// will be returned.
- pub fn new(corners: Vec<Vec2<T>>) -> Result<Self, PolygonError<T>>
+ pub fn new<M>(corners: Vec<Vec2<T>>, t_margin: M) -> Result<Self, PolygonError<T>>
where
- T: RealField,
+ T: RealField + ApproxEq<Margin = M>,
+ M: Copy,
{
- Self::check_validity(&corners)?;
+ Self::check_validity(&corners, t_margin)?;
- let corners = if combined_angle(&corners) > T::zero() {
+ let corners = if combined_angle(&corners, t_margin) > T::zero() {
corners
} else {
corners.into_iter().rev().collect()
@@ -57,13 +58,14 @@ impl<T: Scalar + Copy> Polygon<T> {
}
/// Like new, but does not perform any validity checks, so be careful when using this function.
- pub fn new_unchecked(corners: Vec<Vec2<T>>) -> Self
+ pub fn new_unchecked<M>(corners: Vec<Vec2<T>>, t_margin: M) -> Self
where
- T: RealField,
+ T: RealField + ApproxEq<Margin = M>,
+ M: Copy,
{
- assert!(Polygon::check_validity(&corners).is_ok());
+ assert!(Polygon::check_validity(&corners, t_margin).is_ok());
- let corners = if combined_angle(&corners) > T::zero() {
+ let corners = if combined_angle(&corners, t_margin) > T::zero() {
corners
} else {
corners.into_iter().rev().collect()
@@ -74,9 +76,10 @@ impl<T: Scalar + Copy> Polygon<T> {
/// Checks if a set of corners can be made into a polygon or not. Returns Ok if they can, or
/// the reason they cannot in form of a PolygonError.
- pub fn check_validity(corners: &[Vec2<T>]) -> Result<(), PolygonError<T>>
+ pub fn check_validity<M>(corners: &[Vec2<T>], t_margin: M) -> Result<(), PolygonError<T>>
where
- T: RealField,
+ T: RealField + ApproxEq<Margin = M>,
+ M: Copy,
{
if corners.len() < 3 {
return Err(PolygonError::TooFewVertices(corners.len()));
@@ -105,7 +108,7 @@ impl<T: Scalar + Copy> Polygon<T> {
let next_j = (j + 1) % corners.len();
let line_j = LineSegment::new(corners[j], corners[next_j]);
- if LineSegment::intersect(&line_i, &line_j) {
+ if LineSegment::intersect(&line_i, &line_j, t_margin) {
return Err(PolygonError::SelfIntersect(line_i, line_j));
}
}
@@ -170,31 +173,28 @@ impl<T: Scalar + Copy> Polygon<T> {
/// Join this polygon with another, ensuring the area of the two stays the same, but the
/// overlap is not doubled, but instead joined into one.
/// Returns the Polygons themselves, if there is no overlap
- pub fn unite(self, other: Polygon<T>) -> Vec<Polygon<T>>
+ pub fn unite<M>(self, other: Polygon<T>, t_margin: M) -> Vec<Polygon<T>>
where
- T: RealField,
+ T: RealField + ApproxEq<Margin = M>,
+ M: Copy,
{
let mut graph = PolygonGraph::from_polygon(&self);
graph.add_all(&other);
- // TODO: Make bounding box support multiple polygons
- vec![graph.bounding_polygon()]
+ // TODO: Make bounding polygon support multiple polygons
+ match graph.bounding_polygon(t_margin) {
+ Some(polygon) => vec![polygon],
+ None => vec![],
+ }
}
}
-impl<
- T: Scalar
- + Copy
- + ClosedSub
- + ClosedMul
- + ClosedDiv
- + Neg<Output = T>
- + PartialOrd
- + RealField
- + Zero,
- > Surface<T> for Polygon<T>
+impl<T, M> Surface<T, M> for Polygon<T>
+where
+ T: RealField + ApproxEq<Margin = M>,
+ M: Copy,
{
- fn contains_point(&self, p: &Vec2<T>) -> bool {
+ fn contains_point(&self, p: &Vec2<T>, margin: M) -> bool {
let n = self.corners.len();
let a = self.corners[n - 1];
@@ -247,7 +247,7 @@ impl<
}
let lx = ax + (((bx - ax) * -ay) / (by - ay));
- if lx == T::zero() {
+ if lx.approx_eq(T::zero(), margin) {
// point on edge
return true;
}
@@ -269,11 +269,13 @@ impl<
(depth & 1) == 1
}
- fn contains_line_segment(&self, line_segment: &LineSegment<T>) -> bool {
+ fn contains_line_segment(&self, line_segment: &LineSegment<T>, margin: M) -> bool {
/* In case at least one of the points is not contained by the polygon, the line cannot lie
* inside of the polygon in its entirety.
*/
- if !self.contains_point(&line_segment.start) || !self.contains_point(&line_segment.end) {
+ if !self.contains_point(&line_segment.start, margin)
+ || !self.contains_point(&line_segment.end, margin)
+ {
return false;
}
@@ -294,9 +296,13 @@ impl<
let prev = (c + self.corners.len() - 1) % self.corners.len();
let next = (c + 1) % self.corners.len();
- let edge_angle =
- math::triplet_angle(self.corners[prev], self.corners[c], self.corners[next]);
- let vec_angle = math::triplet_angle(self.corners[prev], self.corners[c], p);
+ let edge_angle = math::triplet_angle(
+ self.corners[prev],
+ self.corners[c],
+ self.corners[next],
+ margin,
+ );
+ let vec_angle = math::triplet_angle(self.corners[prev], self.corners[c], p, margin);
vec_angle == T::zero() || vec_angle >= edge_angle
};
@@ -328,16 +334,18 @@ impl<
let current_edge = LineSegment::new(self.corners[c], self.corners[next]);
- if LineSegment::intersect(&line_segment, &current_edge) {
+ if LineSegment::intersect(&line_segment, &current_edge, margin) {
let orientation_start = math::triplet_orientation(
current_edge.start,
current_edge.end,
line_segment.start,
+ margin,
);
let orientation_end = math::triplet_orientation(
current_edge.start,
current_edge.end,
line_segment.end,
+ margin,
);
match (orientation_start, orientation_end) {
/* If at least one of the points is on the edge, make sure, the line points
@@ -364,7 +372,7 @@ impl<
true
}
- fn contains_rect(&self, rect: &Rect<T>) -> bool {
+ fn contains_rect(&self, rect: &Rect<T>, margin: M) -> bool {
/* Turn the rectangle into a vector with its hull line segments. If all hull segments are
* contained in the polygon, the rectangle is contained completely.
*/
@@ -393,18 +401,19 @@ impl<
hull_edges
.iter()
- .all(|edge| self.contains_line_segment(edge))
+ .all(|edge| self.contains_line_segment(edge, margin))
}
- fn contains_polygon(&self, polygon: &Polygon<T>) -> bool {
+ fn contains_polygon(&self, polygon: &Polygon<T>, margin: M) -> bool {
/* Check for all edges of the polygon that they are contained by the polygon. If they all
* are, then the polygon itself must also be contained.
*/
for i in 0..polygon.corners.len() {
let next = (i + 1) % polygon.corners.len();
- if !self
- .contains_line_segment(&LineSegment::new(polygon.corners[i], polygon.corners[next]))
- {
+ if !self.contains_line_segment(
+ &LineSegment::new(polygon.corners[i], polygon.corners[next]),
+ margin,
+ ) {
return false;
}
}
@@ -421,13 +430,17 @@ impl<
* after another until finally connecting the last point to the first point in radians. Negative,
* when the points in sum are right-turning, positive, when they are left-turning.
*/
-fn combined_angle<T: Scalar + Copy + RealField>(points: &[Vec2<T>]) -> T {
+fn combined_angle<T: Scalar + Copy + RealField, M>(points: &[Vec2<T>], margin: M) -> T
+where
+ T: ApproxEq<Margin = M>,
+ M: Copy,
+{
let mut combined_angle = T::zero();
for i in 0..points.len() {
let prev = (i + points.len() - 1) % points.len();
let next = (i + 1) % points.len();
- let angle = math::triplet_angle(points[prev], points[i], points[next]);
+ let angle = math::triplet_angle(points[prev], points[i], points[next], margin);
if angle == T::zero() || angle == T::two_pi() {
continue;
}
@@ -445,21 +458,27 @@ mod test {
#[test]
fn check_validity() {
- Polygon::check_validity(&[Vec2::new(0., 0.), Vec2::new(1., 0.), Vec2::new(0., 1.)])
- .expect("Simple triangle does not pass validity check");
+ Polygon::check_validity(
+ &[Vec2::new(0., 0.), Vec2::new(1., 0.), Vec2::new(0., 1.)],
+ (f64::EPSILON, 0),
+ )
+ .expect("Simple triangle does not pass validity check");
}
#[test]
fn polygon_contains() {
- let polygon = Polygon::new(vec![
- Vec2::new(0., 0.),
- Vec2::new(-1., 1.),
- Vec2::new(0., 2.),
- Vec2::new(1., 3.),
- Vec2::new(3., 1.5),
- Vec2::new(2., 0.),
- Vec2::new(1., 1.),
- ])
+ let polygon = Polygon::new(
+ vec![
+ Vec2::new(0., 0.),
+ Vec2::new(-1., 1.),
+ Vec2::new(0., 2.),
+ Vec2::new(1., 3.),
+ Vec2::new(3., 1.5),
+ Vec2::new(2., 0.),
+ Vec2::new(1., 1.),
+ ],
+ (f64::EPSILON, 0),
+ )
.unwrap();
assert!(!polygon.contains_point(&Vec2::new(1., -2.)));
@@ -474,18 +493,21 @@ mod test {
#[test]
fn contains_line_segment() {
- let polygon = Polygon::new(vec![
- Vec2::new(0., 0.),
- Vec2::new(0., 4.5),
- Vec2::new(6.5, 4.5),
- Vec2::new(5.5, 0.),
- Vec2::new(5.5, 3.),
- Vec2::new(1.5, 3.),
- Vec2::new(1.5, 1.),
- Vec2::new(2., 0.5),
- Vec2::new(4., 2.),
- Vec2::new(4., 0.),
- ])
+ let polygon = Polygon::new(
+ vec![
+ Vec2::new(0., 0.),
+ Vec2::new(0., 4.5),
+ Vec2::new(6.5, 4.5),
+ Vec2::new(5.5, 0.),
+ Vec2::new(5.5, 3.),
+ Vec2::new(1.5, 3.),
+ Vec2::new(1.5, 1.),
+ Vec2::new(2., 0.5),
+ Vec2::new(4., 2.),
+ Vec2::new(4., 0.),
+ ],
+ (f64::EPSILON, 0),
+ )
.unwrap();
/* NOTE: From now on, inside means inside the polygon, but might be on an edge or on a
@@ -531,22 +553,28 @@ mod test {
#[test]
fn polygon_union() {
- let first = Polygon::new(vec![
- Vec2::new(-2., 1.),
- Vec2::new(-0.5, 2.5),
- Vec2::new(2., 2.),
- Vec2::new(0.5, 1.5),
- Vec2::new(1., 0.),
- Vec2::new(-0.5, 1.),
- ])
+ let first = Polygon::new(
+ vec![
+ Vec2::new(-2., 1.),
+ Vec2::new(-0.5, 2.5),
+ Vec2::new(2., 2.),
+ Vec2::new(0.5, 1.5),
+ Vec2::new(1., 0.),
+ Vec2::new(-0.5, 1.),
+ ],
+ (f64::EPSILON, 0),
+ )
.unwrap();
- let second = Polygon::new(vec![
- Vec2::new(0., 0.),
- Vec2::new(-2., 2.),
- Vec2::new(3., 2.),
- Vec2::new(1.5, 0.),
- ])
+ let second = Polygon::new(
+ vec![
+ Vec2::new(0., 0.),
+ Vec2::new(-2., 2.),
+ Vec2::new(3., 2.),
+ Vec2::new(1.5, 0.),
+ ],
+ (f64::EPSILON, 0),
+ )
.unwrap();
let union = first.unite(second);
diff --git a/src/math/polygon/polygon_graph.rs b/src/math/polygon/polygon_graph.rs
index fd373dd..5e3a576 100644
--- a/src/math/polygon/polygon_graph.rs
+++ b/src/math/polygon/polygon_graph.rs
@@ -5,16 +5,18 @@
use super::Polygon;
use crate::math::{self, LineSegment, Vec2};
+use float_cmp::ApproxEq;
use nalgebra::{RealField, Scalar};
use std::cmp::{Ordering, PartialOrd};
-#[derive(Debug)]
-struct Node<T: Scalar + Copy> {
+#[derive(Debug, Clone)]
+pub(super) struct Node<T: Scalar + Copy> {
pub vec: Vec2<T>,
pub adjacent: Vec<Vec2<T>>,
}
-struct EdgeIterator<'a, T: Scalar + Copy> {
+/// An iterator over the graph edges. These are not in a particular order.
+pub struct EdgeIterator<'a, T: Scalar + Copy> {
nodes: &'a [Node<T>],
pos: (usize, usize),
}
@@ -22,7 +24,7 @@ struct EdgeIterator<'a, T: Scalar + Copy> {
/// An undirected graph, that is optimised for polygon edge operations. Since edges of a polygon
/// are an undirected graph, this structure also holds both directions. This makes it rather space
/// inefficient, but makes edge operations rather swift.
-#[derive(Debug)]
+#[derive(Debug, Clone)]
pub struct PolygonGraph<T: Scalar + Copy + PartialOrd> {
/// The nodes of the graph, together with their adjacency list.
nodes: Vec<Node<T>>,
@@ -45,7 +47,7 @@ fn find_node<T: Scalar + Copy + PartialOrd>(
}
impl<'a, T: Scalar + Copy> EdgeIterator<'a, T> {
- pub fn new(nodes: &'a [Node<T>]) -> Self {
+ pub(super) fn new(nodes: &'a [Node<T>]) -> Self {
Self { nodes, pos: (0, 0) }
}
}
@@ -114,6 +116,11 @@ impl<T: Scalar + Copy + PartialOrd> PolygonGraph<T> {
// Helper function to add the edge into the internal graph representation for one side only.
// Since to the outside the graph should always be undirected, this must be private.
fn add_edge_onesided(&mut self, from: &Vec2<T>, to: &Vec2<T>) -> bool {
+ // Cannot add self-referential edges.
+ if from == to {
+ return false;
+ }
+
match find_node(&self.nodes, from) {
Ok(pos) => match find_vec2(&self.nodes[pos].adjacent, to) {
Ok(_) => return false,
@@ -131,8 +138,10 @@ impl<T: Scalar + Copy + PartialOrd> PolygonGraph<T> {
true
}
- /// Add an edge between the given vectors. If the edge already exists, it does nothing and
- /// returns false, otherwise it returns true after addition.
+ /// Add an edge between the given vectors. If the edge already exists or the starting and end
+ /// point are the same, it does nothing and returns false, otherwise it returns true after
+ /// addition. Note, that in a normal graph adding a self-referential edge would be perfectly fine,
+ /// but in a graph representing a polygon this does not really make sense.
pub fn add_edge(&mut self, from: &Vec2<T>, to: &Vec2<T>) -> bool {
if !self.add_edge_onesided(from, to) {
return false;
@@ -204,9 +213,10 @@ impl<T: Scalar + Copy + PartialOrd> PolygonGraph<T> {
/// Calculates all points where the graph edges intersect with one another. It then adds them
/// into the adjacency list such that the intersection point lies between the nodes of the
/// lines.
- pub fn intersect_self(&mut self)
+ pub fn intersect_self<M>(&mut self, margin: M)
where
- T: RealField,
+ T: RealField + ApproxEq<Margin = M>,
+ M: Copy,
{
// Find all intersections with all other edges.
let mut to_delete: Vec<LineSegment<T>> = Vec::new();
@@ -216,12 +226,14 @@ impl<T: Scalar + Copy + PartialOrd> PolygonGraph<T> {
* intersecting with.
*/
let mut intersections: Vec<Vec2<T>> = Vec::new();
- for compare_segment in EdgeIterator::new(&self.nodes) {
+ for compare_segment in self.edge_iter() {
if segment.eq_ignore_dir(&compare_segment) {
continue;
}
- if let Some(intersection) = LineSegment::intersection(&segment, &compare_segment) {
+ if let Some(intersection) =
+ LineSegment::intersection(&segment, &compare_segment, margin)
+ {
intersections.push(intersection);
}
}
@@ -233,7 +245,7 @@ impl<T: Scalar + Copy + PartialOrd> PolygonGraph<T> {
to_delete.push(segment.clone());
// Safe, since at least the line segment itself is represented.
- let segments = segment.segments(&intersections);
+ let segments = segment.segments(&intersections, margin);
for i in 1..segments.len() {
to_add.push((segments[i - 1], segments[i]));
}
@@ -247,16 +259,32 @@ impl<T: Scalar + Copy + PartialOrd> PolygonGraph<T> {
}
}
+ /// Get an iterator over all edges in the graph.
+ pub fn edge_iter(&self) -> EdgeIterator<T> {
+ EdgeIterator::new(&self.nodes)
+ }
+
+ /// Check if the the graph has a vertex (node) at the given position. Returns true if so.
+ /// A point that lies on an edge, but is not registered as a node will not count.
+ pub fn has_node(&self, at: &Vec2<T>) -> bool {
+ find_node(&self.nodes, at).is_ok()
+ }
+
/// Finds the minimal polygon that could hold this graph together, i.e. could contain the
/// entire graph, but with the minimal amount of space. It may however still contain extra
/// corner points, meaning an extra edge for three collinear points on this edge, that can be
/// cleaned up.
- pub fn bounding_polygon(mut self) -> Polygon<T>
+ /// If the graph cannot be turned into a polygon, it will return `None`
+ pub fn bounding_polygon<M>(mut self, margin: M) -> Option<Polygon<T>>
where
- T: RealField,
+ T: RealField + ApproxEq<Margin = M>,
+ M: Copy,
{
- assert!(self.num_nodes() >= 3);
- self.intersect_self();
+ if self.num_nodes() < 3 {
+ return None;
+ }
+
+ self.intersect_self(margin);
/* Start with the top-left node. Since the nodes are always sorted by y over x from top to
* bottom and left to right, this is the very first element in the vector. This is also a
@@ -279,8 +307,8 @@ impl<T: Scalar + Copy + PartialOrd> PolygonGraph<T> {
.adjacent
.iter()
.max_by(|&a, &b| {
- math::triplet_angle(last_vec, current_node.vec, *a)
- .partial_cmp(&math::triplet_angle(last_vec, current_node.vec, *b))
+ math::triplet_angle(last_vec, current_node.vec, *a, margin)
+ .partial_cmp(&math::triplet_angle(last_vec, current_node.vec, *b, margin))
.unwrap_or(Ordering::Equal)
})
.expect("Adjacency list is empty. The polygon has an open edge (is broken)");
@@ -296,7 +324,8 @@ impl<T: Scalar + Copy + PartialOrd> PolygonGraph<T> {
.expect("Failure to find node that should be inside list.")];
}
- Polygon::new(bounding_corners).expect("PolygonGraph produced invalid polygon")
+ // Try to create a polygon from the corners and return it.
+ Polygon::new(bounding_corners, margin).ok()
}
}
diff --git a/src/math/polygon/triangulate.rs b/src/math/polygon/triangulate.rs
index 8a18cd7..4106095 100644
--- a/src/math/polygon/triangulate.rs
+++ b/src/math/polygon/triangulate.rs
@@ -2,7 +2,8 @@
use super::Polygon;
use crate::math::{self, LineSegment, Surface, Triangle};
-use nalgebra::{RealField, Scalar};
+use float_cmp::ApproxEq;
+use nalgebra::RealField;
/// Type that saves the flags that match a corner in a space efficient manner.
type Flags = u8;
@@ -16,9 +17,10 @@ const FLAG_EAR: Flags = 0b0000_0001;
// used. Consider removing it entirely.
const FLAG_CONVEX: Flags = 0b0000_0010;
-fn flag_corner<T: Scalar + Copy>(polygon: &Polygon<T>, corner: usize) -> Flags
+fn flag_corner<T: RealField, M>(polygon: &Polygon<T>, corner: usize, margin: M) -> Flags
where
- T: RealField,
+ T: ApproxEq<Margin = M>,
+ M: Copy,
{
// First, check if it is convex. If it is not, it can also not be an ear.
let prev = (corner + polygon.corners.len() - 1) % polygon.corners.len();
@@ -31,6 +33,7 @@ where
polygon.corners[prev],
polygon.corners[corner],
polygon.corners[next],
+ margin,
) < T::pi()
{
// The corner is reflex.
@@ -38,10 +41,10 @@ where
}
// The corner is convex, check if it is also an ear.
- if polygon.contains_line_segment(&LineSegment::new(
- polygon.corners[prev],
- polygon.corners[next],
- )) {
+ if polygon.contains_line_segment(
+ &LineSegment::new(polygon.corners[prev], polygon.corners[next]),
+ margin,
+ ) {
// Corner is an ear.
FLAG_EAR | FLAG_CONVEX
} else {
@@ -50,13 +53,14 @@ where
}
}
-/// Uses earclipping algorithm (see https://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf)
+/// Uses earclipping algorithm (see <https://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf>)
/// to find an explanation of what exactly is happening.
/// Currently only handles simple polygons, but once the polygon struct supports holes must be
/// extended to also support those.
-pub fn triangulate<T: Scalar + Copy>(mut polygon: Polygon<T>) -> Vec<Triangle<T>>
+pub fn triangulate<T: RealField, M>(mut polygon: Polygon<T>, margin: M) -> Vec<Triangle<T>>
where
- T: RealField,
+ T: ApproxEq<Margin = M>,
+ M: Copy,
{
assert!(polygon.corners.len() >= 3);
/* Information about the corner of the polygon. See the flags constant for information about
@@ -64,7 +68,7 @@ where
*/
let mut flags = Vec::with_capacity(polygon.corners.len());
for c in 0..polygon.corners.len() {
- flags.push(flag_corner(&polygon, c));
+ flags.push(flag_corner(&polygon, c, margin));
}
let mut triangles = Vec::with_capacity(polygon.corners.len() - 2);
@@ -91,6 +95,7 @@ where
polygon.corners[prev],
polygon.corners[ear],
polygon.corners[next],
+ margin,
));
// Remove the ear from the polygon and the flag list.
@@ -107,8 +112,8 @@ where
};
let next = if ear == polygon.corners.len() { 0 } else { ear };
- flags[prev] = flag_corner(&polygon, prev);
- flags[next] = flag_corner(&polygon, next);
+ flags[prev] = flag_corner(&polygon, prev, margin);
+ flags[next] = flag_corner(&polygon, next, margin);
}
// Push the remaining triangle into the list.
@@ -116,6 +121,7 @@ where
polygon.corners[0],
polygon.corners[1],
polygon.corners[2],
+ margin,
));
triangles
@@ -128,18 +134,21 @@ mod test {
#[test]
fn triangulate() {
- let polygon = Polygon::new(vec![
- Vec2::new(0., 0.),
- Vec2::new(0., 4.5),
- Vec2::new(6.5, 4.5),
- Vec2::new(5.5, 0.),
- Vec2::new(5.5, 3.),
- Vec2::new(1.5, 3.),
- Vec2::new(1.5, 1.),
- Vec2::new(2., 0.5),
- Vec2::new(4., 2.),
- Vec2::new(4., 0.),
- ])
+ let polygon = Polygon::new(
+ vec![
+ Vec2::new(0., 0.),
+ Vec2::new(0., 4.5),
+ Vec2::new(6.5, 4.5),
+ Vec2::new(5.5, 0.),
+ Vec2::new(5.5, 3.),
+ Vec2::new(1.5, 3.),
+ Vec2::new(1.5, 1.),
+ Vec2::new(2., 0.5),
+ Vec2::new(4., 2.),
+ Vec2::new(4., 0.),
+ ],
+ (f64::EPSILON, 0),
+ )
.unwrap();
let triangles = super::triangulate(polygon);
diff --git a/src/math/rect.rs b/src/math/rect.rs
index 6f993d1..b019ad5 100644
--- a/src/math/rect.rs
+++ b/src/math/rect.rs
@@ -1,9 +1,8 @@
//! Rectangles where the sides are parallel to the x and y-axes.
-use super::{LineSegment, Polygon, Surface, Vec2};
+use super::{ExactSurface, LineSegment, Polygon, Vec2};
//use alga::general::{Additive, Identity};
-use nalgebra::{ClosedAdd, ClosedSub, RealField, Scalar};
-use num_traits::identities::Zero;
+use nalgebra::{RealField, Scalar};
use num_traits::{NumCast, ToPrimitive};
use serde::{Deserialize, Serialize};
use std::ops::{Add, AddAssign};
@@ -150,7 +149,7 @@ impl<T: Scalar + Copy> Rect<T> {
}
}
-impl<T: Scalar + Copy + PartialOrd + ClosedAdd + ClosedSub + Zero> Surface<T> for Rect<T> {
+impl<T: RealField> ExactSurface<T> for Rect<T> {
fn contains_point(&self, point: &Vec2<T>) -> bool {
point.x >= self.x
&& point.x <= self.x + self.w
diff --git a/src/math/surface.rs b/src/math/surface.rs
index ab1c703..088ac47 100644
--- a/src/math/surface.rs
+++ b/src/math/surface.rs
@@ -1,10 +1,34 @@
//! Surfaces, which are areas at a certain position in a vector space.
use super::{LineSegment, Polygon, Rect, Vec2};
-use nalgebra::Scalar;
+use float_cmp::ApproxEq;
+use nalgebra::RealField;
-/// Trait that describes an area in the vector space on the field of T
-pub trait Surface<T: Scalar + Copy> {
+/// Trait that describes an area in the vector space on the field of T, with T unable to be
+/// used without rounding.
+pub trait Surface<T: RealField, M>
+where
+ T: ApproxEq<Margin = M>,
+{
+ /// Checks if a point lies on this surface.
+ fn contains_point(&self, point: &Vec2<T>, margin: M) -> bool;
+
+ /// Checks if a line segment is entirely contained by this surface.
+ fn contains_line_segment(&self, line_segment: &LineSegment<T>, margin: M) -> bool;
+
+ /// Checks if a rectangle is entirely contained inside this surface.
+ fn contains_rect(&self, rect: &Rect<T>, margin: M) -> bool;
+
+ /// Checks if a polygon is contained wholly by this surface.
+ fn contains_polygon(&self, polygon: &Polygon<T>, margin: M) -> bool;
+
+ /// Checks if this surface is contained by the rect in it's entirety. Think of it as the reverse
+ /// operation for contains_... on a rectangle.
+ fn is_inside_rect(&self, rect: &Rect<T>) -> bool;
+}
+
+/// The same as Surface, but the vector space will be assumed to be perfectly divideable or checkable.
+pub trait ExactSurface<T: RealField> {
/// Checks if a point lies on this surface.
fn contains_point(&self, point: &Vec2<T>) -> bool;
@@ -21,3 +45,28 @@ pub trait Surface<T: Scalar + Copy> {
/// operation for contains_... on a rectangle.
fn is_inside_rect(&self, rect: &Rect<T>) -> bool;
}
+
+/*
+// Every exact surface must also be an approximate surface, with margin 0 to be exact.
+impl<T, S> Surface<T> for S where S: ExactSurface<T> {
+ fn contains_point<M>(&self, point: &Vec2<T>, _margin: M) -> bool {
+ ExactSurface::contains_point(&self, point)
+ }
+
+ fn contains_line_segment<M>(&self, line_segment: &LineSegment<T>, margin: M) -> bool {
+ ExactSurface::contains_line_segment(&self, line_segment)
+ }
+
+ fn contains_rect<M>(&self, rect: &Rect<T>, margin: M) -> bool {
+ ExactSurface::contains_rect(&self, rect)
+ }
+
+ fn contains_polygon<M>(&self, polygon: &Polygon<T>, margin: M) -> bool {
+ ExactSurface::contains_polygon(&self, polygon)
+ }
+
+ fn is_inside_rect(&self, rect: &Rect<T>) -> bool {
+ ExactSurface::is_inside_rect(&self, rect)
+ }
+}
+*/
diff --git a/src/math/triangle.rs b/src/math/triangle.rs
index b5c1bda..2b0b9ac 100644
--- a/src/math/triangle.rs
+++ b/src/math/triangle.rs
@@ -2,6 +2,7 @@
use super::{LineSegment, Vec2};
use alga::general::{ClosedMul, ClosedSub};
+use float_cmp::ApproxEq;
use nalgebra::{RealField, Scalar};
use num_traits::Zero;
@@ -15,12 +16,13 @@ pub struct Triangle<T: Scalar + Copy> {
impl<T: Scalar + Copy> Triangle<T> {
/// Create a new Triangle as defined by its three corner points
- pub fn new(a: Vec2<T>, b: Vec2<T>, c: Vec2<T>) -> Self
+ pub fn new<M>(a: Vec2<T>, b: Vec2<T>, c: Vec2<T>, margin: M) -> Self
where
- T: ClosedSub + ClosedMul + PartialOrd + Zero,
+ T: ClosedSub + ClosedMul + PartialOrd + Zero + ApproxEq<Margin = M>,
+ M: Copy,
{
// Make sure the three points are in counterclockwise order.
- match triplet_orientation(a, b, c) {
+ match triplet_orientation(a, b, c, margin) {
TripletOrientation::Counterclockwise => Self { corners: [a, b, c] },
TripletOrientation::Clockwise => Self { corners: [a, c, b] },
TripletOrientation::Collinear => {
@@ -40,24 +42,26 @@ impl<T: Scalar + Copy> Triangle<T> {
/// Create a new Triangle from a three-point slice, instead of the three points one after
/// another.
- pub fn from_slice(corners: [Vec2<T>; 3]) -> Self
+ pub fn from_slice<M>(corners: [Vec2<T>; 3], margin: M) -> Self
where
- T: ClosedSub + ClosedMul + PartialOrd + Zero,
+ T: ClosedSub + ClosedMul + PartialOrd + Zero + ApproxEq<Margin = M>,
+ M: Copy,
{
- Self::new(corners[0], corners[1], corners[2])
+ Self::new(corners[0], corners[1], corners[2], margin)
}
/// Check if the triangle contains a given point. If the point is right on an edge, it still
/// counts as inside it.
- pub fn contains_point(&self, point: Vec2<T>) -> bool
+ pub fn contains_point<M>(&self, point: Vec2<T>, margin: M) -> bool
where
- T: ClosedSub + ClosedMul + PartialOrd + Zero,
+ T: ClosedSub + ClosedMul + PartialOrd + Zero + ApproxEq<Margin = M>,
+ M: Copy,
{
// Since the points are ordered counterclockwise, check if the point is to the left of all
// edges (or on an edge) from one point to the next. When the point is to the left of all
// edges, it must be inside the triangle.
for i in 0..3 {
- if triplet_orientation(self.corners[i], self.corners[(i + 1) % 3], point)
+ if triplet_orientation(self.corners[i], self.corners[(i + 1) % 3], point, margin)
== TripletOrientation::Clockwise
{
return false;
@@ -69,11 +73,13 @@ impl<T: Scalar + Copy> Triangle<T> {
}
/// Convert a three-point-slice into a triangle
-impl<T: Scalar + Copy + ClosedSub + ClosedMul + PartialOrd + Zero> From<[Vec2<T>; 3]>
- for Triangle<T>
+impl<T, M> From<([Vec2<T>; 3], M)> for Triangle<T>
+where
+ T: Scalar + Copy + ClosedSub + ClosedMul + PartialOrd + Zero + ApproxEq<Margin = M>,
+ M: Copy,
{
- fn from(corners: [Vec2<T>; 3]) -> Self {
- Self::new(corners[0], corners[1], corners[2])
+ fn from((corners, margin): ([Vec2<T>; 3], M)) -> Self {
+ Self::new(corners[0], corners[1], corners[2], margin)
}
}
/// Convert a triangle into a three-point-slice. The corners are in counterclockwise order.
@@ -112,18 +118,26 @@ pub(crate) enum TripletOrientation {
/// Helper function to determine which direction one would turn to traverse from the first point
/// over the second to the third point. The third option is collinear, in which case the three points
/// are on the same line.
-pub(crate) fn triplet_orientation<T>(a: Vec2<T>, b: Vec2<T>, c: Vec2<T>) -> TripletOrientation
+pub(crate) fn triplet_orientation<T, M>(
+ a: Vec2<T>,
+ b: Vec2<T>,
+ c: Vec2<T>,
+ margin: M,
+) -> TripletOrientation
where
- T: Scalar + Copy + ClosedSub + ClosedMul + PartialOrd + Zero,
+ T: Scalar + Copy + ClosedSub + ClosedMul + PartialOrd + Zero + ApproxEq<Margin = M>,
{
/* Check the slopes of the vector from a to b and b to c. If the slope of ab is greater than
* that of bc, the rotation is clockwise. If ab is smaller than bc it's counterclockwise. If
* they are the same it follows that the three points are collinear.
*/
- match (b.y - a.y) * (c.x - b.x) - (b.x - a.x) * (c.y - b.y) {
- q if q > T::zero() => TripletOrientation::Counterclockwise,
- q if q < T::zero() => TripletOrientation::Clockwise,
- _ => TripletOrientation::Collinear,
+ let slope_diff = (b.y - a.y) * (c.x - b.x) - (b.x - a.x) * (c.y - b.y);
+ if slope_diff.approx_eq(T::zero(), margin) {
+ TripletOrientation::Collinear
+ } else if slope_diff > T::zero() {
+ TripletOrientation::Counterclockwise
+ } else {
+ TripletOrientation::Clockwise
}
}
@@ -133,15 +147,15 @@ where
///
/// # Panics
/// If the length from a to b or the length from b to c is zero.
-pub(crate) fn triplet_angle<T>(a: Vec2<T>, b: Vec2<T>, c: Vec2<T>) -> T
+pub(crate) fn triplet_angle<T, M>(a: Vec2<T>, b: Vec2<T>, c: Vec2<T>, margin: M) -> T
where
- T: Scalar + Copy + ClosedSub + RealField + Zero,
+ T: Scalar + Copy + ClosedSub + RealField + Zero + ApproxEq<Margin = M>,
{
assert!(a != b);
assert!(b != c);
// Handle the extreme 0 and 180 degree cases
- let orientation = triplet_orientation(a, b, c);
+ let orientation = triplet_orientation(a, b, c, margin);
if orientation == TripletOrientation::Collinear {
if LineSegment::new(a, b).contains_collinear(c)
|| LineSegment::new(b, c).contains_collinear(a)
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<Vec2<f64>>,
- pub working_corner: Vec2<f64>,
-}
-
/// The tool itself.
pub struct PolygonRoomTool {
- unfinished_polygon: Option<UnfinishedPolygon>,
-}
-
-impl UnfinishedPolygon {
- // Check the validity of the already completed corners only
- pub fn check_validity_completed(&self) -> Result<(), PolygonError<f64>> {
- 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<f64>> {
- // 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<Polygon<f64>> {
- 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<Polygon<f64>> {
- 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<f64>> {
- 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<f64>, Vec2<f64>)>,
+ last_mouse_pos_m: Vec2<f64>,
}
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<f64>) {
- // 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<f64>; 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<f64>; 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<f64>; 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<f64>) {
- 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,