// Some notes on terminology as used in this project: // // Body frame (or body-fixed frame): // - A coordinate frame that moves and rotates with the body. // - The origin of the body is fixed at the origin of this frame, and points on the body // have constant coordinates relative to the origin. For example, the ship's nose // is always at a fixed distance along the x-axis in this frame. // // Local frame (or co-moving frame): // - A coordinate frame that moves with the object's position (e.g., center of mass) // but does not rotate with it. // - Points on the body may rotate relative to this frame as the object rotates in space. // // World frame (or inertial frame): // - A fixed coordinate frame that does not move or rotate with the body. // - All positions and orientations of the object are expressed relative to this absolute frame. // // This should be consistent with standard terminology. use std::f64; use std::collections::HashSet; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement}; use web_sys::console; mod cga; use cga::CGA; const PI: f64 = f64::consts::PI; const TWO_PI: f64 = 2.*PI; const ERROR: f64 = 0.000001; const WIDTH: f64 = 600.; const HEIGHT: f64 = 600.; const CENTER_X: f64 = 300.; const CENTER_Y: f64 = 300.; const RADIUS: f64 = 250.; const BACKGROUND: &str = "#ffffff"; const BLACK: &str = "#000000"; const RED: &str = "#ff0000"; const GREEN: &str = "#00ff00"; const BLUE: &str = "#0000ff"; const SCALAR: usize = 0; const E1: usize = 1; 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 E123: usize = 16; const E124: usize = 17; const E125: usize = 18; const E134: usize = 19; const E135: usize = 20; const E145: usize = 21; const E234: usize = 22; const E235: usize = 23; const E245: usize = 24; const E345: usize = 25; const E1234: usize = 26; const E1235: usize = 27; const E1245: usize = 28; const E1345: usize = 29; const E2345: usize = 30; const E12345: usize = 31; const DRAG: f64 = 1.; const ACCEL_STR: f64 = 1.; struct Ship { com_rotor: CGA, verts: Vec::, vel: CGA, orientation: CGA, } impl Ship { 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(); let mut a_vec = - DRAG * &vel_2; if keys.contains("w") { let accel_world = self.fixed_to_world(&(ACCEL_STR * CGA::e15())); a_vec = a_vec - accel_world; //Sum of drag and thrust. } if keys.contains("q") { self.orientation = bivector_exponential(&(dt_s * CGA::e12())) * &self.orientation; } if keys.contains("e") { 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. let old = self.com_rotor.clone(); self.com_rotor = pos; self.vel = vel_2; if self.intersects_circle(boundary) { let pos = self.fixed_to_world(&gen_hyperbolic_point(&CGA::zero())); let x = pos[E1]; let y = pos[E2]; let r_sqr = x*x+y*y; let r = r_sqr.sqrt(); let clamp = x/r *CGA::e15() + y/r*CGA::e25(); let rotor = bivector_exponential(&(clamp*0.01)); self.com_rotor = rotor * 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 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, &p1); draw_point(context, &p2); draw_point(context, &p3); draw_line_between(context, &p1, &p2); 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()); let dot = delta_gen | &com; let line = CGA::e4() ^ com ^ dot; draw_line(context, &line); } 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 { Circle(f64, f64, f64), //x,y,r Line(f64, f64, f64), //x,y,theta } /* 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. let e_vec = CGA::e4(); let e_bar = CGA::e5(); let n_vec = &e_vec + &e_bar; //Null vector for the point at infinity. let n_bar = &e_vec - &e_bar; //Null vector for the origin. let mut ux = x; let mut uy = y; let mut mag_sqr = ux*ux + uy*uy; if mag_sqr > 1. { ux /= mag_sqr.sqrt(); uy /= mag_sqr.sqrt(); mag_sqr = 1.; } 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; let screen_y = y*RADIUS + CENTER_Y; (screen_x, screen_y) } fn draw_point(context: &CanvasRenderingContext2d, point: &CGA) { //Naively assume we are given a valid point let (x,y) = get_hyperbolic_point(point); let (x,y) = point_to_screen_space(x, y); context.begin_path(); context.set_fill_style_str(RED); context.arc(x, y, 2., 0., TWO_PI).unwrap(); context.fill(); let (x,y) = get_point(point); let (x,y) = point_to_screen_space(x,y); context.begin_path(); context.arc(x, y, 2., 0., TWO_PI).unwrap(); context.fill(); } //TODO Integrate into get geometry fn get_hyperbolic_point(point: &CGA) -> (f64, f64) { let n_bar = CGA::e4() - CGA::e5(); //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 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]; let n = CGA::e4()+CGA::e5(); let norm = -(point | &n)[SCALAR]; (x/norm, y/norm) } fn get_geometry(trivector: &CGA) -> Geometry { let trivector = grade_selection(trivector, 3); let i4 = CGA::e1() ^ CGA::e2() ^ CGA::e4() ^ CGA::e5(); //Left out e3 intentionally let i2 = CGA::e1() ^ CGA::e2(); let n_bar = CGA::e4() - CGA::e5(); let n_vec = CGA::e4() + CGA::e5(); let wedge = &trivector ^ &n_vec; let mag = magnitude(&wedge); let dual = &trivector * &i4; if mag < ERROR { //Line let d = 0.5*(&dual | &n_bar)[SCALAR]; let m = -1.*(&dual - 0.5*d*&n_vec)*&i2; let theta = (m[E2]).atan2(m[E1]); return Geometry::Line(0., -d/m[E1], theta); } else { //Circle let normalize = -(&dual | &n_vec)[SCALAR]; let dual = dual * (1.0/normalize); let center = &trivector * &n_vec * &trivector; let (x,y) = get_point(¢er); let sqr = (&dual * &dual)[SCALAR]; let r = sqr.sqrt(); return Geometry::Circle(x, y, r); } } fn draw_line(context: &CanvasRenderingContext2d, line: &CGA) { context.begin_path(); let geometry = get_geometry(line); match geometry { 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) => { 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)); context.move_to(canvas_x1, canvas_y1); context.line_to(canvas_x2, canvas_y2); }, } context.stroke(); } fn draw_line_between(context: &CanvasRenderingContext2d, a: &CGA, b: &CGA) { let line = CGA::e4() ^ a ^ b; let geometry = get_geometry(&line); context.begin_path(); match geometry { Geometry::Circle(x,y,r) => { let (a_x, a_y) = get_point(a); let (b_x, b_y) = get_point(b); let mut theta_a = (a_y-y).atan2(a_x-x); let mut theta_b = (b_y-y).atan2(b_x-x); if theta_a < 0. { theta_a += TWO_PI; } if theta_b < 0. { theta_b += TWO_PI; } let mut delta = theta_b - theta_a; if delta < -PI { delta += TWO_PI; } else if delta > PI { delta -= TWO_PI; } let (x,y) = point_to_screen_space(x,y); let r = RADIUS*r; if delta < 0. { if theta_a + delta < 0. { context.arc(x, y, r, 0., theta_a).unwrap(); context.arc(x, y, r, theta_a + delta + TWO_PI, TWO_PI).unwrap(); } else { context.arc(x, y, r, theta_a + delta, theta_a).unwrap(); } } else { if theta_a + delta > TWO_PI { context.arc(x, y, r, theta_a, TWO_PI).unwrap(); context.arc(x, y, r, 0., theta_a + delta - TWO_PI).unwrap(); } else { context.arc(x, y, r, theta_a, theta_a + delta).unwrap(); } } }, _ => {}, } context.stroke(); } fn _get_hyperbolic_translation(vec: &CGA) -> CGA { let vec_sqr = (vec * vec)[SCALAR]; if vec_sqr >= 1. { panic!("Translation Vector Out of Bounds"); } let t = 1.0 / ( 1. - vec_sqr).sqrt() * ( CGA::new(1., SCALAR) + CGA::e5()*vec); t } enum CircleIntersection { TwoPoints(CGA, CGA), //OnePoint(CGA), None, } fn extract_point_pair(pair: &CGA) -> (CGA, CGA) { let n_vec = CGA::e4() + CGA::e5(); let pair = grade_selection(&pair, 2); //Examine only the bivector part let f = pair.normalized(); let p = CGA::new(0.5, SCALAR) + 0.5*&f; //Projection Operator let p_rev = CGA::new(0.5, SCALAR) - 0.5*&f; //Projection Operator let diff = &pair | &n_vec; //point A - point B (or a scalar multiple) let a = -1. * &p_rev * &diff; let b = 1. * &p * &diff; (a,b) } fn circle_intersection(circle_1: &CGA, circle_2: &CGA) -> CircleIntersection { //Assumes circles intersect in two places. let circle_1 = grade_selection(circle_1, 3); //Explicitly check only trivector part let circle_2 = grade_selection(circle_2, 3); let i4 = CGA::e1245(); //xy plane in 3D CGA. let ab = &circle_1 * &circle_2; let ba = &circle_2 * &circle_1; let comm = &ab - &ba; //Twice the commutator of circle_1 and circle_2. Unnormalized, but that isn't important. let inter = &i4 * &comm; //The bivector of the pair of intersection points. let sqr = (&inter * &inter)[0]; if sqr > ERROR { let (a,b) = extract_point_pair(&inter); CircleIntersection::TwoPoints(a,b) } else { CircleIntersection::None //Technically could have one intersection, but that's not tested yet } } fn _line_intersection(line_1: &CGA, line_2: &CGA) -> bool { let line_1 = grade_selection(line_1, 4); let line_2 = grade_selection(line_2, 4); let meet = line_1 & line_2; let mag = magnitude(&meet); if mag < ERROR { true } else { false } } fn _get_line_intersection(line_1: &CGA, line_2: &CGA) -> Option { if !_line_intersection(line_1, line_2) { return None; } let n = CGA::e4() + CGA::e5(); let x = CGA::up(0., 0., 0.); //Pick an arbitrary point let l_1_tick = line_2 * line_1 * line_2; //reflect line_1 by line_2 let l_1_2tick = line_1 - l_1_tick; //bisector of line_1 and its reflection let x_tick = &l_1_2tick * &x * & l_1_2tick; //reflection of x in the bisector let x_2tick = 0.5*x + 0.5*&x_tick; //midpoint, on the bisector let x_3tick = line_2*&x_2tick*line_2; //Reflection of midpoint in line_2 let p_tick = 0.5*&x_2tick + 0.5 * x_3tick; //Intersection point of lines, but with extra n component. let dot = &p_tick | &n; let dot_sqr = dot[SCALAR]*dot[SCALAR]; let p = -1.*&p_tick*&n*&p_tick*(0.5/dot_sqr); //Get the point with the correct n component. Some(p) } fn grade_selection(multi: &CGA, grade: usize) -> CGA { match grade { 0 => { CGA::new(multi[SCALAR], SCALAR) }, 1 => { multi[E1]*CGA::e1() + multi[E2]*CGA::e2() + multi[E3]*CGA::e3() + multi[E4]*CGA::e4() + multi[E5]*CGA::e5() }, 2 => { multi[E12]*CGA::e12() + multi[E13]*CGA::e13() + multi[E14]*CGA::e14() + multi[E15]*CGA::e15() + multi[E23]*CGA::e23() + multi[E24]*CGA::e24() + multi[E25]*CGA::e25() + multi[E34]*CGA::e34() + multi[E35]*CGA::e35() + multi[E45]*CGA::e45() }, 3 => { multi[E123]*CGA::e123() + multi[E124]*CGA::e124() + multi[E125]*CGA::e125() + multi[E134]*CGA::e134() + multi[E135]*CGA::e135() + multi[E145]*CGA::e145() + multi[E234]*CGA::e234() + multi[E235]*CGA::e235() + multi[E245]*CGA::e245() + multi[E345]*CGA::e345() }, 4 => { multi[E1234]*CGA::e1234() + multi[E1235]*CGA::e1235() + multi[E1245]*CGA::e1245() + multi[E1345]*CGA::e1345() + multi[E2345]*CGA::e2345() }, 5 => { multi[E12345]*CGA::e12345() } _ => { CGA::zero() } } } fn magnitude(multi: &CGA) -> f64 { multi[SCALAR]*multi[SCALAR] + multi[E1]*multi[E1] + multi[E2]*multi[E2] + multi[E3]*multi[E3] + multi[E4]*multi[E4] + multi[E5]*multi[E5] + multi[E12]*multi[E12] + multi[E13]*multi[E13] + multi[E14]*multi[E14] + multi[E15]*multi[E15] + multi[E23]*multi[E23] + multi[E24]*multi[E24] + multi[E25]*multi[E25] + multi[E34]*multi[E34] + multi[E35]*multi[E35] + multi[E45]*multi[E45] + multi[E123]*multi[E123] + multi[E124]*multi[E124] + multi[E125]*multi[E125] + multi[E134]*multi[E134] + multi[E135]*multi[E135] + multi[E145]*multi[E145] + multi[E234]*multi[E234] + multi[E235]*multi[E235] + multi[E245]*multi[E245] + multi[E345]*multi[E345] + multi[E1234]*multi[E1234] + multi[E1235]*multi[E1235] + multi[E1245]*multi[E1245] + multi[E1345]*multi[E1345] + multi[E2345]*multi[E2345] + multi[E12345]*multi[E12345] } fn gen_hyperbolic_point(vec: &CGA) -> CGA { let vec_sqr = (vec * vec)[SCALAR]; if vec_sqr >= 1. { panic!("Vector out of bounds"); } let n = CGA::e4() + CGA::e5(); let p = 1. / (1. - vec_sqr)*(vec_sqr*n/*CGA::new(vec_sqr, SCALAR)*/ + 2.*vec - (CGA::e4() - CGA::e5())); p } fn bivector_exponential(bivector: &CGA) -> CGA { //Explicitly take only the bivector part. //The library doesn't provide a grade 2 selection. let bivector = grade_selection(bivector, 2); 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 { circle: CGA, vel: CGA, } fn circle_from_coords(x: f64, y: f64, r: f64) -> CGA { let (a_x, a_y) = (x + r * 0.0_f64.cos(), y + r * 0.0_f64.sin()); let (b_x, b_y) = (x + r * 1.0_f64.cos(), y + r * 1.0_f64.sin()); let (c_x, c_y) = (x + r * 2.0_f64.cos(), y + r * 2.0_f64.sin()); let a = point_to_cga(a_x, a_y); let b = point_to_cga(b_x, b_y); let c = point_to_cga(c_x, c_y); let circle = a^b^c; circle } impl Asteroid { fn new() -> 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.5, 0., 0.4); Asteroid { circle, vel } } fn _new_from_coords(x: f64, y: f64, r: f64, vel: CGA) -> Asteroid { let circle = circle_from_coords(x, y, r); Asteroid { circle, vel } } fn update(&mut self, dt_s: f64) { let vel = &self.vel; let delta = vel * dt_s; let vel_rotor = bivector_exponential(&delta); let circle = &vel_rotor*&self.circle*&vel_rotor.Reverse(); self.circle = circle; } fn draw(&self, context: &CanvasRenderingContext2d) { draw_line(context, &self.circle); } } #[wasm_bindgen] pub struct Game { context: CanvasRenderingContext2d, ship: Ship, asteroids: Vec::, boundary: CGA, keys: HashSet, dt: f64 } #[wasm_bindgen] impl Game { #[wasm_bindgen(constructor)] pub fn new(canvas_id: &str) -> Self { let window = web_sys::window().unwrap(); let document = window.document().unwrap(); let canvas = document .get_element_by_id(canvas_id) .ok_or_else( || JsValue::from_str("Canvas not found")).unwrap() .dyn_into::().unwrap(); canvas.set_width(WIDTH as u32); canvas.set_height(HEIGHT as u32); let context = canvas .get_context("2d").unwrap() .ok_or_else( || JsValue::from_str("Failed to get context")).unwrap() .dyn_into::().unwrap(); let tx_gen = CGA::e15(); let ty_gen = CGA::e25(); let v1_gen = -0.1*&tx_gen; let v2_gen = 0.05*&tx_gen+0.02*&ty_gen; let v3_gen = 0.05*&tx_gen-0.02*&ty_gen; let v1_rot = bivector_exponential(&v1_gen); let v2_rot = bivector_exponential(&v2_gen); let v3_rot = bivector_exponential(&v3_gen); let origin = gen_hyperbolic_point(&CGA::zero()); let v1 = &v1_rot * &origin * &v1_rot.Reverse(); let v2 = &v2_rot * &origin * &v2_rot.Reverse(); let v3 = &v3_rot * &origin * &v3_rot.Reverse(); let ship = Ship { com_rotor: CGA::new(1., SCALAR), verts: vec![ v1, v2, v3], vel: CGA::zero(), orientation: CGA::new(1., SCALAR), }; let boundary = circle_from_coords(0., 0., 0.9); Game { context: context, ship, boundary, asteroids: vec![Asteroid::new()], keys: HashSet::new(), dt: 100. } } pub fn draw(&self) { self.context.begin_path(); self.context.set_fill_style_str(BACKGROUND); self.context.fill_rect(0.0, 0.0, WIDTH, HEIGHT); self.context.fill(); self.context.set_stroke_style_str(BLACK); 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); let mut intersects = false; self.context.set_stroke_style_str(RED); for asteroid in &self.asteroids { if self.ship.intersects_circle(&asteroid.circle) { intersects = true; } asteroid.draw(&self.context); } 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) { let dt_s = if dt_m > 100. { 0.1 } else { 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.boundary); } pub fn key_down(&mut self, key: String) { self.keys.insert(key); } pub fn key_up(&mut self, key: String) { self.keys.remove(&key); } } // Start entry point #[wasm_bindgen(start)] pub fn start() -> Result<(), JsValue> { // let _ship = Ship { _pos: point_to_cga(0.,0.), _vel: CGA::zero(), _orientation: CGA::zero() }; /* // Grab document and canvas let window = web_sys::window().unwrap(); let document = window.document().unwrap(); let canvas = document .get_element_by_id("game-canvas") .unwrap() .dyn_into::()?; canvas.set_width(WIDTH as u32); canvas.set_height(HEIGHT as u32); let context = canvas .get_context("2d")? .unwrap() .dyn_into::().unwrap(); //Set up play area. context.begin_path(); context.set_fill_style_str(BACKGROUND); context.fill_rect(0.0, 0.0, WIDTH, HEIGHT); context.fill(); context.set_stroke_style_str(BLACK); context.arc(CENTER_X, CENTER_Y, RADIUS, 0., TWO_PI).unwrap(); context.stroke(); */ /* //Testing example lines and circles. let point = point_to_cga(0.,0.); draw_point(&context, &point); let p1 = point_to_cga(0.9, 0.0); draw_point(&context, &p1); let p2 = point_to_cga(0.0, 0.9); draw_point(&context, &p2); let p3 = point_to_cga(0.7, 0.6); draw_point(&context, &p3); let circle = &p1 ^ &p2 ^ &p3 ^ &CGA::e3(); //wedge e3 to force into the plane draw_line(&context, &circle); let p1 = point_to_cga(0.9, 0.2); draw_point(&context, &p1); let p2 = point_to_cga(-0.9, -0.); draw_point(&context, &p2); let p3 = point_to_cga(0., 0.1); draw_point(&context, &p3); let circle = &p1 ^ &p2 ^ &p3 ^ &CGA::e3(); draw_line(&context, &circle); let p1 = point_to_cga(0.9, 0.1); draw_point(&context, &p1); let p2 = point_to_cga(-0.9, -0.1); draw_point(&context, &p2); let p3 = point_to_cga(0., 0.0); draw_point(&context, &p3); let circle = &p1 ^ &p2 ^ &p3 ^ &CGA::e3(); draw_line(&context, &circle); let p1 = point_to_cga(0.9, 0.); draw_point(&context, &p1); let p2 = point_to_cga(0.9, -0.1); draw_point(&context, &p2); let circle = &p1 ^ &p2 ^ &CGA::e4() ^ &CGA::e3(); //Wedge e4 to create a circle through p1 and p1 that is tangent to the unit circle. draw_line(&context, &circle); let p1 = point_to_cga(0.9, 0.); draw_point(&context, &p1); let p2 = point_to_cga(0.9, -0.1); draw_point(&context, &p2); let circle = &p1 ^ &p2 ^ &CGA::e5() ^ &CGA::e3(); //Orthogonal to the line at infinity draw_line(&context, &circle);*/ Ok(()) }