tilezz 0.2.0

Utilities to work with perfect-precision polygonal tiles built on top of cyclotomic integer rings.
Documentation
//! Rendering for the `--mode render` GIF path.
//!
//! `polygons<ZZ>` converts canonical angle sequences to floating-point
//! polylines (one per rat); `run_rat_enum_polylines` is the runtime-ring
//! wrapper used by the CLI; `rats_gif` assembles those polylines into the
//! animated-GIF frames the `rat_enum` binary writes. All the rat-specific
//! presentation (tile styling, vertex labels, viewport) lives here, next to
//! its only caller, over the generic `tilezz::vis` primitives.

use crate::cyclotomic::IsRing;
use crate::enumerate::enumerate_dispatch;
use crate::geom::rat::Rat;
use crate::geom::snake::Turtle;
use crate::vis::plotutils::P64;

pub fn polygons<ZZ: IsRing>(rats: Vec<Vec<i8>>) -> Vec<Vec<P64>> {
    rats.into_iter()
        .map(|seq| Rat::<ZZ>::from_slice_trusted(&seq).to_polyline_f64(Turtle::default()))
        .collect()
}

#[allow(clippy::too_many_arguments)]
pub fn run_rat_enum_polylines(
    ring: u8,
    max_steps: usize,
    step: i8,
    n_threads: usize,
    free: bool,
    paranoid: bool,
    domino: bool,
) -> Vec<Vec<P64>> {
    crate::dispatch_ring!(
        ring,
        polygons::<ZZ>(
            enumerate_dispatch::<ZZ>(max_steps, step, n_threads, free, paranoid, domino).0
        )
    )
}

/// Render rat polylines (as produced by [`run_rat_enum_polylines`]) as an
/// animated GIF: one frame per rat, a filled translucent-yellow tile with a
/// thin black border and red index-labelled vertex markers, on a symmetric
/// square viewport sized to fit every polygon centered at the origin. `img_w`
/// is the frame width in pixels and `delay_ms` the per-frame delay. Returns
/// the GIF bytes.
#[cfg(feature = "animation")]
pub fn rats_gif(rats: &[Vec<P64>], img_w: u32, delay_ms: u16) -> Vec<u8> {
    use crate::vis::animation::render_gif;
    use crate::vis::draw::{MarkerStyle, TileStyle};
    use crate::vis::scene::{Color, Fill, Scene, Stroke, TextStyle, Viewport};

    // Symmetric square viewport sized to fit every polygon centered at the
    // origin (each rat goes through to_polyline_f64 with the default Turtle).
    let mut max_abs: f64 = 1.0;
    for poly in rats {
        for (x, y) in poly {
            max_abs = max_abs.max(x.abs()).max(y.abs());
        }
    }
    let r = max_abs + 0.15 * max_abs;
    let bounds = ((-r, -r), (r, r));

    // Filled yellow tile, thin black border, red index-labelled vertices.
    let style = TileStyle::filled(
        Fill::solid(Color::YELLOW.with_alpha(80)),
        Stroke::solid(Color::BLACK, 0.01 * r),
    )
    .with_vertex_marker(MarkerStyle::filled_circle(0.06 * r, Color::RED))
    .with_vertex_labels(TextStyle::new(0.04 * r, Color::WHITE).bold());

    let frames: Vec<Scene> = rats
        .iter()
        .map(|tile| {
            let mut scene = Scene::new().with_background(Color::WHITE);
            scene.draw_tile(tile, &style);
            scene
        })
        .collect();
    let vp = Viewport::square_for(img_w, bounds, 8);
    render_gif(&frames, &vp, delay_ms).expect("render GIF")
}