Compare commits
2 commits
80686f7b20
...
2e467aca96
Author | SHA1 | Date | |
---|---|---|---|
Logan | 2e467aca96 | ||
Logan | 042055273f |
1158
Cargo.lock
generated
14
Cargo.toml
|
@ -6,19 +6,15 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
log = "0.4"
|
image = "0.25"
|
||||||
image = "0.24"
|
gif = "0.13"
|
||||||
gif = "0.12"
|
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
wgpu = {version="0.18", features=["webgl"]}
|
tokio = {version="1.37", features=["full"]}
|
||||||
|
wgpu = {version="22"}
|
||||||
bytemuck = {version="1.14", features=["derive"]}
|
bytemuck = {version="1.14", features=["derive"]}
|
||||||
futures-intrusive = "0.5"
|
futures-intrusive = "0.5"
|
||||||
pollster = "0.3"
|
rust-cgi = "0.7"
|
||||||
clap = { version = "4.4.16", features = ["derive"] }
|
|
||||||
indicatif = "0.17"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
flexi_logger = "0.27"
|
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
opt-level=1
|
opt-level=1
|
||||||
|
|
11
README.md
|
@ -1,11 +0,0 @@
|
||||||
# Fishbowl
|
|
||||||
This program turns images into animated physics simulations using
|
|
||||||
verlet integration. Image processing is done using WGPU,
|
|
||||||
a low level GPU rendering interface. It accepts most common image
|
|
||||||
formats, and can produce animations of variable quality and size.
|
|
||||||
|
|
||||||
## Web demo
|
|
||||||
Fishbowl is intended to be used as a server-side API that allows users
|
|
||||||
to submit images. However, the previous API was prone to crashing when bots
|
|
||||||
flooded the server with malformed images and requests. Fishbowl will return
|
|
||||||
soon to the new Apache server set up when I have the time to migrate it
|
|
BIN
garden.gif
Before Width: | Height: | Size: 4.7 MiB |
BIN
resources/cat.jpg
Normal file
After Width: | Height: | Size: 86 KiB |
BIN
resources/dog.jpg
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
resources/goback.jpg
Normal file
After Width: | Height: | Size: 326 KiB |
BIN
resources/skull.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
resources/starry.jpg
Normal file
After Width: | Height: | Size: 1 MiB |
BIN
resources/starved.jpg
Normal file
After Width: | Height: | Size: 54 KiB |
BIN
resources/stoic.jpg
Normal file
After Width: | Height: | Size: 1.3 MiB |
5
resources/web-assembly-icon.svg
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 107.62 107.62"><defs><style>
|
||||||
|
.cls-1 {
|
||||||
|
fill: #654ff0;
|
||||||
|
}
|
||||||
|
</style></defs><title>web-assembly-icon</title><g id="Layer_2" data-name="Layer 2"><g id="Notch_-_Purple" data-name="Notch - Purple"><g id="icon"><path class="cls-1" d="M66.12,0c0,.19,0,.38,0,.58a12.34,12.34,0,1,1-24.68,0c0-.2,0-.39,0-.58H0V107.62H107.62V0ZM51.38,96.1,46.14,70.17H46L40.39,96.1H33.18L25,58h7.13L37,83.93h.09L42.94,58h6.67L54.9,84.25H55L60.55,58h7L58.46,96.1Zm39.26,0-2.43-8.48H75.4L73.53,96.1H66.36L75.59,58H86.83L98,96.1Z"/><polygon class="cls-1" points="79.87 67.39 76.76 81.37 86.44 81.37 82.87 67.39 79.87 67.39"/></g></g></g></svg>
|
After Width: | Height: | Size: 685 B |
BIN
resources/wimpy.png
Normal file
After Width: | Height: | Size: 401 KiB |
13
src/draw.rs
|
@ -118,7 +118,7 @@ pub struct QuickDraw {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl QuickDraw {
|
impl QuickDraw {
|
||||||
pub async fn new(width: u32, height: u32, max_circles: u64) -> Self {
|
pub async fn new(width: u32, height: u32, max_circles: u64) -> Option<Self> {
|
||||||
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
||||||
backends: wgpu::Backends::all(),
|
backends: wgpu::Backends::all(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -135,7 +135,7 @@ impl QuickDraw {
|
||||||
Some(a) => a,
|
Some(a) => a,
|
||||||
None => {
|
None => {
|
||||||
eprintln!("Could not find WGPU adapter");
|
eprintln!("Could not find WGPU adapter");
|
||||||
std::process::exit(1);
|
return None;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
let (device, queue) =
|
let (device, queue) =
|
||||||
|
@ -144,7 +144,7 @@ impl QuickDraw {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("Could not find WGPU device:");
|
eprintln!("Could not find WGPU device:");
|
||||||
eprintln!("{}", e);
|
eprintln!("{}", e);
|
||||||
std::process::exit(1);
|
return None;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -244,14 +244,17 @@ impl QuickDraw {
|
||||||
multiview: None,
|
multiview: None,
|
||||||
label: Some("Render Pipeline"),
|
label: Some("Render Pipeline"),
|
||||||
layout: Some(&render_pipeline_layout),
|
layout: Some(&render_pipeline_layout),
|
||||||
|
cache: None,
|
||||||
vertex: wgpu::VertexState {
|
vertex: wgpu::VertexState {
|
||||||
module: &vert,
|
module: &vert,
|
||||||
entry_point: "vs_main",
|
entry_point: "vs_main",
|
||||||
buffers: &[Vertex::desc(), Circle::desc()],
|
buffers: &[Vertex::desc(), Circle::desc()],
|
||||||
|
compilation_options: PipelineCompilationOptions::default(),
|
||||||
},
|
},
|
||||||
fragment: Some(wgpu::FragmentState {
|
fragment: Some(wgpu::FragmentState {
|
||||||
module: &frag,
|
module: &frag,
|
||||||
entry_point: "fs_main",
|
entry_point: "fs_main",
|
||||||
|
compilation_options: PipelineCompilationOptions::default(),
|
||||||
targets: &[Some(wgpu::ColorTargetState {
|
targets: &[Some(wgpu::ColorTargetState {
|
||||||
format: texture_desc.format,
|
format: texture_desc.format,
|
||||||
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
|
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
|
||||||
|
@ -277,7 +280,7 @@ impl QuickDraw {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
Self {
|
Some(Self {
|
||||||
device,
|
device,
|
||||||
queue,
|
queue,
|
||||||
width,
|
width,
|
||||||
|
@ -292,7 +295,7 @@ impl QuickDraw {
|
||||||
output_buffer,
|
output_buffer,
|
||||||
vertex_buffer,
|
vertex_buffer,
|
||||||
pipeline,
|
pipeline,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn resize(&mut self, width: u32, height: u32, max_circles: usize) {
|
pub async fn resize(&mut self, width: u32, height: u32, max_circles: usize) {
|
||||||
|
|
162
src/main.rs
|
@ -1,23 +1,15 @@
|
||||||
#![feature(future_join)]
|
#![feature(future_join)]
|
||||||
use std::{cell::RefCell, future::join, io::Write, path::Path, sync::Arc};
|
use std::{io::Cursor, sync::Arc};
|
||||||
|
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
use draw::QuickDraw;
|
use draw::QuickDraw;
|
||||||
use gif::Frame;
|
use gif::Frame;
|
||||||
use image::{ImageBuffer, Rgb};
|
use image::{ImageBuffer, Rgb};
|
||||||
use indicatif::{ProgressBar, ProgressStyle};
|
|
||||||
use log::debug;
|
|
||||||
|
|
||||||
pub mod draw;
|
pub mod draw;
|
||||||
pub mod helper;
|
pub mod helper;
|
||||||
pub mod sim;
|
pub mod sim;
|
||||||
pub mod tests;
|
|
||||||
|
|
||||||
pub fn make_progress(msg: &'static str, max: u64) -> ProgressBar {
|
|
||||||
let progress = ProgressBar::new(max);
|
|
||||||
progress.set_style(ProgressStyle::with_template("{msg} :: {bar}").unwrap());
|
|
||||||
progress.set_message(msg);
|
|
||||||
progress
|
|
||||||
}
|
|
||||||
|
|
||||||
const WIDTH: f32 = 512.0;
|
const WIDTH: f32 = 512.0;
|
||||||
const HEIGHT: f32 = 512.0;
|
const HEIGHT: f32 = 512.0;
|
||||||
|
@ -27,21 +19,21 @@ pub async fn preprocess(
|
||||||
radius: f32,
|
radius: f32,
|
||||||
) -> (sim::Simulation, usize, usize) {
|
) -> (sim::Simulation, usize, usize) {
|
||||||
let (sim, it, max_circles) =
|
let (sim, it, max_circles) =
|
||||||
sim::Simulation::simulate_image(WIDTH, HEIGHT, radius, image).await;
|
sim::Simulation::simulate_image(WIDTH, HEIGHT, radius, image);
|
||||||
(sim, it, max_circles)
|
(sim, it, max_circles)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn simulate(
|
pub async fn simulate(
|
||||||
draw: &mut QuickDraw,
|
draw: Arc<Mutex<QuickDraw>>,
|
||||||
mut sim: Simulation,
|
mut sim: Simulation,
|
||||||
it: usize,
|
it: usize,
|
||||||
step: usize,
|
step: usize,
|
||||||
max_circles: usize,
|
max_circles: usize,
|
||||||
) -> Vec<Frame<'static>> {
|
) -> Vec<Frame<'static>> {
|
||||||
|
let _draw = draw;
|
||||||
|
let mut draw = _draw.lock().await;
|
||||||
draw.resize(WIDTH as u32, HEIGHT as u32, max_circles).await;
|
draw.resize(WIDTH as u32, HEIGHT as u32, max_circles).await;
|
||||||
let mut frames = vec![];
|
let mut frames = vec![];
|
||||||
let progress =
|
|
||||||
make_progress("Simulating ", ((it - sim.clock) / sim.substeps) as u64);
|
|
||||||
while sim.clock < it {
|
while sim.clock < it {
|
||||||
let circles = &sim
|
let circles = &sim
|
||||||
.circles
|
.circles
|
||||||
|
@ -52,14 +44,12 @@ pub async fn simulate(
|
||||||
color: [c.color.0, c.color.1, c.color.2, 255],
|
color: [c.color.0, c.color.1, c.color.2, 255],
|
||||||
})
|
})
|
||||||
.collect::<Vec<draw::Circle>>();
|
.collect::<Vec<draw::Circle>>();
|
||||||
let bytes_future = draw.draw_circles(circles);
|
sim.steps(step);
|
||||||
let steps = sim.steps(step);
|
let mut bytes = draw.draw_circles(circles).await;
|
||||||
let mut bytes = join!(bytes_future, steps).await.0;
|
let frame =
|
||||||
let frame = gif::Frame::from_rgba(WIDTH as u16, HEIGHT as u16, &mut bytes);
|
gif::Frame::from_rgba_speed(WIDTH as u16, HEIGHT as u16, &mut bytes, 10);
|
||||||
frames.push(frame);
|
frames.push(frame);
|
||||||
progress.inc(step as u64);
|
|
||||||
}
|
}
|
||||||
progress.finish();
|
|
||||||
frames
|
frames
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,13 +57,10 @@ pub async fn encode(frames: Vec<Frame<'static>>, repeat: bool) -> Vec<u8> {
|
||||||
let mut buffer = Vec::<u8>::new();
|
let mut buffer = Vec::<u8>::new();
|
||||||
let mut encoder =
|
let mut encoder =
|
||||||
gif::Encoder::new(&mut buffer, WIDTH as u16, HEIGHT as u16, &[]).unwrap();
|
gif::Encoder::new(&mut buffer, WIDTH as u16, HEIGHT as u16, &[]).unwrap();
|
||||||
let progress = make_progress("Encoding ", frames.len() as u64);
|
|
||||||
for mut frame in frames {
|
for mut frame in frames {
|
||||||
frame.delay = 1;
|
frame.delay = 1;
|
||||||
encoder.write_frame(&frame).unwrap();
|
encoder.write_frame(&frame).unwrap();
|
||||||
progress.inc(1);
|
|
||||||
}
|
}
|
||||||
progress.finish();
|
|
||||||
if repeat {
|
if repeat {
|
||||||
encoder.set_repeat(gif::Repeat::Infinite).unwrap();
|
encoder.set_repeat(gif::Repeat::Infinite).unwrap();
|
||||||
}
|
}
|
||||||
|
@ -81,96 +68,49 @@ pub async fn encode(frames: Vec<Frame<'static>>, repeat: bool) -> Vec<u8> {
|
||||||
buffer
|
buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
use clap::Parser;
|
|
||||||
use sim::Simulation;
|
use sim::Simulation;
|
||||||
#[derive(Parser, Debug)]
|
|
||||||
#[command(author, version, about, long_about = None)]
|
|
||||||
struct Args {
|
|
||||||
/// Input file to process. All common image types are
|
|
||||||
/// supported, see the `image` crate docs for specific
|
|
||||||
/// compatibility
|
|
||||||
#[arg(short = 'i', value_hint = clap::ValueHint::DirPath)]
|
|
||||||
input: std::path::PathBuf,
|
|
||||||
|
|
||||||
/// Output file path ('./output.gif' by default)
|
#[derive(Debug, Clone)]
|
||||||
#[arg(short = 'o', value_hint = clap::ValueHint::DirPath)]
|
pub enum Error {
|
||||||
output: Option<std::path::PathBuf>,
|
CantGuessFormat,
|
||||||
|
DecodeError,
|
||||||
/// How many physics-steps between frames. Affects the
|
|
||||||
/// speed and length of the animation
|
|
||||||
#[arg(short = 's')]
|
|
||||||
step: Option<u32>,
|
|
||||||
|
|
||||||
/// Radius of the circle, smaller means more detailed but
|
|
||||||
/// slower to compute (8.0 by default, must be between
|
|
||||||
/// 1.0 and 50.0 inclusive)
|
|
||||||
#[arg(short = 'r')]
|
|
||||||
radius: Option<f32>,
|
|
||||||
|
|
||||||
/// Loop the GIF
|
|
||||||
#[arg(short = 'l', long = "loop")]
|
|
||||||
looping: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open_image<P: AsRef<Path>>(path: P) -> ImageBuffer<Rgb<u8>, Vec<u8>> {
|
pub async fn make_quickdraw() -> Option<Arc<Mutex<QuickDraw>>> {
|
||||||
let image = match image::io::Reader::open(path) {
|
Some(Arc::new(Mutex::new(QuickDraw::new(512, 512, 1000).await?)))
|
||||||
Ok(i) => i,
|
}
|
||||||
Err(e) => {
|
|
||||||
eprintln!("Error opening file for processing: ");
|
pub async fn make_gif(
|
||||||
eprintln!("{}", e);
|
input: &[u8],
|
||||||
std::process::exit(1);
|
draw: Arc<Mutex<QuickDraw>>,
|
||||||
},
|
) -> Result<Vec<u8>, Error> {
|
||||||
|
const STEP: usize = 20;
|
||||||
|
const RADIUS: f32 = 7.0;
|
||||||
|
let image = image::io::Reader::new(Cursor::new(input.to_vec().as_slice()))
|
||||||
|
.with_guessed_format()
|
||||||
|
.map_err(|_| Error::CantGuessFormat)?
|
||||||
|
.decode()
|
||||||
|
.map_err(|_| Error::DecodeError)?
|
||||||
|
.to_rgb8();
|
||||||
|
let (sim, it, max) = preprocess(image, RADIUS).await;
|
||||||
|
let frames = simulate(draw, sim, it, STEP, max).await;
|
||||||
|
let gif = encode(frames, true).await;
|
||||||
|
Ok(gif)
|
||||||
|
}
|
||||||
|
|
||||||
|
use rust_cgi as cgi;
|
||||||
|
|
||||||
|
const CONTENT_TYPE: &'static str = "Content-Type: image/gif";
|
||||||
|
|
||||||
|
rust_cgi::cgi_main!(|req: cgi::http::Request<Vec<u8>>| -> rust_cgi::Response {
|
||||||
|
let qd = match futures::executor::block_on(make_quickdraw()) {
|
||||||
|
Some(qd) => qd,
|
||||||
|
None => return cgi::text_response(500, "Failed to initialize WGPU"),
|
||||||
};
|
};
|
||||||
|
let image = req.into_body();
|
||||||
let decoded = match image.decode() {
|
let gif = match futures::executor::block_on(make_gif(&image, qd)) {
|
||||||
Ok(d) => d,
|
Ok(gif) => gif,
|
||||||
Err(e) => {
|
Err(_) => return cgi::text_response(500, "Failed to process image"),
|
||||||
eprintln!("Error processing file: ");
|
|
||||||
eprintln!("{}", e);
|
|
||||||
std::process::exit(1);
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
decoded.to_rgb8()
|
rust_cgi::binary_response(200, CONTENT_TYPE, gif)
|
||||||
}
|
});
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let args = Args::parse();
|
|
||||||
|
|
||||||
let output = args.output.unwrap_or("output.gif".into());
|
|
||||||
let step = args.step.unwrap_or(20) as usize;
|
|
||||||
if step == 0 {
|
|
||||||
eprintln!("Physics step cannot be 0");
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
let radius = args.radius.unwrap_or(8.0);
|
|
||||||
if radius < 1.0 || radius > 50.0 {
|
|
||||||
eprintln!("Invalid radius {}", radius);
|
|
||||||
eprintln!("Must be between 1.0 and 50.0 inclusive");
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let image = open_image(args.input);
|
|
||||||
let (sim, it, max) = pollster::block_on(preprocess(image, radius));
|
|
||||||
let mut draw = pollster::block_on(QuickDraw::new(512, 512, 1000));
|
|
||||||
let frames = pollster::block_on(simulate(&mut draw, sim, it, step, max));
|
|
||||||
let gif = pollster::block_on(encode(frames, args.looping));
|
|
||||||
let mut file = match std::fs::File::create(output.clone()) {
|
|
||||||
Ok(f) => f,
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("Error opening file for writing:");
|
|
||||||
eprintln!("{}", e);
|
|
||||||
std::process::exit(1);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
match file.write(&gif) {
|
|
||||||
Ok(_) => {
|
|
||||||
println!("Successfully created file at '{}'", output.display());
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("Error writing output file: ");
|
|
||||||
eprintln!("{}", e);
|
|
||||||
std::process::exit(1);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
51
src/sim.rs
|
@ -1,6 +1,6 @@
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
||||||
use crate::{helper::*, make_progress};
|
use crate::helper::*;
|
||||||
use image::{ImageBuffer, Rgb};
|
use image::{ImageBuffer, Rgb};
|
||||||
|
|
||||||
pub struct Circle {
|
pub struct Circle {
|
||||||
|
@ -55,10 +55,7 @@ impl Simulation {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
async fn assign_colors_from_image(
|
fn assign_colors_from_image(&mut self, img: ImageBuffer<Rgb<u8>, Vec<u8>>) {
|
||||||
&mut self,
|
|
||||||
img: ImageBuffer<Rgb<u8>, Vec<u8>>,
|
|
||||||
) {
|
|
||||||
let (width, height) = (img.width() as f32 - 1.0, img.height() as f32 - 1.0);
|
let (width, height) = (img.width() as f32 - 1.0, img.height() as f32 - 1.0);
|
||||||
for (pos, index) in self.circles.iter().map(|c| (c.position, c.index)) {
|
for (pos, index) in self.circles.iter().map(|c| (c.position, c.index)) {
|
||||||
let img_x =
|
let img_x =
|
||||||
|
@ -73,7 +70,7 @@ impl Simulation {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub async fn simulate_image(
|
pub fn simulate_image(
|
||||||
width: f32,
|
width: f32,
|
||||||
height: f32,
|
height: f32,
|
||||||
circle_radius: f32,
|
circle_radius: f32,
|
||||||
|
@ -85,22 +82,22 @@ impl Simulation {
|
||||||
s.finish()
|
s.finish()
|
||||||
} % 1204) as usize;
|
} % 1204) as usize;
|
||||||
let mut sim = Simulation::new(width, height, circle_radius, image_hash);
|
let mut sim = Simulation::new(width, height, circle_radius, image_hash);
|
||||||
let progress = make_progress(
|
// let progress = make_progress(
|
||||||
"Preprocessing",
|
// "Preprocessing",
|
||||||
(sim.max_circles + Self::POST_PROCESS) as u64,
|
// (sim.max_circles + Self::POST_PROCESS) as u64,
|
||||||
);
|
// );
|
||||||
while sim.circles() < sim.max_circles {
|
while sim.circles() < sim.max_circles {
|
||||||
sim.step().await;
|
sim.step();
|
||||||
progress.inc(2);
|
// progress.inc(2);
|
||||||
}
|
}
|
||||||
for _ in 0..Self::POST_PROCESS {
|
for _ in 0..Self::POST_PROCESS {
|
||||||
sim.step().await;
|
sim.step();
|
||||||
progress.inc(1);
|
// progress.inc(1);
|
||||||
}
|
}
|
||||||
progress.finish();
|
// progress.finish();
|
||||||
let total_iterations = sim.clock;
|
let total_iterations = sim.clock;
|
||||||
let max_circles = sim.circles.len();
|
let max_circles = sim.circles.len();
|
||||||
sim.assign_colors_from_image(img).await;
|
sim.assign_colors_from_image(img);
|
||||||
sim.circles.clear();
|
sim.circles.clear();
|
||||||
sim.clock = sim.rand_seed;
|
sim.clock = sim.rand_seed;
|
||||||
(sim, total_iterations, max_circles)
|
(sim, total_iterations, max_circles)
|
||||||
|
@ -144,7 +141,7 @@ impl Simulation {
|
||||||
|
|
||||||
// Insertion sort
|
// Insertion sort
|
||||||
#[inline]
|
#[inline]
|
||||||
async fn sort(&mut self) {
|
fn sort(&mut self) {
|
||||||
if self.circles.len() == 1 {
|
if self.circles.len() == 1 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -159,7 +156,7 @@ impl Simulation {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
async fn integrate(&mut self) {
|
fn integrate(&mut self) {
|
||||||
let delta = self.timescale * (1.0 / self.substeps as f32);
|
let delta = self.timescale * (1.0 / self.substeps as f32);
|
||||||
let gravity = Vector2::new(0.0, self.gravity) * delta.powi(2);
|
let gravity = Vector2::new(0.0, self.gravity) * delta.powi(2);
|
||||||
self.circles.iter_mut().for_each(|circle| {
|
self.circles.iter_mut().for_each(|circle| {
|
||||||
|
@ -170,7 +167,7 @@ impl Simulation {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
async fn collide(&mut self) {
|
fn collide(&mut self) {
|
||||||
for i in 0..self.circles.len() {
|
for i in 0..self.circles.len() {
|
||||||
// Apply gravity
|
// Apply gravity
|
||||||
for j in i..self.circles.len() {
|
for j in i..self.circles.len() {
|
||||||
|
@ -218,7 +215,7 @@ impl Simulation {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub async fn step(&mut self) {
|
pub fn step(&mut self) {
|
||||||
if self.circles.len() < self.max_circles {
|
if self.circles.len() < self.max_circles {
|
||||||
self.launch();
|
self.launch();
|
||||||
}
|
}
|
||||||
|
@ -228,21 +225,19 @@ impl Simulation {
|
||||||
|
|
||||||
for _ in 0..self.substeps {
|
for _ in 0..self.substeps {
|
||||||
self.constrain_rect();
|
self.constrain_rect();
|
||||||
self.sort().await;
|
self.sort();
|
||||||
self.collide().await;
|
self.collide();
|
||||||
self.integrate().await;
|
self.integrate();
|
||||||
self.clock += 1;
|
self.clock += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub async fn steps(&mut self, steps: usize) {
|
pub fn steps(&mut self, steps: usize) {
|
||||||
for _ in 0..steps {
|
for _ in 0..steps {
|
||||||
self.step().await;
|
self.step();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn circles(&self) -> usize {
|
pub fn circles(&self) -> usize { self.circles.len() }
|
||||||
self.circles.len()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
19
src/tests.rs
|
@ -1,19 +0,0 @@
|
||||||
#[cfg(test)]
|
|
||||||
#[test]
|
|
||||||
fn bench_async() {
|
|
||||||
use std::io::Write;
|
|
||||||
flexi_logger::Logger::try_with_str("debug").unwrap();
|
|
||||||
|
|
||||||
let image = image::io::Reader::open(
|
|
||||||
"resources/mona
|
|
||||||
lisa.png",
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
.decode()
|
|
||||||
.unwrap()
|
|
||||||
.to_rgb8();
|
|
||||||
// let gif = pollster::block_on(crate::generate_async(image, None));
|
|
||||||
// let mut file =
|
|
||||||
// std::fs::File::create("test.gif").unwrap();
|
|
||||||
// file.write(&gif).unwrap();
|
|
||||||
}
|
|