feat(lighting): flicker tipus foc per antorxes

This commit is contained in:
JailDesigner committed 2026-05-12 22:07:25 +02:00
1 parent 3195746189
commit 35aaa6c519
8 files changed
+124 -16

No files matched your search

+49 -2
View File
@@ -1,8 +1,11 @@
#include "core/rendering/lighting.hpp"
#include <SDL3/SDL.h>
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <numbers>
#include <vector>
#include "core/resources/resource_helper.hpp"
@@ -36,6 +39,32 @@ namespace Lighting {
return cfg_;
}
auto activeFlicker() -> FlickerConfig& {
static FlickerConfig fc_{
.wave_amplitude_px = Defaults::Lighting::FLICKER_WAVE_AMPLITUDE_PX,
.wave_frequency_hz = Defaults::Lighting::FLICKER_WAVE_FREQUENCY_HZ,
.noise_amplitude_px = Defaults::Lighting::FLICKER_NOISE_AMPLITUDE_PX,
.noise_frequency_hz = Defaults::Lighting::FLICKER_NOISE_FREQUENCY_HZ,
};
return fc_;
}
// 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
// antorxes diferents (cx, cy) les fases queden incommensurables
// entre si, així que mai oscil·len al unison — patró típic de
// foc. Multipliquem cx i cy per constants irracionals petites
// per evitar fases "rodones" quan la posició és múltiple de 16.
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>;
const float PHASE = (cx * 0.0731F) + (cy * 0.1373F);
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 DELTA = (fc.wave_amplitude_px * W1) + (fc.noise_amplitude_px * W2);
const int R = base_radius + static_cast<int>(std::lround(DELTA));
return std::max(1, R);
}
// Quantitza un factor 0..1 a 0..255. `clamp` per defensar-nos de
// YAMLs que entreguen valors fora de rang sense forçar la mà.
auto toByte(float v) -> std::uint8_t {
@@ -154,6 +183,14 @@ namespace Lighting {
readField(g, "mid_intensity", cfg.mid_intensity);
readField(g, "outer_intensity", cfg.outer_intensity);
}
if (yaml.contains("flicker")) {
const auto& f = yaml["flicker"];
auto& fc = activeFlicker();
readField(f, "wave_amplitude_px", fc.wave_amplitude_px);
readField(f, "wave_frequency_hz", fc.wave_frequency_hz);
readField(f, "noise_amplitude_px", fc.noise_amplitude_px);
readField(f, "noise_frequency_hz", fc.noise_frequency_hz);
}
} catch (const std::exception& ex) {
Log::warn("Lighting", "error parsejant {}: {} — s'usaran els defaults compilats", yaml_path, ex.what());
}
@@ -175,10 +212,20 @@ namespace Lighting {
std::ranges::fill(lum, 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 auto RINGS = computeRings(e.radius, cfg);
rasterizeEmitter(e, RINGS, lum.data(), width, height);
const int R_EFF = e.flicker
? computeFlickerRadius(e.radius, e.cx, e.cy, T_S, fc)
: e.radius;
const auto RINGS = computeRings(R_EFF, cfg);
// El rasteritzador llegeix el radi de l'`Emitter` per al
// 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, .flicker = e.flicker};
rasterizeEmitter(EFF, RINGS, lum.data(), width, height);
}
modulateCanvas(canvas, lum.data(), width, height);