fixed main fireworks
This commit is contained in:
@ -3,6 +3,55 @@
|
|||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <random>
|
#include <random>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr float PI_F = 3.14159265358979323846f;
|
||||||
|
|
||||||
|
float randRange(float minVal, float maxVal) {
|
||||||
|
return minVal + (static_cast<float>(rand()) / static_cast<float>(RAND_MAX)) * (maxVal - minVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Color randomFireworkColor() {
|
||||||
|
static const SDL_Color palette[] = {
|
||||||
|
{255, 120, 80, 255},
|
||||||
|
{255, 190, 60, 255},
|
||||||
|
{120, 210, 255, 255},
|
||||||
|
{170, 120, 255, 255},
|
||||||
|
{255, 90, 180, 255},
|
||||||
|
{120, 255, 170, 255},
|
||||||
|
{255, 255, 180, 255}
|
||||||
|
};
|
||||||
|
size_t idx = static_cast<size_t>(rand() % (sizeof(palette) / sizeof(palette[0])));
|
||||||
|
return palette[idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Color scaleColor(SDL_Color color, float factor, Uint8 alphaOverride = 0) {
|
||||||
|
auto clampChannel = [](float value) -> Uint8 {
|
||||||
|
return static_cast<Uint8>(std::max(0.0f, std::min(255.0f, std::round(value))));
|
||||||
|
};
|
||||||
|
SDL_Color result;
|
||||||
|
result.r = clampChannel(color.r * factor);
|
||||||
|
result.g = clampChannel(color.g * factor);
|
||||||
|
result.b = clampChannel(color.b * factor);
|
||||||
|
result.a = alphaOverride ? alphaOverride : color.a;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Color mixColors(SDL_Color a, SDL_Color b, float t) {
|
||||||
|
t = std::clamp(t, 0.0f, 1.0f);
|
||||||
|
auto lerpChannel = [t](Uint8 ca, Uint8 cb) -> Uint8 {
|
||||||
|
float blended = ca + (cb - ca) * t;
|
||||||
|
return static_cast<Uint8>(std::max(0.0f, std::min(255.0f, blended)));
|
||||||
|
};
|
||||||
|
SDL_Color result;
|
||||||
|
result.r = lerpChannel(a.r, b.r);
|
||||||
|
result.g = lerpChannel(a.g, b.g);
|
||||||
|
result.b = lerpChannel(a.b, b.b);
|
||||||
|
result.a = lerpChannel(a.a, b.a);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
GlobalState& GlobalState::instance() {
|
GlobalState& GlobalState::instance() {
|
||||||
static GlobalState instance;
|
static GlobalState instance;
|
||||||
@ -41,88 +90,163 @@ void GlobalState::shutdown() {
|
|||||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "[GlobalState] Shutdown complete");
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "[GlobalState] Shutdown complete");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
using Firework = GlobalState::TetrisFirework;
|
||||||
|
using BlockParticle = GlobalState::BlockParticle;
|
||||||
|
using SparkParticle = GlobalState::SparkParticle;
|
||||||
|
void spawnSparks(Firework& firework, float cx, float cy, SDL_Color baseColor, float speedBase) {
|
||||||
|
int sparkCount = 10 + (rand() % 10);
|
||||||
|
for (int i = 0; i < sparkCount; ++i) {
|
||||||
|
SparkParticle spark;
|
||||||
|
spark.x = cx;
|
||||||
|
spark.y = cy;
|
||||||
|
float angle = randRange(0.0f, PI_F * 2.0f);
|
||||||
|
float speed = speedBase * randRange(1.05f, 1.6f);
|
||||||
|
spark.vx = std::cos(angle) * speed;
|
||||||
|
spark.vy = std::sin(angle) * speed - randRange(30.0f, 90.0f);
|
||||||
|
spark.life = 0.0f;
|
||||||
|
spark.maxLife = 260.0f + randRange(0.0f, 200.0f);
|
||||||
|
spark.thickness = randRange(0.8f, 2.2f);
|
||||||
|
spark.color = scaleColor(baseColor, randRange(0.85f, 1.2f), 255);
|
||||||
|
firework.sparks.push_back(spark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void triggerFireworkBurst(Firework& firework, int burstIndex) {
|
||||||
|
SDL_Color burstColor = firework.burstColors[burstIndex % firework.burstColors.size()];
|
||||||
|
float centerX = firework.originX + randRange(-30.0f, 30.0f);
|
||||||
|
float centerY = firework.originY - burstIndex * randRange(14.0f, 24.0f) + randRange(-10.0f, 10.0f);
|
||||||
|
int particleCount = 22 + (rand() % 16);
|
||||||
|
float speedBase = 90.0f + burstIndex * 40.0f;
|
||||||
|
|
||||||
|
for (int i = 0; i < particleCount; ++i) {
|
||||||
|
BlockParticle particle;
|
||||||
|
particle.x = centerX;
|
||||||
|
particle.y = centerY;
|
||||||
|
float angle = randRange(0.0f, PI_F * 2.0f);
|
||||||
|
float speed = speedBase + randRange(-20.0f, 70.0f);
|
||||||
|
particle.vx = std::cos(angle) * speed;
|
||||||
|
particle.vy = std::sin(angle) * speed - randRange(35.0f, 95.0f);
|
||||||
|
particle.maxLife = 950.0f + randRange(0.0f, 420.0f) + burstIndex * 220.0f;
|
||||||
|
particle.life = particle.maxLife;
|
||||||
|
particle.crackle = (rand() % 100) < 75;
|
||||||
|
particle.flickerSeed = randRange(0.0f, PI_F * 2.0f);
|
||||||
|
if (particle.crackle) {
|
||||||
|
particle.size = randRange(1.4f, 3.2f);
|
||||||
|
} else {
|
||||||
|
particle.size = 3.2f + randRange(0.0f, 2.6f) + burstIndex * 0.6f;
|
||||||
|
}
|
||||||
|
particle.color = scaleColor(burstColor, randRange(0.85f, 1.2f));
|
||||||
|
particle.dualColor = (rand() % 100) < 55;
|
||||||
|
if (particle.dualColor) {
|
||||||
|
SDL_Color alt = randomFireworkColor();
|
||||||
|
float luminanceDiff = std::abs(static_cast<float>(alt.r + alt.g + alt.b) - (particle.color.r + particle.color.g + particle.color.b));
|
||||||
|
if (luminanceDiff < 40.0f) {
|
||||||
|
alt = scaleColor(particle.color, randRange(0.6f, 1.4f));
|
||||||
|
}
|
||||||
|
particle.accentColor = alt;
|
||||||
|
particle.colorBlendSpeed = randRange(0.6f, 1.4f);
|
||||||
|
} else {
|
||||||
|
particle.accentColor = particle.color;
|
||||||
|
particle.colorBlendSpeed = 1.0f;
|
||||||
|
}
|
||||||
|
firework.particles.push_back(particle);
|
||||||
|
}
|
||||||
|
|
||||||
|
spawnSparks(firework, centerX, centerY, burstColor, speedBase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void GlobalState::updateFireworks(double frameMs) {
|
void GlobalState::updateFireworks(double frameMs) {
|
||||||
|
if (frameMs <= 0.0) {
|
||||||
|
frameMs = 16.0;
|
||||||
|
}
|
||||||
|
|
||||||
const Uint64 currentTime = SDL_GetTicks();
|
const Uint64 currentTime = SDL_GetTicks();
|
||||||
|
size_t activeCount = 0;
|
||||||
// Check if we have any active fireworks
|
|
||||||
bool hasActiveFirework = false;
|
|
||||||
for (const auto& fw : fireworks) {
|
for (const auto& fw : fireworks) {
|
||||||
if (fw.active) {
|
if (fw.active) {
|
||||||
hasActiveFirework = true;
|
++activeCount;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only create new firework if no active ones exist
|
constexpr size_t MAX_SIMULTANEOUS_FIREWORKS = 2;
|
||||||
if (!hasActiveFirework && currentTime - lastFireworkTime > 1500 + (rand() % 2000)) {
|
bool canSpawnNew = activeCount < MAX_SIMULTANEOUS_FIREWORKS;
|
||||||
float x = Config::Logical::WIDTH * (0.15f + (rand() % 70) / 100.0f);
|
bool spawnedFirework = false;
|
||||||
float y = Config::Logical::HEIGHT * (0.20f + (rand() % 60) / 100.0f);
|
|
||||||
|
if (canSpawnNew) {
|
||||||
|
Uint64 interval = 1300 + static_cast<Uint64>(rand() % 1400);
|
||||||
|
if (currentTime - lastFireworkTime > interval) {
|
||||||
|
float x = Config::Logical::WIDTH * (0.15f + randRange(0.0f, 0.70f));
|
||||||
|
float y = Config::Logical::HEIGHT * (0.18f + randRange(0.0f, 0.40f));
|
||||||
|
createFirework(x, y);
|
||||||
|
lastFireworkTime = currentTime;
|
||||||
|
lastFireworkX = x;
|
||||||
|
lastFireworkY = y;
|
||||||
|
pendingStaggerFirework = (rand() % 100) < 65;
|
||||||
|
if (pendingStaggerFirework) {
|
||||||
|
nextStaggerFireworkTime = currentTime + 250 + static_cast<Uint64>(rand() % 420);
|
||||||
|
}
|
||||||
|
spawnedFirework = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!spawnedFirework && pendingStaggerFirework && canSpawnNew && currentTime >= nextStaggerFireworkTime) {
|
||||||
|
float x = lastFireworkX + randRange(-140.0f, 140.0f);
|
||||||
|
float y = lastFireworkY + randRange(-80.0f, 50.0f);
|
||||||
|
x = std::clamp(x, Config::Logical::WIDTH * 0.10f, Config::Logical::WIDTH * 0.90f);
|
||||||
|
y = std::clamp(y, Config::Logical::HEIGHT * 0.15f, Config::Logical::HEIGHT * 0.70f);
|
||||||
createFirework(x, y);
|
createFirework(x, y);
|
||||||
lastFireworkTime = currentTime;
|
lastFireworkTime = currentTime;
|
||||||
|
lastFireworkX = x;
|
||||||
|
lastFireworkY = y;
|
||||||
|
pendingStaggerFirework = false;
|
||||||
|
spawnedFirework = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update existing fireworks
|
const float dtSeconds = static_cast<float>(frameMs / 1000.0);
|
||||||
|
const float deltaMs = static_cast<float>(frameMs);
|
||||||
|
|
||||||
for (auto& firework : fireworks) {
|
for (auto& firework : fireworks) {
|
||||||
if (!firework.active) continue;
|
if (!firework.active) {
|
||||||
|
continue;
|
||||||
bool hasActiveParticles = false;
|
|
||||||
std::vector<BlockParticle> newParticles;
|
|
||||||
newParticles.reserve(20); // Pre-allocate to avoid reallocation
|
|
||||||
|
|
||||||
for (auto& particle : firework.particles) {
|
|
||||||
if (particle.life <= 0) continue;
|
|
||||||
|
|
||||||
// Update physics
|
|
||||||
float dt = float(frameMs / 1000.0f);
|
|
||||||
particle.x += particle.vx * dt;
|
|
||||||
particle.y += particle.vy * dt;
|
|
||||||
particle.vx *= (1.0f - 0.5f * dt);
|
|
||||||
particle.vy = particle.vy * (1.0f - 0.2f * dt) + 80.0f * dt;
|
|
||||||
particle.life -= frameMs;
|
|
||||||
|
|
||||||
// Update size
|
|
||||||
float lifeRatio = particle.life / particle.maxLife;
|
|
||||||
particle.size = (6.0f - particle.generation * 2.0f) + (4.0f - particle.generation) * lifeRatio;
|
|
||||||
|
|
||||||
// Only primary particles create secondary explosions (single cascade level)
|
|
||||||
if (!particle.hasExploded && particle.generation == 0 && lifeRatio < 0.5f) {
|
|
||||||
particle.hasExploded = true;
|
|
||||||
|
|
||||||
// Spawn only 3-4 secondary particles
|
|
||||||
int secondaryCount = 3 + (rand() % 2);
|
|
||||||
for (int i = 0; i < secondaryCount; ++i) {
|
|
||||||
BlockParticle secondary;
|
|
||||||
secondary.x = particle.x;
|
|
||||||
secondary.y = particle.y;
|
|
||||||
secondary.generation = 1; // Only one level of cascade
|
|
||||||
secondary.hasExploded = true; // Don't cascade further
|
|
||||||
|
|
||||||
float angle = (float)(rand() % 360) * 3.14159f / 180.0f;
|
|
||||||
float speed = 40.0f + (rand() % 40);
|
|
||||||
secondary.vx = cos(angle) * speed;
|
|
||||||
secondary.vy = sin(angle) * speed - 20.0f;
|
|
||||||
|
|
||||||
secondary.type = 1 + (rand() % 7);
|
|
||||||
secondary.maxLife = 700.0f + (rand() % 400);
|
|
||||||
secondary.life = secondary.maxLife;
|
|
||||||
secondary.size = 3.0f + (rand() % 2);
|
|
||||||
|
|
||||||
newParticles.push_back(secondary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (particle.life > 0) {
|
|
||||||
hasActiveParticles = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add secondary particles
|
|
||||||
for (auto& newParticle : newParticles) {
|
|
||||||
firework.particles.push_back(newParticle);
|
|
||||||
hasActiveParticles = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
firework.active = hasActiveParticles;
|
firework.elapsedMs += deltaMs;
|
||||||
|
while (firework.nextBurst < static_cast<int>(firework.burstSchedule.size()) &&
|
||||||
|
firework.elapsedMs >= firework.burstSchedule[firework.nextBurst]) {
|
||||||
|
triggerFireworkBurst(firework, firework.nextBurst);
|
||||||
|
firework.nextBurst++;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto it = firework.particles.begin(); it != firework.particles.end();) {
|
||||||
|
it->life -= deltaMs;
|
||||||
|
if (it->life <= 0.0f) {
|
||||||
|
it = firework.particles.erase(it);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
it->x += it->vx * dtSeconds;
|
||||||
|
it->y += it->vy * dtSeconds;
|
||||||
|
it->vx *= 0.986f;
|
||||||
|
it->vy = it->vy * 0.972f + 70.0f * dtSeconds;
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto it = firework.sparks.begin(); it != firework.sparks.end();) {
|
||||||
|
it->life += deltaMs;
|
||||||
|
if (it->life >= it->maxLife) {
|
||||||
|
it = firework.sparks.erase(it);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
it->x += it->vx * dtSeconds;
|
||||||
|
it->y += it->vy * dtSeconds;
|
||||||
|
it->vx *= 0.992f;
|
||||||
|
it->vy = it->vy * 0.965f + 120.0f * dtSeconds;
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pendingBursts = firework.nextBurst < static_cast<int>(firework.burstSchedule.size());
|
||||||
|
firework.active = pendingBursts || !firework.particles.empty() || !firework.sparks.empty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,78 +266,103 @@ void GlobalState::createFirework(float x, float y) {
|
|||||||
firework = &fireworks.back();
|
firework = &fireworks.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize firework
|
|
||||||
firework->active = true;
|
firework->active = true;
|
||||||
firework->particles.clear();
|
firework->particles.clear();
|
||||||
|
firework->sparks.clear();
|
||||||
|
firework->originX = x;
|
||||||
|
firework->originY = y;
|
||||||
|
firework->elapsedMs = 0.0f;
|
||||||
|
firework->nextBurst = 0;
|
||||||
|
firework->burstSchedule = {
|
||||||
|
0.0f,
|
||||||
|
220.0f + randRange(0.0f, 160.0f),
|
||||||
|
420.0f + randRange(0.0f, 260.0f)
|
||||||
|
};
|
||||||
|
|
||||||
// Create fewer particles for subtle background effect
|
SDL_Color baseColor = randomFireworkColor();
|
||||||
const int particleCount = 12 + (rand() % 8); // 12-20 particles per explosion
|
for (int i = 0; i < 3; ++i) {
|
||||||
for (int i = 0; i < particleCount; ++i) {
|
float wobble = randRange(-0.08f, 0.08f);
|
||||||
BlockParticle particle;
|
firework->burstColors[i] = scaleColor(baseColor, 1.0f - i * 0.12f + wobble, 255);
|
||||||
particle.x = x;
|
|
||||||
particle.y = y;
|
|
||||||
particle.generation = 0; // Primary explosion
|
|
||||||
particle.hasExploded = false;
|
|
||||||
|
|
||||||
// Random velocity in all directions
|
|
||||||
float angle = (float)(rand() % 360) * 3.14159f / 180.0f;
|
|
||||||
float speed = 80.0f + (rand() % 100); // Moderate speed
|
|
||||||
particle.vx = cos(angle) * speed;
|
|
||||||
particle.vy = sin(angle) * speed - 50.0f; // Slight upward bias
|
|
||||||
|
|
||||||
particle.type = 1 + (rand() % 7); // Random tetris piece color
|
|
||||||
particle.maxLife = 1500.0f + (rand() % 1000); // Medium life: ~1.5-2.5 seconds
|
|
||||||
particle.life = particle.maxLife;
|
|
||||||
particle.size = 6.0f + (rand() % 5); // Smaller particles
|
|
||||||
|
|
||||||
firework->particles.push_back(particle);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GlobalState::drawFireworks(SDL_Renderer* renderer, SDL_Texture* blocksTex) {
|
void GlobalState::drawFireworks(SDL_Renderer* renderer, SDL_Texture* blocksTex) {
|
||||||
(void)blocksTex; // Not using texture anymore
|
(void)blocksTex;
|
||||||
|
|
||||||
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
|
|
||||||
|
|
||||||
|
auto renderCircle = [renderer](float cx, float cy, float radius) {
|
||||||
|
int ir = static_cast<int>(std::ceil(radius));
|
||||||
|
for (int dy = -ir; dy <= ir; ++dy) {
|
||||||
|
float row = std::sqrt(std::max(0.0f, radius * radius - static_cast<float>(dy * dy)));
|
||||||
|
SDL_FRect line{cx - row, cy + dy, row * 2.0f, 1.0f};
|
||||||
|
SDL_RenderFillRect(renderer, &line);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_ADD);
|
||||||
|
for (const auto& firework : fireworks) {
|
||||||
|
if (!firework.active) continue;
|
||||||
|
|
||||||
|
for (const auto& spark : firework.sparks) {
|
||||||
|
if (spark.life >= spark.maxLife) continue;
|
||||||
|
float progress = spark.life / spark.maxLife;
|
||||||
|
Uint8 alpha = static_cast<Uint8>((1.0f - progress) * 255.0f);
|
||||||
|
if (alpha == 0) continue;
|
||||||
|
SDL_SetRenderDrawColor(renderer, spark.color.r, spark.color.g, spark.color.b, alpha);
|
||||||
|
float trailScale = 0.015f * spark.thickness;
|
||||||
|
float tailX = spark.x - spark.vx * trailScale;
|
||||||
|
float tailY = spark.y - spark.vy * trailScale;
|
||||||
|
SDL_RenderLine(renderer,
|
||||||
|
static_cast<int>(spark.x),
|
||||||
|
static_cast<int>(spark.y),
|
||||||
|
static_cast<int>(tailX),
|
||||||
|
static_cast<int>(tailY));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto sampleParticleColor = [](const BlockParticle& particle) -> SDL_Color {
|
||||||
|
if (!particle.dualColor) {
|
||||||
|
return particle.color;
|
||||||
|
}
|
||||||
|
float elapsed = particle.maxLife - particle.life;
|
||||||
|
float phase = particle.flickerSeed * 1.8f + elapsed * 0.0025f * particle.colorBlendSpeed;
|
||||||
|
float mixFactor = 0.5f + 0.5f * std::sin(phase);
|
||||||
|
return mixColors(particle.color, particle.accentColor, mixFactor);
|
||||||
|
};
|
||||||
|
|
||||||
|
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
|
||||||
for (const auto& firework : fireworks) {
|
for (const auto& firework : fireworks) {
|
||||||
if (!firework.active) continue;
|
if (!firework.active) continue;
|
||||||
|
|
||||||
for (const auto& particle : firework.particles) {
|
for (const auto& particle : firework.particles) {
|
||||||
if (particle.life <= 0) continue;
|
if (particle.life <= 0.0f) continue;
|
||||||
|
|
||||||
// Calculate alpha based on remaining life
|
|
||||||
float lifeRatio = particle.life / particle.maxLife;
|
float lifeRatio = particle.life / particle.maxLife;
|
||||||
Uint8 alpha = (Uint8)(128 * std::min(1.0f, lifeRatio * 1.6f));
|
float alphaF = std::pow(std::max(0.0f, lifeRatio), 0.75f);
|
||||||
|
if (particle.crackle) {
|
||||||
// Color based on particle type
|
SDL_Color dynamicColor = sampleParticleColor(particle);
|
||||||
SDL_Color colors[] = {
|
float flicker = 0.55f + 0.45f * std::sin(particle.flickerSeed + particle.life * 0.018f);
|
||||||
{0, 240, 240, alpha}, // Cyan
|
Uint8 alpha = static_cast<Uint8>(alphaF * flicker * 255.0f);
|
||||||
{240, 160, 0, alpha}, // Orange
|
if (alpha == 0) continue;
|
||||||
{0, 0, 240, alpha}, // Blue
|
SDL_SetRenderDrawColor(renderer, dynamicColor.r, dynamicColor.g, dynamicColor.b, alpha);
|
||||||
{240, 240, 0, alpha}, // Yellow
|
float stretch = particle.size * (2.5f + (1.0f - lifeRatio) * 1.3f);
|
||||||
{0, 240, 0, alpha}, // Green
|
float angle = particle.flickerSeed * 3.0f + particle.life * 0.004f;
|
||||||
{160, 0, 240, alpha}, // Purple
|
float dx = std::cos(angle) * stretch;
|
||||||
{240, 0, 0, alpha} // Red
|
float dy = std::sin(angle) * stretch * 0.7f;
|
||||||
};
|
SDL_RenderLine(renderer,
|
||||||
|
static_cast<int>(particle.x - dx),
|
||||||
SDL_Color color = colors[(particle.type - 1) % 7];
|
static_cast<int>(particle.y - dy),
|
||||||
SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
|
static_cast<int>(particle.x + dx),
|
||||||
|
static_cast<int>(particle.y + dy));
|
||||||
// For small particles, just draw a filled rect (much faster)
|
SDL_RenderLine(renderer,
|
||||||
if (particle.size <= 4.0f) {
|
static_cast<int>(particle.x - dy * 0.45f),
|
||||||
SDL_FRect rect{particle.x - particle.size/2, particle.y - particle.size/2, particle.size, particle.size};
|
static_cast<int>(particle.y + dx * 0.45f),
|
||||||
SDL_RenderFillRect(renderer, &rect);
|
static_cast<int>(particle.x + dy * 0.45f),
|
||||||
|
static_cast<int>(particle.y - dx * 0.45f));
|
||||||
} else {
|
} else {
|
||||||
// For larger particles, draw a simple circle approximation
|
SDL_Color dynamicColor = sampleParticleColor(particle);
|
||||||
float radius = particle.size / 2.0f;
|
Uint8 alpha = static_cast<Uint8>(alphaF * 255.0f);
|
||||||
int r = (int)radius;
|
SDL_SetRenderDrawColor(renderer, dynamicColor.r, dynamicColor.g, dynamicColor.b, alpha);
|
||||||
for (int dy = -r; dy <= r; ++dy) {
|
float radius = particle.size * (0.5f + 0.3f * lifeRatio);
|
||||||
int width = (int)sqrt(radius*radius - dy*dy) * 2;
|
renderCircle(particle.x, particle.y, radius);
|
||||||
if (width > 0) {
|
|
||||||
SDL_FRect line{particle.x - width/2.0f, particle.y + dy, (float)width, 1.0f};
|
|
||||||
SDL_RenderFillRect(renderer, &line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <array>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
@ -73,21 +74,52 @@ public:
|
|||||||
|
|
||||||
// Fireworks system (for menu animation)
|
// Fireworks system (for menu animation)
|
||||||
struct BlockParticle {
|
struct BlockParticle {
|
||||||
float x, y, vx, vy;
|
float x = 0.0f;
|
||||||
int type;
|
float y = 0.0f;
|
||||||
float life, maxLife;
|
float vx = 0.0f;
|
||||||
float size;
|
float vy = 0.0f;
|
||||||
int generation = 0; // 0 = primary, 1 = secondary, 2 = tertiary
|
float size = 0.0f;
|
||||||
bool hasExploded = false; // Track if this particle has spawned children
|
float life = 0.0f;
|
||||||
|
float maxLife = 0.0f;
|
||||||
|
SDL_Color color{255, 255, 255, 255};
|
||||||
|
SDL_Color accentColor{255, 255, 255, 255};
|
||||||
|
int generation = 0;
|
||||||
|
bool hasExploded = false;
|
||||||
|
bool crackle = false;
|
||||||
|
float flickerSeed = 0.0f;
|
||||||
|
bool dualColor = false;
|
||||||
|
float colorBlendSpeed = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SparkParticle {
|
||||||
|
float x = 0.0f;
|
||||||
|
float y = 0.0f;
|
||||||
|
float vx = 0.0f;
|
||||||
|
float vy = 0.0f;
|
||||||
|
float life = 0.0f;
|
||||||
|
float maxLife = 0.0f;
|
||||||
|
float thickness = 1.0f;
|
||||||
|
SDL_Color color{255, 255, 255, 255};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TetrisFirework {
|
struct TetrisFirework {
|
||||||
std::vector<BlockParticle> particles;
|
std::vector<BlockParticle> particles;
|
||||||
|
std::vector<SparkParticle> sparks;
|
||||||
bool active = false;
|
bool active = false;
|
||||||
|
float originX = 0.0f;
|
||||||
|
float originY = 0.0f;
|
||||||
|
float elapsedMs = 0.0f;
|
||||||
|
int nextBurst = 0;
|
||||||
|
std::array<float, 3> burstSchedule{0.0f, 250.0f, 520.0f};
|
||||||
|
std::array<SDL_Color, 3> burstColors{};
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<TetrisFirework> fireworks;
|
std::vector<TetrisFirework> fireworks;
|
||||||
Uint64 lastFireworkTime = 0;
|
Uint64 lastFireworkTime = 0;
|
||||||
|
bool pendingStaggerFirework = false;
|
||||||
|
Uint64 nextStaggerFireworkTime = 0;
|
||||||
|
float lastFireworkX = 0.0f;
|
||||||
|
float lastFireworkY = 0.0f;
|
||||||
|
|
||||||
// Fireworks management methods
|
// Fireworks management methods
|
||||||
void updateFireworks(double frameMs);
|
void updateFireworks(double frameMs);
|
||||||
|
|||||||
Reference in New Issue
Block a user