Skip to content

Commit

Permalink
Add hue traits (bevyengine#12399)
Browse files Browse the repository at this point in the history
# Objective

Fixes bevyengine#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 <[email protected]>
Co-authored-by: Alice Cecile <[email protected]>
  • Loading branch information
3 people authored Mar 22, 2024
1 parent 887bc27 commit 0950348
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 37 deletions.
36 changes: 36 additions & 0 deletions crates/bevy_color/src/color_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand All @@ -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);
}
}
25 changes: 19 additions & 6 deletions crates/bevy_color/src/hsla.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -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 }
Expand Down Expand Up @@ -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 {
Expand Down
24 changes: 18 additions & 6 deletions crates/bevy_color/src/hsva.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand Down Expand Up @@ -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 }
Expand Down Expand Up @@ -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 {
Expand Down
24 changes: 18 additions & 6 deletions crates/bevy_color/src/hwba.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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 }
Expand Down Expand Up @@ -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 {
Expand Down
24 changes: 18 additions & 6 deletions crates/bevy_color/src/lcha.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand Down Expand Up @@ -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 }
Expand Down Expand Up @@ -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 {
Expand Down
24 changes: 18 additions & 6 deletions crates/bevy_color/src/oklcha.rs
Original file line number Diff line number Diff line change
@@ -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::*;
Expand Down Expand Up @@ -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`.
///
Expand Down Expand Up @@ -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 {
Expand Down
17 changes: 10 additions & 7 deletions examples/3d/animated_material.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
Expand All @@ -47,13 +52,11 @@ fn animate_materials(
time: Res<Time>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
for (i, material_handle) in material_handles.iter().enumerate() {
for material_handle in material_handles.iter() {
if let Some(material) = materials.get_mut(material_handle) {
material.base_color = Color::hsl(
((i as f32 * 2.345 + time.elapsed_seconds_wrapped()) * 100.0) % 360.0,
1.0,
0.5,
);
if let Color::Hsla(ref mut hsla) = material.base_color {
*hsla = hsla.rotate_hue(time.delta_seconds() * 100.0);
}
}
}
}

0 comments on commit 0950348

Please sign in to comment.