diff options
| author | Arne Dußin | 2020-12-15 00:46:54 +0100 |
|---|---|---|
| committer | Arne Dußin | 2020-12-15 22:51:46 +0100 |
| commit | 9799d3c6a8f0c242668203a1c70d7b6cfed3e855 (patch) | |
| tree | 9116acbc886f680f82309a42b4e6147e65c1433b /src/map | |
| parent | 3bc690803fb59493ea8180fd630d65b3e26642d0 (diff) | |
| download | graf_karto-9799d3c6a8f0c242668203a1c70d7b6cfed3e855.tar.gz graf_karto-9799d3c6a8f0c242668203a1c70d7b6cfed3e855.zip | |
Refactor to make interaction between tools easier
Diffstat (limited to 'src/map')
| -rw-r--r-- | src/map/data.rs | 64 | ||||
| -rw-r--r-- | src/map/icon.rs | 82 | ||||
| -rw-r--r-- | src/map/icon_renderer.rs | 77 | ||||
| -rw-r--r-- | src/map/mappable.rs | 20 | ||||
| -rw-r--r-- | src/map/mod.rs | 122 | ||||
| -rw-r--r-- | src/map/polygon_room.rs | 68 | ||||
| -rw-r--r-- | src/map/rect_room.rs | 68 | ||||
| -rw-r--r-- | src/map/wall.rs | 98 |
8 files changed, 599 insertions, 0 deletions
diff --git a/src/map/data.rs b/src/map/data.rs new file mode 100644 index 0000000..f978081 --- /dev/null +++ b/src/map/data.rs @@ -0,0 +1,64 @@ +use super::{IconData, PolygonRoomData, RectRoomData, WallData}; +use ron::de::from_reader; +use ron::ser::{to_string_pretty, PrettyConfig}; +use serde::{Deserialize, Serialize}; +use std::fs::File; +use std::io::{self, Write}; +use std::path::Path; + +/// The serialisable and deserialisable parts of the map. This can be created to get a version of the +/// map which is persistifiable or sendable/receivable without data overhead or data that might make +/// it easily corruptable. +#[derive(Serialize, Deserialize)] +pub struct MapData { + pub(super) rect_rooms: Vec<RectRoomData>, + pub(super) polygon_rooms: Vec<PolygonRoomData>, + pub(super) walls: Vec<WallData>, + pub(super) icons: Vec<IconData>, +} + +impl MapData { + pub fn new( + rect_rooms: Vec<RectRoomData>, + polygon_rooms: Vec<PolygonRoomData>, + walls: Vec<WallData>, + icons: Vec<IconData>, + ) -> Self { + Self { + rect_rooms, + polygon_rooms, + walls, + icons, + } + } + + pub fn load_from_file<P: AsRef<Path>>(&mut self, path: P) -> io::Result<Self> { + let file = File::open(&path)?; + let data: Self = match from_reader(file) { + Ok(data) => data, + Err(err) => { + return Err(io::Error::new(io::ErrorKind::InvalidData, err)); + } + }; + + Ok(data) + } + + pub fn write_to_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()> { + let mut file = File::create(&path)?; + + let pretty_conf = PrettyConfig::new() + .with_depth_limit(4) + .with_decimal_floats(true) + .with_separate_tuple_members(true) + .with_indentor("\t".to_owned()); + let string = match to_string_pretty(&self, pretty_conf) { + Ok(string) => string, + Err(err) => { + return Err(io::Error::new(io::ErrorKind::InvalidInput, err)); + } + }; + + file.write_all(&string.as_bytes()) + } +} diff --git a/src/map/icon.rs b/src/map/icon.rs new file mode 100644 index 0000000..50e1906 --- /dev/null +++ b/src/map/icon.rs @@ -0,0 +1,82 @@ +use super::icon_renderer::IconRenderer; +use crate::map::Mappable; +use crate::math::{Vec2, Rect}; +use crate::transform::Transform; +use raylib::core::drawing::{RaylibDraw, RaylibDrawHandle}; +use raylib::ffi::Color; +use serde::{Deserialize, Serialize}; +use std::ops::{Deref, DerefMut}; +use std::rc::Rc; + +#[derive(Clone, Serialize, Deserialize)] +pub struct IconData { + /// The id of the icon is the icons position in the currently loaded icon_data vector. + pub id: usize, + /// The position of the icon on the map, given by the vector in meters. + pub position: Vec2<f64>, + /// Rotation of the icon texture in degrees. + pub rotation: f64, +} + +#[derive(Clone)] +pub struct Icon { + data: IconData, + renderer: Rc<IconRenderer>, +} + +impl Icon { + pub fn new(id: usize, position: Vec2<f64>, rotation: f64, renderer: Rc<IconRenderer>) -> Self { + Self::from_data( + IconData { + id, + position, + rotation, + }, + renderer, + ) + } + + pub fn from_data(data: IconData, renderer: Rc<IconRenderer>) -> Self { + Self { data, renderer } + } +} + +impl Mappable for Icon { + fn draw(&self, rld: &mut RaylibDrawHandle, transform: &Transform) { + let (texture, info) = self.renderer.get(self.id); + // Round the position to whole pixels to fix rotation problems. + let mut position_px = + transform.point_m_to_px(&(self.position - (info.anchor / info.pixels_per_m))); + position_px.x = position_px.x.floor(); + position_px.y = position_px.y.floor(); + rld.draw_texture_ex( + texture, + position_px, + self.rotation as f32, + (transform.pixels_per_m() / info.pixels_per_m) as f32, + Color { + r: 255, + g: 255, + b: 255, + a: 255, + }, + ); + } + + fn bounding_rect(&self) -> Rect<f64> { + Rect::new(self.data.position.x, self.data.position.y, 0., 0.) + } +} + +impl Deref for Icon { + type Target = IconData; + + fn deref(&self) -> &Self::Target { + &self.data + } +} +impl DerefMut for Icon { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.data + } +} diff --git a/src/map/icon_renderer.rs b/src/map/icon_renderer.rs new file mode 100644 index 0000000..fb81e24 --- /dev/null +++ b/src/map/icon_renderer.rs @@ -0,0 +1,77 @@ +use crate::math::Vec2; +use raylib::core::texture::Texture2D; +use raylib::{RaylibHandle, RaylibThread}; +use ron::de::from_reader; +use serde::Deserialize; +use std::fs::{self, File}; + +pub const ICON_DIR: &str = "assets/icons"; + +#[derive(Deserialize)] +pub(super) struct IconFileInfo { + /// The position the icon should be anchored in pixels. This is the Vector it will be moved by + /// relative to the mouse pointer (to the left and up). + pub anchor: Vec2<f64>, + /// The scale of the icon as expressed in image pixels per real meter. + pub pixels_per_m: f64, +} + +pub struct IconRenderer { + texture_data: Vec<(Texture2D, IconFileInfo)>, +} + +impl IconRenderer { + pub fn new(rl: &mut RaylibHandle, rlt: &RaylibThread) -> Self { + /* Read all available icons from the icon directory. SVGs do not need any special scale + * file, but pixel-based file formats require a RON-file declaring what the scale of the + * picture is right beside them. + */ + let mut image_files = Vec::new(); + for entry in fs::read_dir(ICON_DIR).expect("Could not open icon directory") { + let entry = entry.expect("Failed to read file from icon directory"); + + // Ignore the RON-files for now and put the image files into the vec + if entry + .path() + .extension() + .expect("Entry does not have a file extension") + != "ron" + { + image_files.push(entry); + } + } + + // Read the RON-files where it is necessary. + let mut texture_data = Vec::with_capacity(image_files.len()); + for file in image_files { + // TODO: Handle svg + + let texture = rl + .load_texture( + rlt, + file.path() + .to_str() + .expect("Unable to convert path to string."), + ) + .expect("Could not read image file"); + + let mut file = file.path(); + file.set_extension("ron"); + let ron = File::open(file).expect("Could not read ron file for icon information."); + let icon_info: IconFileInfo = + from_reader(ron).expect("Could not parse icon info from reader."); + + texture_data.push((texture, icon_info)); + } + + Self { texture_data } + } + + pub(super) fn get(&self, icon_id: usize) -> &(Texture2D, IconFileInfo) { + &self.texture_data[icon_id] + } + + pub fn num_icons(&self) -> usize { + self.texture_data.len() + } +} diff --git a/src/map/mappable.rs b/src/map/mappable.rs new file mode 100644 index 0000000..f75c990 --- /dev/null +++ b/src/map/mappable.rs @@ -0,0 +1,20 @@ +//! Something that's mappable can be placed on the map and drawn at a specific position. It has a +//! dimension on the map and may be scaleable + +use crate::math::Rect; +use crate::scaleable::Scaleable; +use crate::transform::Transform; +use raylib::drawing::RaylibDrawHandle; + +pub trait Mappable { + /// Draw this to `rld` with the transform. An item that cannot be drawn is not mappable, so + /// this must always be implemented. + fn draw(&self, rld: &mut RaylibDrawHandle, transform: &Transform); + + /// Get the rectangle that contains the mappable object in its entirety without excess. + fn bounding_rect(&self) -> Rect<f64>; + + fn as_scaleable(&self) -> Option<&dyn Scaleable> { + None + } +} diff --git a/src/map/mod.rs b/src/map/mod.rs new file mode 100644 index 0000000..93f51a2 --- /dev/null +++ b/src/map/mod.rs @@ -0,0 +1,122 @@ +pub mod data; +pub mod icon; +pub mod icon_renderer; +pub mod mappable; +pub mod polygon_room; +pub mod rect_room; +pub mod wall; + +pub use data::MapData; +pub use icon::*; +pub use mappable::Mappable; +pub use polygon_room::*; +pub use rect_room::*; +pub use wall::*; + +use crate::transform::Transform; +use icon_renderer::IconRenderer; +use raylib::drawing::RaylibDrawHandle; +use raylib::{RaylibHandle, RaylibThread}; +use std::rc::Rc; + +pub struct Map { + rect_rooms: Vec<RectRoom>, + polygon_rooms: Vec<PolygonRoom>, + walls: Vec<Wall>, + icons: Vec<Icon>, + icon_renderer: Rc<IconRenderer>, +} + +impl Map { + pub fn new(rl: &mut RaylibHandle, rlt: &RaylibThread) -> Self { + Self { + rect_rooms: Vec::new(), + polygon_rooms: Vec::new(), + walls: Vec::new(), + icons: Vec::new(), + icon_renderer: Rc::new(IconRenderer::new(rl, rlt)), + } + } + + pub fn push_rect_room(&mut self, room_data: RectRoomData) { + self.rect_rooms.push(RectRoom::from_data(room_data)); + } + + pub fn push_polygon_room(&mut self, room_data: PolygonRoomData) { + self.polygon_rooms.push(PolygonRoom::from_data(room_data)); + } + + pub fn push_wall(&mut self, wall_data: WallData) { + /* Check for intersections with any wall that was arleady created so the wall ends can be + * rendered properly. + */ + let mut start_intersects = false; + let mut end_intersects = false; + for wall in &self.walls { + if wall.data().contains_collinear(wall_data.start) { + start_intersects = true; + } + if wall.data().contains_collinear(wall_data.end) { + end_intersects = true; + } + + // Currently, additional intersections can be ignored, since it is just a yes-no-question + if start_intersects && end_intersects { + break; + } + } + + self.walls + .push(Wall::from_data(wall_data, start_intersects, end_intersects)); + } + + pub fn push_icon(&mut self, icon: Icon) { + self.icons.push(icon); + } + + pub fn draw(&self, rld: &mut RaylibDrawHandle, transform: &Transform) { + for element in self.iter() { + element.draw(rld, transform); + } + } + + pub fn icon_renderer(&self) -> Rc<IconRenderer> { + self.icon_renderer.clone() + } + + /// Retain all map elements that fulfill the given predicate. + pub fn retain<F>(&mut self, mut f: F) + where + F: FnMut(&dyn Mappable) -> bool, + { + // Call retain on all vectors containing the maps actual types. + self.rect_rooms.retain(|r| f(r as &dyn Mappable)); + self.polygon_rooms.retain(|p| f(p as &dyn Mappable)); + self.walls.retain(|w| f(w as &dyn Mappable)); + self.icons.retain(|i| f(i as &dyn Mappable)); + } + + /// Iterator over all elements as objects when an operation needs to go over all elements of the + /// map. + pub fn iter(&self) -> impl Iterator<Item = &dyn Mappable> { + self.rect_rooms + .iter() + .map(|r| r as &dyn Mappable) + .chain(self.polygon_rooms.iter().map(|p| p as &dyn Mappable)) + .chain(self.walls.iter().map(|w| w as &dyn Mappable)) + .chain(self.icons.iter().map(|i| i as &dyn Mappable)) + } + + pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut dyn Mappable> { + self.rect_rooms + .iter_mut() + .map(|r| r as &mut dyn Mappable) + .chain( + self.polygon_rooms + .iter_mut() + .map(|p| p as &mut dyn Mappable), + ) + .chain(self.walls.iter_mut().map(|w| w as &mut dyn Mappable)) + .chain(self.icons.iter_mut().map(|i| i as &mut dyn Mappable)) + } +} diff --git a/src/map/polygon_room.rs b/src/map/polygon_room.rs new file mode 100644 index 0000000..12c480b --- /dev/null +++ b/src/map/polygon_room.rs @@ -0,0 +1,68 @@ +use super::Mappable; +use crate::math::{self, Rect, Polygon, Triangle, Vec2}; +use crate::scaleable::Scaleable; +use crate::transform::Transform; +use raylib::drawing::{RaylibDraw, RaylibDrawHandle}; +use raylib::ffi::Color; + +pub type PolygonRoomData = Polygon<f64>; + +pub struct PolygonRoom { + data: PolygonRoomData, + // The polygon shape, but in triangles, so the polygon does not have to be triangulated every frame. + triangulated: Vec<Triangle<f64>>, +} + +impl PolygonRoom { + pub fn from_data(data: PolygonRoomData) -> Self { + Self { + data: data.clone(), + triangulated: math::triangulate(data) + } + } + + fn retriangulate(&mut self) { + self.triangulated = math::triangulate(self.data.clone()); + } +} + +impl Mappable for PolygonRoom { + fn draw(&self, rld: &mut RaylibDrawHandle, transform: &Transform) { + for triangle in &self.triangulated { + rld.draw_triangle( + transform.point_m_to_px(&triangle.corners()[0]), + transform.point_m_to_px(&triangle.corners()[1]), + transform.point_m_to_px(&triangle.corners()[2]), + Color { + r: 180, + g: 180, + b: 180, + a: 255, + }, + ) + } + } + + fn bounding_rect(&self) -> Rect<f64> { + Rect::bounding_rect_n(&self.data.corners()) + } + + fn as_scaleable(&self) -> Option<&dyn Scaleable> { + Some(self as &dyn Scaleable) + } +} + +impl Scaleable for PolygonRoom { + fn scale(&mut self, by: &Vec2<f64>) { + if by.x < 0. || by.y < 0. { + panic!("Cannot set dimensions with negative size"); + } + + for corner in self.data.corners_mut() { + corner.x *= by.x; + corner.y *= by.y; + } + + self.retriangulate(); + } +} diff --git a/src/map/rect_room.rs b/src/map/rect_room.rs new file mode 100644 index 0000000..07f201c --- /dev/null +++ b/src/map/rect_room.rs @@ -0,0 +1,68 @@ +use crate::map::Mappable; +use crate::math::{Rect, Vec2}; +use crate::scaleable::Scaleable; +use crate::transform::Transform; +use raylib::drawing::{RaylibDraw, RaylibDrawHandle}; +use raylib::ffi::Color; +use serde::Serialize; +use std::ops::{Deref, DerefMut}; + +pub type RectRoomData = Rect<f64>; + +#[derive(Serialize)] +pub struct RectRoom { + data: RectRoomData, +} + +impl RectRoom { + pub fn from_data(data: RectRoomData) -> Self { + RectRoom { data } + } +} + +impl Mappable for RectRoom { + fn draw(&self, rld: &mut RaylibDrawHandle, transform: &Transform) { + rld.draw_rectangle_rec( + transform.rect_m_to_px(&self.data), + Color { + r: 180, + g: 180, + b: 180, + a: 255, + }, + ); + } + + fn bounding_rect(&self) -> Rect<f64> { + self.data.clone() + } + + fn as_scaleable(&self) -> Option<&dyn Scaleable> { + Some(self as &dyn Scaleable) + } +} + +impl Scaleable for RectRoom { + fn scale(&mut self, by: &Vec2<f64>) { + if by.x < 0. || by.y < 0. { + panic!("Cannot set dimensions with negative size"); + } + + self.data.x *= by.x; + self.data.y *= by.y; + } +} + +impl Deref for RectRoom { + type Target = RectRoomData; + + fn deref(&self) -> &Self::Target { + &self.data + } +} + +impl DerefMut for RectRoom { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.data + } +} diff --git a/src/map/wall.rs b/src/map/wall.rs new file mode 100644 index 0000000..6c90fda --- /dev/null +++ b/src/map/wall.rs @@ -0,0 +1,98 @@ +use super::Mappable; +use crate::math::{LineSegment, Vec2, Rect}; +use crate::scaleable::Scaleable; +use crate::transform::Transform; +use raylib::drawing::{RaylibDraw, RaylibDrawHandle}; +use raylib::ffi::Color; +use std::ops::{Deref, DerefMut}; + +pub type WallData = LineSegment<f64>; + +pub struct Wall { + data: WallData, + round_start: bool, + round_end: bool, +} + +impl Wall { + pub fn from_data(data: WallData, round_start: bool, round_end: bool) -> Self { + Self { + data, + round_start, + round_end, + } + } + + pub fn data(&self) -> &WallData { + &self.data + } +} + +fn draw_round_corner(rld: &mut RaylibDrawHandle, pos_px: Vec2<f64>, transform: &Transform) { + rld.draw_circle_v( + pos_px, + transform.length_m_to_px(0.05) as f32, + Color { + r: 200, + g: 120, + b: 120, + a: 255, + }, + ); +} + +impl Mappable for Wall { + fn draw(&self, rld: &mut RaylibDrawHandle, transform: &Transform) { + let start_px = transform.point_m_to_px(&self.data.start); + let end_px = transform.point_m_to_px(&self.data.end); + rld.draw_line_ex( + start_px, + end_px, + transform.length_m_to_px(0.1) as f32, + Color { + r: 200, + g: 120, + b: 120, + a: 255, + }, + ); + + if self.round_start { + draw_round_corner(rld, start_px, transform); + } + if self.round_end { + draw_round_corner(rld, end_px, transform); + } + } + + fn bounding_rect(&self) -> Rect<f64> { + Rect::bounding_rect(self.data.start, self.data.end) + } +} + +impl Scaleable for Wall { + fn scale(&mut self, by: &Vec2<f64>) { + if by.x <= 0. || by.y <= 0. { + panic!("Cannot set dimensions with negative size"); + } + + self.data.start.x *= by.x; + self.data.start.y *= by.y; + self.data.end.x *= by.x; + self.data.end.y *= by.y; + } +} + +impl Deref for Wall { + type Target = WallData; + + fn deref(&self) -> &Self::Target { + &self.data + } +} + +impl DerefMut for Wall { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.data + } +} |
