diff --git a/src/graphics/renderers/GameRenderer.cpp b/src/graphics/renderers/GameRenderer.cpp index f875fd5..5eaa425 100644 --- a/src/graphics/renderers/GameRenderer.cpp +++ b/src/graphics/renderers/GameRenderer.cpp @@ -25,6 +25,18 @@ struct ImpactSpark { SDL_Color color{255, 255, 255, 255}; }; +struct Sparkle { + float x = 0.0f; + float y = 0.0f; + float vx = 0.0f; + float vy = 0.0f; + float lifeMs = 0.0f; + float maxLifeMs = 0.0f; + float size = 0.0f; + SDL_Color color{255, 255, 255, 255}; + float pulse = 0.0f; +}; + struct ActivePieceSmoothState { uint64_t sequence = 0; float visualX = 0.0f; @@ -35,6 +47,8 @@ ActivePieceSmoothState s_activePieceSmooth; Starfield3D s_inGridStarfield; bool s_starfieldInitialized = false; +std::vector s_sparkles; +float s_sparkleSpawnAcc = 0.0f; } // Color constants (copied from main.cpp) @@ -312,6 +326,121 @@ void GameRenderer::renderPlayingState( SDL_GetRenderDrawBlendMode(renderer, &oldBlend); SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); s_inGridStarfield.draw(renderer, gridX, gridY, 0.22f, true); + + // Update and spawn ambient sparkles inside/around the grid + // Use the same RNG and timing values used for impact sparks + if (!game->isPaused()) { + // Spawn rate: ~10 sparks/sec total (adjustable) + const float spawnInterval = 0.08f; // seconds + s_sparkleSpawnAcc += deltaSeconds; + while (s_sparkleSpawnAcc >= spawnInterval) { + s_sparkleSpawnAcc -= spawnInterval; + + Sparkle s; + // Choose spawn area: near active piece magnet if present, otherwise along top/border + bool spawnNearPiece = appliedMagnet && (std::uniform_real_distribution(0.0f,1.0f)(s_impactRng) > 0.35f); + float sx = 0.0f, sy = 0.0f; + if (spawnNearPiece) { + // Use starfield magnet target if set (approx center of active piece) + // Random jitter around magnet + float jitterX = std::uniform_real_distribution(-finalBlockSize * 1.2f, finalBlockSize * 1.2f)(s_impactRng); + float jitterY = std::uniform_real_distribution(-finalBlockSize * 1.2f, finalBlockSize * 1.2f)(s_impactRng); + // s_inGridStarfield stores magnet in local coords when used; approximate from magnet calculations above + // We'll center near grid center if magnet not available + sx = std::clamp(GRID_W * 0.5f + jitterX, -finalBlockSize * 2.0f, GRID_W + finalBlockSize * 2.0f); + sy = std::clamp(GRID_H * 0.4f + jitterY, -finalBlockSize * 2.0f, GRID_H + finalBlockSize * 2.0f); + } else { + // Spawn along border: choose side and position + float side = std::uniform_real_distribution(0.0f, 1.0f)(s_impactRng); + // Border band width (how far outside the grid sparks can appear) + const float borderBand = std::max(12.0f, finalBlockSize * 1.0f); + if (side < 0.2f) { // left (outside) + sx = std::uniform_real_distribution(-borderBand, 0.0f)(s_impactRng); + sy = std::uniform_real_distribution(-borderBand, GRID_H + borderBand)(s_impactRng); + } else if (side < 0.4f) { // right (outside) + sx = std::uniform_real_distribution(GRID_W, GRID_W + borderBand)(s_impactRng); + sy = std::uniform_real_distribution(-borderBand, GRID_H + borderBand)(s_impactRng); + } else if (side < 0.6f) { // top (outside) + sx = std::uniform_real_distribution(-borderBand, GRID_W + borderBand)(s_impactRng); + sy = std::uniform_real_distribution(-borderBand, 0.0f)(s_impactRng); + } else if (side < 0.9f) { // top/inside border area + sx = std::uniform_real_distribution(0.0f, GRID_W)(s_impactRng); + sy = std::uniform_real_distribution(0.0f, finalBlockSize * 2.0f)(s_impactRng); + } else { // bottom (outside) + sx = std::uniform_real_distribution(-borderBand, GRID_W + borderBand)(s_impactRng); + sy = std::uniform_real_distribution(GRID_H, GRID_H + borderBand)(s_impactRng); + } + } + + s.x = sx; + s.y = sy; + float speed = std::uniform_real_distribution(10.0f, 60.0f)(s_impactRng); + float ang = std::uniform_real_distribution(-3.14159f, 3.14159f)(s_impactRng); + s.vx = std::cos(ang) * speed; + s.vy = std::sin(ang) * speed * 0.25f; // slower vertical movement + s.maxLifeMs = std::uniform_real_distribution(350.0f, 900.0f)(s_impactRng); + s.lifeMs = s.maxLifeMs; + s.size = std::uniform_real_distribution(1.5f, 5.0f)(s_impactRng); + // Soft color range towards warm/cyan tints + if (std::uniform_real_distribution(0.0f,1.0f)(s_impactRng) < 0.5f) { + s.color = SDL_Color{255, 230, 180, 255}; + } else { + s.color = SDL_Color{180, 220, 255, 255}; + } + s.pulse = std::uniform_real_distribution(0.0f, 6.28f)(s_impactRng); + + s_sparkles.push_back(s); + } + } + + // Update and draw sparkles + if (!s_sparkles.empty()) { + auto it = s_sparkles.begin(); + while (it != s_sparkles.end()) { + Sparkle &sp = *it; + sp.lifeMs -= sparkDeltaMs; + if (sp.lifeMs <= 0.0f) { + // On expiration, spawn a small burst of ImpactSparks (smaller boxes) + const int burstCount = std::uniform_int_distribution(4, 8)(s_impactRng); + for (int bi = 0; bi < burstCount; ++bi) { + ImpactSpark ps; + // Position in absolute coords (same space as other impact sparks) + ps.x = gridX + sp.x + std::uniform_real_distribution(-2.0f, 2.0f)(s_impactRng); + ps.y = gridY + sp.y + std::uniform_real_distribution(-2.0f, 2.0f)(s_impactRng); + float ang = std::uniform_real_distribution(0.0f, 6.2831853f)(s_impactRng); + float speed = std::uniform_real_distribution(10.0f, 120.0f)(s_impactRng); + ps.vx = std::cos(ang) * speed; + ps.vy = std::sin(ang) * speed * 0.8f; + ps.maxLifeMs = std::uniform_real_distribution(220.0f, 500.0f)(s_impactRng); + ps.lifeMs = ps.maxLifeMs; + ps.size = std::max(1.0f, sp.size * 0.5f); + ps.color = sp.color; + s_impactSparks.push_back(ps); + } + + it = s_sparkles.erase(it); + continue; + } + float lifeRatio = sp.lifeMs / sp.maxLifeMs; + // simple motion + sp.x += sp.vx * deltaSeconds; + sp.y += sp.vy * deltaSeconds; + sp.vy *= 0.995f; // slight damping + sp.pulse += deltaSeconds * 8.0f; + + // Fade and pulse alpha + float pulse = 0.5f + 0.5f * std::sin(sp.pulse); + Uint8 alpha = static_cast(std::clamp(lifeRatio * pulse, 0.0f, 1.0f) * 255.0f); + SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); + SDL_SetRenderDrawColor(renderer, sp.color.r, sp.color.g, sp.color.b, alpha); + float half = sp.size * 0.5f; + SDL_FRect fr{gridX + sp.x - half, gridY + sp.y - half, sp.size, sp.size}; + SDL_RenderFillRect(renderer, &fr); + + ++it; + } + } + SDL_SetRenderDrawBlendMode(renderer, oldBlend); // Draw next piece preview panel border