use sfml::system::Vector2; use sfml::graphics::{ IntRect, FloatRect }; use engine::texture::Texture; use engine::renderer::Drawable; use engine::math::Matrix; /// This enum represents if the collision happens on the X axis or the Y axis. pub enum CollisionAxis { /// The X axis. X, /// The Y axis. Y, } /// This struct represents the different sides from which a collision can occur. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct CollisionTile { /// If the character comes from the top, it will collide if this bool is true. pub from_top: bool, /// If the character comes from the left, it will collide if this bool is true. pub from_left: bool, /// If the character comes from the right, it will collide if this bool is true. pub from_right: bool, /// If the character comes from the bottom, it will collide if this bool is true. pub from_bottom: bool, } impl CollisionTile { /// Creates a collision tile that does not collide. pub fn empty() -> CollisionTile { CollisionTile { from_top: false, from_left: false, from_right: false, from_bottom: false, } } /// Creates a collision tile that collides from every side. pub fn full() -> CollisionTile { CollisionTile { from_top: true, from_left: true, from_right: true, from_bottom: true, } } } /// This struct represents a renderable tile linking to its part in the tileset texture. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum GraphicTile { /// There is nothing to draw. Hidden, /// Top left corner of a solid tile. TopLeft, /// Top of a solid tile. Top, /// Top right corner of a solid tile. TopRight, /// Left of a solid tile. Left, /// Center of a solid tile. Center, /// Right of a solid tile. Right, /// Bottom left corner of a solid tile. BottomLeft, /// Bottom of a solid tile. Bottom, /// Bottom right corner of a solid tile. BottomRight, } impl GraphicTile { /// Checks if a graphic tile has a top border. pub fn is_top(&self) -> bool { match *self { GraphicTile::TopLeft | GraphicTile::Top | GraphicTile::TopRight => true, _ => false, } } /// Checks if a graphic tile has a left border. pub fn is_left(&self) -> bool { match *self { GraphicTile::TopLeft | GraphicTile::Left | GraphicTile::BottomLeft => true, _ => false, } } /// Checks if a graphic tile has a right border. pub fn is_right(&self) -> bool { match *self { GraphicTile::TopRight | GraphicTile::Right | GraphicTile::BottomRight => true, _ => false, } } /// Checks if a graphic tile has a bottom border. pub fn is_bottom(&self) -> bool { match *self { GraphicTile::BottomLeft | GraphicTile::Bottom | GraphicTile::BottomRight => true, _ => false, } } /// Creates a vec containing all the non hidden graphic tiles. pub fn all() -> Vec { vec![ GraphicTile::TopLeft, GraphicTile::Top, GraphicTile::TopRight, GraphicTile::Left, GraphicTile::Center, GraphicTile::Right, GraphicTile::BottomLeft, GraphicTile::Bottom, GraphicTile::BottomRight, ] } /// Creates the correct graphic tile depending on the neighbours. /// /// A none will be considered solid. pub fn from_neighbour_options( top: Option, left: Option, right: Option, bottom: Option) -> GraphicTile { GraphicTile::from_neighbours( top.unwrap_or(CollisionTile::full()), left.unwrap_or(CollisionTile::full()), right.unwrap_or(CollisionTile::full()), bottom.unwrap_or(CollisionTile::full()), ) } /// Creates the correct graphic tile depending on the neighbours. pub fn from_neighbours( top: CollisionTile, left: CollisionTile, right: CollisionTile, bottom: CollisionTile) -> GraphicTile { let mut all = GraphicTile::all() .into_iter() .map(|x| (x, true)) .collect::>(); for (ref mut tile, ref mut possible) in &mut all { if tile.is_top() == (top == CollisionTile::full()) { *possible = false; } if tile.is_left() == (left == CollisionTile::full()) { *possible = false; } if tile.is_right() == (right == CollisionTile::full()) { *possible = false; } if tile.is_bottom() == (bottom == CollisionTile::full()) { *possible = false; } } for (tile, possible) in all { if possible { return tile; } } panic!("Did not find a tile, implementation error"); } /// Returns the offset to the corresponding graphic tile in the texture. pub fn offset(&self) -> (i32, i32) { let vertical = match *self { GraphicTile::TopLeft | GraphicTile::Top | GraphicTile::TopRight => 0, GraphicTile::Left | GraphicTile::Center | GraphicTile::Right => 16, GraphicTile::BottomLeft | GraphicTile::Bottom | GraphicTile::BottomRight => 32, _ => 0, }; let horizontal = match *self { GraphicTile::TopLeft | GraphicTile::Left | GraphicTile::BottomLeft => 0, GraphicTile::Top | GraphicTile::Center | GraphicTile::Bottom => 16, GraphicTile::TopRight | GraphicTile::Right | GraphicTile::BottomRight => 32, _ => 0, }; // (horizontal, vertical) (vertical, horizontal) } } /// A tile and its position. pub struct PositionedTile { /// The graphic representation of the positioned tile. pub graphic: GraphicTile, /// The collision representation of the positioned tile. pub collision: CollisionTile, /// The position of the positioned tile. pub position: (f32, f32), } impl Drawable for PositionedTile { fn texture(&self) -> Texture { Texture::Overworld } fn texture_rect(&self) -> IntRect { let offset = self.graphic.offset(); IntRect::new(offset.0, offset.1, 16, 16) } fn position(&self) -> Vector2 { self.position.into() } } /// The map represents the tiles contained in a level. pub struct Map { /// The tiles contained in the level. tiles: Matrix<(CollisionTile, GraphicTile)>, } impl Map { /// Creates a map full of nothing, with a ground at the bottom. pub fn new(rows: usize, cols: usize) -> Map { let mut tiles = Matrix::from_size(rows, cols, CollisionTile::empty()); let rows = tiles.rows(); for i in 0 .. tiles.cols() { tiles[(rows - 1, i)] = CollisionTile::full(); } tiles[(25, 12)] = CollisionTile::full(); tiles[(25, 13)] = CollisionTile::full(); tiles[(25, 14)] = CollisionTile::full(); tiles[(25, 15)] = CollisionTile::full(); Map::from_collision_tiles(tiles) } /// Creates a map from a txt file. pub fn from_str(text: &str) -> Result { let split = text.split('\n').collect::>(); // First two usize are the size of the map let size = split[0] .split_whitespace() .map(|x| x.parse::().unwrap()) .collect::>(); let mut tiles = Matrix::from_size(size[0], size[1], CollisionTile::empty()); for (row, line) in split.iter().skip(1).enumerate() { for (col, tile) in line.split_whitespace().enumerate() { let num = tile.parse::().unwrap(); match num { 0 => (), 1 => tiles[(row, col)] = CollisionTile::full(), _ => panic!("Expecting 0 or 1 in level files"), } } } Ok(Map::from_collision_tiles(tiles)) } /// Creates a map from its tiles. pub fn from_collision_tiles(tiles: Matrix) -> Map { let rows = tiles.rows(); let cols = tiles.cols(); let mut matrix = Matrix::from_size(rows, cols, (CollisionTile::empty(), GraphicTile::Hidden) ); for i in 0 .. rows { for j in 0 .. cols { let graphic = if tiles[(i, j)] == CollisionTile::full() { // TODO This is uggly // If there is an overflow, we should give None instead let (i, j) = (i as isize, j as isize); GraphicTile::from_neighbour_options( tiles.get(((i ) as usize, (j-1) as usize)).map(|x| *x), tiles.get(((i-1) as usize, (j ) as usize)).map(|x| *x), tiles.get(((i+1) as usize, (j ) as usize)).map(|x| *x), tiles.get(((i ) as usize, (j+1) as usize)).map(|x| *x), ) } else { GraphicTile::Hidden }; matrix[(i, j)] = (tiles[(i, j)], graphic); } } Map { tiles: matrix, } } /// Returns an iterator to the positioned tiles. pub fn at(&self, row: usize, col: usize) -> PositionedTile { PositionedTile { collision: self.tiles[(row, col)].0, graphic: self.tiles[(row, col)].1, position: (col as f32 * 16.0, row as f32 * 16.0), } } /// Returns the number of rows of the map. pub fn rows(&self) -> usize { self.tiles.rows() } /// Returns the number of columns of the map. pub fn cols(&self) -> usize { self.tiles.cols() } /// Checks whether the bounding box collides with an element of the map pub fn collides_bbox(&self, old: FloatRect, new: FloatRect) -> Option<(CollisionAxis, Vector2)> { // Top left corner if let Some((axis, collision)) = self.collides_point( Vector2::new(old.left, old.top), Vector2::new(new.left, new.top), ) { return Some((axis, collision)); } // Top right corner if let Some((axis, collision)) = self.collides_point( Vector2::new(old.left + old.width, old.top), Vector2::new(new.left + new.width, new.top), ) { return Some((axis, collision - Vector2::new(new.width, 0.0))); } // Bottom left corner if let Some((axis, collision)) = self.collides_point( Vector2::new(old.left, old.top + old.height), Vector2::new(new.left, new.top + new.height), ) { return Some((axis, collision - Vector2::new(0.0, new.height))); } // Bottom right corner if let Some((axis, collision)) = self.collides_point( Vector2::new(old.left + old.width, old.top + old.height), Vector2::new(new.left + new.width, new.top + new.height), ) { return Some((axis, collision - Vector2::new(new.width, new.height))); } None } /// Checks whether the vector (old, new) collides with an element of the map. /// /// Returns the height of the collision if any. pub fn collides_point(&self, old: Vector2, new: Vector2) -> Option<(CollisionAxis, Vector2)> { let vert = self.collides_point_vertical(old, new); let horiz = self.collides_point_horizontal(old, new); match (vert, horiz) { (Some(a), Some(b)) => { if (old.x - a.1.x).abs() < (old.x - b.1.x).abs() { Some(a) } else { Some(b) } }, (Some(a), None) => Some(a), (None, Some(b)) => Some(b), (None, None) => None, } } /// Checks whether the vector (old, new) collides horizontally with an element of the map. /// /// Returns the height of the collision if any. pub fn collides_point_horizontal(&self, old: Vector2, new: Vector2) -> Option<(CollisionAxis, Vector2)> { let width = new.y - old.y; if width == 0.0 { return None; } let x = if width > 0.0 { (old.x / 16.0).ceil() * 16.0 } else { (old.x / 16.0).floor() * 16.0 }; let mut col = (x / 16.0) as isize; while (col as f32 * 16.0 - new.x) * width.signum() < 0.0 { let current_width = col as f32 * 16.0 - old.x; let y = old.y + (new.y - old.y) * current_width / width; // Find tile on x, y if x > 0.0 && y > 0.0 { let row = (y / 16.0) as usize; let col = if width > 0.0 { col } else { col - 1 }; if let Some((tile, _)) = self.tiles.get((row, col as usize)) { if tile.from_top { return Some((CollisionAxis::X, Vector2::new(x, y))); } } } col += width.signum() as isize; } None } /// Checks whether the vector (old, new) collides vertically with an element of the map. /// /// Returns the height of the collision if any. pub fn collides_point_vertical(&self, old: Vector2, new: Vector2) -> Option<(CollisionAxis, Vector2)> { let height = new.y - old.y; if height == 0.0 { return None; } let y = if height > 0.0 { (old.y / 16.0).ceil() * 16.0 } else { (old.y / 16.0).floor() * 16.0 }; let mut row = (y / 16.0) as isize; while (row as f32 * 16.0 - new.y) * height.signum() < 0.0 { let current_height = row as f32 * 16.0 - old.y; let x = old.x + (new.x - old.x) * current_height / height; // Find tile on x, y if x > 0.0 && y > 0.0 { let col = (x / 16.0) as usize; let row = if height > 0.0 { row } else { row - 1 }; if let Some((tile, _)) = self.tiles.get((row as usize, col)) { if tile.from_top { return Some((CollisionAxis::Y, Vector2::new(x, y))); } } } row += height.signum() as isize; } None } }