-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
# Objective - Currently we have the `ColorRange` trait to interpolate linearly between two colors - It would be cool to have: 1. linear interpolation between n colors where `n >= 1` 2. other kinds of interpolation ## Solution 1. Implement `ColorGradient` which takes `n >= 1` colors and linearly interpolates between consecutive pairs of them 2. Implement `Curve` intergration for this `ColorGradient` which yields a curve struct. After that we can apply all of the cool curve adaptors like `.reparametrize()` and `.map()` to the gradient ## Testing - Added doc tests - Added tests ## Showcase ```rust // let gradient = ColorGradient::new(vec![]).unwrap(); // panic! 💥 let gradient = ColorGradient::new([basic::RED, basic::LIME, basic::BLUE]).expect("non-empty"); let curve = gradient.to_curve(); let brighter_curve = curve.map(|c| c.mix(&basic::WHITE, 0.5)); ``` --- Kind of related to #14971 (comment) --------- Co-authored-by: Zachary Harrold <zac@harrold.com.au> Co-authored-by: Matty <weatherleymatthew@gmail.com>
- Loading branch information
1 parent
01a3b0e
commit 8a64b76
Showing
3 changed files
with
109 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)] | ||
#[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); | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters