Created
November 18, 2021 00:24
-
-
Save agrif/3d0450a92924c8367736733450d58c2d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use angle::Rad; | |
use wasm_bindgen::prelude::*; | |
use wasm_bindgen::JsCast; | |
macro_rules! log { | |
( $( $t:tt )* ) => { | |
web_sys::console::log_1(&format!( $( $t )* ).into()); | |
} | |
} | |
type JsResult<T> = Result<T, wasm_bindgen::JsValue>; | |
#[derive(Clone, Debug)] | |
struct Orbit<F = f64> { | |
// eccentricity: 0 to infinity, unitless | |
e: F, | |
// semimajor axis: 0 to infinity, distance | |
a: F, | |
// argument of periapsis | |
w: Rad<F>, | |
// inclination | |
i: Rad<F>, | |
// longitude of the ascending node | |
o: Rad<F>, | |
// mean anomaly | |
m: Rad<F>, | |
// standard gravitational parameter, 0 to infinity, distance^3/time^2 | |
u: F, | |
} | |
impl<F> Orbit<F> | |
where | |
F: num_traits::Float + num_traits::FloatConst, | |
{ | |
// constructors | |
pub fn elliptical(r1: F, r2: F) -> Self { | |
let ratio = if r1 > r2 { r1 / r2 } else { r2 / r1 }; | |
let two = F::from(2.0).unwrap(); | |
let e = F::one() - two / (ratio + F::one()); | |
Orbit { | |
e: e, | |
a: (r1 + r2) / two, | |
w: Rad(F::zero()), | |
i: Rad(F::zero()), | |
o: Rad(F::zero()), | |
m: Rad(F::zero()), | |
u: F::zero(), | |
} | |
} | |
pub fn circular(r: F) -> Self { | |
Self::elliptical(r, r) | |
} | |
// properties | |
pub fn apoapsis(&self) -> F { | |
self.a * (F::one() + self.e) | |
} | |
pub fn argument_of_periapsis(&self) -> Rad<F> { | |
self.w | |
} | |
pub fn eccentricity(&self) -> F { | |
self.e | |
} | |
pub fn gravitational_parameter(&self) -> F { | |
self.u | |
} | |
pub fn inclination(&self) -> Rad<F> { | |
self.i | |
} | |
pub fn linear_eccentricity(&self) -> F { | |
self.a * self.e | |
} | |
pub fn longitude_of_ascending_node(&self) -> Rad<F> { | |
self.o | |
} | |
pub fn mean_anomaly(&self) -> Rad<F> { | |
self.m | |
} | |
pub fn mean_motion(&self) -> F { | |
(self.u * self.a.powi(3)).sqrt() | |
} | |
pub fn periapsis(&self) -> F { | |
self.a * (F::one() - self.e) | |
} | |
pub fn period(&self) -> F { | |
F::from(2.0).unwrap() * F::PI() / self.mean_motion() | |
} | |
pub fn semilatus_rectum(&self) -> F { | |
self.a * (F::one() - self.e.powi(2)) | |
} | |
pub fn semimajor_axis(&self) -> F { | |
self.a | |
} | |
pub fn semiminor_axis(&self) -> F { | |
self.a * (F::one() - self.e.powi(2)).sqrt() | |
} | |
} | |
struct Orrery { | |
ctx: web_sys::CanvasRenderingContext2d, | |
scale: f64, | |
scale_orbit: f64, | |
} | |
impl Orrery { | |
pub fn new(canvas: web_sys::HtmlCanvasElement) -> JsResult<Self> { | |
let width = canvas.width(); | |
let height = canvas.height(); | |
let scale = (width.min(height) as f64) / 2.0; | |
let ctx = canvas | |
.get_context("2d") | |
.unwrap() | |
.unwrap() | |
.dyn_into::<web_sys::CanvasRenderingContext2d>()?; | |
ctx.scale(scale, scale)?; | |
ctx.translate(1.0, 1.0)?; | |
ctx.set_line_width(1.0 / scale); | |
Ok(Orrery { ctx, scale, scale_orbit: 1.0 }) | |
} | |
pub fn set_scale(&mut self, scale: f64) { | |
self.scale_orbit = scale; | |
} | |
pub fn draw_orbit(&mut self, orbit: &Orbit) -> JsResult<()> { | |
self.ctx.save(); | |
self.ctx.scale(0.8 / self.scale_orbit, 0.8 / self.scale_orbit)?; | |
self.ctx.begin_path(); | |
self.ctx.ellipse( | |
orbit.linear_eccentricity(), | |
0.0, | |
orbit.semimajor_axis(), | |
orbit.semiminor_axis(), | |
0.0, | |
0.0, | |
2.0 * std::f64::consts::PI, | |
)?; | |
self.ctx.restore(); | |
self.ctx.stroke(); | |
Ok(()) | |
} | |
pub fn draw_planet(&mut self) -> JsResult<()> { | |
self.ctx.begin_path(); | |
self.ctx.arc(0.0, 0.0, | |
10.0 / self.scale, | |
0.0, 2.0 * std::f64::consts::PI)?; | |
self.ctx.fill(); | |
Ok(()) | |
} | |
} | |
#[wasm_bindgen(start)] | |
pub fn start() -> JsResult<()> { | |
let document = web_sys::window().unwrap().document() | |
.ok_or("could not get window")?; | |
let canvas = document.get_element_by_id("canvas") | |
.ok_or("could not get canvas")?; | |
let canvas = canvas.dyn_into::<web_sys::HtmlCanvasElement>()?; | |
let mut orr = Orrery::new(canvas)?; | |
let orb = Orbit::elliptical(100.0, 1000.0); | |
let orb2 = Orbit::circular(orb.apoapsis()); | |
log!("orbit: {:?}", orb); | |
log!("peri: {:?} apo: {:?}", orb.periapsis(), orb.apoapsis()); | |
log!("orbit2: {:?}", orb2); | |
log!("peri: {:?} apo: {:?}", orb2.periapsis(), orb2.apoapsis()); | |
orr.set_scale(orb.apoapsis()); | |
orr.draw_orbit(&orb)?; | |
orr.draw_orbit(&orb2)?; | |
orr.draw_planet()?; | |
Ok(()) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment