From a9ce9b697813188b332147a5da6123178853b83e Mon Sep 17 00:00:00 2001 From: SimonFJ20 Date: Thu, 11 Apr 2024 15:16:48 +0200 Subject: [PATCH] cowision hawd >~< --- src/collision.rs | 259 +++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 233 +++++++++--------------------------------- 2 files changed, 307 insertions(+), 185 deletions(-) create mode 100644 src/collision.rs diff --git a/src/collision.rs b/src/collision.rs new file mode 100644 index 0000000..73c8487 --- /dev/null +++ b/src/collision.rs @@ -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 { + 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 { + 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::(id); + collider.on_ground = false; + let collider = ctx.entity_component::(id).clone(); + if !collider.resolve { + continue; + } + let body = ctx.entity_component::(id).clone(); + for other_id in query!(ctx, RigidBody, Collider) { + if id == other_id { + continue; + } + let other_body = ctx.entity_component::(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::(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(()) + } +} diff --git a/src/main.rs b/src/main.rs index 1904782..342d270 100644 --- a/src/main.rs +++ b/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::(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, -} - -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::(id); - collider.colliding_surface = None; - let collider = ctx.entity_component::(id).clone(); - if !collider.resolve { - continue; - } - let body = ctx.entity_component::(id).clone(); - for other_id in query!(ctx, RigidBody, Collider) { - if id == other_id { - continue; - } - let other_body = ctx.entity_component::(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::(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::(id).clone(); + let sprite = ctx.entity_component::(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::(id).clone(); - let sprite = ctx.entity_component::(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(); }