spiral.rs
8.0 kB · rust · 238 lines
1use crate::{rgba, theme, Fault, Pixels};2use mrlyrs::core::json;3use mrlyrs::num::factor::factorize;4use mrlyrs::num::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 = lattice.parse::<Lattice>()?;23 if side.is_multiple_of(2) || side > SIDE {24 return Err(Fault::new(format!("the side is odd and at most {SIDE}.")));25 }26 if size == 0 || size > SIZE {27 return Err(Fault::new(format!("the sheet is 1 to {SIZE} pixels wide.")));28 }29 let radius = lattice.radius(side) as i64;30 let half = size as f64 / 2.0;31 let scale = match lattice {32 Lattice::Square => size as f64 / side as f64,33 Lattice::Hex => {34 let r = radius as f64;35 (size as f64 / (2.0 * ROOT3 * (r + 0.5))).min(size as f64 / (3.0 * r + 2.0))36 }37 };38 Ok(Sheet {39 lattice,40 radius,41 half,42 scale,43 })44 }45 fn center(&self, x: i64, y: i64) -> (f64, f64) {46 let (x, y) = (x as f64, y as f64);47 match self.lattice {48 Lattice::Square => (self.half + x * self.scale, self.half - y * self.scale),49 Lattice::Hex => (50 self.half + self.scale * ROOT3 * (x + y / 2.0),51 self.half + 1.5 * self.scale * y,52 ),53 }54 }55 fn span(&self) -> f64 {56 match self.lattice {57 Lattice::Square => self.scale,58 Lattice::Hex => self.scale * ROOT3,59 }60 }61 fn cell(&self, px: f64, py: f64) -> Option<(i64, i64)> {62 let (x, y) = (px - self.half, py - self.half);63 let cell = match self.lattice {64 Lattice::Square => (65 (x / self.scale + 0.5).floor() as i64,66 (-y / self.scale + 0.5).floor() as i64,67 ),68 Lattice::Hex => {69 let q = (ROOT3 / 3.0 * x - y / 3.0) / self.scale;70 let r = 2.0 / 3.0 * y / self.scale;71 let s = -q - r;72 let (mut rq, mut rr, rs) = (q.round(), r.round(), s.round());73 let (dq, dr, ds) = ((rq - q).abs(), (rr - r).abs(), (rs - s).abs());74 if dq > dr && dq > ds {75 rq = -rr - rs;76 } else if dr > ds {77 rr = -rq - rs;78 }79 (rq as i64, rr as i64)80 }81 };82 (self.lattice.ring_of(cell.0, cell.1) <= self.radius as u64).then_some(cell)83 }84 fn gap(&self, px: f64, py: f64, cell: (i64, i64)) -> bool {85 if self.scale < 6.0 {86 return false;87 }88 let (cx, cy) = self.center(cell.0, cell.1);89 let (x, y) = (px - cx, py - cy);90 match self.lattice {91 Lattice::Square => x + self.scale / 2.0 < 1.0 || y + self.scale / 2.0 < 1.0,92 Lattice::Hex => {93 let reach = x94 .abs()95 .max((x / 2.0 + y * ROOT3 / 2.0).abs())96 .max((y * ROOT3 / 2.0 - x / 2.0).abs());97 reach > self.scale * ROOT3 / 2.0 - 1.098 }99 }100 }101}102103fn read(lattice: Lattice, side: usize, a: i32, b: i32, c: i32) -> Result<Diagonal, Fault> {104 if a < 1 {105 return Err(Fault::new("a quadratic needs a of at least 1."));106 }107 if b.abs() > REACH || c.abs() > REACH {108 return Err(Fault::new(format!("b and c stay within {REACH}.")));109 }110 Ok(spiral::diagonal(111 lattice,112 side,113 i64::from(a),114 i64::from(b),115 i64::from(c),116 ))117}118119/// 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.120#[wasm_bindgen]121#[allow(clippy::too_many_arguments)]122pub fn spiral_pixels(123 lattice: &str,124 side: usize,125 a: i32,126 b: i32,127 c: i32,128 mark: &str,129 faint: bool,130 size: usize,131) -> Result<Pixels, Fault> {132 let sheet = Sheet::new(lattice, side, size)?;133 let mark = mark.parse::<Mark>()?;134 let quadratic = read(sheet.lattice, side, a, b, c)?;135 let ink = theme();136 let ground = rgba(ink.ground);137 let mut look: Vec<[u8; 4]> = spiral::marks(mark, quadratic.top)138 .iter()139 .map(|&m| match m {140 1 => rgba(ink.yellow),141 -1 => rgba(ink.pink),142 _ if faint => rgba(ink.line),143 _ => ground,144 })145 .collect();146 for (&value, &hit) in quadratic.values.iter().zip(&quadratic.hit) {147 look[value as usize] = rgba(if hit { ink.orange } else { ink.blue });148 }149 let mut colors = Vec::with_capacity(size * size);150 for py in 0..size {151 for px in 0..size {152 let (fx, fy) = (px as f64 + 0.5, py as f64 + 0.5);153 colors.push(match sheet.cell(fx, fy) {154 Some(cell) if !sheet.gap(fx, fy, cell) => {155 look[sheet.lattice.n(cell.0, cell.1) as usize]156 }157 _ => ground,158 });159 }160 }161 Ok(Pixels::of(size, size, colors))162}163164/// Returns the cell of a number and its ring: x right and y up on the square, axial q and r on the hexagon.165#[wasm_bindgen]166pub fn spiral_xy(lattice: &str, n: u32) -> Result<Vec<i32>, Fault> {167 let lattice = lattice.parse::<Lattice>()?;168 let (x, y) = lattice.xy(u64::from(n));169 Ok(vec![x as i32, y as i32, lattice.ring(u64::from(n)) as i32])170}171172/// Reads the cell under a pixel of the sheet: its number, cell, pixel centre and width, ring, primality and factors, as JSON.173#[wasm_bindgen]174pub fn spiral_at(lattice: &str, side: usize, x: f64, y: f64, size: usize) -> Result<String, Fault> {175 let sheet = Sheet::new(lattice, side, size)?;176 let (cx, cy) = sheet177 .cell(x, y)178 .ok_or_else(|| Fault::new("the click missed the spiral."))?;179 let n = sheet.lattice.n(cx, cy);180 let factors = factorize(n as usize);181 let (px, py) = sheet.center(cx, cy);182 Ok(json!({183 "n": n,184 "x": cx,185 "y": cy,186 "px": px,187 "py": py,188 "span": sheet.span(),189 "ring": sheet.lattice.ring(n),190 "prime": factors.len() == 1 && factors[0].1 == 1,191 "factors": factors,192 })193 .to_string())194}195196/// 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.197#[wasm_bindgen]198pub fn spiral_polynomial(199 lattice: &str,200 side: usize,201 a: i32,202 b: i32,203 c: i32,204) -> Result<String, Fault> {205 let sheet = Sheet::new(lattice, side, 1)?;206 let quadratic = read(sheet.lattice, side, a, b, c)?;207 Ok(json!({208 "top": quadratic.top,209 "primes": quadratic.primes,210 "density": quadratic.density,211 "count": quadratic.values.len(),212 "hits": quadratic.hits,213 "share": quadratic.share,214 "streak": quadratic.streak,215 "values": quadratic.values,216 "hit": quadratic.hit,217 "cells": quadratic.cells,218 })219 .to_string())220}221222/// Returns the pixel centre of every number from one across the sheet, x then y, for sheets of at most a few thousand cells.223#[wasm_bindgen]224pub fn spiral_centers(lattice: &str, side: usize, size: usize) -> Result<Vec<f32>, Fault> {225 let sheet = Sheet::new(lattice, side, size)?;226 let top = sheet.lattice.count(side);227 if top > LABELS {228 return Err(Fault::new(format!("the labels stop at {LABELS} cells.")));229 }230 let mut out = Vec::with_capacity(2 * top);231 for n in 1..=top as u64 {232 let (x, y) = sheet.lattice.xy(n);233 let (px, py) = sheet.center(x, y);234 out.push(px as f32);235 out.push(py as f32);236 }237 Ok(out)238}