cowision hawd >~<

This commit is contained in:
SimonFJ20 2024-04-11 15:16:48 +02:00
parent 7cbde4f614
commit a9ce9b6978
2 changed files with 307 additions and 185 deletions

259
src/collision.rs Normal file
View 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(())
}
}

View File

@ -1,14 +1,11 @@
#![allow(dead_code)] #![allow(dead_code)]
mod cuwusions; mod collision;
mod engine; mod engine;
use engine::{Component, System}; use engine::{Component, System};
#[derive(Component)] use crate::collision::{Collider, CollisionSystem};
struct Sprite {
sprite: engine::Sprite,
}
#[derive(Component, Default, Clone, Debug)] #[derive(Component, Default, Clone, Debug)]
struct RigidBody { struct RigidBody {
@ -23,153 +20,13 @@ impl System for VelocitySystem {
fn on_update(&self, ctx: &mut engine::Context, delta: f64) -> Result<(), engine::Error> { fn on_update(&self, ctx: &mut engine::Context, delta: f64) -> Result<(), engine::Error> {
for id in query!(ctx, RigidBody) { for id in query!(ctx, RigidBody) {
let body = ctx.entity_component::<RigidBody>(id); let body = ctx.entity_component::<RigidBody>(id);
// body.pos.0 += body.vel.0 * delta; body.pos.0 += body.vel.0 * delta;
// body.pos.1 += body.vel.1 * delta; body.pos.1 += body.vel.1 * delta;
} }
Ok(()) 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; struct GravitySystem;
impl System for GravitySystem { impl System for GravitySystem {
fn on_update(&self, ctx: &mut engine::Context, delta: f64) -> Result<(), engine::Error> { 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)] #[derive(Component)]
struct Cloud; 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)] #[derive(Component)]
struct PlayerMovement; struct PlayerMovement;
@ -259,7 +121,7 @@ impl System for PlayerMovementSystem {
} else { } else {
0.0 0.0
}; };
if let (Some(Surface::Top), true) = (collider.colliding_surface, w_down) { if collider.on_ground && w_down {
body.vel.1 = -1000.0; body.vel.1 = -1000.0;
} }
} }
@ -271,10 +133,10 @@ fn main() {
let mut game = engine::Game::new().unwrap(); let mut game = engine::Game::new().unwrap();
let mut context = game.context(); let mut context = game.context();
context.add_system(VelocitySystem);
context.add_system(CollisionSystem); context.add_system(CollisionSystem);
context.add_system(VelocitySystem);
context.add_system(SpriteRenderer); context.add_system(SpriteRenderer);
context.add_system(PlayerMovementSystem); // context.add_system(PlayerMovementSystem);
context.add_system(GravitySystem); context.add_system(GravitySystem);
context.add_system(CloudSystem); context.add_system(CloudSystem);
let player = context.load_sprite("textures/player.png").unwrap(); let player = context.load_sprite("textures/player.png").unwrap();
@ -291,7 +153,8 @@ fn main() {
&mut context, &mut context,
Sprite { sprite: player }, Sprite { sprite: player },
RigidBody { RigidBody {
pos: (400.0, 400.0), pos: (400.0, 200.0),
vel: (10.0, 0.0),
rect: (128.0, 128.0), rect: (128.0, 128.0),
gravity: true, gravity: true,
..Default::default() ..Default::default()
@ -313,27 +176,27 @@ fn main() {
Collider::default(), Collider::default(),
); );
spawn!( // spawn!(
&mut context, // &mut context,
RigidBody { // RigidBody {
pos: (300.0, 200.0), // pos: (300.0, 200.0),
rect: (32.0, 32.0), // rect: (32.0, 32.0),
..Default::default() // ..Default::default()
}, // },
Collider::default(), // Collider::default(),
Sprite { sprite: nope }, // Sprite { sprite: nope },
); // );
//
spawn!( // spawn!(
&mut context, // &mut context,
RigidBody { // RigidBody {
pos: (900.0, 400.0), // pos: (900.0, 400.0),
rect: (32.0, 32.0), // rect: (32.0, 32.0),
..Default::default() // ..Default::default()
}, // },
Collider::default(), // Collider::default(),
Sprite { sprite: nope }, // Sprite { sprite: nope },
); // );
game.run(); game.run();
} }