cowision hawd >~<
This commit is contained in:
parent
7cbde4f614
commit
a9ce9b6978
259
src/collision.rs
Normal file
259
src/collision.rs
Normal file
@ -0,0 +1,259 @@
|
||||
use crate::{
|
||||
engine::{self, Component, System},
|
||||
query, RigidBody,
|
||||
};
|
||||
|
||||
type V2 = (f64, f64);
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Direction {
|
||||
Top,
|
||||
Bottom,
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
impl Direction {
|
||||
pub fn opposite(&self) -> Self {
|
||||
use Direction::*;
|
||||
match self {
|
||||
Top => Bottom,
|
||||
Bottom => Top,
|
||||
Left => Right,
|
||||
Right => Left,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
enum Diagonal {
|
||||
TopLeft,
|
||||
TopRight,
|
||||
BottomRight,
|
||||
BottomLeft,
|
||||
}
|
||||
|
||||
enum DiagonalCommonResult {
|
||||
None,
|
||||
Direction(Direction),
|
||||
Diagonal(Diagonal),
|
||||
}
|
||||
|
||||
impl Diagonal {
|
||||
pub fn common(&self, other: &Diagonal) -> DiagonalCommonResult {
|
||||
use Diagonal::*;
|
||||
use DiagonalCommonResult as R;
|
||||
use Direction::*;
|
||||
match (self, other) {
|
||||
(TopLeft, TopRight) => R::Direction(Top),
|
||||
(TopLeft, BottomLeft) => R::Direction(Left),
|
||||
(TopRight, TopLeft) => R::Direction(Top),
|
||||
(TopRight, BottomRight) => R::Direction(Right),
|
||||
(BottomRight, TopRight) => R::Direction(Right),
|
||||
(BottomRight, BottomLeft) => R::Direction(Bottom),
|
||||
(BottomLeft, TopLeft) => R::Direction(Left),
|
||||
(BottomLeft, BottomRight) => R::Direction(Bottom),
|
||||
(left, right) if left == right => R::Diagonal(left.clone()),
|
||||
_ => R::None,
|
||||
}
|
||||
}
|
||||
pub fn contains(&self, dir: Direction) -> bool {
|
||||
match (self, dir) {
|
||||
(Diagonal::TopLeft, Direction::Top)
|
||||
| (Diagonal::TopLeft, Direction::Left)
|
||||
| (Diagonal::TopRight, Direction::Top)
|
||||
| (Diagonal::TopRight, Direction::Left)
|
||||
| (Diagonal::TopRight, Direction::Right)
|
||||
| (Diagonal::BottomRight, Direction::Bottom)
|
||||
| (Diagonal::BottomRight, Direction::Right)
|
||||
| (Diagonal::BottomLeft, Direction::Bottom)
|
||||
| (Diagonal::BottomLeft, Direction::Left) => true,
|
||||
(Diagonal::TopLeft, Direction::Bottom)
|
||||
| (Diagonal::TopLeft, Direction::Right)
|
||||
| (Diagonal::TopRight, Direction::Bottom)
|
||||
| (Diagonal::BottomRight, Direction::Top)
|
||||
| (Diagonal::BottomRight, Direction::Left)
|
||||
| (Diagonal::BottomLeft, Direction::Top)
|
||||
| (Diagonal::BottomLeft, Direction::Right) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn point_rect_closest_point(pos: V2, other_pos: V2, rect: V2) -> V2 {
|
||||
[
|
||||
other_pos,
|
||||
(other_pos.0, other_pos.1 + rect.1),
|
||||
(other_pos.0 + rect.0, other_pos.1),
|
||||
(other_pos.0 + rect.0, other_pos.1 + rect.1),
|
||||
]
|
||||
.into_iter()
|
||||
.map(|p| (p, point_distance(pos, p)))
|
||||
.min_by(|(_, a), (_, b)| a.total_cmp(b))
|
||||
.map(|(p, _)| p)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn rect_adjacent_corners(pos: V2, rect: V2, corner: V2) -> (V2, (f64, f64)) {
|
||||
if corner == pos {
|
||||
((pos.0, pos.1 + rect.1), (pos.0 + rect.0, pos.1))
|
||||
} else if corner == (pos.0 + rect.0, pos.1) {
|
||||
(pos, (pos.0 + rect.0, pos.1 + rect.1))
|
||||
} else if corner == (pos.0 + rect.0, pos.1 + rect.1) {
|
||||
((pos.0 + rect.0, pos.1), (pos.0, pos.1 + rect.1))
|
||||
} else if corner == (pos.0, pos.1 + rect.1) {
|
||||
((pos.0 + rect.0, pos.1), pos)
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
fn line_intersection(p0: V2, r0: V2, p1: V2, r1: V2) -> Option<V2> {
|
||||
if r0.0 == 0.0 && r1.0 == 0.0 {
|
||||
// both vertical
|
||||
return None;
|
||||
}
|
||||
// y = ax + b
|
||||
// a = y / x
|
||||
let a0 = r0.1 / r0.0;
|
||||
let a1 = r1.1 / r1.0;
|
||||
if a0 == a1 {
|
||||
// parallel
|
||||
return None;
|
||||
}
|
||||
// b = y - ax
|
||||
let b0 = p0.1 - a0 * p0.0;
|
||||
let b1 = p1.1 - a1 * p1.0;
|
||||
// y = a0 * x + b0
|
||||
// y = a1 * x + b1
|
||||
// a0 * x + b0 = a1 * x + b1
|
||||
// a0 * x - a1 * x = b1 - b0
|
||||
// x * (a0 - a1) = b1 - b0
|
||||
// x = (b1 - b0) / (a0 - a1)
|
||||
let x = (b1 - b0) / (a0 - a1);
|
||||
let y = a0 * x + b0;
|
||||
Some((x, y))
|
||||
}
|
||||
|
||||
fn point_between_points(p: V2, p0: V2, p1: V2) -> bool {
|
||||
let t = (p.0 - p0.0) / (p1.0 - p0.0);
|
||||
0.0 < t && t < 1.0
|
||||
}
|
||||
|
||||
fn point_distance(a: V2, b: (f64, f64)) -> f64 {
|
||||
((a.0 - b.0).abs().powi(2) + (a.1 - b.1).abs().powi(2)).sqrt()
|
||||
}
|
||||
|
||||
fn rects_closet_points(
|
||||
pos: V2,
|
||||
rect: V2,
|
||||
other_pos: V2,
|
||||
other_rect: V2,
|
||||
) -> ((V2, Diagonal), (V2, Diagonal)) {
|
||||
use Diagonal::*;
|
||||
let points = [
|
||||
((pos.0, pos.1), TopLeft),
|
||||
((pos.0 + rect.0, pos.1), TopRight),
|
||||
((pos.0 + rect.0, pos.1 + rect.1), BottomRight),
|
||||
((pos.0, pos.1 + rect.1), BottomLeft),
|
||||
];
|
||||
|
||||
let other_points = [
|
||||
((other_pos.0, other_pos.1), TopLeft),
|
||||
((other_pos.0 + other_rect.0, other_pos.1), TopRight),
|
||||
(
|
||||
(other_pos.0 + other_rect.0, other_pos.1 + other_rect.1),
|
||||
BottomRight,
|
||||
),
|
||||
((other_pos.0, other_pos.1 + other_rect.1), BottomLeft),
|
||||
];
|
||||
|
||||
let mut lowest = (
|
||||
f64::INFINITY,
|
||||
(((0.0, 0.0), TopLeft), ((0.0, 0.0), TopLeft)),
|
||||
);
|
||||
for (point, dir) in points {
|
||||
for (other_point, other_dir) in other_points.iter() {
|
||||
let distance = point_distance(point, *other_point);
|
||||
if distance < lowest.0 {
|
||||
lowest = (
|
||||
distance,
|
||||
((point, dir.clone()), (*other_point, other_dir.clone())),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
lowest.1
|
||||
}
|
||||
|
||||
fn point_vel_rect_collision(pos: V2, vel: V2, other_pos: V2, rect: V2) -> Option<V2> {
|
||||
let c1 = point_rect_closest_point(pos, other_pos, rect);
|
||||
let (c0, c2) = rect_adjacent_corners(other_pos, rect, c1);
|
||||
let intersection_c1_c0 = line_intersection(pos, vel, c1, (c0.0 - c1.0, c0.1 - c1.0))?;
|
||||
if point_between_points(intersection_c1_c0, c1, c0) {
|
||||
return Some(intersection_c1_c0);
|
||||
}
|
||||
let intersection_c1_c2 = line_intersection(pos, vel, c1, (c2.0 - c1.0, c2.1 - c1.0))?;
|
||||
if point_between_points(intersection_c1_c2, c1, c2) {
|
||||
return Some(intersection_c1_c2);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn rect_collision(
|
||||
pos: V2,
|
||||
vel: V2,
|
||||
rect: V2,
|
||||
other_pos: V2,
|
||||
other_rect: V2,
|
||||
) -> Option<(V2, Direction)> {
|
||||
let ((p0, d0), (_, d1)) = rects_closet_points(pos, rect, other_pos, other_rect);
|
||||
let common = match d0.common(&d1) {
|
||||
DiagonalCommonResult::Direction(dir) => dir,
|
||||
_ => return None,
|
||||
};
|
||||
let new_pos = point_vel_rect_collision(p0, vel, other_pos, other_rect)?;
|
||||
Some((new_pos, common))
|
||||
}
|
||||
|
||||
#[derive(Component, Clone, Default)]
|
||||
pub struct Collider {
|
||||
pub resolve: bool,
|
||||
pub on_ground: bool,
|
||||
}
|
||||
|
||||
pub struct CollisionSystem;
|
||||
impl System for CollisionSystem {
|
||||
fn on_update(&self, ctx: &mut engine::Context, delta: f64) -> Result<(), engine::Error> {
|
||||
for id in query!(ctx, RigidBody, Collider) {
|
||||
let collider = ctx.entity_component::<Collider>(id);
|
||||
collider.on_ground = false;
|
||||
let collider = ctx.entity_component::<Collider>(id).clone();
|
||||
if !collider.resolve {
|
||||
continue;
|
||||
}
|
||||
let body = ctx.entity_component::<RigidBody>(id).clone();
|
||||
for other_id in query!(ctx, RigidBody, Collider) {
|
||||
if id == other_id {
|
||||
continue;
|
||||
}
|
||||
let other_body = ctx.entity_component::<RigidBody>(other_id).clone();
|
||||
let Some((new_pos, dir)) = rect_collision(
|
||||
body.pos,
|
||||
(body.vel.0 * delta, body.vel.1 * delta),
|
||||
body.rect,
|
||||
other_body.pos,
|
||||
other_body.rect,
|
||||
) else {
|
||||
continue;
|
||||
};
|
||||
let body = ctx.entity_component::<RigidBody>(id);
|
||||
body.pos = new_pos;
|
||||
match dir {
|
||||
Direction::Top | Direction::Bottom => body.vel.0 = 0.0,
|
||||
Direction::Left | Direction::Right => body.vel.1 = 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
233
src/main.rs
233
src/main.rs
@ -1,14 +1,11 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
mod cuwusions;
|
||||
mod collision;
|
||||
mod engine;
|
||||
|
||||
use engine::{Component, System};
|
||||
|
||||
#[derive(Component)]
|
||||
struct Sprite {
|
||||
sprite: engine::Sprite,
|
||||
}
|
||||
use crate::collision::{Collider, CollisionSystem};
|
||||
|
||||
#[derive(Component, Default, Clone, Debug)]
|
||||
struct RigidBody {
|
||||
@ -23,153 +20,13 @@ impl System for VelocitySystem {
|
||||
fn on_update(&self, ctx: &mut engine::Context, delta: f64) -> Result<(), engine::Error> {
|
||||
for id in query!(ctx, RigidBody) {
|
||||
let body = ctx.entity_component::<RigidBody>(id);
|
||||
// body.pos.0 += body.vel.0 * delta;
|
||||
// body.pos.1 += body.vel.1 * delta;
|
||||
body.pos.0 += body.vel.0 * delta;
|
||||
body.pos.1 += body.vel.1 * delta;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn rects_collide(
|
||||
rect: (f64, f64),
|
||||
pos: (f64, f64),
|
||||
other_rect: (f64, f64),
|
||||
other_pos: (f64, f64),
|
||||
) -> bool {
|
||||
pos.0 + rect.0 > other_pos.0
|
||||
&& pos.0 < other_pos.0 + other_rect.0
|
||||
&& pos.1 + rect.1 > other_pos.1
|
||||
&& pos.1 < other_pos.1 + other_rect.1
|
||||
}
|
||||
|
||||
fn rec_point_collides(rect: (f64, f64), pos: (f64, f64), point: (f64, f64)) -> bool {
|
||||
pos.0 + rect.0 < point.0 && pos.0 > point.0 && pos.1 + rect.1 < point.1 && pos.1 > point.1
|
||||
}
|
||||
|
||||
#[derive(Component, Clone, Default)]
|
||||
struct Collider {
|
||||
resolve: bool,
|
||||
colliding_surface: Option<Surface>,
|
||||
}
|
||||
|
||||
fn horizontal_line_intersect(p0: (f64, f64), vel: (f64, f64), line_y: f64) -> Option<(f64, f64)> {
|
||||
if vel.1 == 0.0 {
|
||||
// exclusively going left or right, ergo there's no collision with a horizontal line
|
||||
return None;
|
||||
} else if vel.0 == 0.0 {
|
||||
// no change in x, ergo the intersect must be at p0_x
|
||||
return Some((p0.0, line_y));
|
||||
}
|
||||
// y = ax + b
|
||||
let a = vel.1 / vel.0;
|
||||
let b = p0.1 - p0.0 * a;
|
||||
|
||||
let x = (line_y - b) / a;
|
||||
Some((x, line_y))
|
||||
}
|
||||
|
||||
fn vertical_line_intersect(p0: (f64, f64), vel: (f64, f64), line_x: f64) -> Option<(f64, f64)> {
|
||||
if vel.0 == 0.0 {
|
||||
// exclusively going up or down, ergo there's no collision with a vertical line
|
||||
return None;
|
||||
} else if vel.1 == 0.0 {
|
||||
// no change in y, ergo the intersect must be at p0_y
|
||||
return Some((line_x, p0.1));
|
||||
}
|
||||
//
|
||||
// y = ax + b
|
||||
let a = vel.1 / vel.0;
|
||||
let b = p0.1 - p0.0 * a;
|
||||
|
||||
let y = a * line_x + b;
|
||||
Some((line_x, y))
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum Surface {
|
||||
Top,
|
||||
Bottom,
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
fn point_distance(a: (f64, f64), b: (f64, f64)) -> f64 {
|
||||
((a.0 - b.0).abs().powi(2) + (a.1 - b.1).abs().powi(2)).sqrt()
|
||||
}
|
||||
|
||||
fn point_rect_closest_surface(
|
||||
p0: (f64, f64),
|
||||
vel: (f64, f64),
|
||||
rect_pos: (f64, f64),
|
||||
rect: (f64, f64),
|
||||
) -> Option<(f64, Surface)> {
|
||||
let closest_horizontal_intersect = [
|
||||
(horizontal_line_intersect(p0, vel, rect_pos.1), Surface::Top),
|
||||
(
|
||||
horizontal_line_intersect(p0, vel, rect_pos.1 + rect.1),
|
||||
Surface::Bottom,
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.filter_map(|(intersect, surface)| intersect.map(|intersect| (intersect, surface)))
|
||||
.map(|(point, surface)| (point_distance(p0, point), surface))
|
||||
.min_by(|(dist_a, _), (dist_b, _)| dist_a.total_cmp(dist_b));
|
||||
|
||||
let closest_vertical_intersect = [
|
||||
(vertical_line_intersect(p0, vel, rect_pos.0), Surface::Left),
|
||||
(
|
||||
vertical_line_intersect(p0, vel, rect_pos.0 + rect.0),
|
||||
Surface::Right,
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.filter_map(|(intersect, surface)| intersect.map(|intersect| (intersect, surface)))
|
||||
.map(|(point, surface)| (point_distance(p0, point), surface))
|
||||
.min_by(|(dist_a, _), (dist_b, _)| dist_a.total_cmp(dist_b));
|
||||
|
||||
match (closest_horizontal_intersect, closest_vertical_intersect) {
|
||||
(Some(left), Some(right)) => {
|
||||
if right.0 < left.0 {
|
||||
Some(right)
|
||||
} else {
|
||||
Some(left)
|
||||
}
|
||||
}
|
||||
(None, v) | (v, None) => v,
|
||||
}
|
||||
}
|
||||
|
||||
struct CollisionSystem;
|
||||
impl System for CollisionSystem {
|
||||
fn on_update(&self, ctx: &mut engine::Context, delta: f64) -> Result<(), engine::Error> {
|
||||
for id in query!(ctx, RigidBody, Collider) {
|
||||
let collider = ctx.entity_component::<Collider>(id);
|
||||
collider.colliding_surface = None;
|
||||
let collider = ctx.entity_component::<Collider>(id).clone();
|
||||
if !collider.resolve {
|
||||
continue;
|
||||
}
|
||||
let body = ctx.entity_component::<RigidBody>(id).clone();
|
||||
for other_id in query!(ctx, RigidBody, Collider) {
|
||||
if id == other_id {
|
||||
continue;
|
||||
}
|
||||
let other_body = ctx.entity_component::<RigidBody>(other_id).clone();
|
||||
let new_pos = cuwusions::rects_collision_resolution_pos(
|
||||
body.pos,
|
||||
(body.vel.0 * delta, body.vel.1 * delta),
|
||||
body.rect,
|
||||
other_body.pos,
|
||||
other_body.rect,
|
||||
);
|
||||
|
||||
let body = ctx.entity_component::<RigidBody>(id);
|
||||
body.pos = new_pos;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
struct GravitySystem;
|
||||
impl System for GravitySystem {
|
||||
fn on_update(&self, ctx: &mut engine::Context, delta: f64) -> Result<(), engine::Error> {
|
||||
@ -188,6 +45,24 @@ impl System for GravitySystem {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct Sprite {
|
||||
sprite: engine::Sprite,
|
||||
}
|
||||
|
||||
struct SpriteRenderer;
|
||||
impl System for SpriteRenderer {
|
||||
fn on_update(&self, ctx: &mut engine::Context, _delta: f64) -> Result<(), engine::Error> {
|
||||
for id in query!(ctx, Sprite, RigidBody) {
|
||||
let body = ctx.entity_component::<RigidBody>(id).clone();
|
||||
let sprite = ctx.entity_component::<Sprite>(id).sprite;
|
||||
|
||||
ctx.draw_sprite(sprite, body.pos.0 as i32, body.pos.1 as i32)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct Cloud;
|
||||
|
||||
@ -227,19 +102,6 @@ impl System for CloudSystem {
|
||||
}
|
||||
}
|
||||
|
||||
struct SpriteRenderer;
|
||||
impl System for SpriteRenderer {
|
||||
fn on_update(&self, ctx: &mut engine::Context, _delta: f64) -> Result<(), engine::Error> {
|
||||
for id in query!(ctx, Sprite, RigidBody) {
|
||||
let body = ctx.entity_component::<RigidBody>(id).clone();
|
||||
let sprite = ctx.entity_component::<Sprite>(id).sprite;
|
||||
|
||||
ctx.draw_sprite(sprite, body.pos.0 as i32, body.pos.1 as i32)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct PlayerMovement;
|
||||
|
||||
@ -259,7 +121,7 @@ impl System for PlayerMovementSystem {
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
if let (Some(Surface::Top), true) = (collider.colliding_surface, w_down) {
|
||||
if collider.on_ground && w_down {
|
||||
body.vel.1 = -1000.0;
|
||||
}
|
||||
}
|
||||
@ -271,10 +133,10 @@ fn main() {
|
||||
let mut game = engine::Game::new().unwrap();
|
||||
|
||||
let mut context = game.context();
|
||||
context.add_system(VelocitySystem);
|
||||
context.add_system(CollisionSystem);
|
||||
context.add_system(VelocitySystem);
|
||||
context.add_system(SpriteRenderer);
|
||||
context.add_system(PlayerMovementSystem);
|
||||
// context.add_system(PlayerMovementSystem);
|
||||
context.add_system(GravitySystem);
|
||||
context.add_system(CloudSystem);
|
||||
let player = context.load_sprite("textures/player.png").unwrap();
|
||||
@ -291,7 +153,8 @@ fn main() {
|
||||
&mut context,
|
||||
Sprite { sprite: player },
|
||||
RigidBody {
|
||||
pos: (400.0, 400.0),
|
||||
pos: (400.0, 200.0),
|
||||
vel: (10.0, 0.0),
|
||||
rect: (128.0, 128.0),
|
||||
gravity: true,
|
||||
..Default::default()
|
||||
@ -313,27 +176,27 @@ fn main() {
|
||||
Collider::default(),
|
||||
);
|
||||
|
||||
spawn!(
|
||||
&mut context,
|
||||
RigidBody {
|
||||
pos: (300.0, 200.0),
|
||||
rect: (32.0, 32.0),
|
||||
..Default::default()
|
||||
},
|
||||
Collider::default(),
|
||||
Sprite { sprite: nope },
|
||||
);
|
||||
|
||||
spawn!(
|
||||
&mut context,
|
||||
RigidBody {
|
||||
pos: (900.0, 400.0),
|
||||
rect: (32.0, 32.0),
|
||||
..Default::default()
|
||||
},
|
||||
Collider::default(),
|
||||
Sprite { sprite: nope },
|
||||
);
|
||||
// spawn!(
|
||||
// &mut context,
|
||||
// RigidBody {
|
||||
// pos: (300.0, 200.0),
|
||||
// rect: (32.0, 32.0),
|
||||
// ..Default::default()
|
||||
// },
|
||||
// Collider::default(),
|
||||
// Sprite { sprite: nope },
|
||||
// );
|
||||
//
|
||||
// spawn!(
|
||||
// &mut context,
|
||||
// RigidBody {
|
||||
// pos: (900.0, 400.0),
|
||||
// rect: (32.0, 32.0),
|
||||
// ..Default::default()
|
||||
// },
|
||||
// Collider::default(),
|
||||
// Sprite { sprite: nope },
|
||||
// );
|
||||
|
||||
game.run();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user