<file_sep>mod boid;
pub mod boid_pool;
mod lattice;
<file_sep>use super::boid::Boid;
use super::lattice::Lattice;
use vector2d::Vector2D;

#[link(wasm_import_module = "../src/canvas.js")]
extern "C" {
    fn clear_canvas();
    fn draw_boid(x: f64, y: f64, angle: f64);
}

pub struct BoidPool {
    boids: Vec<Boid>,
}

impl BoidPool {
    pub fn new() -> BoidPool {
        BoidPool { boids: vec![] }
    }

    pub fn add_boid(&mut self, x: f64, y: f64) {
        self.boids.push(Boid::new(Vector2D::new(x, y)));
    }

    pub fn update(&mut self, width: &f64, height: &f64, mouse_pos: &Vector2D<f64>) {
        let lattice = Lattice::new(&self.boids, width, height);

        for boid in &mut self.boids {
            let close_boids = lattice.get_neighbors(boid);

            boid.align(&close_boids);
            boid.cohesion(&close_boids);
            boid.separation(&close_boids);
            boid.wander();
            //boid.seek(mouse_pos);
            boid.update(width, height);
        }
    }

    pub fn render(&self) {
        unsafe {
            clear_canvas();

            for boid in &self.boids {
                draw_boid(boid.position.x, boid.position.y, boid.velocity.angle());
            }
        }
    }
}
<file_sep>const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');

canvas.width = Number(canvas.getBoundingClientRect().width) * 2;
canvas.height = Number(canvas.getBoundingClientRect().height) * 2;
context.scale(2, 2);

export function clear_canvas() {
    context.clearRect(0, 0, canvas.width, canvas.height);
}

export function draw_boid(x, y, angle) {
    context.save();
    context.translate(x, y);
    context.rotate(angle);

    context.beginPath();

    [[5, 0], [-5, -3], [-5, 3]].forEach(([x, y], index) => (index === 0 ? context.moveTo(x, y) : context.lineTo(x, y)));

    context.closePath();
    context.stroke();
    context.restore();
}
<file_sep>use crate::utils::vector2d::Vector2DExt;
use vector2d::Vector2D;
use wasm_bindgen::__rt::core::f64::consts::PI;

pub const MAX_FORCE: f64 = 1.0;
pub const MAX_VELOCITY: f64 = 3.0;
pub const VIEW_RADIUS: f64 = 20.0;
pub const WEIGHT: f64 = 10.0;

const WANDER_CHANGEABLE_ANGLE: f64 = PI / 2.0;
const WANDER_HALF_CHANGEABLE_ANGLE: f64 = WANDER_CHANGEABLE_ANGLE / 2.0;
const WANDER_CIRCLE_DISTANCE: f64 = 2.0;
const WANDER_CIRCLE_RADIUS: f64 = 7.0;

#[derive(Clone)]
pub struct Boid {
    pub position: Vector2D<f64>,
    pub velocity: Vector2D<f64>,
    steering: Vector2D<f64>,
    wander_vector: Vector2D<f64>,
}

impl Boid {
    pub fn new(position: Vector2D<f64>) -> Boid {
        Boid {
            position,
            velocity: Vector2D::new(
                rand::random::<f64>() * 10.0 - 5.0,
                rand::random::<f64>() * 10.0 - 5.0,
            )
            .normalise()
                * MAX_VELOCITY,
            steering: Vector2D::new(0.0, 0.0),
            wander_vector: Vector2D::new(0.0, 0.0),
        }
    }

    pub fn seek(&mut self, target: &Vector2D<f64>) {
        let desired_position = *target - self.position;
        let distance = desired_position.length();
        let mut desired_velocity = desired_position.normalise() * MAX_VELOCITY;

        // slow down the closer the boid gets
        if distance <= VIEW_RADIUS {
            desired_velocity *= distance / VIEW_RADIUS;
        }

        self.steering += (desired_velocity - self.velocity).limit(MAX_FORCE);
    }

