Skip to content

Commit 043041f

Browse files
bevy_color: Add sequence_dispersed to Hsla, Lcha, Oklcha (#12173)
# Objective - Fixes #12170 ## Solution - Moved the existing `color_from_entity` internals into `Hsla::sequence_dispersed` which generates a randomly distributed but deterministic color sequence based. - Replicated the method for `Lcha` and `Oklcha` as well. ## Examples ### Getting a few colours for a quick palette ```rust let palette = Hsla::sequence_dispersed().take(5).collect::<Vec<_>>(); /*[ Hsla::hsl(0.0, 1., 0.5), Hsla::hsl(222.49225, 1., 0.5), Hsla::hsl(84.984474, 1., 0.5), Hsla::hsl(307.4767, 1., 0.5), Hsla::hsl(169.96895, 1., 0.5), ]*/ ``` ### Getting a colour from an `Entity` ```rust let color = Oklcha::sequence_dispersed().nth(entity.index() as u32).unwrap(); ``` ## Notes This was previously a private function exclusively for `Entity` types. I've decided it should instead be public and operate on a `u32` directly, since this function may have broader uses for debugging purposes. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
1 parent 7826313 commit 043041f

File tree

4 files changed

+106
-12
lines changed

4 files changed

+106
-12
lines changed

crates/bevy_color/src/hsla.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,35 @@ impl Hsla {
6666
pub const fn with_lightness(self, lightness: f32) -> Self {
6767
Self { lightness, ..self }
6868
}
69+
70+
/// Generate a deterministic but [quasi-randomly distributed](https://en.wikipedia.org/wiki/Low-discrepancy_sequence)
71+
/// color from a provided `index`.
72+
///
73+
/// This can be helpful for generating debug colors.
74+
///
75+
/// # Examples
76+
///
77+
/// ```rust
78+
/// # use bevy_color::Hsla;
79+
/// // Unique color for an entity
80+
/// # let entity_index = 123;
81+
/// // let entity_index = entity.index();
82+
/// let color = Hsla::sequential_dispersed(entity_index);
83+
///
84+
/// // Palette with 5 distinct hues
85+
/// let palette = (0..5).map(Hsla::sequential_dispersed).collect::<Vec<_>>();
86+
/// ```
87+
pub fn sequential_dispersed(index: u32) -> Self {
88+
const FRAC_U32MAX_GOLDEN_RATIO: u32 = 2654435769; // (u32::MAX / Φ) rounded up
89+
const RATIO_360: f32 = 360.0 / u32::MAX as f32;
90+
91+
// from https://extremelearning.com.au/unreasonable-effectiveness-of-quasirandom-sequences/
92+
//
93+
// Map a sequence of integers (eg: 154, 155, 156, 157, 158) into the [0.0..1.0] range,
94+
// so that the closer the numbers are, the larger the difference of their image.
95+
let hue = index.wrapping_mul(FRAC_U32MAX_GOLDEN_RATIO) as f32 * RATIO_360;
96+
Self::hsl(hue, 1., 0.5)
97+
}
6998
}
7099

71100
impl Default for Hsla {
@@ -306,4 +335,21 @@ mod tests {
306335
assert_approx_eq!(hsla2.mix(&hsla0, 0.5).hue, 0., 0.001);
307336
assert_approx_eq!(hsla2.mix(&hsla0, 0.75).hue, 5., 0.001);
308337
}
338+
339+
#[test]
340+
fn test_from_index() {
341+
let references = [
342+
Hsla::hsl(0.0, 1., 0.5),
343+
Hsla::hsl(222.49225, 1., 0.5),
344+
Hsla::hsl(84.984474, 1., 0.5),
345+
Hsla::hsl(307.4767, 1., 0.5),
346+
Hsla::hsl(169.96895, 1., 0.5),
347+
];
348+
349+
for (index, reference) in references.into_iter().enumerate() {
350+
let color = Hsla::sequential_dispersed(index as u32);
351+
352+
assert_approx_eq!(color.hue, reference.hue, 0.001);
353+
}
354+
}
309355
}

crates/bevy_color/src/lcha.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,35 @@ impl Lcha {
7070
pub const fn with_lightness(self, lightness: f32) -> Self {
7171
Self { lightness, ..self }
7272
}
73+
74+
/// Generate a deterministic but [quasi-randomly distributed](https://en.wikipedia.org/wiki/Low-discrepancy_sequence)
75+
/// color from a provided `index`.
76+
///
77+
/// This can be helpful for generating debug colors.
78+
///
79+
/// # Examples
80+
///
81+
/// ```rust
82+
/// # use bevy_color::Lcha;
83+
/// // Unique color for an entity
84+
/// # let entity_index = 123;
85+
/// // let entity_index = entity.index();
86+
/// let color = Lcha::sequential_dispersed(entity_index);
87+
///
88+
/// // Palette with 5 distinct hues
89+
/// let palette = (0..5).map(Lcha::sequential_dispersed).collect::<Vec<_>>();
90+
/// ```
91+
pub fn sequential_dispersed(index: u32) -> Self {
92+
const FRAC_U32MAX_GOLDEN_RATIO: u32 = 2654435769; // (u32::MAX / Φ) rounded up
93+
const RATIO_360: f32 = 360.0 / u32::MAX as f32;
94+
95+
// from https://extremelearning.com.au/unreasonable-effectiveness-of-quasirandom-sequences/
96+
//
97+
// Map a sequence of integers (eg: 154, 155, 156, 157, 158) into the [0.0..1.0] range,
98+
// so that the closer the numbers are, the larger the difference of their image.
99+
let hue = index.wrapping_mul(FRAC_U32MAX_GOLDEN_RATIO) as f32 * RATIO_360;
100+
Self::lch(0.75, 0.35, hue)
101+
}
73102
}
74103

75104
impl Default for Lcha {

crates/bevy_color/src/oklcha.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,35 @@ impl Oklcha {
6969
pub const fn with_h(self, hue: f32) -> Self {
7070
Self { hue, ..self }
7171
}
72+
73+
/// Generate a deterministic but [quasi-randomly distributed](https://en.wikipedia.org/wiki/Low-discrepancy_sequence)
74+
/// color from a provided `index`.
75+
///
76+
/// This can be helpful for generating debug colors.
77+
///
78+
/// # Examples
79+
///
80+
/// ```rust
81+
/// # use bevy_color::Oklcha;
82+
/// // Unique color for an entity
83+
/// # let entity_index = 123;
84+
/// // let entity_index = entity.index();
85+
/// let color = Oklcha::sequential_dispersed(entity_index);
86+
///
87+
/// // Palette with 5 distinct hues
88+
/// let palette = (0..5).map(Oklcha::sequential_dispersed).collect::<Vec<_>>();
89+
/// ```
90+
pub fn sequential_dispersed(index: u32) -> Self {
91+
const FRAC_U32MAX_GOLDEN_RATIO: u32 = 2654435769; // (u32::MAX / Φ) rounded up
92+
const RATIO_360: f32 = 360.0 / u32::MAX as f32;
93+
94+
// from https://extremelearning.com.au/unreasonable-effectiveness-of-quasirandom-sequences/
95+
//
96+
// Map a sequence of integers (eg: 154, 155, 156, 157, 158) into the [0.0..1.0] range,
97+
// so that the closer the numbers are, the larger the difference of their image.
98+
let hue = index.wrapping_mul(FRAC_U32MAX_GOLDEN_RATIO) as f32 * RATIO_360;
99+
Self::lch(0.75, 0.1, hue)
100+
}
72101
}
73102

74103
impl Default for Oklcha {

crates/bevy_gizmos/src/aabb.rs

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use crate as bevy_gizmos;
44

55
use bevy_app::{Plugin, PostUpdate};
6+
use bevy_color::Oklcha;
67
use bevy_ecs::{
78
component::Component,
89
entity::Entity,
@@ -97,18 +98,7 @@ fn draw_all_aabbs(
9798
}
9899

99100
fn color_from_entity(entity: Entity) -> LegacyColor {
100-
let index = entity.index();
101-
102-
// from https://extremelearning.com.au/unreasonable-effectiveness-of-quasirandom-sequences/
103-
//
104-
// See https://en.wikipedia.org/wiki/Low-discrepancy_sequence
105-
// Map a sequence of integers (eg: 154, 155, 156, 157, 158) into the [0.0..1.0] range,
106-
// so that the closer the numbers are, the larger the difference of their image.
107-
const FRAC_U32MAX_GOLDEN_RATIO: u32 = 2654435769; // (u32::MAX / Φ) rounded up
108-
const RATIO_360: f32 = 360.0 / u32::MAX as f32;
109-
let hue = index.wrapping_mul(FRAC_U32MAX_GOLDEN_RATIO) as f32 * RATIO_360;
110-
111-
LegacyColor::hsl(hue, 1., 0.5)
101+
Oklcha::sequential_dispersed(entity.index()).into()
112102
}
113103

114104
fn aabb_transform(aabb: Aabb, transform: GlobalTransform) -> GlobalTransform {

0 commit comments

Comments
 (0)