diff options
| author | brian cully <bjc@spork.org> | 2025-12-27 12:58:44 -0500 |
|---|---|---|
| committer | brian cully <bjc@spork.org> | 2025-12-27 12:58:44 -0500 |
| commit | 69591cc5483d36bc819c75dce9347b08b04e33bf (patch) | |
| tree | 93d0f158621c6caea2f54e449a942f45ec829395 | |
| parent | 0eaa19448a85473e85d4679faa4ab30108dbf4b5 (diff) | |
| download | polyring-69591cc5483d36bc819c75dce9347b08b04e33bf.tar.gz polyring-69591cc5483d36bc819c75dce9347b08b04e33bf.zip | |
add rust/wasm impl
| -rw-r--r-- | Cargo.lock | 78 | ||||
| -rw-r--r-- | Cargo.toml | 3 | ||||
| -rw-r--r-- | site/index.html | 3 | ||||
| -rw-r--r-- | site/pure.mjs (renamed from site/main.mjs) | 3 | ||||
| -rw-r--r-- | site/wasm.mjs | 7 | ||||
| -rw-r--r-- | src/lib.rs | 62 | ||||
| -rw-r--r-- | src/line.rs | 22 | ||||
| -rw-r--r-- | src/point.rs | 50 | ||||
| -rw-r--r-- | src/state.rs | 190 |
9 files changed, 371 insertions, 47 deletions
@@ -4,15 +4,15 @@ version = 4 [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "cfg-if" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "console_log" @@ -25,10 +25,16 @@ dependencies = [ ] [[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" dependencies = [ "once_cell", "wasm-bindgen", @@ -36,9 +42,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.27" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "once_cell" @@ -51,6 +57,7 @@ name = "polyring" version = "0.1.0" dependencies = [ "console_log", + "fastrand", "log", "wasm-bindgen", "web-sys", @@ -58,33 +65,33 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] [[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "syn" -version = "2.0.104" +version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", @@ -93,41 +100,28 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -135,31 +129,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.77" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" dependencies = [ "js-sys", "wasm-bindgen", @@ -12,7 +12,8 @@ crate-type = ["cdylib", "rlib"] console_log = "1.0" log = "0.4" wasm-bindgen = "0.2" +fastrand = "2.3" [dependencies.web-sys] -features = ["Document", "Element", "HtmlElement", "Node", "Window"] +features = ["Document", "DocumentTimeline", "Element", "HtmlElement", "HtmlCanvasElement", "CanvasRenderingContext2d", "Node", "Window"] version = "0.3" diff --git a/site/index.html b/site/index.html index 012e301..bdb2f5d 100644 --- a/site/index.html +++ b/site/index.html @@ -16,7 +16,8 @@ <br> <canvas width='500' height='500'></canvas> - <script src='./main.mjs' type='module'></script> + <!-- <script src='./pure.mjs' type='module'></script> --> + <script src='./wasm.mjs' type='module'></script> <footer> <address><a href='https://git.spork.org/polyring.git'>src</a></address> diff --git a/site/main.mjs b/site/pure.mjs index dc67d73..43f7213 100644 --- a/site/main.mjs +++ b/site/pure.mjs @@ -112,7 +112,7 @@ function findPoly3(points) { let res = [points[0]]; do { // last result is always a point on the edge. - const p1 = res[res.length-1]; + // const p1 = res[res.length-1]; // console.debug(`-- checking lines from ${p1}`); let last = res[res.length-1]; let minTheta = TAU; @@ -233,6 +233,7 @@ async function loaded() { } ctx.clearRect(0, 0, canvas.width, canvas.height); + renderPoints(ctx, points); if (bouncePoints(points)) { //goButton.onclick({ target: goButton }); diff --git a/site/wasm.mjs b/site/wasm.mjs new file mode 100644 index 0000000..29a2741 --- /dev/null +++ b/site/wasm.mjs @@ -0,0 +1,7 @@ +import init from './wasm/polyring.js'; + +async function loaded() { + await init(); +} + +document.addEventListener('DOMContentLoaded', loaded); @@ -1,11 +1,69 @@ -use log::{Level, error, info}; +use std::rc::Rc; +use std::cell::RefCell; + +use log::{Level, info}; use wasm_bindgen::prelude::*; -use web_sys::js_sys; + +use state::State; + +mod line; +mod point; +mod state; + +struct RenderLoop { + animation_id: Option<i32>, + pub closure: Option<Closure<dyn FnMut(f64)>>, +} + +impl RenderLoop { + pub fn new() -> Self { + Self { + animation_id: None, + closure: None, + } + } +} + +fn window() -> web_sys::Window { + web_sys::window().expect("no window") +} #[wasm_bindgen(start)] pub fn init() -> Result<(), JsValue> { console_log::init_with_level(Level::Debug).expect("couldn't init console log"); info!("wasm init"); + let mut s = State::new()?; + s.render_frame(0.0)?; + + // + // from https://users.rust-lang.org/t/wasm-web-sys-how-to-use-window-request-animation-frame-resolved/20882 + // + + let render_loop: Rc<RefCell<RenderLoop>> = Rc::new(RefCell::new(RenderLoop::new())); + { + let closure: Closure<dyn FnMut(f64)> = { + let render_loop = render_loop.clone(); + Closure::wrap(Box::new(move |t| { + s.render_frame(t).expect("should render frame"); + if !s.paused { + let mut render_loop = render_loop.borrow_mut(); + render_loop.animation_id = if let Some(ref closure) = render_loop.closure { + Some(window().request_animation_frame(closure.as_ref().unchecked_ref()).expect("req anim frame")) + } else { + None + } + } + })) + }; + let mut render_loop = render_loop.borrow_mut(); + render_loop.animation_id = Some(window().request_animation_frame(closure.as_ref().unchecked_ref()).expect("req anim frame")); + render_loop.closure = Some(closure); + } + + // + // + // + Ok(()) } diff --git a/src/line.rs b/src/line.rs new file mode 100644 index 0000000..07a1f03 --- /dev/null +++ b/src/line.rs @@ -0,0 +1,22 @@ +use crate::point::Point; + +pub struct Line { + p1: Point, + p2: Point, +} + +impl Line { + pub fn new(p1: Point, p2: Point) -> Self { + Self { p1, p2 } + } + + pub fn vec(&self) -> Point { + Point::new(self.p2.x - self.p1.x, self.p2.y - self.p1.y) + } + + pub fn angle_between(&self, other: &Self) -> f64 { + let v1 = self.vec(); + let v2 = other.vec(); + (v1.dot(&v2) / (v1.mag() * v2.mag())).acos() + } +} diff --git a/src/point.rs b/src/point.rs new file mode 100644 index 0000000..76f9ced --- /dev/null +++ b/src/point.rs @@ -0,0 +1,50 @@ +use crate::state::{MAX_SPEED, TAU}; + +const POINT_RADIUS: f64 = 0.01; + +fn rand_color() -> String { + format!("rgb({} {} {})", fastrand::u8(0..255), fastrand::u8(0..255), fastrand::u8(0..255)) +} + +#[derive(Clone, Debug)] +pub struct Point { + pub x: f64, + pub y: f64, + pub radius: f64, + pub heading: f64, + pub speed: f64, + pub color: String, +} + +impl Point { + pub fn new(x: f64, y: f64) -> Self { + Self { + x, + y, + radius: POINT_RADIUS, + heading: fastrand::f64() * TAU, + speed: fastrand::f64() * MAX_SPEED, + color: rand_color(), + } + } + + pub fn equal_pos(&self, other: &Self) -> bool { + self.x == other.x && self.y == other.y + } + + pub fn dot(&self, other: &Self) -> f64 { + self.x * other.x + self.y * other.y + } + + pub fn mag(&self) -> f64 { + (self.x.powi(2) + self.y.powi(2)).sqrt() + } + + pub fn speed_x(&self) -> f64 { + self.speed * self.heading.cos() + } + + pub fn speed_y(&self) -> f64 { + self.speed * self.heading.sin() + } +} diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..8941a3b --- /dev/null +++ b/src/state.rs @@ -0,0 +1,190 @@ +use std::f64::consts::PI; + +use log::debug; +use wasm_bindgen::prelude::*; +use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement, HtmlElement}; + +use crate::line::Line; +use crate::point::Point; + +pub const TAU: f64 = PI * 2.0; +pub const MAX_SPEED: f64 = 0.01; + +const NUM_POINTS: usize = 40; +const VELVEC_SCALE: f64 = 3.0; + +fn window() -> web_sys::Window { + web_sys::window().expect("no window") +} + +fn document() -> web_sys::Document { + window().document().expect("no document") +} + +fn canvas() -> Result<web_sys::HtmlCanvasElement, JsValue> { + let x = document() + .query_selector("canvas")? + .expect("canvas element"); + Ok(x.dyn_into::<web_sys::HtmlCanvasElement>()?) +} + +#[derive(Debug)] +pub struct State { + canvas: HtmlCanvasElement, + ctx: CanvasRenderingContext2d, + fps: HtmlElement, + points: Vec<Point>, + pub paused: bool, + last_time: Option<f64>, + inter_count: u16, +} + +impl State { + pub fn new() -> Result<Self, JsValue> { + debug!("State::new()"); + let canvas = canvas()?; + let ctx: CanvasRenderingContext2d = canvas + .get_context("2d")? + .expect("2d context on canvas") + .dyn_into()?; + ctx.scale(canvas.width().into(), canvas.height().into())?; + let fps = document().query_selector("#fps")?.expect("fps counter exists").dyn_into::<HtmlElement>().expect("is html element"); + + let points = (0..NUM_POINTS) + .map(|_| Point::new(fastrand::f64(), fastrand::f64())) + .collect(); + + Ok(Self { + canvas, + ctx, + fps, + points, + paused: false, + last_time: document().timeline().current_time(), + inter_count: 1, + }) + } + + pub fn render_frame(&mut self, t: f64) -> Result<(), JsValue> { + if let Some(last_time) = self.last_time { + if t > last_time { + let ic: f64 = self.inter_count.into(); + let val: f64 = (1_000.0 * ic / (t - last_time)).trunc(); + self.fps.set_text_content(Some(val.to_string().as_str())); + self.last_time = Some(t); + self.inter_count = 1; + } else { + self.inter_count += 1; + } + } + + self.ctx.clear_rect( + 0.0, + 0.0, + self.canvas.width().into(), + self.canvas.height().into(), + ); + self.render_points()?; + if self.bounce_points() { + //debug!("point bounced"); + } + self.move_points(); + + // poly finding assumes sorted + self.points.sort_by(|a, b| { + if a.y > b.y { + std::cmp::Ordering::Greater + } else if a.y == b.y { + std::cmp::Ordering::Equal + } else { + std::cmp::Ordering::Less + } + }); + let poly_points = self.find_poly(); + + self.ctx.set_line_width(0.005); + self.ctx.begin_path(); + self.ctx.move_to(poly_points[0].x, poly_points[0].y); + for p in poly_points { + self.ctx.line_to(p.x, p.y); + } + self.ctx.set_stroke_style_str("blue"); + self.ctx.stroke(); + + Ok(()) + } + + fn render_points(&self) -> Result<(), JsValue> { + for p in &self.points { + self.ctx.set_fill_style_str(&p.color); + self.ctx.begin_path(); + self.ctx.arc(p.x, p.y, p.radius, 0.0, TAU)?; + self.ctx.fill(); + + self.ctx.set_line_width(0.005); + self.ctx.set_stroke_style_str(&p.color); + self.ctx.begin_path(); + self.ctx.move_to(p.x, p.y); + self.ctx.line_to( + p.x + p.speed_x() * VELVEC_SCALE, + p.y + p.speed_y() * VELVEC_SCALE, + ); + self.ctx.stroke(); + } + Ok(()) + } + + fn find_poly(&self) -> Vec<Point> { + let mut last_line = Line::new(Point::new(0.0, 0.0), Point::new(1.0, 0.0)); + let mut xxx = 100; + let mut res = vec![self.points[0].clone()]; + loop { + let last = res.last().expect("something in res"); + let mut min_theta = TAU; + let mut min_p = &self.points[0]; + for p in &self.points { + if !last.equal_pos(p) { + let l2 = Line::new(last.clone(), p.clone()); + let theta = last_line.angle_between(&l2); + if theta < min_theta { + min_theta = theta; + min_p = p; + } + } + } + last_line = Line::new(res.last().expect("something in res").clone(), min_p.clone()); + res.push(min_p.clone()); + + xxx -= 1; + if xxx == 0 || res[0].equal_pos(res.last().expect("something in res")) { + break; + } + } + + res + } + + fn bounce_points(&mut self) -> bool { + let mut did_bounce = false; + for p in &mut self.points { + let x = p.x + p.speed_x(); + let y = p.y + p.speed_y(); + + if x < 0.0 || x >= 1.0 { + p.heading = PI - p.heading; + did_bounce = true; + } else if y < 0.0 || y >= 1.0 { + p.heading = -p.heading; + did_bounce = true; + } + } + did_bounce + } + + fn move_points(&mut self) { + for p in &mut self.points { + p.x += p.speed_x(); + p.y += p.speed_y(); + } + } +} |
