Initial commit: wowee native WoW 3.3.5a client

This commit is contained in:
Kelsi 2026-02-02 12:24:50 -08:00
commit ce6cb8f38e
147 changed files with 32347 additions and 0 deletions

View file

@ -0,0 +1,52 @@
#pragma once
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
namespace wowee {
namespace rendering {
struct Ray {
glm::vec3 origin;
glm::vec3 direction;
};
class Camera {
public:
Camera();
void setPosition(const glm::vec3& pos) { position = pos; updateViewMatrix(); }
void setRotation(float yaw, float pitch) { this->yaw = yaw; this->pitch = pitch; updateViewMatrix(); }
void setAspectRatio(float aspect) { aspectRatio = aspect; updateProjectionMatrix(); }
void setFov(float fov) { this->fov = fov; updateProjectionMatrix(); }
const glm::vec3& getPosition() const { return position; }
const glm::mat4& getViewMatrix() const { return viewMatrix; }
const glm::mat4& getProjectionMatrix() const { return projectionMatrix; }
glm::mat4 getViewProjectionMatrix() const { return projectionMatrix * viewMatrix; }
float getAspectRatio() const { return aspectRatio; }
glm::vec3 getForward() const;
glm::vec3 getRight() const;
glm::vec3 getUp() const;
Ray screenToWorldRay(float screenX, float screenY, float screenW, float screenH) const;
private:
void updateViewMatrix();
void updateProjectionMatrix();
glm::vec3 position = glm::vec3(0.0f);
float yaw = 0.0f;
float pitch = 0.0f;
float fov = 45.0f;
float aspectRatio = 16.0f / 9.0f;
float nearPlane = 0.1f;
float farPlane = 200000.0f; // Large draw distance for terrain visibility
glm::mat4 viewMatrix = glm::mat4(1.0f);
glm::mat4 projectionMatrix = glm::mat4(1.0f);
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,130 @@
#pragma once
#include "rendering/camera.hpp"
#include "core/input.hpp"
#include <SDL2/SDL.h>
#include <functional>
namespace wowee {
namespace rendering {
class TerrainManager;
class WMORenderer;
class WaterRenderer;
class CameraController {
public:
CameraController(Camera* camera);
void update(float deltaTime);
void processMouseMotion(const SDL_MouseMotionEvent& event);
void processMouseButton(const SDL_MouseButtonEvent& event);
void setMovementSpeed(float speed) { movementSpeed = speed; }
void setMouseSensitivity(float sensitivity) { mouseSensitivity = sensitivity; }
void setEnabled(bool enabled) { this->enabled = enabled; }
void setTerrainManager(TerrainManager* tm) { terrainManager = tm; }
void setWMORenderer(WMORenderer* wmo) { wmoRenderer = wmo; }
void setWaterRenderer(WaterRenderer* wr) { waterRenderer = wr; }
void processMouseWheel(float delta);
void setFollowTarget(glm::vec3* target);
void reset();
float getMovementSpeed() const { return movementSpeed; }
bool isMoving() const;
float getYaw() const { return yaw; }
bool isThirdPerson() const { return thirdPerson; }
bool isGrounded() const { return grounded; }
bool isJumping() const { return !grounded && verticalVelocity > 0.0f; }
bool isFalling() const { return !grounded && verticalVelocity <= 0.0f; }
bool isSprinting() const;
bool isRightMouseHeld() const { return rightMouseDown; }
bool isSitting() const { return sitting; }
bool isSwimming() const { return swimming; }
const glm::vec3* getFollowTarget() const { return followTarget; }
// Movement callback for sending opcodes to server
using MovementCallback = std::function<void(uint32_t opcode)>;
void setMovementCallback(MovementCallback cb) { movementCallback = std::move(cb); }
void setUseWoWSpeed(bool use) { useWoWSpeed = use; }
private:
Camera* camera;
TerrainManager* terrainManager = nullptr;
WMORenderer* wmoRenderer = nullptr;
WaterRenderer* waterRenderer = nullptr;
// Stored rotation (avoids lossy forward-vector round-trip)
float yaw = 180.0f;
float pitch = -30.0f;
// Movement settings
float movementSpeed = 50.0f;
float sprintMultiplier = 3.0f;
float slowMultiplier = 0.3f;
// Mouse settings
float mouseSensitivity = 0.2f;
bool mouseButtonDown = false;
bool leftMouseDown = false;
bool rightMouseDown = false;
// Third-person orbit camera
bool thirdPerson = false;
float orbitDistance = 15.0f;
float minOrbitDistance = 3.0f;
float maxOrbitDistance = 50.0f;
float zoomSpeed = 2.0f;
glm::vec3* followTarget = nullptr;
// Gravity / grounding
float verticalVelocity = 0.0f;
bool grounded = false;
float eyeHeight = 5.0f;
float lastGroundZ = 0.0f; // Last known ground height (fallback when no terrain)
static constexpr float GRAVITY = -30.0f;
static constexpr float JUMP_VELOCITY = 15.0f;
// Swimming
bool swimming = false;
bool wasSwimming = false;
static constexpr float SWIM_SPEED_FACTOR = 0.67f;
static constexpr float SWIM_GRAVITY = -5.0f;
static constexpr float SWIM_BUOYANCY = 8.0f;
static constexpr float SWIM_SINK_SPEED = -3.0f;
static constexpr float WATER_SURFACE_OFFSET = 1.5f;
// State
bool enabled = true;
bool sitting = false;
bool xKeyWasDown = false;
// Movement state tracking (for sending opcodes on state change)
bool wasMovingForward = false;
bool wasMovingBackward = false;
bool wasStrafingLeft = false;
bool wasStrafingRight = false;
bool wasJumping = false;
bool wasFalling = false;
// Movement callback
MovementCallback movementCallback;
// WoW-correct speeds
bool useWoWSpeed = false;
static constexpr float WOW_RUN_SPEED = 7.0f;
static constexpr float WOW_WALK_SPEED = 2.5f;
static constexpr float WOW_BACK_SPEED = 4.5f;
static constexpr float WOW_GRAVITY = -19.29f;
static constexpr float WOW_JUMP_VELOCITY = 7.96f;
// Default spawn position (in front of Stormwind gate)
glm::vec3 defaultPosition = glm::vec3(-8900.0f, -170.0f, 150.0f);
float defaultYaw = 0.0f; // Look north toward Stormwind gate
float defaultPitch = -5.0f;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,101 @@
#pragma once
#include <memory>
#include <glm/glm.hpp>
namespace wowee {
namespace rendering {
class Shader;
class Camera;
/**
* Celestial body renderer
*
* Renders sun and moon that move across the sky based on time of day.
* Sun rises at dawn, sets at dusk. Moon is visible at night.
*/
class Celestial {
public:
Celestial();
~Celestial();
bool initialize();
void shutdown();
/**
* Render celestial bodies (sun and moon)
* @param camera Camera for view matrix
* @param timeOfDay Time of day in hours (0-24)
*/
void render(const Camera& camera, float timeOfDay);
/**
* Enable/disable celestial rendering
*/
void setEnabled(bool enabled) { renderingEnabled = enabled; }
bool isEnabled() const { return renderingEnabled; }
/**
* Update celestial bodies (for moon phase cycling)
*/
void update(float deltaTime);
/**
* Set moon phase (0.0 = new moon, 0.25 = first quarter, 0.5 = full, 0.75 = last quarter, 1.0 = new)
*/
void setMoonPhase(float phase);
float getMoonPhase() const { return moonPhase; }
/**
* Enable/disable automatic moon phase cycling
*/
void setMoonPhaseCycling(bool enabled) { moonPhaseCycling = enabled; }
bool isMoonPhaseCycling() const { return moonPhaseCycling; }
/**
* Get sun position in world space
*/
glm::vec3 getSunPosition(float timeOfDay) const;
/**
* Get moon position in world space
*/
glm::vec3 getMoonPosition(float timeOfDay) const;
/**
* Get sun color (changes with time of day)
*/
glm::vec3 getSunColor(float timeOfDay) const;
/**
* Get sun intensity (0-1, fades at dawn/dusk)
*/
float getSunIntensity(float timeOfDay) const;
private:
void createCelestialQuad();
void destroyCelestialQuad();
void renderSun(const Camera& camera, float timeOfDay);
void renderMoon(const Camera& camera, float timeOfDay);
float calculateCelestialAngle(float timeOfDay, float riseTime, float setTime) const;
std::unique_ptr<Shader> celestialShader;
uint32_t vao = 0;
uint32_t vbo = 0;
uint32_t ebo = 0;
bool renderingEnabled = true;
// Moon phase system
float moonPhase = 0.5f; // 0.0-1.0 (0=new, 0.5=full)
bool moonPhaseCycling = true;
float moonPhaseTimer = 0.0f;
static constexpr float MOON_CYCLE_DURATION = 240.0f; // 4 minutes for full cycle
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,180 @@
#pragma once
#include "pipeline/m2_loader.hpp"
#include <GL/glew.h>
#include <glm/glm.hpp>
#include <memory>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <string>
namespace wowee {
namespace pipeline { class AssetManager; }
namespace rendering {
// Forward declarations
class Shader;
class Texture;
class Camera;
// Weapon attached to a character instance at a bone attachment point
struct WeaponAttachment {
uint32_t weaponModelId;
uint32_t weaponInstanceId;
uint32_t attachmentId; // 1=RightHand, 2=LeftHand
uint16_t boneIndex;
glm::vec3 offset;
};
/**
* Character renderer for M2 models with skeletal animation
*
* Features:
* - Skeletal animation with bone transformations
* - Keyframe interpolation (linear position/scale, slerp rotation)
* - Vertex skinning (GPU-accelerated)
* - Texture loading from BLP via AssetManager
*/
class CharacterRenderer {
public:
CharacterRenderer();
~CharacterRenderer();
bool initialize();
void shutdown();
void setAssetManager(pipeline::AssetManager* am) { assetManager = am; }
bool loadModel(const pipeline::M2Model& model, uint32_t id);
uint32_t createInstance(uint32_t modelId, const glm::vec3& position,
const glm::vec3& rotation = glm::vec3(0.0f),
float scale = 1.0f);
void playAnimation(uint32_t instanceId, uint32_t animationId, bool loop = true);
void update(float deltaTime);
void render(const Camera& camera, const glm::mat4& view, const glm::mat4& projection);
void setInstancePosition(uint32_t instanceId, const glm::vec3& position);
void setInstanceRotation(uint32_t instanceId, const glm::vec3& rotation);
void setActiveGeosets(uint32_t instanceId, const std::unordered_set<uint16_t>& geosets);
void removeInstance(uint32_t instanceId);
/** Attach a weapon model to a character instance at the given attachment point. */
bool attachWeapon(uint32_t charInstanceId, uint32_t attachmentId,
const pipeline::M2Model& weaponModel, uint32_t weaponModelId,
const std::string& texturePath);
/** Detach a weapon from the given attachment point. */
void detachWeapon(uint32_t charInstanceId, uint32_t attachmentId);
size_t getInstanceCount() const { return instances.size(); }
private:
// GPU representation of M2 model
struct M2ModelGPU {
uint32_t vao = 0;
uint32_t vbo = 0;
uint32_t ebo = 0;
pipeline::M2Model data; // Original model data
std::vector<glm::mat4> bindPose; // Inverse bind pose matrices
// Textures loaded from BLP (indexed by texture array position)
std::vector<GLuint> textureIds;
};
// Character instance
struct CharacterInstance {
uint32_t id;
uint32_t modelId;
glm::vec3 position;
glm::vec3 rotation;
float scale;
// Animation state
uint32_t currentAnimationId = 0;
int currentSequenceIndex = -1; // Index into M2Model::sequences
float animationTime = 0.0f;
bool animationLoop = true;
std::vector<glm::mat4> boneMatrices; // Current bone transforms
// Geoset visibility — which submesh IDs to render
// Empty = render all (for non-character models)
std::unordered_set<uint16_t> activeGeosets;
// Weapon attachments (weapons parented to this instance's bones)
std::vector<WeaponAttachment> weaponAttachments;
// Override model matrix (used for weapon instances positioned by parent bone)
bool hasOverrideModelMatrix = false;
glm::mat4 overrideModelMatrix{1.0f};
};
void setupModelBuffers(M2ModelGPU& gpuModel);
void calculateBindPose(M2ModelGPU& gpuModel);
void updateAnimation(CharacterInstance& instance, float deltaTime);
void calculateBoneMatrices(CharacterInstance& instance);
glm::mat4 getBoneTransform(const pipeline::M2Bone& bone, float time, int sequenceIndex);
glm::mat4 getModelMatrix(const CharacterInstance& instance) const;
// Keyframe interpolation helpers
static int findKeyframeIndex(const std::vector<uint32_t>& timestamps, float time);
static glm::vec3 interpolateVec3(const pipeline::M2AnimationTrack& track,
int seqIdx, float time, const glm::vec3& defaultVal);
static glm::quat interpolateQuat(const pipeline::M2AnimationTrack& track,
int seqIdx, float time);
public:
/**
* Build a composited character skin texture by alpha-blending overlay
* layers (e.g. underwear) onto a base skin BLP. Each overlay is placed
* at the correct CharComponentTextureSections region based on its
* filename (pelvis, torso, etc.). Returns the resulting GL texture ID.
*/
GLuint compositeTextures(const std::vector<std::string>& layerPaths);
/**
* Build a composited character skin with explicit region-based equipment overlays.
* @param basePath Body skin texture path
* @param baseLayers Underwear overlay paths (placed by filename keyword)
* @param regionLayers Pairs of (region_index, blp_path) for equipment textures
* @return GL texture ID of the composited result
*/
GLuint compositeWithRegions(const std::string& basePath,
const std::vector<std::string>& baseLayers,
const std::vector<std::pair<int, std::string>>& regionLayers);
/** Load a BLP texture from MPQ and return the GL texture ID (cached). */
GLuint loadTexture(const std::string& path);
/** Replace a loaded model's texture at the given slot with a new GL texture. */
void setModelTexture(uint32_t modelId, uint32_t textureSlot, GLuint textureId);
/** Reset a model's texture slot back to white fallback. */
void resetModelTexture(uint32_t modelId, uint32_t textureSlot);
private:
std::unique_ptr<Shader> characterShader;
pipeline::AssetManager* assetManager = nullptr;
// Texture cache
std::unordered_map<std::string, GLuint> textureCache;
GLuint whiteTexture = 0;
std::unordered_map<uint32_t, M2ModelGPU> models;
std::unordered_map<uint32_t, CharacterInstance> instances;
uint32_t nextInstanceId = 1;
// Maximum bones supported (GPU uniform limit)
static constexpr int MAX_BONES = 200;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,95 @@
#pragma once
#include <GL/glew.h>
#include <glm/glm.hpp>
#include <memory>
#include <vector>
namespace wowee {
namespace rendering {
class Camera;
class Shader;
/**
* @brief Renders procedural animated clouds on a sky dome
*
* Features:
* - Procedural cloud generation using multiple noise layers
* - Two cloud layers at different altitudes
* - Animated wind movement
* - Time-of-day color tinting (orange at sunrise/sunset)
* - Transparency and soft edges
*/
class Clouds {
public:
Clouds();
~Clouds();
/**
* @brief Initialize cloud system (generate mesh and shaders)
* @return true if initialization succeeded
*/
bool initialize();
/**
* @brief Render clouds
* @param camera The camera to render from
* @param timeOfDay Current time (0-24 hours)
*/
void render(const Camera& camera, float timeOfDay);
/**
* @brief Update cloud animation
* @param deltaTime Time since last frame
*/
void update(float deltaTime);
/**
* @brief Enable or disable cloud rendering
*/
void setEnabled(bool enabled) { this->enabled = enabled; }
bool isEnabled() const { return enabled; }
/**
* @brief Set cloud density (0.0 = clear, 1.0 = overcast)
*/
void setDensity(float density);
float getDensity() const { return density; }
/**
* @brief Set wind speed multiplier
*/
void setWindSpeed(float speed) { windSpeed = speed; }
float getWindSpeed() const { return windSpeed; }
private:
void generateMesh();
void cleanup();
glm::vec3 getCloudColor(float timeOfDay) const;
// OpenGL objects
GLuint vao = 0;
GLuint vbo = 0;
GLuint ebo = 0;
std::unique_ptr<Shader> shader;
// Mesh data
std::vector<glm::vec3> vertices;
std::vector<unsigned int> indices;
int triangleCount = 0;
// Cloud parameters
bool enabled = true;
float density = 0.5f; // Cloud coverage
float windSpeed = 1.0f;
float windOffset = 0.0f; // Accumulated wind movement
// Mesh generation parameters
static constexpr int SEGMENTS = 32; // Horizontal segments
static constexpr int RINGS = 8; // Vertical rings (only upper hemisphere)
static constexpr float RADIUS = 900.0f; // Slightly smaller than skybox
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,88 @@
#pragma once
#include <glm/glm.hpp>
#include <array>
namespace wowee {
namespace rendering {
/**
* Frustum plane
*/
struct Plane {
glm::vec3 normal;
float distance;
Plane() : normal(0.0f), distance(0.0f) {}
Plane(const glm::vec3& n, float d) : normal(n), distance(d) {}
/**
* Calculate signed distance from point to plane
* Positive = in front, Negative = behind
*/
float distanceToPoint(const glm::vec3& point) const {
return glm::dot(normal, point) + distance;
}
};
/**
* View frustum for culling
*
* Six planes: left, right, bottom, top, near, far
*/
class Frustum {
public:
enum Side {
LEFT = 0,
RIGHT,
BOTTOM,
TOP,
NEAR,
FAR
};
Frustum() = default;
/**
* Extract frustum planes from view-projection matrix
* @param viewProjection Combined view * projection matrix
*/
void extractFromMatrix(const glm::mat4& viewProjection);
/**
* Test if point is inside frustum
*/
bool containsPoint(const glm::vec3& point) const;
/**
* Test if sphere is inside or intersecting frustum
* @param center Sphere center
* @param radius Sphere radius
* @return true if sphere is visible (fully or partially inside)
*/
bool intersectsSphere(const glm::vec3& center, float radius) const;
/**
* Test if axis-aligned bounding box intersects frustum
* @param min Box minimum corner
* @param max Box maximum corner
* @return true if box is visible (fully or partially inside)
*/
bool intersectsAABB(const glm::vec3& min, const glm::vec3& max) const;
/**
* Get frustum plane
*/
const Plane& getPlane(Side side) const { return planes[side]; }
private:
std::array<Plane, 6> planes;
/**
* Normalize plane (ensure unit length normal)
*/
void normalizePlane(Plane& plane);
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,85 @@
#pragma once
#include <GL/glew.h>
#include <glm/glm.hpp>
#include <memory>
#include <vector>
namespace wowee {
namespace rendering {
class Camera;
class Shader;
/**
* @brief Renders lens flare effect when looking at the sun
*
* Features:
* - Multiple flare elements (ghosts) along sun-to-center axis
* - Sun glow at sun position
* - Colored flare elements (chromatic aberration simulation)
* - Intensity based on sun visibility and angle
* - Additive blending for realistic light artifacts
*/
class LensFlare {
public:
LensFlare();
~LensFlare();
/**
* @brief Initialize lens flare system
* @return true if initialization succeeded
*/
bool initialize();
/**
* @brief Render lens flare effect
* @param camera The camera to render from
* @param sunPosition World-space sun position
* @param timeOfDay Current time (0-24 hours)
*/
void render(const Camera& camera, const glm::vec3& sunPosition, float timeOfDay);
/**
* @brief Enable or disable lens flare rendering
*/
void setEnabled(bool enabled) { this->enabled = enabled; }
bool isEnabled() const { return enabled; }
/**
* @brief Set flare intensity multiplier
*/
void setIntensity(float intensity);
float getIntensity() const { return intensityMultiplier; }
private:
struct FlareElement {
float position; // Position along sun-center axis (-1 to 1, 0 = center)
float size; // Size in screen space
glm::vec3 color; // RGB color
float brightness; // Brightness multiplier
};
void generateFlareElements();
void cleanup();
float calculateSunVisibility(const Camera& camera, const glm::vec3& sunPosition) const;
glm::vec2 worldToScreen(const Camera& camera, const glm::vec3& worldPos) const;
// OpenGL objects
GLuint vao = 0;
GLuint vbo = 0;
std::unique_ptr<Shader> shader;
// Flare elements
std::vector<FlareElement> flareElements;
// Parameters
bool enabled = true;
float intensityMultiplier = 1.0f;
// Quad vertices for rendering flare sprites
static constexpr int VERTICES_PER_QUAD = 6;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,105 @@
#pragma once
#include <glm/glm.hpp>
#include <memory>
#include <vector>
namespace wowee {
namespace rendering {
// Forward declarations
class Shader;
class Camera;
/**
* Lightning system for thunder storm effects
*
* Features:
* - Random lightning strikes during rain
* - Screen flash effect
* - Procedural lightning bolts with branches
* - Thunder timing (light then sound delay)
* - Intensity scaling with weather
*/
class Lightning {
public:
Lightning();
~Lightning();
bool initialize();
void shutdown();
void update(float deltaTime, const Camera& camera);
void render(const Camera& camera, const glm::mat4& view, const glm::mat4& projection);
// Control
void setEnabled(bool enabled);
bool isEnabled() const { return enabled; }
void setIntensity(float intensity); // 0.0 - 1.0 (affects frequency)
float getIntensity() const { return intensity; }
// Trigger manual strike (for testing or scripted events)
void triggerStrike(const glm::vec3& position);
private:
struct LightningBolt {
glm::vec3 startPos;
glm::vec3 endPos;
float lifetime;
float maxLifetime;
std::vector<glm::vec3> segments; // Bolt path
std::vector<glm::vec3> branches; // Branch points
float brightness;
bool active;
};
struct Flash {
float intensity; // 0.0 - 1.0
float lifetime;
float maxLifetime;
bool active;
};
void generateLightningBolt(LightningBolt& bolt);
void generateBoltSegments(const glm::vec3& start, const glm::vec3& end,
std::vector<glm::vec3>& segments, int depth = 0);
void updateBolts(float deltaTime);
void updateFlash(float deltaTime);
void spawnRandomStrike(const glm::vec3& cameraPos);
void renderBolts(const glm::mat4& viewProj);
void renderFlash();
bool enabled = true;
float intensity = 0.5f; // Strike frequency multiplier
// Timing
float strikeTimer = 0.0f;
float nextStrikeTime = 0.0f;
// Active effects
std::vector<LightningBolt> bolts;
Flash flash;
// Rendering
std::unique_ptr<Shader> boltShader;
std::unique_ptr<Shader> flashShader;
unsigned int boltVAO = 0;
unsigned int boltVBO = 0;
unsigned int flashVAO = 0;
unsigned int flashVBO = 0;
// Configuration
static constexpr int MAX_BOLTS = 3;
static constexpr float MIN_STRIKE_INTERVAL = 2.0f;
static constexpr float MAX_STRIKE_INTERVAL = 8.0f;
static constexpr float BOLT_LIFETIME = 0.15f; // Quick flash
static constexpr float FLASH_LIFETIME = 0.3f;
static constexpr float STRIKE_DISTANCE = 200.0f; // From camera
static constexpr int MAX_SEGMENTS = 64;
static constexpr float BRANCH_PROBABILITY = 0.3f;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,145 @@
#pragma once
#include "pipeline/m2_loader.hpp"
#include <GL/glew.h>
#include <glm/glm.hpp>
#include <memory>
#include <unordered_map>
#include <vector>
#include <string>
namespace wowee {
namespace pipeline {
class AssetManager;
}
namespace rendering {
class Shader;
class Camera;
/**
* GPU representation of an M2 model
*/
struct M2ModelGPU {
struct BatchGPU {
GLuint texture = 0;
uint32_t indexStart = 0; // offset in indices (not bytes)
uint32_t indexCount = 0;
bool hasAlpha = false;
};
GLuint vao = 0;
GLuint vbo = 0;
GLuint ebo = 0;
uint32_t indexCount = 0;
uint32_t vertexCount = 0;
std::vector<BatchGPU> batches;
glm::vec3 boundMin;
glm::vec3 boundMax;
float boundRadius = 0.0f;
std::string name;
bool isValid() const { return vao != 0 && indexCount > 0; }
};
/**
* Instance of an M2 model in the world
*/
struct M2Instance {
uint32_t id = 0; // Unique instance ID
uint32_t modelId;
glm::vec3 position;
glm::vec3 rotation; // Euler angles in degrees
float scale;
glm::mat4 modelMatrix;
void updateModelMatrix();
};
/**
* M2 Model Renderer
*
* Handles rendering of M2 models (doodads like trees, rocks, bushes)
*/
class M2Renderer {
public:
M2Renderer();
~M2Renderer();
bool initialize(pipeline::AssetManager* assets);
void shutdown();
/**
* Load an M2 model to GPU
* @param model Parsed M2 model data
* @param modelId Unique ID for this model
* @return True if successful
*/
bool loadModel(const pipeline::M2Model& model, uint32_t modelId);
/**
* Create an instance of a loaded model
* @param modelId ID of the loaded model
* @param position World position
* @param rotation Rotation in degrees (x, y, z)
* @param scale Scale factor (1.0 = normal)
* @return Instance ID
*/
uint32_t createInstance(uint32_t modelId, const glm::vec3& position,
const glm::vec3& rotation = glm::vec3(0.0f),
float scale = 1.0f);
/**
* Create an instance with a pre-computed model matrix
* Used for WMO doodads where the full transform is computed externally
*/
uint32_t createInstanceWithMatrix(uint32_t modelId, const glm::mat4& modelMatrix,
const glm::vec3& position);
/**
* Render all visible instances
*/
void render(const Camera& camera, const glm::mat4& view, const glm::mat4& projection);
/**
* Remove a specific instance by ID
* @param instanceId Instance ID returned by createInstance()
*/
void removeInstance(uint32_t instanceId);
/**
* Clear all models and instances
*/
void clear();
// Stats
uint32_t getModelCount() const { return static_cast<uint32_t>(models.size()); }
uint32_t getInstanceCount() const { return static_cast<uint32_t>(instances.size()); }
uint32_t getTotalTriangleCount() const;
uint32_t getDrawCallCount() const { return lastDrawCallCount; }
private:
pipeline::AssetManager* assetManager = nullptr;
std::unique_ptr<Shader> shader;
std::unordered_map<uint32_t, M2ModelGPU> models;
std::vector<M2Instance> instances;
uint32_t nextInstanceId = 1;
uint32_t lastDrawCallCount = 0;
GLuint loadTexture(const std::string& path);
std::unordered_map<std::string, GLuint> textureCache;
GLuint whiteTexture = 0;
// Lighting uniforms
glm::vec3 lightDir = glm::vec3(0.5f, 0.5f, 1.0f);
glm::vec3 ambientColor = glm::vec3(0.4f, 0.4f, 0.45f);
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,32 @@
#pragma once
#include <memory>
#include <glm/glm.hpp>
namespace wowee {
namespace rendering {
class Shader;
class Texture;
class Material {
public:
Material() = default;
~Material() = default;
void setShader(std::shared_ptr<Shader> shader) { this->shader = shader; }
void setTexture(std::shared_ptr<Texture> texture) { this->texture = texture; }
void setColor(const glm::vec4& color) { this->color = color; }
std::shared_ptr<Shader> getShader() const { return shader; }
std::shared_ptr<Texture> getTexture() const { return texture; }
const glm::vec4& getColor() const { return color; }
private:
std::shared_ptr<Shader> shader;
std::shared_ptr<Texture> texture;
glm::vec4 color = glm::vec4(1.0f);
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,33 @@
#pragma once
#include <vector>
#include <GL/glew.h>
#include <glm/glm.hpp>
namespace wowee {
namespace rendering {
struct Vertex {
glm::vec3 position;
glm::vec3 normal;
glm::vec2 texCoord;
};
class Mesh {
public:
Mesh() = default;
~Mesh();
void create(const std::vector<Vertex>& vertices, const std::vector<uint32_t>& indices);
void destroy();
void draw() const;
private:
GLuint VAO = 0;
GLuint VBO = 0;
GLuint EBO = 0;
size_t indexCount = 0;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,54 @@
#pragma once
#include <GL/glew.h>
#include <glm/glm.hpp>
#include <memory>
namespace wowee {
namespace rendering {
class Shader;
class Camera;
class TerrainRenderer;
class Minimap {
public:
Minimap();
~Minimap();
bool initialize(int size = 200);
void shutdown();
void setTerrainRenderer(TerrainRenderer* tr) { terrainRenderer = tr; }
void render(const Camera& playerCamera, int screenWidth, int screenHeight);
void setEnabled(bool enabled) { this->enabled = enabled; }
bool isEnabled() const { return enabled; }
void toggle() { enabled = !enabled; }
void setViewRadius(float radius) { viewRadius = radius; }
private:
void renderTerrainToFBO(const Camera& playerCamera);
void renderQuad(int screenWidth, int screenHeight);
TerrainRenderer* terrainRenderer = nullptr;
// FBO for offscreen rendering
GLuint fbo = 0;
GLuint fboTexture = 0;
GLuint fboDepth = 0;
// Screen quad
GLuint quadVAO = 0;
GLuint quadVBO = 0;
std::unique_ptr<Shader> quadShader;
int mapSize = 200;
float viewRadius = 500.0f;
bool enabled = false;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,100 @@
#pragma once
#include <string>
#include <deque>
namespace wowee {
namespace rendering {
class Renderer;
class Camera;
}
namespace rendering {
/**
* Performance HUD for displaying real-time statistics
*
* Shows FPS, frame time, rendering stats, and terrain info
*/
class PerformanceHUD {
public:
PerformanceHUD();
~PerformanceHUD();
/**
* Update HUD with latest frame time
* @param deltaTime Time since last frame in seconds
*/
void update(float deltaTime);
/**
* Render HUD using ImGui
* @param renderer Renderer for accessing stats
* @param camera Camera for position info
*/
void render(const Renderer* renderer, const Camera* camera);
/**
* Enable/disable HUD display
*/
void setEnabled(bool enabled) { this->enabled = enabled; }
bool isEnabled() const { return enabled; }
/**
* Toggle HUD visibility
*/
void toggle() { enabled = !enabled; }
/**
* Set HUD position
*/
enum class Position {
TOP_LEFT,
TOP_RIGHT,
BOTTOM_LEFT,
BOTTOM_RIGHT
};
void setPosition(Position pos) { position = pos; }
/**
* Enable/disable specific sections
*/
void setShowFPS(bool show) { showFPS = show; }
void setShowRenderer(bool show) { showRenderer = show; }
void setShowTerrain(bool show) { showTerrain = show; }
void setShowCamera(bool show) { showCamera = show; }
void setShowControls(bool show) { showControls = show; }
private:
/**
* Calculate average FPS from frame time history
*/
void calculateFPS();
bool enabled = true; // Enabled by default, press F1 to toggle
Position position = Position::TOP_LEFT;
// Section visibility
bool showFPS = true;
bool showRenderer = true;
bool showTerrain = true;
bool showCamera = true;
bool showControls = true;
// FPS tracking
std::deque<float> frameTimeHistory;
static constexpr size_t MAX_FRAME_HISTORY = 120; // 2 seconds at 60 FPS
float currentFPS = 0.0f;
float averageFPS = 0.0f;
float minFPS = 0.0f;
float maxFPS = 0.0f;
float frameTime = 0.0f;
// Update timing
float updateTimer = 0.0f;
static constexpr float UPDATE_INTERVAL = 0.1f; // Update stats every 0.1s
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,174 @@
#pragma once
#include <memory>
#include <string>
#include <cstdint>
#include <glm/glm.hpp>
namespace wowee {
namespace core { class Window; }
namespace game { class World; class ZoneManager; }
namespace audio { class MusicManager; }
namespace pipeline { class AssetManager; }
namespace rendering {
class Camera;
class CameraController;
class Scene;
class TerrainRenderer;
class TerrainManager;
class PerformanceHUD;
class WaterRenderer;
class Skybox;
class Celestial;
class StarField;
class Clouds;
class LensFlare;
class Weather;
class SwimEffects;
class CharacterRenderer;
class WMORenderer;
class M2Renderer;
class Minimap;
class Renderer {
public:
Renderer();
~Renderer();
bool initialize(core::Window* window);
void shutdown();
void beginFrame();
void endFrame();
void renderWorld(game::World* world);
/**
* Update renderer (camera, etc.)
*/
void update(float deltaTime);
/**
* Load test terrain for debugging
* @param assetManager Asset manager to load terrain data
* @param adtPath Path to ADT file (e.g., "World\\Maps\\Azeroth\\Azeroth_32_49.adt")
*/
bool loadTestTerrain(pipeline::AssetManager* assetManager, const std::string& adtPath);
/**
* Enable/disable terrain rendering
*/
void setTerrainEnabled(bool enabled) { terrainEnabled = enabled; }
/**
* Enable/disable wireframe mode
*/
void setWireframeMode(bool enabled);
/**
* Load terrain tiles around position
* @param mapName Map name (e.g., "Azeroth", "Kalimdor")
* @param centerX Center tile X coordinate
* @param centerY Center tile Y coordinate
* @param radius Load radius in tiles
*/
bool loadTerrainArea(const std::string& mapName, int centerX, int centerY, int radius = 1);
/**
* Enable/disable terrain streaming
*/
void setTerrainStreaming(bool enabled);
/**
* Render performance HUD
*/
void renderHUD();
Camera* getCamera() { return camera.get(); }
CameraController* getCameraController() { return cameraController.get(); }
Scene* getScene() { return scene.get(); }
TerrainRenderer* getTerrainRenderer() const { return terrainRenderer.get(); }
TerrainManager* getTerrainManager() const { return terrainManager.get(); }
PerformanceHUD* getPerformanceHUD() { return performanceHUD.get(); }
WaterRenderer* getWaterRenderer() const { return waterRenderer.get(); }
Skybox* getSkybox() const { return skybox.get(); }
Celestial* getCelestial() const { return celestial.get(); }
StarField* getStarField() const { return starField.get(); }
Clouds* getClouds() const { return clouds.get(); }
LensFlare* getLensFlare() const { return lensFlare.get(); }
Weather* getWeather() const { return weather.get(); }
CharacterRenderer* getCharacterRenderer() const { return characterRenderer.get(); }
WMORenderer* getWMORenderer() const { return wmoRenderer.get(); }
M2Renderer* getM2Renderer() const { return m2Renderer.get(); }
Minimap* getMinimap() const { return minimap.get(); }
const std::string& getCurrentZoneName() const { return currentZoneName; }
// Third-person character follow
void setCharacterFollow(uint32_t instanceId);
glm::vec3& getCharacterPosition() { return characterPosition; }
uint32_t getCharacterInstanceId() const { return characterInstanceId; }
float getCharacterYaw() const { return characterYaw; }
// Emote support
void playEmote(const std::string& emoteName);
void cancelEmote();
bool isEmoteActive() const { return emoteActive; }
static std::string getEmoteText(const std::string& emoteName);
// Targeting support
void setTargetPosition(const glm::vec3* pos);
bool isMoving() const;
private:
core::Window* window = nullptr;
std::unique_ptr<Camera> camera;
std::unique_ptr<CameraController> cameraController;
std::unique_ptr<Scene> scene;
std::unique_ptr<TerrainRenderer> terrainRenderer;
std::unique_ptr<TerrainManager> terrainManager;
std::unique_ptr<PerformanceHUD> performanceHUD;
std::unique_ptr<WaterRenderer> waterRenderer;
std::unique_ptr<Skybox> skybox;
std::unique_ptr<Celestial> celestial;
std::unique_ptr<StarField> starField;
std::unique_ptr<Clouds> clouds;
std::unique_ptr<LensFlare> lensFlare;
std::unique_ptr<Weather> weather;
std::unique_ptr<SwimEffects> swimEffects;
std::unique_ptr<CharacterRenderer> characterRenderer;
std::unique_ptr<WMORenderer> wmoRenderer;
std::unique_ptr<M2Renderer> m2Renderer;
std::unique_ptr<Minimap> minimap;
std::unique_ptr<audio::MusicManager> musicManager;
std::unique_ptr<game::ZoneManager> zoneManager;
pipeline::AssetManager* cachedAssetManager = nullptr;
uint32_t currentZoneId = 0;
std::string currentZoneName;
// Third-person character state
glm::vec3 characterPosition = glm::vec3(0.0f);
uint32_t characterInstanceId = 0;
float characterYaw = 0.0f;
// Character animation state
enum class CharAnimState { IDLE, WALK, RUN, JUMP_START, JUMP_MID, JUMP_END, SIT_DOWN, SITTING, EMOTE, SWIM_IDLE, SWIM };
CharAnimState charAnimState = CharAnimState::IDLE;
void updateCharacterAnimation();
// Emote state
bool emoteActive = false;
uint32_t emoteAnimId = 0;
bool emoteLoop = false;
// Target facing
const glm::vec3* targetPosition = nullptr;
bool terrainEnabled = true;
bool terrainLoaded = false;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,27 @@
#pragma once
#include <vector>
#include <memory>
namespace wowee {
namespace rendering {
class Mesh;
class Scene {
public:
Scene() = default;
~Scene() = default;
void addMesh(std::shared_ptr<Mesh> mesh);
void removeMesh(std::shared_ptr<Mesh> mesh);
void clear();
const std::vector<std::shared_ptr<Mesh>>& getMeshes() const { return meshes; }
private:
std::vector<std::shared_ptr<Mesh>> meshes;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,41 @@
#pragma once
#include <string>
#include <GL/glew.h>
#include <glm/glm.hpp>
namespace wowee {
namespace rendering {
class Shader {
public:
Shader() = default;
~Shader();
bool loadFromFile(const std::string& vertexPath, const std::string& fragmentPath);
bool loadFromSource(const std::string& vertexSource, const std::string& fragmentSource);
void use() const;
void unuse() const;
void setUniform(const std::string& name, int value);
void setUniform(const std::string& name, float value);
void setUniform(const std::string& name, const glm::vec2& value);
void setUniform(const std::string& name, const glm::vec3& value);
void setUniform(const std::string& name, const glm::vec4& value);
void setUniform(const std::string& name, const glm::mat3& value);
void setUniform(const std::string& name, const glm::mat4& value);
GLuint getProgram() const { return program; }
private:
bool compile(const std::string& vertexSource, const std::string& fragmentSource);
GLint getUniformLocation(const std::string& name) const;
GLuint program = 0;
GLuint vertexShader = 0;
GLuint fragmentShader = 0;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,83 @@
#pragma once
#include <memory>
#include <glm/glm.hpp>
namespace wowee {
namespace rendering {
class Shader;
class Camera;
/**
* Skybox renderer
*
* Renders an atmospheric sky dome with gradient colors.
* The sky uses a dome/sphere approach for realistic appearance.
*/
class Skybox {
public:
Skybox();
~Skybox();
bool initialize();
void shutdown();
/**
* Render the skybox
* @param camera Camera for view matrix (position is ignored for skybox)
* @param timeOfDay Time of day in hours (0-24), affects sky color
*/
void render(const Camera& camera, float timeOfDay = 12.0f);
/**
* Enable/disable skybox rendering
*/
void setEnabled(bool enabled) { renderingEnabled = enabled; }
bool isEnabled() const { return renderingEnabled; }
/**
* Set time of day (0-24 hours)
* 0 = midnight, 6 = dawn, 12 = noon, 18 = dusk, 24 = midnight
*/
void setTimeOfDay(float time);
float getTimeOfDay() const { return timeOfDay; }
/**
* Enable/disable time progression
*/
void setTimeProgression(bool enabled) { timeProgressionEnabled = enabled; }
bool isTimeProgressionEnabled() const { return timeProgressionEnabled; }
/**
* Update time progression
*/
void update(float deltaTime);
/**
* Get horizon color for fog (public for fog system)
*/
glm::vec3 getHorizonColor(float time) const;
private:
void createSkyDome();
void destroySkyDome();
glm::vec3 getSkyColor(float altitude, float time) const;
glm::vec3 getZenithColor(float time) const;
std::unique_ptr<Shader> skyShader;
uint32_t vao = 0;
uint32_t vbo = 0;
uint32_t ebo = 0;
int indexCount = 0;
float timeOfDay = 12.0f; // Default: noon
float timeSpeed = 1.0f; // 1.0 = 1 hour per real second
bool timeProgressionEnabled = false;
bool renderingEnabled = true;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,76 @@
#pragma once
#include <memory>
#include <vector>
#include <glm/glm.hpp>
namespace wowee {
namespace rendering {
class Shader;
class Camera;
/**
* Star field renderer
*
* Renders a field of stars across the night sky.
* Stars fade in at dusk and out at dawn.
*/
class StarField {
public:
StarField();
~StarField();
bool initialize();
void shutdown();
/**
* Render the star field
* @param camera Camera for view matrix
* @param timeOfDay Time of day in hours (0-24)
*/
void render(const Camera& camera, float timeOfDay);
/**
* Update star twinkle animation
*/
void update(float deltaTime);
/**
* Enable/disable star rendering
*/
void setEnabled(bool enabled) { renderingEnabled = enabled; }
bool isEnabled() const { return renderingEnabled; }
/**
* Get number of stars
*/
int getStarCount() const { return starCount; }
private:
void generateStars();
void createStarBuffers();
void destroyStarBuffers();
float getStarIntensity(float timeOfDay) const;
std::unique_ptr<Shader> starShader;
struct Star {
glm::vec3 position;
float brightness; // 0.3 to 1.0
float twinklePhase; // 0 to 2π for animation
};
std::vector<Star> stars;
int starCount = 1000;
uint32_t vao = 0;
uint32_t vbo = 0;
float twinkleTime = 0.0f;
bool renderingEnabled = true;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,59 @@
#pragma once
#include <GL/glew.h>
#include <glm/glm.hpp>
#include <memory>
#include <vector>
namespace wowee {
namespace rendering {
class Camera;
class CameraController;
class WaterRenderer;
class Shader;
class SwimEffects {
public:
SwimEffects();
~SwimEffects();
bool initialize();
void shutdown();
void update(const Camera& camera, const CameraController& cc,
const WaterRenderer& water, float deltaTime);
void render(const Camera& camera);
private:
struct Particle {
glm::vec3 position;
glm::vec3 velocity;
float lifetime;
float maxLifetime;
float size;
float alpha;
};
static constexpr int MAX_RIPPLE_PARTICLES = 200;
static constexpr int MAX_BUBBLE_PARTICLES = 150;
std::vector<Particle> ripples;
std::vector<Particle> bubbles;
GLuint rippleVAO = 0, rippleVBO = 0;
GLuint bubbleVAO = 0, bubbleVBO = 0;
std::unique_ptr<Shader> rippleShader;
std::unique_ptr<Shader> bubbleShader;
std::vector<float> rippleVertexData;
std::vector<float> bubbleVertexData;
float rippleSpawnAccum = 0.0f;
float bubbleSpawnAccum = 0.0f;
void spawnRipple(const glm::vec3& pos, const glm::vec3& moveDir, float waterH);
void spawnBubble(const glm::vec3& pos, float waterH);
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,270 @@
#pragma once
#include "pipeline/adt_loader.hpp"
#include "pipeline/terrain_mesh.hpp"
#include "pipeline/m2_loader.hpp"
#include "pipeline/wmo_loader.hpp"
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <memory>
#include <optional>
#include <thread>
#include <mutex>
#include <atomic>
#include <queue>
#include <condition_variable>
#include <glm/glm.hpp>
namespace wowee {
namespace pipeline { class AssetManager; }
namespace rendering { class TerrainRenderer; class Camera; class WaterRenderer; class M2Renderer; class WMORenderer; }
namespace rendering {
/**
* Terrain tile coordinates
*/
struct TileCoord {
int x;
int y;
bool operator==(const TileCoord& other) const {
return x == other.x && y == other.y;
}
struct Hash {
size_t operator()(const TileCoord& coord) const {
return std::hash<int>()(coord.x) ^ (std::hash<int>()(coord.y) << 1);
}
};
};
/**
* Loaded terrain tile data
*/
struct TerrainTile {
TileCoord coord;
pipeline::ADTTerrain terrain;
pipeline::TerrainMesh mesh;
bool loaded = false;
// Tile bounds in world coordinates
float minX, minY, maxX, maxY;
// Instance IDs for cleanup on unload
std::vector<uint32_t> wmoInstanceIds;
std::vector<uint32_t> m2InstanceIds;
std::vector<uint32_t> doodadUniqueIds; // For dedup cleanup on unload
};
/**
* Pre-processed tile data ready for GPU upload (produced by background thread)
*/
struct PendingTile {
TileCoord coord;
pipeline::ADTTerrain terrain;
pipeline::TerrainMesh mesh;
// Pre-loaded M2 data
struct M2Ready {
uint32_t modelId;
pipeline::M2Model model;
std::string path;
};
std::vector<M2Ready> m2Models;
// M2 instance placement data (references modelId from m2Models)
struct M2Placement {
uint32_t modelId;
uint32_t uniqueId;
glm::vec3 position;
glm::vec3 rotation;
float scale;
};
std::vector<M2Placement> m2Placements;
// Pre-loaded WMO data
struct WMOReady {
uint32_t modelId;
pipeline::WMOModel model;
glm::vec3 position;
glm::vec3 rotation;
};
std::vector<WMOReady> wmoModels;
// WMO doodad M2 models (M2s placed inside WMOs)
struct WMODoodadReady {
uint32_t modelId;
pipeline::M2Model model;
glm::vec3 worldPosition; // For frustum culling
glm::mat4 modelMatrix; // Pre-computed world transform
};
std::vector<WMODoodadReady> wmoDoodads;
};
/**
* Terrain manager for multi-tile terrain streaming
*
* Handles loading and unloading terrain tiles based on camera position
*/
class TerrainManager {
public:
TerrainManager();
~TerrainManager();
/**
* Initialize terrain manager
* @param assetManager Asset manager for loading files
* @param terrainRenderer Terrain renderer for GPU upload
*/
bool initialize(pipeline::AssetManager* assetManager, TerrainRenderer* terrainRenderer);
/**
* Update terrain streaming based on camera position
* @param camera Current camera
* @param deltaTime Time since last update
*/
void update(const Camera& camera, float deltaTime);
/**
* Set map name
* @param mapName Map name (e.g., "Azeroth", "Kalimdor")
*/
void setMapName(const std::string& mapName) { this->mapName = mapName; }
/**
* Load a single tile
* @param x Tile X coordinate (0-63)
* @param y Tile Y coordinate (0-63)
* @return true if loaded successfully
*/
bool loadTile(int x, int y);
/**
* Unload a tile
* @param x Tile X coordinate
* @param y Tile Y coordinate
*/
void unloadTile(int x, int y);
/**
* Unload all tiles
*/
void unloadAll();
/**
* Set streaming parameters
*/
void setLoadRadius(int radius) { loadRadius = radius; }
void setUnloadRadius(int radius) { unloadRadius = radius; }
void setStreamingEnabled(bool enabled) { streamingEnabled = enabled; }
void setWaterRenderer(WaterRenderer* renderer) { waterRenderer = renderer; }
void setM2Renderer(M2Renderer* renderer) { m2Renderer = renderer; }
void setWMORenderer(WMORenderer* renderer) { wmoRenderer = renderer; }
/**
* Get terrain height at GL coordinates
* @param glX GL X position
* @param glY GL Y position
* @return Height (GL Z) if terrain loaded at that position, empty otherwise
*/
std::optional<float> getHeightAt(float glX, float glY) const;
/**
* Get statistics
*/
int getLoadedTileCount() const { return static_cast<int>(loadedTiles.size()); }
TileCoord getCurrentTile() const { return currentTile; }
private:
/**
* Get tile coordinates from world position
*/
TileCoord worldToTile(float worldX, float worldY) const;
/**
* Get world bounds for a tile
*/
void getTileBounds(const TileCoord& coord, float& minX, float& minY,
float& maxX, float& maxY) const;
/**
* Build ADT file path
*/
std::string getADTPath(const TileCoord& coord) const;
/**
* Load tiles in radius around current tile
*/
void streamTiles();
/**
* Background thread: prepare tile data (CPU work only, no OpenGL)
*/
std::unique_ptr<PendingTile> prepareTile(int x, int y);
/**
* Main thread: upload prepared tile data to GPU
*/
void finalizeTile(std::unique_ptr<PendingTile> pending);
/**
* Background worker thread loop
*/
void workerLoop();
/**
* Main thread: poll for completed tiles and upload to GPU
*/
void processReadyTiles();
pipeline::AssetManager* assetManager = nullptr;
TerrainRenderer* terrainRenderer = nullptr;
WaterRenderer* waterRenderer = nullptr;
M2Renderer* m2Renderer = nullptr;
WMORenderer* wmoRenderer = nullptr;
std::string mapName = "Azeroth";
// Loaded tiles (keyed by coordinate)
std::unordered_map<TileCoord, std::unique_ptr<TerrainTile>, TileCoord::Hash> loadedTiles;
// Tiles that failed to load (don't retry)
std::unordered_map<TileCoord, bool, TileCoord::Hash> failedTiles;
// Current tile (where camera is)
TileCoord currentTile = {-1, -1};
TileCoord lastStreamTile = {-1, -1};
// Streaming parameters
bool streamingEnabled = true;
int loadRadius = 4; // Load tiles within this radius (9x9 grid, ~2133 units)
int unloadRadius = 6; // Unload tiles beyond this radius (~3200 units, past far clip)
float updateInterval = 0.1f; // Check streaming every 0.1 seconds
float timeSinceLastUpdate = 0.0f;
// Tile size constants (WoW ADT specifications)
// A tile (ADT) = 16x16 chunks = 533.33 units across
// A chunk = 8x8 vertex quads = 33.33 units across
static constexpr float TILE_SIZE = 533.33333f; // One tile = 533.33 units
static constexpr float CHUNK_SIZE = 33.33333f; // One chunk = 33.33 units
// Background loading thread
std::thread workerThread;
std::mutex queueMutex;
std::condition_variable queueCV;
std::queue<TileCoord> loadQueue;
std::queue<std::unique_ptr<PendingTile>> readyQueue;
std::atomic<bool> workerRunning{false};
// Track tiles currently queued or being processed to avoid duplicates
std::unordered_map<TileCoord, bool, TileCoord::Hash> pendingTiles;
// Dedup set for doodad placements across tile boundaries
std::unordered_set<uint32_t> placedDoodadIds;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,193 @@
#pragma once
#include "pipeline/terrain_mesh.hpp"
#include "rendering/shader.hpp"
#include "rendering/texture.hpp"
#include "rendering/camera.hpp"
#include <GL/glew.h>
#include <glm/glm.hpp>
#include <memory>
#include <unordered_map>
#include <string>
namespace wowee {
// Forward declarations
namespace pipeline { class AssetManager; }
namespace rendering {
class Frustum;
/**
* GPU-side terrain chunk data
*/
struct TerrainChunkGPU {
GLuint vao = 0; // Vertex array object
GLuint vbo = 0; // Vertex buffer
GLuint ibo = 0; // Index buffer
uint32_t indexCount = 0; // Number of indices to draw
// Texture IDs for this chunk
GLuint baseTexture = 0;
std::vector<GLuint> layerTextures;
std::vector<GLuint> alphaTextures;
// World position for culling
float worldX = 0.0f;
float worldY = 0.0f;
float worldZ = 0.0f;
// Owning tile coordinates (for per-tile removal)
int tileX = -1, tileY = -1;
// Bounding sphere for frustum culling
float boundingSphereRadius = 0.0f;
glm::vec3 boundingSphereCenter = glm::vec3(0.0f);
bool isValid() const { return vao != 0 && vbo != 0 && ibo != 0; }
};
/**
* Terrain renderer
*
* Handles uploading terrain meshes to GPU and rendering them
*/
class TerrainRenderer {
public:
TerrainRenderer();
~TerrainRenderer();
/**
* Initialize terrain renderer
* @param assetManager Asset manager for loading textures
*/
bool initialize(pipeline::AssetManager* assetManager);
/**
* Shutdown and cleanup GPU resources
*/
void shutdown();
/**
* Load terrain mesh and upload to GPU
* @param mesh Terrain mesh to load
* @param texturePaths Texture file paths from ADT
* @param tileX Tile X coordinate for tracking ownership (-1 = untracked)
* @param tileY Tile Y coordinate for tracking ownership (-1 = untracked)
*/
bool loadTerrain(const pipeline::TerrainMesh& mesh,
const std::vector<std::string>& texturePaths,
int tileX = -1, int tileY = -1);
/**
* Remove all chunks belonging to a specific tile
* @param tileX Tile X coordinate
* @param tileY Tile Y coordinate
*/
void removeTile(int tileX, int tileY);
/**
* Render loaded terrain
* @param camera Camera for view/projection matrices
*/
void render(const Camera& camera);
/**
* Clear all loaded terrain
*/
void clear();
/**
* Set lighting parameters
*/
void setLighting(const float lightDir[3], const float lightColor[3],
const float ambientColor[3]);
/**
* Set fog parameters
*/
void setFog(const float fogColor[3], float fogStart, float fogEnd);
/**
* Enable/disable wireframe rendering
*/
void setWireframe(bool enabled) { wireframe = enabled; }
/**
* Enable/disable frustum culling
*/
void setFrustumCulling(bool enabled) { frustumCullingEnabled = enabled; }
/**
* Enable/disable distance fog
*/
void setFogEnabled(bool enabled) { fogEnabled = enabled; }
bool isFogEnabled() const { return fogEnabled; }
/**
* Get statistics
*/
int getChunkCount() const { return static_cast<int>(chunks.size()); }
int getRenderedChunkCount() const { return renderedChunks; }
int getCulledChunkCount() const { return culledChunks; }
int getTriangleCount() const;
private:
/**
* Upload single chunk to GPU
*/
TerrainChunkGPU uploadChunk(const pipeline::ChunkMesh& chunk);
/**
* Load texture from asset manager
*/
GLuint loadTexture(const std::string& path);
/**
* Create alpha texture from raw alpha data
*/
GLuint createAlphaTexture(const std::vector<uint8_t>& alphaData);
/**
* Check if chunk is in view frustum
*/
bool isChunkVisible(const TerrainChunkGPU& chunk, const Frustum& frustum);
/**
* Calculate bounding sphere for chunk
*/
void calculateBoundingSphere(TerrainChunkGPU& chunk, const pipeline::ChunkMesh& meshChunk);
pipeline::AssetManager* assetManager = nullptr;
std::unique_ptr<Shader> shader;
// Loaded terrain chunks
std::vector<TerrainChunkGPU> chunks;
// Texture cache (path -> GL texture ID)
std::unordered_map<std::string, GLuint> textureCache;
// Lighting parameters
float lightDir[3] = {-0.5f, -1.0f, -0.5f};
float lightColor[3] = {1.0f, 1.0f, 0.9f};
float ambientColor[3] = {0.3f, 0.3f, 0.35f};
// Fog parameters
float fogColor[3] = {0.5f, 0.6f, 0.7f};
float fogStart = 400.0f;
float fogEnd = 800.0f;
// Rendering state
bool wireframe = false;
bool frustumCullingEnabled = true;
bool fogEnabled = true;
int renderedChunks = 0;
int culledChunks = 0;
// Default white texture (fallback)
GLuint whiteTexture = 0;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,31 @@
#pragma once
#include <string>
#include <GL/glew.h>
namespace wowee {
namespace rendering {
class Texture {
public:
Texture() = default;
~Texture();
bool loadFromFile(const std::string& path);
bool loadFromMemory(const unsigned char* data, int width, int height, int channels);
void bind(GLuint unit = 0) const;
void unbind() const;
GLuint getID() const { return textureID; }
int getWidth() const { return width; }
int getHeight() const { return height; }
private:
GLuint textureID = 0;
int width = 0;
int height = 0;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,123 @@
#pragma once
#include <vector>
#include <memory>
#include <optional>
#include <glm/glm.hpp>
namespace wowee {
namespace pipeline {
struct ADTTerrain;
struct LiquidData;
}
namespace rendering {
class Camera;
class Shader;
/**
* Water surface for a single map chunk
*/
struct WaterSurface {
glm::vec3 position; // World position
float minHeight; // Minimum water height
float maxHeight; // Maximum water height
uint8_t liquidType; // 0=water, 1=ocean, 2=magma, 3=slime
// Owning tile coordinates (for per-tile removal)
int tileX = -1, tileY = -1;
// Water layer dimensions within chunk (0-7 offset, 1-8 size)
uint8_t xOffset = 0;
uint8_t yOffset = 0;
uint8_t width = 8; // Width in tiles (1-8)
uint8_t height = 8; // Height in tiles (1-8)
// Height map for water surface ((width+1) x (height+1) vertices)
std::vector<float> heights;
// Render mask (which tiles have water)
std::vector<uint8_t> mask;
// Render data
uint32_t vao = 0;
uint32_t vbo = 0;
uint32_t ebo = 0;
int indexCount = 0;
bool hasHeightData() const { return !heights.empty(); }
};
/**
* Water renderer
*
* Renders water surfaces with transparency and animation.
* Supports multiple liquid types (water, ocean, magma, slime).
*/
class WaterRenderer {
public:
WaterRenderer();
~WaterRenderer();
bool initialize();
void shutdown();
/**
* Load water surfaces from ADT terrain
* @param terrain The ADT terrain data
* @param append If true, add to existing water instead of replacing
* @param tileX Tile X coordinate for tracking ownership (-1 = untracked)
* @param tileY Tile Y coordinate for tracking ownership (-1 = untracked)
*/
void loadFromTerrain(const pipeline::ADTTerrain& terrain, bool append = false,
int tileX = -1, int tileY = -1);
/**
* Remove all water surfaces belonging to a specific tile
* @param tileX Tile X coordinate
* @param tileY Tile Y coordinate
*/
void removeTile(int tileX, int tileY);
/**
* Clear all water surfaces
*/
void clear();
/**
* Render all water surfaces
*/
void render(const Camera& camera, float time);
/**
* Enable/disable water rendering
*/
void setEnabled(bool enabled) { renderingEnabled = enabled; }
bool isEnabled() const { return renderingEnabled; }
/**
* Query the water height at a given world position.
* Returns the highest water surface height at that XY, or nullopt if no water.
*/
std::optional<float> getWaterHeightAt(float glX, float glY) const;
/**
* Get water surface count
*/
int getSurfaceCount() const { return static_cast<int>(surfaces.size()); }
private:
void createWaterMesh(WaterSurface& surface);
void destroyWaterMesh(WaterSurface& surface);
glm::vec4 getLiquidColor(uint8_t liquidType) const;
float getLiquidAlpha(uint8_t liquidType) const;
std::unique_ptr<Shader> waterShader;
std::vector<WaterSurface> surfaces;
bool renderingEnabled = true;
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,112 @@
#pragma once
#include <GL/glew.h>
#include <glm/glm.hpp>
#include <memory>
#include <vector>
namespace wowee {
namespace rendering {
class Camera;
class Shader;
/**
* @brief Weather particle system for rain and snow
*
* Features:
* - Rain particles (fast vertical drops)
* - Snow particles (slow floating flakes)
* - Particle recycling for efficiency
* - Camera-relative positioning (follows player)
* - Adjustable intensity (light, medium, heavy)
* - GPU instanced rendering
*/
class Weather {
public:
enum class Type {
NONE,
RAIN,
SNOW
};
Weather();
~Weather();
/**
* @brief Initialize weather system
* @return true if initialization succeeded
*/
bool initialize();
/**
* @brief Update weather particles
* @param camera Camera for particle positioning
* @param deltaTime Time since last frame
*/
void update(const Camera& camera, float deltaTime);
/**
* @brief Render weather particles
* @param camera Camera for rendering
*/
void render(const Camera& camera);
/**
* @brief Set weather type
*/
void setWeatherType(Type type) { weatherType = type; }
Type getWeatherType() const { return weatherType; }
/**
* @brief Set weather intensity (0.0 = none, 1.0 = heavy)
*/
void setIntensity(float intensity);
float getIntensity() const { return intensity; }
/**
* @brief Enable or disable weather
*/
void setEnabled(bool enabled) { this->enabled = enabled; }
bool isEnabled() const { return enabled; }
/**
* @brief Get active particle count
*/
int getParticleCount() const;
private:
struct Particle {
glm::vec3 position;
glm::vec3 velocity;
float lifetime;
float maxLifetime;
};
void cleanup();
void resetParticles(const Camera& camera);
void updateParticle(Particle& particle, const Camera& camera, float deltaTime);
glm::vec3 getRandomPosition(const glm::vec3& center) const;
// OpenGL objects
GLuint vao = 0;
GLuint vbo = 0; // Instance buffer
std::unique_ptr<Shader> shader;
// Particles
std::vector<Particle> particles;
std::vector<glm::vec3> particlePositions; // For rendering
// Weather parameters
bool enabled = false;
Type weatherType = Type::NONE;
float intensity = 0.5f;
// Particle system parameters
static constexpr int MAX_PARTICLES = 2000;
static constexpr float SPAWN_VOLUME_SIZE = 100.0f; // Size of spawn area around camera
static constexpr float SPAWN_HEIGHT = 80.0f; // Height above camera to spawn
};
} // namespace rendering
} // namespace wowee

View file

@ -0,0 +1,262 @@
#pragma once
#include <GL/glew.h>
#include <glm/glm.hpp>
#include <memory>
#include <unordered_map>
#include <vector>
#include <string>
#include <optional>
namespace wowee {
namespace pipeline {
struct WMOModel;
struct WMOGroup;
class AssetManager;
}
namespace rendering {
class Camera;
class Shader;
/**
* WMO (World Model Object) Renderer
*
* Renders buildings, dungeons, and large structures from WMO files.
* Features:
* - Multi-material rendering
* - Batched rendering per group
* - Frustum culling
* - Portal visibility (future)
* - Dynamic lighting support (future)
*/
class WMORenderer {
public:
WMORenderer();
~WMORenderer();
/**
* Initialize renderer and create shaders
* @param assetManager Asset manager for loading textures (optional)
*/
bool initialize(pipeline::AssetManager* assetManager = nullptr);
/**
* Cleanup GPU resources
*/
void shutdown();
/**
* Load WMO model and create GPU resources
* @param model WMO model with geometry data
* @param id Unique identifier for this WMO instance
* @return True if successful
*/
bool loadModel(const pipeline::WMOModel& model, uint32_t id);
/**
* Unload WMO model and free GPU resources
* @param id WMO model identifier
*/
void unloadModel(uint32_t id);
/**
* Create a WMO instance in the world
* @param modelId WMO model to instantiate
* @param position World position
* @param rotation Rotation (euler angles in radians)
* @param scale Uniform scale
* @return Instance ID
*/
uint32_t createInstance(uint32_t modelId, const glm::vec3& position,
const glm::vec3& rotation = glm::vec3(0.0f),
float scale = 1.0f);
/**
* Remove WMO instance
* @param instanceId Instance to remove
*/
void removeInstance(uint32_t instanceId);
/**
* Remove all instances
*/
void clearInstances();
/**
* Render all WMO instances
* @param camera Camera for view/projection matrices
* @param view View matrix
* @param projection Projection matrix
*/
void render(const Camera& camera, const glm::mat4& view, const glm::mat4& projection);
/**
* Get number of loaded models
*/
uint32_t getModelCount() const { return loadedModels.size(); }
/**
* Get number of active instances
*/
uint32_t getInstanceCount() const { return instances.size(); }
/**
* Get total triangle count (all instances)
*/
uint32_t getTotalTriangleCount() const;
/**
* Get total draw call count (last frame)
*/
uint32_t getDrawCallCount() const { return lastDrawCalls; }
/**
* Enable/disable wireframe rendering
*/
void setWireframeMode(bool enabled) { wireframeMode = enabled; }
/**
* Enable/disable frustum culling
*/
void setFrustumCulling(bool enabled) { frustumCulling = enabled; }
/**
* Get floor height at a GL position via ray-triangle intersection
*/
std::optional<float> getFloorHeight(float glX, float glY, float glZ) const;
/**
* Check wall collision and adjust position
* @param from Starting position
* @param to Desired position
* @param adjustedPos Output adjusted position (pushed away from walls)
* @return true if collision occurred
*/
bool checkWallCollision(const glm::vec3& from, const glm::vec3& to, glm::vec3& adjustedPos) const;
/**
* Check if a position is inside any WMO
* @param outModelId If not null, receives the model ID of the WMO
* @return true if inside a WMO
*/
bool isInsideWMO(float glX, float glY, float glZ, uint32_t* outModelId = nullptr) const;
private:
/**
* WMO group GPU resources
*/
struct GroupResources {
GLuint vao = 0;
GLuint vbo = 0;
GLuint ebo = 0;
uint32_t indexCount = 0;
uint32_t vertexCount = 0;
glm::vec3 boundingBoxMin;
glm::vec3 boundingBoxMax;
// Material batches (start index, count, material ID)
struct Batch {
uint32_t startIndex; // First index in EBO
uint32_t indexCount; // Number of indices to draw
uint8_t materialId; // Material/texture reference
};
std::vector<Batch> batches;
// Collision geometry (positions only, for floor raycasting)
std::vector<glm::vec3> collisionVertices;
std::vector<uint16_t> collisionIndices;
};
/**
* Loaded WMO model data
*/
struct ModelData {
uint32_t id;
std::vector<GroupResources> groups;
glm::vec3 boundingBoxMin;
glm::vec3 boundingBoxMax;
// Texture handles for this model (indexed by texture path order)
std::vector<GLuint> textures;
// Material texture indices (materialId -> texture index)
std::vector<uint32_t> materialTextureIndices;
// Material blend modes (materialId -> blendMode; 1 = alpha-test cutout)
std::vector<uint32_t> materialBlendModes;
uint32_t getTotalTriangles() const {
uint32_t total = 0;
for (const auto& group : groups) {
total += group.indexCount / 3;
}
return total;
}
};
/**
* WMO instance in the world
*/
struct WMOInstance {
uint32_t id;
uint32_t modelId;
glm::vec3 position;
glm::vec3 rotation; // Euler angles (radians)
float scale;
glm::mat4 modelMatrix;
void updateModelMatrix();
};
/**
* Create GPU resources for a WMO group
*/
bool createGroupResources(const pipeline::WMOGroup& group, GroupResources& resources);
/**
* Render a single group
*/
void renderGroup(const GroupResources& group, const ModelData& model,
const glm::mat4& modelMatrix,
const glm::mat4& view, const glm::mat4& projection);
/**
* Check if group is visible in frustum
*/
bool isGroupVisible(const GroupResources& group, const glm::mat4& modelMatrix,
const Camera& camera) const;
/**
* Load a texture from path
*/
GLuint loadTexture(const std::string& path);
// Shader
std::unique_ptr<Shader> shader;
// Asset manager for loading textures
pipeline::AssetManager* assetManager = nullptr;
// Texture cache (path -> texture ID)
std::unordered_map<std::string, GLuint> textureCache;
// Default white texture
GLuint whiteTexture = 0;
// Loaded models (modelId -> ModelData)
std::unordered_map<uint32_t, ModelData> loadedModels;
// Active instances
std::vector<WMOInstance> instances;
uint32_t nextInstanceId = 1;
// Rendering state
bool wireframeMode = false;
bool frustumCulling = true;
uint32_t lastDrawCalls = 0;
};
} // namespace rendering
} // namespace wowee