// starfield_parallax.cpp - Implementació del starfield 2D amb parallax // © 2026 JailDesigner #include "core/graphics/starfield_parallax.hpp" #include #include "core/defaults.hpp" #include "core/rendering/line_renderer.hpp" namespace Graphics { namespace { auto randUniform(float min_v, float max_v) -> float { const float NORM = static_cast(std::rand()) / static_cast(RAND_MAX); return min_v + (NORM * (max_v - min_v)); } } // namespace StarfieldParallax::StarfieldParallax(Rendering::Renderer* renderer) : renderer_(renderer) { buildStars(); } void StarfieldParallax::buildStars() { const SDL_FRect& zone = Defaults::Zones::PLAYAREA; const float MIN_X = zone.x; const float MAX_X = zone.x + zone.w; const float MIN_Y = zone.y; const float MAX_Y = zone.y + zone.h; // Color únic per a totes les estrelles: el mateix blanc-blau gel // del starfield del títol (Defaults::Title::Colors::STARFIELD). const auto FILL_LAYER = [&](int layer, int count, int& idx) { for (int i = 0; i < count; i++) { stars_[idx++] = Star{ .x = randUniform(MIN_X, MAX_X), .y = randUniform(MIN_Y, MAX_Y), .layer = layer, .color = Defaults::Title::Colors::STARFIELD}; } }; int idx = 0; FILL_LAYER(0, Defaults::StarfieldParallax::Far::COUNT, idx); FILL_LAYER(1, Defaults::StarfieldParallax::Mid::COUNT, idx); FILL_LAYER(2, Defaults::StarfieldParallax::Near::COUNT, idx); } auto StarfieldParallax::layerBrightness(int layer) -> float { switch (layer) { case 0: return Defaults::StarfieldParallax::Far::BRIGHTNESS; case 1: return Defaults::StarfieldParallax::Mid::BRIGHTNESS; case 2: return Defaults::StarfieldParallax::Near::BRIGHTNESS; default: return 0.0F; } } auto StarfieldParallax::layerParallax(int layer) -> float { switch (layer) { case 0: return Defaults::StarfieldParallax::Far::PARALLAX_FACTOR; case 1: return Defaults::StarfieldParallax::Mid::PARALLAX_FACTOR; case 2: return Defaults::StarfieldParallax::Near::PARALLAX_FACTOR; default: return 0.0F; } } auto StarfieldParallax::layerSize(int layer) -> int { switch (layer) { case 0: return Defaults::StarfieldParallax::Far::SIZE_PX; case 1: return Defaults::StarfieldParallax::Mid::SIZE_PX; case 2: return Defaults::StarfieldParallax::Near::SIZE_PX; default: return 1; } } void StarfieldParallax::update(float delta_time, Vec2 world_velocity) { const SDL_FRect& zone = Defaults::Zones::PLAYAREA; const float MIN_X = zone.x; const float MAX_X = zone.x + zone.w; const float MIN_Y = zone.y; const float MAX_Y = zone.y + zone.h; const float W = zone.w; const float H = zone.h; for (auto& star : stars_) { const float FACTOR = layerParallax(star.layer); star.x += world_velocity.x * FACTOR * delta_time; star.y += world_velocity.y * FACTOR * delta_time; // Wraparound (PLAYAREA torica). while (star.x < MIN_X) { star.x += W; } while (star.x > MAX_X) { star.x -= W; } while (star.y < MIN_Y) { star.y += H; } while (star.y > MAX_Y) { star.y -= H; } } } void StarfieldParallax::draw() const { for (const auto& star : stars_) { const float B = layerBrightness(star.layer); const int SIZE = layerSize(star.layer); const int X = static_cast(star.x); const int Y = static_cast(star.y); if (SIZE <= 1) { // Punt d'1 px: línia degenerada horitzontal de 1 px. Rendering::linea(renderer_, X, Y, X + 1, Y, B, 0.0F, star.color); } else { // Creu "+" amb extensió HALF des del centre en cada direcció. const int HALF = SIZE - 1; // SIZE=2 → ±1 (creu 3x3); SIZE=3 → ±2 (creu 5x5) Rendering::linea(renderer_, X - HALF, Y, X + HALF + 1, Y, B, 0.0F, star.color); Rendering::linea(renderer_, X, Y - HALF, X, Y + HALF + 1, B, 0.0F, star.color); } } } } // namespace Graphics