This commit is contained in:
Logan 2024-01-12 12:34:30 -06:00
commit f7591439fb
20 changed files with 825 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

398
Cargo.lock generated Normal file
View file

@ -0,0 +1,398 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "arrayref"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545"
[[package]]
name = "arrayvec"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bit_field"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bytemuck"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "color_quant"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]]
name = "crc32fast"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
[[package]]
name = "crunchy"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "either"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
[[package]]
name = "exr"
version = "1.71.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "832a761f35ab3e6664babfbdc6cef35a4860e816ec3916dcfd0882954e98a8a8"
dependencies = [
"bit_field",
"flume",
"half",
"lebe",
"miniz_oxide",
"rayon-core",
"smallvec",
"zune-inflate",
]
[[package]]
name = "fdeflate"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "209098dd6dfc4445aa6111f0e98653ac323eaa4dfd212c9ca3931bf9955c31bd"
dependencies = [
"simd-adler32",
]
[[package]]
name = "fishbowl"
version = "0.1.0"
dependencies = [
"gif",
"image",
"tiny-skia",
]
[[package]]
name = "flate2"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "flume"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181"
dependencies = [
"spin",
]
[[package]]
name = "gif"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045"
dependencies = [
"color_quant",
"weezl",
]
[[package]]
name = "half"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0"
dependencies = [
"crunchy",
]
[[package]]
name = "image"
version = "0.24.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f3dfdbdd72063086ff443e297b61695500514b1e41095b6fb9a5ab48a70a711"
dependencies = [
"bytemuck",
"byteorder",
"color_quant",
"exr",
"gif",
"jpeg-decoder",
"num-rational",
"num-traits",
"png",
"qoi",
"tiff",
]
[[package]]
name = "jpeg-decoder"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e"
dependencies = [
"rayon",
]
[[package]]
name = "lebe"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
[[package]]
name = "lock_api"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "miniz_oxide"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
dependencies = [
"adler",
"simd-adler32",
]
[[package]]
name = "num-integer"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
dependencies = [
"autocfg",
]
[[package]]
name = "png"
version = "0.17.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd75bf2d8dd3702b9707cdbc56a5b9ef42cec752eb8b3bafc01234558442aa64"
dependencies = [
"bitflags",
"crc32fast",
"fdeflate",
"flate2",
"miniz_oxide",
]
[[package]]
name = "qoi"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001"
dependencies = [
"bytemuck",
]
[[package]]
name = "rayon"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "simd-adler32"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
[[package]]
name = "smallvec"
version = "1.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
dependencies = [
"lock_api",
]
[[package]]
name = "strict-num"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
[[package]]
name = "tiff"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d172b0f4d3fba17ba89811858b9d3d97f928aece846475bbda076ca46736211"
dependencies = [
"flate2",
"jpeg-decoder",
"weezl",
]
[[package]]
name = "tiny-skia"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6a067b809476893fce6a254cf285850ff69c847e6cfbade6a20b655b6c7e80d"
dependencies = [
"arrayref",
"arrayvec",
"bytemuck",
"cfg-if",
"log",
"png",
"tiny-skia-path",
]
[[package]]
name = "tiny-skia-path"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5de35e8a90052baaaf61f171680ac2f8e925a1e43ea9d2e3a00514772250e541"
dependencies = [
"arrayref",
"bytemuck",
"strict-num",
]
[[package]]
name = "weezl"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb"
[[package]]
name = "zune-inflate"
version = "0.2.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02"
dependencies = [
"simd-adler32",
]

14
Cargo.toml Normal file
View file

@ -0,0 +1,14 @@
[package]
name = "fishbowl"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
image = "0.24"
gif = "0.12"
tiny-skia = "0.11"
[profile.release]
codegen-units = 1

BIN
image.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8 MiB

BIN
resources/als.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

BIN
resources/face.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
resources/gay.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

BIN
resources/geo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
resources/joe.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
resources/man.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
resources/mona lisa.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 KiB

BIN
resources/person.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 831 KiB

BIN
resources/rust-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

