have a ship that can move on the screen.
This commit is contained in:
@@ -13,6 +13,13 @@ async function run() {
|
|||||||
let game = new Game("game-canvas");
|
let game = new Game("game-canvas");
|
||||||
let last = performance.now();
|
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) {
|
function loop(now) {
|
||||||
const dt = (now - last);
|
const dt = (now - last);
|
||||||
last = now;
|
last = now;
|
||||||
|
|||||||
215
src/lib.rs
215
src/lib.rs
@@ -1,4 +1,5 @@
|
|||||||
use std::f64;
|
use std::f64;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
use wasm_bindgen::JsCast;
|
use wasm_bindgen::JsCast;
|
||||||
@@ -10,7 +11,7 @@ use cga::CGA;
|
|||||||
|
|
||||||
const PI: f64 = f64::consts::PI;
|
const PI: f64 = f64::consts::PI;
|
||||||
const TWO_PI: f64 = 2.*PI;
|
const TWO_PI: f64 = 2.*PI;
|
||||||
const ERROR: f64 = 0.0001;
|
const ERROR: f64 = 0.000001;
|
||||||
|
|
||||||
const WIDTH: f64 = 600.;
|
const WIDTH: f64 = 600.;
|
||||||
const HEIGHT: f64 = 600.;
|
const HEIGHT: f64 = 600.;
|
||||||
@@ -21,7 +22,7 @@ const RADIUS: f64 = 250.;
|
|||||||
const BACKGROUND: &str = "#ffffff";
|
const BACKGROUND: &str = "#ffffff";
|
||||||
const BLACK: &str = "#000000";
|
const BLACK: &str = "#000000";
|
||||||
const RED: &str = "#ff0000";
|
const RED: &str = "#ff0000";
|
||||||
const GREEN: &str = "#00ff00";
|
const _GREEN: &str = "#00ff00";
|
||||||
const _BLUE: &str = "#0000ff";
|
const _BLUE: &str = "#0000ff";
|
||||||
|
|
||||||
const SCALAR: usize = 0;
|
const SCALAR: usize = 0;
|
||||||
@@ -30,16 +31,16 @@ const E2: usize = 2;
|
|||||||
//const E3: usize = 3;
|
//const E3: usize = 3;
|
||||||
//const E4: usize = 4;
|
//const E4: usize = 4;
|
||||||
//const E5: usize = 5;
|
//const E5: usize = 5;
|
||||||
//const E12: usize = 6;
|
const E12: usize = 6;
|
||||||
//const E13: usize = 7;
|
const E13: usize = 7;
|
||||||
//const E14: usize = 8;
|
const E14: usize = 8;
|
||||||
//const E15: usize = 9;
|
const E15: usize = 9;
|
||||||
//const E23: usize = 10;
|
const E23: usize = 10;
|
||||||
//const E24: usize = 11;
|
const E24: usize = 11;
|
||||||
//const E25: usize = 12;
|
const E25: usize = 12;
|
||||||
//const E34: usize = 13;
|
const E34: usize = 13;
|
||||||
//const E35: usize = 14;
|
const E35: usize = 14;
|
||||||
//const E45: usize = 15;
|
const E45: usize = 15;
|
||||||
//const E123: usize = 16;
|
//const E123: usize = 16;
|
||||||
//const E124: usize = 17;
|
//const E124: usize = 17;
|
||||||
//const E125: usize = 18;
|
//const E125: usize = 18;
|
||||||
@@ -57,18 +58,23 @@ const E2: usize = 2;
|
|||||||
//const E2345: usize = 30;
|
//const E2345: usize = 30;
|
||||||
//const E12345: usize = 31;
|
//const E12345: usize = 31;
|
||||||
|
|
||||||
|
const DRAG: f64 = 1.;
|
||||||
|
const ACCEL_STR: f64 = 1.;
|
||||||
|
|
||||||
struct Ship {
|
struct Ship {
|
||||||
pos: CGA,
|
com: CGA,
|
||||||
//_vel: CGA,
|
verts: (CGA, CGA, CGA),
|
||||||
//_orientation: CGA,
|
vel: CGA,
|
||||||
|
orientation: CGA,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Geometry {
|
enum _Geometry {
|
||||||
Circle(f64, f64, f64), //x,y,r
|
Circle(f64, f64, f64), //x,y,r
|
||||||
Line(f64, f64, f64) //x,r,theta
|
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 x_vec = CGA::e1();
|
||||||
let y_vec = CGA::e2();
|
let y_vec = CGA::e2();
|
||||||
let _z_vec =CGA::e3(); //This game assumes everything takes place in the plane.
|
let _z_vec =CGA::e3(); //This game assumes everything takes place in the plane.
|
||||||
@@ -97,7 +103,7 @@ fn point_to_screen_space(x: f64, y: f64) -> (f64, f64) {
|
|||||||
|
|
||||||
fn draw_point(context: &CanvasRenderingContext2d, point: &CGA) {
|
fn draw_point(context: &CanvasRenderingContext2d, point: &CGA) {
|
||||||
//Naively assume we are given a valid point
|
//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);
|
let (x,y) = point_to_screen_space(x, y);
|
||||||
|
|
||||||
context.begin_path();
|
context.begin_path();
|
||||||
@@ -107,18 +113,35 @@ fn draw_point(context: &CanvasRenderingContext2d, point: &CGA) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//TODO Integrate into get geometry
|
//TODO Integrate into get geometry
|
||||||
fn get_point(point: &CGA) -> (f64, f64) {
|
fn get_hyperbolic_point(point: &CGA) -> (f64, f64) {
|
||||||
let e_vec = CGA::e4();
|
let n_bar = CGA::e4() - CGA::e5();
|
||||||
let e_bar = CGA::e5();
|
|
||||||
let n_vec = &e_vec + &e_bar; //Null vector for the point at infinity.
|
|
||||||
|
|
||||||
let normalization = -(point | &n_vec)[SCALAR];
|
//Construct r hat dir for the point.
|
||||||
let x = point[E1] / normalization;
|
let mut x = point[E1];
|
||||||
let y = point[E2] / normalization;
|
let mut y = point[E2];
|
||||||
(x,y)
|
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.);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_geometry(circle: &CGA) -> Geometry {
|
//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 {
|
||||||
//Naively assume we are given a valid line/circle
|
//Naively assume we are given a valid line/circle
|
||||||
let n_bar = CGA::e4() - CGA::e5();
|
let n_bar = CGA::e4() - CGA::e5();
|
||||||
let n_vec = 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 x = a*c/norm_sqr/2.;
|
||||||
let y = b*c/norm_sqr/2.;
|
let y = b*c/norm_sqr/2.;
|
||||||
return Geometry::Line(x,y,theta);
|
return _Geometry::Line(x,y,theta);
|
||||||
} else {
|
} else {
|
||||||
//Circle
|
//Circle
|
||||||
let c_x = a/l;
|
let c_x = a/l;
|
||||||
let c_y = b/l;
|
let c_y = b/l;
|
||||||
let r_sqr = c_x*c_x + c_y*c_y + c/l;
|
let r_sqr = c_x*c_x + c_y*c_y + c/l;
|
||||||
let r = r_sqr.sqrt();
|
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.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 {
|
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_x, canvas_y) = point_to_screen_space(x, y);
|
||||||
let canvas_r = r*RADIUS;
|
let canvas_r = r*RADIUS;
|
||||||
context.arc(canvas_x, canvas_y, canvas_r, 0., TWO_PI).unwrap();
|
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 (p1x, p1y) = (x - theta.cos(), y - theta.sin());
|
||||||
let (p2x, p2y) = (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_x1, canvas_y1) = point_to_screen_space(p1x, p1y);
|
||||||
let (canvas_x2, canvas_y2) = point_to_screen_space(p2x, p2y);
|
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.move_to(canvas_x1, canvas_y1);
|
||||||
context.line_to(canvas_x2, canvas_y2);
|
context.line_to(canvas_x2, canvas_y2);
|
||||||
},
|
},
|
||||||
@@ -172,18 +195,16 @@ fn draw_line(context: &CanvasRenderingContext2d, line: &CGA) {
|
|||||||
context.stroke();
|
context.stroke();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_hyperbolic_translation(vec: &CGA) -> CGA {
|
fn _get_hyperbolic_translation(vec: &CGA) -> CGA {
|
||||||
let vec_sqr = (vec * vec)[SCALAR];
|
let vec_sqr = (vec * vec)[SCALAR];
|
||||||
if vec_sqr >= 1. {
|
if vec_sqr >= 1. {
|
||||||
panic!("Translation Vector Out of Bounds");
|
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);
|
let t = 1.0 / ( 1. - vec_sqr).sqrt() * ( CGA::new(1., SCALAR) + CGA::e5()*vec);
|
||||||
t
|
t
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_hyperbolic_point(vec: &CGA) -> CGA {
|
fn gen_hyperbolic_point(vec: &CGA) -> CGA {
|
||||||
let vec_sqr = (vec * vec)[SCALAR];
|
let vec_sqr = (vec * vec)[SCALAR];
|
||||||
if vec_sqr >= 1. {
|
if vec_sqr >= 1. {
|
||||||
panic!("Vector out of bounds");
|
panic!("Vector out of bounds");
|
||||||
@@ -193,7 +214,36 @@ fn get_hyperbolic_point(vec: &CGA) -> CGA {
|
|||||||
p
|
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]
|
#[wasm_bindgen]
|
||||||
@@ -201,8 +251,7 @@ pub struct Game {
|
|||||||
context: CanvasRenderingContext2d,
|
context: CanvasRenderingContext2d,
|
||||||
ship: Ship,
|
ship: Ship,
|
||||||
//asteroids: Vec::<Asteroid>,
|
//asteroids: Vec::<Asteroid>,
|
||||||
point: (f64, f64),
|
keys: HashSet<String>,
|
||||||
//keys: HashSet<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
@@ -222,8 +271,21 @@ impl Game {
|
|||||||
.ok_or_else( || JsValue::from_str("Failed to get context")).unwrap()
|
.ok_or_else( || JsValue::from_str("Failed to get context")).unwrap()
|
||||||
.dyn_into::<CanvasRenderingContext2d>().unwrap();
|
.dyn_into::<CanvasRenderingContext2d>().unwrap();
|
||||||
|
|
||||||
let ship = Ship { pos: get_hyperbolic_point(&CGA::zero()) };
|
let tx_gen = CGA::e15();
|
||||||
Game { context: context, point: (0.,0.), ship }
|
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) {
|
pub fn draw(&self) {
|
||||||
@@ -235,9 +297,20 @@ 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 x = self.point.0;
|
let origin = gen_hyperbolic_point(&CGA::zero());
|
||||||
let y = self.point.1;
|
let com = bivector_exponential(&self.ship.com);
|
||||||
draw_point(&self.context, &self.ship.pos);
|
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.begin_path();
|
||||||
//self.context.set_fill_style_str(GREEN);
|
//self.context.set_fill_style_str(GREEN);
|
||||||
@@ -246,18 +319,46 @@ impl Game {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, dt: f64) { //dt in milliseconds
|
pub fn update(&mut self, dt_m: f64) {
|
||||||
let theta:f64 = 0.1;
|
let dt_s = if dt_m > 100. {
|
||||||
let vel:f64 = 1.*dt/1000.;
|
0.1
|
||||||
let dir = theta.cos()*CGA::e1() + theta.sin()*CGA::e2();
|
} else {
|
||||||
let vec = vel* &dir;
|
dt_m / 1000.
|
||||||
|
};
|
||||||
|
let vel = self.ship.vel.clone();
|
||||||
|
let mut vel_2 = self.ship.vel.clone();
|
||||||
|
|
||||||
let tx = get_hyperbolic_translation(&vec);
|
let mut a_vec = - DRAG * &vel_2;
|
||||||
let txr = tx.Reverse();
|
|
||||||
self.ship.pos = &tx * &self.ship.pos * &txr;
|
|
||||||
|
|
||||||
let norm = (&self.ship.pos | CGA::e4() )[SCALAR];
|
if self.keys.contains("w") {
|
||||||
self.ship.pos = &self.ship.pos * (1.0 / norm);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user