physarum/src/agent.rs

80 lines
2.6 KiB
Rust

use crate::grid::PopulationConfig;
use crate::{buffer::Buf, util::wrap};
use fastapprox::faster::{cos, sin};
use rand::prelude::IndexedRandom;
use rand::Rng;
use std::f32::consts::TAU;
use std::fmt::{Display, Formatter};
/// A single Physarum agent. The x and y positions are continuous, hence we use floating point numbers instead of integers.
#[derive(Debug, Clone, PartialEq)]
pub struct Agent {
pub x: f32,
pub y: f32,
heading: f32,
}
impl Display for Agent {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
impl Agent {
/// Construct a new agent with random parameters.
pub fn new<R: Rng + ?Sized>(width: usize, height: usize, rng: &mut R) -> Self {
let (x, y, angle) = rng.random::<(f32, f32, f32)>();
Agent {
x: x * width as f32,
y: y * height as f32,
heading: angle * TAU,
}
}
/// Tick an agent
pub fn tick(&mut self, buf: &Buf, pop_config: PopulationConfig, width: usize, height: usize) {
let xc = self.x + cos(self.heading) * pop_config.sensor_distance;
let yc = self.y + sin(self.heading) * pop_config.sensor_distance;
let agent_add_sens = self.heading + pop_config.sensor_angle;
let agent_sub_sens = self.heading - pop_config.sensor_angle;
let xl = self.x + cos(agent_sub_sens) * pop_config.sensor_distance;
let yl = self.y + sin(agent_sub_sens) * pop_config.sensor_distance;
let xr = self.x + cos(agent_add_sens) * pop_config.sensor_distance;
let yr = self.y + sin(agent_add_sens) * pop_config.sensor_distance;
// We sense from the buffer because this is where we previously combined data from all the grid.
let center = buf.get_buf(xc, yc);
let left = buf.get_buf(xl, yl);
let right = buf.get_buf(xr, yr);
// Rotate and move logic
let direction = if (center > left) && (center > right) {
0.0
} else if (center < left) && (center < right) {
*[-1.0, 1.0]
.choose(&mut rand::rng())
.expect("unable to choose random direction")
} else if left < right {
1.0
} else if right < left {
-1.0
} else {
0.0
};
let delta_angle = pop_config.rotation_angle * direction;
self.heading = wrap(self.heading + delta_angle, TAU);
self.x = wrap(
self.x + pop_config.step_distance * cos(self.heading),
width as f32,
);
self.y = wrap(
self.y + pop_config.step_distance * sin(self.heading),
height as f32,
);
}
}