1
resources/rust-logo.svg Normal file
View file

@ -0,0 +1 @@
<svg height="144" width="144" xmlns="http://www.w3.org/2000/svg"><path d="m71.05 23.68c-26.06 0-47.27 21.22-47.27 47.27s21.22 47.27 47.27 47.27 47.27-21.22 47.27-47.27-21.22-47.27-47.27-47.27zm-.07 4.2a3.1 3.11 0 0 1 3.02 3.11 3.11 3.11 0 0 1 -6.22 0 3.11 3.11 0 0 1 3.2-3.11zm7.12 5.12a38.27 38.27 0 0 1 26.2 18.66l-3.67 8.28c-.63 1.43.02 3.11 1.44 3.75l7.06 3.13a38.27 38.27 0 0 1 .08 6.64h-3.93c-.39 0-.55.26-.55.64v1.8c0 4.24-2.39 5.17-4.49 5.4-2 .23-4.21-.84-4.49-2.06-1.18-6.63-3.14-8.04-6.24-10.49 3.85-2.44 7.85-6.05 7.85-10.87 0-5.21-3.57-8.49-6-10.1-3.42-2.25-7.2-2.7-8.22-2.7h-40.6a38.27 38.27 0 0 1 21.41-12.08l4.79 5.02c1.08 1.13 2.87 1.18 4 .09zm-44.2 23.02a3.11 3.11 0 0 1 3.02 3.11 3.11 3.11 0 0 1 -6.22 0 3.11 3.11 0 0 1 3.2-3.11zm74.15.14a3.11 3.11 0 0 1 3.02 3.11 3.11 3.11 0 0 1 -6.22 0 3.11 3.11 0 0 1 3.2-3.11zm-68.29.5h5.42v24.44h-10.94a38.27 38.27 0 0 1 -1.24-14.61l6.7-2.98c1.43-.64 2.08-2.31 1.44-3.74zm22.62.26h12.91c.67 0 4.71.77 4.71 3.8 0 2.51-3.1 3.41-5.65 3.41h-11.98zm0 17.56h9.89c.9 0 4.83.26 6.08 5.28.39 1.54 1.26 6.56 1.85 8.17.59 1.8 2.98 5.4 5.53 5.4h16.14a38.27 38.27 0 0 1 -3.54 4.1l-6.57-1.41c-1.53-.33-3.04.65-3.37 2.18l-1.56 7.28a38.27 38.27 0 0 1 -31.91-.15l-1.56-7.28c-.33-1.53-1.83-2.51-3.36-2.18l-6.43 1.38a38.27 38.27 0 0 1 -3.32-3.92h31.27c.35 0 .59-.06.59-.39v-11.06c0-.32-.24-.39-.59-.39h-9.15zm-14.43 25.33a3.11 3.11 0 0 1 3.02 3.11 3.11 3.11 0 0 1 -6.22 0 3.11 3.11 0 0 1 3.2-3.11zm46.05.14a3.11 3.11 0 0 1 3.02 3.11 3.11 3.11 0 0 1 -6.22 0 3.11 3.11 0 0 1 3.2-3.11z"/><path d="m115.68 70.95a44.63 44.63 0 0 1 -44.63 44.63 44.63 44.63 0 0 1 -44.63-44.63 44.63 44.63 0 0 1 44.63-44.63 44.63 44.63 0 0 1 44.63 44.63zm-.84-4.31 6.96 4.31-6.96 4.31 5.98 5.59-7.66 2.87 4.78 6.65-8.09 1.32 3.4 7.46-8.19-.29 1.88 7.98-7.98-1.88.29 8.19-7.46-3.4-1.32 8.09-6.65-4.78-2.87 7.66-5.59-5.98-4.31 6.96-4.31-6.96-5.59 5.98-2.87-7.66-6.65 4.78-1.32-8.09-7.46 3.4.29-8.19-7.98 1.88 1.88-7.98-8.19.29 3.4-7.46-8.09-1.32 4.78-6.65-7.66-2.87 5.98-5.59-6.96-4.31 6.96-4.31-5.98-5.59 7.66-2.87-4.78-6.65 8.09-1.32-3.4-7.46 8.19.29-1.88-7.98 7.98 1.88-.29-8.19 7.46 3.4 1.32-8.09 6.65 4.78 2.87-7.66 5.59 5.98 4.31-6.96 4.31 6.96 5.59-5.98 2.87 7.66 6.65-4.78 1.32 8.09 7.46-3.4-.29 8.19 7.98-1.88-1.88 7.98 8.19-.29-3.4 7.46 8.09 1.32-4.78 6.65 7.66 2.87z" fill-rule="evenodd" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"/></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
resources/sam.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 664 KiB

