feat(lighting): seed estable per emissor (no deriva pel moviment)
This commit is contained in:
1 parent
0046d526b0
commit
5cc8e349a5
5 files changed
+35
-15
No files matched your search
@@ -50,14 +50,14 @@ namespace Lighting {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Modula el radi base d'un emissor amb la suma de dues sinusoides
|
// Modula el radi base d'un emissor amb la suma de dues sinusoides
|
||||||
// (`wave` + `noise`) i una fase derivada de la posició. Per a dos
|
// (`wave` + `noise`) i una fase derivada de `seed`. La seed és un
|
||||||
// antorxes diferents (cx, cy) les fases queden incommensurables
|
// identificador estable de l'emissor (no canvia amb la posició) —
|
||||||
// entre si, així que mai oscil·len al unison — patró típic de
|
// així el patró no "deriva" quan l'emissor es mou. El factor
|
||||||
// foc. Multipliquem cx i cy per constants irracionals petites
|
// ~φ (golden ratio) dispersa les fases per a seeds consecutives.
|
||||||
// per evitar fases "rodones" quan la posició és múltiple de 16.
|
auto computeFlickerRadius(int base_radius, int seed, float t_s, const FlickerConfig& fc) -> int {
|
||||||
auto computeFlickerRadius(int base_radius, float cx, float cy, float t_s, const FlickerConfig& fc) -> int {
|
|
||||||
constexpr float TWO_PI = 2.0F * std::numbers::pi_v<float>;
|
constexpr float TWO_PI = 2.0F * std::numbers::pi_v<float>;
|
||||||
const float PHASE = (cx * 0.0731F) + (cy * 0.1373F);
|
constexpr float GOLDEN_FRACTION = 0.6180339F;
|
||||||
|
const float PHASE = static_cast<float>(seed) * GOLDEN_FRACTION;
|
||||||
const float W1 = std::sin((TWO_PI * fc.wave_frequency_hz * t_s) + PHASE);
|
const float W1 = std::sin((TWO_PI * fc.wave_frequency_hz * t_s) + PHASE);
|
||||||
const float W2 = std::sin((TWO_PI * fc.noise_frequency_hz * t_s) + (PHASE * 2.7F));
|
const float W2 = std::sin((TWO_PI * fc.noise_frequency_hz * t_s) + (PHASE * 2.7F));
|
||||||
const float DELTA = (fc.wave_amplitude_px * W1) + (fc.noise_amplitude_px * W2);
|
const float DELTA = (fc.wave_amplitude_px * W1) + (fc.noise_amplitude_px * W2);
|
||||||
@@ -217,14 +217,14 @@ namespace Lighting {
|
|||||||
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.cx, e.cy, T_S, fc)
|
? computeFlickerRadius(e.radius, e.seed, T_S, fc)
|
||||||
: e.radius;
|
: 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
|
// El rasteritzador llegeix el radi de l'`Emitter` per al
|
||||||
// càlcul de la bounding box, així que li passem un emissor
|
// càlcul de la bounding box, així que li passem un emissor
|
||||||
// "patxat" amb el radi efectiu d'aquest frame. Mantenim el
|
// "patxat" amb el radi efectiu d'aquest frame. Mantenim el
|
||||||
// valor original al `Emitter` del caller intacte.
|
// valor original al `Emitter` del caller intacte.
|
||||||
const Emitter EFF{.cx = e.cx, .cy = e.cy, .radius = R_EFF, .flicker = e.flicker};
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,14 +27,15 @@ namespace Lighting {
|
|||||||
// 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 (només antorxes
|
// `flicker` activa la modulació del radi per frame; `seed` és un
|
||||||
// avui): el radi base es queda al YAML, l'oscil·lació en viu surt
|
// identificador estable per emissor (no canvia amb la posició) del
|
||||||
// dels paràmetres globals i de la posició per generar fases
|
// qual es deriva la fase del flicker — així els emissors mòbils
|
||||||
// distintes a cada emissor.
|
// mantenen un patró propi que no "deriva" pel moviment.
|
||||||
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};
|
||||||
bool flicker{false};
|
bool flicker{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -266,6 +266,14 @@ 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; }
|
||||||
|
|
||||||
|
// Identificador estable per al flicker. Cada Entity rep un valor
|
||||||
|
// ú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
|
||||||
|
// mou. Si l'entitat respawneja, la nova instància tindrà una seed
|
||||||
|
// nova; com que el cicle visual ja queda trencat per la mort, no és
|
||||||
|
// un problema.
|
||||||
|
[[nodiscard]] auto getLightSeed() const -> int { return light_seed_; }
|
||||||
|
|
||||||
// Centre de la llum en píxels del canvas. Per defecte és el centre del
|
// Centre de la llum en píxels del canvas. Per defecte és el centre del
|
||||||
// `pixel_rect_` (que és la caixa de render: sprite top-left + mida).
|
// `pixel_rect_` (que és la caixa de render: sprite top-left + mida).
|
||||||
// Virtual perquè entitats amb desplaçament visual sobre la posició
|
// Virtual perquè entitats amb desplaçament visual sobre la posició
|
||||||
@@ -287,6 +295,12 @@ class Entity {
|
|||||||
bool blocks_movement_{false};
|
bool blocks_movement_{false};
|
||||||
int height_{0};
|
int height_{0};
|
||||||
int light_radius_{0};
|
int light_radius_{0};
|
||||||
|
// Comptador global d'instàncies; cada Entity nova s'emporta el
|
||||||
|
// valor incrementat com a seed propi. Estàtic perquè no calgui
|
||||||
|
// estat extern (sense risc de threads: les entitats es construeixen
|
||||||
|
// sempre des del fil del joc).
|
||||||
|
static inline int s_next_light_seed{0};
|
||||||
|
int light_seed_{++s_next_light_seed};
|
||||||
bool light_flicker_{false};
|
bool light_flicker_{false};
|
||||||
bool dead_{false};
|
bool dead_{false};
|
||||||
SDL_FlipMode flip_{SDL_FLIP_NONE};
|
SDL_FlipMode flip_{SDL_FLIP_NONE};
|
||||||
|
|||||||
@@ -146,6 +146,11 @@ 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; }
|
||||||
|
// Seed estable per al flicker: reusem `id_` (0 o 1 en multijugador
|
||||||
|
// 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
|
||||||
|
// pròpies que no s'encavalquen amb cap entitat.
|
||||||
|
[[nodiscard]] auto getLightSeed() const -> int { return id_ + 1000; }
|
||||||
[[nodiscard]] auto getLightCenter() const -> SDL_FPoint;
|
[[nodiscard]] auto getLightCenter() const -> SDL_FPoint;
|
||||||
|
|
||||||
Player(const std::string& anim_key, int id, float x, float y, const Controls& controls, const Map::Info& map_info, int paint_tile_index = -1);
|
Player(const std::string& anim_key, int id, float x, float y, const Controls& controls, const Map::Info& map_info, int paint_tile_index = -1);
|
||||||
|
|||||||
@@ -1022,13 +1022,13 @@ void GameScene::applyLightingPass() {
|
|||||||
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, .flicker = PLAYER_FLICKER});
|
emitters.push_back({.cx = C.x, .cy = C.y, .radius = PLAYER_R, .seed = p->getLightSeed(), .flicker = PLAYER_FLICKER});
|
||||||
}
|
}
|
||||||
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, .flicker = e->getLightFlicker()});
|
emitters.push_back({.cx = C.x, .cy = C.y, .radius = R, .seed = e->getLightSeed(), .flicker = e->getLightFlicker()});
|
||||||
}
|
}
|
||||||
Lighting::apply(Screen::get().getCanvasArgb(),
|
Lighting::apply(Screen::get().getCanvasArgb(),
|
||||||
Screen::get().getCanvasWidth(),
|
Screen::get().getCanvasWidth(),
|
||||||
|
|||||||
Reference in new issue
Block a user