mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-24 08:00:14 +00:00
Initial commit: wowee native WoW 3.3.5a client
This commit is contained in:
commit
ce6cb8f38e
147 changed files with 32347 additions and 0 deletions
497
src/rendering/water_renderer.cpp
Normal file
497
src/rendering/water_renderer.cpp
Normal file
|
|
@ -0,0 +1,497 @@
|
|||
#include "rendering/water_renderer.hpp"
|
||||
#include "rendering/shader.hpp"
|
||||
#include "rendering/camera.hpp"
|
||||
#include "pipeline/adt_loader.hpp"
|
||||
#include "core/logger.hpp"
|
||||
#include <GL/glew.h>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <cmath>
|
||||
|
||||
namespace wowee {
|
||||
namespace rendering {
|
||||
|
||||
WaterRenderer::WaterRenderer() = default;
|
||||
|
||||
WaterRenderer::~WaterRenderer() {
|
||||
shutdown();
|
||||
}
|
||||
|
||||
bool WaterRenderer::initialize() {
|
||||
LOG_INFO("Initializing water renderer");
|
||||
|
||||
// Create water shader
|
||||
waterShader = std::make_unique<Shader>();
|
||||
|
||||
// Vertex shader
|
||||
const char* vertexShaderSource = R"(
|
||||
#version 330 core
|
||||
layout (location = 0) in vec3 aPos;
|
||||
layout (location = 1) in vec3 aNormal;
|
||||
layout (location = 2) in vec2 aTexCoord;
|
||||
|
||||
uniform mat4 model;
|
||||
uniform mat4 view;
|
||||
uniform mat4 projection;
|
||||
uniform float time;
|
||||
|
||||
out vec3 FragPos;
|
||||
out vec3 Normal;
|
||||
out vec2 TexCoord;
|
||||
out float WaveOffset;
|
||||
|
||||
void main() {
|
||||
// Simple pass-through for debugging (no wave animation)
|
||||
vec3 pos = aPos;
|
||||
|
||||
FragPos = vec3(model * vec4(pos, 1.0));
|
||||
Normal = mat3(transpose(inverse(model))) * aNormal;
|
||||
TexCoord = aTexCoord;
|
||||
WaveOffset = 0.0;
|
||||
|
||||
gl_Position = projection * view * vec4(FragPos, 1.0);
|
||||
}
|
||||
)";
|
||||
|
||||
// Fragment shader
|
||||
const char* fragmentShaderSource = R"(
|
||||
#version 330 core
|
||||
in vec3 FragPos;
|
||||
in vec3 Normal;
|
||||
in vec2 TexCoord;
|
||||
in float WaveOffset;
|
||||
|
||||
uniform vec3 viewPos;
|
||||
uniform vec4 waterColor;
|
||||
uniform float waterAlpha;
|
||||
uniform float time;
|
||||
|
||||
out vec4 FragColor;
|
||||
|
||||
void main() {
|
||||
// Normalize interpolated normal
|
||||
vec3 norm = normalize(Normal);
|
||||
|
||||
// Simple directional light (sun)
|
||||
vec3 lightDir = normalize(vec3(0.5, 1.0, 0.3));
|
||||
float diff = max(dot(norm, lightDir), 0.0);
|
||||
|
||||
// Specular highlights (shininess for water)
|
||||
vec3 viewDir = normalize(viewPos - FragPos);
|
||||
vec3 reflectDir = reflect(-lightDir, norm);
|
||||
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 64.0);
|
||||
|
||||
// Animated texture coordinates for flowing effect
|
||||
vec2 uv1 = TexCoord + vec2(time * 0.02, time * 0.01);
|
||||
vec2 uv2 = TexCoord + vec2(-time * 0.01, time * 0.015);
|
||||
|
||||
// Combine lighting
|
||||
vec3 ambient = vec3(0.3) * waterColor.rgb;
|
||||
vec3 diffuse = vec3(0.6) * diff * waterColor.rgb;
|
||||
vec3 specular = vec3(1.0) * spec;
|
||||
|
||||
// Add wave offset to brightness
|
||||
float brightness = 1.0 + WaveOffset * 0.1;
|
||||
|
||||
vec3 result = (ambient + diffuse + specular) * brightness;
|
||||
|
||||
// Apply transparency
|
||||
FragColor = vec4(result, waterAlpha);
|
||||
}
|
||||
)";
|
||||
|
||||
if (!waterShader->loadFromSource(vertexShaderSource, fragmentShaderSource)) {
|
||||
LOG_ERROR("Failed to create water shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_INFO("Water renderer initialized");
|
||||
return true;
|
||||
}
|
||||
|
||||
void WaterRenderer::shutdown() {
|
||||
clear();
|
||||
waterShader.reset();
|
||||
}
|
||||
|
||||
void WaterRenderer::loadFromTerrain(const pipeline::ADTTerrain& terrain, bool append,
|
||||
int tileX, int tileY) {
|
||||
if (!append) {
|
||||
LOG_INFO("Loading water from terrain (replacing)");
|
||||
clear();
|
||||
} else {
|
||||
LOG_INFO("Loading water from terrain (appending)");
|
||||
}
|
||||
|
||||
// Load water surfaces from MH2O data
|
||||
int totalLayers = 0;
|
||||
|
||||
for (int chunkIdx = 0; chunkIdx < 256; chunkIdx++) {
|
||||
const auto& chunkWater = terrain.waterData[chunkIdx];
|
||||
|
||||
if (!chunkWater.hasWater()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the terrain chunk for position reference
|
||||
int chunkX = chunkIdx % 16;
|
||||
int chunkY = chunkIdx / 16;
|
||||
const auto& terrainChunk = terrain.getChunk(chunkX, chunkY);
|
||||
|
||||
// Process each water layer in this chunk
|
||||
for (const auto& layer : chunkWater.layers) {
|
||||
WaterSurface surface;
|
||||
|
||||
// Use the chunk base position - layer offsets will be applied in mesh generation
|
||||
// to match terrain's coordinate transformation
|
||||
surface.position = glm::vec3(
|
||||
terrainChunk.position[0],
|
||||
terrainChunk.position[1],
|
||||
layer.minHeight
|
||||
);
|
||||
|
||||
// Debug log first few water surfaces
|
||||
if (totalLayers < 5) {
|
||||
LOG_DEBUG("Water layer ", totalLayers, ": chunk=", chunkIdx,
|
||||
" liquidType=", layer.liquidType,
|
||||
" offset=(", (int)layer.x, ",", (int)layer.y, ")",
|
||||
" size=", (int)layer.width, "x", (int)layer.height,
|
||||
" height range=[", layer.minHeight, ",", layer.maxHeight, "]");
|
||||
}
|
||||
|
||||
surface.minHeight = layer.minHeight;
|
||||
surface.maxHeight = layer.maxHeight;
|
||||
surface.liquidType = layer.liquidType;
|
||||
|
||||
// Store dimensions
|
||||
surface.xOffset = layer.x;
|
||||
surface.yOffset = layer.y;
|
||||
surface.width = layer.width;
|
||||
surface.height = layer.height;
|
||||
|
||||
// Copy height data
|
||||
if (!layer.heights.empty()) {
|
||||
surface.heights = layer.heights;
|
||||
} else {
|
||||
// Flat water at minHeight if no height data
|
||||
size_t numVertices = (layer.width + 1) * (layer.height + 1);
|
||||
surface.heights.resize(numVertices, layer.minHeight);
|
||||
}
|
||||
|
||||
// Copy render mask
|
||||
surface.mask = layer.mask;
|
||||
|
||||
surface.tileX = tileX;
|
||||
surface.tileY = tileY;
|
||||
createWaterMesh(surface);
|
||||
surfaces.push_back(surface);
|
||||
totalLayers++;
|
||||
}
|
||||
}
|
||||
|
||||
LOG_INFO("Loaded ", totalLayers, " water layers from MH2O data");
|
||||
}
|
||||
|
||||
void WaterRenderer::removeTile(int tileX, int tileY) {
|
||||
int removed = 0;
|
||||
auto it = surfaces.begin();
|
||||
while (it != surfaces.end()) {
|
||||
if (it->tileX == tileX && it->tileY == tileY) {
|
||||
destroyWaterMesh(*it);
|
||||
it = surfaces.erase(it);
|
||||
removed++;
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
if (removed > 0) {
|
||||
LOG_DEBUG("Removed ", removed, " water surfaces for tile [", tileX, ",", tileY, "]");
|
||||
}
|
||||
}
|
||||
|
||||
void WaterRenderer::clear() {
|
||||
for (auto& surface : surfaces) {
|
||||
destroyWaterMesh(surface);
|
||||
}
|
||||
surfaces.clear();
|
||||
}
|
||||
|
||||
void WaterRenderer::render(const Camera& camera, float time) {
|
||||
if (!renderingEnabled || surfaces.empty() || !waterShader) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Enable alpha blending for transparent water
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
// Disable depth writing so terrain shows through water
|
||||
glDepthMask(GL_FALSE);
|
||||
|
||||
waterShader->use();
|
||||
|
||||
// Set uniforms
|
||||
glm::mat4 view = camera.getViewMatrix();
|
||||
glm::mat4 projection = camera.getProjectionMatrix();
|
||||
|
||||
waterShader->setUniform("view", view);
|
||||
waterShader->setUniform("projection", projection);
|
||||
waterShader->setUniform("viewPos", camera.getPosition());
|
||||
waterShader->setUniform("time", time);
|
||||
|
||||
// Render each water surface
|
||||
for (const auto& surface : surfaces) {
|
||||
if (surface.vao == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Model matrix (identity, position already in vertices)
|
||||
glm::mat4 model = glm::mat4(1.0f);
|
||||
waterShader->setUniform("model", model);
|
||||
|
||||
// Set liquid-specific color and alpha
|
||||
glm::vec4 color = getLiquidColor(surface.liquidType);
|
||||
float alpha = getLiquidAlpha(surface.liquidType);
|
||||
|
||||
waterShader->setUniform("waterColor", color);
|
||||
waterShader->setUniform("waterAlpha", alpha);
|
||||
|
||||
// Render
|
||||
glBindVertexArray(surface.vao);
|
||||
glDrawElements(GL_TRIANGLES, surface.indexCount, GL_UNSIGNED_INT, nullptr);
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
// Restore state
|
||||
glDepthMask(GL_TRUE);
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
|
||||
void WaterRenderer::createWaterMesh(WaterSurface& surface) {
|
||||
// Variable-size grid based on water layer dimensions
|
||||
const int gridWidth = surface.width + 1; // Vertices = tiles + 1
|
||||
const int gridHeight = surface.height + 1;
|
||||
const float TILE_SIZE = 33.33333f / 8.0f; // Size of one tile (same as terrain unitSize)
|
||||
|
||||
std::vector<float> vertices;
|
||||
std::vector<uint32_t> indices;
|
||||
|
||||
// Generate vertices
|
||||
// Match terrain coordinate transformation: pos[0] = baseX - (y * unitSize), pos[1] = baseY - (x * unitSize)
|
||||
for (int y = 0; y < gridHeight; y++) {
|
||||
for (int x = 0; x < gridWidth; x++) {
|
||||
int index = y * gridWidth + x;
|
||||
|
||||
// Use per-vertex height data if available, otherwise flat at minHeight
|
||||
float height;
|
||||
if (index < static_cast<int>(surface.heights.size())) {
|
||||
height = surface.heights[index];
|
||||
} else {
|
||||
height = surface.minHeight;
|
||||
}
|
||||
|
||||
// Position - match terrain coordinate transformation (swap and negate)
|
||||
// Terrain uses: X = baseX - (offsetY * unitSize), Y = baseY - (offsetX * unitSize)
|
||||
// Also apply layer offset within chunk (xOffset, yOffset)
|
||||
float posX = surface.position.x - ((surface.yOffset + y) * TILE_SIZE);
|
||||
float posY = surface.position.y - ((surface.xOffset + x) * TILE_SIZE);
|
||||
float posZ = height;
|
||||
|
||||
// Debug first surface's corner vertices
|
||||
static int debugCount = 0;
|
||||
if (debugCount < 4 && (x == 0 || x == gridWidth-1) && (y == 0 || y == gridHeight-1)) {
|
||||
LOG_DEBUG("Water vertex: (", posX, ", ", posY, ", ", posZ, ")");
|
||||
debugCount++;
|
||||
}
|
||||
|
||||
vertices.push_back(posX);
|
||||
vertices.push_back(posY);
|
||||
vertices.push_back(posZ);
|
||||
|
||||
// Normal (pointing up for water surface)
|
||||
vertices.push_back(0.0f);
|
||||
vertices.push_back(0.0f);
|
||||
vertices.push_back(1.0f);
|
||||
|
||||
// Texture coordinates
|
||||
vertices.push_back(static_cast<float>(x) / std::max(1, gridWidth - 1));
|
||||
vertices.push_back(static_cast<float>(y) / std::max(1, gridHeight - 1));
|
||||
}
|
||||
}
|
||||
|
||||
// Generate indices (triangles), respecting the render mask
|
||||
for (int y = 0; y < gridHeight - 1; y++) {
|
||||
for (int x = 0; x < gridWidth - 1; x++) {
|
||||
// Check render mask - each bit represents a tile
|
||||
bool renderTile = true;
|
||||
if (!surface.mask.empty()) {
|
||||
int tileIndex = y * surface.width + x;
|
||||
int byteIndex = tileIndex / 8;
|
||||
int bitIndex = tileIndex % 8;
|
||||
if (byteIndex < static_cast<int>(surface.mask.size())) {
|
||||
renderTile = (surface.mask[byteIndex] & (1 << bitIndex)) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!renderTile) {
|
||||
continue; // Skip this tile
|
||||
}
|
||||
|
||||
int topLeft = y * gridWidth + x;
|
||||
int topRight = topLeft + 1;
|
||||
int bottomLeft = (y + 1) * gridWidth + x;
|
||||
int bottomRight = bottomLeft + 1;
|
||||
|
||||
// First triangle
|
||||
indices.push_back(topLeft);
|
||||
indices.push_back(bottomLeft);
|
||||
indices.push_back(topRight);
|
||||
|
||||
// Second triangle
|
||||
indices.push_back(topRight);
|
||||
indices.push_back(bottomLeft);
|
||||
indices.push_back(bottomRight);
|
||||
}
|
||||
}
|
||||
|
||||
if (indices.empty()) {
|
||||
// No visible tiles
|
||||
return;
|
||||
}
|
||||
|
||||
surface.indexCount = static_cast<int>(indices.size());
|
||||
|
||||
// Create OpenGL buffers
|
||||
glGenVertexArrays(1, &surface.vao);
|
||||
glGenBuffers(1, &surface.vbo);
|
||||
glGenBuffers(1, &surface.ebo);
|
||||
|
||||
glBindVertexArray(surface.vao);
|
||||
|
||||
// Upload vertex data
|
||||
glBindBuffer(GL_ARRAY_BUFFER, surface.vbo);
|
||||
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float), vertices.data(), GL_STATIC_DRAW);
|
||||
|
||||
// Upload index data
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, surface.ebo);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(uint32_t), indices.data(), GL_STATIC_DRAW);
|
||||
|
||||
// Set vertex attributes
|
||||
// Position
|
||||
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
|
||||
glEnableVertexAttribArray(0);
|
||||
|
||||
// Normal
|
||||
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
|
||||
glEnableVertexAttribArray(1);
|
||||
|
||||
// Texture coordinates
|
||||
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
|
||||
glEnableVertexAttribArray(2);
|
||||
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
void WaterRenderer::destroyWaterMesh(WaterSurface& surface) {
|
||||
if (surface.vao != 0) {
|
||||
glDeleteVertexArrays(1, &surface.vao);
|
||||
surface.vao = 0;
|
||||
}
|
||||
if (surface.vbo != 0) {
|
||||
glDeleteBuffers(1, &surface.vbo);
|
||||
surface.vbo = 0;
|
||||
}
|
||||
if (surface.ebo != 0) {
|
||||
glDeleteBuffers(1, &surface.ebo);
|
||||
surface.ebo = 0;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<float> WaterRenderer::getWaterHeightAt(float glX, float glY) const {
|
||||
const float TILE_SIZE = 33.33333f / 8.0f;
|
||||
std::optional<float> best;
|
||||
|
||||
for (size_t si = 0; si < surfaces.size(); si++) {
|
||||
const auto& surface = surfaces[si];
|
||||
float gy = (surface.position.x - glX) / TILE_SIZE - static_cast<float>(surface.yOffset);
|
||||
float gx = (surface.position.y - glY) / TILE_SIZE - static_cast<float>(surface.xOffset);
|
||||
|
||||
if (gx < 0.0f || gx > static_cast<float>(surface.width) ||
|
||||
gy < 0.0f || gy > static_cast<float>(surface.height)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int gridWidth = surface.width + 1;
|
||||
|
||||
// Bilinear interpolation
|
||||
int ix = static_cast<int>(gx);
|
||||
int iy = static_cast<int>(gy);
|
||||
float fx = gx - ix;
|
||||
float fy = gy - iy;
|
||||
|
||||
// Clamp to valid vertex range
|
||||
if (ix >= surface.width) { ix = surface.width - 1; fx = 1.0f; }
|
||||
if (iy >= surface.height) { iy = surface.height - 1; fy = 1.0f; }
|
||||
|
||||
int idx00 = iy * gridWidth + ix;
|
||||
int idx10 = idx00 + 1;
|
||||
int idx01 = idx00 + gridWidth;
|
||||
int idx11 = idx01 + 1;
|
||||
|
||||
int total = static_cast<int>(surface.heights.size());
|
||||
if (idx11 >= total) continue;
|
||||
|
||||
float h00 = surface.heights[idx00];
|
||||
float h10 = surface.heights[idx10];
|
||||
float h01 = surface.heights[idx01];
|
||||
float h11 = surface.heights[idx11];
|
||||
|
||||
float h = h00 * (1-fx) * (1-fy) + h10 * fx * (1-fy) +
|
||||
h01 * (1-fx) * fy + h11 * fx * fy;
|
||||
|
||||
if (!best || h > *best) {
|
||||
best = h;
|
||||
}
|
||||
}
|
||||
|
||||
return best;
|
||||
}
|
||||
|
||||
glm::vec4 WaterRenderer::getLiquidColor(uint8_t liquidType) const {
|
||||
// WoW 3.3.5a LiquidType.dbc IDs:
|
||||
// 1,5,9,13,17 = Water variants (still, slow, fast)
|
||||
// 2,6,10,14 = Ocean
|
||||
// 3,7,11,15 = Magma
|
||||
// 4,8,12 = Slime
|
||||
// Map to basic type using (id - 1) % 4 for standard IDs, or handle ranges
|
||||
uint8_t basicType;
|
||||
if (liquidType == 0) {
|
||||
basicType = 0; // Water (fallback)
|
||||
} else {
|
||||
basicType = ((liquidType - 1) % 4);
|
||||
}
|
||||
|
||||
switch (basicType) {
|
||||
case 0: // Water
|
||||
return glm::vec4(0.2f, 0.4f, 0.6f, 1.0f);
|
||||
case 1: // Ocean
|
||||
return glm::vec4(0.1f, 0.3f, 0.5f, 1.0f);
|
||||
case 2: // Magma
|
||||
return glm::vec4(0.9f, 0.3f, 0.05f, 1.0f);
|
||||
case 3: // Slime
|
||||
return glm::vec4(0.2f, 0.6f, 0.1f, 1.0f);
|
||||
default:
|
||||
return glm::vec4(0.2f, 0.4f, 0.6f, 1.0f); // Water fallback
|
||||
}
|
||||
}
|
||||
|
||||
float WaterRenderer::getLiquidAlpha(uint8_t liquidType) const {
|
||||
uint8_t basicType = (liquidType == 0) ? 0 : ((liquidType - 1) % 4);
|
||||
switch (basicType) {
|
||||
case 2: return 0.85f; // Magma - mostly opaque
|
||||
case 3: return 0.75f; // Slime - semi-opaque
|
||||
default: return 0.55f; // Water/Ocean - semi-transparent
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace rendering
|
||||
} // namespace wowee
|
||||
Loading…
Add table
Add a link
Reference in a new issue