latest state
This commit is contained in:
@ -20,6 +20,8 @@ void SpaceWarp::init(int w, int h, int starCount) {
|
||||
for (auto& star : stars) {
|
||||
respawn(star, true);
|
||||
}
|
||||
comets.clear();
|
||||
cometSpawnTimer = randomRange(settings.cometSpawnIntervalMin, settings.cometSpawnIntervalMax);
|
||||
}
|
||||
|
||||
void SpaceWarp::resize(int w, int h) {
|
||||
@ -33,6 +35,7 @@ void SpaceWarp::resize(int w, int h) {
|
||||
void SpaceWarp::setSettings(const SpaceWarpSettings& newSettings) {
|
||||
settings = newSettings;
|
||||
warpFactor = std::max(width, height) * settings.warpFactorScale;
|
||||
cometSpawnTimer = std::clamp(cometSpawnTimer, settings.cometSpawnIntervalMin, settings.cometSpawnIntervalMax);
|
||||
}
|
||||
|
||||
void SpaceWarp::setFlightMode(SpaceWarpFlightMode mode) {
|
||||
@ -69,6 +72,7 @@ void SpaceWarp::setAutoPilotEnabled(bool enabled) {
|
||||
flightMode = SpaceWarpFlightMode::Custom;
|
||||
motionTarget = motion;
|
||||
autoTimer = 0.0f;
|
||||
scheduleNewAutoTarget();
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,6 +86,32 @@ void SpaceWarp::scheduleNewAutoTarget() {
|
||||
autoTimer = randomRange(autoMinInterval, autoMaxInterval);
|
||||
}
|
||||
|
||||
void SpaceWarp::spawnComet() {
|
||||
WarpComet comet;
|
||||
float aspect = static_cast<float>(width) / static_cast<float>(std::max(1, height));
|
||||
float normalizedAspect = std::max(aspect, MIN_ASPECT);
|
||||
float xRange = settings.baseSpawnRange * 1.2f * (aspect >= 1.0f ? aspect : 1.0f);
|
||||
float yRange = settings.baseSpawnRange * 1.2f * (aspect >= 1.0f ? 1.0f : (1.0f / normalizedAspect));
|
||||
comet.x = randomRange(-xRange, xRange);
|
||||
comet.y = randomRange(-yRange, yRange);
|
||||
comet.z = randomRange(minDepth + 4.0f, maxDepth);
|
||||
float baseSpeed = randomRange(settings.minSpeed, settings.maxSpeed);
|
||||
float multiplier = randomRange(settings.cometSpeedMultiplierMin, settings.cometSpeedMultiplierMax);
|
||||
comet.speed = baseSpeed * multiplier;
|
||||
comet.size = randomRange(settings.cometMinSize, settings.cometMaxSize);
|
||||
comet.trailLength = randomRange(settings.cometMinTrail, settings.cometMaxTrail);
|
||||
comet.life = randomRange(1.8f, 3.4f);
|
||||
comet.maxLife = comet.life;
|
||||
float shade = randomRange(0.85f, 1.0f);
|
||||
Uint8 c = static_cast<Uint8>(std::clamp(220.0f + shade * 35.0f, 0.0f, 255.0f));
|
||||
comet.color = SDL_Color{c, Uint8(std::min(255.0f, c * 0.95f)), 255, 255};
|
||||
comet.prevScreenX = centerX;
|
||||
comet.prevScreenY = centerY;
|
||||
comet.screenX = centerX;
|
||||
comet.screenY = centerY;
|
||||
comets.push_back(comet);
|
||||
}
|
||||
|
||||
float SpaceWarp::randomRange(float min, float max) {
|
||||
std::uniform_real_distribution<float> dist(min, max);
|
||||
return dist(rng);
|
||||
@ -112,12 +142,16 @@ void SpaceWarp::respawn(WarpStar& star, bool randomDepth) {
|
||||
}
|
||||
|
||||
bool SpaceWarp::project(const WarpStar& star, float& outX, float& outY) const {
|
||||
if (star.z <= minDepth) {
|
||||
return projectPoint(star.x, star.y, star.z, outX, outY);
|
||||
}
|
||||
|
||||
bool SpaceWarp::projectPoint(float x, float y, float z, float& outX, float& outY) const {
|
||||
if (z <= minDepth) {
|
||||
return false;
|
||||
}
|
||||
float perspective = warpFactor / (star.z + 0.001f);
|
||||
outX = centerX + star.x * perspective;
|
||||
outY = centerY + star.y * perspective;
|
||||
float perspective = warpFactor / (z + 0.001f);
|
||||
outX = centerX + x * perspective;
|
||||
outY = centerY + y * perspective;
|
||||
const float margin = settings.spawnMargin;
|
||||
return outX >= -margin && outX <= width + margin && outY >= -margin && outY <= height + margin;
|
||||
}
|
||||
@ -127,6 +161,14 @@ void SpaceWarp::update(float deltaSeconds) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (settings.cometSpawnIntervalMax > 0.0f) {
|
||||
cometSpawnTimer -= deltaSeconds;
|
||||
if (cometSpawnTimer <= 0.0f) {
|
||||
spawnComet();
|
||||
cometSpawnTimer = randomRange(settings.cometSpawnIntervalMin, settings.cometSpawnIntervalMax);
|
||||
}
|
||||
}
|
||||
|
||||
if (autoPilotEnabled) {
|
||||
autoTimer -= deltaSeconds;
|
||||
if (autoTimer <= 0.0f) {
|
||||
@ -188,6 +230,51 @@ void SpaceWarp::update(float deltaSeconds) {
|
||||
star.prevScreenY = star.screenY - dy * scale;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto it = comets.begin(); it != comets.end();) {
|
||||
auto& comet = *it;
|
||||
comet.life -= deltaSeconds;
|
||||
comet.z -= comet.speed * deltaSeconds * forwardScale;
|
||||
bool expired = comet.life <= 0.0f;
|
||||
if (!movingBackward) {
|
||||
if (comet.z <= minDepth * 0.35f) expired = true;
|
||||
} else {
|
||||
if (comet.z >= maxDepth + 40.0f) expired = true;
|
||||
}
|
||||
|
||||
float closeness = 1.0f - std::clamp(comet.z / maxDepth, 0.0f, 1.0f);
|
||||
float driftScale = (0.45f + closeness * 1.6f);
|
||||
comet.x += lateralSpeed * deltaSeconds * driftScale;
|
||||
comet.y += verticalSpeed * deltaSeconds * driftScale;
|
||||
|
||||
float sx = 0.0f;
|
||||
float sy = 0.0f;
|
||||
if (!projectPoint(comet.x, comet.y, comet.z, sx, sy)) {
|
||||
expired = true;
|
||||
} else {
|
||||
comet.prevScreenX = comet.screenX;
|
||||
comet.prevScreenY = comet.screenY;
|
||||
comet.screenX = sx;
|
||||
comet.screenY = sy;
|
||||
|
||||
float dx = comet.screenX - comet.prevScreenX;
|
||||
float dy = comet.screenY - comet.prevScreenY;
|
||||
float lenSq = dx * dx + dy * dy;
|
||||
float maxTrail = std::max(comet.trailLength, 0.0f);
|
||||
if (maxTrail > 0.0f && lenSq > maxTrail * maxTrail) {
|
||||
float len = std::sqrt(lenSq);
|
||||
float scale = maxTrail / len;
|
||||
comet.prevScreenX = comet.screenX - dx * scale;
|
||||
comet.prevScreenY = comet.screenY - dy * scale;
|
||||
}
|
||||
}
|
||||
|
||||
if (expired) {
|
||||
it = comets.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SpaceWarp::draw(SDL_Renderer* renderer, float alphaScale) {
|
||||
@ -224,5 +311,16 @@ void SpaceWarp::draw(SDL_Renderer* renderer, float alphaScale) {
|
||||
SDL_RenderFillRect(renderer, &dot);
|
||||
}
|
||||
|
||||
for (const auto& comet : comets) {
|
||||
float lifeNorm = std::clamp(comet.life / comet.maxLife, 0.0f, 1.0f);
|
||||
Uint8 alpha = static_cast<Uint8>(std::clamp(220.0f * lifeNorm, 0.0f, 255.0f));
|
||||
SDL_SetRenderDrawColor(renderer, comet.color.r, comet.color.g, comet.color.b, alpha);
|
||||
SDL_RenderLine(renderer, comet.prevScreenX, comet.prevScreenY, comet.screenX, comet.screenY);
|
||||
|
||||
float size = comet.size * (0.8f + (1.0f - lifeNorm) * 0.6f);
|
||||
SDL_FRect head{comet.screenX - size * 0.5f, comet.screenY - size * 0.5f, size, size};
|
||||
SDL_RenderFillRect(renderer, &head);
|
||||
}
|
||||
|
||||
SDL_SetRenderDrawBlendMode(renderer, previous);
|
||||
}
|
||||
|
||||
@ -23,6 +23,14 @@ struct SpaceWarpSettings {
|
||||
bool drawTrails = true; // when true, also render streak lines for hyper-speed look
|
||||
float trailAlphaScale = 0.75f; // relative opacity for streak lines vs dots
|
||||
float maxTrailLength = 36.0f; // clamp length of each streak in pixels
|
||||
float cometSpawnIntervalMin = 2.8f; // minimum seconds between comet spawns
|
||||
float cometSpawnIntervalMax = 6.5f; // maximum seconds between comet spawns
|
||||
float cometSpeedMultiplierMin = 2.2f;// min multiplier for comet forward velocity
|
||||
float cometSpeedMultiplierMax = 4.5f;// max multiplier for comet forward velocity
|
||||
float cometMinTrail = 140.0f; // minimum comet trail length in pixels
|
||||
float cometMaxTrail = 280.0f; // maximum comet trail length in pixels
|
||||
float cometMinSize = 3.5f; // minimum comet head size
|
||||
float cometMaxSize = 6.5f; // maximum comet head size
|
||||
};
|
||||
|
||||
struct SpaceWarpFlightMotion {
|
||||
@ -69,11 +77,30 @@ private:
|
||||
Uint8 baseShade = 220;
|
||||
};
|
||||
|
||||
struct WarpComet {
|
||||
float x = 0.0f;
|
||||
float y = 0.0f;
|
||||
float z = 0.0f;
|
||||
float speed = 0.0f;
|
||||
float life = 0.0f;
|
||||
float maxLife = 0.0f;
|
||||
float prevScreenX = 0.0f;
|
||||
float prevScreenY = 0.0f;
|
||||
float screenX = 0.0f;
|
||||
float screenY = 0.0f;
|
||||
float trailLength = 160.0f;
|
||||
float size = 4.0f;
|
||||
SDL_Color color{255, 255, 255, 255};
|
||||
};
|
||||
|
||||
void respawn(WarpStar& star, bool randomDepth = true);
|
||||
bool project(const WarpStar& star, float& outX, float& outY) const;
|
||||
bool projectPoint(float x, float y, float z, float& outX, float& outY) const;
|
||||
float randomRange(float min, float max);
|
||||
void spawnComet();
|
||||
|
||||
std::vector<WarpStar> stars;
|
||||
std::vector<WarpComet> comets;
|
||||
std::mt19937 rng;
|
||||
|
||||
int width = 0;
|
||||
@ -90,6 +117,7 @@ private:
|
||||
float autoMinInterval = 3.5f;
|
||||
float autoMaxInterval = 7.5f;
|
||||
SpaceWarpFlightMotion motionTarget{};
|
||||
float cometSpawnTimer = 0.0f;
|
||||
|
||||
float minDepth = 2.0f;
|
||||
float maxDepth = 320.0f;
|
||||
|
||||
188
src/graphics/renderers/UIRenderer.cpp
Normal file
188
src/graphics/renderers/UIRenderer.cpp
Normal file
@ -0,0 +1,188 @@
|
||||
#include "UIRenderer.h"
|
||||
#include "../ui/Font.h"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
void UIRenderer::drawSciFiPanel(SDL_Renderer* renderer, const SDL_FRect& rect, float alpha) {
|
||||
if (!renderer) return;
|
||||
|
||||
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
|
||||
Uint8 alphaUint = static_cast<Uint8>(std::clamp(alpha * 255.0f, 0.0f, 255.0f));
|
||||
|
||||
// Drop shadow
|
||||
SDL_FRect shadow{rect.x + 6.0f, rect.y + 10.0f, rect.w, rect.h};
|
||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, static_cast<Uint8>(120.0f * alpha));
|
||||
SDL_RenderFillRect(renderer, &shadow);
|
||||
|
||||
// Glow aura
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
SDL_FRect glow{rect.x - float(i * 2), rect.y - float(i * 2), rect.w + float(i * 4), rect.h + float(i * 4)};
|
||||
Uint8 glowAlpha = static_cast<Uint8>((42 - i * 8) * alpha);
|
||||
SDL_SetRenderDrawColor(renderer, 0, 180, 255, glowAlpha);
|
||||
SDL_RenderRect(renderer, &glow);
|
||||
}
|
||||
|
||||
// Body
|
||||
SDL_SetRenderDrawColor(renderer, 18, 30, 52, alphaUint);
|
||||
SDL_RenderFillRect(renderer, &rect);
|
||||
|
||||
// Border
|
||||
SDL_SetRenderDrawColor(renderer, 70, 120, 210, alphaUint);
|
||||
SDL_RenderRect(renderer, &rect);
|
||||
}
|
||||
|
||||
void UIRenderer::drawButton(SDL_Renderer* renderer, FontAtlas* font, float cx, float cy, float w, float h,
|
||||
const std::string& label, bool isHovered, bool isSelected,
|
||||
SDL_Color bgColor, SDL_Color borderColor, bool textOnly, SDL_Texture* icon) {
|
||||
if (!renderer) return;
|
||||
|
||||
float x = cx - w * 0.5f;
|
||||
float y = cy - h * 0.5f;
|
||||
|
||||
if (!textOnly) {
|
||||
// Adjust colors based on state
|
||||
if (isSelected) {
|
||||
bgColor = {160, 190, 255, 255};
|
||||
SDL_SetRenderDrawColor(renderer, 255, 220, 0, 110);
|
||||
SDL_FRect glow{x - 10, y - 10, w + 20, h + 20};
|
||||
SDL_RenderFillRect(renderer, &glow);
|
||||
} else if (isHovered) {
|
||||
bgColor = {static_cast<Uint8>(std::min(255, bgColor.r + 40)),
|
||||
static_cast<Uint8>(std::min(255, bgColor.g + 40)),
|
||||
static_cast<Uint8>(std::min(255, bgColor.b + 40)),
|
||||
bgColor.a};
|
||||
}
|
||||
|
||||
// Draw button background with border
|
||||
SDL_SetRenderDrawColor(renderer, borderColor.r, borderColor.g, borderColor.b, borderColor.a);
|
||||
SDL_FRect borderRect{x - 2, y - 2, w + 4, h + 4};
|
||||
SDL_RenderFillRect(renderer, &borderRect);
|
||||
|
||||
SDL_SetRenderDrawColor(renderer, bgColor.r, bgColor.g, bgColor.b, bgColor.a);
|
||||
SDL_FRect bgRect{x, y, w, h};
|
||||
SDL_RenderFillRect(renderer, &bgRect);
|
||||
}
|
||||
|
||||
// Draw icon if provided, otherwise draw text
|
||||
if (icon) {
|
||||
// Get icon dimensions
|
||||
float iconW = 0.0f, iconH = 0.0f;
|
||||
SDL_GetTextureSize(icon, &iconW, &iconH);
|
||||
|
||||
// Scale icon to fit nicely in button (60% of button height)
|
||||
float maxIconH = h * 0.6f;
|
||||
float scale = maxIconH / iconH;
|
||||
float scaledW = iconW * scale;
|
||||
float scaledH = iconH * scale;
|
||||
|
||||
// Center icon in button
|
||||
float iconX = cx - scaledW * 0.5f;
|
||||
float iconY = cy - scaledH * 0.5f;
|
||||
|
||||
// Apply yellow tint when selected
|
||||
if (isSelected) {
|
||||
SDL_SetTextureColorMod(icon, 255, 220, 0);
|
||||
} else {
|
||||
SDL_SetTextureColorMod(icon, 255, 255, 255);
|
||||
}
|
||||
|
||||
SDL_FRect iconRect{iconX, iconY, scaledW, scaledH};
|
||||
SDL_RenderTexture(renderer, icon, nullptr, &iconRect);
|
||||
|
||||
// Reset color mod
|
||||
SDL_SetTextureColorMod(icon, 255, 255, 255);
|
||||
} else if (font) {
|
||||
// Draw text
|
||||
float textScale = 1.5f;
|
||||
int textW = 0, textH = 0;
|
||||
font->measure(label, textScale, textW, textH);
|
||||
float tx = x + (w - static_cast<float>(textW)) * 0.5f;
|
||||
// Adjust vertical position for better alignment with background buttons
|
||||
float ty = y + (h - static_cast<float>(textH)) * 0.5f + 2.0f;
|
||||
|
||||
// Choose text color based on selection state
|
||||
SDL_Color textColor = {255, 255, 255, 255}; // Default white
|
||||
if (isSelected) {
|
||||
textColor = {255, 220, 0, 255}; // Yellow when selected
|
||||
}
|
||||
|
||||
// Text shadow
|
||||
font->draw(renderer, tx + 2.0f, ty + 2.0f, label, textScale, {0, 0, 0, 200});
|
||||
// Text
|
||||
font->draw(renderer, tx, ty, label, textScale, textColor);
|
||||
}
|
||||
}
|
||||
|
||||
void UIRenderer::computeContentOffsets(float winW, float winH, float logicalW, float logicalH, float logicalScale, float& outOffsetX, float& outOffsetY) {
|
||||
float contentW = logicalW * logicalScale;
|
||||
float contentH = logicalH * logicalScale;
|
||||
outOffsetX = (winW - contentW) * 0.5f / logicalScale;
|
||||
outOffsetY = (winH - contentH) * 0.5f / logicalScale;
|
||||
}
|
||||
|
||||
void UIRenderer::drawLogo(SDL_Renderer* renderer, SDL_Texture* logoTex, float logicalW, float logicalH, float contentOffsetX, float contentOffsetY, int texW, int texH) {
|
||||
if (!renderer || !logoTex) return;
|
||||
|
||||
float w = 0.0f;
|
||||
float h = 0.0f;
|
||||
|
||||
if (texW > 0 && texH > 0) {
|
||||
w = static_cast<float>(texW);
|
||||
h = static_cast<float>(texH);
|
||||
} else {
|
||||
SDL_GetTextureSize(logoTex, &w, &h);
|
||||
}
|
||||
|
||||
if (w > 0.0f && h > 0.0f) {
|
||||
float maxWidth = logicalW * 0.6f;
|
||||
float scale = std::min(1.0f, maxWidth / w);
|
||||
float dw = w * scale;
|
||||
float dh = h * scale;
|
||||
float logoX = (logicalW - dw) * 0.5f + contentOffsetX;
|
||||
float logoY = logicalH * 0.05f + contentOffsetY;
|
||||
SDL_FRect dst{logoX, logoY, dw, dh};
|
||||
SDL_RenderTexture(renderer, logoTex, nullptr, &dst);
|
||||
}
|
||||
}
|
||||
|
||||
void UIRenderer::drawSettingsPopup(SDL_Renderer* renderer, FontAtlas* font, float logicalW, float logicalH, bool musicEnabled, bool soundEnabled) {
|
||||
if (!renderer || !font) return;
|
||||
|
||||
float popupW = 350, popupH = 260;
|
||||
float popupX = (logicalW - popupW) / 2;
|
||||
float popupY = (logicalH - popupH) / 2;
|
||||
|
||||
// Semi-transparent overlay
|
||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 128);
|
||||
SDL_FRect overlay{0, 0, logicalW, logicalH};
|
||||
SDL_RenderFillRect(renderer, &overlay);
|
||||
|
||||
// Popup background
|
||||
SDL_SetRenderDrawColor(renderer, 100, 120, 160, 255);
|
||||
SDL_FRect bord{popupX-4, popupY-4, popupW+8, popupH+8};
|
||||
SDL_RenderFillRect(renderer, &bord);
|
||||
|
||||
SDL_SetRenderDrawColor(renderer, 40, 50, 70, 255);
|
||||
SDL_FRect body{popupX, popupY, popupW, popupH};
|
||||
SDL_RenderFillRect(renderer, &body);
|
||||
|
||||
// Title
|
||||
font->draw(renderer, popupX + 20, popupY + 20, "SETTINGS", 2.0f, {255, 220, 0, 255});
|
||||
|
||||
// Music toggle
|
||||
font->draw(renderer, popupX + 20, popupY + 70, "MUSIC:", 1.5f, {255, 255, 255, 255});
|
||||
const char* musicStatus = musicEnabled ? "ON" : "OFF";
|
||||
SDL_Color musicColor = musicEnabled ? SDL_Color{0, 255, 0, 255} : SDL_Color{255, 0, 0, 255};
|
||||
font->draw(renderer, popupX + 120, popupY + 70, musicStatus, 1.5f, musicColor);
|
||||
|
||||
// Sound effects toggle
|
||||
font->draw(renderer, popupX + 20, popupY + 100, "SOUND FX:", 1.5f, {255, 255, 255, 255});
|
||||
const char* soundStatus = soundEnabled ? "ON" : "OFF";
|
||||
SDL_Color soundColor = soundEnabled ? SDL_Color{0, 255, 0, 255} : SDL_Color{255, 0, 0, 255};
|
||||
font->draw(renderer, popupX + 140, popupY + 100, soundStatus, 1.5f, soundColor);
|
||||
|
||||
// Instructions
|
||||
font->draw(renderer, popupX + 20, popupY + 150, "M = TOGGLE MUSIC", 1.0f, {200, 200, 220, 255});
|
||||
font->draw(renderer, popupX + 20, popupY + 170, "S = TOGGLE SOUND FX", 1.0f, {200, 200, 220, 255});
|
||||
font->draw(renderer, popupX + 20, popupY + 190, "ESC = CLOSE", 1.0f, {200, 200, 220, 255});
|
||||
}
|
||||
28
src/graphics/renderers/UIRenderer.h
Normal file
28
src/graphics/renderers/UIRenderer.h
Normal file
@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
#include <SDL3/SDL.h>
|
||||
#include <string>
|
||||
|
||||
class FontAtlas;
|
||||
|
||||
class UIRenderer {
|
||||
public:
|
||||
// Draw a sci-fi style panel with glow, shadow, and border
|
||||
static void drawSciFiPanel(SDL_Renderer* renderer, const SDL_FRect& rect, float alpha = 1.0f);
|
||||
|
||||
// Draw a generic button with hover/select states
|
||||
static void drawButton(SDL_Renderer* renderer, FontAtlas* font, float cx, float cy, float w, float h,
|
||||
const std::string& label, bool isHovered, bool isSelected,
|
||||
SDL_Color bgColor = {80, 110, 200, 255},
|
||||
SDL_Color borderColor = {60, 80, 140, 255},
|
||||
bool textOnly = false,
|
||||
SDL_Texture* icon = nullptr);
|
||||
|
||||
// Helper to calculate content offsets for centering
|
||||
static void computeContentOffsets(float winW, float winH, float logicalW, float logicalH, float logicalScale, float& outOffsetX, float& outOffsetY);
|
||||
|
||||
// Draw the game logo centered at the top
|
||||
static void drawLogo(SDL_Renderer* renderer, SDL_Texture* logoTex, float logicalW, float logicalH, float contentOffsetX, float contentOffsetY, int texW = 0, int texH = 0);
|
||||
|
||||
// Draw the settings popup
|
||||
static void drawSettingsPopup(SDL_Renderer* renderer, FontAtlas* font, float logicalW, float logicalH, bool musicEnabled, bool soundEnabled);
|
||||
};
|
||||
Reference in New Issue
Block a user