gled-model 2.34.0

The data model of gled: projects, scenes, effects, animations, curves and assets
Documentation
//! The geometry of SVG paths, as the code reading drawings into definitions
//! needs it: the size of a drawing unit and Bézier curves cut into straight
//! pieces. No SVG library here, so the model stays light.

/// Metres in a CSS pixel: SVG libraries measure a drawing in pixels at 96 dpi,
/// whatever unit its file gives.
pub const METRES_PER_PX: f64 = 0.0254 / 96.0;

/// Straight pieces a curve is cut into. Plenty for spreading LEDs along it.
pub const CURVE_STEPS: usize = 32;

/// The quadratic Bézier curve from `from` over `control` to `to`, as the
/// `steps` points after `from`; the last one is `to`.
pub fn quadratic(from: [f32; 2], control: [f32; 2], to: [f32; 2], steps: usize) -> Vec<[f32; 2]> {
    (1..=steps)
        .map(|step| {
            let t = step as f32 / steps as f32;
            let u = 1.0 - t;
            [0, 1].map(|axis| u * u * from[axis] + 2.0 * u * t * control[axis] + t * t * to[axis])
        })
        .collect()
}

/// The cubic Bézier curve from `from` over `first` and `second` to `to`, as
/// the `steps` points after `from`; the last one is `to`.
pub fn cubic(
    from: [f32; 2],
    first: [f32; 2],
    second: [f32; 2],
    to: [f32; 2],
    steps: usize,
) -> Vec<[f32; 2]> {
    (1..=steps)
        .map(|step| {
            let t = step as f32 / steps as f32;
            let u = 1.0 - t;
            [0, 1].map(|axis| {
                u * u * u * from[axis]
                    + 3.0 * u * u * t * first[axis]
                    + 3.0 * u * t * t * second[axis]
                    + t * t * t * to[axis]
            })
        })
        .collect()
}

#[cfg(test)]
mod tests {
    use super::*;

    fn close(a: [f32; 2], b: [f32; 2]) -> bool {
        (a[0] - b[0]).abs() < 1e-5 && (a[1] - b[1]).abs() < 1e-5
    }

    #[test]
    fn a_quadratic_curve_ends_on_its_end_point() {
        let points = quadratic([0.0, 0.0], [5.0, 10.0], [10.0, 0.0], CURVE_STEPS);
        assert_eq!(points.len(), CURVE_STEPS);
        assert!(close(points[CURVE_STEPS - 1], [10.0, 0.0]), "{points:?}");
        // halfway it is halfway to the control point
        assert!(close(points[CURVE_STEPS / 2 - 1], [5.0, 5.0]), "{points:?}");
    }

    #[test]
    fn a_cubic_curve_bulges_toward_its_controls() {
        let points = cubic([0.0, 0.0], [0.0, 8.0], [10.0, 8.0], [10.0, 0.0], 4);
        assert!(close(points[3], [10.0, 0.0]), "{points:?}");
        // at t = 1/2: (0 + 3·0 + 3·10 + 10) / 8 across, (3·8 + 3·8) / 8 up
        assert!(close(points[1], [5.0, 6.0]), "{points:?}");
    }
}