quackie 0.2.1

A duck for your terminal! 🦆
Documentation
use std::{
    sync::{Arc, OnceLock},
    thread::sleep,
    time::{Duration, Instant},
};

use arguments::Arguments;
use clap::Parser;
use pancurses::{
    initscr, Attributes, ColorPair, COLOR_BLACK, COLOR_RED, COLOR_WHITE, COLOR_YELLOW,
};
use quackie::{
    geometry::matrix::Matrix3D,
    graphics::{
        geometry_buffer::GeometryBuffer,
        pixel_data::PixelData,
        postprocess::{antialias::Antialias, outline::Outline, PostProcessShader},
        textured_triangle::TexturedTriangle2D,
        window_buffer::WindowBuffer,
    },
};

mod arguments;
mod duck;

fn postprocess_by_name(name: String) -> Option<Box<dyn PostProcessShader>> {
    match name.as_str() {
        "antialias" => Some(Box::new(Antialias)),
        "outline" => Some(Box::new(Outline)),
        _ => None,
    }
}

fn main() {
    let arguments = Arguments::parse();
    let postprocesses = arguments
        .postprocesses
        .into_iter()
        .filter_map(postprocess_by_name)
        .collect::<Vec<_>>();
    let window = initscr();
    setup_pancurses();
    let time_reference = Instant::now();
    loop {
        let rotate_by = calculate_rotation(time_reference, arguments.rotation_speed);
        draw_screen(rotate_by, &window, &postprocesses);
        window.refresh();
        sleep(Duration::from_millis(10));
    }
}

fn setup_pancurses() {
    pancurses::start_color();
    pancurses::curs_set(0);
    pancurses::init_pair(0, COLOR_WHITE, COLOR_BLACK);
    pancurses::init_pair(1, COLOR_YELLOW, COLOR_BLACK);
    pancurses::init_pair(2, COLOR_RED, COLOR_BLACK);
}

fn calculate_rotation(time_reference: Instant, rotation_speed: f64) -> f64 {
    let secs = time_reference.elapsed().as_secs_f64();
    secs * rotation_speed
}

fn build_triangles() -> Arc<[TexturedTriangle2D]> {
    use duck::{BASE_TRIANGLES, BEAK_TRIANGLE, EYE_TRIANGLE};
    let mut triangles: Vec<TexturedTriangle2D> = BASE_TRIANGLES
        .iter()
        .map(|triangle| {
            TexturedTriangle2D::unipixeled(
                *triangle,
                PixelData::new('#', Attributes::new() | ColorPair(1)),
            )
        })
        .collect();
    let beak_triangle = TexturedTriangle2D::unipixeled(
        BEAK_TRIANGLE,
        PixelData::new('#', Attributes::new() | ColorPair(2)),
    );
    triangles.push(beak_triangle);
    let eye_triangle = TexturedTriangle2D::unipixeled(
        EYE_TRIANGLE,
        PixelData::new(' ', Attributes::new() | ColorPair(0)),
    );
    triangles.push(eye_triangle);
    Arc::from(triangles)
}

fn draw_screen(
    rotate_by: f64,
    window: &pancurses::Window,
    postprocesses: &[Box<dyn PostProcessShader>],
) {
    let mut window_buffer = WindowBuffer::for_window(
        window,
        PixelData::new(' ', Attributes::new() | ColorPair(0)),
    );
    static TRIANGLES: OnceLock<Arc<[TexturedTriangle2D]>> = OnceLock::new();
    let triangles = TRIANGLES.get_or_init(build_triangles).clone();
    let mut geometry_buffer = GeometryBuffer::new(triangles.to_vec());
    geometry_buffer.transform(
        Matrix3D::transposition(0.5, 0.5)
            * Matrix3D::rotation(-rotate_by)
            * Matrix3D::transposition(-0.5, -0.5),
    );
    window_buffer.draw_geometry(&geometry_buffer);
    for postprocess in postprocesses {
        window_buffer.post_process(postprocess.as_ref());
    }
    window_buffer.draw_screen(window);
}