Reasonable collisions

This commit is contained in:
Thomas Forgione 2018-10-06 18:41:30 +02:00
parent 94f10594df
commit 6bfdfb247e
3 changed files with 131 additions and 156 deletions

View File

@ -1,5 +1,4 @@
14 24
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
13 24
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
@ -10,6 +9,6 @@
0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 1

View File

@ -9,14 +9,37 @@ use engine::renderer::Drawable;
use engine::math::Matrix;
/// This enum represents if the collision happens on the X axis or the Y axis.
#[derive(Copy, Clone)]
pub enum CollisionAxis {
/// The X axis.
X,
/// The Y axis.
Y,
/// Both axis simultaneously
Both,
}
impl CollisionAxis {
/// Returns true if the collision occured on X axis.
pub fn is_x(&self) -> bool {
match *self {
CollisionAxis::Y => false,
_ => true,
}
}
/// Returns true if the collision occured on Y axis.
pub fn is_y(&self) -> bool {
match *self {
CollisionAxis::X => false,
_ => true,
}
}
}
/// This struct represents the different sides from which a collision can occur.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct CollisionTile {
@ -359,164 +382,123 @@ impl Map {
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<f32>)> {
// 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.
/// Checks whether the bounding box collides with elements of the map.
///
/// Returns the height of the collision if any.
pub fn collides_point(&self, old: Vector2<f32>, new: Vector2<f32>)
/// Returns the new correct position.
pub fn collides_bbox(&self, old: FloatRect, new: FloatRect)
-> Option<(CollisionAxis, Vector2<f32>)> {
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)
let cols = self.tiles.cols() - 1;
let rows = self.tiles.rows() - 1;
let min_col = clamp(new.left / 16.0, 0.0, cols as f32) as usize;
let min_row = clamp(new.top / 16.0, 0.0, rows as f32) as usize;
let max_col = clamp((new.left + new.width) / 16.0, 0.0, cols as f32) as usize;
let max_row = clamp((new.top + new.height) / 16.0, 0.0, rows as f32) as usize;
let mut collision_x = false;
let mut collision_y = false;
let mut new = new;
for col in min_col .. max_col + 1 {
for row in min_row .. max_row + 1 {
let tile_left = col as f32 * 16.0;
let tile_top = row as f32 * 16.0;
let tile = FloatRect::new(tile_left, tile_top, 16.0, 16.0);
if ! overlap(new, tile) {
continue;
}
},
(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<f32>, new: Vector2<f32>)
-> Option<(CollisionAxis, Vector2<f32>)> {
// Collisions between feet and ground
if self.tiles[(row, col)].0.from_top {
let width = new.y - old.y;
if old.top + old.height <= tile_top &&
new.top + new.height >= tile_top {
if width == 0.0 {
return None;
}
collision_y = true;
new.top = tile_top - new.height;
}
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;
if ! overlap(new, tile) {
continue;
}
while (col as f32 * 16.0 - new.x) * width.signum() < 0.0 {
// Collisions between right and right wall
if self.tiles[(row, col)].0.from_left {
let current_width = col as f32 * 16.0 - old.x;
let y = old.y + (new.y - old.y) * current_width / width;
if old.left + old.width <= tile_left &&
new.left + new.width >= tile_left {
// Find tile on x, y
if x > 0.0 && y > 0.0 {
collision_x = true;
new.left = tile_left - new.width;
}
}
let row = (y / 16.0) as usize;
if ! overlap(new, tile) {
continue;
}
let col = if width > 0.0 {
col
} else {
col - 1
};
// Collisions between left and left wall
if self.tiles[(row, col)].0.from_right {
if let Some((tile, _)) = self.tiles.get((row, col as usize)) {
if tile.from_top {
return Some((CollisionAxis::X, Vector2::new(x, y)));
if old.left >= tile_left + 16.0 &&
new.left <= tile_left + 16.0 {
collision_x = true;
new.left = tile_left + 16.0;
}
}
if ! overlap(new, tile) {
continue;
}
// Collisions between head and roof
if self.tiles[(row, col)].0.from_bottom {
if old.top >= tile_top + 16.0 &&
new.top <= tile_top + 16.0 {
collision_y = true;
new.top = tile_top + 16.0;
}
}
}
col += width.signum() as isize;
}
None
let new_pos = Vector2::new(new.left, new.top);
match (collision_x, collision_y) {
(true, true) => Some((CollisionAxis::Both, new_pos)),
(true, false) => Some((CollisionAxis::X, new_pos)),
(false, true) => Some((CollisionAxis::Y, new_pos)),
(false, false) => 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<f32>, new: Vector2<f32>)
-> Option<(CollisionAxis, Vector2<f32>)> {
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
}
}
/// Checks if two boxes overlap.
pub fn overlap(box1: FloatRect, box2: FloatRect) -> bool {
box2.left < box1.left + box1.width &&
box2.left + box2.width > box1.left &&
box2.top < box1.top + box1.height &&
box2.top + box2.height > box1.top
}
/// Clamp a number between two boundaries.
pub fn clamp(number: f32, min: f32, max: f32) -> f32 {
if number < min {
min
} else if number > max {
max
} else {
number
}
}

View File

@ -4,10 +4,7 @@ use sfml::window::Event;
use sfml::graphics::View;
use engine::character::Character;
use engine::map::{
Map,
CollisionAxis
};
use engine::map::Map;
/// Contains everything needed to play.
pub struct Scene {
@ -82,16 +79,13 @@ impl Scene {
c.update(duration);
if let Some((axis, position)) = self.map.collides_bbox(old, c.bbox()) {
match axis {
CollisionAxis::X => {
c.speed.x = 0.0;
c.position.x = position.x;
},
CollisionAxis::Y => {
c.speed.y = 0.0;
c.position.y = position.y;
c.ground_collision();
},
c.position = position;
if axis.is_x() {
c.speed.x = 0.0;
}
if axis.is_y() {
c.speed.y = 0.0;
c.ground_collision();
}
} else {
c.fall_off();