use std::io;
use crossterm::event::{Event, MouseEvent, MouseEventKind};
use teng::components::Component;
use teng::rendering::pixel::Pixel;
use teng::rendering::render::Render;
use teng::rendering::renderer::Renderer;
use teng::{install_panic_handler, terminal_cleanup, terminal_setup, BreakingAction, Game, SetupInfo, SharedState, UpdateInfo};
use teng::rendering::display::Display;
use teng::util::fixedupdate::FixedUpdateRunner;
#[derive(Clone)]
struct Ball {
x: f64,
y: f64,
x_vel: f64,
y_vel: f64,
radius: f64,
mass: f64,
}
impl Ball {
fn for_each_coord_in_outline(&self, mut f: impl FnMut(f64, f64) -> bool) {
let mut x = self.radius as i64 + 1;
let mut y = 0;
let mut err = 0;
let center_x = self.x as i64;
let center_y = self.y as i64;
while x >= y {
if f((center_x + x) as f64, (center_y + y/2) as f64) {
return;
}
if f((center_x + y) as f64, (center_y + x/2) as f64) {
return;
}
if f((center_x - y) as f64, (center_y + x/2) as f64) {
return;
}
if f((center_x - x) as f64, (center_y + y/2) as f64) {
return;
}
if f((center_x - x) as f64, (center_y - y/2) as f64) {
return;
}
if f((center_x - y) as f64, (center_y - x/2) as f64) {
return;
}
if f((center_x + y) as f64, (center_y - x/2) as f64) {
return;
}
if f((center_x + x) as f64, (center_y - y/2) as f64) {
return;
}
y += 1;
if err <= 0 {
err += 2 * y + 1;
}
if err > 0 {
x -= 1;
err -= 2 * x + 1;
}
}
}
fn for_each_coord_in_filled(&self, mut f: impl FnMut(f64, f64) -> bool, radius_adjustment: f64) {
let center_x = self.x as i64;
let center_y = self.y as i64;
let mut x = (self.radius + radius_adjustment) as i64;
let mut y = 0;
let mut err = 0.0;
while x >= y {
if y % 2 == 0 {
for i in center_x - x..=center_x + x {
if f(i as f64, (center_y + y/2) as f64) {
return;
}
if f(i as f64, (center_y - y/2) as f64) {
return;
}
}
}
if x % 2 == 0 {
for i in center_x - y..=center_x + y {
if f(i as f64, (center_y + x/2) as f64) {
return;
}
if f(i as f64, (center_y - x/2) as f64) {
return;
}
}
}
y += 1;
if err <= 0.0 {
err += 2.0 * y as f64 + 1.0;
}
if err > 0.0 {
x -= 1;
err -= 2.0 * x as f64 + 1.0;
}
}
}
fn update(&mut self, dt: f64, bottom_wall_height: f64) {
self.y_vel = self.y_vel + 40.0 * dt;
self.y += self.y_vel * dt;
self.x += self.x_vel * dt;
let collides_bottom = self.y + self.radius / 2.0 >= bottom_wall_height;
if collides_bottom {
self.y = bottom_wall_height - self.radius / 2.0;
self.y_vel = -self.y_vel * 0.8;
}
}
fn render(&self, renderer: &mut dyn Renderer, render_outline: bool, depth: i32) {
let radius_x = self.radius;
let radius_y = self.radius / 2.0;
let center_x = self.x as i64;
let center_y = self.y as i64;
let mut x = radius_x as i64;
let mut y = 0;
let mut err = 0.0;
while x >= y {
if y % 2 == 0 {
for i in center_x - x..=center_x + x {
renderer.render_pixel(i as usize, (center_y + y/2) as usize, Pixel::new('X'), depth);
renderer.render_pixel(i as usize, (center_y - y/2) as usize, Pixel::new('X'), depth);
}
}
if x % 2 == 0 {
for i in center_x - y..=center_x + y {
renderer.render_pixel(i as usize, (center_y + x/2) as usize, Pixel::new('X'), depth);
renderer.render_pixel(i as usize, (center_y - x/2) as usize, Pixel::new('X'), depth);
}
}
y += 1;
if err <= 0.0 {
err += 2.0 * y as f64 + 1.0;
}
if err > 0.0 {
x -= 1;
err -= 2.0 * x as f64 + 1.0;
}
}
if !render_outline {
return;
}
let depth_radius = depth + 1;
let pixel = Pixel::new('X').with_color([255, 0, 0]);
self.for_each_coord_in_outline(|x, y| {
renderer.render_pixel(x as usize, y as usize, pixel, depth_radius);
false
});
return;
let mut x = self.radius as i64 + 1;
let mut y = 0;
let mut err = 0;
let center_x = self.x as i64;
let center_y = self.y as i64;
while x >= y {
let pixel = Pixel::new('X').with_color([255, 0, 0]);
renderer.render_pixel((center_x + x) as usize, (center_y + y/2) as usize, pixel, depth_radius);
renderer.render_pixel((center_x + y) as usize, (center_y + x/2) as usize, pixel, depth_radius);
renderer.render_pixel((center_x - y) as usize, (center_y + x/2) as usize, pixel, depth_radius);
renderer.render_pixel((center_x - x) as usize, (center_y + y/2) as usize, pixel, depth_radius);
renderer.render_pixel((center_x - x) as usize, (center_y - y/2) as usize, pixel, depth_radius);
renderer.render_pixel((center_x - y) as usize, (center_y - x/2) as usize, pixel, depth_radius);
renderer.render_pixel((center_x + y) as usize, (center_y - x/2) as usize, pixel, depth_radius);
renderer.render_pixel((center_x + x) as usize, (center_y - y/2) as usize, pixel, depth_radius);
y += 1;
if err <= 0 {
err += 2 * y + 1;
}
if err > 0 {
x -= 1;
err -= 2 * x + 1;
}
}
}
}
struct CircleRasterizerComponent {
free_balls: Vec<Ball>,
current_ball: Option<Ball>,
center_samples: Vec<(f64, f64)>,
fixed_update_runner: FixedUpdateRunner,
did_hold_last: bool,
default_radius: f64,
static_collision: Display<bool>,
}
impl Default for CircleRasterizerComponent {
fn default() -> Self {
Self {
free_balls: vec![],
current_ball: None,
center_samples: vec![],
did_hold_last: false,
fixed_update_runner: FixedUpdateRunner::new(1.0 / 60.0),
default_radius: 10.0,
static_collision: Display::new(0, 0, false),
}
}
}
const MAX_SAMPLES: usize = 5;
impl Component for CircleRasterizerComponent {
fn setup(&mut self, setup_info: &SetupInfo, shared_state: &mut SharedState<()>) {
self.on_resize(setup_info.display_info.width(), setup_info.display_info.height(), shared_state);
}
fn on_resize(&mut self, width: usize, height: usize, shared_state: &mut SharedState<()>) {
self.static_collision.resize_keep(width, height);
}
fn on_event(&mut self, event: Event, shared_state: &mut SharedState<()>) -> Option<BreakingAction> {
if let Event::Mouse(MouseEvent { kind: kind @ (MouseEventKind::ScrollDown | MouseEventKind::ScrollUp), .. }) = event {
let delta = match kind {
MouseEventKind::ScrollDown => -1.0,
MouseEventKind::ScrollUp => 1.0,
_ => 0.0,
};
if let Some(current_ball) = &mut self.current_ball {
current_ball.radius += delta;
current_ball.mass = current_ball.radius * current_ball.radius;
}
self.default_radius += delta;
}
None
}
fn update(&mut self, update_info: UpdateInfo, shared_state: &mut SharedState<()>) {
if shared_state.pressed_keys.did_press_char_ignore_case('c') {
self.free_balls.clear();
}
if shared_state.mouse_info.left_mouse_down {
let current_ball = self.current_ball.get_or_insert(Ball {
x: shared_state.mouse_info.last_mouse_pos.0 as f64,
y: shared_state.mouse_info.last_mouse_pos.1 as f64,
radius: self.default_radius,
mass: self.default_radius * self.default_radius,
x_vel: 0.0,
y_vel: 0.0,
});
current_ball.x = shared_state.mouse_info.last_mouse_pos.0 as f64;
current_ball.y = shared_state.mouse_info.last_mouse_pos.1 as f64;
current_ball.y_vel = 0.0;
current_ball.x_vel = 0.0;
if !self.did_hold_last {
self.center_samples.clear();
}
self.did_hold_last = true;
} else if self.did_hold_last {
let current_ball = self.current_ball.as_mut().unwrap();
self.did_hold_last = false;
let mut sum_x_delta = 0.0;
let mut sum_y_delta = 0.0;
for i in 1..self.center_samples.len() {
let (x1, y1) = self.center_samples[i - 1];
let (x2, y2) = self.center_samples[i];
sum_x_delta += x2 - x1;
sum_y_delta += y2 - y1;
}
let delta_length = self.center_samples.len() as f64 / 60.0;
let avg_x_vel = sum_x_delta / delta_length;
let avg_y_vel = sum_y_delta / delta_length;
let strength = 1.0;
current_ball.x_vel = avg_x_vel * strength;
current_ball.y_vel = avg_y_vel * strength;
self.free_balls.push(current_ball.clone());
self.current_ball = None;
}
if let Some(current_ball) = &mut self.current_ball {
shared_state.debug_info.custom.insert("Circle Radius".to_string(), format!("{:.2}", current_ball.radius));
shared_state.debug_info.custom.insert("Circle Center".to_string(), format!("({}, {})", current_ball.x, current_ball.y));
}
if !shared_state.mouse_info.left_mouse_down {
self.current_ball.as_mut().map(|ball| ball.update(update_info.dt, shared_state.display_info.height() as f64));
}
update_balls(update_info.dt, &mut self.free_balls, shared_state.display_info.height() as f64, &self.static_collision);
self.fixed_update_runner.fuel(update_info.dt);
while self.fixed_update_runner.has_gas() {
self.fixed_update_runner.consume();
if let Some(current_ball) = &mut self.current_ball {
self.center_samples.push((current_ball.x, current_ball.y));
if self.center_samples.len() > MAX_SAMPLES {
self.center_samples.remove(0);
}
}
}
shared_state.mouse_events.for_each_linerp_only_fresh(|mi| {
if mi.right_mouse_down {
self.static_collision.set(mi.last_mouse_pos.0, mi.last_mouse_pos.1, true);
}
})
}
fn render(&self, renderer: &mut dyn Renderer, shared_state: &SharedState, depth_base: i32) {
for ball in &self.free_balls {
ball.render(renderer, false, depth_base);
}
if let Some(current_ball) = &self.current_ball {
current_ball.render(renderer, true, depth_base+10);
}
for x in 0..self.static_collision.width() {
for y in 0..self.static_collision.height() {
if self.static_collision[(x, y)] {
renderer.render_pixel(x, y, Pixel::new('O').with_color([0, 255, 0]), depth_base);
}
}
}
}
}
fn update_balls(dt: f64, balls: &mut [Ball], bottom_wall_height: f64, static_collision: &Display<bool>) {
for i in 0..balls.len() {
let ball = &mut balls[i];
ball.y_vel = ball.y_vel + 40.0 * dt;
ball.x_vel = ball.x_vel + ball.x_vel.signum() * -10.0 * dt;
ball.y += ball.y_vel * dt;
ball.x += ball.x_vel * dt;
let collides_bottom = ball.y + ball.radius / 2.0 >= bottom_wall_height;
if collides_bottom {
ball.y = bottom_wall_height - ball.radius / 2.0 - 0.1;
ball.y_vel = -ball.y_vel * 0.8;
}
}
for ball in balls.iter_mut() {
let mut closest_hit = None;
let mut closest_distance_2 = f64::INFINITY;
ball.for_each_coord_in_filled(|x, y| {
let x_u = x as usize;
let y_u = y as usize;
if let Some(true) = static_collision.get(x_u, y_u) {
let dx = x - ball.x;
let dy = y - ball.y;
let distance = dx*dx + dy*dy;
if distance < closest_distance_2 {
closest_distance_2 = distance;
closest_hit = Some((x, y));
}
}
false
}, 1.0);
if let Some((x, y)) = closest_hit {
let dx = x - ball.x;
let dy = y - ball.y;
let distance = (dx*dx + dy*dy).sqrt();
let normal_x = -dx / distance;
let normal_y = -dy / distance;
let x_vel = ball.x_vel;
let y_vel = ball.y_vel;
let dot = x_vel * normal_x + y_vel * normal_y;
if dot > 0.0 {
continue;
}
let r_x = x_vel - 2.0 * dot * normal_x;
let r_y = y_vel - 2.0 * dot * normal_y;
ball.x_vel = r_x * 0.8;
ball.y_vel = r_y * 0.8;
}
}
for i in 0..balls.len() {
for j in i+1..balls.len() {
let (balls1, balls2) = balls.split_at_mut(j);
let ball1 = &mut balls1[i];
let ball2 = &mut balls2[0];
let dx = ball1.x - ball2.x;
let dy = ball1.y - ball2.y;
let dy = dy * 2.0;
let distance = (dx*dx + dy*dy).sqrt();
let overlap = ball1.radius + ball2.radius - distance;
if overlap > 0.0 {
let overlap = overlap / 2.0;
let dx = dx / distance * overlap;
let dy = dy / distance * overlap;
ball1.x += dx;
ball1.y += dy;
ball2.x -= dx;
ball2.y -= dy;
let ball1_mass = ball1.mass;
let ball2_mass = ball2.mass;
let normal_x = dx / overlap;
let normal_y = dy / overlap;
let relative_velocity_x = ball1.x_vel - ball2.x_vel;
let relative_velocity_y = ball1.y_vel - ball2.y_vel;
let dot_product = relative_velocity_x * normal_x + relative_velocity_y * normal_y;
if dot_product < 0.0 {
let impulse = 2.0 * dot_product / (ball1_mass + ball2_mass);
ball1.x_vel -= impulse * normal_x * ball2_mass;
ball1.y_vel -= impulse * normal_y * ball2_mass;
ball2.x_vel += impulse * normal_x * ball1_mass;
ball2.y_vel += impulse * normal_y * ball1_mass;
}
}
}
}
}
fn main() -> io::Result<()> {
terminal_setup()?;
install_panic_handler();
let mut game = Game::new_with_custom_buf_writer();
game.install_recommended_components();
game.add_component(Box::new(CircleRasterizerComponent::default()));
game.run()?;
terminal_cleanup()?;
Ok(())
}