BIN
resources/troll.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

37
src/drawing.rs Normal file
View file

@ -0,0 +1,37 @@
use tiny_skia::*;
use crate::sim::Circle;
pub fn clear(pixmap: &mut Pixmap) {
let mut black = Paint::default();
black.set_color_rgba8(0, 0, 0, 255);
pixmap.fill_rect(
Rect::from_xywh(0.0, 0.0, pixmap.width() as f32, pixmap.height() as f32)
.unwrap(),
&black,
Transform::identity(),
None,
);
}
pub fn draw_circle(pixmap: &mut Pixmap, circle: &Circle) {
let x = circle.position.x;
let y = circle.position.y;
let radius = circle.radius;
let r = circle.color.0;
let g = circle.color.1;
let b = circle.color.2;
let mut paint = Paint::default();
paint.set_color_rgba8(r, g, b, 255);
let mut brush = PathBuilder::new();
brush.push_circle(x, y, radius);
brush.move_to(x, y);
brush.close();
pixmap.fill_path(
&brush.finish().unwrap(),
&paint,
FillRule::Winding,
tiny_skia::Transform::identity(),
None,
)
}

82
src/helper.rs Normal file
View file

@ -0,0 +1,82 @@
use std::ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign};
#[derive(Copy, Clone)]
pub struct Color(pub u8, pub u8, pub u8);
#[derive(Copy, Clone)]
pub struct Vector2 {
pub x: f32,
pub y: f32,
}
impl Vector2 {
pub const fn new(x: f32, y: f32) -> Vector2 {
Vector2 { x, y }
}
pub fn length2(&self) -> f32 {
(self.x * self.x) + (self.y * self.y)
}
}
impl Add for Vector2 {
type Output = Vector2;
fn add(self, v: Vector2) -> Self {
Vector2 {
x: self.x + v.x,
y: self.y + v.y,
}
}
}
impl Add<f32> for Vector2 {
type Output = Vector2;
fn add(self, value: f32) -> Self {
Vector2 {
x: self.x + value,
y: self.y + value,
}
}
}
impl AddAssign for Vector2 {
fn add_assign(&mut self, v: Vector2) {
*self = *self + v;
}
}
impl Sub for Vector2 {
type Output = Vector2;
fn sub(self, v: Vector2) -> Self {
Vector2 {
x: self.x - v.x,
y: self.y - v.y,
}
}
}
impl SubAssign for Vector2 {
fn sub_assign(&mut self, v: Vector2) {
*self = *self - v;
}
}
impl Mul<f32> for Vector2 {
type Output = Vector2;
fn mul(self, value: f32) -> Self {
Vector2 {
x: self.x * value,
y: self.y * value,
}
}
}
impl MulAssign<f32> for Vector2 {
fn mul_assign(&mut self, value: f32) {
*self = *self * value;
}
}

57
src/lib.rs Normal file
View file

