presets.rs
5.8 kB · rust · 184 lines
1use super::field::Field;2use super::stack::stack;3use super::{Combine, Lattice, Spec};4use mrlycore::errors::{value_error, Result};5use mrlymath::bang::corners_to_code;67/// One named moire recipe: the design, the scales it stacks and the lattice it samples.8#[derive(Clone, Debug, PartialEq)]9pub struct Preset {10 /// The name the recipe answers to.11 pub name: &'static str,12 /// The design sampled at every scale.13 pub spec: Spec,14 /// The side numbers stacked.15 pub numbers: Vec<usize>,16 /// The way the layers merge.17 pub combine: Combine,18 /// The fractal depth of each layer.19 pub level: usize,20 /// The lattice the layers are sampled on.21 pub lattice: Lattice,22}2324fn parity() -> Spec {25 Spec::new(corners_to_code(&[vec![0, 0]], 2, 2), 2, 2)26}2728fn pierced() -> Spec {29 let corners: Vec<Vec<u8>> = (0..3u8)30 .flat_map(|a| (0..3u8).map(move |b| vec![a, b]))31 .filter(|corner| corner != &vec![1, 1])32 .collect();33 Spec::new(corners_to_code(&corners, 2, 3), 3, 2)34}3536fn odds(limit: usize) -> Vec<usize> {37 (1..=limit.max(1)).step_by(2).collect()38}3940impl Preset {41 /// The parity heatmap: odd scales of the low corner summed on the square lattice.42 pub fn heatmap(limit: usize) -> Preset {43 Preset {44 name: "heatmap",45 spec: parity(),46 numbers: odds(limit),47 combine: Combine::Sum,48 level: 1,49 lattice: Lattice::Square,50 }51 }52 /// The parity weave: the same odd scales folded to their parity instead of summed.53 pub fn weave(limit: usize) -> Preset {54 Preset {55 name: "weave",56 combine: Combine::Xor,57 ..Preset::heatmap(limit)58 }59 }60 /// The hive: the parity heatmap sampled on the hexagonal lattice.61 pub fn hive(limit: usize) -> Preset {62 Preset {63 name: "hive",64 lattice: Lattice::Hex,65 ..Preset::heatmap(limit)66 }67 }68 /// The carpet stack: every base-three corner but the centre, summed over odd scales.69 pub fn carpet(limit: usize) -> Preset {70 Preset {71 name: "carpet",72 spec: pierced(),73 ..Preset::heatmap(limit)74 }75 }76 /// Samples the preset into a square field of the given side.77 pub fn field(&self, size: usize) -> Result<Field> {78 stack(79 self.spec,80 &self.numbers,81 self.combine,82 self.level,83 self.lattice,84 size,85 &[],86 )87 }88}8990/// Returns every preset stacked up to the given scale.91pub fn all(limit: usize) -> Vec<Preset> {92 vec![93 Preset::heatmap(limit),94 Preset::weave(limit),95 Preset::hive(limit),96 Preset::carpet(limit),97 ]98}99100/// Returns the preset the name picks, or an error naming the ones there are.101///102/// ```103/// let preset = mrlylab::moire::presets::named("heatmap", 9).unwrap();104/// assert_eq!(preset.numbers, vec![1, 3, 5, 7, 9]);105/// assert!(mrlylab::moire::presets::named("soup", 9).is_err());106/// ```107pub fn named(name: &str, limit: usize) -> Result<Preset> {108 match all(limit).into_iter().find(|p| p.name == name) {109 Some(preset) => Ok(preset),110 None => value_error(format!(111 "no preset {name:?}, only heatmap, weave, hive and carpet."112 )),113 }114}115116#[cfg(test)]117mod tests {118 use super::*;119120 #[test]121 fn the_heatmap_sums_the_parity_of_every_odd_scale() {122 let (size, limit) = (24usize, 9usize);123 let field = Preset::heatmap(limit).field(size).unwrap();124 let mut want = vec![0f32; size * size];125 for n in (1..=limit).step_by(2) {126 for row in 0..size {127 let v = (row as f64 + 0.5) / size as f64;128 for col in 0..size {129 let u = (col as f64 + 0.5) / size as f64;130 let even = |value: f64| (value.floor() as i64).rem_euclid(2) == 0;131 let lit = even(n as f64 * u) && even(n as f64 * v);132 want[row * size + col] += u8::from(lit) as f32;133 }134 }135 }136 assert_eq!(field.data, want);137 assert_eq!(field.size, size);138 }139140 #[test]141 fn the_weave_folds_the_same_layers_to_parity() {142 let field = Preset::weave(9).field(32).unwrap();143 assert!(field.data.iter().all(|&v| v == 0.0 || v == 1.0));144 let summed = Preset::heatmap(9).field(32).unwrap();145 for (woven, count) in field.data.iter().zip(summed.data.iter()) {146 assert_eq!(*woven, (*count as u32 % 2) as f32);147 }148 }149150 #[test]151 fn the_carpet_stack_keeps_eight_corners_of_nine() {152 let preset = Preset::carpet(7);153 assert_eq!(preset.spec.base, 3);154 let table = super::super::sample::membership(preset.spec.code, 3, 2).unwrap();155 assert_eq!(table.iter().filter(|&&lit| lit).count(), 8);156 assert!(!table[super::super::sample::pack(&[1, 1], 3)]);157 let field = preset.field(32).unwrap();158 assert!(field.max() <= preset.numbers.len() as f32);159 assert!(field.mean() > 0.0);160 }161162 #[test]163 fn the_hive_leans_the_lattice() {164 let hive = Preset::hive(9).field(48).unwrap();165 let flat = Preset::heatmap(9).field(48).unwrap();166 assert_eq!(hive.data.len(), flat.data.len());167 assert_ne!(hive.data, flat.data);168 }169170 #[test]171 fn every_preset_names_itself_and_renders() {172 let presets = all(5);173 let mut names: Vec<&str> = presets.iter().map(|p| p.name).collect();174 names.sort_unstable();175 names.dedup();176 assert_eq!(names.len(), presets.len());177 for preset in &presets {178 assert_eq!(named(preset.name, 5).unwrap(), *preset);179 let field = preset.field(16).unwrap();180 assert_eq!(field.data.len(), 256);181 }182 assert!(named("heatmap", 0).unwrap().numbers == vec![1]);183 }184}