diff --git a/index.html b/index.html index fcac4de..d44c520 100644 --- a/index.html +++ b/index.html @@ -13,6 +13,13 @@ async function run() { let game = new Game("game-canvas"); let last = performance.now(); + window.addEventListener("keydown", e => { + game.key_down(e.key); + }); + window.addEventListener("keyup", e => { + game.key_up(e.key); + }); + function loop(now) { const dt = (now - last); last = now; diff --git a/src/lib.rs b/src/lib.rs index 3d5072f..6ec05d7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ use std::f64; +use std::collections::HashSet; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; @@ -10,7 +11,7 @@ use cga::CGA; const PI: f64 = f64::consts::PI; const TWO_PI: f64 = 2.*PI; -const ERROR: f64 = 0.0001; +const ERROR: f64 = 0.000001; const WIDTH: f64 = 600.; const HEIGHT: f64 = 600.; @@ -21,7 +22,7 @@ const RADIUS: f64 = 250.; const BACKGROUND: &str = "#ffffff"; const BLACK: &str = "#000000"; const RED: &str = "#ff0000"; -const GREEN: &str = "#00ff00"; +const _GREEN: &str = "#00ff00"; const _BLUE: &str = "#0000ff"; const SCALAR: usize = 0; @@ -30,16 +31,16 @@ const E2: usize = 2; //const E3: usize = 3; //const E4: usize = 4; //const E5: usize = 5; -//const E12: usize = 6; -//const E13: usize = 7; -//const E14: usize = 8; -//const E15: usize = 9; -//const E23: usize = 10; -//const E24: usize = 11; -//const E25: usize = 12; -//const E34: usize = 13; -//const E35: usize = 14; -//const E45: usize = 15; +const E12: usize = 6; +const E13: usize = 7; +const E14: usize = 8; +const E15: usize = 9; +const E23: usize = 10; +const E24: usize = 11; +const E25: usize = 12; +const E34: usize = 13; +const E35: usize = 14; +const E45: usize = 15; //const E123: usize = 16; //const E124: usize = 17; //const E125: usize = 18; @@ -57,18 +58,23 @@ const E2: usize = 2; //const E2345: usize = 30; //const E12345: usize = 31; +const DRAG: f64 = 1.; +const ACCEL_STR: f64 = 1.; + struct Ship { - pos: CGA, - //_vel: CGA, - //_orientation: CGA, + com: CGA, + verts: (CGA, CGA, CGA), + vel: CGA, + orientation: CGA, } -enum Geometry { +enum _Geometry { Circle(f64, f64, f64), //x,y,r Line(f64, f64, f64) //x,r,theta } -fn point_to_cga(x: f64, y: f64) -> CGA { +/* Translation of a Euclidean Point. Need to use hyperbolic*/ +fn _point_to_cga(x: f64, y: f64) -> CGA { let x_vec = CGA::e1(); let y_vec = CGA::e2(); let _z_vec =CGA::e3(); //This game assumes everything takes place in the plane. @@ -87,7 +93,7 @@ fn point_to_cga(x: f64, y: f64) -> CGA { } 0.5*mag_sqr*n_vec + ux*x_vec + uy*y_vec - 0.5*n_bar //F(x) = 1/2(x^2 n + 2x - nbar) = -1/2(x-e)n(x-e) -} +} fn point_to_screen_space(x: f64, y: f64) -> (f64, f64) { let screen_x = x*RADIUS + CENTER_X; @@ -97,7 +103,7 @@ fn point_to_screen_space(x: f64, y: f64) -> (f64, f64) { fn draw_point(context: &CanvasRenderingContext2d, point: &CGA) { //Naively assume we are given a valid point - let (x,y) = get_point(point); + let (x,y) = get_hyperbolic_point(point); let (x,y) = point_to_screen_space(x, y); context.begin_path(); @@ -107,18 +113,35 @@ fn draw_point(context: &CanvasRenderingContext2d, point: &CGA) { } //TODO Integrate into get geometry -fn get_point(point: &CGA) -> (f64, f64) { - let e_vec = CGA::e4(); - let e_bar = CGA::e5(); - let n_vec = &e_vec + &e_bar; //Null vector for the point at infinity. +fn get_hyperbolic_point(point: &CGA) -> (f64, f64) { + let n_bar = CGA::e4() - CGA::e5(); - let normalization = -(point | &n_vec)[SCALAR]; - let x = point[E1] / normalization; - let y = point[E2] / normalization; - (x,y) + //Construct r hat dir for the point. + let mut x = point[E1]; + let mut y = point[E2]; + let mag_sqr = x*x + y*y; + if mag_sqr > ERROR { + x /= mag_sqr.sqrt(); + y /= mag_sqr.sqrt(); + //(x,y) is a unit vector pointing in the direction of our point + } else { + //mag_sqr is too small to be confident in our vector. + //Assume vector is at the origin. + return (0., 0.); + } + + //A normalized hyperbolic point in the full CGA satisfies p dot e4 = -1, + //where e4 is the positive squaring conformal vector. + let norm = -(point | CGA::e4())[SCALAR]; + //Get the normalize component of the n term. Note n dot n = 0 and n dot n_bar = 2 + let n_term = (0.5/norm) * (point | &n_bar)[SCALAR]; + //Solve for r^2 + let r_sqr = n_term/(1.+n_term); + let r = r_sqr.sqrt(); + (r*x,r*y) } -fn get_geometry(circle: &CGA) -> Geometry { +fn _get_geometry(circle: &CGA) -> _Geometry { //Naively assume we are given a valid line/circle let n_bar = CGA::e4() - CGA::e5(); let n_vec = CGA::e4() + CGA::e5(); @@ -137,34 +160,34 @@ fn get_geometry(circle: &CGA) -> Geometry { } let x = a*c/norm_sqr/2.; let y = b*c/norm_sqr/2.; - return Geometry::Line(x,y,theta); + return _Geometry::Line(x,y,theta); } else { //Circle let c_x = a/l; let c_y = b/l; let r_sqr = c_x*c_x + c_y*c_y + c/l; let r = r_sqr.sqrt(); - return Geometry::Circle(-c_x, -c_y, r); + return _Geometry::Circle(-c_x, -c_y, r); } } -fn draw_line(context: &CanvasRenderingContext2d, line: &CGA) { +fn _draw_line(context: &CanvasRenderingContext2d, line: &CGA) { context.begin_path(); - context.set_stroke_style_str(GREEN); + context.set_stroke_style_str(_GREEN); - let geometry = get_geometry(line); + let geometry = _get_geometry(line); match geometry { - Geometry::Circle(x,y,r) => { + _Geometry::Circle(x,y,r) => { let (canvas_x, canvas_y) = point_to_screen_space(x, y); let canvas_r = r*RADIUS; context.arc(canvas_x, canvas_y, canvas_r, 0., TWO_PI).unwrap(); }, - Geometry::Line(x,y,theta) => { + _Geometry::Line(x,y,theta) => { let (p1x, p1y) = (x - theta.cos(), y - theta.sin()); let (p2x, p2y) = (x + theta.cos(), y + theta.sin()); let (canvas_x1, canvas_y1) = point_to_screen_space(p1x, p1y); let (canvas_x2, canvas_y2) = point_to_screen_space(p2x, p2y); - draw_point(&context, &point_to_cga(x,y)); + //draw_point(&context, &point_to_cga(x,y)); context.move_to(canvas_x1, canvas_y1); context.line_to(canvas_x2, canvas_y2); }, @@ -172,18 +195,16 @@ fn draw_line(context: &CanvasRenderingContext2d, line: &CGA) { context.stroke(); } -fn get_hyperbolic_translation(vec: &CGA) -> CGA { +fn _get_hyperbolic_translation(vec: &CGA) -> CGA { let vec_sqr = (vec * vec)[SCALAR]; if vec_sqr >= 1. { panic!("Translation Vector Out of Bounds"); } - let message = format!("Get TX\nvec {} \nvecvec {}\nvec_sqr {}", vec, vec*vec, vec_sqr); - console::log_1(&message.into()); let t = 1.0 / ( 1. - vec_sqr).sqrt() * ( CGA::new(1., SCALAR) + CGA::e5()*vec); t } -fn get_hyperbolic_point(vec: &CGA) -> CGA { +fn gen_hyperbolic_point(vec: &CGA) -> CGA { let vec_sqr = (vec * vec)[SCALAR]; if vec_sqr >= 1. { panic!("Vector out of bounds"); @@ -193,7 +214,36 @@ fn get_hyperbolic_point(vec: &CGA) -> CGA { p } -struct Asteroid { +fn bivector_exponential(bivector: &CGA) -> CGA { + //Explicitly take only the bivector part. + //The library doesn't provide a grade 2 selection. + let a = bivector[E12]; + let b = bivector[E13]; + let c = bivector[E14]; + let d = bivector[E15]; + let e = bivector[E23]; + let f = bivector[E24]; + let g = bivector[E25]; + let h = bivector[E34]; + let i = bivector[E35]; + let j = bivector[E45]; + + let bivector = a*CGA::e12() + b*CGA::e13() + c*CGA::e14() + + d*CGA::e15() + e*CGA::e23() + f*CGA::e24() + g*CGA::e25() + + h*CGA::e34() + i *CGA::e35() + j*CGA::e45(); + let sqr = (&bivector*&bivector)[SCALAR]; + if sqr.abs() < ERROR { + CGA::new(1., SCALAR) + bivector + } else if sqr < 0. { + let theta = sqr.abs().sqrt(); + CGA::new(theta.cos(), SCALAR) + theta.sin()/theta*bivector //Division by theta to normalize bivector + } else { + let t = sqr.sqrt(); + CGA::new(t.cosh(), SCALAR) + t.sinh()/t*bivector //Division by t to normalize the bivector + } +} + +struct _Asteroid { } #[wasm_bindgen] @@ -201,8 +251,7 @@ pub struct Game { context: CanvasRenderingContext2d, ship: Ship, //asteroids: Vec::, - point: (f64, f64), - //keys: HashSet, + keys: HashSet, } #[wasm_bindgen] @@ -222,8 +271,21 @@ impl Game { .ok_or_else( || JsValue::from_str("Failed to get context")).unwrap() .dyn_into::().unwrap(); - let ship = Ship { pos: get_hyperbolic_point(&CGA::zero()) }; - Game { context: context, point: (0.,0.), ship } + let tx_gen = CGA::e15(); + let ty_gen = CGA::e25(); + + let ship = Ship { + com: CGA::zero(), + verts: (-0.1*&tx_gen, 0.05*&tx_gen+0.02*&ty_gen, 0.05*&tx_gen-0.02*ty_gen), + vel: CGA::zero(), + orientation: CGA::zero() + }; + + Game { + context: context, + ship, + keys: HashSet::new() + } } pub fn draw(&self) { @@ -235,9 +297,20 @@ impl Game { self.context.arc(CENTER_X, CENTER_Y, RADIUS, 0., TWO_PI).unwrap(); self.context.stroke(); - let x = self.point.0; - let y = self.point.1; - draw_point(&self.context, &self.ship.pos); + let origin = gen_hyperbolic_point(&CGA::zero()); + let com = bivector_exponential(&self.ship.com); + let orient_rotor = bivector_exponential(&(0.5 * &self.ship.orientation)); + let v0_rotor = &com*&orient_rotor*bivector_exponential(&self.ship.verts.0); + let v1_rotor = &com*&orient_rotor*bivector_exponential(&self.ship.verts.1); + let v2_rotor = &com*&orient_rotor*bivector_exponential(&self.ship.verts.2); + let p0 = &v0_rotor*&origin*&v0_rotor.Reverse(); + let p1 = &v1_rotor*&origin*&v1_rotor.Reverse(); + let p2 = &v2_rotor*&origin*&v2_rotor.Reverse(); + + draw_point(&self.context, &(&com*&origin*&com.Reverse())); + draw_point(&self.context, &p0); + draw_point(&self.context, &p1); + draw_point(&self.context, &p2); //self.context.begin_path(); //self.context.set_fill_style_str(GREEN); @@ -246,18 +319,46 @@ impl Game { } - pub fn update(&mut self, dt: f64) { //dt in milliseconds - let theta:f64 = 0.1; - let vel:f64 = 1.*dt/1000.; - let dir = theta.cos()*CGA::e1() + theta.sin()*CGA::e2(); - let vec = vel* &dir; + pub fn update(&mut self, dt_m: f64) { + let dt_s = if dt_m > 100. { + 0.1 + } else { + dt_m / 1000. + }; + let vel = self.ship.vel.clone(); + let mut vel_2 = self.ship.vel.clone(); - let tx = get_hyperbolic_translation(&vec); - let txr = tx.Reverse(); - self.ship.pos = &tx * &self.ship.pos * &txr; + let mut a_vec = - DRAG * &vel_2; - let norm = (&self.ship.pos | CGA::e4() )[SCALAR]; - self.ship.pos = &self.ship.pos * (1.0 / norm); + if self.keys.contains("w") { + let orient = bivector_exponential(&(0.5*&self.ship.orientation)); + let accel = &orient * ACCEL_STR * CGA::e15() * orient.Reverse(); + a_vec = a_vec - accel; + } + + if self.keys.contains("q") { + self.ship.orientation = &self.ship.orientation + 1. * dt_s * CGA::e12(); + } + + if self.keys.contains("e") { + self.ship.orientation = &self.ship.orientation - 1. * dt_s * CGA::e12(); + } + + + vel_2 = vel_2 + a_vec*dt_s; + let vel_3 = 0.5*(vel+&vel_2); + let delta = vel_3 * dt_s; + let pos = &self.ship.com + delta; + self.ship.vel = vel_2; + self.ship.com = pos; + } + + pub fn key_down(&mut self, key: String) { + self.keys.insert(key); + } + + pub fn key_up(&mut self, key: String) { + self.keys.remove(&key); } } @@ -278,7 +379,7 @@ pub fn start() -> Result<(), JsValue> { .dyn_into::()?; canvas.set_width(WIDTH as u32); - canvas.set_height(HEIGHT as u32); +canvas.set_height(HEIGHT as u32); let context = canvas .get_context("2d")?