eframe-template #6
							
								
								
									
										169
									
								
								src/app.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										169
									
								
								src/app.rs
									
									
									
									
									
										Normal file
									
								
							| @ -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<Circle>, | ||||
|     circles_count: usize, | ||||
| 
 | ||||
|     colors: Vec<egui::Color32>, | ||||
| } | ||||
| 
 | ||||
| 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 | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| //}
 | ||||
| @ -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 | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
							
								
								
									
										186
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										186
									
								
								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<Circle>, | ||||
|     circles_count: usize, | ||||
| 
 | ||||
|     colors: Vec<egui::Color32>, | ||||
| } | ||||
| 
 | ||||
| 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::<ExampleApp>::default()), | ||||
|         Box::new(|_| Box::<crate::app::Simulation>::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 | ||||
|     ); | ||||
| } | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user