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}