Compare commits

...

3 Commits

Author SHA1 Message Date
cfbb4e4c86 Merge branch 'feature/GridAnimationFx' into develop 2025-11-30 16:48:57 +01:00
9d89106275 fix particle 2025-11-30 16:48:45 +01:00
ace2e6acdc updated stars in game play grid 2025-11-30 16:30:47 +01:00
4 changed files with 164 additions and 41 deletions

View File

@ -23,6 +23,22 @@ void Starfield3D::resize(int w, int h) {
centerY = height * 0.5f;
}
void Starfield3D::setMagnetTarget(float localX, float localY, float strength) {
if (strength <= 0.0f) {
clearMagnetTarget();
return;
}
magnetActive = true;
magnetStrength = strength;
magnetX = std::clamp(localX, 0.0f, static_cast<float>(width));
magnetY = std::clamp(localY, 0.0f, static_cast<float>(height));
}
void Starfield3D::clearMagnetTarget() {
magnetActive = false;
magnetStrength = 0.0f;
}
float Starfield3D::randomFloat(float min, float max) {
std::uniform_real_distribution<float> dist(min, max);
return dist(rng);
@ -147,13 +163,24 @@ void Starfield3D::drawStar(SDL_Renderer* renderer, float x, float y, SDL_Color c
}
void Starfield3D::draw(SDL_Renderer* renderer, float offsetX, float offsetY, float alphaScale, bool grayscale) {
const bool useMagnet = magnetActive && magnetStrength > 0.0f;
for (const Star3D& star : stars) {
// Calculate perspective projection factor
const float k = DEPTH_FACTOR / star.z;
// Calculate screen position with perspective
const float px = star.x * k + centerX;
const float py = star.y * k + centerY;
float px = star.x * k + centerX;
float py = star.y * k + centerY;
if (useMagnet) {
float dx = magnetX - px;
float dy = magnetY - py;
float dist = std::sqrt(dx * dx + dy * dy);
float pull = magnetStrength / (magnetStrength + dist + 1.0f);
pull = std::clamp(pull, 0.0f, 0.35f);
px += dx * pull;
py += dy * pull;
}
// Only draw stars that are within the viewport
if (px >= 0.0f && px <= static_cast<float>(width) &&

View File

@ -15,6 +15,8 @@ public:
void update(float deltaTime);
void draw(SDL_Renderer* renderer, float offsetX = 0.0f, float offsetY = 0.0f, float alphaScale = 1.0f, bool grayscale = false);
void resize(int width, int height);
void setMagnetTarget(float localX, float localY, float strength);
void clearMagnetTarget();
private:
struct Star3D {
@ -37,6 +39,10 @@ private:
std::vector<Star3D> stars;
int width{0}, height{0};
float centerX{0}, centerY{0};
bool magnetActive{false};
float magnetX{0.0f};
float magnetY{0.0f};
float magnetStrength{0.0f};
// Random number generator
std::mt19937 rng;

View File

@ -265,6 +265,39 @@ void GameRenderer::renderPlayingState(
const float deltaSeconds = std::clamp(static_cast<float>(sparkDeltaMs) / 1000.0f, 0.0f, 0.033f);
s_inGridStarfield.update(deltaSeconds);
bool appliedMagnet = false;
if (game) {
const Game::Piece& activePiece = game->current();
const int pieceType = static_cast<int>(activePiece.type);
if (pieceType >= 0 && pieceType < PIECE_COUNT) {
float sumLocalX = 0.0f;
float sumLocalY = 0.0f;
int filledCells = 0;
for (int cy = 0; cy < 4; ++cy) {
for (int cx = 0; cx < 4; ++cx) {
if (!Game::cellFilled(activePiece, cx, cy)) {
continue;
}
sumLocalX += (activePiece.x + cx + 0.5f) * finalBlockSize;
sumLocalY += (activePiece.y + cy + 0.5f) * finalBlockSize;
++filledCells;
}
}
if (filledCells > 0) {
float magnetLocalX = sumLocalX / static_cast<float>(filledCells);
float magnetLocalY = sumLocalY / static_cast<float>(filledCells);
magnetLocalX = std::clamp(magnetLocalX, 0.0f, GRID_W);
magnetLocalY = std::clamp(magnetLocalY, 0.0f, GRID_H);
const float magnetStrength = finalBlockSize * 2.2f;
s_inGridStarfield.setMagnetTarget(magnetLocalX, magnetLocalY, magnetStrength);
appliedMagnet = true;
}
}
}
if (!appliedMagnet) {
s_inGridStarfield.clearMagnetTarget();
}
SDL_BlendMode oldBlend = SDL_BLENDMODE_NONE;
SDL_GetRenderDrawBlendMode(renderer, &oldBlend);
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);

View File

@ -523,27 +523,37 @@ static bool helpOverlayPausedGame = false;
static void drawBlockTexture(SDL_Renderer* renderer, SDL_Texture* blocksTex, float x, float y, float size, int blockType);
// -----------------------------------------------------------------------------
struct BlockParticle {
float x, y, vx, vy, size, alpha, decay;
int blockType; // 0..6
float x{}, y{};
float vx{}, vy{};
float size{}, alpha{}, decay{};
float wobblePhase{}, wobbleSpeed{};
float coreHeat{};
BlockParticle(float sx, float sy)
: x(sx), y(sy) {
float angle = (rand() % 628) / 100.0f; // 0..2pi
float speed = 1.5f + (rand() % 350) / 100.0f; // ~1.5..5.0
vx = std::cos(angle) * speed;
vy = std::sin(angle) * speed;
size = 6.0f + (rand() % 50) / 10.0f; // 6..11 px
const float spreadDeg = 35.0f;
const float angleDeg = -90.0f + spreadDeg * ((rand() % 200) / 100.0f - 1.0f); // bias upward
const float angleRad = angleDeg * 3.1415926f / 180.0f;
float speed = 1.3f + (rand() % 220) / 80.0f; // ~1.3..4.05
vx = std::cos(angleRad) * speed * 0.55f;
vy = std::sin(angleRad) * speed;
size = 6.0f + (rand() % 40) / 10.0f; // 6..10 px
alpha = 1.0f;
decay = 0.012f + (rand() % 200) / 10000.0f; // 0.012..0.032
blockType = rand() % 7; // choose a tetris color
decay = 0.0095f + (rand() % 180) / 12000.0f; // 0.0095..0.0245
wobblePhase = (rand() % 628) / 100.0f;
wobbleSpeed = 0.08f + (rand() % 60) / 600.0f;
coreHeat = 0.65f + (rand() % 35) / 100.0f;
}
bool update() {
vx *= 0.985f; // friction
vy = vy * 0.985f + 0.07f; // gravity
vx *= 0.992f;
vy = vy * 0.985f - 0.015f; // buoyancy pushes upward (negative vy)
x += vx;
y += vy;
wobblePhase += wobbleSpeed;
x += std::sin(wobblePhase) * 0.12f;
alpha -= decay;
size = std::max(2.0f, size - 0.04f);
return alpha > 0.02f;
size = std::max(1.8f, size - 0.03f);
coreHeat = std::max(0.0f, coreHeat - decay * 0.6f);
return alpha > 0.03f;
}
};
@ -592,36 +602,83 @@ static void updateFireworks(double frameMs) {
}
// Primary implementation that accepts a texture pointer
static void drawFireworks_impl(SDL_Renderer* renderer, SDL_Texture* blocksTexture) {
static SDL_Color blendFireColor(float heat, float alphaScale, Uint8 minG, Uint8 minB) {
heat = std::clamp(heat, 0.0f, 1.0f);
Uint8 r = 255;
Uint8 g = static_cast<Uint8>(std::clamp(120.0f + heat * (255.0f - 120.0f), float(minG), 255.0f));
Uint8 b = static_cast<Uint8>(std::clamp(40.0f + (1.0f - heat) * 60.0f, float(minB), 255.0f));
Uint8 a = static_cast<Uint8>(std::clamp(alphaScale * 255.0f, 0.0f, 255.0f));
return SDL_Color{r, g, b, a};
}
static void drawFireworks_impl(SDL_Renderer* renderer, SDL_Texture*) {
SDL_BlendMode previousBlend = SDL_BLENDMODE_NONE;
SDL_GetRenderDrawBlendMode(renderer, &previousBlend);
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_ADD);
static constexpr int quadIndices[6] = {0, 1, 2, 2, 1, 3};
auto makeVertex = [](float px, float py, SDL_Color c) {
SDL_Vertex v{};
v.position.x = px;
v.position.y = py;
v.color = SDL_FColor{
c.r / 255.0f,
c.g / 255.0f,
c.b / 255.0f,
c.a / 255.0f
};
return v;
};
for (auto& f : fireworks) {
// Particle draw uses the texture pointer passed into drawBlockTexture calls from f.draw
// We'll set a thread-local-ish variable by passing the texture as an argument to draw
// routines or using the provided texture in the particle's draw path.
// For simplicity, the particle draw function below will reference a global symbol
// via an argument — we adapt by providing the texture when calling drawBlockTexture.
// Implementation: call a small lambda that temporarily binds the texture for drawBlockTexture.
struct Drawer { SDL_Renderer* r; SDL_Texture* tex; void drawParticle(struct BlockParticle& p) {
if (tex) {
Uint8 prevA = 255;
SDL_GetTextureAlphaMod(tex, &prevA);
Uint8 setA = Uint8(std::max(0.0f, std::min(1.0f, p.alpha)) * 255.0f);
SDL_SetTextureAlphaMod(tex, setA);
// Note: color modulation will be applied by callers of drawBlockTexture where needed
// but we mimic behavior from previous implementation by leaving color mod as default.
drawBlockTexture(r, tex, p.x - p.size * 0.5f, p.y - p.size * 0.5f, p.size, p.blockType);
SDL_SetTextureAlphaMod(tex, prevA);
SDL_SetTextureColorMod(tex, 255, 255, 255);
} else {
SDL_SetRenderDrawColor(r, 255, 255, 255, Uint8(p.alpha * 255));
SDL_FRect rect{p.x - p.size/2, p.y - p.size/2, p.size, p.size};
SDL_RenderFillRect(r, &rect);
}
}
} drawer{renderer, blocksTexture};
for (auto &p : f.particles) {
drawer.drawParticle(p);
const float heat = std::clamp(p.alpha * 1.25f + p.coreHeat * 0.5f, 0.0f, 1.0f);
SDL_Color glowColor = blendFireColor(0.45f + heat * 0.55f, p.alpha * 0.55f, 100, 40);
SDL_Color tailBaseColor = blendFireColor(heat * 0.75f, p.alpha * 0.5f, 70, 25);
SDL_Color tailTipColor = blendFireColor(heat * 0.35f, p.alpha * 0.2f, 40, 15);
SDL_Color coreColor = blendFireColor(heat, std::min(1.0f, p.alpha * 1.1f), 150, 80);
float velLen = std::sqrt(p.vx * p.vx + p.vy * p.vy);
SDL_FPoint dir = velLen > 0.001f ? SDL_FPoint{p.vx / velLen, p.vy / velLen}
: SDL_FPoint{0.0f, -1.0f};
SDL_FPoint perp{-dir.y, dir.x};
const float baseWidth = std::max(0.8f, p.size * 0.55f);
const float tipWidth = baseWidth * 0.35f;
const float tailLen = p.size * (3.0f + (1.0f - p.alpha) * 1.8f);
SDL_FPoint base{p.x, p.y};
SDL_FPoint tip{p.x + dir.x * tailLen, p.y + dir.y * tailLen};
SDL_Vertex tailVerts[4];
tailVerts[0] = makeVertex(base.x + perp.x * baseWidth, base.y + perp.y * baseWidth, tailBaseColor);
tailVerts[1] = makeVertex(base.x - perp.x * baseWidth, base.y - perp.y * baseWidth, tailBaseColor);
tailVerts[2] = makeVertex(tip.x + perp.x * tipWidth, tip.y + perp.y * tipWidth, tailTipColor);
tailVerts[3] = makeVertex(tip.x - perp.x * tipWidth, tip.y - perp.y * tipWidth, tailTipColor);
SDL_RenderGeometry(renderer, nullptr, tailVerts, 4, quadIndices, 6);
const float glowAlong = p.size * 0.95f;
const float glowAcross = p.size * 0.6f;
SDL_Vertex glowVerts[4];
glowVerts[0] = makeVertex(base.x + dir.x * glowAlong, base.y + dir.y * glowAlong, glowColor);
glowVerts[1] = makeVertex(base.x - dir.x * glowAlong, base.y - dir.y * glowAlong, glowColor);
glowVerts[2] = makeVertex(base.x + perp.x * glowAcross, base.y + perp.y * glowAcross, glowColor);
glowVerts[3] = makeVertex(base.x - perp.x * glowAcross, base.y - perp.y * glowAcross, glowColor);
SDL_RenderGeometry(renderer, nullptr, glowVerts, 4, quadIndices, 6);
const float coreWidth = p.size * 0.35f;
const float coreHeight = p.size * 0.9f;
SDL_Vertex coreVerts[4];
coreVerts[0] = makeVertex(base.x + perp.x * coreWidth, base.y + perp.y * coreWidth, coreColor);
coreVerts[1] = makeVertex(base.x - perp.x * coreWidth, base.y - perp.y * coreWidth, coreColor);
coreVerts[2] = makeVertex(base.x + dir.x * coreHeight, base.y + dir.y * coreHeight, coreColor);
coreVerts[3] = makeVertex(base.x - dir.x * coreHeight, base.y - dir.y * coreHeight, coreColor);
SDL_RenderGeometry(renderer, nullptr, coreVerts, 4, quadIndices, 6);
}
}
SDL_SetRenderDrawBlendMode(renderer, previousBlend);
}
// External wrappers for use by other translation units (MenuState)
// Expect callers to pass the blocks texture via StateContext so we avoid globals.