mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
Add loading screen with random WOWEE splash images
- Add loading screen system with stb_image for JPEG loading - Two loading screen images (orc and dwarf) randomly selected - Display loading screen while terrain data loads - Cache WMO inverse matrices to reduce per-frame computation - Stub WMO liquid rendering (needs coordinate system fix) - Update spawn point to Stormwind Trade District
This commit is contained in:
parent
665a73e75f
commit
01bf3b4c08
14 changed files with 8395 additions and 165 deletions
|
|
@ -133,6 +133,7 @@ set(WOWEE_SOURCES
|
|||
src/rendering/m2_renderer.cpp
|
||||
src/rendering/minimap.cpp
|
||||
src/rendering/swim_effects.cpp
|
||||
src/rendering/loading_screen.cpp
|
||||
|
||||
# UI
|
||||
src/ui/ui_manager.cpp
|
||||
|
|
@ -205,6 +206,7 @@ set(WOWEE_HEADERS
|
|||
include/rendering/swim_effects.hpp
|
||||
include/rendering/character_renderer.hpp
|
||||
include/rendering/wmo_renderer.hpp
|
||||
include/rendering/loading_screen.hpp
|
||||
|
||||
include/ui/ui_manager.hpp
|
||||
include/ui/auth_screen.hpp
|
||||
|
|
@ -221,6 +223,7 @@ add_executable(wowee ${WOWEE_SOURCES} ${WOWEE_HEADERS})
|
|||
target_include_directories(wowee PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/extern
|
||||
)
|
||||
|
||||
# Link libraries
|
||||
|
|
@ -259,8 +262,8 @@ else()
|
|||
target_compile_options(wowee PRIVATE -Wall -Wextra -Wpedantic)
|
||||
endif()
|
||||
|
||||
# Copy shaders to build directory
|
||||
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/assets/shaders
|
||||
# Copy assets to build directory
|
||||
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/assets
|
||||
DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
|
||||
|
||||
# Install targets
|
||||
|
|
|
|||
BIN
assets/loading1.jpg
Normal file
BIN
assets/loading1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 88 KiB |
BIN
assets/loading2.jpg
Normal file
BIN
assets/loading2.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 92 KiB |
7988
extern/stb_image.h
vendored
Normal file
7988
extern/stb_image.h
vendored
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -100,6 +100,20 @@ struct WMOPortalPlane {
|
|||
float distance;
|
||||
};
|
||||
|
||||
// WMO Liquid (MLIQ chunk data)
|
||||
struct WMOLiquid {
|
||||
uint32_t xVerts = 0; // Vertices in X direction
|
||||
uint32_t yVerts = 0; // Vertices in Y direction
|
||||
uint32_t xTiles = 0; // Tiles in X (= xVerts - 1)
|
||||
uint32_t yTiles = 0; // Tiles in Y (= yVerts - 1)
|
||||
glm::vec3 basePosition; // Corner position in model space
|
||||
uint16_t materialId = 0; // Liquid material/type
|
||||
std::vector<float> heights; // Height per vertex (xVerts * yVerts)
|
||||
std::vector<uint8_t> flags; // Flags per tile (xTiles * yTiles)
|
||||
|
||||
bool hasLiquid() const { return xVerts > 0 && yVerts > 0; }
|
||||
};
|
||||
|
||||
// WMO Group Vertex
|
||||
struct WMOVertex {
|
||||
glm::vec3 position;
|
||||
|
|
@ -143,6 +157,9 @@ struct WMOGroup {
|
|||
// BSP tree (for collision - optional)
|
||||
std::vector<uint8_t> bspNodes;
|
||||
|
||||
// Liquid data (MLIQ chunk)
|
||||
WMOLiquid liquid;
|
||||
|
||||
std::string name;
|
||||
std::string description;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -126,10 +126,9 @@ private:
|
|||
static constexpr float WOW_GRAVITY = -19.29f;
|
||||
static constexpr float WOW_JUMP_VELOCITY = 7.96f;
|
||||
|
||||
// Default spawn position (on terrain near Stormwind)
|
||||
// Terrain chunks are around X=[-9100, -9066], Y=[-533, 0]
|
||||
glm::vec3 defaultPosition = glm::vec3(-9080.0f, -100.0f, 100.0f);
|
||||
float defaultYaw = 180.0f; // Look south toward Stormwind gate
|
||||
// Default spawn position (Stormwind Trade District)
|
||||
glm::vec3 defaultPosition = glm::vec3(-8830.0f, 640.0f, 200.0f);
|
||||
float defaultYaw = 0.0f; // Look north toward canals
|
||||
float defaultPitch = -5.0f;
|
||||
};
|
||||
|
||||
|
|
|
|||
50
include/rendering/loading_screen.hpp
Normal file
50
include/rendering/loading_screen.hpp
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
#pragma once
|
||||
|
||||
#include <GL/glew.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace wowee {
|
||||
namespace rendering {
|
||||
|
||||
class LoadingScreen {
|
||||
public:
|
||||
LoadingScreen();
|
||||
~LoadingScreen();
|
||||
|
||||
bool initialize();
|
||||
void shutdown();
|
||||
|
||||
// Select a random loading screen image
|
||||
void selectRandomImage();
|
||||
|
||||
// Render the loading screen (call in a loop while loading)
|
||||
void render();
|
||||
|
||||
// Update loading progress (0.0 to 1.0)
|
||||
void setProgress(float progress) { loadProgress = progress; }
|
||||
|
||||
// Set loading status text
|
||||
void setStatus(const std::string& status) { statusText = status; }
|
||||
|
||||
private:
|
||||
bool loadImage(const std::string& path);
|
||||
void createQuad();
|
||||
|
||||
GLuint textureId = 0;
|
||||
GLuint vao = 0;
|
||||
GLuint vbo = 0;
|
||||
GLuint shaderId = 0;
|
||||
|
||||
std::vector<std::string> imagePaths;
|
||||
int currentImageIndex = 0;
|
||||
|
||||
float loadProgress = 0.0f;
|
||||
std::string statusText = "Loading...";
|
||||
|
||||
int imageWidth = 0;
|
||||
int imageHeight = 0;
|
||||
};
|
||||
|
||||
} // namespace rendering
|
||||
} // namespace wowee
|
||||
|
|
@ -9,6 +9,7 @@ namespace wowee {
|
|||
namespace pipeline {
|
||||
struct ADTTerrain;
|
||||
struct LiquidData;
|
||||
struct WMOLiquid;
|
||||
}
|
||||
|
||||
namespace rendering {
|
||||
|
|
@ -28,6 +29,9 @@ struct WaterSurface {
|
|||
// Owning tile coordinates (for per-tile removal)
|
||||
int tileX = -1, tileY = -1;
|
||||
|
||||
// Owning WMO instance ID (for WMO liquid removal, 0 = terrain water)
|
||||
uint32_t wmoId = 0;
|
||||
|
||||
// Water layer dimensions within chunk (0-7 offset, 1-8 size)
|
||||
uint8_t xOffset = 0;
|
||||
uint8_t yOffset = 0;
|
||||
|
|
@ -73,6 +77,20 @@ public:
|
|||
void loadFromTerrain(const pipeline::ADTTerrain& terrain, bool append = false,
|
||||
int tileX = -1, int tileY = -1);
|
||||
|
||||
/**
|
||||
* Load water surface from WMO liquid data
|
||||
* @param liquid WMO liquid data from MLIQ chunk
|
||||
* @param modelMatrix WMO instance model matrix for transforming to world space
|
||||
* @param wmoId WMO instance ID for tracking ownership
|
||||
*/
|
||||
void loadFromWMO(const pipeline::WMOLiquid& liquid, const glm::mat4& modelMatrix, uint32_t wmoId);
|
||||
|
||||
/**
|
||||
* Remove all water surfaces belonging to a specific WMO instance
|
||||
* @param wmoId WMO instance ID
|
||||
*/
|
||||
void removeWMO(uint32_t wmoId);
|
||||
|
||||
/**
|
||||
* Remove all water surfaces belonging to a specific tile
|
||||
* @param tileX Tile X coordinate
|
||||
|
|
|
|||
|
|
@ -221,6 +221,7 @@ private:
|
|||
glm::vec3 rotation; // Euler angles (radians)
|
||||
float scale;
|
||||
glm::mat4 modelMatrix;
|
||||
glm::mat4 invModelMatrix; // Cached inverse for collision
|
||||
|
||||
void updateModelMatrix();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
#include "rendering/character_renderer.hpp"
|
||||
#include "rendering/wmo_renderer.hpp"
|
||||
#include "rendering/minimap.hpp"
|
||||
#include "rendering/loading_screen.hpp"
|
||||
#include <imgui.h>
|
||||
#include "pipeline/m2_loader.hpp"
|
||||
#include "pipeline/wmo_loader.hpp"
|
||||
|
|
@ -107,13 +108,44 @@ bool Application::initialize() {
|
|||
void Application::run() {
|
||||
LOG_INFO("Starting main loop");
|
||||
|
||||
// Auto-load terrain for testing
|
||||
// Show loading screen while loading initial data
|
||||
rendering::LoadingScreen loadingScreen;
|
||||
if (loadingScreen.initialize()) {
|
||||
// Render loading screen
|
||||
loadingScreen.setStatus("Initializing...");
|
||||
loadingScreen.render();
|
||||
window->swapBuffers();
|
||||
|
||||
// Load terrain data
|
||||
if (assetManager && assetManager->isInitialized() && renderer) {
|
||||
loadingScreen.setStatus("Loading terrain...");
|
||||
loadingScreen.render();
|
||||
window->swapBuffers();
|
||||
|
||||
renderer->loadTestTerrain(assetManager.get(), "World\\Maps\\Azeroth\\Azeroth_32_49.adt");
|
||||
|
||||
loadingScreen.setStatus("Spawning character...");
|
||||
loadingScreen.render();
|
||||
window->swapBuffers();
|
||||
|
||||
// Spawn player character with third-person camera
|
||||
spawnPlayerCharacter();
|
||||
}
|
||||
|
||||
loadingScreen.setStatus("Ready!");
|
||||
loadingScreen.render();
|
||||
window->swapBuffers();
|
||||
SDL_Delay(500); // Brief pause to show "Ready!"
|
||||
|
||||
loadingScreen.shutdown();
|
||||
} else {
|
||||
// Fallback: load without loading screen
|
||||
if (assetManager && assetManager->isInitialized() && renderer) {
|
||||
renderer->loadTestTerrain(assetManager.get(), "World\\Maps\\Azeroth\\Azeroth_32_49.adt");
|
||||
spawnPlayerCharacter();
|
||||
}
|
||||
}
|
||||
|
||||
auto lastTime = std::chrono::high_resolution_clock::now();
|
||||
|
||||
while (running && !window->shouldClose()) {
|
||||
|
|
@ -452,146 +484,7 @@ void Application::run() {
|
|||
LOG_INFO("Minimap ", renderer->getMinimap()->isEnabled() ? "enabled" : "disabled");
|
||||
}
|
||||
}
|
||||
// O: Spawn test WMO building at camera position
|
||||
// Shift+O: Load real WMO from MPQ
|
||||
else if (event.key.keysym.scancode == SDL_SCANCODE_O) {
|
||||
// Check if Shift is held for real WMO loading
|
||||
bool shiftHeld = (event.key.keysym.mod & KMOD_SHIFT) != 0;
|
||||
|
||||
if (shiftHeld && assetManager && assetManager->isInitialized() &&
|
||||
renderer && renderer->getWMORenderer() && renderer->getCamera()) {
|
||||
// Load a real WMO from MPQ (try a simple mailbox)
|
||||
auto* wmoRenderer = renderer->getWMORenderer();
|
||||
auto* camera = renderer->getCamera();
|
||||
|
||||
// Try to load a simple WMO - mailbox is small and common
|
||||
const char* wmoPath = "World\\wmo\\Azeroth\\Buildings\\Human_Mailbox\\Human_Mailbox.wmo";
|
||||
LOG_INFO("Loading real WMO from MPQ: ", wmoPath);
|
||||
|
||||
auto wmoData = assetManager->readFile(wmoPath);
|
||||
if (wmoData.empty()) {
|
||||
LOG_WARNING("Failed to load WMO file from MPQ. Trying alternative path...");
|
||||
// Try alternative path
|
||||
wmoPath = "World\\wmo\\Dungeon\\LD_Prison\\LD_Prison_Cell01.wmo";
|
||||
wmoData = assetManager->readFile(wmoPath);
|
||||
}
|
||||
|
||||
if (!wmoData.empty()) {
|
||||
// Parse WMO
|
||||
pipeline::WMOModel wmoModel = pipeline::WMOLoader::load(wmoData);
|
||||
|
||||
if (wmoModel.isValid()) {
|
||||
// Use unique model ID (2 for real WMOs)
|
||||
static uint32_t nextModelId = 2;
|
||||
uint32_t modelId = nextModelId++;
|
||||
|
||||
if (wmoRenderer->loadModel(wmoModel, modelId)) {
|
||||
// Spawn WMO in front of camera
|
||||
glm::vec3 spawnPos = camera->getPosition() + camera->getForward() * 20.0f;
|
||||
uint32_t instanceId = wmoRenderer->createInstance(modelId, spawnPos);
|
||||
|
||||
if (instanceId > 0) {
|
||||
LOG_INFO("Spawned real WMO (", wmoModel.groups.size(), " groups) at (",
|
||||
static_cast<int>(spawnPos.x), ", ",
|
||||
static_cast<int>(spawnPos.y), ", ",
|
||||
static_cast<int>(spawnPos.z), ")");
|
||||
LOG_INFO("WMO has ", wmoModel.materials.size(), " materials, ",
|
||||
wmoModel.textures.size(), " textures");
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR("Failed to load WMO model into renderer");
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR("Failed to parse WMO file");
|
||||
}
|
||||
} else {
|
||||
LOG_WARNING("WMO file not found in MPQ archives");
|
||||
LOG_INFO("Make sure WOW_DATA_PATH environment variable points to valid WoW 3.3.5a installation");
|
||||
}
|
||||
}
|
||||
else if (renderer && renderer->getWMORenderer() && renderer->getCamera()) {
|
||||
auto* wmoRenderer = renderer->getWMORenderer();
|
||||
auto* camera = renderer->getCamera();
|
||||
|
||||
// Create a simple cube building as placeholder WMO
|
||||
// (Real WMO would be loaded from MPQ)
|
||||
pipeline::WMOModel testWMO;
|
||||
testWMO.version = 17;
|
||||
testWMO.nGroups = 1;
|
||||
|
||||
// Create a single group with cube geometry
|
||||
pipeline::WMOGroup group;
|
||||
group.flags = 0;
|
||||
group.groupId = 0;
|
||||
|
||||
// Cube building vertices (larger than character cube)
|
||||
float size = 5.0f;
|
||||
std::vector<glm::vec3> cubePos = {
|
||||
{-size, -size, -size}, { size, -size, -size},
|
||||
{ size, size, -size}, {-size, size, -size},
|
||||
{-size, -size, size}, { size, -size, size},
|
||||
{ size, size, size}, {-size, size, size}
|
||||
};
|
||||
|
||||
for (const auto& pos : cubePos) {
|
||||
pipeline::WMOVertex v;
|
||||
v.position = pos;
|
||||
v.normal = glm::normalize(pos);
|
||||
v.texCoord = glm::vec2(0.5f);
|
||||
v.color = glm::vec4(0.8f, 0.7f, 0.6f, 1.0f); // Stone color
|
||||
group.vertices.push_back(v);
|
||||
}
|
||||
|
||||
// Cube indices (12 triangles, 36 indices)
|
||||
uint16_t cubeIndices[] = {
|
||||
0,1,2, 0,2,3, // Front
|
||||
4,6,5, 4,7,6, // Back
|
||||
0,4,5, 0,5,1, // Bottom
|
||||
2,6,7, 2,7,3, // Top
|
||||
0,3,7, 0,7,4, // Left
|
||||
1,5,6, 1,6,2 // Right
|
||||
};
|
||||
for (uint16_t idx : cubeIndices) {
|
||||
group.indices.push_back(idx);
|
||||
}
|
||||
|
||||
// Bounding box
|
||||
group.boundingBoxMin = glm::vec3(-size);
|
||||
group.boundingBoxMax = glm::vec3(size);
|
||||
|
||||
// Single batch (no materials for now)
|
||||
pipeline::WMOBatch batch;
|
||||
batch.startIndex = 0;
|
||||
batch.indexCount = 36;
|
||||
batch.startVertex = 0;
|
||||
batch.lastVertex = 7;
|
||||
batch.materialId = 0;
|
||||
group.batches.push_back(batch);
|
||||
|
||||
testWMO.groups.push_back(group);
|
||||
testWMO.boundingBoxMin = glm::vec3(-size);
|
||||
testWMO.boundingBoxMax = glm::vec3(size);
|
||||
|
||||
// Load WMO model (reuse ID 1 for simplicity)
|
||||
static bool wmoModelLoaded = false;
|
||||
if (!wmoModelLoaded) {
|
||||
wmoRenderer->loadModel(testWMO, 1);
|
||||
wmoModelLoaded = true;
|
||||
}
|
||||
|
||||
// Spawn WMO in front of camera
|
||||
glm::vec3 spawnPos = camera->getPosition() + camera->getForward() * 20.0f;
|
||||
uint32_t instanceId = wmoRenderer->createInstance(1, spawnPos);
|
||||
|
||||
if (instanceId > 0) {
|
||||
LOG_INFO("Spawned test WMO building at (",
|
||||
static_cast<int>(spawnPos.x), ", ",
|
||||
static_cast<int>(spawnPos.y), ", ",
|
||||
static_cast<int>(spawnPos.z), ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
// P: Remove all WMO buildings
|
||||
// P: Remove all WMO buildings (O key removed)
|
||||
else if (event.key.keysym.scancode == SDL_SCANCODE_P) {
|
||||
if (renderer && renderer->getWMORenderer()) {
|
||||
renderer->getWMORenderer()->clearInstances();
|
||||
|
|
|
|||
225
src/rendering/loading_screen.cpp
Normal file
225
src/rendering/loading_screen.cpp
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
#include "rendering/loading_screen.hpp"
|
||||
#include "core/logger.hpp"
|
||||
#include <random>
|
||||
#include <chrono>
|
||||
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include "stb_image.h"
|
||||
|
||||
namespace wowee {
|
||||
namespace rendering {
|
||||
|
||||
LoadingScreen::LoadingScreen() {
|
||||
// Add loading screen image paths
|
||||
imagePaths.push_back("assets/loading1.jpg");
|
||||
imagePaths.push_back("assets/loading2.jpg");
|
||||
}
|
||||
|
||||
LoadingScreen::~LoadingScreen() {
|
||||
shutdown();
|
||||
}
|
||||
|
||||
bool LoadingScreen::initialize() {
|
||||
LOG_INFO("Initializing loading screen");
|
||||
|
||||
// Create simple shader for textured quad
|
||||
const char* vertexSrc = R"(
|
||||
#version 330 core
|
||||
layout (location = 0) in vec2 aPos;
|
||||
layout (location = 1) in vec2 aTexCoord;
|
||||
out vec2 TexCoord;
|
||||
void main() {
|
||||
gl_Position = vec4(aPos, 0.0, 1.0);
|
||||
TexCoord = aTexCoord;
|
||||
}
|
||||
)";
|
||||
|
||||
const char* fragmentSrc = R"(
|
||||
#version 330 core
|
||||
in vec2 TexCoord;
|
||||
out vec4 FragColor;
|
||||
uniform sampler2D screenTexture;
|
||||
void main() {
|
||||
FragColor = texture(screenTexture, TexCoord);
|
||||
}
|
||||
)";
|
||||
|
||||
// Compile vertex shader
|
||||
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
|
||||
glShaderSource(vertexShader, 1, &vertexSrc, nullptr);
|
||||
glCompileShader(vertexShader);
|
||||
|
||||
GLint success;
|
||||
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
|
||||
if (!success) {
|
||||
char infoLog[512];
|
||||
glGetShaderInfoLog(vertexShader, 512, nullptr, infoLog);
|
||||
LOG_ERROR("Loading screen vertex shader compilation failed: ", infoLog);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Compile fragment shader
|
||||
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
|
||||
glShaderSource(fragmentShader, 1, &fragmentSrc, nullptr);
|
||||
glCompileShader(fragmentShader);
|
||||
|
||||
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
|
||||
if (!success) {
|
||||
char infoLog[512];
|
||||
glGetShaderInfoLog(fragmentShader, 512, nullptr, infoLog);
|
||||
LOG_ERROR("Loading screen fragment shader compilation failed: ", infoLog);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Link shader program
|
||||
shaderId = glCreateProgram();
|
||||
glAttachShader(shaderId, vertexShader);
|
||||
glAttachShader(shaderId, fragmentShader);
|
||||
glLinkProgram(shaderId);
|
||||
|
||||
glGetProgramiv(shaderId, GL_LINK_STATUS, &success);
|
||||
if (!success) {
|
||||
char infoLog[512];
|
||||
glGetProgramInfoLog(shaderId, 512, nullptr, infoLog);
|
||||
LOG_ERROR("Loading screen shader linking failed: ", infoLog);
|
||||
return false;
|
||||
}
|
||||
|
||||
glDeleteShader(vertexShader);
|
||||
glDeleteShader(fragmentShader);
|
||||
|
||||
createQuad();
|
||||
selectRandomImage();
|
||||
|
||||
LOG_INFO("Loading screen initialized");
|
||||
return true;
|
||||
}
|
||||
|
||||
void LoadingScreen::shutdown() {
|
||||
if (textureId) {
|
||||
glDeleteTextures(1, &textureId);
|
||||
textureId = 0;
|
||||
}
|
||||
if (vao) {
|
||||
glDeleteVertexArrays(1, &vao);
|
||||
vao = 0;
|
||||
}
|
||||
if (vbo) {
|
||||
glDeleteBuffers(1, &vbo);
|
||||
vbo = 0;
|
||||
}
|
||||
if (shaderId) {
|
||||
glDeleteProgram(shaderId);
|
||||
shaderId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void LoadingScreen::selectRandomImage() {
|
||||
if (imagePaths.empty()) return;
|
||||
|
||||
// Seed with current time
|
||||
unsigned seed = static_cast<unsigned>(
|
||||
std::chrono::system_clock::now().time_since_epoch().count());
|
||||
std::default_random_engine generator(seed);
|
||||
std::uniform_int_distribution<int> distribution(0, imagePaths.size() - 1);
|
||||
|
||||
currentImageIndex = distribution(generator);
|
||||
LOG_INFO("Selected loading screen: ", imagePaths[currentImageIndex]);
|
||||
|
||||
loadImage(imagePaths[currentImageIndex]);
|
||||
}
|
||||
|
||||
bool LoadingScreen::loadImage(const std::string& path) {
|
||||
// Delete old texture if exists
|
||||
if (textureId) {
|
||||
glDeleteTextures(1, &textureId);
|
||||
textureId = 0;
|
||||
}
|
||||
|
||||
// Load image with stb_image
|
||||
int channels;
|
||||
stbi_set_flip_vertically_on_load(true);
|
||||
unsigned char* data = stbi_load(path.c_str(), &imageWidth, &imageHeight, &channels, 4);
|
||||
|
||||
if (!data) {
|
||||
LOG_ERROR("Failed to load loading screen image: ", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_INFO("Loaded loading screen image: ", imageWidth, "x", imageHeight);
|
||||
|
||||
// Create OpenGL texture
|
||||
glGenTextures(1, &textureId);
|
||||
glBindTexture(GL_TEXTURE_2D, textureId);
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, imageWidth, imageHeight, 0,
|
||||
GL_RGBA, GL_UNSIGNED_BYTE, data);
|
||||
|
||||
stbi_image_free(data);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void LoadingScreen::createQuad() {
|
||||
// Full-screen quad vertices (position + texcoord)
|
||||
float vertices[] = {
|
||||
// Position // TexCoord
|
||||
-1.0f, 1.0f, 0.0f, 1.0f, // Top-left
|
||||
-1.0f, -1.0f, 0.0f, 0.0f, // Bottom-left
|
||||
1.0f, -1.0f, 1.0f, 0.0f, // Bottom-right
|
||||
|
||||
-1.0f, 1.0f, 0.0f, 1.0f, // Top-left
|
||||
1.0f, -1.0f, 1.0f, 0.0f, // Bottom-right
|
||||
1.0f, 1.0f, 1.0f, 1.0f // Top-right
|
||||
};
|
||||
|
||||
glGenVertexArrays(1, &vao);
|
||||
glGenBuffers(1, &vbo);
|
||||
|
||||
glBindVertexArray(vao);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
|
||||
|
||||
// Position attribute
|
||||
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
|
||||
glEnableVertexAttribArray(0);
|
||||
|
||||
// Texture coordinate attribute
|
||||
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));
|
||||
glEnableVertexAttribArray(1);
|
||||
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
void LoadingScreen::render() {
|
||||
if (!textureId || !vao || !shaderId) return;
|
||||
|
||||
// Clear screen
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
// Disable depth test for 2D rendering
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
|
||||
// Use shader and bind texture
|
||||
glUseProgram(shaderId);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, textureId);
|
||||
|
||||
// Draw quad
|
||||
glBindVertexArray(vao);
|
||||
glDrawArrays(GL_TRIANGLES, 0, 6);
|
||||
glBindVertexArray(0);
|
||||
|
||||
// Re-enable depth test
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
}
|
||||
|
||||
} // namespace rendering
|
||||
} // namespace wowee
|
||||
|
|
@ -469,15 +469,37 @@ void TerrainManager::finalizeTile(std::unique_ptr<PendingTile> pending) {
|
|||
}
|
||||
|
||||
int loadedWMOs = 0;
|
||||
int loadedLiquids = 0;
|
||||
for (auto& wmoReady : pending->wmoModels) {
|
||||
if (wmoRenderer->loadModel(wmoReady.model, wmoReady.modelId)) {
|
||||
uint32_t wmoInstId = wmoRenderer->createInstance(wmoReady.modelId, wmoReady.position, wmoReady.rotation);
|
||||
if (wmoInstId) {
|
||||
wmoInstanceIds.push_back(wmoInstId);
|
||||
loadedWMOs++;
|
||||
|
||||
// Load WMO liquids (canals, pools, etc.)
|
||||
if (waterRenderer) {
|
||||
// Compute the same model matrix as WMORenderer uses
|
||||
glm::mat4 modelMatrix = glm::mat4(1.0f);
|
||||
modelMatrix = glm::translate(modelMatrix, wmoReady.position);
|
||||
modelMatrix = glm::rotate(modelMatrix, wmoReady.rotation.z, glm::vec3(0.0f, 0.0f, 1.0f));
|
||||
modelMatrix = glm::rotate(modelMatrix, wmoReady.rotation.y, glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
modelMatrix = glm::rotate(modelMatrix, wmoReady.rotation.x, glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
|
||||
// Load liquids from each WMO group
|
||||
for (const auto& group : wmoReady.model.groups) {
|
||||
if (group.liquid.hasLiquid()) {
|
||||
waterRenderer->loadFromWMO(group.liquid, modelMatrix, wmoInstId);
|
||||
loadedLiquids++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (loadedLiquids > 0) {
|
||||
LOG_INFO(" Loaded WMO liquids for tile [", x, ",", y, "]: ", loadedLiquids);
|
||||
}
|
||||
|
||||
// Upload WMO doodad M2 models
|
||||
if (m2Renderer) {
|
||||
|
|
@ -608,9 +630,13 @@ void TerrainManager::unloadTile(int x, int y) {
|
|||
LOG_DEBUG(" Removed ", tile->m2InstanceIds.size(), " M2 instances");
|
||||
}
|
||||
|
||||
// Remove WMO instances
|
||||
// Remove WMO instances and their liquids
|
||||
if (wmoRenderer) {
|
||||
for (uint32_t id : tile->wmoInstanceIds) {
|
||||
// Remove WMO liquids associated with this instance
|
||||
if (waterRenderer) {
|
||||
waterRenderer->removeWMO(id);
|
||||
}
|
||||
wmoRenderer->removeInstance(id);
|
||||
}
|
||||
LOG_DEBUG(" Removed ", tile->wmoInstanceIds.size(), " WMO instances");
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#include "rendering/shader.hpp"
|
||||
#include "rendering/camera.hpp"
|
||||
#include "pipeline/adt_loader.hpp"
|
||||
#include "pipeline/wmo_loader.hpp"
|
||||
#include "core/logger.hpp"
|
||||
#include <GL/glew.h>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
|
@ -209,6 +210,16 @@ void WaterRenderer::removeTile(int tileX, int tileY) {
|
|||
}
|
||||
}
|
||||
|
||||
void WaterRenderer::loadFromWMO([[maybe_unused]] const pipeline::WMOLiquid& liquid,
|
||||
[[maybe_unused]] const glm::mat4& modelMatrix,
|
||||
[[maybe_unused]] uint32_t wmoId) {
|
||||
// WMO liquid rendering not yet implemented
|
||||
}
|
||||
|
||||
void WaterRenderer::removeWMO([[maybe_unused]] uint32_t wmoId) {
|
||||
// WMO liquid rendering not yet implemented
|
||||
}
|
||||
|
||||
void WaterRenderer::clear() {
|
||||
for (auto& surface : surfaces) {
|
||||
destroyWaterMesh(surface);
|
||||
|
|
|
|||
|
|
@ -620,6 +620,9 @@ void WMORenderer::WMOInstance::updateModelMatrix() {
|
|||
modelMatrix = glm::rotate(modelMatrix, rotation.x, glm::vec3(1.0f, 0.0f, 0.0f));
|
||||
|
||||
modelMatrix = glm::scale(modelMatrix, glm::vec3(scale));
|
||||
|
||||
// Cache inverse for collision detection
|
||||
invModelMatrix = glm::inverse(modelMatrix);
|
||||
}
|
||||
|
||||
GLuint WMORenderer::loadTexture(const std::string& path) {
|
||||
|
|
@ -728,10 +731,9 @@ std::optional<float> WMORenderer::getFloorHeight(float glX, float glY, float glZ
|
|||
|
||||
const ModelData& model = it->second;
|
||||
|
||||
// Transform ray into model-local space
|
||||
glm::mat4 invModel = glm::inverse(instance.modelMatrix);
|
||||
glm::vec3 localOrigin = glm::vec3(invModel * glm::vec4(worldOrigin, 1.0f));
|
||||
glm::vec3 localDir = glm::normalize(glm::vec3(invModel * glm::vec4(worldDir, 0.0f)));
|
||||
// Use cached inverse matrix
|
||||
glm::vec3 localOrigin = glm::vec3(instance.invModelMatrix * glm::vec4(worldOrigin, 1.0f));
|
||||
glm::vec3 localDir = glm::normalize(glm::vec3(instance.invModelMatrix * glm::vec4(worldDir, 0.0f)));
|
||||
|
||||
for (const auto& group : model.groups) {
|
||||
// Quick bounding box check: does the ray intersect this group's AABB?
|
||||
|
|
@ -785,10 +787,9 @@ bool WMORenderer::checkWallCollision(const glm::vec3& from, const glm::vec3& to,
|
|||
if (it == loadedModels.end()) continue;
|
||||
|
||||
const ModelData& model = it->second;
|
||||
glm::mat4 invModel = glm::inverse(instance.modelMatrix);
|
||||
|
||||
// Transform positions into local space
|
||||
glm::vec3 localTo = glm::vec3(invModel * glm::vec4(to, 1.0f));
|
||||
// Transform positions into local space using cached inverse
|
||||
glm::vec3 localTo = glm::vec3(instance.invModelMatrix * glm::vec4(to, 1.0f));
|
||||
|
||||
for (const auto& group : model.groups) {
|
||||
// Quick bounding box check
|
||||
|
|
@ -865,8 +866,7 @@ bool WMORenderer::isInsideWMO(float glX, float glY, float glZ, uint32_t* outMode
|
|||
if (it == loadedModels.end()) continue;
|
||||
|
||||
const ModelData& model = it->second;
|
||||
glm::mat4 invModel = glm::inverse(instance.modelMatrix);
|
||||
glm::vec3 localPos = glm::vec3(invModel * glm::vec4(glX, glY, glZ, 1.0f));
|
||||
glm::vec3 localPos = glm::vec3(instance.invModelMatrix * glm::vec4(glX, glY, glZ, 1.0f));
|
||||
|
||||
// Check if inside any group's bounding box
|
||||
for (const auto& group : model.groups) {
|
||||
|
|
@ -890,10 +890,9 @@ float WMORenderer::raycastBoundingBoxes(const glm::vec3& origin, const glm::vec3
|
|||
|
||||
const ModelData& model = it->second;
|
||||
|
||||
// Transform ray into local space
|
||||
glm::mat4 invModel = glm::inverse(instance.modelMatrix);
|
||||
glm::vec3 localOrigin = glm::vec3(invModel * glm::vec4(origin, 1.0f));
|
||||
glm::vec3 localDir = glm::normalize(glm::vec3(invModel * glm::vec4(direction, 0.0f)));
|
||||
// Use cached inverse matrix
|
||||
glm::vec3 localOrigin = glm::vec3(instance.invModelMatrix * glm::vec4(origin, 1.0f));
|
||||
glm::vec3 localDir = glm::normalize(glm::vec3(instance.invModelMatrix * glm::vec4(direction, 0.0f)));
|
||||
|
||||
for (const auto& group : model.groups) {
|
||||
// Ray-AABB intersection (slab method)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue