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}