|
|
|
|
@@ -8,19 +8,6 @@
|
|
|
|
|
|
|
|
|
|
namespace hd2d {
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
glm::vec3 flameColor(float t) {
|
|
|
|
|
// Yellow core -> orange -> deep red, in linear HDR so the core blooms like
|
|
|
|
|
// HD-2D's torches. Kept saturated: overlapping tongues add up quickly.
|
|
|
|
|
const glm::vec3 c0(4.0f, 2.3f, 0.6f), c1(3.6f, 1.0f, 0.15f), c2(1.8f, 0.26f, 0.04f), c3(0.3f, 0.03f, 0.01f);
|
|
|
|
|
if (t < 0.25f) return glm::mix(c0, c1, t / 0.25f);
|
|
|
|
|
if (t < 0.6f) return glm::mix(c1, c2, (t - 0.25f) / 0.35f);
|
|
|
|
|
return glm::mix(c2, c3, (t - 0.6f) / 0.4f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
ParticleSystem::~ParticleSystem() {
|
|
|
|
|
if (vbo_) glDeleteBuffers(1, &vbo_);
|
|
|
|
|
if (vao_) glDeleteVertexArrays(1, &vao_);
|
|
|
|
|
@@ -79,11 +66,14 @@ void ParticleSystem::spawnFire(const Fire& f, int fireIndex, Kind kind) {
|
|
|
|
|
p.age = 0.0f;
|
|
|
|
|
float a = u(rng_) * 6.2832f;
|
|
|
|
|
if (kind == Kind::Flame) {
|
|
|
|
|
float r = 0.2f * k * std::sqrt(u(rng_));
|
|
|
|
|
// Chunky licks that fill the bowl and stay low and dense, like the
|
|
|
|
|
// painterly brazier fires of HD-2D.
|
|
|
|
|
float r = 0.3f * k * std::sqrt(u(rng_));
|
|
|
|
|
p.pos = f.pos + glm::vec3(std::cos(a) * r, u(rng_) * 0.05f * k, std::sin(a) * r);
|
|
|
|
|
p.vel = glm::vec3(s(rng_) * 0.15f, 1.3f + 0.8f * u(rng_), s(rng_) * 0.15f) * std::sqrt(k);
|
|
|
|
|
p.life = 0.5f + 0.4f * u(rng_);
|
|
|
|
|
p.size = 0.42f * k * (0.8f + 0.4f * u(rng_));
|
|
|
|
|
p.vel = glm::vec3(s(rng_) * 0.12f, 0.6f + 0.45f * u(rng_), s(rng_) * 0.12f) * std::sqrt(k);
|
|
|
|
|
p.life = 0.45f + 0.35f * u(rng_);
|
|
|
|
|
p.size = 0.55f * k * (0.8f + 0.4f * u(rng_)) * (1.0f - 0.35f * r / (0.3f * k + 1e-4f)); // smaller at the rim
|
|
|
|
|
p.color = glm::vec3(0.9f + 0.2f * u(rng_));
|
|
|
|
|
} else if (kind == Kind::Ember) {
|
|
|
|
|
float r = 0.15f * k * u(rng_);
|
|
|
|
|
p.pos = f.pos + glm::vec3(std::cos(a) * r, 0.1f * k, std::sin(a) * r);
|
|
|
|
|
@@ -132,11 +122,11 @@ void ParticleSystem::update(float dt, float time) {
|
|
|
|
|
spawnFire(f, int(i), kind);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
emit(f.flameAccum, 45.0f * f.scale, Kind::Flame);
|
|
|
|
|
emit(f.flameAccum, 70.0f * f.scale, Kind::Flame);
|
|
|
|
|
emit(f.emberAccum, 14.0f * f.scale, Kind::Ember);
|
|
|
|
|
emit(f.smokeAccum, 6.0f * f.scale, Kind::Smoke);
|
|
|
|
|
glm::vec3 glow = glm::vec3(1.0f, 0.45f, 0.13f) * 0.9f * f.flicker;
|
|
|
|
|
gpu_.push_back({glm::vec4(f.pos + glm::vec3(0, 0.35f * f.scale, 0), 1.1f * f.scale * f.flicker),
|
|
|
|
|
glm::vec3 glow = glm::vec3(1.0f, 0.42f, 0.1f) * 0.8f * f.flicker;
|
|
|
|
|
gpu_.push_back({glm::vec4(f.pos + glm::vec3(0, 0.3f * f.scale, 0), 1.5f * f.scale * f.flicker),
|
|
|
|
|
glm::vec4(glow, 1.0f), glm::vec4(float(int(Kind::Glow)), 0, 0, 0)});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -184,13 +174,15 @@ void ParticleSystem::update(float dt, float time) {
|
|
|
|
|
break;
|
|
|
|
|
case Kind::Flame: {
|
|
|
|
|
const Fire& f = fires_[p.fire];
|
|
|
|
|
p.vel.y += 1.6f * dt; // buoyancy
|
|
|
|
|
p.vel.x += (std::sin(t * 9.0f) * 0.9f - (p.pos.x - f.pos.x) * 5.0f) * dt; // flicker + taper
|
|
|
|
|
p.vel.z += (std::cos(t * 7.3f) * 0.9f - (p.pos.z - f.pos.z) * 5.0f) * dt;
|
|
|
|
|
p.vel.y += 0.9f * dt; // buoyancy (kept low: compact, bowl-filling fire)
|
|
|
|
|
p.vel.x += (std::sin(t * 9.0f) * 0.9f - (p.pos.x - f.pos.x) * 3.5f) * dt; // flicker + taper
|
|
|
|
|
p.vel.z += (std::cos(t * 7.3f) * 0.9f - (p.pos.z - f.pos.z) * 3.5f) * dt;
|
|
|
|
|
p.pos += p.vel * dt;
|
|
|
|
|
color = flameColor(lifeT) * 0.22f; // tongues overlap a lot; keep the sum orange, not white
|
|
|
|
|
alpha = std::min(1.0f, lifeT / 0.08f) * std::pow(1.0f - lifeT, 1.3f);
|
|
|
|
|
size = p.size * (0.75f + 0.5f * std::sin(3.1416f * std::min(lifeT * 1.2f, 1.0f))) * (1.0f - 0.5f * lifeT);
|
|
|
|
|
// Intensity only: the yellow -> orange -> red palette is applied
|
|
|
|
|
// per pixel in particles.frag from heat (center + youth).
|
|
|
|
|
color = p.color * f.flicker;
|
|
|
|
|
alpha = std::min(1.0f, lifeT / 0.06f) * std::pow(1.0f - lifeT, 0.6f);
|
|
|
|
|
size = p.size * (1.0f - 0.55f * lifeT) * (0.7f + 0.3f * std::min(1.0f, lifeT / 0.12f));
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case Kind::Ember: {
|
|
|
|
|
@@ -222,6 +214,13 @@ void ParticleSystem::update(float dt, float time) {
|
|
|
|
|
++i;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Flames are alpha-blended (not additive), so draw each fire's oldest,
|
|
|
|
|
// reddest tongues first and the young bright core on top.
|
|
|
|
|
auto firstFlame = std::stable_partition(gpu_.begin(), gpu_.end(), [](const GpuParticle& g) {
|
|
|
|
|
return int(g.params.x) != int(Kind::Flame);
|
|
|
|
|
});
|
|
|
|
|
std::sort(firstFlame, gpu_.end(), [](const GpuParticle& a, const GpuParticle& b) { return a.params.y > b.params.y; });
|
|
|
|
|
|
|
|
|
|
if (!vao_) {
|
|
|
|
|
glCreateVertexArrays(1, &vao_);
|
|
|
|
|
glCreateBuffers(1, &vbo_);
|
|
|
|
|
|