591 lines
19 KiB
Rust
591 lines
19 KiB
Rust
// 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.0001;
|
|
|
|
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: (CGA, CGA, CGA),
|
|
vel: CGA,
|
|
orientation: CGA,
|
|
}
|
|
|
|
impl Ship {
|
|
fn update(&mut self, dt_s: f64, keys: &HashSet<String>) {
|
|
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 orient = &self.orientation; //Transforms body frame to local frame
|
|
let pos = &self.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 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.
|
|
self.vel = vel_2;
|
|
self.com_rotor = pos;
|
|
|
|
}
|
|
|
|
fn draw(&self, context: &CanvasRenderingContext2d) {
|
|
let origin = gen_hyperbolic_point(&CGA::zero()); //Origin in the body frame.
|
|
let orient_rotor = &self.orientation; //Transforms body frame to local frame.
|
|
let com = &self.com_rotor; //Transforms local frame to world frame.
|
|
let v0_rotor = com*orient_rotor*bivector_exponential(&self.verts.0); //Combined transformation to locate a vertex of the ship in the world frame.
|
|
let v1_rotor = com*orient_rotor*bivector_exponential(&self.verts.1); //Combined transformation ...
|
|
let v2_rotor = com*orient_rotor*bivector_exponential(&self.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(context, &(com*&origin*com.Reverse()));
|
|
draw_point(context, &p0);
|
|
draw_point(context, &p1);
|
|
draw_point(context, &p2);
|
|
draw_line_between(context, &p0, &p1);
|
|
draw_line_between(context, &p1, &p2);
|
|
draw_line_between(context, &p2, &p0);
|
|
|
|
//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(context, &line);
|
|
|
|
|
|
}
|
|
}
|
|
|
|
enum Geometry {
|
|
Circle(f64, f64, f64), //x,y,r
|
|
Line(f64, f64, f64), //x,r,theta
|
|
Null,
|
|
}
|
|
|
|
/* 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.set_fill_style_str(BLUE);
|
|
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 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(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 l = (&dual | &n_vec)[SCALAR];
|
|
let c = (&dual | &n_bar)[SCALAR];
|
|
let a = (&dual | CGA::e1())[SCALAR];
|
|
let b = (&dual | CGA::e2())[SCALAR];
|
|
|
|
if l.abs() < ERROR {
|
|
//LINE
|
|
let theta = (-a).atan2(b);
|
|
let norm_sqr = a*a + b*b;
|
|
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);
|
|
} 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);
|
|
}
|
|
}
|
|
|
|
fn draw_line(context: &CanvasRenderingContext2d, line: &CGA) {
|
|
context.begin_path();
|
|
context.set_stroke_style_str(GREEN);
|
|
|
|
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);
|
|
},
|
|
Geometry::Null => {},
|
|
}
|
|
context.stroke();
|
|
}
|
|
|
|
fn draw_line_between(context: &CanvasRenderingContext2d, a: &CGA, b: &CGA) {
|
|
let line = CGA::e4() ^ a ^ b ^ CGA::e3();
|
|
let geometry = get_geometry(&line);
|
|
context.begin_path();
|
|
context.set_stroke_style_str(GREEN);
|
|
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
|
|
}
|
|
|
|
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 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 {
|
|
circle: CGA,
|
|
vel: CGA,
|
|
}
|
|
|
|
impl Asteroid {
|
|
fn new() -> Asteroid {
|
|
let v = 0.1;
|
|
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();
|
|
Self::new_from_coords(0.0_f64, 0.0_f64, 0.1_f64, vel)
|
|
}
|
|
|
|
fn new_from_coords(x: f64, y: f64, r: f64, vel: CGA) -> Asteroid {
|
|
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^CGA::e3();
|
|
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::<Asteroid>,
|
|
keys: HashSet<String>,
|
|
}
|
|
|
|
#[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::<HtmlCanvasElement>().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::<CanvasRenderingContext2d>().unwrap();
|
|
|
|
let tx_gen = CGA::e15();
|
|
let ty_gen = CGA::e25();
|
|
|
|
let ship = Ship {
|
|
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::new(1., SCALAR),
|
|
};
|
|
|
|
Game {
|
|
context: context,
|
|
ship,
|
|
asteroids: vec![Asteroid::new()],
|
|
keys: HashSet::new(),
|
|
}
|
|
}
|
|
|
|
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();
|
|
self.ship.draw(&self.context);
|
|
for asteroid in &self.asteroids {
|
|
asteroid.draw(&self.context);
|
|
}
|
|
}
|
|
|
|
pub fn update(&mut self, dt_m: f64) {
|
|
let dt_s = if dt_m > 100. {
|
|
0.1
|
|
} else {
|
|
dt_m / 1000.
|
|
};
|
|
|
|
for asteroid in &mut self.asteroids {
|
|
asteroid.update(dt_s);
|
|
}
|
|
|
|
self.ship.update(dt_s, &self.keys);
|
|
}
|
|
|
|
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::<HtmlCanvasElement>()?;
|
|
|
|
canvas.set_width(WIDTH as u32);
|
|
canvas.set_height(HEIGHT as u32);
|
|
|
|
let context = canvas
|
|
.get_context("2d")?
|
|
.unwrap()
|
|
.dyn_into::<CanvasRenderingContext2d>().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(())
|
|
}
|