Created
November 8, 2024 20:52
-
-
Save dgerrells/cc8557b392df44c9888fcc70a010de46 to your computer and use it in GitHub Desktop.
how fast is rust
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
# how fast is rust | |
This contains all the various versions and iterations for a project with the goal of simulating as many particles as possible. There are several versions as it works towards the end of simd and multi-threaded. There are also a few wasm versions with the required tooling setup. This does require two different cargo toml files. There is a build web script which runs 3 steps to produce a fast and small wasm file but you will need to update the input output locations. |
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
[package] | |
name = "how_fast_is_rust" | |
version = "0.1.0" | |
edition = "2021" | |
[dependencies] | |
minifb = "0.27" | |
rand = "0.8" | |
wasm-bindgen = "0.2.95" | |
[profile.release] | |
opt-level = 3 | |
jemalloc = true |
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
[package] | |
name = "how_fast_is_rust" | |
version = "0.1.0" | |
edition = "2021" | |
[dependencies] | |
getrandom = { version = "0.2", features = ["js"] } | |
rand = "0.8" | |
wasm-bindgen = "0.2.95" | |
[profile.release] | |
opt-level = 3 | |
lto = true | |
[lib] | |
crate-type = ["cdylib"] | |
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
#/binbash | |
cargo build --target=wasm32-unknown-unknown --release | |
wasm-bindgen ./target/wasm32-unknown-unknown/release/how_fast_is_rust.wasm --out-dir /sabby-rust-basic --target web | |
wasm-opt /how_fast_is_rust_bg.wasm -o /how_fast_is_rust_bg.wasm -O3 --strip-debug |
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
pub mod thread_pool; |
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 std::sync::{Arc, Mutex, Condvar, mpsc}; | |
use std::thread; | |
use std::thread::JoinHandle; | |
// I will be real, chatgpt helped me in this file. | |
// I don't like it and over time I am sure I will get better. | |
pub struct ThreadPool { | |
workers: Vec<Worker>, | |
sender: mpsc::Sender<Message>, | |
job_count: Arc<(Mutex<usize>, Condvar)>, | |
} | |
type Job = Box<dyn FnOnce() + Send + 'static>; | |
enum Message { | |
NewJob(Job), | |
} | |
impl ThreadPool { | |
pub fn new(size: usize) -> ThreadPool { | |
assert!(size > 0); // nifty | |
let (sender, receiver) = mpsc::channel(); | |
let receiver = Arc::new(Mutex::new(receiver)); | |
let job_count = Arc::new((Mutex::new(0), Condvar::new())); | |
let mut workers = Vec::with_capacity(size); | |
for id in 0..size { | |
workers.push(Worker::new(id, Arc::clone(&receiver), Arc::clone(&job_count))); | |
} | |
ThreadPool { | |
workers, | |
sender, | |
job_count, | |
} | |
} | |
pub fn execute<F>(&self, f: F) | |
where | |
F: FnOnce() + Send + 'static, | |
{ | |
let (lock, _) = &*self.job_count; | |
*lock.lock().unwrap() += 1; | |
let job = Box::new(f); | |
self.sender.send(Message::NewJob(job)).unwrap(); | |
} | |
pub fn wait_for_completion(&self) { | |
let (lock, cvar) = &*self.job_count; | |
let mut count = lock.lock().unwrap(); | |
while *count > 0 { | |
count = cvar.wait(count).unwrap(); | |
} | |
} | |
} | |
pub struct Worker { | |
id: usize, | |
handle: Option<JoinHandle<()>>, | |
} | |
impl Worker { | |
fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Message>>>, job_count: Arc<(Mutex<usize>, Condvar)>) -> Worker { | |
let handle = thread::spawn(move || loop { | |
let message = receiver.lock().unwrap().recv(); | |
match message { | |
Ok(Message::NewJob(job)) => { | |
job(); | |
let (lock, cvar) = &*job_count; | |
let mut count = lock.lock().unwrap(); | |
*count -= 1; | |
cvar.notify_all(); | |
} | |
Err(_) => break, | |
} | |
}); | |
Worker { | |
id, | |
handle: Some(handle), | |
} | |
} | |
} |
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 minifb::{Key, MouseButton, Window, WindowOptions}; | |
use rand::Rng; | |
use std::time::Instant; | |
const WIDTH: usize = 1200; | |
const HEIGHT: usize = 800; | |
const PARTICLE_COUNT: usize = 5_000_000; | |
const GRAVITY_STRENGTH: f32 = 205.5*8.0; | |
#[repr(align(16))] | |
struct Particle { | |
x: f32, | |
y: f32, | |
dx: f32, | |
dy: f32, | |
} | |
fn generate_particles(count: usize, width: usize, height: usize) -> Vec<Particle> { | |
let mut rng = rand::thread_rng(); | |
let mut particles = Vec::with_capacity(count); | |
for _ in 0..count { | |
particles.push(Particle { | |
x: rng.gen_range(0.0..width as f32), | |
y: rng.gen_range(0.0..height as f32), | |
dx: rng.gen_range(-1.0..1.0), | |
dy: rng.gen_range(-1.0..1.0), | |
}); | |
} | |
particles | |
} | |
fn main() { | |
let mut particles = generate_particles(PARTICLE_COUNT, WIDTH, HEIGHT); | |
let mut buffer: Vec<u32> = vec![0; WIDTH * HEIGHT]; | |
let mut window = Window::new("Safu", WIDTH, HEIGHT, WindowOptions::default()) | |
.unwrap_or_else(|e| { | |
panic!("{}", e); | |
}); | |
let mut mouse_pos = (WIDTH as f32 / 2.0, HEIGHT as f32 / 2.0); | |
let mut mouse_pressed = false; | |
let friction_per_second = 0.99_f32.powf(60.0); | |
let mut last_frame_time = Instant::now(); | |
let mut frame_count = 0; | |
let mut last_fps_time = Instant::now(); | |
let mut fps = 0; | |
while window.is_open() && !window.is_key_down(Key::Escape) { | |
let now = Instant::now(); | |
let delta_time = now.duration_since(last_frame_time).as_secs_f32(); | |
last_frame_time = now; | |
if let Some((mx, my)) = window.get_mouse_pos(minifb::MouseMode::Clamp) { | |
mouse_pos = (mx, my); | |
} | |
mouse_pressed = window.get_mouse_down(MouseButton::Left); | |
let dt = delta_time; | |
for particle in particles.iter_mut() { | |
if mouse_pressed { | |
let dx = mouse_pos.0 - particle.x; | |
let dy = mouse_pos.1 - particle.y; | |
let distance = (dx * dx + dy * dy).sqrt(); | |
if distance > 0.2 { | |
let inv_gravity = GRAVITY_STRENGTH / distance; | |
particle.dx += dx * inv_gravity * dt; | |
particle.dy += dy * inv_gravity * dt; | |
} | |
} | |
let friction = friction_per_second.powf(dt); | |
particle.dx *= friction; | |
particle.dy *= friction; | |
particle.x += particle.dx * dt; | |
particle.y += particle.dy * dt; | |
} | |
buffer.iter_mut().for_each(|pixel| *pixel = 0); | |
for particle in &particles { | |
let x = particle.x as usize; | |
let y = particle.y as usize; | |
let red = (particle.x / WIDTH as f32 * 255.0 * 0.8) as u32; | |
let green = (particle.y / HEIGHT as f32 * 255.0 * 0.8) as u32; | |
let blue = (255.0*0.5) as u32; | |
let color = (red << 16) | (green << 8) | blue; | |
let idx = (y.max(0).min(HEIGHT-1) * WIDTH + x.max(0).min(WIDTH-1)); | |
buffer[idx] = color; | |
} | |
window.update_with_buffer(&buffer, WIDTH, HEIGHT).unwrap(); | |
frame_count += 1; | |
if now.duration_since(last_fps_time).as_secs_f32() >= 1.0 { | |
fps = frame_count; | |
frame_count = 0; | |
last_fps_time = now; | |
println!("FPS: {} {}", fps, delta_time); | |
} | |
} | |
} |
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 minifb::{Key, MouseButton, Window, WindowOptions}; | |
use std::mem::MaybeUninit; | |
use rand::Rng; | |
use std::time::{Instant}; | |
const WIDTH: usize = 1200; | |
const HEIGHT: usize = 800; | |
const PARTICLE_COUNT: usize = 5_000_000; | |
const GRAVITY_STRENGTH: f32 = 200.5; | |
const MAX_PERF_SAMPLE_FRAMES: usize = 1000; | |
fn main() { | |
let mut particles: Box<[MaybeUninit<f32>]> = Box::new_uninit_slice(PARTICLE_COUNT*4); | |
for i in 0..(PARTICLE_COUNT * 4) { | |
particles[i].write(0.0); | |
} | |
let mut particles: Box<[f32]> = unsafe { std::mem::transmute(particles) }; | |
let mut rng = rand::thread_rng(); | |
let mut buffer: Vec<u32> = vec![0; WIDTH * HEIGHT]; | |
let mut window = Window::new("Safu", WIDTH, HEIGHT, WindowOptions::default()) | |
.unwrap_or_else(|e| { | |
panic!("{}", e); | |
}); | |
window.set_target_fps(120); | |
let mut i: usize = 0; | |
while i < PARTICLE_COUNT { | |
let idx = i*4; | |
particles[idx] = rng.gen_range(0.0..WIDTH as f32); | |
particles[idx+1] = rng.gen_range(0.0..HEIGHT as f32); | |
particles[idx+2] = rng.gen_range(-1.0..1.0); | |
particles[idx+3] = rng.gen_range(-1.0..1.0); | |
i += 1; | |
} | |
let mut mouse_pos = (WIDTH as f32 / 2.0, HEIGHT as f32 / 2.0); | |
let mut mouse_pressed = false; | |
let friction_per_second = 0.99_f32.powf(60.0); | |
let mut frame_count = 0; | |
let mut last_fps_time = Instant::now(); | |
let mut last_frame_time = Instant::now(); | |
let mut simulation_times = vec![]; | |
let mut drawing_times = vec![]; | |
let mut fps = 0; | |
while window.is_open() && !window.is_key_down(Key::Escape) { | |
let now = Instant::now(); | |
let delta_time = now.duration_since(last_frame_time).as_secs_f32(); | |
last_frame_time = now; | |
let sim_start = Instant::now(); | |
if let Some((mx, my)) = window.get_mouse_pos(minifb::MouseMode::Clamp) { | |
mouse_pos = (mx, my); | |
} | |
mouse_pressed = window.get_mouse_down(MouseButton::Left); | |
for particle in particles.chunks_mut(4) { | |
if mouse_pressed { | |
let dx = mouse_pos.0 - particle[0]; | |
let dy = mouse_pos.1 - particle[1]; | |
let distance = (dx * dx + dy * dy).sqrt(); | |
if distance > 1.0 { | |
let inv_gravity = GRAVITY_STRENGTH / distance; | |
particle[2] += dx * inv_gravity * 8.0 * delta_time; | |
particle[3] += dy * inv_gravity * 8.0 * delta_time; | |
} | |
} | |
let friction = friction_per_second.powf(delta_time); | |
particle[2] *= friction; | |
particle[3] *= friction; | |
particle[0] += particle[2] * delta_time; | |
particle[1] += particle[3] * delta_time; | |
} | |
simulation_times.push(sim_start.elapsed().as_secs_f32()); | |
if simulation_times.len() > MAX_PERF_SAMPLE_FRAMES { | |
simulation_times.remove(0); | |
} | |
let draw_start = Instant::now(); | |
buffer.iter_mut().for_each(|pixel| *pixel = 0); | |
for particle in particles.chunks_mut(4) { | |
let x = particle[0] as usize; | |
let y = particle[1] as usize; | |
if x < WIDTH && y < HEIGHT && x > 0 && y > 0 { | |
let red = (particle[0] / WIDTH as f32 * 255.0 * 0.8) as u32; | |
let green = (particle[1] / HEIGHT as f32 * 255.0 * 0.8) as u32; | |
let blue = (255.0*0.5) as u32; | |
let color = (red << 16) | (green << 8) | blue; | |
buffer[y * WIDTH + x] = color; | |
} | |
} | |
window.update_with_buffer(&buffer, WIDTH, HEIGHT).unwrap(); | |
drawing_times.push(draw_start.elapsed().as_secs_f32()); | |
if drawing_times.len() > MAX_PERF_SAMPLE_FRAMES { | |
drawing_times.remove(0); | |
} | |
frame_count += 1; | |
if now.duration_since(last_fps_time).as_secs_f32() >= 1.0 { | |
fps = frame_count; | |
frame_count = 0; | |
last_fps_time = now; | |
let avg_simulation_time: f32 = simulation_times.iter().sum::<f32>() / simulation_times.len() as f32; | |
let avg_drawing_time: f32 = drawing_times.iter().sum::<f32>() / drawing_times.len() as f32; | |
println!("FPS: {}", fps); | |
println!("Avg Simulation Time: {:.5}", avg_simulation_time*1000.0); | |
println!("Avg Drawing Time: {:.5}", avg_drawing_time*1000.0); | |
} | |
} | |
} |
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 minifb::{Key, MouseButton, Window, WindowOptions}; | |
use rand::Rng; | |
use std::mem::MaybeUninit; | |
use std::time::Instant; | |
use rayon::prelude::*; | |
const WIDTH: usize = 1200; | |
const HEIGHT: usize = 800; | |
const PARTICLE_COUNT: usize = 20_000_000; | |
const GRAVITY_STRENGTH: f32 = 200.5; | |
const MAX_PERF_SAMPLE_FRAMES: usize = 100; | |
fn main() { | |
let mut particles: Box<[MaybeUninit<f32>]> = Box::new_uninit_slice(PARTICLE_COUNT * 4); | |
for i in 0..(PARTICLE_COUNT * 4) { | |
particles[i].write(0.0); | |
} | |
let mut particles: Box<[f32]> = unsafe { std::mem::transmute(particles) }; | |
let mut rng = rand::thread_rng(); | |
let mut buffer: Vec<u32> = vec![0; WIDTH * HEIGHT]; | |
let mut window = | |
Window::new("Safu", WIDTH, HEIGHT, WindowOptions::default()).unwrap_or_else(|e| { | |
panic!("{}", e); | |
}); | |
window.set_target_fps(120); | |
let mut i: usize = 0; | |
while i < PARTICLE_COUNT { | |
let idx = i * 4; | |
particles[idx] = rng.gen_range(0.0..WIDTH as f32); | |
particles[idx + 1] = rng.gen_range(0.0..HEIGHT as f32); | |
particles[idx + 2] = rng.gen_range(-1.0..1.0); | |
particles[idx + 3] = rng.gen_range(-1.0..1.0); | |
i += 1; | |
} | |
let mut mouse_pos = (WIDTH as f32 / 2.0, HEIGHT as f32 / 2.0); | |
let mut mouse_pressed = false; | |
let friction_per_second = 0.99_f32.powf(60.0); | |
let mut frame_count = 0; | |
let mut last_fps_time = Instant::now(); | |
let mut last_frame_time = Instant::now(); | |
let mut simulation_times = vec![]; | |
let mut drawing_times = vec![]; | |
let mut fps = 0; | |
while window.is_open() && !window.is_key_down(Key::Escape) { | |
let now = Instant::now(); | |
let delta_time = now.duration_since(last_frame_time).as_secs_f32(); | |
last_frame_time = now; | |
let sim_start = Instant::now(); | |
if let Some((mx, my)) = window.get_mouse_pos(minifb::MouseMode::Clamp) { | |
mouse_pos = (mx, my); | |
} | |
mouse_pressed = window.get_mouse_down(MouseButton::Left); | |
particles.par_chunks_mut(4).with_min_len(10000).for_each(|particle| { | |
if mouse_pressed { | |
let dx = mouse_pos.0 - particle[0]; | |
let dy = mouse_pos.1 - particle[1]; | |
let distance = (dx * dx + dy * dy).sqrt(); | |
if distance > 1.0 { | |
let inv_gravity = GRAVITY_STRENGTH / distance; | |
particle[2] += dx * inv_gravity * 8.0 * delta_time; | |
particle[3] += dy * inv_gravity * 8.0 * delta_time; | |
} | |
} | |
let friction = friction_per_second.powf(delta_time); | |
particle[2] *= friction; | |
particle[3] *= friction; | |
particle[0] += particle[2] * delta_time; | |
particle[1] += particle[3] * delta_time; | |
}); | |
simulation_times.push(sim_start.elapsed().as_secs_f32()); | |
if simulation_times.len() > MAX_PERF_SAMPLE_FRAMES { | |
simulation_times.remove(0); | |
} | |
let draw_start = Instant::now(); | |
buffer.iter_mut().for_each(|pixel| *pixel = 0); | |
for particle in particles.chunks_mut(4) { | |
let x = particle[0] as usize; | |
let y = particle[1] as usize; | |
if x >= WIDTH || y >= HEIGHT || x <= 0 || y <= 0 { | |
continue; | |
} | |
let red = (particle[0] as f32 / WIDTH as f32 * 255.0 * 0.8) as u8; | |
let green = (particle[1] as f32 / HEIGHT as f32 * 255.0 * 0.8) as u8; | |
let blue = (255.0 * 0.5) as u8; | |
let buffer_index = y * WIDTH + x; | |
let existing_color = buffer[buffer_index]; | |
let existing_red = ((existing_color >> 16) & 0xFF) as u8; | |
let existing_green = ((existing_color >> 8) & 0xFF) as u8; | |
let existing_blue = (existing_color & 0xFF) as u8; | |
let combined_red = existing_red.saturating_add(red); | |
let combined_green = existing_green.saturating_add(green); | |
let combined_blue = existing_blue.saturating_add(blue); | |
let combined_color = ((combined_red as u32) << 16) | ((combined_green as u32) << 8) | (combined_blue as u32); | |
buffer[buffer_index] = combined_color; | |
} | |
window.update_with_buffer(&buffer, WIDTH, HEIGHT).unwrap(); | |
drawing_times.push(draw_start.elapsed().as_secs_f32()); | |
if drawing_times.len() > MAX_PERF_SAMPLE_FRAMES { | |
drawing_times.remove(0); | |
} | |
frame_count += 1; | |
if now.duration_since(last_fps_time).as_secs_f32() >= 1.0 { | |
fps = frame_count; | |
frame_count = 0; | |
last_fps_time = now; | |
let avg_simulation_time: f32 = | |
simulation_times.iter().sum::<f32>() / simulation_times.len() as f32; | |
let avg_drawing_time: f32 = | |
drawing_times.iter().sum::<f32>() / drawing_times.len() as f32; | |
println!("FPS: {}", fps); | |
println!("Avg Simulation Time: {:.5}", avg_simulation_time * 1000.0); | |
println!("Avg Drawing Time: {:.5}", avg_drawing_time * 1000.0); | |
} | |
} | |
} |
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 minifb::{Key, MouseButton, Window, WindowOptions}; | |
use rand::Rng; | |
use std::sync::mpsc; | |
use std::thread; | |
use std::time::Instant; | |
const WIDTH: usize = 1200; | |
const HEIGHT: usize = 800; | |
const PARTICLE_COUNT: usize = 20_000_000; | |
const THREAD_COUNT: usize = 8; | |
const PARTICLE_STRIDE: usize = 4; | |
const GRAVITY_STRENGTH: f32 = 200.5; | |
const MAX_PERF_SAMPLE_FRAMES: usize = 100; | |
fn main() { | |
let mut particles: Box<[f32]> = vec![0.0; PARTICLE_COUNT * PARTICLE_STRIDE].into_boxed_slice(); | |
let mut rng = rand::thread_rng(); | |
let mut buffer: Vec<u32> = vec![0; WIDTH * HEIGHT]; | |
let mut p_count_buffer = vec![0 as u8; WIDTH * HEIGHT]; | |
let mut window = Window::new("Safu", WIDTH, HEIGHT, WindowOptions::default()).unwrap_or_else(|e| { | |
panic!("{}", e); | |
}); | |
window.set_target_fps(120); | |
let mut i: usize = 0; | |
while i < PARTICLE_COUNT { | |
let idx = i * PARTICLE_STRIDE; | |
particles[idx] = rng.gen_range(0.0..WIDTH as f32); | |
particles[idx + 1] = rng.gen_range(0.0..HEIGHT as f32); | |
particles[idx + 2] = rng.gen_range(-1.0..1.0); | |
particles[idx + 3] = rng.gen_range(-1.0..1.0); | |
i += 1; | |
} | |
let mut mouse_pos = (WIDTH as f32 / 2.0, HEIGHT as f32 / 2.0); | |
let mut mouse_pressed = false; | |
let friction_per_second = 0.99_f32.powf(60.0); | |
let mut frame_count = 0; | |
let mut last_fps_time = Instant::now(); | |
let mut last_frame_time = Instant::now(); | |
let mut simulation_times = vec![]; | |
let mut drawing_times = vec![]; | |
let mut fps = 0; | |
while window.is_open() && !window.is_key_down(Key::Escape) { | |
let now = Instant::now(); | |
let delta_time = now.duration_since(last_frame_time).as_secs_f32(); | |
last_frame_time = now; | |
let sim_start = Instant::now(); | |
if let Some((mx, my)) = window.get_mouse_pos(minifb::MouseMode::Clamp) { | |
mouse_pos = (mx, my); | |
} | |
mouse_pressed = window.get_mouse_down(MouseButton::Left); | |
let (tx, rx) = mpsc::channel(); | |
let chunk_size = PARTICLE_COUNT / THREAD_COUNT; | |
let particles_ptr = particles.as_mut_ptr(); | |
for t in 0..THREAD_COUNT { | |
let tx = tx.clone(); | |
let mouse_pos = mouse_pos; | |
let mut buffer = vec![0 as u8; WIDTH * HEIGHT]; | |
let start = t * chunk_size * PARTICLE_STRIDE; | |
let end = if t == THREAD_COUNT - 1 { | |
PARTICLE_COUNT * PARTICLE_STRIDE | |
} else { | |
(t + 1) * chunk_size * PARTICLE_STRIDE | |
}; | |
unsafe { | |
let particles_chunk = std::slice::from_raw_parts_mut(particles_ptr.add(start), end - start); | |
thread::spawn(move || { | |
for particle in particles_chunk.chunks_mut(PARTICLE_STRIDE) { | |
if mouse_pressed { | |
let dx = mouse_pos.0 - particle[0]; | |
let dy = mouse_pos.1 - particle[1]; | |
let distance = dx * dx + dy * dy; | |
if distance > 2.0 { | |
let inv_gravity = GRAVITY_STRENGTH / distance.sqrt(); | |
particle[2] += dx * inv_gravity * 8.0 * delta_time; | |
particle[3] += dy * inv_gravity * 8.0 * delta_time; | |
} | |
} | |
let friction = friction_per_second.powf(delta_time); | |
particle[2] *= friction; | |
particle[3] *= friction; | |
particle[0] += particle[2] * delta_time; | |
particle[1] += particle[3] * delta_time; | |
let x = particle[0] as usize; | |
let y = particle[1] as usize; | |
if x >= WIDTH || y >= HEIGHT || x <= 0 || y <= 0 { | |
continue; | |
} | |
let buffer_index = y * WIDTH + x; | |
let p_count = buffer[buffer_index]; | |
buffer[buffer_index] = p_count.saturating_add(1); | |
} | |
tx.send(buffer).unwrap(); | |
}); | |
} | |
} | |
drop(tx); | |
p_count_buffer.iter_mut().for_each(|c| *c = 0); | |
for local_buffer in rx { | |
for (i, &count) in local_buffer.iter().enumerate() { | |
p_count_buffer[i] = p_count_buffer[i].saturating_add(count); | |
} | |
} | |
simulation_times.push(sim_start.elapsed().as_secs_f32()); | |
if simulation_times.len() > MAX_PERF_SAMPLE_FRAMES { | |
simulation_times.remove(0); | |
} | |
let draw_start = Instant::now(); | |
buffer.iter_mut().for_each(|pixel| *pixel = 0); | |
for (i, &count) in p_count_buffer.iter().enumerate() { | |
let x = i % WIDTH; | |
let y = i / WIDTH; | |
let r = (x as f32 / WIDTH as f32 * 12.0 * count as f32) as u8; | |
let g = (y as f32 / HEIGHT as f32 * 12.0 * count as f32) as u8; | |
let b = (10.0 * count as f32) as u8; | |
buffer[i] = ((r as u32) << 16) | ((g as u32) << 8) | (b as u32); | |
} | |
window.update_with_buffer(&buffer, WIDTH, HEIGHT).unwrap(); | |
drawing_times.push(draw_start.elapsed().as_secs_f32()); | |
if drawing_times.len() > MAX_PERF_SAMPLE_FRAMES { | |
drawing_times.remove(0); | |
} | |
frame_count += 1; | |
if now.duration_since(last_fps_time).as_secs_f32() >= 1.0 { | |
fps = frame_count; | |
frame_count = 0; | |
last_fps_time = now; | |
let avg_simulation_time: f32 = | |
simulation_times.iter().sum::<f32>() / simulation_times.len() as f32; | |
let avg_drawing_time: f32 = | |
drawing_times.iter().sum::<f32>() / drawing_times.len() as f32; | |
println!("FPS: {}", fps); | |
println!("Avg Simulation Time: {:.5}", avg_simulation_time * 1000.0); | |
println!("Avg Drawing Time: {:.5}", avg_drawing_time * 1000.0); | |
} | |
} | |
} |
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 how_fast_is_rust::thread_pool::ThreadPool; | |
use minifb::{Key, MouseButton, Window, WindowOptions}; | |
use rand::Rng; | |
use std::time::Instant; | |
const WIDTH: usize = 1200; | |
const HEIGHT: usize = 800; | |
const PARTICLE_COUNT: usize = 10_000_000; | |
const THREAD_COUNT: usize = 8; | |
const PARTICLE_STRIDE: usize = 2; | |
const GRAVITY_STRENGTH: f32 = 200.5; | |
const MAX_PERF_SAMPLE_FRAMES: usize = 100; | |
fn main() { | |
let mut shared_pos_buffer = vec![0.0; PARTICLE_COUNT * PARTICLE_STRIDE].into_boxed_slice(); | |
let mut shared_vel_buffer = vec![0.0; PARTICLE_COUNT * PARTICLE_STRIDE].into_boxed_slice(); | |
let mut shared_p_count_buffer = vec![0 as u8; WIDTH * HEIGHT * THREAD_COUNT].into_boxed_slice(); | |
let mut rng = rand::thread_rng(); | |
let mut frame_buffer: Vec<u32> = vec![0; WIDTH * HEIGHT]; | |
let mut p_count_buffer = vec![0 as u8; WIDTH * HEIGHT]; | |
let mut window = Window::new("Safu", WIDTH, HEIGHT, WindowOptions::default()).unwrap_or_else(|e| { | |
panic!("{}", e); | |
}); | |
window.set_target_fps(120); | |
let mut i: usize = 0; | |
while i < PARTICLE_COUNT { | |
let idx = i * PARTICLE_STRIDE; | |
shared_pos_buffer[idx] = rng.gen_range(0.0..WIDTH as f32); | |
shared_pos_buffer[idx + 1] = rng.gen_range(0.0..HEIGHT as f32); | |
shared_vel_buffer[idx] = rng.gen_range(-2.0..2.0); | |
shared_vel_buffer[idx + 1] = rng.gen_range(-2.0..2.0); | |
i += 1; | |
} | |
let mut mouse_pos = (WIDTH as f32 / 2.0, HEIGHT as f32 / 2.0); | |
let mut mouse_pressed = false; | |
let friction_per_second = 0.99_f32.powf(60.0); | |
let mut frame_count = 0; | |
let mut last_fps_time = Instant::now(); | |
let mut last_frame_time = Instant::now(); | |
let mut simulation_times = vec![]; | |
let mut drawing_times = vec![]; | |
let mut fps = 0; | |
let pool = ThreadPool::new(THREAD_COUNT); | |
let chunk_size = PARTICLE_COUNT / THREAD_COUNT; | |
let shared_pos_buff_ptr = shared_pos_buffer.as_mut_ptr(); | |
let shared_vel_buff_ptr = shared_vel_buffer.as_mut_ptr(); | |
let shared_p_count_buff_ptr = shared_p_count_buffer.as_mut_ptr(); | |
while window.is_open() && !window.is_key_down(Key::Escape) { | |
let now = Instant::now(); | |
let delta_time = now.duration_since(last_frame_time).as_secs_f32(); | |
last_frame_time = now; | |
let sim_start = Instant::now(); | |
if let Some((mx, my)) = window.get_mouse_pos(minifb::MouseMode::Clamp) { | |
mouse_pos = (mx, my); | |
} | |
mouse_pressed = window.get_mouse_down(MouseButton::Left); | |
// clear p_counts | |
shared_p_count_buffer.iter_mut().for_each(|c| *c = 0); | |
for t in 0..THREAD_COUNT { | |
let mouse_pos = mouse_pos; | |
let p_data_start = t * chunk_size * PARTICLE_STRIDE; | |
let p_data_end = if t == THREAD_COUNT - 1 { | |
PARTICLE_COUNT * PARTICLE_STRIDE | |
} else { | |
(t + 1) * chunk_size * PARTICLE_STRIDE | |
}; | |
let p_count_start = t * WIDTH * HEIGHT; | |
let p_count_end = if t == THREAD_COUNT - 1 { | |
WIDTH * HEIGHT | |
} else { | |
(t + 1) * WIDTH * HEIGHT | |
}; | |
unsafe { | |
let pos_chunk = std::slice::from_raw_parts_mut(shared_pos_buff_ptr.add(p_data_start), p_data_end - p_data_start); | |
let vel_chunk = std::slice::from_raw_parts_mut(shared_vel_buff_ptr.add(p_data_start), p_data_end - p_data_start); | |
let p_count_chunk = std::slice::from_raw_parts_mut(shared_p_count_buff_ptr.add(p_count_start), p_count_end - p_count_start); | |
let friction = friction_per_second.powf(delta_time); | |
pool.execute(move || { | |
for (pos, vel) in pos_chunk.chunks_mut(2).zip(vel_chunk.chunks_mut(2)) { | |
if mouse_pressed { | |
let dx = mouse_pos.0 - pos[0]; | |
let dy = mouse_pos.1 - pos[1]; | |
let distance = (dx * dx + dy * dy).sqrt(); | |
if distance > 2.0 { | |
let inv_gravity = GRAVITY_STRENGTH / distance; | |
vel[0] += dx * inv_gravity * 8.0 * delta_time; | |
vel[1] += dy * inv_gravity * 8.0 * delta_time; | |
} | |
} | |
vel[0] *= friction; | |
vel[1] *= friction; | |
pos[0] += vel[0] * delta_time; | |
pos[1] += vel[1] * delta_time; | |
let x = pos[0] as usize; | |
let y = pos[1] as usize; | |
if x >= WIDTH || y >= HEIGHT || x <= 0 || y <= 0 { | |
continue; | |
} | |
let buffer_index = y * WIDTH + x; | |
let p_count = p_count_chunk[buffer_index]; | |
p_count_chunk[buffer_index] = p_count.saturating_add(1); | |
} | |
}); | |
} | |
} | |
pool.wait_for_completion(); | |
simulation_times.push(sim_start.elapsed().as_secs_f32()); | |
if simulation_times.len() > MAX_PERF_SAMPLE_FRAMES { | |
simulation_times.remove(0); | |
} | |
let draw_start = Instant::now(); | |
p_count_buffer.iter_mut().for_each(|c| *c = 0); | |
for counts in shared_p_count_buffer.chunks_mut(WIDTH*HEIGHT) { | |
for (i, &count) in counts.iter().enumerate() { | |
p_count_buffer[i] = p_count_buffer[i].saturating_add(count); | |
} | |
} | |
frame_buffer.iter_mut().for_each(|pixel| *pixel = 0); | |
for (i, &count) in p_count_buffer.iter().enumerate() { | |
let x = i % WIDTH; | |
let y = i / WIDTH; | |
let r = (x as f32 / WIDTH as f32 * 55.0 * count.clamp(0, 4) as f32) as u8; | |
let g = (y as f32 / HEIGHT as f32 * 55.0 * count.clamp(0, 4) as f32) as u8; | |
let b = (55.0 * count.clamp(0, 4) as f32) as u8; | |
frame_buffer[i] = ((r as u32) << 16) | ((g as u32) << 8) | (b as u32); | |
} | |
window.update_with_buffer(&frame_buffer, WIDTH, HEIGHT).unwrap(); | |
drawing_times.push(draw_start.elapsed().as_secs_f32()); | |
if drawing_times.len() > MAX_PERF_SAMPLE_FRAMES { | |
drawing_times.remove(0); | |
} | |
frame_count += 1; | |
if now.duration_since(last_fps_time).as_secs_f32() >= 1.0 { | |
fps = frame_count; | |
frame_count = 0; | |
last_fps_time = now; | |
let avg_simulation_time: f32 = | |
simulation_times.iter().sum::<f32>() / simulation_times.len() as f32; | |
let avg_drawing_time: f32 = | |
drawing_times.iter().sum::<f32>() / drawing_times.len() as f32; | |
println!("FPS: {}", fps); | |
println!("Avg Simulation Time: {:.5}", avg_simulation_time * 1000.0); | |
println!("Avg Drawing Time: {:.5}", avg_drawing_time * 1000.0); | |
} | |
} | |
} |
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
#![feature(portable_simd)] | |
use std::simd::{cmp::SimdPartialOrd, f32x8, simd_swizzle, StdFloat}; | |
use how_fast_is_rust::thread_pool::ThreadPool; | |
use minifb::{Key, MouseButton, Window, WindowOptions}; | |
use rand::Rng; | |
use std::time::Instant; | |
const WIDTH: usize = 1200; | |
const HEIGHT: usize = 800; | |
const PARTICLE_COUNT: usize = 100_000_000; | |
const THREAD_COUNT: usize = 8; | |
const PARTICLE_STRIDE: usize = 2; | |
const GRAVITY_STRENGTH: f32 = 200.5 * 7.0; | |
const MAX_PERF_SAMPLE_FRAMES: usize = 10; | |
fn main() { | |
let mut shared_pos_buffer = vec![0.0; PARTICLE_COUNT * PARTICLE_STRIDE].into_boxed_slice(); | |
let mut shared_vel_buffer = vec![0.0; PARTICLE_COUNT * PARTICLE_STRIDE].into_boxed_slice(); | |
let mut shared_p_count_buffer = vec![0 as u8; WIDTH * HEIGHT * THREAD_COUNT].into_boxed_slice(); | |
let mut rng = rand::thread_rng(); | |
let mut frame_buffer: Vec<u32> = vec![0; WIDTH * HEIGHT]; | |
let mut p_count_buffer = vec![0 as u8; WIDTH * HEIGHT]; | |
let mut window = Window::new("Safu", WIDTH, HEIGHT, WindowOptions::default()).unwrap_or_else(|e| { | |
panic!("{}", e); | |
}); | |
window.set_target_fps(120); | |
let mut i: usize = 0; | |
while i < PARTICLE_COUNT { | |
let idx = i * PARTICLE_STRIDE; | |
shared_pos_buffer[idx] = rng.gen_range(0.0..WIDTH as f32); | |
shared_pos_buffer[idx + 1] = rng.gen_range(0.0..HEIGHT as f32); | |
shared_vel_buffer[idx] = rng.gen_range(-2.0..2.0); | |
shared_vel_buffer[idx + 1] = rng.gen_range(-2.0..2.0); | |
i += 1; | |
} | |
let mut mouse_pos = (WIDTH as f32 / 2.0, HEIGHT as f32 / 2.0); | |
let mut mouse_pressed = false; | |
let friction_per_second = 0.99_f32.powf(60.0); | |
let mut frame_count = 0; | |
let mut last_fps_time = Instant::now(); | |
let mut last_frame_time = Instant::now(); | |
let mut simulation_times = vec![]; | |
let mut drawing_times = vec![]; | |
let mut fps = 0; | |
let pool = ThreadPool::new(THREAD_COUNT); | |
let chunk_size = PARTICLE_COUNT / THREAD_COUNT; | |
let shared_pos_buff_ptr = shared_pos_buffer.as_mut_ptr(); | |
let shared_vel_buff_ptr = shared_vel_buffer.as_mut_ptr(); | |
let shared_p_count_buff_ptr = shared_p_count_buffer.as_mut_ptr(); | |
while window.is_open() && !window.is_key_down(Key::Escape) { | |
let now = Instant::now(); | |
let delta_time = now.duration_since(last_frame_time).as_secs_f32(); | |
last_frame_time = now; | |
let sim_start = Instant::now(); | |
if let Some((mx, my)) = window.get_mouse_pos(minifb::MouseMode::Clamp) { | |
mouse_pos = (mx, my); | |
} | |
mouse_pressed = window.get_mouse_down(MouseButton::Left); | |
// clear p_counts | |
shared_p_count_buffer.iter_mut().for_each(|c| *c = 0); | |
for t in 0..THREAD_COUNT { | |
let mouse_pos = mouse_pos; | |
let p_data_start = t * chunk_size * PARTICLE_STRIDE; | |
let p_data_end = if t == THREAD_COUNT - 1 { | |
PARTICLE_COUNT * PARTICLE_STRIDE | |
} else { | |
(t + 1) * chunk_size * PARTICLE_STRIDE | |
}; | |
let p_count_start = t * WIDTH * HEIGHT; | |
let p_count_end = if t == THREAD_COUNT - 1 { | |
WIDTH * HEIGHT | |
} else { | |
(t + 1) * WIDTH * HEIGHT | |
}; | |
unsafe { | |
let pos_chunk = std::slice::from_raw_parts_mut(shared_pos_buff_ptr.add(p_data_start), p_data_end - p_data_start); | |
let vel_chunk = std::slice::from_raw_parts_mut(shared_vel_buff_ptr.add(p_data_start), p_data_end - p_data_start); | |
let p_count_chunk = std::slice::from_raw_parts_mut(shared_p_count_buff_ptr.add(p_count_start), p_count_end - p_count_start); | |
let friction = friction_per_second.powf(delta_time); | |
pool.execute(move || { | |
let gravity_vec = f32x8::splat(GRAVITY_STRENGTH); | |
let delta_time_vec = f32x8::splat(delta_time); | |
let friction_vec = f32x8::splat(friction); | |
let mouse_pos_vec = f32x8::from_array([mouse_pos.0, mouse_pos.1, mouse_pos.0, mouse_pos.1, mouse_pos.0, mouse_pos.1, mouse_pos.0, mouse_pos.1]); | |
let threshold_vec = f32x8::splat(2.0); | |
for (pos, vel) in pos_chunk.chunks_exact_mut(8).zip(vel_chunk.chunks_exact_mut(8)) { | |
let mut pos_vec = f32x8::from_slice(pos); | |
let mut vel_vec = f32x8::from_slice(vel); | |
if mouse_pressed { | |
let delta = mouse_pos_vec - pos_vec; | |
let distance_sq = delta * delta; | |
let distance_vec: f32x8 = (simd_swizzle!(distance_sq, [1, 0, 3, 2, 5, 4, 7, 6]) + distance_sq).sqrt(); | |
let mask = distance_vec.simd_gt(threshold_vec); | |
let inv_gravity = gravity_vec / distance_vec; | |
let force = delta * inv_gravity * delta_time_vec; | |
vel_vec = mask.select(vel_vec + force, vel_vec); | |
} | |
vel_vec *= friction_vec; | |
pos_vec += vel_vec * delta_time_vec; | |
pos.copy_from_slice(pos_vec.as_array()); | |
vel.copy_from_slice(vel_vec.as_array()); | |
for j in (0..8).step_by(2) { | |
let x = pos[j] as usize; | |
let y = pos[j + 1] as usize; | |
let buffer_index = y.max(0).min(HEIGHT-1) * WIDTH + x.max(0).min(WIDTH-1); | |
let p_count = p_count_chunk.get_unchecked(buffer_index); | |
p_count_chunk[buffer_index] = p_count.saturating_add(1); | |
} | |
} | |
}); | |
} | |
} | |
pool.wait_for_completion(); | |
simulation_times.push(sim_start.elapsed().as_secs_f32()); | |
if simulation_times.len() > MAX_PERF_SAMPLE_FRAMES { | |
simulation_times.remove(0); | |
} | |
let draw_start = Instant::now(); | |
p_count_buffer.iter_mut().for_each(|c| *c = 0); | |
for counts in shared_p_count_buffer.chunks_mut(WIDTH*HEIGHT) { | |
for (i, &count) in counts.iter().enumerate() { | |
p_count_buffer[i] = p_count_buffer[i].saturating_add(count); | |
} | |
} | |
frame_buffer.iter_mut().for_each(|pixel| *pixel = 0); | |
for (i, &count) in p_count_buffer.iter().enumerate() { | |
let x = i % WIDTH; | |
let y = i / WIDTH; | |
let r = (x as f32 / WIDTH as f32 * 10.0 * count.clamp(0, 30) as f32) as u8; | |
let g = (y as f32 / HEIGHT as f32 * 10.0 * count.clamp(0, 30) as f32) as u8; | |
let b = (10.0 * count.clamp(0, 30) as f32) as u8; | |
frame_buffer[i] = ((r as u32) << 16) | ((g as u32) << 8) | (b as u32); | |
} | |
window.update_with_buffer(&frame_buffer, WIDTH, HEIGHT).unwrap(); | |
drawing_times.push(draw_start.elapsed().as_secs_f32()); | |
if drawing_times.len() > MAX_PERF_SAMPLE_FRAMES { | |
drawing_times.remove(0); | |
} | |
frame_count += 1; | |
if now.duration_since(last_fps_time).as_secs_f32() >= 1.0 { | |
fps = frame_count; | |
frame_count = 0; | |
last_fps_time = now; | |
let avg_simulation_time: f32 = | |
simulation_times.iter().sum::<f32>() / simulation_times.len() as f32; | |
let avg_drawing_time: f32 = | |
drawing_times.iter().sum::<f32>() / drawing_times.len() as f32; | |
println!("FPS: {}", fps); | |
println!("Avg Simulation Time: {:.5}", avg_simulation_time * 1000.0); | |
println!("Avg Drawing Time: {:.5}", avg_drawing_time * 1000.0); | |
} | |
} | |
} |
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 minifb::{Key, MouseButton, Window, WindowOptions}; | |
use rand::Rng; | |
use std::time::{Instant}; | |
use std::f32::consts::PI; | |
const WIDTH: usize = 600; | |
const HEIGHT: usize = 400; | |
const PARTICLE_COUNT: usize = 80_000; | |
const GRAVITY_STRENGTH: f32 = 105.0; | |
#[repr(align(16))] | |
struct Particle { | |
x: f32, | |
y: f32, | |
angle: f32, | |
speed: f32, | |
_type: u8, | |
} | |
fn main() { | |
let width = WIDTH as f32; | |
let height = HEIGHT as f32; | |
let spawn_width = 100; | |
let spawn_height = 100; | |
let mut rng = rand::thread_rng(); | |
let mut particles = Vec::with_capacity(PARTICLE_COUNT); | |
for _ in 0..PARTICLE_COUNT { | |
particles.push(Particle { | |
x: rng.gen_range((WIDTH / 2 - spawn_width / 2) as f32..(WIDTH/2 + spawn_width/2) as f32), | |
y: rng.gen_range((HEIGHT / 2 - spawn_height / 2) as f32..(HEIGHT/2 + spawn_height/2) as f32), | |
angle: rng.gen_range(0.0..360.0) * PI / 180.0, | |
speed: 80.0 + rng.gen_range(0.0..10.0), | |
_type: rng.gen_range(0..40), | |
}); | |
} | |
let mut frame_buff: Vec<u32> = vec![0; WIDTH * HEIGHT]; | |
let mut trail_buff: Vec<f32> = vec![0.0; WIDTH * HEIGHT]; | |
let mut window = Window::new("Safu", WIDTH, HEIGHT, WindowOptions::default()) | |
.unwrap_or_else(|e| { | |
panic!("{}", e); | |
}); | |
let mut mouse_pos = (WIDTH as f32 / 2.0, HEIGHT as f32 / 2.0); | |
let mut mouse_pressed = false; | |
let deposition: f32 = 3.0; | |
let sense_dist: f32 = 18.0; | |
let sense_angle: f32 = PI / 180.0 * 18.0; | |
let rotate_angle_dist: f32 = PI / 180.0 * 12.0; | |
let rotation_speed: f32 = 4.0; | |
let sensor_size: i32 = 1; | |
let diffusion_rate: f32 = 24.0; | |
let blur_size = 1; | |
let mut last_frame_time = Instant::now(); | |
while window.is_open() && !window.is_key_down(Key::Escape) { | |
let now = Instant::now(); | |
let delta_time = now.duration_since(last_frame_time).as_secs_f32(); | |
last_frame_time = now; | |
if let Some((mx, my)) = window.get_mouse_pos(minifb::MouseMode::Clamp) { | |
mouse_pos = (mx, my); | |
} | |
mouse_pressed = window.get_mouse_down(MouseButton::Left); | |
for p in particles.iter_mut() { | |
let dt = delta_time; | |
let mut nx = p.x + p.speed * p.angle.cos() * dt; | |
let mut ny = p.y + p.speed * p.angle.sin() * dt; | |
if mouse_pressed { | |
let dx = mouse_pos.0 - nx; | |
let dy = mouse_pos.1 - ny; | |
let dist = (dx*dx + dy*dy).sqrt(); | |
if dist < 110.0 { | |
nx -= dx * GRAVITY_STRENGTH/dist *dt * 1.5; | |
ny -= dy * GRAVITY_STRENGTH/dist *dt * 1.5; | |
p.angle *= -1.0; | |
} | |
nx += dx * GRAVITY_STRENGTH/dist *dt * 0.25; | |
ny += dy * GRAVITY_STRENGTH/dist *dt * 0.25; | |
} | |
if nx <= 0.0 || nx >= width || ny <= 0.0 || ny >= height { | |
p.angle = rng.gen_range(-1.0..1.0) * 2.0 * PI; | |
continue; | |
} | |
p.x = nx; | |
p.y = ny; | |
if p._type <= 2 { | |
trail_buff[p.y as usize * WIDTH + p.x as usize] -= deposition * dt; | |
} else if p._type == 9 { | |
trail_buff[p.y as usize * WIDTH + p.x as usize] -= deposition * dt * 2.30; | |
} else { | |
trail_buff[p.y as usize * WIDTH + p.x as usize] += deposition * dt; | |
} | |
let front = sense(&p, 0.0, sense_dist, sensor_size, &trail_buff); | |
let left = sense(&p, -sense_angle, sense_dist, sensor_size, &trail_buff); | |
let right = sense(&p, sense_angle, sense_dist, sensor_size, &trail_buff); | |
if front > left && front > right { | |
continue; | |
} | |
if front < left && front < right { | |
if rng.gen_range(0.0..1.0) > 0.5 { | |
p.angle += rotate_angle_dist * rotation_speed * dt; | |
} else { | |
p.angle -= rotate_angle_dist * rotation_speed * dt; | |
} | |
continue; | |
} | |
if left < right { | |
p.angle += rotate_angle_dist * rotation_speed * dt; | |
} else { | |
p.angle -= rotate_angle_dist * rotation_speed * dt; | |
} | |
} | |
diffuse(&mut trail_buff, diffusion_rate, delta_time, blur_size); | |
frame_buff.iter_mut().for_each(|pixel| *pixel = 0); | |
for y in 0..HEIGHT { | |
for x in 0..WIDTH { | |
let index = y * WIDTH + x; | |
let trail_intensity = trail_buff[index]; | |
let intensity_byte = (trail_intensity * 255.0).min(255.0).max(0.0) as f32; | |
let sx = x as f32 / width; | |
let sy = y as f32 / height; | |
let r = (intensity_byte*sy) as u32; | |
let g = (intensity_byte*sx) as u32; | |
let b = (intensity_byte*0.5) as u32; | |
let packed_pixel = (r << 16) | (g << 8) | b; | |
frame_buff[index] = packed_pixel; | |
} | |
} | |
for particle in particles.iter() { | |
let x = particle.x as usize; | |
let y = particle.y as usize; | |
if x < WIDTH && y < HEIGHT && x > 0 && y > 0 { | |
let red = (particle.y / height * 255.0 * 0.1) as u32; | |
let green = (particle.x / width * 255.0 * 0.1) as u32; | |
let blue = (255.0 * 0.1) as u32; | |
let current_color = frame_buff[y * WIDTH + x]; | |
let current_red = (current_color >> 16) & 0xFF; | |
let current_green = (current_color >> 8) & 0xFF; | |
let current_blue = current_color & 0xFF; | |
let blended_red = (current_red + red).min(255); | |
let blended_green = (current_green + green).min(255); | |
let blended_blue = (current_blue + blue).min(255); | |
let blended_color = (blended_red << 16) | (blended_green << 8) | blended_blue; | |
frame_buff[y * WIDTH + x] = blended_color; | |
} | |
} | |
window.update_with_buffer(&frame_buff, WIDTH, HEIGHT).unwrap(); | |
} | |
} | |
#[inline] | |
fn sense(p: &Particle, angle_offset: f32, sense_dist: f32, sense_size: i32, trail_map: &Vec<f32>) -> f32 { | |
let sensor_angle = p.angle + angle_offset; | |
let sense_x = p.x + sense_dist * sensor_angle.cos(); | |
let sense_y = p.y + sense_dist * sensor_angle.sin(); | |
let mut sum: f32 = 0.0; | |
for offset_x in -sense_size..sense_size { | |
for offset_y in -sense_size..sense_size { | |
let sample_x = (sense_x as i32 + offset_x).clamp(0, WIDTH as i32 - 1); | |
let sample_y = (sense_y as i32 + offset_y).clamp(0, HEIGHT as i32 - 1); | |
sum += trail_map[sample_y as usize * WIDTH + sample_x as usize] | |
} | |
} | |
sum | |
} | |
fn diffuse(trail_map: &mut Vec<f32>, diffusion_rate: f32, dt: f32, blur_size: isize) { | |
let mut new_map = trail_map.clone(); | |
let df: f32 = dt * diffusion_rate; | |
for y in 0..HEIGHT { | |
for x in 0..WIDTH { | |
let index = y * WIDTH + x; | |
let current_value = trail_map[index]; | |
let mut total_contribution = current_value; | |
let mut count = 1.0; | |
for dy in -blur_size..=blur_size { | |
for dx in -blur_size..=blur_size { | |
if dx == 0 && dy == 0 { | |
continue; | |
} | |
let nx = x as isize + dx; | |
let ny = y as isize + dy; | |
if nx >= 0 && nx < WIDTH as isize && ny >= 0 && ny < HEIGHT as isize { | |
let neighbor_index = ny as usize * WIDTH + nx as usize; | |
total_contribution += trail_map[neighbor_index]; | |
count += 1.0; | |
} | |
} | |
} | |
let new_value = (current_value + (df * (total_contribution / count))) * (1.0-df); | |
new_map[index] = new_value; | |
} | |
} | |
for (i, val) in new_map.iter().enumerate() { | |
trail_map[i] = *val; | |
} | |
} |
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
<!DOCTYPE html> | |
<html lang="en" style="user-select: none"> | |
<head> | |
<meta charset="UTF-8" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
<title>Sabby Rust</title> | |
<link rel="icon" href="./favicon.ico" type="image/x-icon" /> | |
<style> | |
body, | |
html, | |
div, | |
canvas { | |
-webkit-user-select: none; | |
-moz-user-select: none; | |
-webkit-touch-callout: none; | |
-ms-user-select: none; | |
user-select: none; | |
outline: none; | |
} | |
</style> | |
</head> | |
<body | |
style=" | |
margin: 0; | |
width: 100vw; | |
height: 100vh; | |
background: black; | |
-webkit-touch-callout: none; | |
overflow: hidden; | |
" | |
> | |
<div> | |
<canvas id="canvas"></canvas> | |
</div> | |
<script type="module"> | |
import init, { InitInput, TickInput, init as initSimulation, tick } from './how_fast_is_rust.js'; | |
async function runSimulation() { | |
const instance = await init(); | |
const particle_count = 500_000; | |
const gravity = 205.5 * 8.0; | |
const canvas = document.getElementById('canvas'); | |
canvas.style.touchAction = 'none'; | |
canvas.width = window.innerWidth; | |
canvas.height = window.innerHeight; | |
const ctx = canvas.getContext('2d'); | |
let imageData = ctx.createImageData(canvas.width, canvas.height); | |
let frameBuffer = new Uint32Array(imageData.data.buffer); | |
function resize() { | |
canvas.width = window.innerWidth; | |
canvas.height = window.innerHeight; | |
imageData = ctx.createImageData(canvas.width, canvas.height); | |
frameBuffer = new Uint32Array(imageData.data.buffer); | |
initSimulation(new InitInput(canvas.width, canvas.height, particle_count, gravity)); | |
} | |
resize(); | |
let mousePressed = false; | |
let mouseX = canvas.width / 2; | |
let mouseY = canvas.height / 2; | |
window.addEventListener('resize', resize); | |
window.addEventListener('mousedown', () => mousePressed = true); | |
window.addEventListener('mouseup', () => mousePressed = false); | |
window.addEventListener('mousemove', (event) => { | |
mouseX = event.offsetX; | |
mouseY = event.offsetY; | |
}); | |
window.addEventListener('touchend', (event) => { | |
mousePressed = false; | |
}); | |
window.addEventListener('touchmove', (event) => { | |
mousePressed = true; | |
mouseX = event.touches[0].clientX; | |
mouseY = event.touches[0].clientY; | |
}); | |
window.addEventListener('touchstart', (event) => { | |
event.preventDefault(); | |
mousePressed = true; | |
mouseX = event.touches[0].clientX; | |
mouseY = event.touches[0].clientY; | |
}); | |
let prev = 0; | |
function render(timestamp = 0) { | |
const dt = (timestamp - prev)/1000; | |
prev = timestamp; | |
const frame_buff_ptr = tick(new TickInput(dt, mouseX, mouseY, mousePressed)); | |
const buff = new Uint8Array(instance.memory.buffer, frame_buff_ptr, canvas.width * canvas.height*4); | |
for(let i = 0; i < frameBuffer.length;i++) { | |
const r = buff[i*4]; | |
const g = buff[i*4+1]; | |
const b = buff[i*4+2]; | |
const a = buff[i*4+3]; | |
frameBuffer[i] = (r << 24) | (g << 16) | (b << 8) | a; | |
} | |
ctx.putImageData(imageData, 0, 0); | |
ctx.drawImage(canvas, 0, 0); | |
requestAnimationFrame(render); | |
} | |
render(); | |
} | |
runSimulation().catch(e => console.error("Could not run simulation:", e)); | |
</script> | |
</body> | |
</html> |
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
#![no_main] | |
use rand::Rng; | |
use wasm_bindgen::prelude::*; | |
#[repr(align(16))] | |
struct Particle { | |
x: f32, | |
y: f32, | |
// sx: f32, | |
// sy: f32, | |
dx: f32, | |
dy: f32, | |
} | |
static mut PARTICLES: Vec<Particle> = Vec::new(); | |
static mut BUFFER: Vec<u32> = Vec::new(); | |
static mut WIDTH: usize = 1; | |
static mut HEIGHT: usize = 1; | |
static mut GRAVITY_STRENGTH: f32 = 205.5 * 6.0; | |
#[wasm_bindgen] | |
pub struct TickInput { | |
pub dt: f32, | |
pub touch_x: f32, | |
pub touch_y: f32, | |
pub is_touch_down: bool, | |
} | |
#[wasm_bindgen] | |
impl TickInput { | |
#[wasm_bindgen(constructor)] | |
pub fn new(dt: f32, touch_x: f32, touch_y: f32, is_touch_down: bool) -> TickInput { | |
TickInput { dt, touch_x, touch_y, is_touch_down } | |
} | |
} | |
fn generate_particles(count: usize, width: usize, height: usize) -> Vec<Particle> { | |
let mut rng = rand::thread_rng(); | |
let mut particles = Vec::with_capacity(count); | |
for _ in 0..count { | |
let x = rng.gen_range(0.0..width as f32); | |
let y = rng.gen_range(0.0..height as f32); | |
particles.push(Particle { | |
x, | |
y, | |
// sx: x, | |
// sy: y, | |
dx: rng.gen_range(-1.0..1.0), | |
dy: rng.gen_range(-1.0..1.0), | |
}); | |
} | |
particles | |
} | |
#[wasm_bindgen] | |
pub struct InitInput { | |
pub width: usize, | |
pub height: usize, | |
pub particle_count: usize, | |
pub gravity: f32, | |
} | |
#[wasm_bindgen] | |
impl InitInput { | |
#[wasm_bindgen(constructor)] | |
pub fn new(width: usize, height: usize, particle_count: usize, gravity: f32) -> InitInput { | |
InitInput { width, height, particle_count, gravity } | |
} | |
} | |
#[wasm_bindgen] | |
pub fn init(input: InitInput) { | |
unsafe { | |
WIDTH = input.width; | |
HEIGHT = input.height; | |
if PARTICLES.len() != input.particle_count { | |
PARTICLES = generate_particles(input.particle_count, WIDTH, HEIGHT); | |
} | |
BUFFER = vec![0; WIDTH * HEIGHT]; | |
} | |
} | |
#[wasm_bindgen] | |
pub fn tick(input: TickInput) -> *const u32 { | |
let friction_per_second = 0.985_f32.powf(60.0); | |
let dt = input.dt; | |
let touch_x = input.touch_x; | |
let touch_y = input.touch_y; | |
let is_touch_down = input.is_touch_down; | |
unsafe { | |
for particle in PARTICLES.iter_mut() { | |
if is_touch_down { | |
let dx = touch_x - particle.x; | |
let dy = touch_y - particle.y; | |
let distance = (dx * dx + dy * dy).sqrt(); | |
if distance > 0.2 { | |
let inv_gravity = GRAVITY_STRENGTH / distance; | |
particle.dx += dx * inv_gravity * dt; | |
particle.dy += dy * inv_gravity * dt; | |
} | |
} | |
// let dx = particle.sx - particle.x; | |
// let dy = particle.sy - particle.y; | |
// let distance = (dx * dx + dy * dy).sqrt(); | |
// if distance > 4.0 { | |
// let mut inv_gravity = 200.0 / distance; | |
// if distance > 16.0 { | |
// inv_gravity *= 2.5; | |
// } | |
// particle.dx += dx * inv_gravity * dt; | |
// particle.dy += dy * inv_gravity * dt; | |
// } | |
// if distance > 30.0 { | |
// let inv_gravity = 2_000.0 / distance; | |
// particle.dx += dx * inv_gravity * dt; | |
// particle.dy += dy * inv_gravity * dt; | |
// } | |
let friction = friction_per_second.powf(dt); | |
particle.dx *= friction; | |
particle.dy *= friction; | |
particle.x += particle.dx * dt; | |
particle.y += particle.dy * dt; | |
} | |
BUFFER.iter_mut().for_each(|pixel| *pixel = 0); | |
for particle in &PARTICLES { | |
let x = particle.x as usize; | |
let y = particle.y as usize; | |
let idx = y.max(0).min(HEIGHT-1) * WIDTH + x.max(0).min(WIDTH-1); | |
let old_c = BUFFER[idx]; | |
let r = (old_c >> 24) & 0xFF; | |
let g = (old_c >> 16) & 0xFF; | |
let b = (old_c >> 8) & 0xFF; | |
let red = r + (particle.y / HEIGHT as f32 * 255.0 * 0.5) as u32; | |
let green = g + (particle.x / WIDTH as f32 * 255.0 * 0.5) as u32; | |
let blue = b + (255.0 * 0.8) as u32; | |
let color = (red.min(255) << 24) | (green.min(255) << 16) | (blue.min(255) << 8) | 255; | |
BUFFER[idx] = color; | |
} | |
BUFFER.as_ptr() | |
} | |
} |
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
#![no_main] | |
use wasm_bindgen::prelude::*; | |
use rand::Rng; | |
use std::f32::consts::PI; | |
const GRAVITY_STRENGTH: f32 = 105.0; | |
#[repr(align(16))] | |
struct Particle { | |
x: f32, | |
y: f32, | |
angle: f32, | |
speed: f32, | |
_type: u8, | |
} | |
#[wasm_bindgen] | |
pub struct ParticleSimulator { | |
width: usize, | |
height: usize, | |
particles: Vec<Particle>, | |
frame_buff: Vec<u32>, | |
trail_buff: Vec<f32>, | |
rng: rand::rngs::ThreadRng, | |
options: SimulationOptions, | |
} | |
#[wasm_bindgen] | |
pub struct SimulationOptions { | |
particle_count: usize, | |
blur_size: isize, | |
deposition: f32, | |
sense_dist: f32, | |
sense_angle: f32, | |
rotate_angle_dist: f32, | |
rotation_speed: f32, | |
sensor_size: i32, | |
diffusion_rate: f32, | |
} | |
#[wasm_bindgen] | |
impl ParticleSimulator { | |
#[wasm_bindgen(constructor)] | |
pub fn new() -> Self { | |
utils::set_panic_hook(); | |
let options = SimulationOptions { | |
particle_count: 80_000, | |
blur_size: 1, | |
deposition: 3.0, | |
sense_dist: 20.0, | |
sense_angle: PI / 180.0 * 15.0, | |
rotate_angle_dist: PI / 180.0 * 12.0, | |
rotation_speed: 4.0, | |
sensor_size: 1, | |
diffusion_rate: 24.0, | |
}; | |
let rng = rand::thread_rng(); | |
ParticleSimulator { | |
width: 0, | |
height: 0, | |
particles: Vec::with_capacity(options.particle_count), | |
frame_buff: vec![0; 1], | |
trail_buff: vec![0.0; 1], | |
rng, | |
options, | |
} | |
} | |
#[no_mangle] | |
pub fn init(&mut self, width: usize, height: usize, particle_count: usize, blur_size: isize, diffusion_rate: f32, deposition: f32 , sense_size: i32, speed: f32) { | |
self.width = width; | |
self.height = height; | |
self.options.particle_count = particle_count; | |
self.options.blur_size = blur_size; | |
self.options.sensor_size = sense_size; | |
self.options.diffusion_rate = diffusion_rate; | |
self.options.deposition = deposition; | |
self.particles.clear(); | |
self.particles.reserve(particle_count); | |
self.frame_buff = vec![0; width * height]; | |
self.trail_buff = vec![0.0; width * height]; | |
let spawn_width = self.width/4; | |
let spawn_height = self.height/4; | |
for _ in 0..particle_count { | |
self.particles.push(Particle { | |
x: self.rng.gen_range((self.width / 2 - spawn_width / 2) as f32..(self.width/2 + spawn_width/2) as f32), | |
y: self.rng.gen_range((self.height / 2 - spawn_height / 2) as f32..(self.height/2 + spawn_height/2) as f32), | |
angle: self.rng.gen_range(0.0..360.0) * PI / 180.0, | |
speed: speed + self.rng.gen_range(0.0..10.0), | |
_type: self.rng.gen_range(0..80), | |
}); | |
} | |
} | |
#[no_mangle] | |
pub fn simulate(&mut self, mouse_x: f32, mouse_y: f32, mouse_pressed: bool, delta_time: f64) -> *const u32 { | |
let dt = delta_time as f32; | |
for p in self.particles.iter_mut() { | |
let mut nx = p.x + p.speed * p.angle.cos() * dt; | |
let mut ny = p.y + p.speed * p.angle.sin() * dt; | |
if mouse_pressed { | |
let dx = mouse_x - nx; | |
let dy = mouse_y - ny; | |
let dist = (dx*dx + dy*dy).sqrt(); | |
if dist < 80.0 { | |
nx -= dx * GRAVITY_STRENGTH/dist * dt * 1.5; | |
ny -= dy * GRAVITY_STRENGTH/dist * dt * 1.5; | |
p.angle *= -1.0; | |
} | |
nx += dx * GRAVITY_STRENGTH/dist * dt * 0.25; | |
ny += dy * GRAVITY_STRENGTH/dist * dt * 0.25; | |
} | |
if nx <= 0.0 || nx >= self.width as f32 || ny <= 0.0 || ny >= self.height as f32 { | |
p.angle = self.rng.gen_range(-1.0..1.0) * 2.0 * PI; | |
continue; | |
} | |
p.x = nx; | |
p.y = ny; | |
if p._type <= 3 { | |
self.trail_buff[p.y as usize * self.width + p.x as usize] -= self.options.deposition * dt; | |
} else if p._type == 4 { | |
self.trail_buff[p.y as usize * self.width + p.x as usize] -= self.options.deposition * dt * 1.30; | |
} else { | |
self.trail_buff[p.y as usize * self.width + p.x as usize] += self.options.deposition * dt; | |
} | |
let front = sense(p, &self.trail_buff, 0.0, self.options.sense_dist, self.options.sensor_size, self.width, self.height); | |
let left = sense(p, &self.trail_buff, -self.options.sense_angle, self.options.sense_dist, self.options.sensor_size, self.width, self.height); | |
let right = sense(p, &self.trail_buff, self.options.sense_angle, self.options.sense_dist, self.options.sensor_size, self.width, self.height); | |
// let front: f32 = 0.0; | |
// let left: f32 = 0.0; | |
// let right: f32 = 0.0; | |
if front > left && front > right { | |
continue; | |
} | |
if front < left && front < right { | |
if self.rng.gen_range(0.0..1.0) > 0.5 { | |
p.angle += self.options.rotate_angle_dist * self.options.rotation_speed * dt; | |
} else { | |
p.angle -= self.options.rotate_angle_dist * self.options.rotation_speed * dt; | |
} | |
continue; | |
} | |
if left < right { | |
p.angle += self.options.rotate_angle_dist * self.options.rotation_speed * dt; | |
} else { | |
p.angle -= self.options.rotate_angle_dist * self.options.rotation_speed * dt; | |
} | |
} | |
self.diffuse(self.options.diffusion_rate, dt, self.options.blur_size); | |
self.frame_buff.iter_mut().for_each(|pixel| *pixel = 0); | |
for y in 0..self.height { | |
for x in 0..self.width { | |
let index = y * self.width + x; | |
let trail_intensity = self.trail_buff[index]; | |
let intensity_byte = (trail_intensity * 255.0).min(255.0).max(0.0) as f32; | |
let sx = x as f32 / self.width as f32; | |
let sy = y as f32 / self.height as f32; | |
let r = (intensity_byte*sx) as u32; | |
let g = (intensity_byte*sy) as u32; | |
let b = (intensity_byte*(1.0-sx)) as u32; | |
let packed_pixel = (r << 24) | (g << 16) | (b << 8 ) | 255; | |
self.frame_buff[index] = packed_pixel; | |
} | |
} | |
for particle in self.particles.iter() { | |
let x = particle.x as usize; | |
let y = particle.y as usize; | |
if x < self.width && y < self.height && x > 0 && y > 0 { | |
let sx = x as f32 / self.width as f32; | |
let sy = y as f32 / self.height as f32; | |
let red = (sx * 255.0 * 0.15) as u8; | |
let green = (sy * 255.0 * 0.15) as u8; | |
let blue = (255.0 * (1.0-sx) * 0.15) as u8; | |
let current_color = self.frame_buff[y * self.width + x]; | |
let current_red = ((current_color >> 24) & 0xFFFFFF) as u8; | |
let current_green = ((current_color >> 16) & 0xFFFFFF) as u8; | |
let current_blue = ((current_color >> 8) & 0xFFFFFF) as u8; | |
let blended_red = current_red.saturating_add(red); | |
let blended_green = current_green.saturating_add(green); | |
let blended_blue = current_blue.saturating_add(blue); | |
let blended_color: u32 = ((blended_red as u32) << 24) | ((blended_green as u32) << 16) | ((blended_blue as u32) << 8 ) | 255; | |
self.frame_buff[y * self.width + x] = blended_color; | |
} | |
} | |
self.frame_buff.as_ptr() | |
} | |
fn diffuse(&mut self, diffusion_rate: f32, dt: f32, blur_size: isize) { | |
let mut new_map = self.trail_buff.clone(); | |
let df: f32 = dt * diffusion_rate; | |
for y in 0..self.height { | |
for x in 0..self.width { | |
let index = y * self.width + x; | |
let current_value = self.trail_buff[index]; | |
let mut total_contribution = current_value; | |
let mut count = 1.0; | |
for dy in -blur_size..=blur_size { | |
for dx in -blur_size..=blur_size { | |
if dx == 0 && dy == 0 { | |
continue; | |
} | |
let nx = x as isize + dx; | |
let ny = y as isize + dy; | |
if nx >= 0 && nx < self.width as isize && ny >= 0 && ny < self.height as isize { | |
let neighbor_index = ny as usize * self.width + nx as usize; | |
total_contribution += self.trail_buff[neighbor_index]; | |
count += 1.0; | |
} | |
} | |
} | |
let new_value = (current_value + (df * (total_contribution / count))) * (1.0 - df); | |
new_map[index] = new_value; | |
} | |
} | |
for (i, val) in new_map.iter().enumerate() { | |
self.trail_buff[i] = *val; | |
} | |
} | |
} | |
mod utils { | |
pub fn set_panic_hook() { | |
// https://github.com/rustwasm/console_error_panic_hook#readme | |
#[cfg(feature = "console_error_panic_hook")] | |
{ | |
console_error_panic_hook::set_once(); | |
} | |
} | |
} | |
fn sense(p: &Particle, trail_buff: &Vec<f32>, angle_offset: f32, sense_dist: f32, sense_size: i32, width: usize, height: usize,) -> f32 { | |
let sensor_angle = p.angle + angle_offset; | |
let sense_x = p.x + sense_dist * sensor_angle.cos(); | |
let sense_y = p.y + sense_dist * sensor_angle.sin(); | |
let mut sum: f32 = 0.0; | |
for offset_x in -sense_size..sense_size { | |
for offset_y in -sense_size..sense_size { | |
let sample_x = (sense_x as i32 + offset_x).clamp(0, width as i32 - 1); | |
let sample_y = (sense_y as i32 + offset_y).clamp(0, height as i32 - 1); | |
sum += trail_buff[sample_y as usize * width + sample_x as usize]; | |
} | |
} | |
sum | |
} |
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
<!DOCTYPE html> | |
<html lang="en" style="user-select: none"> | |
<head> | |
<meta charset="UTF-8" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
<title>Sabby Rust</title> | |
<link rel="icon" href="./favicon.ico" type="image/x-icon" /> | |
<style> | |
body, | |
html, | |
div, | |
canvas { | |
-webkit-user-select: none; | |
-moz-user-select: none; | |
-webkit-touch-callout: none; | |
-ms-user-select: none; | |
user-select: none; | |
outline: none; | |
} | |
</style> | |
</head> | |
<body | |
style=" | |
margin: 0; | |
width: 100vw; | |
height: 100vh; | |
background: black; | |
-webkit-touch-callout: none; | |
overflow: hidden; | |
" | |
> | |
<div> | |
<canvas id="canvas"></canvas> | |
</div> | |
<script type="module"> | |
import init, { ParticleSimulator } from './how_fast_is_rust.js'; | |
async function runSimulation() { | |
const instance = await init(); | |
const canvas = document.getElementById('canvas'); | |
canvas.style.touchAction = 'none'; | |
canvas.width = window.innerWidth; | |
canvas.height = window.innerHeight; | |
const ctx = canvas.getContext('2d'); | |
let imageData = ctx.createImageData(canvas.width, canvas.height); | |
let frameBuffer = new Uint32Array(imageData.data.buffer); | |
const simulator = new ParticleSimulator(); | |
function resize() { | |
canvas.width = window.innerWidth; | |
canvas.height = window.innerHeight; | |
imageData = ctx.createImageData(canvas.width, canvas.height); | |
frameBuffer = new Uint32Array(imageData.data.buffer); | |
simulator.init(canvas.width, canvas.height, 50000, 0, 14, 5.5, 1, 80); | |
} | |
resize(); | |
let mousePressed = false; | |
let mouseX = canvas.width / 2; | |
let mouseY = canvas.height / 2; | |
window.addEventListener('resize', resize); | |
window.addEventListener('mousedown', () => mousePressed = true); | |
window.addEventListener('mouseup', () => mousePressed = false); | |
window.addEventListener('mousemove', (event) => { | |
mouseX = event.offsetX; | |
mouseY = event.offsetY; | |
}); | |
window.addEventListener('touchend', (event) => { | |
mousePressed = false; | |
}); | |
window.addEventListener('touchmove', (event) => { | |
mousePressed = true; | |
mouseX = event.touches[0].clientX; | |
mouseY = event.touches[0].clientY; | |
}); | |
window.addEventListener('touchstart', (event) => { | |
event.preventDefault(); | |
mousePressed = true; | |
mouseX = event.touches[0].clientX; | |
mouseY = event.touches[0].clientY; | |
}); | |
let prev = 0; | |
function render(timestamp = 0) { | |
const dt = (timestamp - prev)/1000; | |
prev = timestamp; | |
const frame_buff_ptr = simulator.simulate(mouseX, mouseY, mousePressed, 0.016); | |
const buff = new Uint8Array(instance.memory.buffer, frame_buff_ptr, canvas.width * canvas.height*4); | |
for(let i = 0; i < frameBuffer.length;i++) { | |
const r = buff[i*4]; | |
const g = buff[i*4+1]; | |
const b = buff[i*4+2]; | |
const a = buff[i*4+3]; | |
frameBuffer[i] = (r << 24) | (g << 16) | (b << 8) | a; | |
} | |
ctx.putImageData(imageData, 0, 0); | |
// ctx.scale(2,2); | |
ctx.drawImage(canvas, 0, 0); | |
requestAnimationFrame(render); | |
} | |
render(); | |
} | |
runSimulation().catch(e => console.error("Could not run simulation:", e)); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment