aboutsummaryrefslogtreecommitdiffstats
path: root/src/state.rs
diff options
context:
space:
mode:
authorbrian cully <bjc@spork.org>2025-12-27 12:58:44 -0500
committerbrian cully <bjc@spork.org>2025-12-27 12:58:44 -0500
commit69591cc5483d36bc819c75dce9347b08b04e33bf (patch)
tree93d0f158621c6caea2f54e449a942f45ec829395 /src/state.rs
parent0eaa19448a85473e85d4679faa4ab30108dbf4b5 (diff)
downloadpolyring-69591cc5483d36bc819c75dce9347b08b04e33bf.tar.gz
polyring-69591cc5483d36bc819c75dce9347b08b04e33bf.zip
add rust/wasm impl
Diffstat (limited to 'src/state.rs')
-rw-r--r--src/state.rs190
1 files changed, 190 insertions, 0 deletions
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();
+ }
+ }
+}