Skip to content

Commit

Permalink
bevy_color: Add sequence_dispersed to Hsla, Lcha, Oklcha (#…
Browse files Browse the repository at this point in the history
…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 <[email protected]>
  • Loading branch information
bushrat011899 and alice-i-cecile authored Feb 28, 2024
1 parent 7826313 commit 043041f
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 12 deletions.
46 changes: 46 additions & 0 deletions crates/bevy_color/src/hsla.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
}
}
}
29 changes: 29 additions & 0 deletions crates/bevy_color/src/lcha.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
29 changes: 29 additions & 0 deletions crates/bevy_color/src/oklcha.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
14 changes: 2 additions & 12 deletions crates/bevy_gizmos/src/aabb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit 043041f

Please sign in to comment.