use henad_compute::cpu::field::scalar::ScalarFieldSpec;
use henad_compute::cpu::primitives::scatter::Combine;
use henad_core::helpers::{extract_f32, f32_param};
use henad_core::params::{ParamDescriptor, ParamValue};
pub const LOW_PHEROMONE: f32 = 1e-14;
const DISPLAY_DECADES: f32 = 3.0;
const RAMP_STEPS: u8 = 6;
pub const EMPTY: u8 = 0;
pub const OBSTACLE: u8 = 1;
pub const FOOD: u8 = 2;
pub const HOME: u8 = 3;
pub const TO_FOOD: usize = 0;
pub const TO_HOME: usize = 1;
henad_core::params! {
const EVAPORATION = f32_param("evaporation", "Evaporation", 0.999, 0.9, 1.0, Some(0.001));
}
pub const CELL_PALETTE: [[u8; 4]; 16] = [
[0x0E, 0x0E, 0x12, 0xFF], [0x10, 0x1C, 0x30, 0xFF], [0x12, 0x2A, 0x4C, 0xFF], [0x14, 0x3C, 0x6E, 0xFF], [0x16, 0x52, 0x96, 0xFF], [0x1A, 0x6B, 0xC0, 0xFF], [0x2E, 0x8B, 0xE8, 0xFF], [0x30, 0x1E, 0x10, 0xFF], [0x4A, 0x2C, 0x12, 0xFF], [0x6C, 0x3E, 0x14, 0xFF], [0x94, 0x54, 0x16, 0xFF], [0xBE, 0x6E, 0x1A, 0xFF], [0xE8, 0x8C, 0x2E, 0xFF], [0x5A, 0x5A, 0x62, 0xFF], [0x3D, 0xD5, 0x8C, 0xFF], [0xF2, 0xE4, 0x5C, 0xFF], ];
#[derive(Debug)]
pub struct PheromoneField;
#[derive(Debug)]
pub struct FieldParams {
pub evaporation: f32,
}
impl ScalarFieldSpec for PheromoneField {
const FIELDS: usize = 2;
const COMBINE: Combine = Combine::Max;
const PALETTE: &'static [[u8; 4]] = &CELL_PALETTE;
type Params = FieldParams;
fn param_descriptors() -> Vec<ParamDescriptor> {
descriptors()
}
fn from_params(params: &[ParamValue]) -> FieldParams {
FieldParams {
evaporation: extract_f32(params, EVAPORATION, 0.999),
}
}
fn build_sites(width: u32, height: u32, sites: &mut [u8]) {
let (w, h) = (f64::from(width), f64::from(height));
let size = 0.407 * (200.0 / w);
let blob = |x: f64, y: f64, cx: f64, cy: f64| -> bool {
let a = ((x - cx) + (y - cy)) * size;
let b = ((x - cx) - (y - cy)) * size;
a * a / 36.0 + b * b / 1024.0 <= 1.0
};
for j in 0..height {
for i in 0..width {
let (x, y) = (f64::from(i), f64::from(j));
if blob(x, y, 0.500 * w, 0.725 * h) || blob(x, y, 0.450 * w, 0.275 * h) {
sites[(j * width + i) as usize] = OBSTACLE;
}
}
}
sites[food_cell(width, height)] = FOOD;
sites[nest_cell(width, height)] = HOME;
}
fn decay(v: f32, p: &FieldParams) -> f32 {
let d = v * p.evaporation;
if d < LOW_PHEROMONE { 0.0 } else { d }
}
fn quantize(site: u8, values: &[f32], out: &mut u8) {
*out = match site {
OBSTACLE => 13,
FOOD => 14,
HOME => 15,
_ => {
let (food, home) = (values[TO_FOOD], values[TO_HOME]);
let (v, base) = if food > home { (food, 6) } else { (home, 0) };
match ramp_step(v) {
0 => 0,
step => base + step,
}
}
};
}
}
pub fn nest_cell(width: u32, height: u32) -> usize {
let x = (0.875 * width as f32) as u32;
let y = (0.875 * height as f32) as u32;
(y * width + x) as usize
}
pub fn food_cell(width: u32, height: u32) -> usize {
let x = (0.125 * width as f32) as u32;
let y = (0.125 * height as f32) as u32;
(y * width + x) as usize
}
fn ramp_step(v: f32) -> u8 {
if v <= LOW_PHEROMONE {
return 0;
}
let decades = v.log10() / DISPLAY_DECADES + 1.0;
if decades <= 0.0 {
return 0;
}
((decades * f32::from(RAMP_STEPS)) as u8).clamp(1, RAMP_STEPS)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sites_match_the_reference_layout_at_200_squared() {
let mut sites = vec![EMPTY; 200 * 200];
PheromoneField::build_sites(200, 200, &mut sites);
assert_eq!(nest_cell(200, 200), 175 * 200 + 175, "nest");
assert_eq!(food_cell(200, 200), 25 * 200 + 25, "food");
assert_eq!(sites[nest_cell(200, 200)], HOME);
assert_eq!(sites[food_cell(200, 200)], FOOD);
assert!(
sites.contains(&OBSTACLE),
"the two obstacle blobs should cover some cells"
);
}
}