@ -0,0 +1,57 @@
use std::io::Write;
use image::{ImageBuffer, Rgb};
mod drawing;
mod helper;
mod sim;
fn write_frame<W: Write>(
sim: &sim::Simulation,
encoder: &mut gif::Encoder<&mut W>,
) {
let mut pixbuf = tiny_skia::Pixmap::new(500, 500).unwrap();
drawing::clear(&mut pixbuf);
for index in 0..sim.circles.len() {
let circle = &sim.circles[index];
drawing::draw_circle(&mut pixbuf, circle);
}
let mut frame = gif::Frame::from_rgba(500, 500, pixbuf.data_mut());
frame.delay = 1;
encoder.write_frame(&frame).unwrap();
}
async fn write_frame_async<W: Write>(
sim: &sim::Simulation,
encoder: &mut gif::Encoder<&mut W>,
) {
write_frame(sim, encoder);
}
pub fn generate(image: ImageBuffer<Rgb<u8>, Vec<u8>>) -> Vec<u8> {
let (mut sim, it) = sim::Simulation::simulate_image(500.0, 500.0, 8.0, image);
let mut buffer = Vec::<u8>::new();
let mut encoder = gif::Encoder::new(&mut buffer, 500, 500, &[]).unwrap();
// encoder.set_repeat(gif::Repeat::Infinite).unwrap();
while sim.clock < it {
write_frame(&sim, &mut encoder);
(0..5).for_each(|_| sim.step());
}
drop(encoder);
buffer
}
pub async fn generate_async(image: ImageBuffer<Rgb<u8>, Vec<u8>>) -> Vec<u8> {
let (mut sim, it) = sim::Simulation::simulate_image(500.0, 500.0, 8.0, image);
let mut buffer = Vec::<u8>::new();
let mut encoder = gif::Encoder::new(&mut buffer, 500, 500, &[]).unwrap();
// encoder.set_repeat(gif::Repeat::Infinite).unwrap();
while sim.clock < it {
write_frame_async(&sim, &mut encoder).await;
(0..5).for_each(|_| sim.step());
}
drop(encoder);
buffer
}

235
src/sim.rs Normal file
View file

