feat(lighting): mode additive amb color per emissor
This commit is contained in:
1 parent
24609d6c5c
commit
771ff1f6cd
10 files changed
+342
-61
No files matched your search
@@ -15,6 +15,21 @@ tileset: data/tilesets/pyramid_1.gif
|
|||||||
tile_size: 16
|
tile_size: 16
|
||||||
origin: [-8, 0]
|
origin: [-8, 0]
|
||||||
|
|
||||||
|
# Banc de proves del mode `additive` d'il·luminació. La sala queda
|
||||||
|
# lleugerament fosca (level 0.9) i els emissors hi sumen color per
|
||||||
|
# damunt — antorxes càlides, jugador en blanc-blau fred, mòmies amb
|
||||||
|
# halo verd-fantasma. En mode additive el `color` de cada emissor entra
|
||||||
|
# en joc (en subtractive s'ignora). Veure docs al header de
|
||||||
|
# `core/rendering/lighting.hpp` per a la fórmula i el comportament al
|
||||||
|
# solape (suma → tendència al blanc, físicament correcte).
|
||||||
|
lighting:
|
||||||
|
mode: additive
|
||||||
|
level: 0.9
|
||||||
|
emitters:
|
||||||
|
torches: { color: [255, 170, 60] }
|
||||||
|
players: { color: [180, 220, 255] }
|
||||||
|
mummies: { color: [120, 220, 140] }
|
||||||
|
|
||||||
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl
|
||||||
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
# pintable. Un índex per jugador perquè a multijugador local el recorregut
|
||||||
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
# es distingeix a simple vista. Provisional fins que l'art del pintat
|
||||||
|
|||||||
@@ -133,9 +133,9 @@ namespace Lighting {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pasada final: pixel = pixel * lum / 255 per canal. El canal alpha
|
// Pasada final del mode subtractive: pixel = pixel * lum / 255
|
||||||
// es preserva (la resta del pipeline assumeix opacitat total al
|
// per canal. El canal alpha es preserva (la resta del pipeline
|
||||||
// canvas; veure Rendering::OPAQUE_ALPHA).
|
// assumeix opacitat total al canvas; veure Rendering::OPAQUE_ALPHA).
|
||||||
void modulateCanvas(std::uint32_t* canvas, const std::uint8_t* lum, int width, int height) {
|
void modulateCanvas(std::uint32_t* canvas, const std::uint8_t* lum, int width, int height) {
|
||||||
for (int y = 0; y < height; ++y) {
|
for (int y = 0; y < height; ++y) {
|
||||||
std::uint32_t* px = canvas + (static_cast<std::ptrdiff_t>(y) * width);
|
std::uint32_t* px = canvas + (static_cast<std::ptrdiff_t>(y) * width);
|
||||||
@@ -158,6 +158,77 @@ namespace Lighting {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Atenuació ambient del mode additive: aplica `pixel × level` per
|
||||||
|
// canvas abans del bucle d'emissors. Si `level == 255` (sala a
|
||||||
|
// 1.0) salta com a no-op — el cas comú quan només volem el tint
|
||||||
|
// dels emissors sense oscurir la sala.
|
||||||
|
void darkenCanvas(std::uint32_t* canvas, int width, int height, std::uint8_t level) {
|
||||||
|
if (level == 255) { return; }
|
||||||
|
for (int y = 0; y < height; ++y) {
|
||||||
|
std::uint32_t* px = canvas + (static_cast<std::ptrdiff_t>(y) * width);
|
||||||
|
for (int x = 0; x < width; ++x) {
|
||||||
|
const std::uint32_t SRC = px[x];
|
||||||
|
const std::uint8_t SR = (SRC >> 16) & 0xFF;
|
||||||
|
const std::uint8_t SG = (SRC >> 8) & 0xFF;
|
||||||
|
const std::uint8_t SB = SRC & 0xFF;
|
||||||
|
const auto R = static_cast<std::uint8_t>((SR * level) / 255);
|
||||||
|
const auto G = static_cast<std::uint8_t>((SG * level) / 255);
|
||||||
|
const auto B = static_cast<std::uint8_t>((SB * level) / 255);
|
||||||
|
px[x] = (SRC & 0xFF000000U) |
|
||||||
|
(static_cast<std::uint32_t>(R) << 16) |
|
||||||
|
(static_cast<std::uint32_t>(G) << 8) |
|
||||||
|
static_cast<std::uint32_t>(B);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pasada del mode additive per a un emissor: suma `color × intensity`
|
||||||
|
// a cada píxel cobert pel cercle, saturant a 255 per canal. A
|
||||||
|
// diferència del subtractive, NO usa buffer intermig (no cal max())
|
||||||
|
// perquè la suma és commutativa; emissors solapats acumulen
|
||||||
|
// contribucions de manera natural. Si dues llums de colors diferents
|
||||||
|
// es solapen, el resultat tendeix al blanc en la zona de coincidència
|
||||||
|
// (suma física): el solape s'aclareix més que un sol emissor.
|
||||||
|
void rasterizeEmitterAdditive(const Emitter& e, const EmitterRings& rings, std::uint32_t* canvas, int width, int height) {
|
||||||
|
const int R = e.radius;
|
||||||
|
const int CX = static_cast<int>(e.cx);
|
||||||
|
const int CY = static_cast<int>(e.cy);
|
||||||
|
const int X0 = std::max(0, CX - R);
|
||||||
|
const int Y0 = std::max(0, CY - R);
|
||||||
|
const int X1 = std::min(width - 1, CX + R);
|
||||||
|
const int Y1 = std::min(height - 1, CY + R);
|
||||||
|
for (int y = Y0; y <= Y1; ++y) {
|
||||||
|
const int DY = y - CY;
|
||||||
|
const int DY_SQ = DY * DY;
|
||||||
|
if (DY_SQ > rings.radius_sq) { continue; }
|
||||||
|
std::uint32_t* row = canvas + (static_cast<std::ptrdiff_t>(y) * width);
|
||||||
|
for (int x = X0; x <= X1; ++x) {
|
||||||
|
const int DX = x - CX;
|
||||||
|
const int DIST_SQ = (DX * DX) + DY_SQ;
|
||||||
|
if (DIST_SQ > rings.radius_sq) { continue; }
|
||||||
|
std::uint8_t intensity = rings.outer;
|
||||||
|
if (DIST_SQ <= rings.inner_sq) {
|
||||||
|
intensity = rings.inner;
|
||||||
|
} else if (DIST_SQ <= rings.mid_sq) {
|
||||||
|
intensity = rings.mid;
|
||||||
|
}
|
||||||
|
// contrib_channel = emitter_color_channel × intensity / 255
|
||||||
|
// (intensity ja és 0..255 quantitzat a `toByte`).
|
||||||
|
const int CR = (e.color.r * intensity) / 255;
|
||||||
|
const int CG = (e.color.g * intensity) / 255;
|
||||||
|
const int CB = (e.color.b * intensity) / 255;
|
||||||
|
const std::uint32_t SRC = row[x];
|
||||||
|
const int SR = static_cast<int>((SRC >> 16) & 0xFF);
|
||||||
|
const int SG = static_cast<int>((SRC >> 8) & 0xFF);
|
||||||
|
const int SB = static_cast<int>(SRC & 0xFF);
|
||||||
|
const auto NR = static_cast<std::uint32_t>(std::min(255, SR + CR));
|
||||||
|
const auto NG = static_cast<std::uint32_t>(std::min(255, SG + CG));
|
||||||
|
const auto NB = static_cast<std::uint32_t>(std::min(255, SB + CB));
|
||||||
|
row[x] = (SRC & 0xFF000000U) | (NR << 16) | (NG << 8) | NB;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
auto config() -> const GradientConfig& {
|
auto config() -> const GradientConfig& {
|
||||||
@@ -196,39 +267,71 @@ namespace Lighting {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void apply(std::uint32_t* canvas, int width, int height, float room_light, std::span<const Emitter> emitters) {
|
namespace {
|
||||||
if (canvas == nullptr || width <= 0 || height <= 0) { return; }
|
|
||||||
|
|
||||||
// Atajo: si la sala està totalment il·luminada i no hi ha
|
// Helper: emissor "patxat" amb el radi efectiu (post-flicker) per
|
||||||
// emissors, la pasada seria un `pixel * 1.0` global — no cal
|
// a aquest frame. Mantenim l'`Emitter` del caller intacte i la
|
||||||
// tocar res. És el camí curt habitual mentre no posem
|
// resta de camps (color, seed, ...) propagats.
|
||||||
// `light_level` a les sales.
|
auto withEffectiveRadius(const Emitter& e, int r_eff) -> Emitter {
|
||||||
const std::uint8_t ROOM = toByte(room_light);
|
return Emitter{.cx = e.cx, .cy = e.cy, .radius = r_eff, .seed = e.seed, .flicker = e.flicker, .color = e.color};
|
||||||
if (ROOM == 255 && emitters.empty()) { return; }
|
}
|
||||||
|
|
||||||
|
// Camí del mode subtractive (històric): omple un buffer de
|
||||||
|
// luminància amb `max(room, emitter_intensity)` i finalment
|
||||||
|
// multiplica el canvas per ell. Color de l'emissor ignorat.
|
||||||
|
void applySubtractive(std::uint32_t* canvas, int width, int height, std::uint8_t room, std::span<const Emitter> emitters) {
|
||||||
auto& lum = luminanceBuffer();
|
auto& lum = luminanceBuffer();
|
||||||
const std::size_t TOTAL = static_cast<std::size_t>(width) * static_cast<std::size_t>(height);
|
const std::size_t TOTAL = static_cast<std::size_t>(width) * static_cast<std::size_t>(height);
|
||||||
if (lum.size() != TOTAL) { lum.resize(TOTAL); }
|
if (lum.size() != TOTAL) { lum.resize(TOTAL); }
|
||||||
std::ranges::fill(lum, ROOM);
|
std::ranges::fill(lum, room);
|
||||||
|
|
||||||
const auto& cfg = activeConfig();
|
const auto& cfg = activeConfig();
|
||||||
const auto& fc = activeFlicker();
|
const auto& fc = activeFlicker();
|
||||||
const float T_S = static_cast<float>(SDL_GetTicks()) / 1000.0F;
|
const float T_S = static_cast<float>(SDL_GetTicks()) / 1000.0F;
|
||||||
for (const auto& e : emitters) {
|
for (const auto& e : emitters) {
|
||||||
if (e.radius <= 0) { continue; }
|
if (e.radius <= 0) { continue; }
|
||||||
const int R_EFF = e.flicker
|
const int R_EFF = e.flicker ? computeFlickerRadius(e.radius, e.seed, T_S, fc) : e.radius;
|
||||||
? computeFlickerRadius(e.radius, e.seed, T_S, fc)
|
|
||||||
: e.radius;
|
|
||||||
const auto RINGS = computeRings(R_EFF, cfg);
|
const auto RINGS = computeRings(R_EFF, cfg);
|
||||||
// El rasteritzador llegeix el radi de l'`Emitter` per al
|
const Emitter EFF = withEffectiveRadius(e, R_EFF);
|
||||||
// càlcul de la bounding box, així que li passem un emissor
|
|
||||||
// "patxat" amb el radi efectiu d'aquest frame. Mantenim el
|
|
||||||
// valor original al `Emitter` del caller intacte.
|
|
||||||
const Emitter EFF{.cx = e.cx, .cy = e.cy, .radius = R_EFF, .seed = e.seed, .flicker = e.flicker};
|
|
||||||
rasterizeEmitter(EFF, RINGS, lum.data(), width, height);
|
rasterizeEmitter(EFF, RINGS, lum.data(), width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
modulateCanvas(canvas, lum.data(), width, height);
|
modulateCanvas(canvas, lum.data(), width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Camí del mode additive: atenua el canvas per `room` (si <255)
|
||||||
|
// i després suma `color × intensity` per emissor directament al
|
||||||
|
// canvas, sense buffer intermig.
|
||||||
|
void applyAdditive(std::uint32_t* canvas, int width, int height, std::uint8_t room, std::span<const Emitter> emitters) {
|
||||||
|
darkenCanvas(canvas, width, height, room);
|
||||||
|
const auto& cfg = activeConfig();
|
||||||
|
const auto& fc = activeFlicker();
|
||||||
|
const float T_S = static_cast<float>(SDL_GetTicks()) / 1000.0F;
|
||||||
|
for (const auto& e : emitters) {
|
||||||
|
if (e.radius <= 0) { continue; }
|
||||||
|
const int R_EFF = e.flicker ? computeFlickerRadius(e.radius, e.seed, T_S, fc) : e.radius;
|
||||||
|
const auto RINGS = computeRings(R_EFF, cfg);
|
||||||
|
const Emitter EFF = withEffectiveRadius(e, R_EFF);
|
||||||
|
rasterizeEmitterAdditive(EFF, RINGS, canvas, width, height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void apply(std::uint32_t* canvas, int width, int height, Mode mode, float room_light, std::span<const Emitter> emitters) {
|
||||||
|
if (canvas == nullptr || width <= 0 || height <= 0) { return; }
|
||||||
|
|
||||||
|
// Atajo per a tots dos modes: sala totalment "neutra" + cap
|
||||||
|
// emissor → la pasada no canviaria res al canvas.
|
||||||
|
// subtractive: room=1.0 i sense emissors → pixel * 1.0 global.
|
||||||
|
// additive: room=1.0 i sense emissors → cap aportació.
|
||||||
|
const std::uint8_t ROOM = toByte(room_light);
|
||||||
|
if (ROOM == 255 && emitters.empty()) { return; }
|
||||||
|
|
||||||
|
if (mode == Mode::Additive) {
|
||||||
|
applyAdditive(canvas, width, height, ROOM, emitters);
|
||||||
|
} else {
|
||||||
|
applySubtractive(canvas, width, height, ROOM, emitters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Lighting
|
} // namespace Lighting
|
||||||
@@ -7,36 +7,73 @@
|
|||||||
// Sistema d'il·luminació CPU. Treballa sobre el mateix canvas ARGB del
|
// 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
|
// joc (320×240) que tot el pipeline de render: després que GameScene
|
||||||
// haja pintat tilemap, paint layer, sombres i actors, una pasada final
|
// haja pintat tilemap, paint layer, sombres i actors, una pasada final
|
||||||
// multiplica cada píxel del canvas per un factor de luminància 0..1
|
// modifica cada píxel del canvas en funció de la "llum de la sala" i
|
||||||
// derivat de la combinació de "llum de la sala" + cercles emissors al
|
// dels cercles emissors al voltant de certes entitats.
|
||||||
// voltant de certes entitats.
|
|
||||||
//
|
//
|
||||||
// Forma de cada emissor (radial, 3 anells configurables al fitxer
|
// Dos modes seleccionables per la sala (`lighting.mode` al YAML):
|
||||||
// data/config/lighting.yaml; veure `GradientConfig`):
|
//
|
||||||
|
// 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
|
// 0..inner_ratio del radi → inner_intensity
|
||||||
// inner_ratio..mid_ratio → mid_intensity
|
// inner_ratio..mid_ratio → mid_intensity
|
||||||
// mid_ratio..1.0 → outer_intensity
|
// mid_ratio..1.0 → outer_intensity
|
||||||
// fora del radi → light_level de la sala
|
|
||||||
//
|
//
|
||||||
// Combinació entre emissors: per a cada píxel es pren el màxim del
|
// Combinació entre emissors:
|
||||||
// llum de sala i de la intensitat aportada per cada emissor que el
|
// SUBTRACTIVE → per a cada píxel es pren el màxim de la intensitat
|
||||||
// solapa — no se "sumen" intensitats. Coherent amb la idea de "centre
|
// aportada per cada emissor que el solapa (no se sumen intensitats).
|
||||||
// 1.0": el píxel que cau dins de qualsevol nucli no s'enfosqueix.
|
// 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 {
|
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
|
// 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
|
// motor) i radi efectiu de l'emissor. El centre és el del sprite
|
||||||
// visible: és l'entitat qui sap on cau (veure `Entity::getLightCenter`).
|
// visible: és l'entitat qui sap on cau (veure `Entity::getLightCenter`).
|
||||||
// `flicker` activa la modulació del radi per frame; `seed` és un
|
// `flicker` activa la modulació del radi per frame; `seed` és un
|
||||||
// identificador estable per emissor (no canvia amb la posició) del
|
// identificador estable per emissor (no canvia amb la posició) del
|
||||||
// qual es deriva la fase del flicker — així els emissors mòbils
|
// qual es deriva la fase del flicker — així els emissors mòbils
|
||||||
// mantenen un patró propi que no "deriva" pel moviment.
|
// mantenen un patró propi que no "deriva" pel moviment. `color`
|
||||||
|
// només té efecte en mode additive.
|
||||||
struct Emitter {
|
struct Emitter {
|
||||||
float cx{0.0F};
|
float cx{0.0F};
|
||||||
float cy{0.0F};
|
float cy{0.0F};
|
||||||
int radius{0};
|
int radius{0};
|
||||||
int seed{0};
|
int seed{0};
|
||||||
bool flicker{false};
|
bool flicker{false};
|
||||||
|
Color color{};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Forma del gradient. Compartida per tots els emissors per evitar
|
// Forma del gradient. Compartida per tots els emissors per evitar
|
||||||
@@ -71,12 +108,16 @@ namespace Lighting {
|
|||||||
// Defaults::Lighting fins que loadConfig() es crida.
|
// Defaults::Lighting fins que loadConfig() es crida.
|
||||||
[[nodiscard]] auto config() -> const GradientConfig&;
|
[[nodiscard]] auto config() -> const GradientConfig&;
|
||||||
|
|
||||||
// Aplica la pasada d'il·luminació al canvas in-place. `room_light`
|
// Aplica la pasada d'il·luminació al canvas in-place. `mode`
|
||||||
// és l'atenuació base de la sala (0..1, 1 = sense atenuació). El
|
// selecciona la composició (vegeu el comentari de la capçalera).
|
||||||
// span d'emissors pot ser buit — en aquest cas tota la sala queda
|
// `room_light` és l'atenuació ambient de la sala (0..1, 1 = sense
|
||||||
// multiplicada per `room_light`. Aquesta funció no fa cap allocació
|
// atenuació): en subtractive és el "terra" de la intensitat fora
|
||||||
// si es crida amb la mateixa mida de canvas múltiples cops: manté
|
// dels emissors; en additive és la multiplicació prèvia del canvas
|
||||||
// un buffer intern reutilitzable.
|
// abans de sumar els emissors (es pot deixar a 1.0 per "només tint",
|
||||||
void apply(std::uint32_t* canvas, int width, int height, float room_light, std::span<const Emitter> emitters);
|
// 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
|
} // namespace Lighting
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -266,6 +267,19 @@ class Entity {
|
|||||||
[[nodiscard]] auto getLightFlicker() const -> bool { return light_flicker_; }
|
[[nodiscard]] auto getLightFlicker() const -> bool { return light_flicker_; }
|
||||||
void setLightFlicker(bool f) { light_flicker_ = f; }
|
void setLightFlicker(bool f) { light_flicker_ = f; }
|
||||||
|
|
||||||
|
// Color emès en mode additive (RGB 0..255 per canal). Default blanc:
|
||||||
|
// en mode subtractive aquest valor s'ignora completament; en mode
|
||||||
|
// additive és el tint que l'emissor afegeix al canvas. La cadena de
|
||||||
|
// resolució (tipus → sala → spawn) el pobla via `setLightColor`.
|
||||||
|
[[nodiscard]] auto getLightColorR() const -> std::uint8_t { return light_color_r_; }
|
||||||
|
[[nodiscard]] auto getLightColorG() const -> std::uint8_t { return light_color_g_; }
|
||||||
|
[[nodiscard]] auto getLightColorB() const -> std::uint8_t { return light_color_b_; }
|
||||||
|
void setLightColor(std::uint8_t r, std::uint8_t g, std::uint8_t b) {
|
||||||
|
light_color_r_ = r;
|
||||||
|
light_color_g_ = g;
|
||||||
|
light_color_b_ = b;
|
||||||
|
}
|
||||||
|
|
||||||
// Identificador estable per al flicker. Cada Entity rep un valor
|
// Identificador estable per al flicker. Cada Entity rep un valor
|
||||||
// únic a la construcció (contador estàtic) i el conserva fins a la
|
// únic a la construcció (contador estàtic) i el conserva fins a la
|
||||||
// mort — així el patró del flicker no "deriva" quan l'entitat es
|
// mort — així el patró del flicker no "deriva" quan l'entitat es
|
||||||
@@ -302,6 +316,9 @@ class Entity {
|
|||||||
static inline int s_next_light_seed{0};
|
static inline int s_next_light_seed{0};
|
||||||
int light_seed_{++s_next_light_seed};
|
int light_seed_{++s_next_light_seed};
|
||||||
bool light_flicker_{false};
|
bool light_flicker_{false};
|
||||||
|
std::uint8_t light_color_r_{255};
|
||||||
|
std::uint8_t light_color_g_{255};
|
||||||
|
std::uint8_t light_color_b_{255};
|
||||||
bool dead_{false};
|
bool dead_{false};
|
||||||
SDL_FlipMode flip_{SDL_FLIP_NONE};
|
SDL_FlipMode flip_{SDL_FLIP_NONE};
|
||||||
EventBus* bus_{nullptr};
|
EventBus* bus_{nullptr};
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
// consumidors que només volen la base del manager (p.ex. el smoke test
|
// consumidors que només volen la base del manager (p.ex. el smoke test
|
||||||
// stand-alone) enllacen `entity_manager.cpp` sense arrossegar aquest fitxer.
|
// stand-alone) enllacen `entity_manager.cpp` sense arrossegar aquest fitxer.
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdint>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -155,22 +157,38 @@ namespace {
|
|||||||
return c["height"].get_value<int>();
|
return c["height"].get_value<int>();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bloc opcional `light: { radius: N, flicker: bool }` de la plantilla.
|
// Bloc opcional `light: { radius: N, flicker: bool, color: [r,g,b] }`
|
||||||
// Si manca, el radi és 0 i l'entitat no entra a la pasada d'il·luminació.
|
// de la plantilla. Si manca, el radi és 0 i l'entitat no entra a la
|
||||||
// `flicker` activa la modulació "tipus foc" del radi (només antorxes
|
// pasada d'il·luminació. `flicker` activa la modulació "tipus foc"
|
||||||
// l'haurien d'usar). La forma del gradient (3 anells) i els paràmetres
|
// del radi (només antorxes l'haurien d'usar). `color` només té
|
||||||
// del flicker viuen a data/config/lighting.yaml — no cal declarar-los
|
// efecte en mode additive — defecte blanc (255,255,255). La forma
|
||||||
// per entitat.
|
// del gradient (3 anells) i els paràmetres del flicker viuen a
|
||||||
|
// data/config/lighting.yaml — no cal declarar-los per entitat.
|
||||||
struct LightTemplate {
|
struct LightTemplate {
|
||||||
int radius{0};
|
int radius{0};
|
||||||
bool flicker{false};
|
bool flicker{false};
|
||||||
|
std::uint8_t color_r{255};
|
||||||
|
std::uint8_t color_g{255};
|
||||||
|
std::uint8_t color_b{255};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Helper compartit entre les tres capes (tipus, sala via Map, spawn)
|
||||||
|
// per llegir `color: [r, g, b]` amb clamp defensiu a 0..255.
|
||||||
|
auto readColorTriplet(const fkyaml::node& seq, std::uint8_t& r, std::uint8_t& g, std::uint8_t& b) -> bool {
|
||||||
|
if (!seq.is_sequence() || seq.size() != 3) { return false; }
|
||||||
|
r = static_cast<std::uint8_t>(std::clamp(seq[0].get_value<int>(), 0, 255));
|
||||||
|
g = static_cast<std::uint8_t>(std::clamp(seq[1].get_value<int>(), 0, 255));
|
||||||
|
b = static_cast<std::uint8_t>(std::clamp(seq[2].get_value<int>(), 0, 255));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
auto readLight(const fkyaml::node& tmpl) -> LightTemplate {
|
auto readLight(const fkyaml::node& tmpl) -> LightTemplate {
|
||||||
if (!tmpl.contains("light")) { return {}; }
|
if (!tmpl.contains("light")) { return {}; }
|
||||||
const auto& l = tmpl["light"];
|
const auto& l = tmpl["light"];
|
||||||
LightTemplate out;
|
LightTemplate out;
|
||||||
if (l.contains("radius")) { out.radius = l["radius"].get_value<int>(); }
|
if (l.contains("radius")) { out.radius = l["radius"].get_value<int>(); }
|
||||||
if (l.contains("flicker")) { out.flicker = l["flicker"].get_value<bool>(); }
|
if (l.contains("flicker")) { out.flicker = l["flicker"].get_value<bool>(); }
|
||||||
|
if (l.contains("color")) { readColorTriplet(l["color"], out.color_r, out.color_g, out.color_b); }
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,10 +203,15 @@ namespace {
|
|||||||
auto resolveLightWithRoom(const std::string& type, LightTemplate type_default) -> LightTemplate {
|
auto resolveLightWithRoom(const std::string& type, LightTemplate type_default) -> LightTemplate {
|
||||||
const auto* room = Map::getRoomEmitterOverride(type);
|
const auto* room = Map::getRoomEmitterOverride(type);
|
||||||
if (room == nullptr) { return type_default; }
|
if (room == nullptr) { return type_default; }
|
||||||
if (!room->enabled) { return {.radius = 0, .flicker = false}; }
|
if (!room->enabled) { return {.radius = 0, .flicker = false, .color_r = 255, .color_g = 255, .color_b = 255}; }
|
||||||
LightTemplate out = type_default;
|
LightTemplate out = type_default;
|
||||||
if (room->radius.has_value()) { out.radius = *room->radius; }
|
if (room->radius.has_value()) { out.radius = *room->radius; }
|
||||||
if (room->flicker.has_value()) { out.flicker = *room->flicker; }
|
if (room->flicker.has_value()) { out.flicker = *room->flicker; }
|
||||||
|
if (room->color.has_value()) {
|
||||||
|
out.color_r = (*room->color)[0];
|
||||||
|
out.color_g = (*room->color)[1];
|
||||||
|
out.color_b = (*room->color)[2];
|
||||||
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,12 +219,10 @@ namespace {
|
|||||||
// viu a l'entitat (defaults del tipus + override de sala). Tres formes
|
// viu a l'entitat (defaults del tipus + override de sala). Tres formes
|
||||||
// a l'entry del YAML de nivell:
|
// a l'entry del YAML de nivell:
|
||||||
// `light: false` → apaga aquesta instància
|
// `light: false` → apaga aquesta instància
|
||||||
// `light: true` → no canvia res; la línia és
|
// `light: true` → informatiu (cau a capes inferiors)
|
||||||
// informativa (la llum ja la donen
|
// `light: { radius, flicker, color: [r,g,b] }` → camps presents sobreescriuen;
|
||||||
// les capes inferiors)
|
// els absents respecten el que
|
||||||
// `light: { radius, flicker }` → els camps presents sobreescriuen;
|
// ja duia l'entitat
|
||||||
// els absents respecten el que ja
|
|
||||||
// duia l'entitat
|
|
||||||
void applySpawnLightOverride(Entity& entity, const fkyaml::node& spawn) {
|
void applySpawnLightOverride(Entity& entity, const fkyaml::node& spawn) {
|
||||||
if (!spawn.contains("light")) { return; }
|
if (!spawn.contains("light")) { return; }
|
||||||
const auto& l = spawn["light"];
|
const auto& l = spawn["light"];
|
||||||
@@ -215,6 +236,12 @@ namespace {
|
|||||||
if (!l.is_mapping()) { return; }
|
if (!l.is_mapping()) { return; }
|
||||||
if (l.contains("radius")) { entity.setLightRadius(l["radius"].get_value<int>()); }
|
if (l.contains("radius")) { entity.setLightRadius(l["radius"].get_value<int>()); }
|
||||||
if (l.contains("flicker")) { entity.setLightFlicker(l["flicker"].get_value<bool>()); }
|
if (l.contains("flicker")) { entity.setLightFlicker(l["flicker"].get_value<bool>()); }
|
||||||
|
if (l.contains("color")) {
|
||||||
|
std::uint8_t r{};
|
||||||
|
std::uint8_t g{};
|
||||||
|
std::uint8_t b{};
|
||||||
|
if (readColorTriplet(l["color"], r, g, b)) { entity.setLightColor(r, g, b); }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bloc `collision: { size: [w, h], base_size: [bw, bh]?, offset: [x, y]? }`
|
// Bloc `collision: { size: [w, h], base_size: [bw, bh]?, offset: [x, y]? }`
|
||||||
@@ -346,6 +373,7 @@ namespace {
|
|||||||
const auto LIGHT = resolveLightWithRoom(type, readLight(tmpl));
|
const auto LIGHT = resolveLightWithRoom(type, readLight(tmpl));
|
||||||
entity->setLightRadius(LIGHT.radius);
|
entity->setLightRadius(LIGHT.radius);
|
||||||
entity->setLightFlicker(LIGHT.flicker);
|
entity->setLightFlicker(LIGHT.flicker);
|
||||||
|
entity->setLightColor(LIGHT.color_r, LIGHT.color_g, LIGHT.color_b);
|
||||||
anchor_out = readAnchor(tmpl);
|
anchor_out = readAnchor(tmpl);
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,10 +150,23 @@ void Player::loadFrom(const std::string& yaml_path) {
|
|||||||
void Player::readLightBlock(const fkyaml::node& yaml) {
|
void Player::readLightBlock(const fkyaml::node& yaml) {
|
||||||
light_radius = 0;
|
light_radius = 0;
|
||||||
light_flicker = false;
|
light_flicker = false;
|
||||||
|
light_color_r = 255;
|
||||||
|
light_color_g = 255;
|
||||||
|
light_color_b = 255;
|
||||||
if (!yaml.contains("light")) { return; }
|
if (!yaml.contains("light")) { return; }
|
||||||
const auto& l = yaml["light"];
|
const auto& l = yaml["light"];
|
||||||
if (l.contains("radius")) { light_radius = l["radius"].get_value<int>(); }
|
if (l.contains("radius")) { light_radius = l["radius"].get_value<int>(); }
|
||||||
if (l.contains("flicker")) { light_flicker = l["flicker"].get_value<bool>(); }
|
if (l.contains("flicker")) { light_flicker = l["flicker"].get_value<bool>(); }
|
||||||
|
// `color: [r, g, b]` opcional, bytes 0..255. Si manca, blanc (sense
|
||||||
|
// tint) — comportament idèntic al mode subtractive de hui.
|
||||||
|
if (l.contains("color")) {
|
||||||
|
const auto& c = l["color"];
|
||||||
|
if (c.is_sequence() && c.size() == 3) {
|
||||||
|
light_color_r = static_cast<std::uint8_t>(std::clamp(c[0].get_value<int>(), 0, 255));
|
||||||
|
light_color_g = static_cast<std::uint8_t>(std::clamp(c[1].get_value<int>(), 0, 255));
|
||||||
|
light_color_b = static_cast<std::uint8_t>(std::clamp(c[2].get_value<int>(), 0, 255));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::readCollisionBlock(const fkyaml::node& yaml, const std::string& yaml_path) {
|
void Player::readCollisionBlock(const fkyaml::node& yaml, const std::string& yaml_path) {
|
||||||
|
|||||||
@@ -125,12 +125,17 @@ class Player : public AnimatedSprite {
|
|||||||
inline static int body_height = 0;
|
inline static int body_height = 0;
|
||||||
|
|
||||||
// Radi de la llum emesa pel jugador (en píxels del canvas), llegit
|
// Radi de la llum emesa pel jugador (en píxels del canvas), llegit
|
||||||
// del bloc opcional `light: { radius: N, flicker: bool }` del YAML.
|
// del bloc opcional `light: { radius: N, flicker: bool, color: [r,g,b] }`
|
||||||
// 0 = no emet. Es declara estàtic com la resta de configs del jugador
|
// del YAML. 0 = no emet. Es declara estàtic com la resta de configs
|
||||||
// (immunity, jump, ...): tots els jugadors del mateix YAML comparteixen
|
// del jugador (immunity, jump, ...): tots els jugadors del mateix YAML
|
||||||
// el valor. `light_flicker` activa la modulació "tipus foc" del radi.
|
// comparteixen el valor. `light_flicker` activa la modulació "tipus
|
||||||
|
// foc" del radi; `light_color_*` són el tint quan la sala usa mode
|
||||||
|
// additive (en subtractive s'ignora).
|
||||||
inline static int light_radius = 0;
|
inline static int light_radius = 0;
|
||||||
inline static bool light_flicker = false;
|
inline static bool light_flicker = false;
|
||||||
|
inline static std::uint8_t light_color_r = 255;
|
||||||
|
inline static std::uint8_t light_color_g = 255;
|
||||||
|
inline static std::uint8_t light_color_b = 255;
|
||||||
|
|
||||||
// Ha de cridar-se abans d'instanciar Player; GameScene ho fa al ctor.
|
// Ha de cridar-se abans d'instanciar Player; GameScene ho fa al ctor.
|
||||||
static void loadFrom(const std::string& yaml_path);
|
static void loadFrom(const std::string& yaml_path);
|
||||||
@@ -146,6 +151,9 @@ class Player : public AnimatedSprite {
|
|||||||
// per a que el cercle "salti" amb el jugador, no es quedi al sòl.
|
// per a que el cercle "salti" amb el jugador, no es quedi al sòl.
|
||||||
[[nodiscard]] static auto getLightRadius() -> int { return light_radius; }
|
[[nodiscard]] static auto getLightRadius() -> int { return light_radius; }
|
||||||
[[nodiscard]] static auto getLightFlicker() -> bool { return light_flicker; }
|
[[nodiscard]] static auto getLightFlicker() -> bool { return light_flicker; }
|
||||||
|
[[nodiscard]] static auto getLightColorR() -> std::uint8_t { return light_color_r; }
|
||||||
|
[[nodiscard]] static auto getLightColorG() -> std::uint8_t { return light_color_g; }
|
||||||
|
[[nodiscard]] static auto getLightColorB() -> std::uint8_t { return light_color_b; }
|
||||||
// Seed estable per al flicker: reusem `id_` (0 o 1 en multijugador
|
// Seed estable per al flicker: reusem `id_` (0 o 1 en multijugador
|
||||||
// local) amb un offset gran per evitar col·lisions amb el contador
|
// local) amb un offset gran per evitar col·lisions amb el contador
|
||||||
// d'Entity (que arrenca des de 1). Així P1 i P2 oscil·len en fases
|
// d'Entity (que arrenca des de 1). Així P1 i P2 oscil·len en fases
|
||||||
|
|||||||
+32
-3
@@ -1,5 +1,6 @@
|
|||||||
#include "game/map/map.hpp"
|
#include "game/map/map.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
@@ -49,18 +50,45 @@ namespace Map {
|
|||||||
return yaml_key;
|
return yaml_key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parser comú d'un color YAML 3-tupla a bytes. Retorna nullopt
|
||||||
|
// si el node no és una seqüència de 3 elements. Usat pels
|
||||||
|
// overrides de sala i, en `entity_manager_loadlevel.cpp`, també
|
||||||
|
// pels defaults de tipus i overrides de spawn (passa pel mateix
|
||||||
|
// helper per coherència). `clamp` defensiu per a YAMLs que
|
||||||
|
// entreguen valors fora de 0..255.
|
||||||
|
auto parseColorTriplet(const fkyaml::node& node) -> std::optional<std::array<std::uint8_t, 3>> {
|
||||||
|
if (!node.is_sequence() || node.size() != 3) { return std::nullopt; }
|
||||||
|
auto clamp = [](int v) { return static_cast<std::uint8_t>(std::clamp(v, 0, 255)); };
|
||||||
|
return std::array<std::uint8_t, 3>{
|
||||||
|
clamp(node[0].get_value<int>()),
|
||||||
|
clamp(node[1].get_value<int>()),
|
||||||
|
clamp(node[2].get_value<int>()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Llegeix el bloc opcional `lighting:` del YAML d'una sala i
|
// Llegeix el bloc opcional `lighting:` del YAML d'una sala i
|
||||||
// actualitza `light_level` (si declara `level`) i el mapping
|
// actualitza `light_level`, `lighting_mode` i el mapping
|
||||||
// `g_room_emitters`. Idempotent: cada `Map::loadFrom` neteja el
|
// `g_room_emitters`. Idempotent: cada `Map::loadFrom` reseteja
|
||||||
// mapping abans de poblar-lo, així no s'arrosseguen overrides
|
// tot l'estat abans de poblar-lo, així no s'arrosseguen valors
|
||||||
// d'una sala anterior.
|
// d'una sala anterior.
|
||||||
void loadLightingBlock(const fkyaml::node& yaml) {
|
void loadLightingBlock(const fkyaml::node& yaml) {
|
||||||
g_room_emitters.clear();
|
g_room_emitters.clear();
|
||||||
|
lighting_mode = Lighting::Mode::Subtractive;
|
||||||
if (!yaml.contains("lighting")) { return; }
|
if (!yaml.contains("lighting")) { return; }
|
||||||
const auto& lighting = yaml["lighting"];
|
const auto& lighting = yaml["lighting"];
|
||||||
if (lighting.contains("level")) {
|
if (lighting.contains("level")) {
|
||||||
light_level = lighting["level"].get_value<float>();
|
light_level = lighting["level"].get_value<float>();
|
||||||
}
|
}
|
||||||
|
if (lighting.contains("mode")) {
|
||||||
|
const auto MODE_STR = lighting["mode"].get_value<std::string>();
|
||||||
|
if (MODE_STR == "additive") {
|
||||||
|
lighting_mode = Lighting::Mode::Additive;
|
||||||
|
} else if (MODE_STR == "subtractive") {
|
||||||
|
lighting_mode = Lighting::Mode::Subtractive;
|
||||||
|
} else {
|
||||||
|
Log::warn("Map", "lighting.mode '{}' desconegut — s'usa subtractive", MODE_STR);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!lighting.contains("emitters") || !lighting["emitters"].is_mapping()) { return; }
|
if (!lighting.contains("emitters") || !lighting["emitters"].is_mapping()) { return; }
|
||||||
const auto& em = lighting["emitters"];
|
const auto& em = lighting["emitters"];
|
||||||
for (auto it = em.begin(); it != em.end(); ++it) {
|
for (auto it = em.begin(); it != em.end(); ++it) {
|
||||||
@@ -73,6 +101,7 @@ namespace Map {
|
|||||||
ov.enabled = true;
|
ov.enabled = true;
|
||||||
if (val.contains("radius")) { ov.radius = val["radius"].get_value<int>(); }
|
if (val.contains("radius")) { ov.radius = val["radius"].get_value<int>(); }
|
||||||
if (val.contains("flicker")) { ov.flicker = val["flicker"].get_value<bool>(); }
|
if (val.contains("flicker")) { ov.flicker = val["flicker"].get_value<bool>(); }
|
||||||
|
if (val.contains("color")) { ov.color = parseColorTriplet(val["color"]); }
|
||||||
}
|
}
|
||||||
g_room_emitters[KEY] = ov;
|
g_room_emitters[KEY] = ov;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
#include "core/rendering/lighting.hpp"
|
||||||
|
|
||||||
// Mapa d'una piràmide: sala rectangular amb marcador superior, muros pels
|
// Mapa d'una piràmide: sala rectangular amb marcador superior, muros pels
|
||||||
// quatre costats i graella de sarcòfags envoltada per un passadís a cada
|
// quatre costats i graella de sarcòfags envoltada per un passadís a cada
|
||||||
// cantó. La geometria (graella, pitch, amplada de murs) es carrega des del
|
// cantó. La geometria (graella, pitch, amplada de murs) es carrega des del
|
||||||
@@ -60,6 +64,13 @@ namespace Map {
|
|||||||
// al root es manté per compatibilitat enrere.
|
// al root es manté per compatibilitat enrere.
|
||||||
inline float light_level = 1.0F;
|
inline float light_level = 1.0F;
|
||||||
|
|
||||||
|
// Mode de composició de la pasada d'il·luminació (subtractive per
|
||||||
|
// defecte; additive si la sala declara `lighting.mode: additive`).
|
||||||
|
// Veure el comentari de `core/rendering/lighting.hpp` per a la
|
||||||
|
// semàntica de cada mode. En subtractive, els colors dels emissors
|
||||||
|
// s'ignoren — el camí és el de sempre.
|
||||||
|
inline Lighting::Mode lighting_mode = Lighting::Mode::Subtractive;
|
||||||
|
|
||||||
// Override d'emissor declarat al bloc opcional `lighting.emitters.<tipus>`
|
// Override d'emissor declarat al bloc opcional `lighting.emitters.<tipus>`
|
||||||
// de la sala. Tres formes acceptades al YAML:
|
// de la sala. Tres formes acceptades al YAML:
|
||||||
// <tipus>: false → desactiva la llum per a tot el tipus
|
// <tipus>: false → desactiva la llum per a tot el tipus
|
||||||
@@ -80,6 +91,10 @@ namespace Map {
|
|||||||
bool enabled{true};
|
bool enabled{true};
|
||||||
std::optional<int> radius;
|
std::optional<int> radius;
|
||||||
std::optional<bool> flicker;
|
std::optional<bool> flicker;
|
||||||
|
// Color emès en mode additive. `nullopt` → conservar el color
|
||||||
|
// del tipus (default blanc 255,255,255 si el tipus no en declara).
|
||||||
|
// La forma YAML és `color: [r, g, b]` (bytes 0..255).
|
||||||
|
std::optional<std::array<std::uint8_t, 3>> color;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Consulta l'override de sala per a un tipus d'entitat (clau singular,
|
// Consulta l'override de sala per a un tipus d'entitat (clau singular,
|
||||||
|
|||||||
@@ -250,6 +250,11 @@ namespace {
|
|||||||
}
|
}
|
||||||
if (room->radius.has_value()) { Player::light_radius = *room->radius; }
|
if (room->radius.has_value()) { Player::light_radius = *room->radius; }
|
||||||
if (room->flicker.has_value()) { Player::light_flicker = *room->flicker; }
|
if (room->flicker.has_value()) { Player::light_flicker = *room->flicker; }
|
||||||
|
if (room->color.has_value()) {
|
||||||
|
Player::light_color_r = (*room->color)[0];
|
||||||
|
Player::light_color_g = (*room->color)[1];
|
||||||
|
Player::light_color_b = (*room->color)[2];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
@@ -1040,20 +1045,27 @@ void GameScene::applyLightingPass() {
|
|||||||
// gating extern aquí (els jugadors són només 1-2 actors i el cost
|
// gating extern aquí (els jugadors són només 1-2 actors i el cost
|
||||||
// d'afegir-los al vector és menyspreable).
|
// d'afegir-los al vector és menyspreable).
|
||||||
const bool PLAYER_FLICKER = Player::getLightFlicker();
|
const bool PLAYER_FLICKER = Player::getLightFlicker();
|
||||||
|
const Lighting::Color PLAYER_COLOR{
|
||||||
|
.r = Player::getLightColorR(),
|
||||||
|
.g = Player::getLightColorG(),
|
||||||
|
.b = Player::getLightColorB(),
|
||||||
|
};
|
||||||
for (const auto& p : players_) {
|
for (const auto& p : players_) {
|
||||||
if (p->isOutOfPlay()) { continue; }
|
if (p->isOutOfPlay()) { continue; }
|
||||||
const auto C = p->getLightCenter();
|
const auto C = p->getLightCenter();
|
||||||
emitters.push_back({.cx = C.x, .cy = C.y, .radius = PLAYER_R, .seed = p->getLightSeed(), .flicker = PLAYER_FLICKER});
|
emitters.push_back({.cx = C.x, .cy = C.y, .radius = PLAYER_R, .seed = p->getLightSeed(), .flicker = PLAYER_FLICKER, .color = PLAYER_COLOR});
|
||||||
}
|
}
|
||||||
for (const auto* e : entity_manager_.getEntities()) {
|
for (const auto* e : entity_manager_.getEntities()) {
|
||||||
const int R = e->getLightRadius();
|
const int R = e->getLightRadius();
|
||||||
if (R <= 0) { continue; }
|
if (R <= 0) { continue; }
|
||||||
const auto C = e->getLightCenter();
|
const auto C = e->getLightCenter();
|
||||||
emitters.push_back({.cx = C.x, .cy = C.y, .radius = R, .seed = e->getLightSeed(), .flicker = e->getLightFlicker()});
|
const Lighting::Color COL{.r = e->getLightColorR(), .g = e->getLightColorG(), .b = e->getLightColorB()};
|
||||||
|
emitters.push_back({.cx = C.x, .cy = C.y, .radius = R, .seed = e->getLightSeed(), .flicker = e->getLightFlicker(), .color = COL});
|
||||||
}
|
}
|
||||||
Lighting::apply(Screen::get().getCanvasArgb(),
|
Lighting::apply(Screen::get().getCanvasArgb(),
|
||||||
Screen::get().getCanvasWidth(),
|
Screen::get().getCanvasWidth(),
|
||||||
Screen::get().getCanvasHeight(),
|
Screen::get().getCanvasHeight(),
|
||||||
|
Map::lighting_mode,
|
||||||
Map::light_level,
|
Map::light_level,
|
||||||
emitters);
|
emitters);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user