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; | pub static FRICTION: f32 = 0.995; | ||||||
|     use emath::Pos2; |  | ||||||
|     use emath::Rect; |  | ||||||
|     use rand::Rng; |  | ||||||
| 
 | 
 | ||||||
|     pub static FRICTION: f32 = 0.995; | pub struct Circle { | ||||||
|  |     pub v: Vec2, | ||||||
|  |     pub c: Pos2, | ||||||
|  |     pub r: f32, | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|     pub struct Circle { | impl Default for Circle { | ||||||
|         pub v: Vec2, |     fn default() -> Self { | ||||||
|         pub c: Pos2, |         let r = rand::thread_rng().gen_range(20.0..50.0); | ||||||
|         pub r: f32, |         Self { | ||||||
|     } |             r: r, | ||||||
| 
 |             c: Pos2 { | ||||||
|     impl Default for Circle { |                 x: rand::thread_rng().gen_range(r..400.0-r), | ||||||
|         fn default() -> Self { |                 y: rand::thread_rng().gen_range(r..400.0-r)}, | ||||||
|             let r = rand::thread_rng().gen_range(20.0..50.0); |             v: Vec2 { | ||||||
|             Self { |                 x: rand::thread_rng().gen_range(-2.0..2.0), | ||||||
|                 r: r, |                 y: rand::thread_rng().gen_range(-2.0..2.0)}, | ||||||
|                 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 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; | mod app; | ||||||
| use eframe::egui; |  | ||||||
| use egui::Color32; |  | ||||||
| use egui::Stroke; |  | ||||||
| use rand::Rng; |  | ||||||
| 
 |  | ||||||
| mod circle; | 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<()> { | fn main() -> eframe::Result<()> { | ||||||
|  |     //env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
 | ||||||
|  | 
 | ||||||
|     let native_options = eframe::NativeOptions { |     let native_options = eframe::NativeOptions { | ||||||
|         viewport: egui::ViewportBuilder::default().with_inner_size((800.0, 600.0)), |         viewport: eframe::egui::ViewportBuilder::default() | ||||||
|         ..eframe::NativeOptions::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( |     eframe::run_native( | ||||||
|         ExampleApp::name(), |         "egui-circles", | ||||||
|         native_options, |         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