@ -0,0 +1,235 @@
use std::hash::{Hash, Hasher};
use crate::helper::*;
use image::{ImageBuffer, Rgb};
pub struct Circle {
pub position: Vector2,
last_position: Vector2,
pub radius: f32,
pub color: Color,
index: usize,
}
pub struct Simulation {
pub circles: Vec<Circle>,
pub colors: Vec<Color>,
pub max_circles: usize,
pub clock: usize,
substeps: usize,
rand_seed: usize,
timescale: f32,
circle_radius: f32,
radius_variance: f32,
area_size: (f32, f32),
gravity: f32,
response_mod: f32,
}
impl Simulation {
pub fn new(
width: f32,
height: f32,
circle_radius: f32,
rand_seed: usize,
) -> Self {
let area = width * height;
let circle_area = circle_radius.powi(2) * std::f32::consts::PI;
let approx_max = ((area / circle_area).round() * 1.0) as usize;
Self {
circles: Vec::with_capacity(approx_max),
max_circles: approx_max,
timescale: 1.0 / 60.0,
substeps: 4,
colors: vec![Color(255, 255, 255); approx_max],
clock: rand_seed,
rand_seed,
circle_radius,
radius_variance: circle_radius * 0.1,
area_size: (width, height),
gravity: height,
response_mod: 0.4,
}
}
#[inline]
fn assign_colors_from_image(&mut self, img: ImageBuffer<Rgb<u8>, Vec<u8>>) {
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)) {
let img_x =
((pos.x / self.area_size.0).clamp(0.0, 1.0) * width).round() as u32;
let img_y =
((pos.y / self.area_size.1).clamp(0.0, 1.0) * height).round() as u32;
let pixel = img.get_pixel(img_x, img_y);
let color = Color(pixel[0], pixel[1], pixel[2]);
self.colors[index] = color;
}
}
#[inline]
pub fn simulate_image(
width: f32,
height: f32,
circle_radius: f32,
img: ImageBuffer<Rgb<u8>, Vec<u8>>,
) -> (Self, usize) {
let image_hash = ({
let mut s = std::hash::DefaultHasher::new();
img.hash(&mut s);
s.finish()
} % 1204) as usize;
let mut sim = Simulation::new(width, height, circle_radius, image_hash);
while sim.circles() < sim.max_circles {
sim.step();
}
(0..120).for_each(|_| sim.step());
let total_iterations = sim.clock;
sim.assign_colors_from_image(img);
sim.circles.clear();
sim.clock = sim.rand_seed;
(sim, total_iterations)
}
#[inline]
pub fn add_circle(&mut self, position: Vector2, velocity: Vector2) {
self.circles.push(Circle {
position,
last_position: position - velocity,
radius: self.circle_radius
+ (self.clock as f32).sin() * self.radius_variance,
color: self.colors[self.circles.len()],
index: self.circles.len(),
})
}
#[inline]
pub fn launch(&mut self) {
let time = self.clock as f32 / (10.0 * std::f32::consts::PI);
let halfwidth = self.area_size.0 / 2.0;
let x = ((halfwidth - self.circle_radius * 2.0) * time.cos().abs())
+ self.circle_radius;
self.add_circle(
Vector2::new(x, self.circle_radius),
Vector2::new(time.cos(), time.sin().abs()),
)
}
pub fn launch2(&mut self) {
let time = self.clock as f32 / (10.0 * std::f32::consts::PI);
let halfwidth = self.area_size.0 / 2.0;
let x = ((halfwidth - self.circle_radius * 2.0) * time.sin().abs())
+ self.circle_radius;
let x = x + halfwidth;
self.add_circle(
Vector2::new(x, self.circle_radius),
Vector2::new(time.cos(), time.sin().abs()),
)
}
// Insertion sort
#[inline]
fn sort(&mut self) {
if self.circles.len() == 1 {
return;
}
for i in 1..self.circles.len() {
let mut j = i;
while j > 0 && self.circles[j - 1].position.x > self.circles[j].position.x
{
self.circles.swap(j - 1, j);
j -= 1;
}
}
}
#[inline]
fn integrate(&mut self) {
let delta = self.timescale * (1.0 / self.substeps as f32);
let gravity = Vector2::new(0.0, self.gravity) * delta.powi(2);
self.circles.iter_mut().for_each(|circle| {
let velocity = circle.position - circle.last_position;
circle.last_position = circle.position;
circle.position = circle.position + velocity + gravity;
});
}
#[inline]
fn collide(&mut self) {
for i in 0..self.circles.len() {
// Apply gravity
for j in i..self.circles.len() {
let circle = &self.circles[i].position;
let other = &self.circles[j].position;
let this_radius = self.circles[i].radius;
let other_radius = self.circles[i].radius;
let diameter = this_radius + other_radius;
if circle.x < other.x - diameter {
break; // No further collisions possible
}
let dy = (circle.y - other.y).abs();
if dy >= diameter {
continue; // Skip over obvious noncollisions
}
let combined = *circle - *other;
let distance_squared = combined.length2();
if distance_squared >= diameter.powi(2) || distance_squared == 0.0 {
continue;
}
// Finally, resort to expensive calculation
let distance = distance_squared.sqrt();
let normalized = combined * (1.0 / distance);
let delta = 0.5 * self.response_mod * (distance - diameter);
self.circles[i].position -= normalized * delta * 0.5;
self.circles[j].position += normalized * delta * 0.5;
}
}
}
#[inline]
fn constrain_rect(&mut self) {
self
.circles
.iter_mut()
.map(|c| &mut c.position)
.for_each(|pos| {
pos.x = pos
.x
.clamp(self.circle_radius, self.area_size.0 - self.circle_radius);
pos.y = pos
.y
.clamp(self.circle_radius, self.area_size.1 - self.circle_radius);
});
}
#[inline]
pub fn step(&mut self) {
if self.circles.len() < self.max_circles {
self.launch();
}
if self.circles.len() < self.max_circles {
self.launch2();
}
(0..self.substeps).for_each(|_| {
self.constrain_rect();
self.sort();
self.collide();
self.integrate();
self.clock += 1;
});
}
// #[inline]
// pub fn draw(&self, d: &mut RaylibDrawHandle) {
// self
// .circles
// .iter()
// .map(|c| (c.position, c.radius, c.color))
// .for_each(|(pos, radius, color)| d.draw_circle_v(pos,
// radius, color)); }
pub fn circles(&self) -> usize {
self.circles.len()
}
}