// starfield.cpp - Implementació del starfield 3D // © 2026 JailDesigner #include "core/graphics/starfield.hpp" #include #include #include #include "core/defaults.hpp" namespace Graphics { namespace { // Helper: número aleatori en [0, 1) usant rand()/RAND_MAX (mateixa convenció // que la resta del joc — veure starfield.cpp). auto randFloat01() -> float { return static_cast(rand()) / static_cast(RAND_MAX); } auto randRange(float lo, float hi) -> float { return lo + (randFloat01() * (hi - lo)); } } // namespace Starfield::Starfield(Rendering::Renderer* renderer, const Camera3D* camera, int density) : renderer_(renderer), camera_(camera), octahedron_(makeOctahedron()) { stars_.resize(static_cast(std::max(0, density))); for (auto& star : stars_) { // Pre-omplir amb estrelles distribuïdes per tot el rang Z, no només al far. initStar(star, /*spawn_at_far=*/false); } } void Starfield::initStar(Star& star, bool spawn_at_far) { star.position.x = randRange(-HALF_SPAWN_X, HALF_SPAWN_X); star.position.y = randRange(-HALF_SPAWN_Y, HALF_SPAWN_Y); star.position.z = spawn_at_far ? Z_FAR_SPAWN : randRange(Z_NEAR_RESPAWN, Z_FAR_SPAWN); star.velocity_z = -randRange(MIN_VELOCITY_Z, MAX_VELOCITY_Z); star.rot_phase_y = randFloat01() * Defaults::Math::PI * 2.0F; star.rot_phase_x = randFloat01() * Defaults::Math::PI * 2.0F; star.rot_speed_y = randRange(MIN_ROT_SPEED, MAX_ROT_SPEED); star.rot_speed_x = randRange(MIN_ROT_SPEED, MAX_ROT_SPEED); star.scale = STAR_BASE_SCALE + (randRange(-1.0F, 1.0F) * STAR_SCALE_JITTER); } auto Starfield::computeBrightness(const Star& star) const -> float { // Lerp segons distància Z normalitzada [Z_NEAR_RESPAWN .. Z_FAR_SPAWN]. const float SPAN = Z_FAR_SPAWN - Z_NEAR_RESPAWN; const float T_RAW = (star.position.z - Z_NEAR_RESPAWN) / SPAN; const float T = std::clamp(T_RAW, 0.0F, 1.0F); const float BRIGHTNESS = BRIGHTNESS_NEAR + (T * (BRIGHTNESS_FAR - BRIGHTNESS_NEAR)); return std::clamp(BRIGHTNESS * brightness_mult_, 0.0F, 1.0F); } void Starfield::update(float delta_time) { for (auto& star : stars_) { star.position.z += star.velocity_z * delta_time; star.rot_phase_y += star.rot_speed_y * delta_time; star.rot_phase_x += star.rot_speed_x * delta_time; if (star.position.z < Z_NEAR_RESPAWN) { initStar(star, /*spawn_at_far=*/true); } } } void Starfield::draw() const { if (camera_ == nullptr || renderer_ == nullptr) { return; } // Ordena de més lluny a més a prop perquè el blending additiu acumule // brightness sense que els elements de davant queden tapats pels de // darrere. Còpia d'índexs per no modificar l'ordre intern de stars_. std::vector order(stars_.size()); for (std::size_t i = 0; i < order.size(); ++i) { order[i] = i; } std::ranges::sort(order, [&](std::size_t a, std::size_t b) { return stars_[a].position.z > stars_[b].position.z; }); for (std::size_t idx : order) { const Star& star = stars_[idx]; const Transform3D TRANSFORM{ .position = star.position, .rotation_euler = Vec3{.x = star.rot_phase_x, .y = star.rot_phase_y, .z = 0.0F}, .scale = star.scale, }; drawWireframe(renderer_, *camera_, octahedron_, TRANSFORM, computeBrightness(star), color_); } } void Starfield::setBrightness(float multiplier) { brightness_mult_ = std::max(0.0F, multiplier); } void Starfield::setColor(SDL_Color color) { color_ = color; } } // namespace Graphics