commit f7591439fb7ef33226fa19eeb5d88deb3d1cb5b1 Author: Logan Date: Fri Jan 12 12:34:30 2024 -0600 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..653304f --- /dev/null +++ b/Cargo.lock @@ -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", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..28110e1 --- /dev/null +++ b/Cargo.toml @@ -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 diff --git a/image.gif b/image.gif new file mode 100644 index 0000000..1f3a865 Binary files /dev/null and b/image.gif differ diff --git a/resources/als.jpg b/resources/als.jpg new file mode 100644 index 0000000..5f5c759 Binary files /dev/null and b/resources/als.jpg differ diff --git a/resources/face.png b/resources/face.png new file mode 100644 index 0000000..d11f833 Binary files /dev/null and b/resources/face.png differ diff --git a/resources/gay.jpg b/resources/gay.jpg new file mode 100644 index 0000000..bf04409 Binary files /dev/null and b/resources/gay.jpg differ diff --git a/resources/geo.jpg b/resources/geo.jpg new file mode 100644 index 0000000..4e09377 Binary files /dev/null and b/resources/geo.jpg differ diff --git a/resources/joe.jpg b/resources/joe.jpg new file mode 100644 index 0000000..b4c4adf Binary files /dev/null and b/resources/joe.jpg differ diff --git a/resources/man.jpg b/resources/man.jpg new file mode 100644 index 0000000..f7fbe55 Binary files /dev/null and b/resources/man.jpg differ diff --git a/resources/mona lisa.png b/resources/mona lisa.png new file mode 100644 index 0000000..c839f91 Binary files /dev/null and b/resources/mona lisa.png differ diff --git a/resources/person.png b/resources/person.png new file mode 100644 index 0000000..09c7656 Binary files /dev/null and b/resources/person.png differ diff --git a/resources/rust-logo.png b/resources/rust-logo.png new file mode 100644 index 0000000..025a4b1 Binary files /dev/null and b/resources/rust-logo.png differ diff --git a/resources/rust-logo.svg b/resources/rust-logo.svg new file mode 100644 index 0000000..1a6c762 --- /dev/null +++ b/resources/rust-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/sam.png b/resources/sam.png new file mode 100644 index 0000000..70fb00e Binary files /dev/null and b/resources/sam.png differ diff --git a/resources/troll.png b/resources/troll.png new file mode 100644 index 0000000..0def93b Binary files /dev/null and b/resources/troll.png differ diff --git a/src/drawing.rs b/src/drawing.rs new file mode 100644 index 0000000..dff4d55 --- /dev/null +++ b/src/drawing.rs @@ -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, + ) +} diff --git a/src/helper.rs b/src/helper.rs new file mode 100644 index 0000000..0ba374b --- /dev/null +++ b/src/helper.rs @@ -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 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 for Vector2 { + type Output = Vector2; + + fn mul(self, value: f32) -> Self { + Vector2 { + x: self.x * value, + y: self.y * value, + } + } +} + +impl MulAssign for Vector2 { + fn mul_assign(&mut self, value: f32) { + *self = *self * value; + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..4b9df3a --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,57 @@ +use std::io::Write; + +use image::{ImageBuffer, Rgb}; + +mod drawing; +mod helper; +mod sim; + +fn write_frame( + 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( + sim: &sim::Simulation, + encoder: &mut gif::Encoder<&mut W>, +) { + write_frame(sim, encoder); +} + +pub fn generate(image: ImageBuffer, Vec>) -> Vec { + let (mut sim, it) = sim::Simulation::simulate_image(500.0, 500.0, 8.0, image); + + let mut buffer = Vec::::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, Vec>) -> Vec { + let (mut sim, it) = sim::Simulation::simulate_image(500.0, 500.0, 8.0, image); + + let mut buffer = Vec::::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 +} diff --git a/src/sim.rs b/src/sim.rs new file mode 100644 index 0000000..900b5e8 --- /dev/null +++ b/src/sim.rs @@ -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, + pub colors: Vec, + 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, Vec>) { + 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, Vec>, + ) -> (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() + } +}