mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-17 17:43:52 +00:00
Add progress bar to loading screen and handle resize during loading
Loading screen now shows a gold progress bar with percentage and status text. All loading steps poll SDL events for window resize and quit.
This commit is contained in:
parent
01f339908c
commit
89d1e44454
3 changed files with 276 additions and 52 deletions
|
|
@ -18,7 +18,7 @@ public:
|
||||||
// Select a random loading screen image
|
// Select a random loading screen image
|
||||||
void selectRandomImage();
|
void selectRandomImage();
|
||||||
|
|
||||||
// Render the loading screen (call in a loop while loading)
|
// Render the loading screen with progress bar and status text
|
||||||
void render();
|
void render();
|
||||||
|
|
||||||
// Update loading progress (0.0 to 1.0)
|
// Update loading progress (0.0 to 1.0)
|
||||||
|
|
@ -30,12 +30,18 @@ public:
|
||||||
private:
|
private:
|
||||||
bool loadImage(const std::string& path);
|
bool loadImage(const std::string& path);
|
||||||
void createQuad();
|
void createQuad();
|
||||||
|
void createBarQuad();
|
||||||
|
|
||||||
GLuint textureId = 0;
|
GLuint textureId = 0;
|
||||||
GLuint vao = 0;
|
GLuint vao = 0;
|
||||||
GLuint vbo = 0;
|
GLuint vbo = 0;
|
||||||
GLuint shaderId = 0;
|
GLuint shaderId = 0;
|
||||||
|
|
||||||
|
// Progress bar GL objects
|
||||||
|
GLuint barVao = 0;
|
||||||
|
GLuint barVbo = 0;
|
||||||
|
GLuint barShaderId = 0;
|
||||||
|
|
||||||
std::vector<std::string> imagePaths;
|
std::vector<std::string> imagePaths;
|
||||||
int currentImageIndex = 0;
|
int currentImageIndex = 0;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1276,9 +1276,6 @@ void Application::startSinglePlayer() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load weapon models for equipped items (after inventory is populated)
|
|
||||||
loadEquippedWeapons();
|
|
||||||
|
|
||||||
if (gameHandler && renderer && window) {
|
if (gameHandler && renderer && window) {
|
||||||
game::GameHandler::SinglePlayerSettings settings;
|
game::GameHandler::SinglePlayerSettings settings;
|
||||||
bool hasSettings = gameHandler->getSinglePlayerSettings(settings);
|
bool hasSettings = gameHandler->getSinglePlayerSettings(settings);
|
||||||
|
|
@ -1326,7 +1323,40 @@ void Application::startSinglePlayer() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Loading screen: load terrain and wait for streaming before spawning ---
|
// --- Loading screen ---
|
||||||
|
rendering::LoadingScreen loadingScreen;
|
||||||
|
bool loadingScreenOk = loadingScreen.initialize();
|
||||||
|
|
||||||
|
// Helper: poll events (resize/quit), update progress bar, swap buffers
|
||||||
|
auto showProgress = [&](const char* msg, float progress) {
|
||||||
|
// Poll SDL events so resizing and quit work during loading
|
||||||
|
SDL_Event event;
|
||||||
|
while (SDL_PollEvent(&event)) {
|
||||||
|
if (event.type == SDL_QUIT) {
|
||||||
|
window->setShouldClose(true);
|
||||||
|
loadingScreen.shutdown();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (event.type == SDL_WINDOWEVENT &&
|
||||||
|
event.window.event == SDL_WINDOWEVENT_RESIZED) {
|
||||||
|
int w = event.window.data1;
|
||||||
|
int h = event.window.data2;
|
||||||
|
window->setSize(w, h);
|
||||||
|
glViewport(0, 0, w, h);
|
||||||
|
if (renderer && renderer->getCamera()) {
|
||||||
|
renderer->getCamera()->setAspectRatio(static_cast<float>(w) / h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!loadingScreenOk) return;
|
||||||
|
loadingScreen.setStatus(msg);
|
||||||
|
loadingScreen.setProgress(progress);
|
||||||
|
loadingScreen.render();
|
||||||
|
window->swapBuffers();
|
||||||
|
};
|
||||||
|
|
||||||
|
showProgress("Preparing world...", 0.0f);
|
||||||
|
|
||||||
const SpawnPreset* spawnPreset = selectSpawnPreset(std::getenv("WOW_SPAWN"));
|
const SpawnPreset* spawnPreset = selectSpawnPreset(std::getenv("WOW_SPAWN"));
|
||||||
// Canonical WoW coords: +X=North, +Y=West, +Z=Up
|
// Canonical WoW coords: +X=North, +Y=West, +Z=Up
|
||||||
glm::vec3 spawnCanonical = spawnPreset ? spawnPreset->spawnCanonical : spSpawnCanonical_;
|
glm::vec3 spawnCanonical = spawnPreset ? spawnPreset->spawnCanonical : spSpawnCanonical_;
|
||||||
|
|
@ -1365,17 +1395,12 @@ void Application::startSinglePlayer() {
|
||||||
LOG_INFO("Optional spawn overrides (canonical WoW X,Y,Z): WOW_SPAWN_POS=x,y,z WOW_SPAWN_ROT=yaw,pitch");
|
LOG_INFO("Optional spawn overrides (canonical WoW X,Y,Z): WOW_SPAWN_POS=x,y,z WOW_SPAWN_ROT=yaw,pitch");
|
||||||
}
|
}
|
||||||
|
|
||||||
rendering::LoadingScreen loadingScreen;
|
showProgress("Loading character model...", 0.05f);
|
||||||
bool loadingScreenOk = loadingScreen.initialize();
|
|
||||||
|
|
||||||
auto showStatus = [&](const char* msg) {
|
// Spawn player character (loads M2 model, skin, textures, animations, weapons)
|
||||||
if (!loadingScreenOk) return;
|
spawnPlayerCharacter();
|
||||||
loadingScreen.setStatus(msg);
|
|
||||||
loadingScreen.render();
|
|
||||||
window->swapBuffers();
|
|
||||||
};
|
|
||||||
|
|
||||||
showStatus("Loading terrain...");
|
showProgress("Loading terrain...", 0.25f);
|
||||||
|
|
||||||
// Set map name for zone-specific floor cache
|
// Set map name for zone-specific floor cache
|
||||||
if (renderer->getWMORenderer()) {
|
if (renderer->getWMORenderer()) {
|
||||||
|
|
@ -1396,6 +1421,8 @@ void Application::startSinglePlayer() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showProgress("Streaming terrain tiles...", 0.40f);
|
||||||
|
|
||||||
// Wait for surrounding terrain tiles to stream in
|
// Wait for surrounding terrain tiles to stream in
|
||||||
if (terrainOk && renderer->getTerrainManager() && renderer->getCamera()) {
|
if (terrainOk && renderer->getTerrainManager() && renderer->getCamera()) {
|
||||||
auto* terrainMgr = renderer->getTerrainManager();
|
auto* terrainMgr = renderer->getTerrainManager();
|
||||||
|
|
@ -1407,6 +1434,8 @@ void Application::startSinglePlayer() {
|
||||||
auto startTime = std::chrono::high_resolution_clock::now();
|
auto startTime = std::chrono::high_resolution_clock::now();
|
||||||
const float maxWaitSeconds = 15.0f;
|
const float maxWaitSeconds = 15.0f;
|
||||||
|
|
||||||
|
int initialPending = terrainMgr->getPendingTileCount();
|
||||||
|
|
||||||
while (terrainMgr->getPendingTileCount() > 0) {
|
while (terrainMgr->getPendingTileCount() > 0) {
|
||||||
// Poll events to keep window responsive
|
// Poll events to keep window responsive
|
||||||
SDL_Event event;
|
SDL_Event event;
|
||||||
|
|
@ -1416,19 +1445,34 @@ void Application::startSinglePlayer() {
|
||||||
loadingScreen.shutdown();
|
loadingScreen.shutdown();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (event.type == SDL_WINDOWEVENT &&
|
||||||
|
event.window.event == SDL_WINDOWEVENT_RESIZED) {
|
||||||
|
int w = event.window.data1;
|
||||||
|
int h = event.window.data2;
|
||||||
|
window->setSize(w, h);
|
||||||
|
glViewport(0, 0, w, h);
|
||||||
|
if (renderer && renderer->getCamera()) {
|
||||||
|
renderer->getCamera()->setAspectRatio(static_cast<float>(w) / h);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process ready tiles from worker threads
|
// Process ready tiles from worker threads
|
||||||
terrainMgr->update(*camera, 0.016f);
|
terrainMgr->update(*camera, 0.016f);
|
||||||
|
|
||||||
// Update loading screen with progress
|
// Update loading screen with tile progress (40% - 85% range)
|
||||||
if (loadingScreenOk) {
|
if (loadingScreenOk) {
|
||||||
int loaded = terrainMgr->getLoadedTileCount();
|
int loaded = terrainMgr->getLoadedTileCount();
|
||||||
int pending = terrainMgr->getPendingTileCount();
|
int pending = terrainMgr->getPendingTileCount();
|
||||||
|
float tileProgress = (initialPending > 0)
|
||||||
|
? static_cast<float>(initialPending - pending) / initialPending
|
||||||
|
: 1.0f;
|
||||||
|
float progress = 0.40f + tileProgress * 0.45f;
|
||||||
char buf[128];
|
char buf[128];
|
||||||
snprintf(buf, sizeof(buf), "Loading terrain... %d tiles loaded, %d remaining",
|
snprintf(buf, sizeof(buf), "Loading terrain... %d tiles loaded, %d remaining",
|
||||||
loaded, pending);
|
loaded, pending);
|
||||||
loadingScreen.setStatus(buf);
|
loadingScreen.setStatus(buf);
|
||||||
|
loadingScreen.setProgress(progress);
|
||||||
loadingScreen.render();
|
loadingScreen.render();
|
||||||
window->swapBuffers();
|
window->swapBuffers();
|
||||||
}
|
}
|
||||||
|
|
@ -1445,11 +1489,12 @@ void Application::startSinglePlayer() {
|
||||||
|
|
||||||
LOG_INFO("Terrain streaming complete: ", terrainMgr->getLoadedTileCount(), " tiles loaded");
|
LOG_INFO("Terrain streaming complete: ", terrainMgr->getLoadedTileCount(), " tiles loaded");
|
||||||
|
|
||||||
|
showProgress("Building collision cache...", 0.88f);
|
||||||
|
|
||||||
// Load zone-specific floor cache, or precompute if none exists
|
// Load zone-specific floor cache, or precompute if none exists
|
||||||
if (renderer->getWMORenderer()) {
|
if (renderer->getWMORenderer()) {
|
||||||
renderer->getWMORenderer()->loadFloorCache();
|
renderer->getWMORenderer()->loadFloorCache();
|
||||||
if (renderer->getWMORenderer()->getFloorCacheSize() == 0) {
|
if (renderer->getWMORenderer()->getFloorCacheSize() == 0) {
|
||||||
showStatus("Pre-computing collision cache...");
|
|
||||||
renderer->getWMORenderer()->precomputeFloorCache();
|
renderer->getWMORenderer()->precomputeFloorCache();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1461,10 +1506,7 @@ void Application::startSinglePlayer() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
showStatus("Spawning character...");
|
showProgress("Entering world...", 0.95f);
|
||||||
|
|
||||||
// Spawn player character on loaded terrain
|
|
||||||
spawnPlayerCharacter();
|
|
||||||
|
|
||||||
// Final camera reset: now that follow target exists and terrain is loaded,
|
// Final camera reset: now that follow target exists and terrain is loaded,
|
||||||
// snap the third-person camera into the correct orbit position.
|
// snap the third-person camera into the correct orbit position.
|
||||||
|
|
@ -1473,6 +1515,8 @@ void Application::startSinglePlayer() {
|
||||||
renderer->getCameraController()->startIntroPan(2.8f, 140.0f);
|
renderer->getCameraController()->startIntroPan(2.8f, 140.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showProgress("Entering world...", 1.0f);
|
||||||
|
|
||||||
if (loadingScreenOk) {
|
if (loadingScreenOk) {
|
||||||
loadingScreen.shutdown();
|
loadingScreen.shutdown();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
#include "rendering/loading_screen.hpp"
|
#include "rendering/loading_screen.hpp"
|
||||||
#include "core/logger.hpp"
|
#include "core/logger.hpp"
|
||||||
|
#include <imgui.h>
|
||||||
|
#include <imgui_impl_opengl3.h>
|
||||||
|
#include <imgui_impl_sdl2.h>
|
||||||
#include <random>
|
#include <random>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
#define STB_IMAGE_IMPLEMENTATION
|
#define STB_IMAGE_IMPLEMENTATION
|
||||||
#include "stb_image.h"
|
#include "stb_image.h"
|
||||||
|
|
@ -10,7 +14,6 @@ namespace wowee {
|
||||||
namespace rendering {
|
namespace rendering {
|
||||||
|
|
||||||
LoadingScreen::LoadingScreen() {
|
LoadingScreen::LoadingScreen() {
|
||||||
// Add loading screen image paths
|
|
||||||
imagePaths.push_back("assets/loading1.jpeg");
|
imagePaths.push_back("assets/loading1.jpeg");
|
||||||
imagePaths.push_back("assets/loading2.jpeg");
|
imagePaths.push_back("assets/loading2.jpeg");
|
||||||
}
|
}
|
||||||
|
|
@ -22,7 +25,7 @@ LoadingScreen::~LoadingScreen() {
|
||||||
bool LoadingScreen::initialize() {
|
bool LoadingScreen::initialize() {
|
||||||
LOG_INFO("Initializing loading screen");
|
LOG_INFO("Initializing loading screen");
|
||||||
|
|
||||||
// Create simple shader for textured quad
|
// Background image shader (textured quad)
|
||||||
const char* vertexSrc = R"(
|
const char* vertexSrc = R"(
|
||||||
#version 330 core
|
#version 330 core
|
||||||
layout (location = 0) in vec2 aPos;
|
layout (location = 0) in vec2 aPos;
|
||||||
|
|
@ -44,7 +47,6 @@ bool LoadingScreen::initialize() {
|
||||||
}
|
}
|
||||||
)";
|
)";
|
||||||
|
|
||||||
// Compile vertex shader
|
|
||||||
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
|
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
|
||||||
glShaderSource(vertexShader, 1, &vertexSrc, nullptr);
|
glShaderSource(vertexShader, 1, &vertexSrc, nullptr);
|
||||||
glCompileShader(vertexShader);
|
glCompileShader(vertexShader);
|
||||||
|
|
@ -58,7 +60,6 @@ bool LoadingScreen::initialize() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compile fragment shader
|
|
||||||
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
|
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
|
||||||
glShaderSource(fragmentShader, 1, &fragmentSrc, nullptr);
|
glShaderSource(fragmentShader, 1, &fragmentSrc, nullptr);
|
||||||
glCompileShader(fragmentShader);
|
glCompileShader(fragmentShader);
|
||||||
|
|
@ -71,7 +72,6 @@ bool LoadingScreen::initialize() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Link shader program
|
|
||||||
shaderId = glCreateProgram();
|
shaderId = glCreateProgram();
|
||||||
glAttachShader(shaderId, vertexShader);
|
glAttachShader(shaderId, vertexShader);
|
||||||
glAttachShader(shaderId, fragmentShader);
|
glAttachShader(shaderId, fragmentShader);
|
||||||
|
|
@ -88,7 +88,41 @@ bool LoadingScreen::initialize() {
|
||||||
glDeleteShader(vertexShader);
|
glDeleteShader(vertexShader);
|
||||||
glDeleteShader(fragmentShader);
|
glDeleteShader(fragmentShader);
|
||||||
|
|
||||||
|
// Simple solid-color shader for progress bar
|
||||||
|
const char* barVertSrc = R"(
|
||||||
|
#version 330 core
|
||||||
|
layout (location = 0) in vec2 aPos;
|
||||||
|
void main() {
|
||||||
|
gl_Position = vec4(aPos, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
const char* barFragSrc = R"(
|
||||||
|
#version 330 core
|
||||||
|
out vec4 FragColor;
|
||||||
|
uniform vec4 uColor;
|
||||||
|
void main() {
|
||||||
|
FragColor = uColor;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
GLuint bv = glCreateShader(GL_VERTEX_SHADER);
|
||||||
|
glShaderSource(bv, 1, &barVertSrc, nullptr);
|
||||||
|
glCompileShader(bv);
|
||||||
|
GLuint bf = glCreateShader(GL_FRAGMENT_SHADER);
|
||||||
|
glShaderSource(bf, 1, &barFragSrc, nullptr);
|
||||||
|
glCompileShader(bf);
|
||||||
|
|
||||||
|
barShaderId = glCreateProgram();
|
||||||
|
glAttachShader(barShaderId, bv);
|
||||||
|
glAttachShader(barShaderId, bf);
|
||||||
|
glLinkProgram(barShaderId);
|
||||||
|
|
||||||
|
glDeleteShader(bv);
|
||||||
|
glDeleteShader(bf);
|
||||||
|
|
||||||
createQuad();
|
createQuad();
|
||||||
|
createBarQuad();
|
||||||
selectRandomImage();
|
selectRandomImage();
|
||||||
|
|
||||||
LOG_INFO("Loading screen initialized");
|
LOG_INFO("Loading screen initialized");
|
||||||
|
|
@ -112,12 +146,23 @@ void LoadingScreen::shutdown() {
|
||||||
glDeleteProgram(shaderId);
|
glDeleteProgram(shaderId);
|
||||||
shaderId = 0;
|
shaderId = 0;
|
||||||
}
|
}
|
||||||
|
if (barVao) {
|
||||||
|
glDeleteVertexArrays(1, &barVao);
|
||||||
|
barVao = 0;
|
||||||
|
}
|
||||||
|
if (barVbo) {
|
||||||
|
glDeleteBuffers(1, &barVbo);
|
||||||
|
barVbo = 0;
|
||||||
|
}
|
||||||
|
if (barShaderId) {
|
||||||
|
glDeleteProgram(barShaderId);
|
||||||
|
barShaderId = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoadingScreen::selectRandomImage() {
|
void LoadingScreen::selectRandomImage() {
|
||||||
if (imagePaths.empty()) return;
|
if (imagePaths.empty()) return;
|
||||||
|
|
||||||
// Seed with current time
|
|
||||||
unsigned seed = static_cast<unsigned>(
|
unsigned seed = static_cast<unsigned>(
|
||||||
std::chrono::system_clock::now().time_since_epoch().count());
|
std::chrono::system_clock::now().time_since_epoch().count());
|
||||||
std::default_random_engine generator(seed);
|
std::default_random_engine generator(seed);
|
||||||
|
|
@ -130,13 +175,11 @@ void LoadingScreen::selectRandomImage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LoadingScreen::loadImage(const std::string& path) {
|
bool LoadingScreen::loadImage(const std::string& path) {
|
||||||
// Delete old texture if exists
|
|
||||||
if (textureId) {
|
if (textureId) {
|
||||||
glDeleteTextures(1, &textureId);
|
glDeleteTextures(1, &textureId);
|
||||||
textureId = 0;
|
textureId = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load image with stb_image
|
|
||||||
int channels;
|
int channels;
|
||||||
stbi_set_flip_vertically_on_load(true);
|
stbi_set_flip_vertically_on_load(true);
|
||||||
unsigned char* data = stbi_load(path.c_str(), &imageWidth, &imageHeight, &channels, 4);
|
unsigned char* data = stbi_load(path.c_str(), &imageWidth, &imageHeight, &channels, 4);
|
||||||
|
|
@ -148,7 +191,6 @@ bool LoadingScreen::loadImage(const std::string& path) {
|
||||||
|
|
||||||
LOG_INFO("Loaded loading screen image: ", imageWidth, "x", imageHeight);
|
LOG_INFO("Loaded loading screen image: ", imageWidth, "x", imageHeight);
|
||||||
|
|
||||||
// Create OpenGL texture
|
|
||||||
glGenTextures(1, &textureId);
|
glGenTextures(1, &textureId);
|
||||||
glBindTexture(GL_TEXTURE_2D, textureId);
|
glBindTexture(GL_TEXTURE_2D, textureId);
|
||||||
|
|
||||||
|
|
@ -167,16 +209,15 @@ bool LoadingScreen::loadImage(const std::string& path) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoadingScreen::createQuad() {
|
void LoadingScreen::createQuad() {
|
||||||
// Full-screen quad vertices (position + texcoord)
|
|
||||||
float vertices[] = {
|
float vertices[] = {
|
||||||
// Position // TexCoord
|
// Position // TexCoord
|
||||||
-1.0f, 1.0f, 0.0f, 1.0f, // Top-left
|
-1.0f, 1.0f, 0.0f, 1.0f,
|
||||||
-1.0f, -1.0f, 0.0f, 0.0f, // Bottom-left
|
-1.0f, -1.0f, 0.0f, 0.0f,
|
||||||
1.0f, -1.0f, 1.0f, 0.0f, // Bottom-right
|
1.0f, -1.0f, 1.0f, 0.0f,
|
||||||
|
|
||||||
-1.0f, 1.0f, 0.0f, 1.0f, // Top-left
|
-1.0f, 1.0f, 0.0f, 1.0f,
|
||||||
1.0f, -1.0f, 1.0f, 0.0f, // Bottom-right
|
1.0f, -1.0f, 1.0f, 0.0f,
|
||||||
1.0f, 1.0f, 1.0f, 1.0f // Top-right
|
1.0f, 1.0f, 1.0f, 1.0f
|
||||||
};
|
};
|
||||||
|
|
||||||
glGenVertexArrays(1, &vao);
|
glGenVertexArrays(1, &vao);
|
||||||
|
|
@ -186,38 +227,171 @@ void LoadingScreen::createQuad() {
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, vbo);
|
glBindBuffer(GL_ARRAY_BUFFER, vbo);
|
||||||
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
|
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
|
||||||
|
|
||||||
// Position attribute
|
|
||||||
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
|
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
|
||||||
glEnableVertexAttribArray(0);
|
glEnableVertexAttribArray(0);
|
||||||
|
|
||||||
// Texture coordinate attribute
|
|
||||||
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));
|
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));
|
||||||
glEnableVertexAttribArray(1);
|
glEnableVertexAttribArray(1);
|
||||||
|
|
||||||
glBindVertexArray(0);
|
glBindVertexArray(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoadingScreen::render() {
|
void LoadingScreen::createBarQuad() {
|
||||||
if (!textureId || !vao || !shaderId) return;
|
// Dynamic quad — vertices updated each frame via glBufferSubData
|
||||||
|
glGenVertexArrays(1, &barVao);
|
||||||
|
glGenBuffers(1, &barVbo);
|
||||||
|
|
||||||
|
glBindVertexArray(barVao);
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, barVbo);
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, 12 * sizeof(float), nullptr, GL_DYNAMIC_DRAW);
|
||||||
|
|
||||||
|
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0);
|
||||||
|
glEnableVertexAttribArray(0);
|
||||||
|
|
||||||
|
glBindVertexArray(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoadingScreen::render() {
|
||||||
|
if (!vao || !shaderId) return;
|
||||||
|
|
||||||
// Clear screen
|
|
||||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
// Disable depth test for 2D rendering
|
|
||||||
glDisable(GL_DEPTH_TEST);
|
glDisable(GL_DEPTH_TEST);
|
||||||
|
|
||||||
// Use shader and bind texture
|
// Draw background image
|
||||||
glUseProgram(shaderId);
|
if (textureId) {
|
||||||
glActiveTexture(GL_TEXTURE0);
|
glUseProgram(shaderId);
|
||||||
glBindTexture(GL_TEXTURE_2D, textureId);
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, textureId);
|
||||||
|
glBindVertexArray(vao);
|
||||||
|
glDrawArrays(GL_TRIANGLES, 0, 6);
|
||||||
|
glBindVertexArray(0);
|
||||||
|
}
|
||||||
|
|
||||||
// Draw quad
|
// Draw progress bar at bottom center
|
||||||
glBindVertexArray(vao);
|
if (barVao && barShaderId) {
|
||||||
glDrawArrays(GL_TRIANGLES, 0, 6);
|
// Bar dimensions in NDC: centered, near bottom
|
||||||
glBindVertexArray(0);
|
const float barWidth = 0.6f; // half-width in NDC (total 1.2 of 2.0 range = 60% of screen)
|
||||||
|
const float barHeight = 0.015f;
|
||||||
|
const float barY = -0.82f; // near bottom
|
||||||
|
|
||||||
|
float left = -barWidth;
|
||||||
|
float right = -barWidth + 2.0f * barWidth * loadProgress;
|
||||||
|
float top = barY + barHeight;
|
||||||
|
float bottom = barY - barHeight;
|
||||||
|
|
||||||
|
// Background (dark)
|
||||||
|
{
|
||||||
|
float bgVerts[] = {
|
||||||
|
-barWidth, top,
|
||||||
|
-barWidth, bottom,
|
||||||
|
barWidth, bottom,
|
||||||
|
-barWidth, top,
|
||||||
|
barWidth, bottom,
|
||||||
|
barWidth, top,
|
||||||
|
};
|
||||||
|
glUseProgram(barShaderId);
|
||||||
|
GLint colorLoc = glGetUniformLocation(barShaderId, "uColor");
|
||||||
|
glUniform4f(colorLoc, 0.1f, 0.1f, 0.1f, 0.8f);
|
||||||
|
|
||||||
|
glEnable(GL_BLEND);
|
||||||
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
|
||||||
|
glBindVertexArray(barVao);
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, barVbo);
|
||||||
|
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(bgVerts), bgVerts);
|
||||||
|
glDrawArrays(GL_TRIANGLES, 0, 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filled portion (gold/amber like WoW)
|
||||||
|
if (loadProgress > 0.001f) {
|
||||||
|
float fillVerts[] = {
|
||||||
|
left, top,
|
||||||
|
left, bottom,
|
||||||
|
right, bottom,
|
||||||
|
left, top,
|
||||||
|
right, bottom,
|
||||||
|
right, top,
|
||||||
|
};
|
||||||
|
GLint colorLoc = glGetUniformLocation(barShaderId, "uColor");
|
||||||
|
glUniform4f(colorLoc, 0.78f, 0.61f, 0.13f, 1.0f);
|
||||||
|
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, barVbo);
|
||||||
|
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(fillVerts), fillVerts);
|
||||||
|
glDrawArrays(GL_TRIANGLES, 0, 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Border (thin bright outline)
|
||||||
|
{
|
||||||
|
const float borderInset = 0.002f;
|
||||||
|
float borderLeft = -barWidth - borderInset;
|
||||||
|
float borderRight = barWidth + borderInset;
|
||||||
|
float borderTop = top + borderInset;
|
||||||
|
float borderBottom = bottom - borderInset;
|
||||||
|
|
||||||
|
// Draw 4 thin border edges as line strip
|
||||||
|
glUseProgram(barShaderId);
|
||||||
|
GLint colorLoc = glGetUniformLocation(barShaderId, "uColor");
|
||||||
|
glUniform4f(colorLoc, 0.55f, 0.43f, 0.1f, 1.0f);
|
||||||
|
|
||||||
|
float borderVerts[] = {
|
||||||
|
borderLeft, borderTop,
|
||||||
|
borderRight, borderTop,
|
||||||
|
borderRight, borderBottom,
|
||||||
|
borderLeft, borderBottom,
|
||||||
|
borderLeft, borderTop,
|
||||||
|
};
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, barVbo);
|
||||||
|
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(borderVerts), borderVerts);
|
||||||
|
glDrawArrays(GL_LINE_STRIP, 0, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindVertexArray(0);
|
||||||
|
glDisable(GL_BLEND);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw status text and percentage with ImGui overlay
|
||||||
|
{
|
||||||
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
|
float screenW = io.DisplaySize.x;
|
||||||
|
float screenH = io.DisplaySize.y;
|
||||||
|
|
||||||
|
ImGui_ImplOpenGL3_NewFrame();
|
||||||
|
ImGui_ImplSDL2_NewFrame();
|
||||||
|
ImGui::NewFrame();
|
||||||
|
|
||||||
|
// Invisible fullscreen window for text overlay
|
||||||
|
ImGui::SetNextWindowPos(ImVec2(0, 0));
|
||||||
|
ImGui::SetNextWindowSize(ImVec2(screenW, screenH));
|
||||||
|
ImGui::Begin("##LoadingOverlay", nullptr,
|
||||||
|
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
|
||||||
|
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar |
|
||||||
|
ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoBackground |
|
||||||
|
ImGuiWindowFlags_NoBringToFrontOnFocus);
|
||||||
|
|
||||||
|
// Percentage text centered above bar
|
||||||
|
char pctBuf[32];
|
||||||
|
snprintf(pctBuf, sizeof(pctBuf), "%d%%", static_cast<int>(loadProgress * 100.0f));
|
||||||
|
|
||||||
|
float barCenterY = screenH * (1.0f - ((-0.82f + 1.0f) / 2.0f)); // NDC -0.82 to screen Y
|
||||||
|
float textY = barCenterY - 30.0f;
|
||||||
|
|
||||||
|
ImVec2 pctSize = ImGui::CalcTextSize(pctBuf);
|
||||||
|
ImGui::SetCursorPos(ImVec2((screenW - pctSize.x) * 0.5f, textY));
|
||||||
|
ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 1.0f), "%s", pctBuf);
|
||||||
|
|
||||||
|
// Status text centered below bar
|
||||||
|
float statusY = barCenterY + 16.0f;
|
||||||
|
ImVec2 statusSize = ImGui::CalcTextSize(statusText.c_str());
|
||||||
|
ImGui::SetCursorPos(ImVec2((screenW - statusSize.x) * 0.5f, statusY));
|
||||||
|
ImGui::TextColored(ImVec4(0.8f, 0.8f, 0.8f, 1.0f), "%s", statusText.c_str());
|
||||||
|
|
||||||
|
ImGui::End();
|
||||||
|
ImGui::Render();
|
||||||
|
|
||||||
|
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||||
|
}
|
||||||
|
|
||||||
// Re-enable depth test
|
|
||||||
glEnable(GL_DEPTH_TEST);
|
glEnable(GL_DEPTH_TEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue