110 lines
4.0 KiB
C++
110 lines
4.0 KiB
C++
// starfield.cpp - Implementació del starfield 3D
|
|
// © 2026 JailDesigner
|
|
|
|
#include "core/graphics/starfield.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
|
|
|
|
Starfield::Starfield(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 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<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), color_);
|
|
}
|
|
}
|
|
|
|
void Starfield::setBrightness(float multiplier) {
|
|
brightness_mult_ = std::max(0.0F, multiplier);
|
|
}
|
|
|
|
void Starfield::setColor(SDL_Color color) {
|
|
color_ = color;
|
|
}
|
|
|
|
} // namespace Graphics
|