/// file: main.rs /// author: Rohmer Maxime /// date: 10/05/2022 /// version: 0.1.0 /// Sources: /// https://eater.net/boids /// https://docs.rs/sdl2/latest/sdl2/ use sdl2::pixels::Color; use sdl2::event::Event; use sdl2::keyboard::Keycode; //use sdl2::keyboard::Scancode; //use std::collections::HashSet; use std::time::Duration; use sdl2::rect::Point; use sdl2::rect::Rect; use rand::Rng; const WINDOW_HEIGHT:i32 = 800; const WINDOW_WIDTH:i32 = 1000; const MAX_BIRD_SPEED:i32 = 40; const BIRDS_COUNT:i32 = 200; const BIRD_SIZE:i32 = 15; const VISION_RANGE:i32 = 10; const COHERENCE_RATE:i32 = 1; const SEPRATION_RATE:i32 = 1; const ALIGNEMENT_RATE:i32 = 1; const NEIGHBOUR_TRESHOLD:i32 = 10; pub struct Bird{ shape:Rect, velocity:Point, } pub struct Simulation{ birds:Vec, } impl Simulation{ pub fn render(&mut self,ctx:&mut sdl2::render::Canvas){ ctx.set_draw_color(Color::RGB(0x6D, 0x6D, 0x64)); ctx.clear(); for bird in self.birds.iter(){ ctx.set_draw_color(Color::RGB(0x33, 0x33, 0x33)); ctx.fill_rect(bird.shape).expect("Rusty Boids"); ctx.set_draw_color(Color::RGB(0xe7, 0x4c, 0x3c)); let start_pos = Point::new(bird.shape.x + bird.shape.width() as i32 / 2,bird.shape.y + bird.shape.height() as i32 /2); let end_pos = Point::new(start_pos.x + bird.velocity.x * 2,start_pos.y + bird.velocity.y * 2); ctx.draw_line(start_pos,end_pos).expect("Could nor draw vector line"); } } pub fn apply_coherence(&mut self){ // first we calculate all the averages for every birds let mut averages:Vec = Vec::with_capacity(BIRDS_COUNT as usize); for i in 0..BIRDS_COUNT{ let mut sum = Point::new(0,0); let target = &self.birds[i as usize]; let mut x_offset:i32; let mut y_offset:i32; let mut neighbours_count = 0; for bird in &self.birds{ x_offset = (target.shape.x - bird.shape.x).abs(); y_offset = (target.shape.y - bird.shape.y).abs(); if x_offset <= VISION_RANGE && y_offset <= VISION_RANGE{ sum.x += bird.shape.x; sum.y += bird.shape.y; neighbours_count += 1; } } let average = Point::new(sum.x / neighbours_count as i32,sum.y / neighbours_count as i32); averages.push(average); } for i in 0..BIRDS_COUNT{ //now we need to steer torwards it let bird = &mut self.birds[i as usize]; let posx = bird.shape.x; let posy = bird.shape.y; let average = averages[i as usize]; let mut added_velocity:Point = Point::new(0,0); if posx > average.x{ added_velocity.x -= COHERENCE_RATE; }else if posx < average.x{ added_velocity.x += COHERENCE_RATE; } if posy > average.y{ added_velocity.y -= COHERENCE_RATE; }else if posy < average.y{ added_velocity.y += COHERENCE_RATE; } if added_velocity.x > COHERENCE_RATE / 3{ added_velocity.x = COHERENCE_RATE / 3; } if added_velocity.x < -COHERENCE_RATE / 3{ added_velocity.x = -COHERENCE_RATE / 3; } if added_velocity.y > COHERENCE_RATE / 3{ added_velocity.y = COHERENCE_RATE / 3; } if added_velocity.y < -COHERENCE_RATE / 3{ added_velocity.y = -COHERENCE_RATE / 3; } bird.velocity.x += added_velocity.x; bird.velocity.y += added_velocity.y; } } pub fn apply_separation(&mut self){ // first we calculate all the averages for every birds let mut averages:Vec = Vec::with_capacity(BIRDS_COUNT as usize); for i in 0..BIRDS_COUNT{ let mut sum = Point::new(0,0); let target = &self.birds[i as usize]; let mut x_offset:i32; let mut y_offset:i32; let mut neighbours_count = 0; for bird in &self.birds{ x_offset = (target.shape.x - bird.shape.x).abs(); y_offset = (target.shape.y - bird.shape.y).abs(); if x_offset <= NEIGHBOUR_TRESHOLD && y_offset <= NEIGHBOUR_TRESHOLD{ sum.x += bird.shape.x; sum.y += bird.shape.y; neighbours_count += 1; } } let average = Point::new(sum.x / neighbours_count as i32,sum.y / neighbours_count as i32); averages.push(average); } for i in 0..BIRDS_COUNT{ //now we need to steer torwards it let bird = &mut self.birds[i as usize]; let posx = bird.shape.x; let posy = bird.shape.y; let average = averages[i as usize]; let mut added_velocity:Point = Point::new(0,0); if posx > average.x{ added_velocity.x -= SEPRATION_RATE; }else if posx < average.x{ added_velocity.x += SEPRATION_RATE; } if posy > average.y{ added_velocity.y -= SEPRATION_RATE; }else if posy < average.y{ added_velocity.y += SEPRATION_RATE; } if added_velocity.x > MAX_BIRD_SPEED / 3{ added_velocity.x = MAX_BIRD_SPEED / 3; } if added_velocity.x < -MAX_BIRD_SPEED / 3{ added_velocity.x = -MAX_BIRD_SPEED / 3; } if added_velocity.y > MAX_BIRD_SPEED / 3{ added_velocity.y = MAX_BIRD_SPEED / 3; } if added_velocity.y < -MAX_BIRD_SPEED / 3{ added_velocity.y = -MAX_BIRD_SPEED / 3; } bird.velocity.x += added_velocity.x; bird.velocity.y += added_velocity.y; } } pub fn apply_alignement(&mut self){ // first we calculate all the averages for every birds let mut averages:Vec = Vec::with_capacity(BIRDS_COUNT as usize); for i in 0..BIRDS_COUNT{ let mut sum = Point::new(0,0); let target = &self.birds[i as usize]; let mut x_offset:i32; let mut y_offset:i32; let mut neighbours_count = 0; for bird in &self.birds{ x_offset = (target.shape.x - bird.shape.x).abs(); y_offset = (target.shape.y - bird.shape.y).abs(); if x_offset <= VISION_RANGE && y_offset <= VISION_RANGE{ sum.x += bird.velocity.x; sum.y += bird.velocity.y; neighbours_count += 1; } } let average = Point::new(sum.x / neighbours_count as i32,sum.y / neighbours_count as i32); averages.push(average); } for i in 0..BIRDS_COUNT{ //now we need to steer torwards it let bird = &mut self.birds[i as usize]; let posx = bird.shape.x; let posy = bird.shape.y; let average = averages[i as usize]; let mut added_velocity:Point = Point::new(0,0); if posx > average.x{ added_velocity.x -= ALIGNEMENT_RATE; }else if posx < average.x{ added_velocity.x += ALIGNEMENT_RATE; } if posy > average.y{ added_velocity.y -= ALIGNEMENT_RATE; }else if posy < average.y{ added_velocity.y += ALIGNEMENT_RATE; } if added_velocity.x > MAX_BIRD_SPEED / 3{ added_velocity.x = MAX_BIRD_SPEED / 3; } if added_velocity.x < -MAX_BIRD_SPEED / 3{ added_velocity.x = -MAX_BIRD_SPEED / 3; } if added_velocity.y > MAX_BIRD_SPEED / 3{ added_velocity.y = MAX_BIRD_SPEED / 3; } if added_velocity.y < -MAX_BIRD_SPEED / 3{ added_velocity.y = -MAX_BIRD_SPEED / 3; } bird.velocity.x += added_velocity.x; bird.velocity.y += added_velocity.y; } } pub fn update(&mut self){ self.apply_coherence(); self.apply_alignement(); self.apply_separation(); for bird in &mut self.birds{ check_velocity(bird); let mut posx = bird.shape.x; let mut posy = bird.shape.y; let width:i32 = bird.shape.width() as i32; let height:i32 = bird.shape.height() as i32; let push_back = 1; let soft_border_margin = 30; let left_soft_border = 0 + soft_border_margin; let right_soft_border = WINDOW_WIDTH - soft_border_margin; let top_soft_border = 0 + soft_border_margin; let bottom_soft_border = WINDOW_HEIGHT -soft_border_margin; if posx + bird.velocity.x > WINDOW_WIDTH - width || posx + bird.velocity.x < 0{ bird.velocity.x = 0; } if posy + bird.velocity.y > WINDOW_HEIGHT - height || posy + bird.velocity.y < 0{ bird.velocity.y = 0; } if posx + bird.velocity.x > right_soft_border && bird.velocity.x <= 0{ //the bird is coming from the border so we accelerate it bird.velocity.x -= push_back; }else{ bird.velocity.x += push_back; } if posx - bird.velocity.x < left_soft_border && bird.velocity.x >= 0{ //the bird is coming from the border so we accelerate it bird.velocity.x += push_back; }else{ bird.velocity.x -= push_back; } if posy + bird.velocity.y > bottom_soft_border && bird.velocity.y <= 0{ //the bird is coming from the border so we accelerate it bird.velocity.y -= push_back; }else{ bird.velocity.y += push_back; } if posy - bird.velocity.y < top_soft_border && bird.velocity.y >= 0{ //the bird is coming from the border so we accelerate it bird.velocity.y += push_back; }else{ bird.velocity.y -= push_back; } posx += bird.velocity.x; posy += bird.velocity.y; bird.shape.set_x(posx); bird.shape.set_y(posy); bird.velocity = Point::new(0,0); } } } pub fn check_velocity(bird:&mut Bird){ if bird.velocity.y > MAX_BIRD_SPEED{ bird.velocity.y = MAX_BIRD_SPEED; }else if bird.velocity.y < -MAX_BIRD_SPEED{ bird.velocity.y = -MAX_BIRD_SPEED; } if bird.velocity.x > MAX_BIRD_SPEED{ bird.velocity.x = MAX_BIRD_SPEED; }else if bird.velocity.x < -MAX_BIRD_SPEED{ bird.velocity.x = -MAX_BIRD_SPEED; } } pub fn main_loop(canvas: &mut sdl2::render::Canvas,event_pump:&mut sdl2::EventPump,game:&mut Simulation){ 'running: loop { for event in event_pump.poll_iter() { match event { Event::Quit {..} | Event::KeyDown { keycode: Some(Keycode::Escape), .. } => { break 'running }, _ => {} } } // MAIN LOOP game.update(); game.render(canvas); // MAIN LOOP canvas.present(); ::std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 60)); } } pub fn generate_birds() -> Vec{ let mut rng = rand::thread_rng(); let mut birds = Vec::with_capacity(BIRDS_COUNT as usize); for _bird in 0..BIRDS_COUNT{ let rectangle = Rect::new(rng.gen_range(0..WINDOW_WIDTH - BIRD_SIZE),rng.gen_range(0..WINDOW_HEIGHT - BIRD_SIZE),BIRD_SIZE as u32, BIRD_SIZE as u32); let velocity = Point::new(rng.gen_range(-MAX_BIRD_SPEED..MAX_BIRD_SPEED), rng.gen_range(-MAX_BIRD_SPEED..MAX_BIRD_SPEED)); birds.push(Bird{shape:rectangle,velocity:velocity}); } return birds; } pub fn main() { let sdl_context = sdl2::init().unwrap(); let video_subsystem = sdl_context.video().unwrap(); let window = video_subsystem.window("Rust Boids Simulation", WINDOW_WIDTH as u32, WINDOW_HEIGHT as u32) .position_centered() .build() .unwrap(); let mut canvas = window.into_canvas().build().unwrap(); let mut event_pump = sdl_context.event_pump().unwrap(); let mut simulation = Simulation{birds:generate_birds()}; main_loop(&mut canvas,&mut event_pump,&mut simulation); }