From 433ee08f66e20178433893d9d9b6b4ea4b993019 Mon Sep 17 00:00:00 2001 From: djmil Date: Wed, 3 Jul 2024 20:31:14 +0200 Subject: [PATCH] split main and app sources --- src/app.rs | 169 +++++++++++++++++++++++++++++++++++++++++++++ src/circle.rs | 99 +++++++++++++-------------- src/main.rs | 186 ++++---------------------------------------------- 3 files changed, 231 insertions(+), 223 deletions(-) create mode 100644 src/app.rs diff --git a/src/app.rs b/src/app.rs new file mode 100644 index 0000000..cbc47b5 --- /dev/null +++ b/src/app.rs @@ -0,0 +1,169 @@ +use emath::Vec2; +use eframe::egui; +use egui::Color32; +use egui::Stroke; +use rand::Rng; + +use crate::circle::Circle; + +pub struct Simulation { + circles: Vec, + circles_count: usize, + + colors: Vec, +} + +impl Default for Simulation { + fn default() -> Self { + Self { + circles: Vec::new(), + circles_count: 2, + colors: Vec::new(), + } + } +} + +impl eframe::App for Simulation { + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { + // Looks better on 4k montior + ctx.set_pixels_per_point(1.5); + + egui::CentralPanel::default().show(ctx, |ui| { + if ui.button("halt").clicked() { + self.circles.iter_mut().for_each(|c| (*c).v = Vec2{x: 0.0, y: 0.0}); + }; + + if ui.button("push").clicked() { + self.circles.iter_mut().for_each(|c| (*c).v = Vec2{ + x: rand::thread_rng().gen_range(-2.0..2.0), + y: rand::thread_rng().gen_range(-2.0..2.0)}); + }; + + + ui.add(egui::Slider::new(&mut self.circles_count, 0..=25).text("circles count")); + + let diff = (self.circles.len() as i32) - (self.circles_count as i32); + if diff > 0 { + self.circles.truncate(self.circles_count); + } else { + for _ in diff..0 { + self.circles.push(Circle::default()); + self.colors.push( + egui::Color32::from_rgba_premultiplied( + rand::thread_rng().gen_range(0..255), + rand::thread_rng().gen_range(0..255), + rand::thread_rng().gen_range(0..255), + 64) + ); + } + } + + let painter = ui.painter(); + + let (hover_pos, any_down, any_released) = ctx.input(|input| ( + input.pointer.hover_pos(), + input.pointer.any_down(), + input.pointer.any_released() + )); + + if let Some(mousepos) = hover_pos { + + self.circles.iter_mut().for_each(|circle|{ + let d = circle.c - mousepos; + if d.length() < circle.r { + if any_down { + painter.line_segment( + [circle.c, circle.c +d], + Stroke{width: 1.0, color: Color32::from_rgb(128, 255, 255)}); + } + + if any_released { + circle.v += d.normalized() * (d.length() / circle.r) * 8.0; + } + } + }); + } + + for i in 0..self.circles_count { + painter.circle( + self.circles[i].c, + self.circles[i].r, + self.colors[i] /*Color32::TRANSPARENT*/, + Stroke{width: 2.0, color: Color32::from_rgb(255, 255, 255)} + ); + } + }); + + for circle in &mut self.circles { + circle.apply_force(&ctx.used_rect()); + } + + // Naive N^2 Colition detection + // Optimization https://en.wikipedia.org/wiki/Sweep_and_prune + for i in 0..self.circles_count { + for j in i+1..self.circles_count { + + if (i + j) % 3 == 0 { + continue; // skip collsions for every third ball + } + + let dc = self.circles[i].c - self.circles[j].c; + let dr = self.circles[i].r + self.circles[j].r; + + if dc.length() < dr { + (self.circles[i].v, self.circles[j].v) = collision(&self.circles[i], &self.circles[j]); + } + } + } + + // This is how to go into continuous mode - uncomment this to see example of continuous mode + ctx.request_repaint(); + } +} + + +fn collision(c1: &Circle, c2: &Circle) -> (Vec2, Vec2) { + let m1 = c1.r; + let m2 = c2.r; + + // collision normal + let n = Vec2{ + x: c2.c.x - c1.c.x, + y: c2.c.y - c1.c.y + }; + + // normal vector unit + let un = n.normalized(); + + // collision tangen + let ut = Vec2{x: -un.y, y: un.x}; + + // 3 + let v1n = un.dot(c1.v); + let v1t = ut.dot(c1.v); + + let v2n = un.dot(c2.v); + let v2t = ut.dot(c2.v); + + // 4 + let v1t_new = v1t; + let v2t_new = v2t; + + // 5 + let v1n_new = (v1n * (m1 - m2) + 2.0 * m2 * v2n) / (m1 + m2); + let v2n_new = (v2n * (m2 - m1) + 2.0 * m1 * v1n) / (m1 + m2); + + // 6 + let vec1n = v1n_new * un; + let vec1t = v1t_new * ut; + + let vec2n = v2n_new * un; + let vec2t = v2t_new * ut; + + return ( + vec1n + vec1t, + vec2n + vec2t + ); +} + +//} diff --git a/src/circle.rs b/src/circle.rs index e0c892c..8be5263 100644 --- a/src/circle.rs +++ b/src/circle.rs @@ -1,58 +1,55 @@ -pub mod circle { +use emath::Vec2; +use emath::Pos2; +use emath::Rect; +use rand::Rng; - use emath::Vec2; - use emath::Pos2; - use emath::Rect; - use rand::Rng; +pub static FRICTION: f32 = 0.995; - pub static FRICTION: f32 = 0.995; +pub struct Circle { + pub v: Vec2, + pub c: Pos2, + pub r: f32, +} - pub struct Circle { - pub v: Vec2, - pub c: Pos2, - pub r: f32, - } - - impl Default for Circle { - fn default() -> Self { - let r = rand::thread_rng().gen_range(20.0..50.0); - Self { - r: r, - c: Pos2 { - x: rand::thread_rng().gen_range(r..400.0-r), - y: rand::thread_rng().gen_range(r..400.0-r)}, - v: Vec2 { - x: rand::thread_rng().gen_range(-2.0..2.0), - y: rand::thread_rng().gen_range(-2.0..2.0)}, - } - } - } - - impl Circle { - pub fn apply_force(&mut self, bb: &Rect) { - self.v *= FRICTION; - self.c += self.v; - - if self.v.x > 0.0 { - if self.c.x + self.r > bb.right() { - self.v.x *= -1.0 - } - } else { - if self.c.x - self.r < bb.left() { - self.v.x *= -1.0 - } - } - - if self.v.y > 0.0 { - if self.c.y + self.r > bb.bottom() { - self.v.y *= -1.0 - } - } else { - if self.c.y - self.r < bb.top() { - self.v.y *= -1.0 - } - } +impl Default for Circle { + fn default() -> Self { + let r = rand::thread_rng().gen_range(20.0..50.0); + Self { + r: r, + c: Pos2 { + x: rand::thread_rng().gen_range(r..400.0-r), + y: rand::thread_rng().gen_range(r..400.0-r)}, + v: Vec2 { + x: rand::thread_rng().gen_range(-2.0..2.0), + y: rand::thread_rng().gen_range(-2.0..2.0)}, } } } +impl Circle { + pub fn apply_force(&mut self, bb: &Rect) { + self.v *= FRICTION; + self.c += self.v; + + if self.v.x > 0.0 { + if self.c.x + self.r > bb.right() { + self.v.x *= -1.0 + } + } else { + if self.c.x - self.r < bb.left() { + self.v.x *= -1.0 + } + } + + if self.v.y > 0.0 { + if self.c.y + self.r > bb.bottom() { + self.v.y *= -1.0 + } + } else { + if self.c.y - self.r < bb.top() { + self.v.y *= -1.0 + } + } + } + +} diff --git a/src/main.rs b/src/main.rs index d2b0b9c..cd5c5f3 100755 --- a/src/main.rs +++ b/src/main.rs @@ -1,182 +1,24 @@ -use emath::Vec2; -use eframe::egui; -use egui::Color32; -use egui::Stroke; -use rand::Rng; - +mod app; mod circle; -pub use circle::circle::Circle; - -struct ExampleApp { - circles: Vec, - circles_count: usize, - - colors: Vec, -} - -impl ExampleApp { - fn name() -> &'static str { - "egui-circles" - } -} - -impl Default for ExampleApp { - fn default() -> Self { - Self { - circles: Vec::new(), - circles_count: 2, - colors: Vec::new(), - } - } -} - -impl eframe::App for ExampleApp { - fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { - // Looks better on 4k montior - ctx.set_pixels_per_point(1.5); - - egui::CentralPanel::default().show(ctx, |ui| { - if ui.button("halt").clicked() { - //frame.quit() - self.circles.iter_mut().for_each(|c| (*c).v = Vec2{x: 0.0, y: 0.0}); - }; - - if ui.button("push").clicked() { - self.circles.iter_mut().for_each(|c| (*c).v = Vec2{ - x: rand::thread_rng().gen_range(-2.0..2.0), - y: rand::thread_rng().gen_range(-2.0..2.0)}); - }; - - - ui.add(egui::Slider::new(&mut self.circles_count, 0..=25).text("circles count")); - let diff = (self.circles.len() as i32) - (self.circles_count as i32); - if diff > 0 { - self.circles.truncate(self.circles_count); - } else { - for _ in diff..0 { - self.circles.push(Circle::default()); - self.colors.push( - egui::Color32::from_rgba_premultiplied( - rand::thread_rng().gen_range(0..255), - rand::thread_rng().gen_range(0..255), - rand::thread_rng().gen_range(0..255), - 64) - ); - } - } - - let painter = ui.painter(); - - let (hover_pos, any_down, any_released) = ctx.input(|input| (input.pointer.hover_pos(), input.pointer.any_down(), input.pointer.any_released())); - - if let Some(mousepos) = hover_pos { - - self.circles.iter_mut().for_each(|circle|{ - let d = (*circle).c - mousepos; - if d.length() < (*circle).r { - if any_down { - painter.line_segment( - [(*circle).c, (*circle).c +d], - Stroke{width: 1.0, color: Color32::from_rgb(128, 255, 255)}); - } - - if any_released { - (*circle).v += d.normalized() * (d.length() / (*circle).r) *8.0; - } - } - }); - } - - for i in 0..self.circles_count { - painter.circle( - self.circles[i].c, - self.circles[i].r, - self.colors[i] /*Color32::TRANSPARENT*/, - Stroke{width: 2.0, color: Color32::from_rgb(255, 255, 255)} - ); - } - }); - - for circle in &mut self.circles { - (*circle).apply_force(&ctx.used_rect()); - } - - // Naive N^2 Colition detection - // Optimization https://en.wikipedia.org/wiki/Sweep_and_prune - for i in 0..self.circles_count { - for j in i+1..self.circles_count { - - if (i + j) % 3 == 0 { - continue; // skip collsions for every third ball - } - - let dc = self.circles[i].c - self.circles[j].c; - let dr = self.circles[i].r + self.circles[j].r; - - if dc.length() < dr { - (self.circles[i].v, self.circles[j].v) = collision(&self.circles[i], &self.circles[j]); - } - } - } - - // This is how to go into continuous mode - uncomment this to see example of continuous mode - ctx.request_repaint(); - } -} fn main() -> eframe::Result<()> { + //env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`). + let native_options = eframe::NativeOptions { - viewport: egui::ViewportBuilder::default().with_inner_size((800.0, 600.0)), - ..eframe::NativeOptions::default() + viewport: eframe::egui::ViewportBuilder::default() + .with_inner_size([800.0, 600.0]) + .with_min_inner_size([300.0, 220.0]) + .with_icon( + // NOTE: Adding an icon is optional + eframe::icon_data::from_png_bytes(&include_bytes!("../assets/icon-256.png")[..]) + .expect("Failed to load icon"), + ), + ..Default::default() }; eframe::run_native( - ExampleApp::name(), + "egui-circles", native_options, - Box::new(|_| Box::::default()), + Box::new(|_| Box::::default()), ) } - -fn collision(c1: &Circle, c2: &Circle) -> (Vec2, Vec2) { - let m1 = c1.r; - let m2 = c2.r; - - // collision normal - let n = Vec2{ - x: c2.c.x - c1.c.x, - y: c2.c.y - c1.c.y - }; - - // normal vector unit - let un = n.normalized(); - - // collision tangen - let ut = Vec2{x: -un.y, y: un.x}; - - // 3 - let v1n = un.dot(c1.v); - let v1t = ut.dot(c1.v); - - let v2n = un.dot(c2.v); - let v2t = ut.dot(c2.v); - - // 4 - let v1t_new = v1t; - let v2t_new = v2t; - - // 5 - let v1n_new = (v1n * (m1 - m2) + 2.0 * m2 * v2n) / (m1 + m2); - let v2n_new = (v2n * (m2 - m1) + 2.0 * m1 * v1n) / (m1 + m2); - - // 6 - let vec1n = v1n_new * un; - let vec1t = v1t_new * ut; - - let vec2n = v2n_new * un; - let vec2t = v2t_new * ut; - - return ( - vec1n + vec1t, - vec2n + vec2t - ); -} \ No newline at end of file