feat(starfield3d): camp d'estrelles 3D amb octaedres rotants cap a càmera

This commit is contained in:
2026-05-22 08:10:52 +02:00
parent e20bdec470
commit 8722a46d06
2 changed files with 173 additions and 0 deletions
+105
View File
@@ -0,0 +1,105 @@
// starfield3d.cpp - Implementació del starfield 3D
// © 2026 JailDesigner
#include "core/graphics/starfield3d.hpp"
#include <algorithm>
#include <cmath>
#include <cstdlib>
#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<float>(rand()) / static_cast<float>(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::size_t>(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<std::size_t> 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
+68
View File
@@ -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 <vector>
#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<Star> 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