#pragma once #include #include #include // Sistema d'il·luminació CPU. Treballa sobre el mateix canvas ARGB del // joc (320×240) que tot el pipeline de render: després que GameScene // haja pintat tilemap, paint layer, sombres i actors, una pasada final // modifica cada píxel del canvas en funció de la "llum de la sala" i // dels cercles emissors al voltant de certes entitats. // // Dos modes seleccionables per la sala (`lighting.mode` al YAML): // // SUBTRACTIVE (mode històric, default si no es declara mode): // out = src × max(room_light, max_emitter_intensity_at_pixel) // Els emissors "trenquen" una capa fosca multiplicativa. Tot el // trip cromàtic dels emissors s'ignora — només la intensitat // compta. Pensat per a sales fosques (cripta, pirámide). // // ADDITIVE (mode nou): // out = saturate( (src × room_light) + Σ (emitter_color × intensity) ) // El canvas s'enfosqueix opcionalment per `room_light` (atenuació // ambient) i els emissors hi sumen color cap a sobre. Les intensitats // dels anells es llegeixen com a "alfa" del color afegit (0..1). // Pensat per a sales clares on les antorxes aporten halo cromàtic // sense restar contrast — o per a sales lleument fosques (0.85, p.ex.) // on el halo de l'antorxa es veu càlid en lloc de "blanquinós". // // Forma del cercle de cada emissor (radial, 3 anells configurables al // fitxer data/config/lighting.yaml; veure `GradientConfig`): // 0..inner_ratio del radi → inner_intensity // inner_ratio..mid_ratio → mid_intensity // mid_ratio..1.0 → outer_intensity // // La corba viu en dos blocs separats al YAML perquè la mateixa escala // 0..1 té significats molt diferents per mode: // `gradient:` → corba del mode subtractive (1.0 = "no fosqueix") // `gradient_additive:` → corba del mode additive (1.0 = "afegeix // el color sencer i satura"). Es vol molt més // baixa per a no inundar la sala de color. // Cada mode llegeix la seva pròpia corba — no es comparteixen valors. // // Combinació entre emissors: // SUBTRACTIVE → per a cada píxel es pren el màxim de la intensitat // aportada per cada emissor que el solapa (no se sumen intensitats). // ADDITIVE → per a cada píxel se sumen totes les contribucions // (`color × intensity`), saturant a 255 per canal. Dues llums de // colors diferents que solapen produeixen la suma natural (orange + // blue → near-white) — comportament físic. namespace Lighting { // Mode de composició. La sala el selecciona via `lighting.mode: // additive | subtractive` (default subtractive si manca, retrocompat). enum class Mode : std::uint8_t { SUBTRACTIVE, ADDITIVE, }; // Color emès. Default blanc (sense modificació quan s'usa en mode // additive). En mode subtractive s'ignora — la pasada multiplicativa // només té un canal (intensitat). El YAML accepta `color: [r, g, b]` // a qualsevol nivell de la cadena de resolució (tipus, sala, spawn). struct Color { std::uint8_t r{255}; std::uint8_t g{255}; std::uint8_t b{255}; }; // Posició al canvas (en píxels, mateixa unitat que la resta del // motor) i radi efectiu de l'emissor. El centre és el del sprite // visible: és l'entitat qui sap on cau (veure `Entity::getLightCenter`). // `flicker` activa la modulació del radi per frame; `seed` és un // identificador estable per emissor (no canvia amb la posició) del // qual es deriva la fase del flicker — així els emissors mòbils // mantenen un patró propi que no "deriva" pel moviment. `color` // només té efecte en mode additive. struct Emitter { float cx{0.0F}; float cy{0.0F}; int radius{0}; int seed{0}; bool flicker{false}; Color color{}; }; // Forma del gradient. Compartida per tots els emissors per evitar // duplicar la corba a cada YAML d'entitat. Càrrega via loadConfig. struct GradientConfig { float inner_ratio{0.8F}; float mid_ratio{0.9F}; float inner_intensity{1.0F}; float mid_intensity{0.9F}; float outer_intensity{0.8F}; }; // Paràmetres del flicker per a emissors amb `flicker=true`. La // forma final del radi és la suma d'una sinusoide "respiració" // (wave_*) i una sinusoide secundària de freqüència més alta // (noise_*) que aporta la sensació d'inquietud. Les fases es // deriven de la posició de cada emissor — sense estat per emissor. struct FlickerConfig { float wave_amplitude_px{2.0F}; float wave_frequency_hz{3.0F}; float noise_amplitude_px{1.0F}; float noise_frequency_hz{7.0F}; }; // Llegeix `yaml_path` (data/config/lighting.yaml) i pobla la // configuració global. Qualsevol clau absent cau als Defaults::Lighting. // No es crida més d'una vegada en un run normal; tornar-la a cridar // simplement reemplaça la configuració activa. void loadConfig(const std::string& yaml_path); // Configuració activa (per a debug/inspectors). Retorna sempre la // corba subtractive — la corba additive viu en paral·lel al cpp i, // si algun dia es necessita inspeccionar des de fora, caldrà afegir // una variant `configAdditive()`. Inicialitzada amb els // Defaults::Lighting fins que loadConfig() es crida. [[nodiscard]] auto config() -> const GradientConfig&; // Aplica la pasada d'il·luminació al canvas in-place. `mode` // selecciona la composició (vegeu el comentari de la capçalera). // `room_light` és l'atenuació ambient de la sala (0..1, 1 = sense // atenuació): en subtractive és el "terra" de la intensitat fora // dels emissors; en additive és la multiplicació prèvia del canvas // abans de sumar els emissors (es pot deixar a 1.0 per "només tint", // o baixar per combinar oscuriment ambient + halo cromàtic). El // span d'emissors pot ser buit. La funció no fa cap allocació si // es crida amb la mateixa mida de canvas múltiples cops: manté un // buffer intern reutilitzable (només l'usa el mode subtractive). void apply(std::uint32_t* canvas, int width, int height, Mode mode, float room_light, std::span emitters); } // namespace Lighting