From 8722a46d06e0485118f927f3084398eb7136341e Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Fri, 22 May 2026 08:10:52 +0200 Subject: [PATCH] =?UTF-8?q?feat(starfield3d):=20camp=20d'estrelles=203D=20?= =?UTF-8?q?amb=20octaedres=20rotants=20cap=20a=20c=C3=A0mera?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/core/graphics/starfield3d.cpp | 105 +++++++++++++++++++++++++++ source/core/graphics/starfield3d.hpp | 68 +++++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 source/core/graphics/starfield3d.cpp create mode 100644 source/core/graphics/starfield3d.hpp diff --git a/source/core/graphics/starfield3d.cpp b/source/core/graphics/starfield3d.cpp new file mode 100644 index 0000000..2923ffa --- /dev/null +++ b/source/core/graphics/starfield3d.cpp @@ -0,0 +1,105 @@ +// starfield3d.cpp - Implementació del starfield 3D +// © 2026 JailDesigner + +#include "core/graphics/starfield3d.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 + + Starfield3D::Starfield3D(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 Starfield3D::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 Starfield3D::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 Starfield3D::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 Starfield3D::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)); + } + } + + void Starfield3D::setBrightness(float multiplier) { + brightness_mult_ = std::max(0.0F, multiplier); + } + +} // namespace Graphics diff --git a/source/core/graphics/starfield3d.hpp b/source/core/graphics/starfield3d.hpp new file mode 100644 index 0000000..f441697 --- /dev/null +++ b/source/core/graphics/starfield3d.hpp @@ -0,0 +1,68 @@ +// starfield3d.hpp - Camp de estrelles 3D real per a l'escena de títol +// © 2026 JailDesigner +// +// Equivalent 3D del `Graphics::Starfield`. Cada estrella és un octaedre +// wireframe situat en l'espai mundial (Vec3). Es desplacen cap a la càmera +// (Z disminueix); quan creuen el pla Z_NEAR_RESPAWN es regeneren a Z_FAR_SPAWN +// amb X/Y aleatori. Cada octaedre rota lentament sobre Y i X per donar volum. + +#pragma once + +#include + +#include "core/graphics/camera3d.hpp" +#include "core/graphics/wireframe3d.hpp" +#include "core/rendering/render_context.hpp" +#include "core/types.hpp" + +namespace Graphics { + + class Starfield3D { + public: + Starfield3D(Rendering::Renderer* renderer, const Camera3D* camera, int density = 200); + + void update(float delta_time); + void draw() const; + + void setBrightness(float multiplier); + + private: + struct Star { + Vec3 position{}; + float velocity_z{0.0F}; // Negatiu: cap a càmera + float rot_phase_y{0.0F}; + float rot_phase_x{0.0F}; + float rot_speed_y{0.0F}; + float rot_speed_x{0.0F}; + float scale{1.0F}; + }; + + static void initStar(Star& star, bool spawn_at_far); + [[nodiscard]] auto computeBrightness(const Star& star) const -> float; + + Rendering::Renderer* renderer_; + const Camera3D* camera_; + std::vector stars_; + Mesh3D octahedron_; + float brightness_mult_{1.0F}; + + // Volum de spawn / regeneració en l'espai 3D. + static constexpr float Z_NEAR_RESPAWN = 5.0F; // Si Z < aquest valor → regenera + static constexpr float Z_FAR_SPAWN = 800.0F; // Z de regeneració (lluny) + static constexpr float HALF_SPAWN_X = 600.0F; // X aleatori dins [-, +] + static constexpr float HALF_SPAWN_Y = 360.0F; // Y aleatori dins [-, +] + + // Mida i moviment. + static constexpr float STAR_BASE_SCALE = 1.8F; + static constexpr float STAR_SCALE_JITTER = 0.6F; + static constexpr float MIN_VELOCITY_Z = 80.0F; + static constexpr float MAX_VELOCITY_Z = 200.0F; + static constexpr float MIN_ROT_SPEED = 0.2F; + static constexpr float MAX_ROT_SPEED = 0.8F; + + // Brightness en funció de la distància Z (a prop = més brillant). + static constexpr float BRIGHTNESS_FAR = 0.15F; + static constexpr float BRIGHTNESS_NEAR = 1.0F; + }; + +} // namespace Graphics