Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Color gradient curve #14976

Merged
merged 15 commits into from
Sep 2, 2024
102 changes: 102 additions & 0 deletions crates/bevy_color/src/color_gradient.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
use crate::Mix;
use bevy_math::curve::{
cores::{EvenCore, EvenCoreError},
Curve, Interval,
};

/// A curve whose samples are defined by a collection of colors.
#[derive(Clone, Debug)]
alice-i-cecile marked this conversation as resolved.
Show resolved Hide resolved
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
pub struct ColorCurve<T> {
core: EvenCore<T>,
}

impl<T> ColorCurve<T>
where
T: Mix + Clone,
{
/// Create a new [`ColorCurve`] from a collection of [mixable] types. The domain of this curve
/// will always be `[0.0, len - 1]` where `len` is the amount of mixable objects in the
/// collection.
///
/// This fails if there's not at least two mixable things in the collection.
///
/// [mixable]: `Mix`
///
/// # Example
///
/// ```
/// # use bevy_color::palettes::basic::*;
/// # use bevy_color::Mix;
/// # use bevy_color::Srgba;
/// # use bevy_color::ColorCurve;
/// # use bevy_math::curve::Interval;
/// # use bevy_math::curve::Curve;
/// let broken = ColorCurve::new([RED]);
/// assert!(broken.is_err());
/// let gradient = ColorCurve::new([RED, GREEN, BLUE]);
/// assert!(gradient.is_ok());
/// assert_eq!(gradient.unwrap().domain(), Interval::new(0.0, 2.0).unwrap());
/// ```
pub fn new(colors: impl IntoIterator<Item = T>) -> Result<Self, EvenCoreError> {
let colors = colors.into_iter().collect::<Vec<_>>();
Interval::new(0.0, colors.len().saturating_sub(1) as f32)
.map_err(|_| EvenCoreError::NotEnoughSamples {
samples: colors.len(),
})
.and_then(|domain| EvenCore::new(domain, colors))
.map(|core| Self { core })
}
}

impl<T> Curve<T> for ColorCurve<T>
where
T: Mix + Clone,
{
fn domain(&self) -> Interval {
self.core.domain()
}

fn sample_unchecked(&self, t: f32) -> T {
self.core.sample_with(t, T::mix)
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::palettes::basic;
use crate::Srgba;

#[test]
fn test_color_curve() {
let broken = ColorCurve::new([basic::RED]);
assert!(broken.is_err());

let gradient = [basic::RED, basic::LIME, basic::BLUE];
let curve = ColorCurve::new(gradient).unwrap();

assert_eq!(curve.domain(), Interval::new(0.0, 2.0).unwrap());

let brighter_curve = curve.map(|c: Srgba| c.mix(&basic::WHITE, 0.5));

[
(-0.1, None),
(0.0, Some([1.0, 0.5, 0.5, 1.0])),
(0.5, Some([0.75, 0.75, 0.5, 1.0])),
(1.0, Some([0.5, 1.0, 0.5, 1.0])),
(1.5, Some([0.5, 0.75, 0.75, 1.0])),
(2.0, Some([0.5, 0.5, 1.0, 1.0])),
(2.1, None),
]
.map(|(t, maybe_rgba)| {
let maybe_srgba = maybe_rgba.map(|[r, g, b, a]| Srgba::new(r, g, b, a));
(t, maybe_srgba)
})
.into_iter()
.for_each(|(t, maybe_color)| {
assert_eq!(brighter_curve.sample(t), maybe_color);
});
}
}
6 changes: 5 additions & 1 deletion crates/bevy_color/src/color_range.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub trait ColorRange<T: Mix> {

impl<T: Mix> ColorRange<T> for Range<T> {
fn at(&self, factor: f32) -> T {
self.start.mix(&self.end, factor)
self.start.mix(&self.end, factor.clamp(0.0, 1.0))
}
}

Expand All @@ -28,16 +28,20 @@ mod tests {
#[test]
fn test_color_range() {
let range = basic::RED..basic::BLUE;
assert_eq!(range.at(-0.5), basic::RED);
assert_eq!(range.at(0.0), basic::RED);
assert_eq!(range.at(0.5), Srgba::new(0.5, 0.0, 0.5, 1.0));
assert_eq!(range.at(1.0), basic::BLUE);
assert_eq!(range.at(1.5), basic::BLUE);

let lred: LinearRgba = basic::RED.into();
let lblue: LinearRgba = basic::BLUE.into();

let range = lred..lblue;
assert_eq!(range.at(-0.5), lred);
assert_eq!(range.at(0.0), lred);
assert_eq!(range.at(0.5), LinearRgba::new(0.5, 0.0, 0.5, 1.0));
assert_eq!(range.at(1.0), lblue);
assert_eq!(range.at(1.5), lblue);
}
}
2 changes: 2 additions & 0 deletions crates/bevy_color/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@

mod color;
pub mod color_difference;
mod color_gradient;
mod color_ops;
mod color_range;
mod hsla;
Expand Down Expand Up @@ -127,6 +128,7 @@ pub mod prelude {
}

pub use color::*;
pub use color_gradient::*;
pub use color_ops::*;
pub use color_range::*;
pub use hsla::*;
Expand Down