presets.rs
6.0 kB · rust · 193 lines
1use super::field::Field;2use super::stack::stack;3use super::{Combine, Lattice, Spec};4use crate::core::error::{value_error, Result};5use crate::math::bang::corners_to_code;6use serde::Serialize;78/// One named moire recipe: the design, the scales it stacks and the lattice it samples.9#[derive(Clone, Debug, PartialEq, Serialize)]10pub struct Preset {11 /// The name the recipe answers to.12 pub name: &'static str,13 /// The design sampled at every scale.14 pub spec: Spec,15 /// The side numbers stacked.16 pub numbers: Vec<usize>,17 /// The way the layers merge.18 pub combine: Combine,19 /// The fractal depth of each layer.20 pub level: usize,21 /// The lattice the layers are sampled on.22 pub lattice: Lattice,23}2425fn parity() -> Spec {26 Spec::new(corners_to_code(&[vec![0, 0]], 2, 2).get(), 2, 2)27}2829fn pierced() -> Spec {30 let corners: Vec<Vec<u8>> = (0..3u8)31 .flat_map(|a| (0..3u8).map(move |b| vec![a, b]))32 .filter(|corner| corner != &vec![1, 1])33 .collect();34 Spec::new(corners_to_code(&corners, 2, 3).get(), 3, 2)35}3637fn odds(limit: usize) -> Vec<usize> {38 (1..=limit.max(1)).step_by(2).collect()39}4041impl Preset {42 /// The parity heatmap: odd scales of the low corner summed on the square lattice.43 pub fn heatmap(limit: usize) -> Preset {44 Preset {45 name: "heatmap",46 spec: parity(),47 numbers: odds(limit),48 combine: Combine::Sum,49 level: 1,50 lattice: Lattice::Square,51 }52 }53 /// The parity weave: the same odd scales folded to their parity instead of summed.54 pub fn weave(limit: usize) -> Preset {55 Preset {56 name: "weave",57 combine: Combine::Xor,58 ..Preset::heatmap(limit)59 }60 }61 /// The hive: the parity heatmap sampled on the hexagonal lattice.62 pub fn hive(limit: usize) -> Preset {63 Preset {64 name: "hive",65 lattice: Lattice::Hex,66 ..Preset::heatmap(limit)67 }68 }69 /// The carpet stack: every base-three corner but the centre, summed over odd scales.70 pub fn carpet(limit: usize) -> Preset {71 Preset {72 name: "carpet",73 spec: pierced(),74 ..Preset::heatmap(limit)75 }76 }77 /// Samples the preset into a square field of the given side.78 ///79 /// # Errors80 ///81 /// Errors when the preset's code is out of range.82 pub fn field(&self, size: usize) -> Result<Field> {83 stack(84 self.spec,85 &self.numbers,86 self.combine,87 self.level,88 self.lattice,89 size,90 &[],91 )92 }93}9495/// Returns every preset stacked up to the given scale.96pub fn all(limit: usize) -> Vec<Preset> {97 vec![98 Preset::heatmap(limit),99 Preset::weave(limit),100 Preset::hive(limit),101 Preset::carpet(limit),102 ]103}104105/// Returns the preset the name picks.106///107/// ```108/// let preset = mrlyrs::math::moire::presets::named("heatmap", 9).unwrap();109/// assert_eq!(preset.numbers, vec![1, 3, 5, 7, 9]);110/// assert!(mrlyrs::math::moire::presets::named("soup", 9).is_err());111/// ```112///113/// # Errors114///115/// Errors for a name that is not heatmap, weave, hive or carpet.116pub fn named(name: &str, limit: usize) -> Result<Preset> {117 match all(limit).into_iter().find(|p| p.name == name) {118 Some(preset) => Ok(preset),119 None => value_error(format!(120 "no preset {name:?}, only heatmap, weave, hive and carpet."121 )),122 }123}124125#[cfg(test)]126mod tests {127 use super::*;128129 #[test]130 fn the_heatmap_sums_the_parity_of_every_odd_scale() {131 let (size, limit) = (24usize, 9usize);132 let field = Preset::heatmap(limit).field(size).unwrap();133 let mut want = vec![0f32; size * size];134 for n in (1..=limit).step_by(2) {135 for row in 0..size {136 let v = (row as f64 + 0.5) / size as f64;137 for col in 0..size {138 let u = (col as f64 + 0.5) / size as f64;139 let even = |value: f64| (value.floor() as i64).rem_euclid(2) == 0;140 let lit = even(n as f64 * u) && even(n as f64 * v);141 want[row * size + col] += u8::from(lit) as f32;142 }143 }144 }145 assert_eq!(field.data, want);146 assert_eq!(field.size, size);147 }148149 #[test]150 fn the_weave_folds_the_same_layers_to_parity() {151 let field = Preset::weave(9).field(32).unwrap();152 assert!(field.data.iter().all(|&v| v == 0.0 || v == 1.0));153 let summed = Preset::heatmap(9).field(32).unwrap();154 for (woven, count) in field.data.iter().zip(summed.data.iter()) {155 assert_eq!(*woven, (*count as u32 % 2) as f32);156 }157 }158159 #[test]160 fn the_carpet_stack_keeps_eight_corners_of_nine() {161 let preset = Preset::carpet(7);162 assert_eq!(preset.spec.base, 3);163 let table = super::super::sample::membership(preset.spec.code, 3, 2).unwrap();164 assert_eq!(table.iter().filter(|&&lit| lit).count(), 8);165 assert!(!table[super::super::sample::pack(&[1, 1], 3)]);166 let field = preset.field(32).unwrap();167 assert!(field.max() <= preset.numbers.len() as f32);168 assert!(field.mean() > 0.0);169 }170171 #[test]172 fn the_hive_leans_the_lattice() {173 let hive = Preset::hive(9).field(48).unwrap();174 let flat = Preset::heatmap(9).field(48).unwrap();175 assert_eq!(hive.data.len(), flat.data.len());176 assert_ne!(hive.data, flat.data);177 }178179 #[test]180 fn every_preset_names_itself_and_renders() {181 let presets = all(5);182 let mut names: Vec<&str> = presets.iter().map(|p| p.name).collect();183 names.sort_unstable();184 names.dedup();185 assert_eq!(names.len(), presets.len());186 for preset in &presets {187 assert_eq!(named(preset.name, 5).unwrap(), *preset);188 let field = preset.field(16).unwrap();189 assert_eq!(field.data.len(), 256);190 }191 assert!(named("heatmap", 0).unwrap().numbers == vec![1]);192 }193}