extern crate emath; extern crate rand; extern crate eframe; use self::emath::Vec2; use self::eframe::egui; use self::egui::Color32; use self::egui::Stroke; use self::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 ); } //}