Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 5 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,20 @@ name = "vectorflow"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
gloo = "0.3"
gloo-utils = "0.1"
js-sys = "0.3"
yew = { version = "0.20.0", features = ["csr"] }
wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4.30"
rand = "0.8.4"
getrandom = { version = "0.2", features = ["js"] }
log = "0.4.20"
wasm-bindgen-futures = "0.4.68"
rand = "0.10.1"
getrandom = { version = "0.4", features = ["wasm_js"] }
log = "0.4.29"
wasm-logger = "0.2.0"

[dependencies.web-sys]
version = "0.3.58"
version = "0.3.95"
features = [
"Navigator",
"Request",
Expand Down
95 changes: 53 additions & 42 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use wasm_bindgen::{prelude::{Closure, wasm_bindgen}, JsCast, JsValue};
use log::info;
use wasm_bindgen::{
prelude::{wasm_bindgen, Closure},
JsCast,
};
use web_sys::window;
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement};
use yew::prelude::*;
use log::info;


mod function_input;
use function_input::FunctionInput;
Expand All @@ -21,7 +23,7 @@ pub enum Msg {
UpdateFunc(String),
}

const TARGET_FPS: f64 = 32.0; // never set this higher than 60
const TARGET_FPS: f64 = 32.0; // never set this higher than 60
const FPS_UPDATE_PERIOD: f64 = 500.0;
const STARTING_NUM_PARTICLES: usize = 10_000;
const BACKGROUND_COLOUR: &str = "#000";
Expand All @@ -35,8 +37,8 @@ struct Config {
width: usize,
height: usize,
avg_lifetime: i32,
fg_colour: JsValue,
bg_colour: JsValue,
fg_colour: String,
bg_colour: String,
func: Box<dyn Fn((f64, f64, f64)) -> (f64, f64)>,
target_fps: f64,
}
Expand All @@ -62,17 +64,17 @@ impl Component for AnimationCanvas {

fn create(ctx: &Context<Self>) -> Self {
let func = interpret_field_function(&DEFAULT_FUNCTION.to_string()).unwrap();


ctx.link().send_future(async {Msg::Init});
ctx.link().send_future(async { Msg::Init });
let comp_ctx = ctx.link().clone();
let callback = Closure::wrap(Box::new(move || comp_ctx.send_message(Msg::Render)) as Box<dyn FnMut()>);
let callback =
Closure::wrap(Box::new(move || comp_ctx.send_message(Msg::Render)) as Box<dyn FnMut()>);
let config = Config {
width: window().unwrap().inner_width().unwrap().as_f64().unwrap() as usize + 100,
height: window().unwrap().inner_height().unwrap().as_f64().unwrap() as usize + 100,
avg_lifetime: 200,
fg_colour: JsValue::from(FOREGROUND_COLOUR),
bg_colour: JsValue::from(BACKGROUND_COLOUR),
fg_colour: FOREGROUND_COLOUR.to_string(),
bg_colour: BACKGROUND_COLOUR.to_string(),
target_fps: TARGET_FPS,
func: func,
};
Expand Down Expand Up @@ -102,19 +104,16 @@ impl Component for AnimationCanvas {
let w = self.config.width as i32;
let h = self.config.height as i32;
for _ in 0..STARTING_NUM_PARTICLES {
self.particles.push(Particle::new(
(-w, w),
(-h, h),
self.config.avg_lifetime,
));
self.particles
.push(Particle::new((-w, w), (-h, h), self.config.avg_lifetime));
}

ctx.link().send_message(Msg::Render);
true
}
Msg::Render => {
let delta = 1000.0 / self.average_fps;

self.update_particles(delta);
self.render();
self.frame_counter += 1;
Expand All @@ -125,19 +124,20 @@ impl Component for AnimationCanvas {
self.average_fps = 1000.0 * self.frame_counter as f64 / FPS_UPDATE_PERIOD;
self.frame_counter = 0;

let fps_ratio = (self.average_fps / self.config.target_fps).max(0.90).min(1.10);
let fps_ratio = (self.average_fps / self.config.target_fps)
.max(0.90)
.min(1.10);
let target_num_particles = (self.particles.len() as f64 * fps_ratio) as usize;
info!("FPS: {} {}", self.average_fps as i32, target_num_particles);

info!(
"FPS: {} {}",
self.average_fps as i32, target_num_particles
);

let w = self.config.width as i32;
let h = self.config.height as i32;
self.particles.resize(
target_num_particles,
Particle::new(
(-w, w),
(-h, h),
self.config.avg_lifetime,
),
Particle::new((-w, w), (-h, h), self.config.avg_lifetime),
);
true
} else {
Expand All @@ -150,8 +150,8 @@ impl Component for AnimationCanvas {
Ok(f) => {
self.config.func = f;
self.func_error_message = "".to_string();
info!("{}", pretty_print(self.func_string.to_string()));
},
info!("{}", pretty_print(self.func_string.to_string()));
}
Err(e) => {
info!("{}", e);
self.func_error_message = e;
Expand All @@ -169,8 +169,10 @@ impl Component for AnimationCanvas {
let mut valid_count = 0;
for _ in 0..100 {
// samples between self.config.width and -self.config.width
let x = (js_sys::Math::random() * self.config.width as f64 * 2.0) - self.config.width as f64;
let y = (js_sys::Math::random() * self.config.height as f64 * 2.0) - self.config.height as f64;
let x = (js_sys::Math::random() * self.config.width as f64 * 2.0)
- self.config.width as f64;
let y = (js_sys::Math::random() * self.config.height as f64 * 2.0)
- self.config.height as f64;
let t = js_sys::Math::random() * 60.0;
let (dx, dy) = f((x, y, t));
// Check for NaN
Expand All @@ -187,11 +189,15 @@ impl Component for AnimationCanvas {
}
valid_count += 1;
}

if valid_count > 30 {
self.config.func = f;
self.func_string = func_string;
info!("found function after {} attempts\n{}", counter, pretty_print(self.func_string.to_string()));
info!(
"found function after {} attempts\n{}",
counter,
pretty_print(self.func_string.to_string())
);
self.func_error_message = "".to_string();
return false;
}
Expand All @@ -214,12 +220,12 @@ impl Component for AnimationCanvas {

html! {
<div>
<div style="position: absolute; color: #1ce;">
<div style="position: absolute; color: #1ce;">
<FunctionInput {on_change} value={self.func_string.clone()} />
{func_error_message_html}
<div>
<button class="button" onclick={ctx.link().callback(|_| Msg::RandomFunction)}> {"🎲"} </button>
{"FPS: "} {self.average_fps as usize } {" Particles: "} {self.particles.len()}
<div>
<button class="button" onclick={ctx.link().callback(|_| Msg::RandomFunction)}> {"🎲"} </button>
{"FPS: "} {self.average_fps as usize } {" Particles: "} {self.particles.len()}
</div>
</div>
<canvas
Expand All @@ -239,7 +245,7 @@ impl AnimationCanvas {
self.start_time = js_sys::Date::now();
}
for particle in self.particles.iter_mut() {
if !particle.update(&self.config.func, delta, t) {
if !particle.update(&self.config.func, delta, t) {
particle.respawn();
particle.update(&self.config.func, delta, t);
}
Expand All @@ -248,16 +254,22 @@ impl AnimationCanvas {

fn render(&mut self) {
let canvas: HtmlCanvasElement = self.canvas.cast().unwrap();
let ctx: CanvasRenderingContext2d = canvas.get_context("2d").unwrap().unwrap().unchecked_into();
let ctx: CanvasRenderingContext2d =
canvas.get_context("2d").unwrap().unwrap().unchecked_into();

// put a black square over canvas to fade old particles
ctx.set_global_alpha(0.01); // lower values make the trails longer
ctx.set_fill_style(&self.config.bg_colour);
ctx.fill_rect(0.0, 0.0, self.config.width as f64, self.config.height as f64);
ctx.set_global_alpha(0.01); // lower values make the trails longer
ctx.set_fill_style_str(&self.config.bg_colour);
ctx.fill_rect(
0.0,
0.0,
self.config.width as f64,
self.config.height as f64,
);

// render all updated particles
ctx.set_global_alpha(1.0);
ctx.set_fill_style(&self.config.fg_colour);
ctx.set_fill_style_str(&self.config.fg_colour);
for particle in self.particles.iter_mut() {
// shift the particle's position so that the origin is in the center of the canvas
let x = particle.pos.0 + (self.config.width as f64 / 2.0);
Expand All @@ -281,7 +293,6 @@ fn app_body() -> Html {
}
}


#[wasm_bindgen(start)]
pub fn main() {
wasm_logger::init(wasm_logger::Config::default());
Expand Down
Loading
Loading