polydf 0.1.1

Distance queries for parametric curves in 2D and 3D (nearest point, unsigned distance, early-out with speed bounds).
Documentation
// Copyright (c) 2025 William Bradley
use image::{ImageBuffer, Luma};
use indicatif::{ProgressBar, ProgressStyle};
use nalgebra::{Vector2, vector};
use polydf::{NearestOptions, nearest_t_within};

// Curve: f(t) = ( t cos(theta), t sin(theta) )
// theta(t) = sin(0.04 * sqrt(t) + 5)
fn curve(t: f32) -> Vector2<f32> {
    let s = 0.04 * t.sqrt() + 5.0;
    let theta = s.sin();
    let (st, ct) = theta.sin_cos();
    vector![t * ct, t * st]
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Image parameters
    let width: u32 = 512;
    let height: u32 = 512;

    // World domain
    let x_min = 0.0f32;
    let x_max = 500_000.0f32; // 5e5
    let y_min = -250_000.0f32; // -2.5e5
    let y_max = 250_000.0f32; // 2.5e5

    let threshold = 15000.0f32; // 5e4

    // Parameter search domain: t in [0, 5e5]
    let t_range = 0.0f32..=x_max;

    // A conservative upper bound on |C'(t)| over [0, 5e5]
    // |C'(t)| <= 1 + t * |theta'(t)| <= 1 + 0.02 * sqrt(t) <= ~15.2
    let speed_upper_bound = Some(16.0f32);

    // Fewer samples for speed in the example; adjust for quality/perf tradeoff.
    let opts = NearestOptions {
        samples: 150,
        ..Default::default()
    };

    let mut img: ImageBuffer<Luma<u8>, Vec<u8>> = ImageBuffer::new(width, height);

    // Precompute scales to map pixels to world coords
    let sx = if width > 1 {
        (x_max - x_min) / (width - 1) as f32
    } else {
        0.0
    };
    let sy = if height > 1 {
        (y_max - y_min) / (height - 1) as f32
    } else {
        0.0
    };

    let pb = ProgressBar::new(height as u64);
    pb.set_style(
        ProgressStyle::with_template(
            "{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} rows ({eta})",
        )
        .unwrap()
        .progress_chars("#>-"),
    );

    for y in 0..height {
        let wy = y_min + (y as f32) * sy;
        for x in 0..width {
            let wx = x_min + (x as f32) * sx;
            let p = vector![wx, wy];

            // Combined query: early-out when provably far; otherwise we also get the exact distance.
            let lum = match nearest_t_within(
                p,
                curve,
                t_range.clone(),
                threshold,
                speed_upper_bound,
                opts,
            ) {
                None => 255u8, // definitely farther than threshold: white
                Some(res) => {
                    let d = res.distance;
                    // Map [0, threshold] -> [0, 255], clamp for safety.
                    ((d / threshold).clamp(0.0, 1.0) * 255.0).round() as u8
                }
            };

            img.put_pixel(x, height - 1 - y, Luma([lum]));
        }
        pb.inc(1);
    }
    pb.finish_with_message("render complete");

    img.save("wave.png")?;
    eprintln!("Wrote wave.png");
    Ok(())
}