This commit is contained in:
sergio
2026-05-12 18:55:29 +00:00
parent 6596c81271
commit 52acaabcf4
23 changed files with 1774 additions and 0 deletions
@@ -0,0 +1,130 @@
//! Paleta visual de GioSer: cuatro elementos + cosmos.
//!
//! Los colores se exponen en RGB lineal (rango `0..=1`). Para CSS,
//! convertir con `to_srgb_hex()`. Para shaders WebGL, pasar como `vec3`.
//! La separación lineal/sRGB es deliberada: el motor blending suma luz
//! en lineal y el ojo lee sRGB.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Rgb(pub f32, pub f32, pub f32);
impl Rgb {
pub const fn new(r: f32, g: f32, b: f32) -> Self {
Self(r, g, b)
}
pub const fn array(self) -> [f32; 3] {
[self.0, self.1, self.2]
}
/// Hex string sRGB `#rrggbb` en bytes ASCII (7 chars).
/// Hex en bytes evita allocs al pasar a CSS desde WASM.
pub fn to_srgb_hex(self) -> [u8; 7] {
fn encode(c: f32) -> u8 {
let g = if c <= 0.003_130_8 {
12.92 * c
} else {
1.055 * c.clamp(0.0, 1.0).powf(1.0 / 2.4) - 0.055
};
(g.clamp(0.0, 1.0) * 255.0 + 0.5) as u8
}
fn nib(x: u8) -> u8 {
if x < 10 {
b'0' + x
} else {
b'a' + (x - 10)
}
}
let r = encode(self.0);
let g = encode(self.1);
let b = encode(self.2);
[
b'#',
nib(r >> 4),
nib(r & 0x0f),
nib(g >> 4),
nib(g & 0x0f),
nib(b >> 4),
nib(b & 0x0f),
]
}
pub fn lerp(self, other: Rgb, t: f32) -> Rgb {
Rgb(
self.0 + (other.0 - self.0) * t,
self.1 + (other.1 - self.1) * t,
self.2 + (other.2 - self.2) * t,
)
}
}
/// Los cuatro elementos canónicos.
pub mod elements {
use super::Rgb;
/// Aire — azul-blanco luminoso. Software, IA, aspiración.
pub const AIRE: Rgb = Rgb(0.78, 0.86, 1.00);
/// Agua — cyan profundo. Espiritualidad aplicada.
pub const AGUA: Rgb = Rgb(0.28, 0.74, 0.95);
/// Fuego — ámbar/escarlata. Inspiración.
pub const FUEGO: Rgb = Rgb(0.98, 0.45, 0.18);
/// Tierra — ocre cálido. Cuerpo.
pub const TIERRA: Rgb = Rgb(0.82, 0.55, 0.28);
pub const ALL: [(&str, Rgb); 4] = [
("aire", AIRE),
("agua", AGUA),
("fuego", FUEGO),
("tierra", TIERRA),
];
}
/// Fondo cósmico + elementos arquitectónicos.
pub mod cosmos {
use super::Rgb;
/// Vacío profundo, casi negro con tinte violeta.
pub const VOID: Rgb = Rgb(0.030, 0.025, 0.060);
/// Nebulosa interior — violeta tenue.
pub const NEBULA_A: Rgb = Rgb(0.220, 0.130, 0.380);
/// Nebulosa exterior — azul profundo.
pub const NEBULA_B: Rgb = Rgb(0.080, 0.180, 0.320);
/// Núcleo solar central.
pub const SUN_CORE: Rgb = Rgb(1.000, 0.860, 0.520);
/// Líneas de la chacana — cyan helado.
pub const CHACANA_LINE: Rgb = Rgb(0.55, 0.92, 1.00);
/// Aro de fuego del logo — dorado-ámbar.
pub const CHACANA_RIM: Rgb = Rgb(0.95, 0.65, 0.32);
/// Polvo de estrellas.
pub const STARDUST: Rgb = Rgb(0.85, 0.88, 1.00);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hex_white() {
assert_eq!(&Rgb(1.0, 1.0, 1.0).to_srgb_hex(), b"#ffffff");
}
#[test]
fn hex_black() {
assert_eq!(&Rgb(0.0, 0.0, 0.0).to_srgb_hex(), b"#000000");
}
#[test]
fn lerp_midpoint() {
let m = Rgb(0.0, 0.0, 0.0).lerp(Rgb(1.0, 0.5, 0.0), 0.5);
assert_eq!(m, Rgb(0.5, 0.25, 0.0));
}
#[test]
fn linear_to_srgb_midgray_lifts_brightness() {
// 0.5 lineal codifica a sRGB ≈ 0.735 → byte ≈ 187 (0xbb..0xbc segun redondeo de powf).
// Bandgap [0xb8, 0xbf] permite drift de implementaciones de powf entre plataformas.
let h = Rgb(0.5, 0.5, 0.5).to_srgb_hex();
let lo = b"#b8b8b8";
let hi = b"#bfbfbf";
assert!(h.as_slice() >= lo.as_slice() && h.as_slice() <= hi.as_slice(),
"got {:?}", core::str::from_utf8(&h).unwrap_or(""));
}
}