From 0950348916e1da051a6c98a0fe779a2f80570f1e Mon Sep 17 00:00:00 2001 From: oyasumi731 Date: Fri, 22 Mar 2024 09:36:46 +0900 Subject: [PATCH] Add hue traits (#12399) # Objective Fixes #12200 . ## Solution I added a Hue Trait with the rotate_hue method to enable hue rotation. Additionally, I modified the implementation of animations in the animated_material sample. --- ## Changelog - Added a `Hue` trait to `bevy_color/src/color_ops.rs`. - Added the `Hue` trait implementation to `Hsla`, `Hsva`, `Hwba`, `Lcha`, and `Oklcha`. - Updated animated_material sample. ## Migration Guide Users of Oklcha need to change their usage to use the with_hue method instead of the with_h method. --------- Co-authored-by: Pablo Reinhardt <126117294+pablo-lua@users.noreply.github.com> Co-authored-by: Alice Cecile --- crates/bevy_color/src/color_ops.rs | 36 ++++++++++++++++++++++++++++++ crates/bevy_color/src/hsla.rs | 25 ++++++++++++++++----- crates/bevy_color/src/hsva.rs | 24 +++++++++++++++----- crates/bevy_color/src/hwba.rs | 24 +++++++++++++++----- crates/bevy_color/src/lcha.rs | 24 +++++++++++++++----- crates/bevy_color/src/oklcha.rs | 24 +++++++++++++++----- examples/3d/animated_material.rs | 17 ++++++++------ 7 files changed, 137 insertions(+), 37 deletions(-) diff --git a/crates/bevy_color/src/color_ops.rs b/crates/bevy_color/src/color_ops.rs index d06e337012..460fc985d1 100644 --- a/crates/bevy_color/src/color_ops.rs +++ b/crates/bevy_color/src/color_ops.rs @@ -62,6 +62,24 @@ pub trait Alpha: Sized { } } +/// Trait for manipulating the hue of a color. +pub trait Hue: Sized { + /// Return a new version of this color with the hue channel set to the given value. + fn with_hue(&self, hue: f32) -> Self; + + /// Return the hue of this color [0.0, 360.0]. + fn hue(&self) -> f32; + + /// Sets the hue of this color. + fn set_hue(&mut self, hue: f32); + + /// Return a new version of this color with the hue channel rotated by the given degrees. + fn rotate_hue(&self, degrees: f32) -> Self { + let rotated_hue = (self.hue() + degrees).rem_euclid(360.); + self.with_hue(rotated_hue) + } +} + /// Trait with methods for asserting a colorspace is within bounds. /// /// During ordinary usage (e.g. reading images from disk, rendering images, picking colors for UI), colors should always be within their ordinary bounds (such as 0 to 1 for RGB colors). @@ -78,3 +96,21 @@ pub trait ClampColor: Sized { /// Are all the fields of this color in bounds? fn is_within_bounds(&self) -> bool; } + +#[cfg(test)] +mod tests { + use super::*; + use crate::Hsla; + + #[test] + fn test_rotate_hue() { + let hsla = Hsla::hsl(180.0, 1.0, 0.5); + assert_eq!(hsla.rotate_hue(90.0), Hsla::hsl(270.0, 1.0, 0.5)); + assert_eq!(hsla.rotate_hue(-90.0), Hsla::hsl(90.0, 1.0, 0.5)); + assert_eq!(hsla.rotate_hue(180.0), Hsla::hsl(0.0, 1.0, 0.5)); + assert_eq!(hsla.rotate_hue(-180.0), Hsla::hsl(0.0, 1.0, 0.5)); + assert_eq!(hsla.rotate_hue(0.0), hsla); + assert_eq!(hsla.rotate_hue(360.0), hsla); + assert_eq!(hsla.rotate_hue(-360.0), hsla); + } +} diff --git a/crates/bevy_color/src/hsla.rs b/crates/bevy_color/src/hsla.rs index b908afd035..37bc74fab5 100644 --- a/crates/bevy_color/src/hsla.rs +++ b/crates/bevy_color/src/hsla.rs @@ -1,5 +1,6 @@ use crate::{ - Alpha, ClampColor, Hsva, Hwba, Lcha, LinearRgba, Luminance, Mix, Srgba, StandardColor, Xyza, + Alpha, ClampColor, Hsva, Hue, Hwba, Lcha, LinearRgba, Luminance, Mix, Srgba, StandardColor, + Xyza, }; use bevy_reflect::prelude::*; use serde::{Deserialize, Serialize}; @@ -54,11 +55,6 @@ impl Hsla { Self::new(hue, saturation, lightness, 1.0) } - /// Return a copy of this color with the hue channel set to the given value. - pub const fn with_hue(self, hue: f32) -> Self { - Self { hue, ..self } - } - /// Return a copy of this color with the saturation channel set to the given value. pub const fn with_saturation(self, saturation: f32) -> Self { Self { saturation, ..self } @@ -143,6 +139,23 @@ impl Alpha for Hsla { } } +impl Hue for Hsla { + #[inline] + fn with_hue(&self, hue: f32) -> Self { + Self { hue, ..*self } + } + + #[inline] + fn hue(&self) -> f32 { + self.hue + } + + #[inline] + fn set_hue(&mut self, hue: f32) { + self.hue = hue; + } +} + impl Luminance for Hsla { #[inline] fn with_luminance(&self, lightness: f32) -> Self { diff --git a/crates/bevy_color/src/hsva.rs b/crates/bevy_color/src/hsva.rs index 8f45a476d6..dd959d266b 100644 --- a/crates/bevy_color/src/hsva.rs +++ b/crates/bevy_color/src/hsva.rs @@ -1,4 +1,4 @@ -use crate::{Alpha, ClampColor, Hwba, Lcha, LinearRgba, Srgba, StandardColor, Xyza}; +use crate::{Alpha, ClampColor, Hue, Hwba, Lcha, LinearRgba, Srgba, StandardColor, Xyza}; use bevy_reflect::prelude::*; use serde::{Deserialize, Serialize}; @@ -52,11 +52,6 @@ impl Hsva { Self::new(hue, saturation, value, 1.0) } - /// Return a copy of this color with the hue channel set to the given value. - pub const fn with_hue(self, hue: f32) -> Self { - Self { hue, ..self } - } - /// Return a copy of this color with the saturation channel set to the given value. pub const fn with_saturation(self, saturation: f32) -> Self { Self { saturation, ..self } @@ -91,6 +86,23 @@ impl Alpha for Hsva { } } +impl Hue for Hsva { + #[inline] + fn with_hue(&self, hue: f32) -> Self { + Self { hue, ..*self } + } + + #[inline] + fn hue(&self) -> f32 { + self.hue + } + + #[inline] + fn set_hue(&mut self, hue: f32) { + self.hue = hue; + } +} + impl ClampColor for Hsva { fn clamped(&self) -> Self { Self { diff --git a/crates/bevy_color/src/hwba.rs b/crates/bevy_color/src/hwba.rs index bfde45d8dc..a83473d06a 100644 --- a/crates/bevy_color/src/hwba.rs +++ b/crates/bevy_color/src/hwba.rs @@ -2,7 +2,7 @@ //! in [_HWB - A More Intuitive Hue-Based Color Model_] by _Smith et al_. //! //! [_HWB - A More Intuitive Hue-Based Color Model_]: https://web.archive.org/web/20240226005220/http://alvyray.com/Papers/CG/HWB_JGTv208.pdf -use crate::{Alpha, ClampColor, Lcha, LinearRgba, Srgba, StandardColor, Xyza}; +use crate::{Alpha, ClampColor, Hue, Lcha, LinearRgba, Srgba, StandardColor, Xyza}; use bevy_reflect::prelude::*; use serde::{Deserialize, Serialize}; @@ -56,11 +56,6 @@ impl Hwba { Self::new(hue, whiteness, blackness, 1.0) } - /// Return a copy of this color with the hue channel set to the given value. - pub const fn with_hue(self, hue: f32) -> Self { - Self { hue, ..self } - } - /// Return a copy of this color with the whiteness channel set to the given value. pub const fn with_whiteness(self, whiteness: f32) -> Self { Self { whiteness, ..self } @@ -95,6 +90,23 @@ impl Alpha for Hwba { } } +impl Hue for Hwba { + #[inline] + fn with_hue(&self, hue: f32) -> Self { + Self { hue, ..*self } + } + + #[inline] + fn hue(&self) -> f32 { + self.hue + } + + #[inline] + fn set_hue(&mut self, hue: f32) { + self.hue = hue; + } +} + impl ClampColor for Hwba { fn clamped(&self) -> Self { Self { diff --git a/crates/bevy_color/src/lcha.rs b/crates/bevy_color/src/lcha.rs index cceaa43eea..7ba7dd154d 100644 --- a/crates/bevy_color/src/lcha.rs +++ b/crates/bevy_color/src/lcha.rs @@ -1,4 +1,4 @@ -use crate::{Alpha, ClampColor, Laba, LinearRgba, Luminance, Mix, Srgba, StandardColor, Xyza}; +use crate::{Alpha, ClampColor, Hue, Laba, LinearRgba, Luminance, Mix, Srgba, StandardColor, Xyza}; use bevy_reflect::prelude::*; use serde::{Deserialize, Serialize}; @@ -56,11 +56,6 @@ impl Lcha { } } - /// Return a copy of this color with the hue channel set to the given value. - pub const fn with_hue(self, hue: f32) -> Self { - Self { hue, ..self } - } - /// Return a copy of this color with the chroma channel set to the given value. pub const fn with_chroma(self, chroma: f32) -> Self { Self { chroma, ..self } @@ -137,6 +132,23 @@ impl Alpha for Lcha { } } +impl Hue for Lcha { + #[inline] + fn with_hue(&self, hue: f32) -> Self { + Self { hue, ..*self } + } + + #[inline] + fn hue(&self) -> f32 { + self.hue + } + + #[inline] + fn set_hue(&mut self, hue: f32) { + self.hue = hue; + } +} + impl Luminance for Lcha { #[inline] fn with_luminance(&self, lightness: f32) -> Self { diff --git a/crates/bevy_color/src/oklcha.rs b/crates/bevy_color/src/oklcha.rs index 133794e256..5a8ccc4b7a 100644 --- a/crates/bevy_color/src/oklcha.rs +++ b/crates/bevy_color/src/oklcha.rs @@ -1,5 +1,5 @@ use crate::{ - color_difference::EuclideanDistance, Alpha, ClampColor, Hsla, Hsva, Hwba, Laba, Lcha, + color_difference::EuclideanDistance, Alpha, ClampColor, Hsla, Hsva, Hue, Hwba, Laba, Lcha, LinearRgba, Luminance, Mix, Oklaba, Srgba, StandardColor, Xyza, }; use bevy_reflect::prelude::*; @@ -65,11 +65,6 @@ impl Oklcha { Self { chroma, ..self } } - /// Return a copy of this color with the 'hue' channel set to the given value. - pub const fn with_hue(self, hue: f32) -> Self { - Self { hue, ..self } - } - /// Generate a deterministic but [quasi-randomly distributed](https://en.wikipedia.org/wiki/Low-discrepancy_sequence) /// color from a provided `index`. /// @@ -136,6 +131,23 @@ impl Alpha for Oklcha { } } +impl Hue for Oklcha { + #[inline] + fn with_hue(&self, hue: f32) -> Self { + Self { hue, ..*self } + } + + #[inline] + fn hue(&self) -> f32 { + self.hue + } + + #[inline] + fn set_hue(&mut self, hue: f32) { + self.hue = hue; + } +} + impl Luminance for Oklcha { #[inline] fn with_luminance(&self, lightness: f32) -> Self { diff --git a/examples/3d/animated_material.rs b/examples/3d/animated_material.rs index f588295ec3..ecf18cea04 100644 --- a/examples/3d/animated_material.rs +++ b/examples/3d/animated_material.rs @@ -30,14 +30,19 @@ fn setup( )); let cube = meshes.add(Cuboid::new(0.5, 0.5, 0.5)); + + const GOLDEN_ANGLE: f32 = 137.507_77; + + let mut hsla = Hsla::hsl(0.0, 1.0, 0.5); for x in -1..2 { for z in -1..2 { commands.spawn(PbrBundle { mesh: cube.clone(), - material: materials.add(Color::WHITE), + material: materials.add(Color::from(hsla)), transform: Transform::from_translation(Vec3::new(x as f32, 0.0, z as f32)), ..default() }); + hsla = hsla.rotate_hue(GOLDEN_ANGLE); } } } @@ -47,13 +52,11 @@ fn animate_materials( time: Res