    pub fn wander(&mut self) {
        let circle_center = self.velocity.normalise() * WANDER_CIRCLE_DISTANCE;
        let angle: f64 =
            WANDER_CHANGEABLE_ANGLE * rand::random::<f64>() - WANDER_HALF_CHANGEABLE_ANGLE;
        let circle = self.wander_vector.rotate(angle) * WANDER_CIRCLE_RADIUS;
        let new_wander_vector = circle_center + circle;

        self.steering += new_wander_vector.limit(MAX_FORCE);
        self.wander_vector = new_wander_vector.normalise();
    }

    pub fn align(&mut self, boids: &Vec<&Boid>) {
        let mut steering = Vector2D::new(0.0, 0.0);

        for boid in boids {
            steering += boid.velocity;
        }

        if boids.len() > 0 {
            steering /= boids.len() as f64;
            steering = steering.normalise() * MAX_VELOCITY;
            self.steering += steering - self.velocity;
        }
    }

    pub fn cohesion(&mut self, boids: &Vec<&Boid>) {
        let mut steering = Vector2D::new(0.0, 0.0);

        for boid in boids {
            steering += boid.position;
        }

        if boids.len() > 0 {
            steering /= boids.len() as f64;
            steering = (steering - self.position).normalise() * MAX_VELOCITY;
            steering -= self.velocity;
            steering = steering.limit(MAX_FORCE);
            self.steering += steering;
        }
    }

    pub fn separation(&mut self, boids: &Vec<&Boid>) {
        let mut steering = Vector2D::new(0.0, 0.0);

        for boid in boids {
            steering += (self.position - boid.position).normalise();
        }

        if boids.len() > 0 {
            steering /= boids.len() as f64;
            steering = steering.normalise() * MAX_VELOCITY;
            steering -= self.velocity;
            steering = steering.limit(MAX_FORCE);
            self.steering += steering;
        }
    }

    pub fn update(&mut self, width: &f64, height: &f64) {
        let velocity = self.velocity + self.steering.limit(MAX_FORCE) / WEIGHT;
        self.velocity = velocity.limit(MAX_VELOCITY);
        self.calculate_next_position(width, height);
        self.steering = Vector2D::new(0.0, 0.0);
    }

    fn calculate_next_position(&mut self, width: &f64, height: &f64) {
        let mut pos = self.position + self.velocity;

        if pos.x < 0.0 {
            pos.x += *width;
        } else if pos.x >= *width {
            pos.x -= *width;
        }

        if pos.y < 0.0 {
            pos.y += *height;
        } else if pos.y >= *height {
            pos.y -= *height;
        }

        self.position = pos;
    }
}
<file_sep>use super::boid::{Boid, VIEW_RADIUS};
use vector2d::Vector2D;
use wasm_bindgen::__rt::std::collections::HashMap;

const SQUARED_VIEW_RADIUS: f64 = VIEW_RADIUS * VIEW_RADIUS;

pub struct Lattice {
    x_cells: i32,
    y_cells: i32,
    boids: HashMap<i32, Vec<Boid>>,
}

impl Lattice {
    pub fn new(boids: &Vec<Boid>, width: &f64, height: &f64) -> Lattice {
        let mut lattice: HashMap<i32, Vec<Boid>> = HashMap::new();
        let x_cells = (*width / VIEW_RADIUS * 2.0).ceil() as i32;
        let y_cells = (*height / VIEW_RADIUS * 2.0).ceil() as i32;

        boids.iter().for_each(|boid| {
            let index = Self::convert_position_to_lattice_index(&boid.position, &x_cells, &y_cells);

            if lattice.contains_key(&index) {
                lattice.get_mut(&index).unwrap().push(boid.clone());
            } else {
                lattice.insert(index, vec![boid.clone()]);
            }
        });

        Lattice {
            x_cells,
            y_cells,
            boids: lattice,
        }
    }

