Some basic collision detection.

This commit is contained in:
2025-09-04 18:19:10 -05:00
parent 1b9c842077
commit 47b3b9c46c

View File

@@ -88,7 +88,7 @@ struct Ship {
} }
impl Ship { impl Ship {
fn update(&mut self, dt_s: f64, keys: &HashSet<String>) { fn update(&mut self, dt_s: f64, keys: &HashSet<String>, boundary: &CGA) {
let vel = self.vel.clone(); //Velocity at beginning of frame let vel = self.vel.clone(); //Velocity at beginning of frame
let mut vel_2 = self.vel.clone(); let mut vel_2 = self.vel.clone();
@@ -107,29 +107,33 @@ impl Ship {
self.orientation = bivector_exponential(&(-dt_s * CGA::e12())) * &self.orientation; self.orientation = bivector_exponential(&(-dt_s * CGA::e12())) * &self.orientation;
} }
vel_2 = vel_2 + a_vec*dt_s; //Velocity at end of frame vel_2 = vel_2 + a_vec*dt_s; //Velocity at end of frame
let vel_3 = 0.5*(vel+&vel_2); let vel_3 = 0.5*(vel+&vel_2);
let delta = vel_3 * dt_s; let delta = vel_3 * dt_s;
let pos = bivector_exponential(&delta)*&self.com_rotor; //Update position. 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.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) { fn draw(&self, context: &CanvasRenderingContext2d) {
let origin = gen_hyperbolic_point(&CGA::zero()); //Origin in the body frame. let origin = gen_hyperbolic_point(&CGA::zero()); //Origin in the body frame.
let com = self.fixed_to_world(&origin); let com = self.fixed_to_world(&origin);
let p0 = self.fixed_to_world(&self.verts[0]); let p1 = self.fixed_to_world(&self.verts[0]);
let p1 = self.fixed_to_world(&self.verts[1]); let p2 = self.fixed_to_world(&self.verts[1]);
let p2 = self.fixed_to_world(&self.verts[2]); let p3 = self.fixed_to_world(&self.verts[2]);
draw_point(context, &com); draw_point(context, &com);
draw_point(context, &p0);
draw_point(context, &p1); draw_point(context, &p1);
draw_point(context, &p2); draw_point(context, &p2);
draw_line_between(context, &p0, &p1); draw_point(context, &p3);
draw_line_between(context, &p1, &p2); 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. //Construct and draw line pointing straight ahead, to help orient the player.
let delta_gen = self.fixed_to_world(&CGA::e14()); let delta_gen = self.fixed_to_world(&CGA::e14());
@@ -141,6 +145,83 @@ impl Ship {
fn fixed_to_world(&self, vec: &CGA) -> CGA { fn fixed_to_world(&self, vec: &CGA) -> CGA {
&self.com_rotor * &self.orientation * vec * &self.orientation.Reverse() * &self.com_rotor.Reverse() &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 { enum Geometry {
@@ -190,7 +271,6 @@ fn draw_point(context: &CanvasRenderingContext2d, point: &CGA) {
let (x,y) = point_to_screen_space(x,y); let (x,y) = point_to_screen_space(x,y);
context.begin_path(); context.begin_path();
context.set_fill_style_str(BLUE);
context.arc(x, y, 2., 0., TWO_PI).unwrap(); context.arc(x, y, 2., 0., TWO_PI).unwrap();
context.fill(); context.fill();
@@ -225,6 +305,11 @@ fn get_hyperbolic_point(point: &CGA) -> (f64, f64) {
(r*x,r*y) (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) { fn get_point(point: &CGA) -> (f64, f64) {
let x = point[E1]; let x = point[E1];
let y = point[E2]; let y = point[E2];
@@ -263,8 +348,6 @@ fn get_geometry(trivector: &CGA) -> Geometry {
fn draw_line(context: &CanvasRenderingContext2d, line: &CGA) { fn draw_line(context: &CanvasRenderingContext2d, line: &CGA) {
context.begin_path(); context.begin_path();
context.set_stroke_style_str(GREEN);
let geometry = get_geometry(line); let geometry = get_geometry(line);
match geometry { match geometry {
Geometry::Circle(x,y,r) => { 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 line = CGA::e4() ^ a ^ b;
let geometry = get_geometry(&line); let geometry = get_geometry(&line);
context.begin_path(); context.begin_path();
context.set_stroke_style_str(GREEN);
match geometry { match geometry {
Geometry::Circle(x,y,r) => { Geometry::Circle(x,y,r) => {
let (a_x, a_y) = get_point(a); let (a_x, a_y) = get_point(a);
@@ -483,7 +565,7 @@ impl Asteroid {
let v = 0.; let v = 0.;
let (v_x, v_y) = (v * 1.0_f64.cos(), v*1.0_f64.sin()); 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 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 } Asteroid { circle, vel }
} }
@@ -512,6 +594,7 @@ pub struct Game {
asteroids: Vec::<Asteroid>, asteroids: Vec::<Asteroid>,
boundary: CGA, boundary: CGA,
keys: HashSet<String>, keys: HashSet<String>,
dt: f64
} }
#[wasm_bindgen] #[wasm_bindgen]
@@ -560,6 +643,7 @@ impl Game {
boundary, boundary,
asteroids: vec![Asteroid::new()], asteroids: vec![Asteroid::new()],
keys: HashSet::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.arc(CENTER_X, CENTER_Y, RADIUS, 0., TWO_PI).unwrap();
self.context.stroke(); 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); 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 { for asteroid in &self.asteroids {
let pair = circle_intersection(&asteroid.circle, &self.boundary); if self.ship.intersects_circle(&asteroid.circle) {
match pair { intersects = true;
CircleIntersection::TwoPoints(a,b) => {
draw_point(&self.context, &a);
draw_point(&self.context, &b);
},
_ => {}
} }
asteroid.draw(&self.context); 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) { pub fn update(&mut self, dt_m: f64) {
@@ -604,11 +689,13 @@ impl Game {
dt_m / 1000. dt_m / 1000.
}; };
self.dt = dt_m;
for asteroid in &mut self.asteroids { for asteroid in &mut self.asteroids {
asteroid.update(dt_s); 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) { pub fn key_down(&mut self, key: String) {