diff options
| author | Arne Dußin | 2021-01-27 14:01:50 +0100 |
|---|---|---|
| committer | Arne Dußin | 2021-02-02 22:16:15 +0100 |
| commit | f92e9f6f07b1e3834c2ca58ce3510734819d08e4 (patch) | |
| tree | 20e3d3afce342a56ae98f6c20491482ccd2b5c6b /src/client/map | |
| parent | c60a6d07efb120724b308e29e8e70f27c87c952d (diff) | |
| download | graf_karto-f92e9f6f07b1e3834c2ca58ce3510734819d08e4.tar.gz graf_karto-f92e9f6f07b1e3834c2ca58ce3510734819d08e4.zip | |
Rework graf karto to fit the client/server structure
Diffstat (limited to 'src/client/map')
| -rw-r--r-- | src/client/map/icon_mark.rs | 95 | ||||
| -rw-r--r-- | src/client/map/icon_texture_manager.rs | 91 | ||||
| -rw-r--r-- | src/client/map/mappable.rs | 23 | ||||
| -rw-r--r-- | src/client/map/mod.rs | 206 | ||||
| -rw-r--r-- | src/client/map/room_mark.rs | 75 | ||||
| -rw-r--r-- | src/client/map/wall_mark.rs | 106 |
6 files changed, 596 insertions, 0 deletions
diff --git a/src/client/map/icon_mark.rs b/src/client/map/icon_mark.rs new file mode 100644 index 0000000..39fd554 --- /dev/null +++ b/src/client/map/icon_mark.rs @@ -0,0 +1,95 @@ +//! Icon marker on the map. For information about icons see [Icon](crate::) + +use super::icon_texture_manager::IconTextureManager; +use crate::client::colours::DEFAULT_COLOURS; +use crate::client::map::Mappable; +use crate::client::transform::Transform; +use crate::math::Vec2; +use crate::world::{Component, Icon}; +use raylib::core::drawing::{RaylibDraw, RaylibDrawHandle}; +use std::ops::{Deref, DerefMut}; +use std::rc::Rc; + +/// Describes an icon in the world and can be drawn. +#[derive(Clone)] +pub struct IconMark { + icon: Icon, + selected: bool, + textures: Rc<IconTextureManager>, +} + +impl IconMark { + /// Create a new icon marker. Requires the icon textures that are used to render this icon, as well as all + /// the information necessary to describe the icon itself. + pub fn new( + id: usize, + position: Vec2<f64>, + rotation: f64, + renderer: Rc<IconTextureManager>, + ) -> Self { + Self::from_icon( + Icon { + id, + position, + rotation, + }, + renderer, + ) + } + + /// Like `new()`, but with the icon data bundled into the icon type. + pub fn from_icon(icon: Icon, textures: Rc<IconTextureManager>) -> Self { + Self { + icon, + selected: false, + textures, + } + } +} + +impl Mappable for IconMark { + fn draw(&self, rld: &mut RaylibDrawHandle, transform: &Transform) { + let (texture, info) = self.textures.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, + if self.selected() { + DEFAULT_COLOURS.icon_selected + } else { + DEFAULT_COLOURS.icon_normal + }, + ); + } + + fn set_selected(&mut self, selected: bool) { + self.selected = selected; + } + + fn selected(&self) -> bool { + self.selected + } + + fn as_component(&self) -> &dyn Component { + self.deref() + } +} + +impl Deref for IconMark { + type Target = Icon; + + fn deref(&self) -> &Self::Target { + &self.icon + } +} +impl DerefMut for IconMark { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.icon + } +} diff --git a/src/client/map/icon_texture_manager.rs b/src/client/map/icon_texture_manager.rs new file mode 100644 index 0000000..c6b7fea --- /dev/null +++ b/src/client/map/icon_texture_manager.rs @@ -0,0 +1,91 @@ +//! Since the same icon may be used very often on a map, it becomes impracticalto let every icon have +//! it's own texture data saved, which is why a texture manager type of struct is used for it instead. + +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}; + +/// The directory containing all files related to icons. +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, +} + +/// Manager for all icon texture or rendering data. +pub struct IconTextureManager { + texture_data: Vec<(Texture2D, IconFileInfo)>, +} + +impl IconTextureManager { + /// Create a new icon manager. This loads all textures and information for icons that is needed + /// to draw them to the screen. Usually, there should be only one IconTextureManager, or at least one + /// manager per directory and program instance. + 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 } + } + + /// Get the textures needed to render an icon of type `icon_id`. + /// + /// # Panics + /// If the `icon_id` does not describe a valid icon (is out of bounds), there is no data to be + /// accessed and the function panics. + pub(super) fn get(&self, icon_id: usize) -> &(Texture2D, IconFileInfo) { + &self.texture_data[icon_id] + } + + /// The number of icons registered in this texture manager. + pub fn num_icons(&self) -> usize { + self.texture_data.len() + } +} diff --git a/src/client/map/mappable.rs b/src/client/map/mappable.rs new file mode 100644 index 0000000..39e774b --- /dev/null +++ b/src/client/map/mappable.rs @@ -0,0 +1,23 @@ +//! 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 transformable in various ways. + +use crate::client::transform::Transform; +use crate::world::Component; +use raylib::drawing::RaylibDrawHandle; + +/// Anything that can be added to the map or world must implement this trait. +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); + + /// Set the selection status of this item. If it is selected, actions that concern all selected + /// items will be applied to this item as well. + fn set_selected(&mut self, selected: bool); + + /// Get if this item is currently selected. + fn selected(&self) -> bool; + + /// Get this mappable as a world component. + fn as_component(&self) -> &dyn Component; +} diff --git a/src/client/map/mod.rs b/src/client/map/mod.rs new file mode 100644 index 0000000..eaab72f --- /dev/null +++ b/src/client/map/mod.rs @@ -0,0 +1,206 @@ +//! The map is a visual interpretation of all the items that make up the world. +//! +//! It's the main structure that the client uses to interact with the world, since +//! the world contains all the + +pub mod icon_mark; +pub mod icon_texture_manager; +pub mod mappable; +pub mod room_mark; +pub mod wall_mark; + +pub use icon_mark::*; +pub use mappable::Mappable; +pub use room_mark::*; +pub use wall_mark::*; + +use crate::client::Transform; +use crate::stable_vec::StableVec; +use crate::world::{Room, Wall, World}; +use icon_texture_manager::IconTextureManager; +use raylib::drawing::RaylibDrawHandle; +use raylib::{RaylibHandle, RaylibThread}; +use std::rc::Rc; + +/// The map containing all map elements that are seen on the screen. +pub struct Map { + rooms: StableVec<RoomMark>, + walls: StableVec<WallMark>, + icons: StableVec<IconMark>, + used_ids: StableVec<()>, + icon_renderer: Rc<IconTextureManager>, +} + +impl Map { + /// Create a new, empty map/world. + pub fn new(rl: &mut RaylibHandle, rlt: &RaylibThread) -> Self { + Self { + rooms: StableVec::new(), + walls: StableVec::new(), + icons: StableVec::new(), + used_ids: StableVec::new(), + icon_renderer: Rc::new(IconTextureManager::new(rl, rlt)), + } + } + + pub fn add_room(&mut self, id: usize, room: Room) -> bool { + if self.used_ids.try_insert(id, ()).is_ok() { + self.rooms + .try_insert(id, RoomMark::from_room(room)) + .unwrap(); + true + } else { + error!("Unable to add room. Id already in use."); + false + } + } + + /// Add a wall with a specific id. May fail if there already is an entity with that id. + pub fn add_wall(&mut self, id: usize, wall: Wall) -> bool { + if self.used_ids.try_insert(id, ()).is_ok() { + /* 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.iter() { + if wall.shape().contains_collinear(wall.shape().start) { + start_intersects = true; + } + if wall.shape().contains_collinear(wall.shape().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 + .try_insert( + id, + WallMark::from_wall(wall, start_intersects, end_intersects), + ) + .unwrap(); + true + } else { + error!("Unable to add wall. Id already in use."); + false + } + } + + /// Add an icon with a specific id. May fail if there already is an entity with that id. + pub fn add_icon(&mut self, id: usize, icon: IconMark) -> bool { + if self.used_ids.try_insert(id, ()).is_ok() { + self.icons.try_insert(id, icon).unwrap(); + true + } else { + error!("Unable to add icon. Id already in use."); + false + } + } + + /// Draw all elements of the map to the screen. This should be called after the background of the + /// map has already been drawn. + pub fn draw(&self, rld: &mut RaylibDrawHandle, transform: &Transform) { + for (_, element) in self.elements() { + element.draw(rld, transform); + } + } + + /// Get the icon-renderer that is currently used to render the icons. + pub fn icon_renderer(&self) -> Rc<IconTextureManager> { + self.icon_renderer.clone() + } + + /// Remove item with the id, if it exists. Returns `true` if an item existed, `false` otherwise. + pub fn remove(&mut self, id: usize) -> bool { + if self.used_ids.remove(id).is_none() { + return false; + } + + if self.rooms.remove(id).is_some() + || self.walls.remove(id).is_some() + || self.icons.remove(id).is_some() + { + true + } else { + panic!( + "Id {} was still registered, eventhough there was no such entity.", + id + ); + } + } + + /// Iterator over all elements as objects when an operation needs to go over all elements of the + /// map. + pub fn elements(&self) -> impl Iterator<Item = (usize, &dyn Mappable)> { + self.rooms + .iter() + .map(|(id, p)| (*id, p as &dyn Mappable)) + .chain(self.walls.iter().map(|(id, w)| (*id, w as &dyn Mappable))) + .chain(self.icons.iter().map(|(id, i)| (*id, i as &dyn Mappable))) + } + + /// Iterator over all elements, but the individual elements can be mutated. It is however + /// impossible to add or remove elements in this way. For that, use the dedicated functions. + pub fn elements_mut(&mut self) -> impl Iterator<Item = (usize, &mut dyn Mappable)> { + self.rooms + .id_iter_mut() + .map(|(id, p)| (id, p as &mut dyn Mappable)) + .chain( + self.walls + .id_iter_mut() + .map(|(id, w)| (id, w as &mut dyn Mappable)), + ) + .chain( + self.icons + .id_iter_mut() + .map(|(id, i)| (id, i as &mut dyn Mappable)), + ) + } + + /// Get the rooms of this map. + pub fn rooms(&self) -> &StableVec<RoomMark> { + &self.rooms + } + + /// Get the walls of this map. + pub fn walls(&self) -> &StableVec<WallMark> { + &self.walls + } + + /// Get the icons of this map. + pub fn icons(&self) -> &StableVec<IconMark> { + &self.icons + } + + /// Replace the internal map data with the data of the world provided. + /// (Load and replace) + pub fn set_data(&mut self, world: World) { + // Remove all data. + self.icons.clear(); + self.rooms.clear(); + self.walls.clear(); + + // Add all data from the map data. + self.add_data(world); + } + + /// Add the data provided to the current data on the map. All elements will + /// remain, with the additional elements being pushed also. This must be + /// used with caution, since the ids of the items will remain unchanged, and + /// items with the same id will therefore be ignored and not added. + pub fn add_data(&mut self, world: World) { + for (id, i) in world.icons().iter() { + self.add_icon(*id, IconMark::from_icon(*i, self.icon_renderer.clone())); + } + for (id, r) in world.rooms().iter() { + self.add_room(*id, r.clone()); + } + for (id, w) in world.walls().iter() { + self.add_wall(*id, w.clone()); + } + } +} diff --git a/src/client/map/room_mark.rs b/src/client/map/room_mark.rs new file mode 100644 index 0000000..a9777fb --- /dev/null +++ b/src/client/map/room_mark.rs @@ -0,0 +1,75 @@ +//! Polygon rooms are the standard rooms in graf karto. They can take the form of anything that a +//! [Polygon](crate::math::Polygon) can have. + +use super::Mappable; +use crate::client::colours::DEFAULT_COLOURS; +use crate::client::transform::Transform; +use crate::client::FLOAT_MARGIN; +use crate::math::{self, Triangle}; +use crate::world::{Component, Room}; +use raylib::drawing::{RaylibDraw, RaylibDrawHandle}; +use std::ops::Deref; + +/// A polygon room, which can be placed and modified in the world. +pub struct RoomMark { + room: Room, + // The polygon shape, but in triangles, so the polygon does not have to be triangulated every frame. + triangulated: Vec<Triangle<f64>>, + selected: bool, +} + +impl RoomMark { + /// Create a room from the given polygon data. + pub fn from_room(room: Room) -> Self { + let shape = room.shape().clone(); + Self { + room, + triangulated: math::triangulate(shape, FLOAT_MARGIN), + selected: false, + } + } + + /* 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.room.shape().clone(), FLOAT_MARGIN); + } +} + +impl Mappable for RoomMark { + 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]), + if self.selected() { + DEFAULT_COLOURS.room_selected + } else { + DEFAULT_COLOURS.room_normal + }, + ) + } + } + + fn set_selected(&mut self, selected: bool) { + self.selected = selected; + } + + fn selected(&self) -> bool { + self.selected + } + + fn as_component(&self) -> &dyn Component { + self.deref() + } +} + +impl Deref for RoomMark { + type Target = Room; + + fn deref(&self) -> &Self::Target { + &self.room + } +} diff --git a/src/client/map/wall_mark.rs b/src/client/map/wall_mark.rs new file mode 100644 index 0000000..c51af9d --- /dev/null +++ b/src/client/map/wall_mark.rs @@ -0,0 +1,106 @@ +//! Walls, solid barriers that are generally unclimbable. +//! +//! This interpretation is generally up to the GM to decide, but generally speaking, a wall cannot be +//! crossed by a player. If special conditions apply (for instance, when the player wants to scale the +//! wall), a check is necessary. If a check is not necessary, then maybe you were not thinking about +//! a wall. + +use super::Mappable; +use crate::client::colours::DEFAULT_COLOURS; +use crate::client::transform::Transform; +use crate::math::Vec2; +use crate::world::{Component, Wall}; +use raylib::drawing::{RaylibDraw, RaylibDrawHandle}; +use std::ops::{Deref, DerefMut}; + +/// A solid wall a player cannot go through, except if special conditions apply. +pub struct WallMark { + wall: Wall, + selected: bool, + round_start: bool, + round_end: bool, +} + +impl WallMark { + /// Create a new wall from the deserialised data and information known from internal sources. + pub fn from_wall(wall: Wall, round_start: bool, round_end: bool) -> Self { + Self { + wall, + selected: false, + round_start, + round_end, + } + } + + /// Get the internal data for serialisation + pub fn data(&self) -> &Wall { + &self.wall + } +} + +fn draw_round_corner( + rld: &mut RaylibDrawHandle, + pos_px: Vec2<f64>, + transform: &Transform, + selected: bool, +) { + rld.draw_circle_v( + pos_px, + transform.length_m_to_px(0.05) as f32, + if selected { + DEFAULT_COLOURS.wall_selected + } else { + DEFAULT_COLOURS.wall_normal + }, + ); +} + +impl Mappable for WallMark { + fn draw(&self, rld: &mut RaylibDrawHandle, transform: &Transform) { + let start_px = transform.point_m_to_px(&self.wall.shape().start); + let end_px = transform.point_m_to_px(&self.wall.shape().end); + rld.draw_line_ex( + start_px, + end_px, + transform.length_m_to_px(0.1) as f32, + if self.selected() { + DEFAULT_COLOURS.wall_selected + } else { + DEFAULT_COLOURS.wall_normal + }, + ); + + if self.round_start { + draw_round_corner(rld, start_px, transform, self.selected()); + } + if self.round_end { + draw_round_corner(rld, end_px, transform, self.selected()); + } + } + + fn set_selected(&mut self, selected: bool) { + self.selected = selected; + } + + fn selected(&self) -> bool { + self.selected + } + + fn as_component(&self) -> &dyn Component { + self.deref() + } +} + +impl Deref for WallMark { + type Target = Wall; + + fn deref(&self) -> &Self::Target { + &self.wall + } +} + +impl DerefMut for WallMark { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.wall + } +} |
