use crate::{agent::Agent, blur::Blur, buffer::Buf}; use rand::Rng; use rand_distr::Uniform; use rayon::{iter::ParallelIterator, prelude::*}; use std::fmt::{Display, Formatter}; /// A population configuration. #[derive(Debug, Clone, Copy)] pub struct PopulationConfig { pub sensor_distance: f32, pub step_distance: f32, pub sensor_angle: f32, pub rotation_angle: f32, deposition_amount: f32, } impl Display for PopulationConfig { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{:?}", self) } } impl PopulationConfig { /// Construct a random configuration. pub fn new(rng: &mut R) -> Self { PopulationConfig { sensor_distance: rng.random_range(0.0..=64.0), step_distance: rng.random_range(0.2..=2.0), sensor_angle: rng.random_range(0.0_f32..=120.0).to_radians(), rotation_angle: rng.random_range(0.0_f32..=120.0).to_radians(), deposition_amount: rng.random_range(5.0..=5.0), } } } // A 2D grid with a scalar value per each grid block. Each grid is occupied by a single population, hence we store the population config inside the grid. #[derive(Debug, Clone)] pub struct Grid { pub config: PopulationConfig, pub width: usize, pub height: usize, pub data: Vec, // Scratch space for the blur operation. buf: Buf, blur: Blur, pub agents: Vec, } impl Grid { /// Create a new grid filled with random floats in the [0.0..1.0) range. pub fn new( width: usize, height: usize, rng: &mut R, agents: Vec, ) -> Self { let range = Uniform::new(0.0, 1.0).expect("unable to create uniform distr"); let data = rng.sample_iter(range).take(width * height).collect(); Grid { width, height, data, config: PopulationConfig::new(rng), buf: Buf::new(width, height), blur: Blur::new(width), agents, } } /// Truncate x and y and return a corresponding index into the data slice. const fn index(&self, x: f32, y: f32) -> usize { crate::util::index(self.width, self.height, x, y) } /// Diffuse grid data and apply a decay multiplier. pub fn diffuse(&mut self, radius: usize) { self.blur.run( &mut self.data, &mut self.buf.buf, self.width, self.height, radius as f32, 0.1, // decay is always 0.1 ); } pub fn tick(&mut self) { self.agents.par_iter_mut().for_each(|agent| { agent.tick(&self.buf, self.config, self.width, self.height); }); self.deposit_all(); } pub fn deposit_all(&mut self) { for agent in self.agents.iter() { let idx = self.index(agent.x, agent.y); self.data[idx] += self.config.deposition_amount; } } } pub fn combine(grids: &mut [Grid], attraction_table: &[T]) where T: AsRef<[f32]> + Sync, { let datas: Vec<_> = grids.iter().map(|grid| &grid.data).collect(); let bufs: Vec<_> = grids.iter().map(|grid| &grid.buf.buf).collect(); // We mutate grid buffers and read grid data. We use unsafe because we need shared/unique borrows on different fields of the same Grid struct. bufs.iter().enumerate().for_each(|(i, buf)| { let buf_ptr = *buf as *const Vec as *mut Vec; // SAFETY! we can take these are raw pointers because we are // getting it from a `&mut [Grid]` let buf_ptr_mut = unsafe { buf_ptr.as_mut().unwrap_unchecked() }; buf_ptr_mut.fill(0.0); datas.iter().enumerate().for_each(|(j, other)| { let multiplier = attraction_table[i].as_ref()[j]; buf_ptr_mut .iter_mut() .zip(*other) .for_each(|(to, from)| *to += from * multiplier) }) }); } #[cfg(test)] mod tests { use super::*; #[test] fn test_grid_new() { let mut rng = rand::rng(); let grid = Grid::new(8, 8, &mut rng, vec![]); assert_eq!(grid.index(0.5, 0.6), 0); assert_eq!(grid.index(1.5, 0.6), 1); assert_eq!(grid.index(0.5, 1.6), 8); assert_eq!(grid.index(2.5, 0.6), 2); assert_eq!(grid.index(2.5, 1.6), 10); assert_eq!(grid.index(7.9, 7.9), 63); } }