1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
//! Demonstrates how to animate colors in different color spaces using mixing and splines.

use bevy::{math::VectorSpace, prelude::*};

// We define this trait so we can reuse the same code for multiple color types that may be implemented using curves.
trait CurveColor: VectorSpace + Into<Color> + Send + Sync + 'static {}
impl<T: VectorSpace + Into<Color> + Send + Sync + 'static> CurveColor for T {}

// We define this trait so we can reuse the same code for multiple color types that may be implemented using mixing.
trait MixedColor: Mix + Into<Color> + Send + Sync + 'static {}
impl<T: Mix + Into<Color> + Send + Sync + 'static> MixedColor for T {}

#[derive(Debug, Component)]
struct Curve<T: CurveColor>(CubicCurve<T>);

#[derive(Debug, Component)]
struct Mixed<T: MixedColor>([T; 4]);

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, setup)
        .add_systems(
            Update,
            (
                animate_curve::<LinearRgba>,
                animate_curve::<Oklaba>,
                animate_curve::<Xyza>,
                animate_mixed::<Hsla>,
                animate_mixed::<Srgba>,
                animate_mixed::<Oklcha>,
            ),
        )
        .run();
}

fn setup(mut commands: Commands) {
    commands.spawn(Camera2dBundle::default());

    // The color spaces `Oklaba`, `Laba`, `LinearRgba`, `Srgba` and `Xyza` all are either perceptually or physically linear.
    // This property allows us to define curves, e.g. bezier curves through these spaces.

    // Define the control points for the curve.
    // For more information, please see the cubic curve example.
    let colors = [
        LinearRgba::WHITE,
        LinearRgba::rgb(1., 1., 0.), // Yellow
        LinearRgba::RED,
        LinearRgba::BLACK,
    ];
    // Spawn a sprite using the provided colors as control points.
    spawn_curve_sprite(&mut commands, 275., colors);

    // Spawn another sprite using the provided colors as control points after converting them to the `Xyza` color space.
    spawn_curve_sprite(&mut commands, 175., colors.map(Xyza::from));

    spawn_curve_sprite(&mut commands, 75., colors.map(Oklaba::from));

    // Other color spaces like `Srgba` or `Hsva` are neither perceptually nor physically linear.
    // As such, we cannot use curves in these spaces.
    // However, we can still mix these colours and animate that way. In fact, mixing colors works in any color space.

    // Spawn a spritre using the provided colors for mixing.
    spawn_mixed_sprite(&mut commands, -75., colors.map(Hsla::from));

    spawn_mixed_sprite(&mut commands, -175., colors.map(Srgba::from));

    spawn_mixed_sprite(&mut commands, -275., colors.map(Oklcha::from));
}

fn spawn_curve_sprite<T: CurveColor>(commands: &mut Commands, y: f32, points: [T; 4]) {
    commands.spawn((
        SpriteBundle {
            transform: Transform::from_xyz(0., y, 0.),
            sprite: Sprite {
                custom_size: Some(Vec2::new(75., 75.)),
                ..Default::default()
            },
            ..Default::default()
        },
        Curve(CubicBezier::new([points]).to_curve()),
    ));
}

fn spawn_mixed_sprite<T: MixedColor>(commands: &mut Commands, y: f32, colors: [T; 4]) {
    commands.spawn((
        SpriteBundle {
            transform: Transform::from_xyz(0., y, 0.),
            sprite: Sprite {
                custom_size: Some(Vec2::new(75., 75.)),
                ..Default::default()
            },
            ..Default::default()
        },
        Mixed(colors),
    ));
}

fn animate_curve<T: CurveColor>(
    time: Res<Time>,
    mut query: Query<(&mut Transform, &mut Sprite, &Curve<T>)>,
) {
    let t = (time.elapsed_seconds().sin() + 1.) / 2.;

    for (mut transform, mut sprite, cubic_curve) in &mut query {
        // position takes a point from the curve where 0 is the initial point
        // and 1 is the last point
        sprite.color = cubic_curve.0.position(t).into();
        transform.translation.x = 600. * (t - 0.5);
    }
}

fn animate_mixed<T: MixedColor>(
    time: Res<Time>,
    mut query: Query<(&mut Transform, &mut Sprite, &Mixed<T>)>,
) {
    let t = (time.elapsed_seconds().sin() + 1.) / 2.;

    for (mut transform, mut sprite, mixed) in &mut query {
        sprite.color = {
            // First, we determine the amount of intervals between colors.
            // For four colors, there are three intervals between those colors;
            let intervals = (mixed.0.len() - 1) as f32;

            // Next we determine the index of the first of the two colorts to mix.
            let start_i = (t * intervals).floor().min(intervals - 1.);

            // Lastly we determine the 'local' value of t in this interval.
            let local_t = (t * intervals) - start_i;

            let color = mixed.0[start_i as usize].mix(&mixed.0[start_i as usize + 1], local_t);
            color.into()
        };
        transform.translation.x = 600. * (t - 0.5);
    }
}