mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-23 15:50:20 +00:00
Anisotropic filtering now queries GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT once and applies via a single applyAnisotropicFiltering() utility, replacing hardcoded calls across all renderers. Fog (sky horizon color, 100-600 range) and Blinn-Phong specular highlights are added to WMO, M2, and character shaders for visual parity with terrain. Shadow sampling plumbing (sampler2DShadow with 3x3 PCF) is wired into all three shaders gated by uShadowEnabled, ready for a future shadow map pass.
354 lines
10 KiB
C++
354 lines
10 KiB
C++
#pragma once
|
|
|
|
#include <GL/glew.h>
|
|
#include <glm/glm.hpp>
|
|
#include <memory>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#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(); }
|
|
|
|
/**
|
|
* Remove models that have no instances referencing them
|
|
* Call periodically to free GPU memory
|
|
*/
|
|
void cleanupUnusedModels();
|
|
|
|
/**
|
|
* 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; }
|
|
|
|
void setFog(const glm::vec3& color, float start, float end) {
|
|
fogColor = color; fogStart = start; fogEnd = end;
|
|
}
|
|
|
|
void setShadowMap(GLuint depthTex, const glm::mat4& lightSpace) {
|
|
shadowDepthTex = depthTex; lightSpaceMatrix = lightSpace; shadowEnabled = true;
|
|
}
|
|
void clearShadowMap() { shadowEnabled = false; }
|
|
|
|
/**
|
|
* Render depth-only for shadow casting (reuses VAOs)
|
|
*/
|
|
void renderShadow(const glm::mat4& lightView, const glm::mat4& lightProj, Shader& shadowShader);
|
|
|
|
/**
|
|
* 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;
|
|
|
|
/**
|
|
* Raycast against WMO bounding boxes for camera collision
|
|
* @param origin Ray origin (e.g., character head position)
|
|
* @param direction Ray direction (normalized)
|
|
* @param maxDistance Maximum ray distance to check
|
|
* @return Distance to first intersection, or maxDistance if no hit
|
|
*/
|
|
float raycastBoundingBoxes(const glm::vec3& origin, const glm::vec3& direction, float maxDistance) const;
|
|
|
|
/**
|
|
* Limit expensive collision/raycast queries to objects near a focus point.
|
|
*/
|
|
void setCollisionFocus(const glm::vec3& worldPos, float radius);
|
|
void clearCollisionFocus();
|
|
|
|
void resetQueryStats();
|
|
double getQueryTimeMs() const { return queryTimeMs; }
|
|
uint32_t getQueryCallCount() const { return queryCallCount; }
|
|
|
|
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;
|
|
glm::mat4 invModelMatrix; // Cached inverse for collision
|
|
glm::vec3 worldBoundsMin;
|
|
glm::vec3 worldBoundsMax;
|
|
std::vector<std::pair<glm::vec3, glm::vec3>> worldGroupBounds;
|
|
|
|
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);
|
|
|
|
struct GridCell {
|
|
int x;
|
|
int y;
|
|
int z;
|
|
bool operator==(const GridCell& other) const {
|
|
return x == other.x && y == other.y && z == other.z;
|
|
}
|
|
};
|
|
struct GridCellHash {
|
|
size_t operator()(const GridCell& c) const {
|
|
size_t h1 = std::hash<int>()(c.x);
|
|
size_t h2 = std::hash<int>()(c.y);
|
|
size_t h3 = std::hash<int>()(c.z);
|
|
return h1 ^ (h2 * 0x9e3779b9u) ^ (h3 * 0x85ebca6bu);
|
|
}
|
|
};
|
|
|
|
GridCell toCell(const glm::vec3& p) const;
|
|
void rebuildSpatialIndex();
|
|
void gatherCandidates(const glm::vec3& queryMin, const glm::vec3& queryMax, std::vector<size_t>& outIndices) const;
|
|
|
|
// 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;
|
|
|
|
// Fog parameters
|
|
glm::vec3 fogColor = glm::vec3(0.5f, 0.6f, 0.7f);
|
|
float fogStart = 400.0f;
|
|
float fogEnd = 1200.0f;
|
|
|
|
// Shadow mapping
|
|
GLuint shadowDepthTex = 0;
|
|
glm::mat4 lightSpaceMatrix = glm::mat4(1.0f);
|
|
bool shadowEnabled = false;
|
|
|
|
// Optional query-space culling for collision/raycast hot paths.
|
|
bool collisionFocusEnabled = false;
|
|
glm::vec3 collisionFocusPos = glm::vec3(0.0f);
|
|
float collisionFocusRadius = 0.0f;
|
|
float collisionFocusRadiusSq = 0.0f;
|
|
|
|
// Uniform grid for fast local collision queries.
|
|
static constexpr float SPATIAL_CELL_SIZE = 64.0f;
|
|
std::unordered_map<GridCell, std::vector<uint32_t>, GridCellHash> spatialGrid;
|
|
std::unordered_map<uint32_t, size_t> instanceIndexById;
|
|
mutable std::vector<size_t> candidateScratch;
|
|
mutable std::unordered_set<uint32_t> candidateIdScratch;
|
|
|
|
// Collision query profiling (per frame).
|
|
mutable double queryTimeMs = 0.0;
|
|
mutable uint32_t queryCallCount = 0;
|
|
};
|
|
|
|
} // namespace rendering
|
|
} // namespace wowee
|