sieve.rs
6.7 kB · rust · 192 lines
1use crate::space::Pack;2use crate::{Fault, Grid};3use mrlyrs::core::json;4use mrlyrs::math::three::Vec3;5use mrlyrs::num::sieve;6use wasm_bindgen::prelude::*;78const PLANE_SITES: usize = 4_000_000;9const HOLE_BUDGET: u128 = 20_000;10const READ_LEVELS: usize = 16;11const WALK_STOPS: usize = 400;1213fn word(kind: &str, letter: u32, levels: usize) -> Result<Vec<u64>, Fault> {14 let side = u64::from(letter);15 if levels > READ_LEVELS {16 return Err(Fault::new(format!(17 "the levels must be between 1 and {READ_LEVELS}."18 )));19 }20 match kind {21 "odd" => Ok(sieve::odd_word(levels)),22 "flat" => {23 if side < 3 || side % 2 == 0 || side > 15 {24 return Err(Fault::new(format!(25 "the letter {letter} is not an odd side between three and fifteen."26 )));27 }28 Ok(sieve::flat_word(side, levels))29 }30 _ => Err(Fault::new(format!("no schedule is named {kind:?}."))),31 }32}3334fn axes(dimension: usize) -> Result<u32, Fault> {35 match dimension {36 2 | 3 => Ok(dimension as u32),37 _ => Err(Fault::new("the sieve draws in two or three dimensions.")),38 }39}4041fn fits(word: &[u64], dimension: u32) -> Result<bool, Fault> {42 if dimension == 2 {43 let side = sieve::side(word)?;44 return Ok(side * side <= PLANE_SITES as u128);45 }46 Ok(sieve::holes(word, dimension)? <= HOLE_BUDGET)47}4849/// Returns the deepest level the schedule reaches before it outgrows the sites this page rasters in the plane or the punctures it draws in the cube.50#[wasm_bindgen]51pub fn wallis_cap(kind: &str, letter: u32, dimension: usize) -> Result<usize, Fault> {52 let axes = axes(dimension)?;53 let mut top = 1;54 for levels in 1..=READ_LEVELS {55 if !fits(&word(kind, letter, levels)?, axes)? {56 break;57 }58 top = levels;59 }60 Ok(top)61}6263/// Reads the schedule at every level up to the one asked: its letter, its side, its surviving cells, its punctures, the share of the whole it leaves and the box exponent it reads, then the word's own reading beside the limit its schedule walks to and the gap left, as JSON.64#[wasm_bindgen]65pub fn wallis_read(66 kind: &str,67 letter: u32,68 levels: usize,69 dimension: usize,70) -> Result<String, Fault> {71 let axes = axes(dimension)?;72 let schedule = word(kind, letter, levels.max(2))?;73 let word = word(kind, letter, levels)?;74 let mut rows: Vec<mrlyrs::core::Json> = Vec::with_capacity(word.len());75 for n in 1..=word.len() {76 let prefix = &word[..n];77 rows.push(json!({78 "level": n,79 "letter": prefix[n - 1],80 "side": sieve::side(prefix)?.to_string(),81 "cells": sieve::cells(prefix, axes)?.to_string(),82 "holes": sieve::holes(prefix, axes)?.to_string(),83 "ratio": sieve::ratio(prefix, axes)?,84 "exponent": sieve::exponent(prefix, axes)?,85 }));86 }87 let ratio = sieve::ratio(&word, axes)?;88 let limit = sieve::limit(&schedule, axes)?.unwrap_or(0.0);89 Ok(json!({90 "word": word.clone(),91 "dimension": dimension,92 "side": sieve::side(&word)?.to_string(),93 "cells": sieve::cells(&word, axes)?.to_string(),94 "holes": sieve::holes(&word, axes)?.to_string(),95 "ratio": ratio,96 "exponent": sieve::exponent(&word, axes)?,97 "limit": limit,98 "gap": ratio - limit,99 "closed": limit > 0.0,100 "levels": rows,101 })102 .to_string())103}104105/// Walks the share of the whole the schedule leaves at level one through the count of stops, one number a level, so the approach to the limit can be drawn past the level the page rasters.106#[wasm_bindgen]107pub fn wallis_walk(108 kind: &str,109 letter: u32,110 dimension: usize,111 stops: usize,112) -> Result<Vec<f64>, Fault> {113 let axes = axes(dimension)?;114 if !(1..=WALK_STOPS).contains(&stops) {115 return Err(Fault::new(format!(116 "the stops must be between 1 and {WALK_STOPS}."117 )));118 }119 let word = match kind {120 "odd" => sieve::odd_word(stops),121 _ => word(kind, letter, 1).map(|_| sieve::flat_word(u64::from(letter), stops))?,122 };123 let mut out = Vec::with_capacity(stops);124 for n in 1..=stops {125 out.push(sieve::ratio(&word[..n], axes)?);126 }127 Ok(out)128}129130/// Builds the plane sieve the schedule spells as a byte grid, one byte a site, one where the site survives and zero where a level punched it out.131#[wasm_bindgen]132pub fn wallis_grid(kind: &str, letter: u32, levels: usize) -> Result<Grid, Fault> {133 let word = word(kind, letter, levels)?;134 if !fits(&word, 2)? {135 return Err(Fault::new(format!(136 "a side of {} is more than this page rasters; lower the level.",137 sieve::side(&word)?138 )));139 }140 let (side, sites) = sieve::raster(&word)?;141 Ok(Grid {142 width: side as u32,143 height: side as u32,144 types: sites,145 })146}147148/// Packs the punctures of the solid sieve the schedule spells as boxes: two section lengths, then six floats per vertex, position and normal, in the unit box, one box a hole at the size the level that punched it left.149#[wasm_bindgen]150pub fn wallis_faces(kind: &str, letter: u32, levels: usize) -> Result<Vec<f32>, Fault> {151 let word = word(kind, letter, levels)?;152 if !fits(&word, 3)? {153 return Err(Fault::new(format!(154 "{} punctures is more than this page draws; lower the level.",155 sieve::holes(&word, 3)?156 )));157 }158 let half = sieve::side(&word)? as f32 / 2.0;159 let at =160 |x: f32, y: f32, z: f32| Vec3::new((x - half) / half, (y - half) / half, (z - half) / half);161 let mut pack = Pack::new();162 for hole in sieve::punctures(&word, 3)?.chunks(4) {163 let (x, y, z) = (hole[0] as f32, hole[1] as f32, hole[2] as f32);164 let s = hole[3] as f32;165 let (a, b, c) = (x + s, y + s, z + s);166 pack.quad(167 [at(x, y, z), at(x, y, c), at(x, b, c), at(x, b, z)],168 Vec3::new(-1.0, 0.0, 0.0),169 );170 pack.quad(171 [at(a, y, z), at(a, b, z), at(a, b, c), at(a, y, c)],172 Vec3::new(1.0, 0.0, 0.0),173 );174 pack.quad(175 [at(x, y, z), at(a, y, z), at(a, y, c), at(x, y, c)],176 Vec3::new(0.0, -1.0, 0.0),177 );178 pack.quad(179 [at(x, b, z), at(x, b, c), at(a, b, c), at(a, b, z)],180 Vec3::new(0.0, 1.0, 0.0),181 );182 pack.quad(183 [at(x, y, z), at(x, b, z), at(a, b, z), at(a, y, z)],184 Vec3::new(0.0, 0.0, -1.0),185 );186 pack.quad(187 [at(x, y, c), at(a, y, c), at(a, b, c), at(x, b, c)],188 Vec3::new(0.0, 0.0, 1.0),189 );190 }191 Ok(pack.buffer())192}