spiral.rs

8.2 kB · rust · 241 lines

1use crate::{rgba, theme, Fault, Pixels};2use mrlycore::json;3use mrlynum::factor::factorize;4use mrlynum::spiral::{self, Diagonal, Lattice, Mark};5use wasm_bindgen::prelude::*;67const SIDE: usize = 401;8const SIZE: usize = 1024;9const REACH: i32 = 1_000_000;10const LABELS: usize = 4096;11const ROOT3: f64 = 1.732_050_807_568_877_2;1213struct Sheet {14    lattice: Lattice,15    radius: i64,16    half: f64,17    scale: f64,18}1920impl Sheet {21    fn new(lattice: &str, side: usize, size: usize) -> Result<Sheet, Fault> {22        let lattice =23            Lattice::named(lattice).ok_or_else(|| Fault::new("the lattice is square or hex."))?;24        if side.is_multiple_of(2) || side > SIDE {25            return Err(Fault::new(format!("the side is odd and at most {SIDE}.")));26        }27        if size == 0 || size > SIZE {28            return Err(Fault::new(format!("the sheet is 1 to {SIZE} pixels wide.")));29        }30        let radius = lattice.radius(side) as i64;31        let half = size as f64 / 2.0;32        let scale = match lattice {33            Lattice::Square => size as f64 / side as f64,34            Lattice::Hex => {35                let r = radius as f64;36                (size as f64 / (2.0 * ROOT3 * (r + 0.5))).min(size as f64 / (3.0 * r + 2.0))37            }38        };39        Ok(Sheet {40            lattice,41            radius,42            half,43            scale,44        })45    }46    fn center(&self, x: i64, y: i64) -> (f64, f64) {47        let (x, y) = (x as f64, y as f64);48        match self.lattice {49            Lattice::Square => (self.half + x * self.scale, self.half - y * self.scale),50            Lattice::Hex => (51                self.half + self.scale * ROOT3 * (x + y / 2.0),52                self.half + 1.5 * self.scale * y,53            ),54        }55    }56    fn span(&self) -> f64 {57        match self.lattice {58            Lattice::Square => self.scale,59            Lattice::Hex => self.scale * ROOT3,60        }61    }62    fn cell(&self, px: f64, py: f64) -> Option<(i64, i64)> {63        let (x, y) = (px - self.half, py - self.half);64        let cell = match self.lattice {65            Lattice::Square => (66                (x / self.scale + 0.5).floor() as i64,67                (-y / self.scale + 0.5).floor() as i64,68            ),69            Lattice::Hex => {70                let q = (ROOT3 / 3.0 * x - y / 3.0) / self.scale;71                let r = 2.0 / 3.0 * y / self.scale;72                let s = -q - r;73                let (mut rq, mut rr, rs) = (q.round(), r.round(), s.round());74                let (dq, dr, ds) = ((rq - q).abs(), (rr - r).abs(), (rs - s).abs());75                if dq > dr && dq > ds {76                    rq = -rr - rs;77                } else if dr > ds {78                    rr = -rq - rs;79                }80                (rq as i64, rr as i64)81            }82        };83        (self.lattice.ring_of(cell.0, cell.1) <= self.radius as u64).then_some(cell)84    }85    fn gap(&self, px: f64, py: f64, cell: (i64, i64)) -> bool {86        if self.scale < 6.0 {87            return false;88        }89        let (cx, cy) = self.center(cell.0, cell.1);90        let (x, y) = (px - cx, py - cy);91        match self.lattice {92            Lattice::Square => x + self.scale / 2.0 < 1.0 || y + self.scale / 2.0 < 1.0,93            Lattice::Hex => {94                let reach = x95                    .abs()96                    .max((x / 2.0 + y * ROOT3 / 2.0).abs())97                    .max((y * ROOT3 / 2.0 - x / 2.0).abs());98                reach > self.scale * ROOT3 / 2.0 - 1.099            }100        }101    }102}103104fn read(lattice: Lattice, side: usize, a: i32, b: i32, c: i32) -> Result<Diagonal, Fault> {105    if a < 1 {106        return Err(Fault::new("a quadratic needs a of at least 1."));107    }108    if b.abs() > REACH || c.abs() > REACH {109        return Err(Fault::new(format!("b and c stay within {REACH}.")));110    }111    Ok(spiral::diagonal(112        lattice,113        side,114        i64::from(a),115        i64::from(b),116        i64::from(c),117    ))118}119120/// Paints the numbers from one wound on the lattice over a sheet the odd side wide: marked cells gold, a Mobius minus one pink, the quadratic a k^2 + b k + c orange on a prime and blue otherwise, the rest faint or dark.121#[wasm_bindgen]122#[allow(clippy::too_many_arguments)]123pub fn spiral_pixels(124    lattice: &str,125    side: usize,126    a: i32,127    b: i32,128    c: i32,129    mark: &str,130    faint: bool,131    size: usize,132) -> Result<Pixels, Fault> {133    let sheet = Sheet::new(lattice, side, size)?;134    let mark = Mark::named(mark)135        .ok_or_else(|| Fault::new("the mark is prime, twin, squarefree or mobius."))?;136    let quadratic = read(sheet.lattice, side, a, b, c)?;137    let ink = theme();138    let ground = rgba(ink.ground);139    let mut look: Vec<[u8; 4]> = spiral::marks(mark, quadratic.top)140        .iter()141        .map(|&m| match m {142            1 => rgba(ink.yellow),143            -1 => rgba(ink.pink),144            _ if faint => rgba(ink.line),145            _ => ground,146        })147        .collect();148    for (&value, &hit) in quadratic.values.iter().zip(&quadratic.hit) {149        look[value as usize] = rgba(if hit { ink.orange } else { ink.blue });150    }151    let mut colors = Vec::with_capacity(size * size);152    for py in 0..size {153        for px in 0..size {154            let (fx, fy) = (px as f64 + 0.5, py as f64 + 0.5);155            colors.push(match sheet.cell(fx, fy) {156                Some(cell) if !sheet.gap(fx, fy, cell) => {157                    look[sheet.lattice.n(cell.0, cell.1) as usize]158                }159                _ => ground,160            });161        }162    }163    Ok(Pixels::of(size, size, colors))164}165166/// Returns the cell of a number and its ring: x right and y up on the square, axial q and r on the hexagon.167#[wasm_bindgen]168pub fn spiral_xy(lattice: &str, n: u32) -> Result<Vec<i32>, Fault> {169    let lattice =170        Lattice::named(lattice).ok_or_else(|| Fault::new("the lattice is square or hex."))?;171    let (x, y) = lattice.xy(u64::from(n));172    Ok(vec![x as i32, y as i32, lattice.ring(u64::from(n)) as i32])173}174175/// Reads the cell under a pixel of the sheet: its number, cell, pixel centre and width, ring, primality and factors, as JSON.176#[wasm_bindgen]177pub fn spiral_at(lattice: &str, side: usize, x: f64, y: f64, size: usize) -> Result<String, Fault> {178    let sheet = Sheet::new(lattice, side, size)?;179    let (cx, cy) = sheet180        .cell(x, y)181        .ok_or_else(|| Fault::new("the click missed the spiral."))?;182    let n = sheet.lattice.n(cx, cy);183    let factors = factorize(n as usize);184    let (px, py) = sheet.center(cx, cy);185    Ok(json!({186        "n": n,187        "x": cx,188        "y": cy,189        "px": px,190        "py": py,191        "span": sheet.span(),192        "ring": sheet.lattice.ring(n),193        "prime": factors.len() == 1 && factors[0].1 == 1,194        "factors": factors,195    })196    .to_string())197}198199/// Reads the quadratic a k^2 + b k + c over the sheet: the count of numbers, of primes and their density, the values inside with their cells and prime hits, the hit count, its share and the opening streak, as JSON.200#[wasm_bindgen]201pub fn spiral_polynomial(202    lattice: &str,203    side: usize,204    a: i32,205    b: i32,206    c: i32,207) -> Result<String, Fault> {208    let sheet = Sheet::new(lattice, side, 1)?;209    let quadratic = read(sheet.lattice, side, a, b, c)?;210    Ok(json!({211        "top": quadratic.top,212        "primes": quadratic.primes,213        "density": quadratic.density,214        "count": quadratic.values.len(),215        "hits": quadratic.hits,216        "share": quadratic.share,217        "streak": quadratic.streak,218        "values": quadratic.values,219        "hit": quadratic.hit,220        "cells": quadratic.cells,221    })222    .to_string())223}224225/// Returns the pixel centre of every number from one across the sheet, x then y, for sheets of at most a few thousand cells.226#[wasm_bindgen]227pub fn spiral_centers(lattice: &str, side: usize, size: usize) -> Result<Vec<f32>, Fault> {228    let sheet = Sheet::new(lattice, side, size)?;229    let top = sheet.lattice.count(side);230    if top > LABELS {231        return Err(Fault::new(format!("the labels stop at {LABELS} cells.")));232    }233    let mut out = Vec::with_capacity(2 * top);234    for n in 1..=top as u64 {235        let (x, y) = sheet.lattice.xy(n);236        let (px, py) = sheet.center(x, y);237        out.push(px as f32);238        out.push(py as f32);239    }240    Ok(out)241}