mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-22 11:08:10 +00:00
feat: improve team colors with LCH color space (#3146)
## Summary Refactor `generateTeamColors()` to use LCH (Lightness-Chroma-Hue) color space instead of HSL for perceptually uniform team color variations. ## Changes - **`Colors.ts`**: Rewrite `generateTeamColors()` to use LCH color space - Golden angle hue distribution clamped to ±12° to preserve team identity - Chroma oscillates ±10% around the base to add variety without washing out - Lightness alternates ±18 around the base to keep teammates recognizable ## Why LCH? LCH is a perceptually uniform color space, meaning equal numeric differences correspond to equal perceived differences. This produces team color variations that look more consistent and distinguishable compared to HSL-based generation. ## Notes - The "skip ally attack confirmation" feature that was previously in this PR has been split into a separate PR as requested by @evanpelle.
This commit is contained in:
@@ -24,20 +24,27 @@ export const greenTeamColors: Colord[] = generateTeamColors(green);
|
||||
export const botTeamColors: Colord[] = [botColor];
|
||||
|
||||
function generateTeamColors(baseColor: Colord): Colord[] {
|
||||
const hsl = baseColor.toHsl();
|
||||
const lch = baseColor.toLch();
|
||||
const colorCount = 64;
|
||||
const goldenAngle = 137.508;
|
||||
|
||||
return Array.from({ length: colorCount }, (_, index) => {
|
||||
const progression = index / (colorCount - 1);
|
||||
if (index === 0) return baseColor;
|
||||
|
||||
const saturation = hsl.s * (1.0 - 0.3 * progression);
|
||||
const lightness = Math.min(100, hsl.l + progression * 30);
|
||||
// Spread hues evenly across ±12° band using golden angle within that range
|
||||
const hueShift = ((index * goldenAngle) % 24) - 12;
|
||||
const h = (lch.h + hueShift + 360) % 360;
|
||||
|
||||
return colord({
|
||||
h: hsl.h,
|
||||
s: saturation,
|
||||
l: lightness,
|
||||
});
|
||||
// Chroma oscillates ±10% around the base to add variety without washing out
|
||||
const chromaFactor = 1.0 + 0.1 * Math.sin(index * 0.7);
|
||||
const c = Math.max(10, Math.min(130, lch.c * chromaFactor));
|
||||
|
||||
// Lightness alternates above/below the base using golden angle spacing
|
||||
// Tighter range (±18) keeps teammates recognizable as the same team
|
||||
const lightOffset = 18 * Math.sin(index * goldenAngle * (Math.PI / 180));
|
||||
const l = Math.max(25, Math.min(80, lch.l + lightOffset));
|
||||
|
||||
return colord({ l, c, h });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user