141 lines
3.8 KiB
Rust
141 lines
3.8 KiB
Rust
use rand::prelude::*;
|
|
use serde::Serialize;
|
|
use crate::system::{Position, Storage};
|
|
use crate::system::spawner::Spawner;
|
|
use crate::system::sticker::Sticker;
|
|
use crate::system::walker::Walker;
|
|
|
|
#[derive(Serialize)]
|
|
pub struct HistoryLine<P: Position> {
|
|
pub frame: usize,
|
|
pub cluster_radius: f32,
|
|
pub fd: f32,
|
|
pub position: P,
|
|
}
|
|
|
|
pub struct DLASystem<R: Rng, P: Position, S: Storage<P>, W: Walker<P>, Sp: Spawner<P>, St: Sticker<P, S>> {
|
|
rng: R,
|
|
|
|
/*
|
|
* These object encapsulate the behaviour of our particular model.
|
|
*/
|
|
|
|
/*
|
|
* The space, along with the chosen position choose the embedding space that the cluster grows
|
|
* in.
|
|
*/
|
|
space: S,
|
|
|
|
/*
|
|
* Walkers allow us to choose between different particle movement behaviours, eg random or
|
|
* biased walker
|
|
*/
|
|
walker: W,
|
|
spawner: Sp,
|
|
sticker: St,
|
|
|
|
pub frame: usize,
|
|
pub running: bool,
|
|
pub history: Vec<HistoryLine<P>>,
|
|
|
|
max_particles: usize,
|
|
particles: Vec<P>,
|
|
active_particle: Option<P>,
|
|
|
|
add_ratio: f32,
|
|
add_circle: f32,
|
|
kill_ratio: f32,
|
|
kill_circle: f32,
|
|
cluster_radius: f32,
|
|
}
|
|
|
|
impl<R: Rng, P: Position, S: Storage<P>, W: Walker<P>, Sp: Spawner<P>, St: Sticker<P, S>> DLASystem<R, P, S, W, Sp, St> {
|
|
pub fn new(rng: R, space: S, walker: W, spawner: Sp, sticker: St, max_particles: usize) -> Self {
|
|
let mut sys = DLASystem {
|
|
rng,
|
|
max_particles,
|
|
running: true,
|
|
spawner,
|
|
sticker,
|
|
|
|
space,
|
|
walker,
|
|
|
|
frame: 0,
|
|
particles: vec![],
|
|
history: vec![],
|
|
active_particle: None,
|
|
|
|
add_ratio: 1.2,
|
|
kill_ratio: 1.7,
|
|
|
|
add_circle: 10.0,
|
|
kill_circle: 20.0,
|
|
cluster_radius: 0.0,
|
|
};
|
|
|
|
sys.deposit(&P::zero());
|
|
|
|
sys
|
|
}
|
|
|
|
pub fn update(&mut self) {
|
|
self.frame += 1;
|
|
if self.active_particle.is_some() {
|
|
self.move_particle();
|
|
} else if self.particles.len() < self.max_particles {
|
|
self.spawn_particle();
|
|
} else {
|
|
self.running = false;
|
|
}
|
|
}
|
|
|
|
fn move_particle(&mut self) {
|
|
let current_position = &self
|
|
.active_particle
|
|
.clone()
|
|
.expect("No active particle");
|
|
|
|
let next_position = self.walker.walk(&mut self.rng, current_position);
|
|
let distance = next_position.abs();
|
|
|
|
if distance > self.kill_circle {
|
|
self.active_particle = None;
|
|
} else if !self.space.is_occupied(&next_position) {
|
|
if self.sticker.should_stick(&mut self.rng, &self.space, &next_position) {
|
|
self.deposit(&next_position);
|
|
self.active_particle = None;
|
|
return;
|
|
} else {
|
|
self.active_particle.replace(next_position);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn spawn_particle(&mut self) {
|
|
let position = self.spawner.spawn(&mut self.rng, self.add_circle);
|
|
|
|
if !self.space.is_occupied(&position) {
|
|
self.active_particle = Some(position);
|
|
}
|
|
}
|
|
|
|
fn deposit(&mut self, p0: &P) {
|
|
self.particles.push(p0.clone());
|
|
self.space.deposit(p0);
|
|
|
|
let distance = p0.abs();
|
|
if distance > self.cluster_radius {
|
|
self.cluster_radius = distance;
|
|
|
|
let new_add_circle = (self.cluster_radius * self.add_ratio).max(self.cluster_radius + 5.0);
|
|
if self.add_circle < new_add_circle {
|
|
self.add_circle = new_add_circle;
|
|
self.kill_circle = self.kill_ratio * self.add_circle;
|
|
}
|
|
}
|
|
|
|
self.history.push(HistoryLine { frame: self.frame, position: p0.clone(), cluster_radius: self.cluster_radius, fd: (self.particles.len() as f32).ln() / self.cluster_radius.ln() });
|
|
}
|
|
}
|