170 lines
5.7 KiB
C++
170 lines
5.7 KiB
C++
// Starfield3D.cpp - 3D Parallax Starfield Implementation
|
|
#include "Starfield3D.h"
|
|
#include <cmath>
|
|
#include <algorithm>
|
|
|
|
Starfield3D::Starfield3D() : rng(std::random_device{}()), width(800), height(600), centerX(400), centerY(300) {
|
|
}
|
|
|
|
void Starfield3D::init(int w, int h, int starCount) {
|
|
width = w;
|
|
height = h;
|
|
centerX = width * 0.5f;
|
|
centerY = height * 0.5f;
|
|
|
|
stars.resize(starCount);
|
|
createStarfield();
|
|
}
|
|
|
|
void Starfield3D::resize(int w, int h) {
|
|
width = w;
|
|
height = h;
|
|
centerX = width * 0.5f;
|
|
centerY = height * 0.5f;
|
|
}
|
|
|
|
float Starfield3D::randomFloat(float min, float max) {
|
|
std::uniform_real_distribution<float> dist(min, max);
|
|
return dist(rng);
|
|
}
|
|
|
|
int Starfield3D::randomRange(int min, int max) {
|
|
std::uniform_int_distribution<int> dist(min, max - 1);
|
|
return dist(rng);
|
|
}
|
|
|
|
void Starfield3D::setRandomDirection(Star3D& star) {
|
|
star.targetVx = randomFloat(-MAX_VELOCITY, MAX_VELOCITY);
|
|
star.targetVy = randomFloat(-MAX_VELOCITY, MAX_VELOCITY);
|
|
|
|
// Allow stars to move both toward and away from viewer
|
|
if (randomFloat(0.0f, 1.0f) < REVERSE_PROBABILITY) {
|
|
// Move away from viewer (positive Z)
|
|
star.targetVz = STAR_SPEED * randomFloat(0.5f, 1.0f);
|
|
} else {
|
|
// Move toward viewer (negative Z)
|
|
star.targetVz = -STAR_SPEED * randomFloat(0.7f, 1.3f);
|
|
}
|
|
|
|
star.changing = true;
|
|
star.changeTimer = randomFloat(30.0f, 120.0f); // Direction change lasts 30-120 frames
|
|
}
|
|
|
|
void Starfield3D::updateStar(int index) {
|
|
Star3D& star = stars[index];
|
|
|
|
star.x = randomFloat(-25.0f, 25.0f);
|
|
star.y = randomFloat(-25.0f, 25.0f);
|
|
star.z = randomFloat(1.0f, MAX_DEPTH);
|
|
|
|
// Give stars initial velocities in all possible directions
|
|
if (randomFloat(0.0f, 1.0f) < 0.5f) {
|
|
// Half stars start moving toward viewer
|
|
star.vx = randomFloat(-0.1f, 0.1f);
|
|
star.vy = randomFloat(-0.1f, 0.1f);
|
|
star.vz = -STAR_SPEED * randomFloat(0.8f, 1.2f);
|
|
} else {
|
|
// Half stars start moving in random directions
|
|
star.vx = randomFloat(-0.2f, 0.2f);
|
|
star.vy = randomFloat(-0.2f, 0.2f);
|
|
|
|
// 30% chance to start moving away
|
|
if (randomFloat(0.0f, 1.0f) < 0.3f) {
|
|
star.vz = STAR_SPEED * randomFloat(0.5f, 0.8f);
|
|
} else {
|
|
star.vz = -STAR_SPEED * randomFloat(0.8f, 1.2f);
|
|
}
|
|
}
|
|
|
|
star.targetVx = star.vx;
|
|
star.targetVy = star.vy;
|
|
star.targetVz = star.vz;
|
|
star.changing = false;
|
|
star.changeTimer = 0.0f;
|
|
star.type = randomRange(0, COLOR_COUNT);
|
|
|
|
// Give some stars initial direction variations
|
|
if (randomFloat(0.0f, 1.0f) < 0.4f) {
|
|
setRandomDirection(star);
|
|
}
|
|
}
|
|
|
|
void Starfield3D::createStarfield() {
|
|
for (size_t i = 0; i < stars.size(); ++i) {
|
|
updateStar(static_cast<int>(i));
|
|
}
|
|
}
|
|
|
|
void Starfield3D::update(float deltaTime) {
|
|
const float frameRate = 60.0f; // Target 60 FPS for consistency
|
|
const float frameMultiplier = deltaTime * frameRate;
|
|
|
|
for (size_t i = 0; i < stars.size(); ++i) {
|
|
Star3D& star = stars[i];
|
|
|
|
// Randomly change direction occasionally
|
|
if (!star.changing && randomFloat(0.0f, 1.0f) < DIRECTION_CHANGE_PROBABILITY * frameMultiplier) {
|
|
setRandomDirection(star);
|
|
}
|
|
|
|
// Update velocities to approach target values
|
|
if (star.changing) {
|
|
// Smoothly transition to target velocities
|
|
const float change = VELOCITY_CHANGE * frameMultiplier;
|
|
star.vx += (star.targetVx - star.vx) * change;
|
|
star.vy += (star.targetVy - star.vy) * change;
|
|
star.vz += (star.targetVz - star.vz) * change;
|
|
|
|
// Decrement change timer
|
|
star.changeTimer -= frameMultiplier;
|
|
if (star.changeTimer <= 0.0f) {
|
|
star.changing = false;
|
|
}
|
|
}
|
|
|
|
// Update position using current velocity
|
|
star.x += star.vx * frameMultiplier;
|
|
star.y += star.vy * frameMultiplier;
|
|
star.z += star.vz * frameMultiplier;
|
|
|
|
// Handle boundaries - reset star if it moves out of bounds, too close, or too far
|
|
if (star.z <= MIN_Z ||
|
|
star.z >= MAX_Z ||
|
|
std::abs(star.x) > 50.0f ||
|
|
std::abs(star.y) > 50.0f) {
|
|
updateStar(static_cast<int>(i));
|
|
}
|
|
}
|
|
}
|
|
|
|
void Starfield3D::drawStar(SDL_Renderer* renderer, float x, float y, SDL_Color color, float alphaScale) {
|
|
Uint8 alpha = static_cast<Uint8>(std::clamp(color.a * alphaScale, 0.0f, 255.0f));
|
|
SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, alpha);
|
|
|
|
// Draw star as a small rectangle (1x1 pixel)
|
|
SDL_FRect rect{x, y, 1.0f, 1.0f};
|
|
SDL_RenderFillRect(renderer, &rect);
|
|
}
|
|
|
|
void Starfield3D::draw(SDL_Renderer* renderer, float offsetX, float offsetY, float alphaScale, bool grayscale) {
|
|
for (const Star3D& star : stars) {
|
|
// Calculate perspective projection factor
|
|
const float k = DEPTH_FACTOR / star.z;
|
|
|
|
// Calculate screen position with perspective
|
|
const float px = star.x * k + centerX;
|
|
const float py = star.y * k + centerY;
|
|
|
|
// Only draw stars that are within the viewport
|
|
if (px >= 0.0f && px <= static_cast<float>(width) &&
|
|
py >= 0.0f && py <= static_cast<float>(height)) {
|
|
SDL_Color baseColor = STAR_COLORS[star.type % COLOR_COUNT];
|
|
if (grayscale) {
|
|
Uint8 gray = static_cast<Uint8>(0.299f * baseColor.r + 0.587f * baseColor.g + 0.114f * baseColor.b);
|
|
baseColor.r = baseColor.g = baseColor.b = gray;
|
|
}
|
|
drawStar(renderer, px + offsetX, py + offsetY, baseColor, alphaScale);
|
|
}
|
|
}
|
|
}
|