aboutsummaryrefslogtreecommitdiff
path: root/src/client/map
diff options
context:
space:
mode:
authorArne Dußin2021-01-27 14:01:50 +0100
committerArne Dußin2021-02-02 22:16:15 +0100
commitf92e9f6f07b1e3834c2ca58ce3510734819d08e4 (patch)
tree20e3d3afce342a56ae98f6c20491482ccd2b5c6b /src/client/map
parentc60a6d07efb120724b308e29e8e70f27c87c952d (diff)
downloadgraf_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.rs95
-rw-r--r--src/client/map/icon_texture_manager.rs91
-rw-r--r--src/client/map/mappable.rs23
-rw-r--r--src/client/map/mod.rs206
-rw-r--r--src/client/map/room_mark.rs75
-rw-r--r--src/client/map/wall_mark.rs106
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
+ }
+}