rill_patchbay/automaton/
cellular.rs1use crate::engine::{Automaton, NoAction, Range, Time};
7use rill_core::traits::ParamValue;
8
9#[derive(Debug, Clone, Copy, PartialEq)]
11pub enum CellularType {
12 OneDimensional,
14 TwoDimensional,
16 Cyclic,
18}
19
20#[derive(Debug, Clone)]
22pub struct CellularAutomaton {
23 name: String,
25 cell_type: CellularType,
27 rule: u8,
29 size: usize,
31 width: usize,
33 height: usize,
35 output_mode: OutputMode,
37 range: Range,
39 rng_state: u64,
41}
42
43#[derive(Debug, Clone, Copy, PartialEq)]
45pub enum OutputMode {
46 Center,
48 Sum,
50 Density,
52 Index(usize),
54}
55
56impl CellularAutomaton {
57 pub fn one_dimensional(name: &str, rule: u8, size: usize) -> Self {
59 Self {
60 name: name.to_string(),
61 cell_type: CellularType::OneDimensional,
62 rule,
63 size,
64 width: size,
65 height: 1,
66 output_mode: OutputMode::Center,
67 range: Range::unipolar(),
68 rng_state: 123456789,
69 }
70 }
71
72 pub fn game_of_life(name: &str, width: usize, height: usize) -> Self {
74 Self {
75 name: name.to_string(),
76 cell_type: CellularType::TwoDimensional,
77 rule: 0,
78 size: width * height,
79 width,
80 height,
81 output_mode: OutputMode::Density,
82 range: Range::unipolar(),
83 rng_state: 123456789,
84 }
85 }
86
87 pub fn with_output_mode(mut self, mode: OutputMode) -> Self {
89 self.output_mode = mode;
90 self
91 }
92
93 pub fn with_range(mut self, range: Range) -> Self {
95 self.range = range;
96 self
97 }
98
99 fn random_initial(&self, rng: &mut u64) -> Vec<u8> {
101 let mut gen = Vec::with_capacity(self.size);
102 for _ in 0..self.size {
103 gen.push(if self.random_bit(rng) { 1 } else { 0 });
104 }
105 gen
106 }
107
108 fn random_bit(&self, rng: &mut u64) -> bool {
110 let mut x = *rng;
111 x ^= x << 13;
112 x ^= x >> 7;
113 x ^= x << 17;
114 *rng = x;
115 (x & 1) == 1
116 }
117
118 fn apply_rule_1d(&self, generation: &[u8]) -> Vec<u8> {
120 let mut next = vec![0; generation.len()];
121
122 for i in 0..generation.len() {
123 let left = if i > 0 {
124 generation[i - 1]
125 } else {
126 generation[generation.len() - 1]
127 };
128 let center = generation[i];
129 let right = if i < generation.len() - 1 {
130 generation[i + 1]
131 } else {
132 generation[0]
133 };
134
135 let pattern = (left << 2) | (center << 1) | right;
136 let bit = (self.rule >> pattern) & 1;
137 next[i] = bit;
138 }
139
140 next
141 }
142
143 fn apply_rule_gol(&self, generation: &[u8]) -> Vec<u8> {
145 let mut next = vec![0; generation.len()];
146
147 for y in 0..self.height {
148 for x in 0..self.width {
149 let idx = y * self.width + x;
150 let cell = generation[idx];
151
152 let mut neighbors = 0;
154 for dy in -1..=1 {
155 for dx in -1..=1 {
156 if dx == 0 && dy == 0 {
157 continue;
158 }
159
160 let nx = (x as i32 + dx + self.width as i32) % self.width as i32;
161 let ny = (y as i32 + dy + self.height as i32) % self.height as i32;
162 let nidx = (ny * self.width as i32 + nx) as usize;
163
164 if generation[nidx] == 1 {
165 neighbors += 1;
166 }
167 }
168 }
169
170 next[idx] = match (cell, neighbors) {
172 (1, 2) | (1, 3) => 1, (0, 3) => 1, _ => 0, };
176 }
177 }
178
179 next
180 }
181
182 fn compute_output(&self, generation: &[u8]) -> f64 {
184 match self.output_mode {
185 OutputMode::Center => {
186 let idx = self.size / 2;
187 generation[idx] as f64
188 }
189
190 OutputMode::Sum => {
191 let sum: usize = generation.iter().map(|&c| c as usize).sum();
192 sum as f64 / self.size as f64
193 }
194
195 OutputMode::Density => {
196 let sum: usize = generation.iter().map(|&c| c as usize).sum();
197 sum as f64 / self.size as f64
198 }
199
200 OutputMode::Index(idx) => {
201 if idx < generation.len() {
202 generation[idx] as f64
203 } else {
204 0.0
205 }
206 }
207 }
208 }
209}
210
211impl Automaton for CellularAutomaton {
212 type Internal = (Vec<u8>, usize);
213 type Action = NoAction;
214
215 fn step(
216 &self,
217 internal: &mut Self::Internal,
218 _current: &ParamValue,
219 _time: Time,
220 _action: &Self::Action,
221 ) -> ParamValue {
222 let (cells, generation) = internal;
223
224 *cells = match self.cell_type {
225 CellularType::OneDimensional => self.apply_rule_1d(cells),
226 CellularType::TwoDimensional => self.apply_rule_gol(cells),
227 CellularType::Cyclic => cells.clone(),
228 };
229 *generation += 1;
230
231 let raw = self.compute_output(cells);
232 let value = self.range.clamp(raw);
233 ParamValue::Float(value as f32)
234 }
235
236 fn initial_internal(&self) -> Self::Internal {
237 let mut rng = self.rng_state;
238 let generation = self.random_initial(&mut rng);
239 (generation, 0)
240 }
241
242 fn name(&self) -> &str {
243 &self.name
244 }
245}
246
247pub mod rules {
249 pub const RULE_30: u8 = 30;
251 pub const RULE_90: u8 = 90;
253 pub const RULE_110: u8 = 110;
255 pub const RULE_184: u8 = 184;
257}
258
259#[cfg(test)]
260mod tests {
261 use super::*;
262
263 #[test]
264 fn test_rule_30() {
265 let ca = CellularAutomaton::one_dimensional("Rule 30", rules::RULE_30, 31);
266 let mut internal = ca.initial_internal();
267 let current = ParamValue::Float(0.0);
268
269 let value = ca.step(&mut internal, ¤t, 0.0, &NoAction);
270 assert!(value.as_f32().is_some());
271 }
272}