1use std::io;
2use crossterm::event::{Event, MouseEvent, MouseEventKind};
3use teng::components::Component;
4use teng::rendering::pixel::Pixel;
5use teng::rendering::render::Render;
6use teng::rendering::renderer::Renderer;
7use teng::{install_panic_handler, terminal_cleanup, terminal_setup, BreakingAction, Game, SetupInfo, SharedState, UpdateInfo};
8use teng::rendering::display::Display;
9use teng::util::fixedupdate::FixedUpdateRunner;
10use crate::ball::Ball;
11
12mod ball {
13 use teng::rendering::display::Display;
14 use teng::rendering::pixel::Pixel;
15 use teng::rendering::renderer::Renderer;
16
17 #[derive(Clone)]
18 pub struct Ball {
19 x: f64,
21 y: f64,
23 pub x_vel: f64,
25 pub y_vel: f64,
27 pub radius: f64,
28 pub mass: f64,
29 old_x: f64,
30 old_y: f64,
31 }
32
33 impl Ball {
34 pub fn new(x: f64, y: f64, radius: f64) -> Self {
35 let (local_x, local_y) = Self::world_to_local(x, y);
36 Self {
37 x: local_x,
38 y: local_y,
39 x_vel: 0.0,
40 y_vel: 0.0,
41 radius,
42 mass: radius * radius,
43 old_x: local_x,
44 old_y: local_y,
45 }
46 }
47
48 pub fn set_world_x(&mut self, x: f64) {
49 self.x = x;
50 }
51
52 pub fn set_world_y(&mut self, y: f64) {
53 self.y = y * 2.0;
54 }
55
56 pub fn world_x(&self) -> f64 {
57 self.x
58 }
59
60 pub fn world_y(&self) -> f64 {
61 self.y / 2.0
62 }
63
64 pub fn local_x(&self) -> f64 {
65 self.x
66 }
67
68 pub fn local_y(&self) -> f64 {
69 self.y
70 }
71
72 pub fn world_to_local(x: f64, y: f64) -> (f64, f64) {
73 (x, y * 2.0)
74 }
75 pub fn local_to_world(x: f64, y: f64) -> (f64, f64) {
76 (x, y / 2.0)
77 }
78
79 fn update_local(&mut self, dt: f64, bottom_height_local: f64) -> (f64, f64) {
80 let (old_x, old_y) = (self.x, self.y);
81 self.old_x = old_x;
82 self.old_y = old_y;
83
84 self.x += self.x_vel * dt;
85 self.y += self.y_vel * dt;
86
87 if self.y + self.radius >= bottom_height_local {
88 self.y = (bottom_height_local - self.radius).floor();
89 self.y_vel = -self.y_vel * 0.8;
90 }
91
92 (old_x, old_y)
93 }
94
95 fn for_each_coord_in_outline(&self, mut f: impl FnMut(f64, f64) -> bool) {
97 let mut x = self.radius as i64 + 1;
98 let mut y = 0;
99 let mut err = 0;
100
101 let center_x = self.x as i64;
102 let center_y = self.y as i64;
103
104 while x >= y {
105 if f((center_x + x) as f64, (center_y + y) as f64) {
106 return;
107 }
108 if f((center_x + y) as f64, (center_y + x) as f64) {
109 return;
110 }
111 if f((center_x - y) as f64, (center_y + x) as f64) {
112 return;
113 }
114 if f((center_x - x) as f64, (center_y + y) as f64) {
115 return;
116 }
117 if f((center_x - x) as f64, (center_y - y) as f64) {
118 return;
119 }
120 if f((center_x - y) as f64, (center_y - x) as f64) {
121 return;
122 }
123 if f((center_x + y) as f64, (center_y - x) as f64) {
124 return;
125 }
126 if f((center_x + x) as f64, (center_y - y) as f64) {
127 return;
128 }
129
130 y += 1;
131 if err <= 0 {
132 err += 2 * y + 1;
133 }
134 if err > 0 {
135 x -= 1;
136 err -= 2 * x + 1;
137 }
138 }
139 }
140
141 fn for_each_coord_in_filled(&self, mut f: impl FnMut(f64, f64) -> bool, radius_adjustment: f64) {
143 let center_x = self.x as i64;
144 let center_y = self.y as i64;
145
146 let mut x = (self.radius + radius_adjustment) as i64;
147 let mut y = 0;
148 let mut err = 0.0;
149
150 while x >= y {
151 for i in center_x - x..=center_x + x {
152 if f(i as f64, (center_y + y) as f64) {
153 return;
154 }
155 if f(i as f64, (center_y - y) as f64) {
156 return;
157 }
158 }
159 for i in center_x - y..=center_x + y {
160 if f(i as f64, (center_y + x) as f64) {
161 return;
162 }
163 if f(i as f64, (center_y - x) as f64) {
164 return;
165 }
166 }
167
168 y += 1;
169 if err <= 0.0 {
170 err += 2.0 * y as f64 + 1.0;
171 }
172 if err > 0.0 {
173 x -= 1;
174 err -= 2.0 * x as f64 + 1.0;
175 }
176 }
177 }
178
179 pub fn render(&self, renderer: &mut dyn Renderer, render_outline: bool, depth: i32) {
180 let pixel = Pixel::new('X');
185
186 self.for_each_coord_in_filled(|x, y| {
187 let (x, y) = Self::local_to_world(x, y);
188 if y < 0.0 || x < 0.0 {
189 return false;
190 }
191 renderer.render_pixel(x as usize, y as usize, pixel, depth);
192 false
193 }, 0.0);
194
195
196 if !render_outline {
197 return;
198 }
199
200 let depth_radius = depth + 1;
201
202
203 let pixel = Pixel::new('X').with_color([255, 0, 0]);
205 self.for_each_coord_in_outline(|x, y| {
206 let (x, y) = Self::local_to_world(x, y);
207 if y < 0.0 || x < 0.0 {
208 return false;
209 }
210 renderer.render_pixel(x as usize, y as usize, pixel, depth_radius);
211 false
212 });
213 }
214 }
215
216 pub fn update_balls(dt: f64, balls: &mut [Ball], bottom_wall_height_world: f64, is_solid_world: impl Fn(f64, f64) -> bool) {
217 let bottom_wall_height_local = bottom_wall_height_world * 2.0;
218 let is_solid_local = |x: f64, y: f64| is_solid_world(x, y / 2.0);
219
220 for ball in balls.iter_mut() {
221 ball.update_local(dt, bottom_wall_height_local);
223
224 }
225
226
227 for i in 0..balls.len() {
229 for j in i+1..balls.len() {
230 let (balls1, balls2) = balls.split_at_mut(j);
231 let ball1 = &mut balls1[i];
232 let ball2 = &mut balls2[0];
233 let dx = ball1.x - ball2.x;
234 let dy = ball1.y - ball2.y;
235 let distance = (dx*dx + dy*dy).sqrt();
236 let overlap = ball1.radius + ball2.radius - distance;
237 if overlap > 0.0 {
238 let overlap = overlap / 2.0;
239 let dx = dx / distance * overlap;
240 let dy = dy / distance * overlap;
241 ball1.x += dx;
242 ball1.y += dy;
243 ball2.x -= dx;
244 ball2.y -= dy;
245 let ball1_mass = ball1.mass;
247 let ball2_mass = ball2.mass;
248 let normal_x = dx / overlap;
249 let normal_y = dy / overlap;
250 let relative_velocity_x = ball1.x_vel - ball2.x_vel;
251 let relative_velocity_y = ball1.y_vel - ball2.y_vel;
252 let dot_product = relative_velocity_x * normal_x + relative_velocity_y * normal_y;
253 if dot_product < 0.0 {
254 let impulse = 2.0 * dot_product / (ball1_mass + ball2_mass);
255 ball1.x_vel -= impulse * normal_x * ball2_mass;
256 ball1.y_vel -= impulse * normal_y * ball2_mass;
257 ball2.x_vel += impulse * normal_x * ball1_mass;
258 ball2.y_vel += impulse * normal_y * ball1_mass;
259 }
260
261 }
262 }
263 }
264
265 for ball in balls.iter_mut() {
267
268 let mut closest_hit = None;
270 let mut closest_distance_2 = f64::INFINITY;
271
272 ball.for_each_coord_in_filled(|x, y| {
273 if is_solid_local(x, y) {
274 let dx = x - ball.x;
275 let dy = y - ball.y;
276 let distance = dx*dx + dy*dy;
277 if distance < closest_distance_2 {
278 closest_distance_2 = distance;
279 closest_hit = Some((x, y));
280 }
281
282 }
283
284 false
285 }, 1.0);
286
287 if let Some((x, y)) = closest_hit {
288 let dx = x - ball.x;
290 let dy = y - ball.y;
291 let distance = (dx*dx + dy*dy).sqrt();
292
293 ball.x = ball.old_x;
296 ball.y = ball.old_y;
297
298 let normal_x = -dx / distance;
300 let normal_y = -dy / distance;
301
302 let x_vel = ball.x_vel;
310 let y_vel = ball.y_vel;
311 let dot = x_vel * normal_x + y_vel * normal_y;
312 if dot > 0.0 {
314 continue;
319 }
320
321 let r_x = x_vel - 2.0 * dot * normal_x;
323 let r_y = y_vel - 2.0 * dot * normal_y;
324
325 ball.x_vel = r_x * 0.8;
326 ball.y_vel = r_y * 0.8;
327
328 }
329 }
330 }
331}
332
333
334struct CircleRasterizerComponent {
335 free_balls: Vec<Ball>,
336 current_ball: Option<Ball>,
337 center_samples: Vec<(f64, f64)>,
338 fixed_update_runner: FixedUpdateRunner,
339 did_hold_last: bool,
341 default_radius: f64,
342 static_collision: Display<bool>,
343}
344
345impl Default for CircleRasterizerComponent {
346 fn default() -> Self {
347 Self {
348 free_balls: vec![],
349 current_ball: None,
350 center_samples: vec![],
351 did_hold_last: false,
352 fixed_update_runner: FixedUpdateRunner::new(1.0 / 60.0),
353 default_radius: 10.0,
354 static_collision: Display::new(0, 0, false),
355 }
356 }
357}
358
359const MAX_SAMPLES: usize = 5;
360
361impl Component for CircleRasterizerComponent {
362 fn setup(&mut self, setup_info: &SetupInfo, shared_state: &mut SharedState<()>) {
363 self.on_resize(setup_info.display_info.width(), setup_info.display_info.height(), shared_state);
364 }
365
366 fn on_resize(&mut self, width: usize, height: usize, shared_state: &mut SharedState<()>) {
367 self.static_collision.resize_keep(width, height);
368 }
369
370 fn on_event(&mut self, event: Event, shared_state: &mut SharedState<()>) -> Option<BreakingAction> {
371 if let Event::Mouse(MouseEvent { kind: kind @ (MouseEventKind::ScrollDown | MouseEventKind::ScrollUp), .. }) = event {
372 let delta = match kind {
373 MouseEventKind::ScrollDown => -1.0,
374 MouseEventKind::ScrollUp => 1.0,
375 _ => 0.0,
376 };
377 if let Some(current_ball) = &mut self.current_ball {
378 current_ball.radius += delta;
379 current_ball.mass = current_ball.radius * current_ball.radius;
380 }
381 self.default_radius += delta;
382 }
383
384 None
385 }
386
387 fn update(&mut self, update_info: UpdateInfo, shared_state: &mut SharedState<()>) {
388 if shared_state.pressed_keys.did_press_char_ignore_case('c') {
389 self.free_balls.clear();
390 }
391
392 if shared_state.pressed_keys.did_press_char_ignore_case('r') {
393 self.static_collision.fill(false);
394 }
395
396 if shared_state.mouse_info.left_mouse_down {
397 let world_x = shared_state.mouse_info.last_mouse_pos.0 as f64;
398 let world_y = shared_state.mouse_info.last_mouse_pos.1 as f64;
399 let current_ball = self.current_ball.get_or_insert_with(|| {
400 Ball::new(world_x, world_y, self.default_radius)
401 });
402
403 current_ball.set_world_x(world_x);
404 current_ball.set_world_y(world_y);
405 current_ball.x_vel = 0.0;
406 if !self.did_hold_last {
407 self.center_samples.clear();
409 }
410 self.did_hold_last = true;
411 } else if self.did_hold_last {
412 let current_ball = self.current_ball.as_mut().unwrap();
414 self.did_hold_last = false;
416 let mut sum_x_delta = 0.0;
418 let mut sum_y_delta = 0.0;
419 for i in 1..self.center_samples.len() {
420 let (x1, y1) = self.center_samples[i - 1];
421 let (x2, y2) = self.center_samples[i];
422 sum_x_delta += x2 - x1;
423 sum_y_delta += y2 - y1;
424 }
425 let delta_length = self.center_samples.len() as f64 / 60.0;
426 let avg_x_vel = sum_x_delta / delta_length;
427 let avg_y_vel = sum_y_delta / delta_length;
428 let strength = 1.0;
429 current_ball.x_vel = avg_x_vel * strength;
430 current_ball.y_vel = avg_y_vel * strength;
431 self.free_balls.push(current_ball.clone());
433 self.current_ball = None;
434 }
435
436 if let Some(current_ball) = &mut self.current_ball {
437 shared_state.debug_info.custom.insert("Circle Radius".to_string(), format!("{:.2}", current_ball.radius));
438 shared_state.debug_info.custom.insert("Circle Center (local)".to_string(), format!("({}, {})", current_ball.local_x(), current_ball.local_y()));
439 shared_state.debug_info.custom.insert("Circle Center (world)".to_string(), format!("({}, {})", current_ball.world_x(), current_ball.world_y()));
440 }
441
442 if let Some(first_ball) = self.free_balls.first() {
443 shared_state.debug_info.custom.insert("First Ball Center (local)".to_string(), format!("({:.2}, {:.2})", first_ball.local_x(), first_ball.local_y()));
444 shared_state.debug_info.custom.insert("First Ball Center (world)".to_string(), format!("({:.2}, {:.2})", first_ball.world_x(), first_ball.world_y()));
445 shared_state.debug_info.custom.insert("First Ball velocity".to_string(), format!("({:.2}, {:.2})", first_ball.x_vel, first_ball.y_vel));
446
447 }
448
449 update_balls(update_info.dt, &mut self.free_balls, shared_state.display_info.height() as f64, &self.static_collision);
450
451 self.fixed_update_runner.fuel(update_info.dt);
452 while self.fixed_update_runner.has_gas() {
453 self.fixed_update_runner.consume();
454 if let Some(current_ball) = &mut self.current_ball {
455 self.center_samples.push((current_ball.local_x(), current_ball.local_y()));
456 if self.center_samples.len() > MAX_SAMPLES {
457 self.center_samples.remove(0);
458 }
459 }
460 }
461
462 shared_state.mouse_events.for_each_linerp_only_fresh(|mi| {
464 if mi.right_mouse_down {
465 self.static_collision.set(mi.last_mouse_pos.0, mi.last_mouse_pos.1, true);
466 }
467 })
468 }
469
470 fn render(&self, renderer: &mut dyn Renderer, shared_state: &SharedState, depth_base: i32) {
471 for ball in &self.free_balls {
472 ball.render(renderer, false, depth_base);
473 }
474 if let Some(current_ball) = &self.current_ball {
475 current_ball.render(renderer, true, depth_base+10);
476 }
477
478 for x in 0..self.static_collision.width() {
480 for y in 0..self.static_collision.height() {
481 if self.static_collision[(x, y)] {
482 renderer.render_pixel(x, y, Pixel::new('O').with_color([0, 255, 0]), depth_base);
483 }
484 }
485 }
486 }
487}
488
489fn update_balls(dt: f64, balls: &mut [Ball], bottom_wall_height: f64, static_collision: &Display<bool>) {
490 for i in 0..balls.len() {
491 let ball = &mut balls[i];
493 ball.y_vel = ball.y_vel + 80.0 * dt;
494 ball.x_vel = ball.x_vel + ball.x_vel.signum() * -10.0 * dt;
496
497 }
499
500 let is_solid_world = |x: f64, y: f64| {
501 if x < 0.0 || y < 0.0 {
502 return false;
503 }
504 *static_collision.get(x as usize, y as usize).unwrap_or(&false)
505 };
506
507 ball::update_balls(dt, balls, bottom_wall_height, is_solid_world);
508
509 }
641
642fn main() -> io::Result<()> {
643 terminal_setup()?;
644 install_panic_handler();
645
646 let mut game = Game::new_with_custom_buf_writer();
647 game.install_recommended_components();
648 game.add_component(Box::new(CircleRasterizerComponent::default()));
649 game.run()?;
650
651 terminal_cleanup()?;
652
653 Ok(())
654}