    pub fn get_neighbors(&self, boid: &Boid) -> Vec<&Boid> {
        let index =
            Self::convert_position_to_lattice_index(&boid.position, &self.x_cells, &self.y_cells);
        let neighbor_indexes = self.calculate_neighbor_indexes(&index);

        let filter_by_distance =
            |b: &&Boid| (b.position - boid.position).length_squared() <= SQUARED_VIEW_RADIUS;

        let self_cell_boids: Vec<&Boid> = self.boids[&index]
            .iter()
            .filter(|b| boid.position != b.position)
            .filter(filter_by_distance)
            .collect();

        let mut neighbor_boids: Vec<&Boid> = neighbor_indexes
            .iter()
            .filter_map(|i| self.boids.get(i))
            .flatten()
            .filter(filter_by_distance)
            .collect();

        neighbor_boids.extend(self_cell_boids.into_iter());

        neighbor_boids
    }

    fn calculate_neighbor_indexes(&self, index: &i32) -> Vec<i32> {
        let calculate_left_and_right = |num: &i32| {
            [*num - 1, *num + 1]
                .iter()
                .map(|i| {
                    let initial_y_cell = *num / self.x_cells;
                    let y_cell = *i / self.x_cells;
                    if y_cell < initial_y_cell {
                        *num + self.x_cells - 1
                    } else if y_cell > initial_y_cell {
                        *num - self.x_cells + 1
                    } else {
                        *i
                    }
                })
                .collect::<Vec<i32>>()
        };

        let top_and_bottom = [*index - self.x_cells, *index + self.x_cells]
            .iter()
            .map(|i| {
                if *i < 0 {
                    *index + self.x_cells * (self.y_cells - 1)
                } else if *i > self.x_cells * self.y_cells - 1 {
                    *index - self.x_cells * (self.y_cells - 1)
                } else {
                    *i
                }
            })
            .collect::<Vec<i32>>();

        let diagonals = top_and_bottom
            .iter()
            .flat_map(calculate_left_and_right)
            .collect::<Vec<i32>>();

        let mut indexes = calculate_left_and_right(index);
        indexes.extend(top_and_bottom.iter());
        indexes.extend(diagonals.iter());
        indexes
    }

    fn convert_position_to_lattice_index(
        position: &Vector2D<f64>,
        x_cells: &i32,
        y_cells: &i32,
    ) -> i32 {
        let Vector2D { x, y } = position;
        let x_cell = (*x / *x_cells as f64).floor();
        let y_cell = (*y / *y_cells as f64).floor();
        (y_cell * (*x_cells as f64) + x_cell) as i32
    }
}
<file_sep>extern crate rand;
extern crate vector2d;
extern crate wasm_bindgen;

mod boids;
mod utils;

use boids::boid_pool::BoidPool;
use vector2d::Vector2D;
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub struct App {
    width: f64,
    height: f64,
    mouse_pos: Vector2D<f64>,
    boid_pool: BoidPool,
}

#[wasm_bindgen]
impl App {
    pub fn new(width: f64, height: f64) -> App {
        App {
            width,
            height,
            mouse_pos: Vector2D::new(width / 2.0, height / 2.0),
            boid_pool: BoidPool::new(),
        }
    }

    pub fn set_mouse_pos(&mut self, x: f64, y: f64) {
        self.mouse_pos.x = x;
        self.mouse_pos.y = y;
    }

    pub fn add_boid(&mut self, x: f64, y: f64) {
        self.boid_pool.add_boid(x, y);
    }

    pub fn update(&mut self) {
        self.boid_pool
            .update(&self.width, &self.height, &self.mouse_pos);
    }

    pub fn render(&self) {
        self.boid_pool.render();
    }
}
<file_sep>use vector2d::Vector2D;

pub trait Vector2DExt<T> {
    fn limit(&self, val: T) -> Self;
    fn rotate(&self, angle: f64) -> Self;
}

impl Vector2DExt<f64> for Vector2D<f64> {
    fn limit(&self, val: f64) -> Self {
        if self.length_squared() > val.powi(2) {
            self.normalise() * val
        } else {
            *self
        }
    }

    fn rotate(&self, angle: f64) -> Self {
        let angle_cos = angle.cos();
        let angle_sin = angle.sin();

        let x = self.x * angle_cos - self.y * angle_sin;
        let y = self.x * angle_sin + self.y * angle_cos;

        Self { x, y }
    }
}
