Kelsidavis-WoWee/src/rendering/starfield.cpp

260 lines
7.2 KiB
C++
Raw Normal View History

#include "rendering/starfield.hpp"
#include "rendering/shader.hpp"
#include "rendering/camera.hpp"
#include "core/logger.hpp"
#include <GL/glew.h>
#include <glm/gtc/matrix_transform.hpp>
#include <cmath>
#include <random>
namespace wowee {
namespace rendering {
StarField::StarField() = default;
StarField::~StarField() {
shutdown();
}
bool StarField::initialize() {
LOG_INFO("Initializing star field");
// Create star shader
starShader = std::make_unique<Shader>();
// Vertex shader - simple point rendering
const char* vertexShaderSource = R"(
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in float aBrightness;
layout (location = 2) in float aTwinklePhase;
uniform mat4 view;
uniform mat4 projection;
uniform float time;
uniform float intensity;
out float Brightness;
void main() {
// Remove translation from view matrix (stars are infinitely far)
mat4 viewNoTranslation = mat4(mat3(view));
gl_Position = projection * viewNoTranslation * vec4(aPos, 1.0);
// Twinkle effect (subtle brightness variation)
float twinkle = sin(time * 2.0 + aTwinklePhase) * 0.2 + 0.8; // 0.6 to 1.0
Brightness = aBrightness * twinkle * intensity;
// Point size based on brightness
gl_PointSize = 2.0 + aBrightness * 2.0; // 2-4 pixels
}
)";
// Fragment shader - star color
const char* fragmentShaderSource = R"(
#version 330 core
in float Brightness;
out vec4 FragColor;
void main() {
// Circular point (not square)
vec2 coord = gl_PointCoord - vec2(0.5);
float dist = length(coord);
if (dist > 0.5) {
discard;
}
// Soften edges
float alpha = smoothstep(0.5, 0.3, dist);
// Star color (slightly blue-white)
vec3 starColor = vec3(0.9, 0.95, 1.0);
FragColor = vec4(starColor * Brightness, alpha * Brightness);
}
)";
if (!starShader->loadFromSource(vertexShaderSource, fragmentShaderSource)) {
LOG_ERROR("Failed to create star shader");
return false;
}
// Generate random stars
generateStars();
// Create OpenGL buffers
createStarBuffers();
LOG_INFO("Star field initialized: ", starCount, " stars");
return true;
}
void StarField::shutdown() {
destroyStarBuffers();
starShader.reset();
stars.clear();
}
void StarField::render(const Camera& camera, float timeOfDay) {
if (!renderingEnabled || vao == 0 || !starShader || stars.empty()) {
return;
}
// Get star intensity based on time of day
float intensity = getStarIntensity(timeOfDay);
// Don't render if stars would be invisible
if (intensity <= 0.01f) {
return;
}
// Enable blending for star glow
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// Enable point sprites
glEnable(GL_PROGRAM_POINT_SIZE);
// Disable depth writing (stars are background)
glDepthMask(GL_FALSE);
starShader->use();
// Set uniforms
glm::mat4 view = camera.getViewMatrix();
glm::mat4 projection = camera.getProjectionMatrix();
starShader->setUniform("view", view);
starShader->setUniform("projection", projection);
starShader->setUniform("time", twinkleTime);
starShader->setUniform("intensity", intensity);
// Render stars as points
glBindVertexArray(vao);
glDrawArrays(GL_POINTS, 0, starCount);
glBindVertexArray(0);
// Restore state
glDepthMask(GL_TRUE);
glDisable(GL_PROGRAM_POINT_SIZE);
glDisable(GL_BLEND);
}
void StarField::update(float deltaTime) {
// Update twinkle animation
twinkleTime += deltaTime;
}
void StarField::generateStars() {
stars.clear();
stars.reserve(starCount);
// Random number generator
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<float> phiDist(0.0f, M_PI / 2.0f); // 0 to 90 degrees (hemisphere)
std::uniform_real_distribution<float> thetaDist(0.0f, 2.0f * M_PI); // 0 to 360 degrees
std::uniform_real_distribution<float> brightnessDist(0.3f, 1.0f); // Varying brightness
std::uniform_real_distribution<float> twinkleDist(0.0f, 2.0f * M_PI); // Random twinkle phase
const float radius = 900.0f; // Slightly larger than skybox
for (int i = 0; i < starCount; i++) {
Star star;
// Spherical coordinates (hemisphere)
float phi = phiDist(gen); // Elevation angle
float theta = thetaDist(gen); // Azimuth angle
// Convert to Cartesian coordinates
float x = radius * std::sin(phi) * std::cos(theta);
float y = radius * std::sin(phi) * std::sin(theta);
float z = radius * std::cos(phi);
star.position = glm::vec3(x, y, z);
star.brightness = brightnessDist(gen);
star.twinklePhase = twinkleDist(gen);
stars.push_back(star);
}
LOG_DEBUG("Generated ", stars.size(), " stars");
}
void StarField::createStarBuffers() {
// Prepare vertex data (position, brightness, twinkle phase)
std::vector<float> vertexData;
vertexData.reserve(stars.size() * 5); // 3 pos + 1 brightness + 1 phase
for (const auto& star : stars) {
vertexData.push_back(star.position.x);
vertexData.push_back(star.position.y);
vertexData.push_back(star.position.z);
vertexData.push_back(star.brightness);
vertexData.push_back(star.twinklePhase);
}
// Create OpenGL buffers
glGenVertexArrays(1, &vao);
glGenBuffers(1, &vbo);
glBindVertexArray(vao);
// Upload vertex data
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, vertexData.size() * sizeof(float), vertexData.data(), GL_STATIC_DRAW);
// Set vertex attributes
// Position
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// Brightness
glVertexAttribPointer(1, 1, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
// Twinkle phase
glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(4 * sizeof(float)));
glEnableVertexAttribArray(2);
glBindVertexArray(0);
}
void StarField::destroyStarBuffers() {
if (vao != 0) {
glDeleteVertexArrays(1, &vao);
vao = 0;
}
if (vbo != 0) {
glDeleteBuffers(1, &vbo);
vbo = 0;
}
}
float StarField::getStarIntensity(float timeOfDay) const {
// Stars visible at night (fade in/out at dusk/dawn)
// Full night: 20:00-4:00
if (timeOfDay >= 20.0f || timeOfDay < 4.0f) {
return 1.0f;
}
// Fade in at dusk: 18:00-20:00
else if (timeOfDay >= 18.0f && timeOfDay < 20.0f) {
return (timeOfDay - 18.0f) / 2.0f; // 0 to 1 over 2 hours
}
// Fade out at dawn: 4:00-6:00
else if (timeOfDay >= 4.0f && timeOfDay < 6.0f) {
return 1.0f - (timeOfDay - 4.0f) / 2.0f; // 1 to 0 over 2 hours
}
// Daytime: no stars
else {
return 0.0f;
}
}
} // namespace rendering
} // namespace wowee