diff --git a/crates/bevy_color/src/color_gradient.rs b/crates/bevy_color/src/color_gradient.rs new file mode 100644 index 0000000000..60920d6ca3 --- /dev/null +++ b/crates/bevy_color/src/color_gradient.rs @@ -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)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))] +pub struct ColorCurve { + core: EvenCore, +} + +impl ColorCurve +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) -> Result { + let colors = colors.into_iter().collect::>(); + 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 Curve for ColorCurve +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); + }); + } +} diff --git a/crates/bevy_color/src/color_range.rs b/crates/bevy_color/src/color_range.rs index 16d6f04866..72260b27f4 100644 --- a/crates/bevy_color/src/color_range.rs +++ b/crates/bevy_color/src/color_range.rs @@ -15,7 +15,7 @@ pub trait ColorRange { impl ColorRange for Range { fn at(&self, factor: f32) -> T { - self.start.mix(&self.end, factor) + self.start.mix(&self.end, factor.clamp(0.0, 1.0)) } } @@ -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); } } diff --git a/crates/bevy_color/src/lib.rs b/crates/bevy_color/src/lib.rs index 95c51a494d..c03c1dd348 100644 --- a/crates/bevy_color/src/lib.rs +++ b/crates/bevy_color/src/lib.rs @@ -92,6 +92,7 @@ mod color; pub mod color_difference; +mod color_gradient; mod color_ops; mod color_range; mod hsla; @@ -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::*;