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; #[derive(Debug)] pub struct State { canvas: HtmlCanvasElement, ctx: CanvasRenderingContext2d, fps: HtmlElement, points: Vec, last_time: Option, inter_count: u16, } impl State { pub fn new(canvas: HtmlCanvasElement, fps: HtmlElement) -> Result { debug!("State::new()"); let ctx: CanvasRenderingContext2d = canvas .get_context("2d")? .expect("2d context on canvas") .dyn_into()?; ctx.scale(canvas.width().into(), canvas.height().into())?; let points = (0..NUM_POINTS) .map(|_| Point::new(fastrand::f64(), fastrand::f64())) .collect(); Ok(Self { canvas, ctx, fps, points, last_time: None, 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.inter_count = 1; } else { self.inter_count += 1; } } self.last_time = Some(t); 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); let mut iter = poly_points.into_iter(); let mut last = iter.next().ok_or("no poly points")?; for p in iter { self.ctx.begin_path(); self.ctx.move_to(last.x, last.y); self.ctx.set_stroke_style_str(&last.color); self.ctx.line_to(p.x, p.y); self.ctx.stroke(); last = p; } 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 p1 = Point::new(0.0, 0.0); let p2 = Point::new(1.0, 0.0); let mut last_line = Line::new(&p1, &p2); let mut xxx = 100; let mut res = vec![&self.points[0]]; 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, p); 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"), min_p); res.push(min_p); 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(); } } }