From 47b3b9c46c36b4e1fffd123f0fddfde3956ba5f5 Mon Sep 17 00:00:00 2001 From: Lily Iliana Luna Ylva Anderson Grigaitis Date: Thu, 4 Sep 2025 18:19:10 -0500 Subject: [PATCH] Some basic collision detection. --- src/lib.rs | 153 +++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 120 insertions(+), 33 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4437be5..71167d0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -88,7 +88,7 @@ struct Ship { } impl Ship { - fn update(&mut self, dt_s: f64, keys: &HashSet) { + fn update(&mut self, dt_s: f64, keys: &HashSet, boundary: &CGA) { let vel = self.vel.clone(); //Velocity at beginning of frame let mut vel_2 = self.vel.clone(); @@ -107,29 +107,33 @@ impl Ship { self.orientation = bivector_exponential(&(-dt_s * CGA::e12())) * &self.orientation; } - vel_2 = vel_2 + a_vec*dt_s; //Velocity at end of frame let vel_3 = 0.5*(vel+&vel_2); let delta = vel_3 * dt_s; let pos = bivector_exponential(&delta)*&self.com_rotor; //Update position. - self.vel = vel_2; + let old = self.com_rotor.clone(); self.com_rotor = pos; + self.vel = vel_2; + if self.intersects_circle(boundary) { + self.com_rotor = 0.99*old; + //self.vel = CGA::zero(); + } } fn draw(&self, context: &CanvasRenderingContext2d) { let origin = gen_hyperbolic_point(&CGA::zero()); //Origin in the body frame. let com = self.fixed_to_world(&origin); - let p0 = self.fixed_to_world(&self.verts[0]); - let p1 = self.fixed_to_world(&self.verts[1]); - let p2 = self.fixed_to_world(&self.verts[2]); + let p1 = self.fixed_to_world(&self.verts[0]); + let p2 = self.fixed_to_world(&self.verts[1]); + let p3 = self.fixed_to_world(&self.verts[2]); draw_point(context, &com); - draw_point(context, &p0); draw_point(context, &p1); draw_point(context, &p2); - draw_line_between(context, &p0, &p1); + draw_point(context, &p3); draw_line_between(context, &p1, &p2); - draw_line_between(context, &p2, &p0); + draw_line_between(context, &p2, &p3); + draw_line_between(context, &p3, &p1); //Construct and draw line pointing straight ahead, to help orient the player. let delta_gen = self.fixed_to_world(&CGA::e14()); @@ -141,6 +145,83 @@ impl Ship { fn fixed_to_world(&self, vec: &CGA) -> CGA { &self.com_rotor * &self.orientation * vec * &self.orientation.Reverse() * &self.com_rotor.Reverse() } + + fn intersects_circle(&self, circle: &CGA) -> bool { + //Explicitly select only tri-vector part. + let circle = grade_selection(circle, 3); + //Find vertices in world space. + let p1 = self.fixed_to_world(&self.verts[0]); + let p2 = self.fixed_to_world(&self.verts[1]); + let p3 = self.fixed_to_world(&self.verts[2]); + //Construct sides of ship in world space. + let e4 = CGA::e4(); + let side_1 = &e4 ^ &p1 ^ &p2; + let side_2 = &e4 ^ &p1 ^ &p3; + let side_3 = &e4 ^ &p2 ^ &p3; + //Find intersections between any side and the given circle. + let int_1 = circle_intersection(&side_1, &circle); + let int_2 = circle_intersection(&side_2, &circle); + let int_3 = circle_intersection(&side_3, &circle); + //For each side, test if the intersection points are between the two vertices defining the + //side. + match int_1 { + CircleIntersection::TwoPoints(a, b) => { + if between_points(&a, &p1, &p2) { + return true; + } + if between_points(&b, &p1, &p2) { + return true; + } + }, + CircleIntersection::None => {}, + } + match int_2 { + CircleIntersection::TwoPoints(a, b) => { + if between_points(&a, &p1, &p3) { + return true; + } + if between_points(&b, &p1, &p3) { + return true; + } + }, + CircleIntersection::None => {}, + } + match int_3 { + CircleIntersection::TwoPoints(a, b) => { + if between_points(&a, &p2, &p3) { + return true; + } + if between_points(&b, &p2, &p3) { + return true; + } + }, + CircleIntersection::None => {}, + } + false + } +} + +fn hyperbolic_distance(point_1: &CGA, point_2: &CGA) -> f64 { + let a = grade_selection(point_1, 1); + let b = grade_selection(point_2, 1); + let a = hyperbolic_normalize(&a); + let b = hyperbolic_normalize(&b); + let dot = (&a | &b)[SCALAR]; + let rad = -0.5*dot; + let root = rad.sqrt(); + let dist = root.asinh(); + dist +} + +fn between_points(a: &CGA, p1: &CGA, p2: &CGA) -> bool { + let total = hyperbolic_distance(p1, p2); + let p1_a = hyperbolic_distance(p1, a); + let a_p2 = hyperbolic_distance(a, p2); + if p1_a + a_p2 - total < ERROR { + true + } else { + false + } } enum Geometry { @@ -190,7 +271,6 @@ fn draw_point(context: &CanvasRenderingContext2d, point: &CGA) { let (x,y) = point_to_screen_space(x,y); context.begin_path(); - context.set_fill_style_str(BLUE); context.arc(x, y, 2., 0., TWO_PI).unwrap(); context.fill(); @@ -225,6 +305,11 @@ fn get_hyperbolic_point(point: &CGA) -> (f64, f64) { (r*x,r*y) } +fn hyperbolic_normalize(point: &CGA) -> CGA { + let norm = -(point | CGA::e4())[SCALAR]; + point * (1. / norm) +} + fn get_point(point: &CGA) -> (f64, f64) { let x = point[E1]; let y = point[E2]; @@ -263,8 +348,6 @@ fn get_geometry(trivector: &CGA) -> Geometry { fn draw_line(context: &CanvasRenderingContext2d, line: &CGA) { context.begin_path(); - context.set_stroke_style_str(GREEN); - let geometry = get_geometry(line); match geometry { Geometry::Circle(x,y,r) => { @@ -289,7 +372,6 @@ fn draw_line_between(context: &CanvasRenderingContext2d, a: &CGA, b: &CGA) { let line = CGA::e4() ^ a ^ b; let geometry = get_geometry(&line); context.begin_path(); - context.set_stroke_style_str(GREEN); match geometry { Geometry::Circle(x,y,r) => { let (a_x, a_y) = get_point(a); @@ -483,7 +565,7 @@ impl Asteroid { let v = 0.; let (v_x, v_y) = (v * 1.0_f64.cos(), v*1.0_f64.sin()); let vel = v_x*CGA::e15() + v_y*CGA::e25(); - let circle = circle_from_coords(0.9, 0., 0.1); + let circle = circle_from_coords(0.5, 0., 0.4); Asteroid { circle, vel } } @@ -512,6 +594,7 @@ pub struct Game { asteroids: Vec::, boundary: CGA, keys: HashSet, + dt: f64 } #[wasm_bindgen] @@ -560,6 +643,7 @@ impl Game { boundary, asteroids: vec![Asteroid::new()], keys: HashSet::new(), + dt: 100. } } @@ -572,29 +656,30 @@ impl Game { self.context.arc(CENTER_X, CENTER_Y, RADIUS, 0., TWO_PI).unwrap(); self.context.stroke(); + let fps = 1000. / self.dt; + self.context.set_fill_style_str(BLACK); + self.context.set_font("20px Arial"); + let message = format!("FPS: {}", fps); + self.context.fill_text(&message, 10., 30.).unwrap(); + draw_line(&self.context, &self.boundary); - self.ship.draw(&self.context); - let line = 100.*self.ship.fixed_to_world(&self.ship.verts[0]) ^ self.ship.fixed_to_world(&self.ship.verts[1]) ^ CGA::e4(); //Scaled to keep above floating point error + + let mut intersects = false; + + self.context.set_stroke_style_str(RED); for asteroid in &self.asteroids { - let pair = circle_intersection(&asteroid.circle, &self.boundary); - match pair { - CircleIntersection::TwoPoints(a,b) => { - draw_point(&self.context, &a); - draw_point(&self.context, &b); - }, - _ => {} + if self.ship.intersects_circle(&asteroid.circle) { + intersects = true; } asteroid.draw(&self.context); - let pair = circle_intersection(&asteroid.circle, &line); - match pair { - CircleIntersection::TwoPoints(a,b) => { - draw_point(&self.context, &a); - draw_point(&self.context, &b); - draw_line(&self.context, &line); - } - _ => {}, - } } + + if intersects { + self.context.set_stroke_style_str(RED); + } else { + self.context.set_stroke_style_str(GREEN); + } + self.ship.draw(&self.context); } pub fn update(&mut self, dt_m: f64) { @@ -604,11 +689,13 @@ impl Game { dt_m / 1000. }; + self.dt = dt_m; + for asteroid in &mut self.asteroids { asteroid.update(dt_s); } - self.ship.update(dt_s, &self.keys); + self.ship.update(dt_s, &self.keys, &self.boundary); } pub fn key_down(&mut self, key: String) {