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}