151 lines
7.3 KiB
C++
151 lines
7.3 KiB
C++
#pragma once
|
||
|
||
#include <cstdint>
|
||
#include <span>
|
||
#include <string>
|
||
|
||
// 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};
|
||
};
|
||
|
||
// 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 seed estable de cada emissor — sense estat per
|
||
// emissor. Les amplituds són **ràtios** sobre el radi base (p.ex.
|
||
// 0.05 = 5% del radi): així una torxa de radi 40 i una de radi 24
|
||
// tenen el mateix percentatge d'oscil·lació visual, no els mateixos
|
||
// píxels (que es notarien més en la petita).
|
||
struct FlickerConfig {
|
||
float wave_amplitude_ratio{0.05F};
|
||
float wave_frequency_hz{3.0F};
|
||
float noise_amplitude_ratio{0.025F};
|
||
float noise_frequency_hz{7.0F};
|
||
};
|
||
|
||
// 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{};
|
||
// Paràmetres del flicker resolts per a aquest emissor (combinació
|
||
// global + override del tipus/sala/spawn). Només té sentit si
|
||
// `flicker == true`; en cas contrari s'ignoren. Per defecte
|
||
// s'inicialitza amb els defaults compilats — el caller (game_scene)
|
||
// hi posa el resolt correcte.
|
||
FlickerConfig flicker_params{};
|
||
};
|
||
|
||
// 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&;
|
||
|
||
// Defaults globals del flicker (data/config/lighting.yaml, bloc
|
||
// `flicker:`). Els callers del construct path d'entitats el llegeixen
|
||
// per inicialitzar la `FlickerConfig` de cada emissor, i aplicar a
|
||
// sobre els overrides per tipus/sala/spawn.
|
||
[[nodiscard]] auto flickerConfig() -> const FlickerConfig&;
|
||
|
||
// 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<const Emitter> emitters);
|
||
|
||
} // namespace Lighting
|