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>
This commit is contained in:
Zachary Harrold 2024-02-29 08:45:48 +11:00 committed by GitHub
parent 7826313405
commit 043041f3aa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 106 additions and 12 deletions

View File

@ -66,6 +66,35 @@ impl Hsla {
pub const fn with_lightness(self, lightness: f32) -> Self {
Self { lightness, ..self }
}
/// Generate a deterministic but [quasi-randomly distributed](https://en.wikipedia.org/wiki/Low-discrepancy_sequence)
/// color from a provided `index`.
///
/// This can be helpful for generating debug colors.
///
/// # Examples
///
/// ```rust
/// # use bevy_color::Hsla;
/// // Unique color for an entity
/// # let entity_index = 123;
/// // let entity_index = entity.index();
/// let color = Hsla::sequential_dispersed(entity_index);
///
/// // Palette with 5 distinct hues
/// let palette = (0..5).map(Hsla::sequential_dispersed).collect::<Vec<_>>();
/// ```
pub fn sequential_dispersed(index: u32) -> Self {
const FRAC_U32MAX_GOLDEN_RATIO: u32 = 2654435769; // (u32::MAX / Φ) rounded up
const RATIO_360: f32 = 360.0 / u32::MAX as f32;
// from https://extremelearning.com.au/unreasonable-effectiveness-of-quasirandom-sequences/
//
// Map a sequence of integers (eg: 154, 155, 156, 157, 158) into the [0.0..1.0] range,
// so that the closer the numbers are, the larger the difference of their image.
let hue = index.wrapping_mul(FRAC_U32MAX_GOLDEN_RATIO) as f32 * RATIO_360;
Self::hsl(hue, 1., 0.5)
}
}
impl Default for Hsla {
@ -306,4 +335,21 @@ mod tests {
assert_approx_eq!(hsla2.mix(&hsla0, 0.5).hue, 0., 0.001);
assert_approx_eq!(hsla2.mix(&hsla0, 0.75).hue, 5., 0.001);
}
#[test]
fn test_from_index() {
let references = [
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),
];
for (index, reference) in references.into_iter().enumerate() {
let color = Hsla::sequential_dispersed(index as u32);
assert_approx_eq!(color.hue, reference.hue, 0.001);
}
}
}

View File

@ -70,6 +70,35 @@ impl Lcha {
pub const fn with_lightness(self, lightness: f32) -> Self {
Self { lightness, ..self }
}
/// Generate a deterministic but [quasi-randomly distributed](https://en.wikipedia.org/wiki/Low-discrepancy_sequence)
/// color from a provided `index`.
///
/// This can be helpful for generating debug colors.
///
/// # Examples
///
/// ```rust
/// # use bevy_color::Lcha;
/// // Unique color for an entity
/// # let entity_index = 123;
/// // let entity_index = entity.index();
/// let color = Lcha::sequential_dispersed(entity_index);
///
/// // Palette with 5 distinct hues
/// let palette = (0..5).map(Lcha::sequential_dispersed).collect::<Vec<_>>();
/// ```
pub fn sequential_dispersed(index: u32) -> Self {
const FRAC_U32MAX_GOLDEN_RATIO: u32 = 2654435769; // (u32::MAX / Φ) rounded up
const RATIO_360: f32 = 360.0 / u32::MAX as f32;
// from https://extremelearning.com.au/unreasonable-effectiveness-of-quasirandom-sequences/
//
// Map a sequence of integers (eg: 154, 155, 156, 157, 158) into the [0.0..1.0] range,
// so that the closer the numbers are, the larger the difference of their image.
let hue = index.wrapping_mul(FRAC_U32MAX_GOLDEN_RATIO) as f32 * RATIO_360;
Self::lch(0.75, 0.35, hue)
}
}
impl Default for Lcha {

View File

@ -69,6 +69,35 @@ impl Oklcha {
pub const fn with_h(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`.
///
/// This can be helpful for generating debug colors.
///
/// # Examples
///
/// ```rust
/// # use bevy_color::Oklcha;
/// // Unique color for an entity
/// # let entity_index = 123;
/// // let entity_index = entity.index();
/// let color = Oklcha::sequential_dispersed(entity_index);
///
/// // Palette with 5 distinct hues
/// let palette = (0..5).map(Oklcha::sequential_dispersed).collect::<Vec<_>>();
/// ```
pub fn sequential_dispersed(index: u32) -> Self {
const FRAC_U32MAX_GOLDEN_RATIO: u32 = 2654435769; // (u32::MAX / Φ) rounded up
const RATIO_360: f32 = 360.0 / u32::MAX as f32;
// from https://extremelearning.com.au/unreasonable-effectiveness-of-quasirandom-sequences/
//
// Map a sequence of integers (eg: 154, 155, 156, 157, 158) into the [0.0..1.0] range,
// so that the closer the numbers are, the larger the difference of their image.
let hue = index.wrapping_mul(FRAC_U32MAX_GOLDEN_RATIO) as f32 * RATIO_360;
Self::lch(0.75, 0.1, hue)
}
}
impl Default for Oklcha {

View File

@ -3,6 +3,7 @@
use crate as bevy_gizmos;
use bevy_app::{Plugin, PostUpdate};
use bevy_color::Oklcha;
use bevy_ecs::{
component::Component,
entity::Entity,
@ -97,18 +98,7 @@ fn draw_all_aabbs(
}
fn color_from_entity(entity: Entity) -> LegacyColor {
let index = entity.index();
// from https://extremelearning.com.au/unreasonable-effectiveness-of-quasirandom-sequences/
//
// See https://en.wikipedia.org/wiki/Low-discrepancy_sequence
// Map a sequence of integers (eg: 154, 155, 156, 157, 158) into the [0.0..1.0] range,
// so that the closer the numbers are, the larger the difference of their image.
const FRAC_U32MAX_GOLDEN_RATIO: u32 = 2654435769; // (u32::MAX / Φ) rounded up
const RATIO_360: f32 = 360.0 / u32::MAX as f32;
let hue = index.wrapping_mul(FRAC_U32MAX_GOLDEN_RATIO) as f32 * RATIO_360;
LegacyColor::hsl(hue, 1., 0.5)
Oklcha::sequential_dispersed(entity.index()).into()
}
fn aabb_transform(aabb: Aabb, transform: GlobalTransform) -> GlobalTransform {