gled-model 2.30.2

The data model of gled: projects, scenes, effects, animations, curves and assets
Documentation
//! A point of a curve: `x` is the beat position in 0..=4, `y` the value in
//! 0..=1, counted from the top. Serialized like egui's `Pos2`, so saved curves
//! load unchanged.

use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Default)]
pub struct CurvePos {
    pub x: f32,
    pub y: f32,
}

impl Eq for CurvePos {}

impl CurvePos {
    pub const fn new(x: f32, y: f32) -> Self {
        Self { x, y }
    }
}

/// A point on the quadratic Bézier curve through `points` at `t` in 0..=1.
/// The same arithmetic, in the same order, as epaint's
/// `QuadraticBezierShape::sample`, so values do not shift by a rounding error
/// against curves drawn by egui.
pub(crate) fn quadratic_bezier(points: [CurvePos; 3], t: f32) -> CurvePos {
    let h = 1.0 - t;
    let a = t * t;
    let b = 2.0 * t * h;
    let c = h * h;
    CurvePos {
        x: points[2].x * a + points[1].x * b + points[0].x * c,
        y: points[2].y * a + points[1].y * b + points[0].y * c,
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use egui::{Color32, Pos2, Stroke, epaint::QuadraticBezierShape};

    #[test]
    fn samples_exactly_like_epaint() {
        let points = [
            CurvePos::new(0.0, 1.0),
            CurvePos::new(0.37, 0.11),
            CurvePos::new(1.0, 0.0),
        ];
        let shape = QuadraticBezierShape::from_points_stroke(
            points.map(|p| Pos2::new(p.x, p.y)),
            false,
            Color32::TRANSPARENT,
            Stroke::default(),
        );
        for i in 0..=1000 {
            let t = i as f32 / 1000.0;
            let ours = quadratic_bezier(points, t);
            let theirs = shape.sample(t);
            assert_eq!((ours.x, ours.y), (theirs.x, theirs.y), "t = {t}");
        }
    }

    #[test]
    fn serializes_like_pos2() {
        let pos = CurvePos::new(1.5, 0.25);
        assert_eq!(
            serde_json::to_string(&pos).expect("serializes"),
            serde_json::to_string(&Pos2::new(1.5, 0.25)).expect("serializes")
        );
    }
}