diff --git a/src/lib.rs b/src/lib.rs index 6ec05d7..9b86446 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,22 @@ +// 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; @@ -11,7 +30,7 @@ use cga::CGA; const PI: f64 = f64::consts::PI; const TWO_PI: f64 = 2.*PI; -const ERROR: f64 = 0.000001; +const ERROR: f64 = 0.0001; const WIDTH: f64 = 600.; const HEIGHT: f64 = 600.; @@ -62,15 +81,16 @@ const DRAG: f64 = 1.; const ACCEL_STR: f64 = 1.; struct Ship { - com: CGA, + com_rotor: 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 + Line(f64, f64, f64), //x,r,theta + Null, } /* Translation of a Euclidean Point. Need to use hyperbolic*/ @@ -141,11 +161,11 @@ fn get_hyperbolic_point(point: &CGA) -> (f64, f64) { (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(); - let dual = circle.Dual(); + let dual = (&circle).Dual(); let l = (&dual | &n_vec)[SCALAR]; let c = (&dual | &n_bar)[SCALAR]; let a = (&dual | CGA::e1())[SCALAR]; @@ -155,34 +175,34 @@ fn _get_geometry(circle: &CGA) -> _Geometry { //LINE let theta = (-a).atan2(b); let norm_sqr = a*a + b*b; - if norm_sqr < ERROR { - panic!("Not a line or circle"); + if norm_sqr < ERROR*ERROR { + return Geometry::Null; } 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); - 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); @@ -191,6 +211,7 @@ fn _draw_line(context: &CanvasRenderingContext2d, line: &CGA) { context.move_to(canvas_x1, canvas_y1); context.line_to(canvas_x2, canvas_y2); }, + Geometry::Null => {}, } context.stroke(); } @@ -275,16 +296,16 @@ impl Game { let ty_gen = CGA::e25(); let ship = Ship { - com: CGA::zero(), + com_rotor: CGA::new(1., SCALAR), 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() + vel: CGA::zero(), + orientation: CGA::new(1., SCALAR), }; Game { - context: context, + context: context, ship, - keys: HashSet::new() + keys: HashSet::new(), } } @@ -297,26 +318,27 @@ impl Game { self.context.arc(CENTER_X, CENTER_Y, RADIUS, 0., TWO_PI).unwrap(); self.context.stroke(); - 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(); + let origin = gen_hyperbolic_point(&CGA::zero()); //Origin in the body frame. + let orient_rotor = &self.ship.orientation; //Transforms body frame to local frame. + let com = &self.ship.com_rotor; //Transforms local frame to world frame. + let v0_rotor = com*orient_rotor*bivector_exponential(&self.ship.verts.0); //Combined transformation to locate a vertex of the ship in the world frame. + let v1_rotor = com*orient_rotor*bivector_exponential(&self.ship.verts.1); //Combined transformation ... + let v2_rotor = com*orient_rotor*bivector_exponential(&self.ship.verts.2); //Combined transformation ... + let p0 = &v0_rotor*&origin*&v0_rotor.Reverse(); //Produce a vertex of the ship in the world frame. + 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, &(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); - //self.context.arc(x, y, 20., 0., TWO_PI).unwrap(); - //self.context.fill(); - + //Construct and draw line pointing straight ahead, to help orient the player. + let com_p = com * gen_hyperbolic_point(&CGA::zero()) * com.Reverse(); + let delta_gen = com * orient_rotor * CGA::e14() * &orient_rotor.Reverse() * com.Reverse(); + let dot = delta_gen | &com_p; + let line = CGA::e4() ^ com_p ^ dot ^ CGA::e3(); + draw_line(&self.context, &line); } pub fn update(&mut self, dt_m: f64) { @@ -325,32 +347,35 @@ impl Game { } else { dt_m / 1000. }; - let vel = self.ship.vel.clone(); + let vel = self.ship.vel.clone(); //Velocity at beginning of frame let mut vel_2 = self.ship.vel.clone(); let mut a_vec = - DRAG * &vel_2; 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; + let orient = &self.ship.orientation; //Transforms body frame to local frame + let pos = &self.ship.com_rotor; //Transforms local frame to world frame + let accel_body = ACCEL_STR * CGA::e15(); //The acceleration in the body frame. + let accel_local = orient * accel_body * orient.Reverse(); //The acceleration in the local frame. + let accel_world = pos * accel_local * pos.Reverse(); //The acceleration in the world frame. + a_vec = a_vec - accel_world; //Sum of drag and thrust. } if self.keys.contains("q") { - self.ship.orientation = &self.ship.orientation + 1. * dt_s * CGA::e12(); + self.ship.orientation = bivector_exponential(&(dt_s * CGA::e12())) * &self.ship.orientation; } if self.keys.contains("e") { - self.ship.orientation = &self.ship.orientation - 1. * dt_s * CGA::e12(); + self.ship.orientation = bivector_exponential(&(-dt_s * CGA::e12())) * &self.ship.orientation; } - vel_2 = vel_2 + a_vec*dt_s; + 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 = &self.ship.com + delta; + let pos = bivector_exponential(&delta)*&self.ship.com_rotor; //Update position. self.ship.vel = vel_2; - self.ship.com = pos; + self.ship.com_rotor = pos; } pub fn key_down(&mut self, key: String) {