spirograph.rs

7.2 kB · rust · 191 lines

1use crate::Fault;2use mrlycore::{json, Json};3use mrlynum::spirograph as roulette;4use mrlynum::spirograph::{Kind, Pencil, Track};5use wasm_bindgen::prelude::*;67#[allow(clippy::too_many_arguments)]8fn build(9    types: &[u8],10    width: usize,11    height: usize,12    pens: &str,13    track: &str,14    ring: usize,15    wheel: usize,16    sides: usize,17    laps: usize,18    reach: f64,19    jitter: f64,20    seed: u32,21) -> Result<(Track, Vec<Pencil>), Fault> {22    let path = roulette::track(track, ring, wheel, sides, laps)?;23    let pencils = roulette::pencils(types, width, height, pens, reach, jitter, seed)?;24    Ok((path, pencils))25}2627/// Traces the roulette of a byte grid taken as the wheel: a pencil on every chosen site (`fill`, `void`, `both` or `corners`), the wheel of radius `wheel` rolling without slipping on the track (`line`, `in`, `out`, `polyin` or `polyout`) laid at radius `ring` with `sides` sides for `laps` laps, the tile scaled to `reach` wheel radii and every seat jittered by up to `jitter` cells under the seed. Pencil by pencil, `samples` points each along the whole track, x then y, in the track's units.28#[wasm_bindgen]29#[allow(clippy::too_many_arguments)]30pub fn spirograph(31    types: &[u8],32    width: usize,33    height: usize,34    pens: &str,35    track: &str,36    ring: usize,37    wheel: usize,38    sides: usize,39    laps: usize,40    reach: f64,41    jitter: f64,42    seed: u32,43    samples: usize,44) -> Result<Vec<f32>, Fault> {45    let (path, pencils) = build(46        types, width, height, pens, track, ring, wheel, sides, laps, reach, jitter, seed,47    )?;48    Ok(roulette::trace(&path, &pencils, samples)?)49}5051/// Reads the roulette: the pencil count by kind, the classes under the coincidence law, which are the distinct curves on a circle and shapes up to a shift on a line, the generic node count on a circle inside the seat window `0 < |p| < min(1, A)` and null elsewhere, the ring over the wheel in lowest terms, the orbits the track closes after, the picture's rotation order, the wheel turns, the path length, the cell side on the wheel, every seat with its kind, the track outline and whether it closes, and the box the picture sits in, as JSON.52#[wasm_bindgen]53#[allow(clippy::too_many_arguments)]54pub fn spirograph_read(55    types: &[u8],56    width: usize,57    height: usize,58    pens: &str,59    track: &str,60    ring: usize,61    wheel: usize,62    sides: usize,63    laps: usize,64    reach: f64,65    jitter: f64,66    seed: u32,67) -> Result<String, Fault> {68    let (path, pencils) = build(69        types, width, height, pens, track, ring, wheel, sides, laps, reach, jitter, seed,70    )?;71    let counts = roulette::seats(&pencils);72    let seats: Vec<Json> = pencils73        .iter()74        .map(|p| {75            let kind = match p.kind {76                Kind::Fill => "fill",77                Kind::Void => "void",78                Kind::Corner => "corner",79            };80            json!([p.x, p.y, kind])81        })82        .collect();83    let outline: Vec<Json> = path.outline.iter().map(|&(x, y)| json!([x, y])).collect();84    Ok(json!({85        "pencils": pencils.len(),86        "fills": counts.fills,87        "voids": counts.voids,88        "corners": counts.corners,89        "distinct": roulette::distinct(&path, &pencils, jitter == 0.0),90        "nodes": roulette::nodes(&path, &pencils, jitter == 0.0),91        "a": path.ratio.0,92        "b": path.ratio.1,93        "orbits": path.orbits,94        "fold": path.fold,95        "turns": path.total / (std::f64::consts::TAU * path.wheel),96        "total": path.total,97        "wheel": path.wheel,98        "side": path.side,99        "cell": roulette::cell(width, height, reach),100        "seats": seats,101        "outline": outline,102        "closed": path.closed,103        "frame": roulette::frame(&path, &pencils),104    })105    .to_string())106}107108/// The wheel's centre and its turn in radians a fraction `at` of the way along the track, as `[x, y, turn]`.109#[wasm_bindgen]110pub fn spirograph_pose(111    track: &str,112    ring: usize,113    wheel: usize,114    sides: usize,115    laps: usize,116    at: f64,117) -> Result<Vec<f64>, Fault> {118    let path = roulette::track(track, ring, wheel, sides, laps)?;119    let s = path.total * at.clamp(0.0, 1.0);120    let (x, y) = roulette::pose(&path, s);121    Ok(vec![x, y, roulette::turn(&path, s)])122}123124/// The caps: the most pencils a wheel seats, the most points a trace returns, the largest radius, the most laps and the sides a polygon may have, as JSON.125#[wasm_bindgen]126pub fn spirograph_caps() -> String {127    json!({128        "pencils": roulette::PENCIL_CAP,129        "points": roulette::POINT_CAP,130        "radius": roulette::RADIUS_CAP,131        "laps": roulette::LAPS_CAP,132        "sides": [roulette::SIDES.0, roulette::SIDES.1],133    })134    .to_string()135}136137/// The shape a roulette walls off inside its own disc: the raster, its numbers and the disc it sits in.138#[wasm_bindgen(getter_with_clone)]139pub struct Cover {140    /// The raster's side in pixels.141    pub side: u32,142    /// The raster row by row, the ordinate falling down the rows: zero outside the disc, one the flood poured from outside, two the flood poured at the centre, three the shape.143    pub mask: Vec<u8>,144    /// The shape's share of the disc's pixels, the walls counted in.145    pub covered: f64,146    /// The centre flood's share of the disc's pixels.147    pub hole: f64,148    /// The share of the disc's pixels the curves themselves mark, the boundary error `covered` carries and loses as the raster grows.149    pub wall: f64,150    /// The mean signed winding number of the disc's pixel centres, read by scanline off the polylines.151    pub winding: f64,152    /// The closed form `winding` converges to: the distinct curves' signed areas summed and divided by the disc's area.153    pub areas: f64,154    /// The disc in the track's units: its centre, the radius no curve leaves, and the radius no curve enters.155    pub disc: Vec<f64>,156}157158/// Covers the roulette of a byte grid taken as the wheel, the arguments of `spirograph` and a raster side: every curve is a wall, one fluid is poured from outside the picture and one at the centre of the track, and the shape is everything between the two walls, the walls and their pockets included. The cover takes as many samples per curve as the raster needs. `winding` is read by scanline off the polylines and `areas` is the closed form it converges to, so the pair checks the raster against Green's theorem and never the floods. A circle track only, `in` or `out`: the wall of a line or a polygon roulette need not close.159#[wasm_bindgen]160#[allow(clippy::too_many_arguments)]161pub fn spirograph_cover(162    types: &[u8],163    width: usize,164    height: usize,165    pens: &str,166    track: &str,167    ring: usize,168    wheel: usize,169    sides: usize,170    laps: usize,171    reach: f64,172    jitter: f64,173    seed: u32,174    side: usize,175) -> Result<Cover, Fault> {176    let (path, pencils) = build(177        types, width, height, pens, track, ring, wheel, sides, laps, reach, jitter, seed,178    )?;179    let bounds = roulette::disc(&path, &pencils)?;180    let out = roulette::cover(&path, &pencils, jitter == 0.0, 2, side)?;181    Ok(Cover {182        side: out.side as u32,183        mask: out.mask,184        covered: out.covered,185        hole: out.hole,186        wall: out.wall,187        winding: out.winding,188        areas: out.areas,189        disc: vec![bounds.x, bounds.y, bounds.radius, bounds.hole],190    })191}