#pragma once #include #include #include #include #include #include #include #include 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 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 batches; // Collision geometry (positions only, for floor raycasting) std::vector collisionVertices; std::vector collisionIndices; }; /** * Loaded WMO model data */ struct ModelData { uint32_t id; std::vector groups; glm::vec3 boundingBoxMin; glm::vec3 boundingBoxMax; // Texture handles for this model (indexed by texture path order) std::vector textures; // Material texture indices (materialId -> texture index) std::vector materialTextureIndices; // Material blend modes (materialId -> blendMode; 1 = alpha-test cutout) std::vector 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> 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()(c.x); size_t h2 = std::hash()(c.y); size_t h3 = std::hash()(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& outIndices) const; // Shader std::unique_ptr shader; // Asset manager for loading textures pipeline::AssetManager* assetManager = nullptr; // Texture cache (path -> texture ID) std::unordered_map textureCache; // Default white texture GLuint whiteTexture = 0; // Loaded models (modelId -> ModelData) std::unordered_map loadedModels; // Active instances std::vector 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, GridCellHash> spatialGrid; std::unordered_map instanceIndexById; mutable std::vector candidateScratch; mutable std::unordered_set candidateIdScratch; // Collision query profiling (per frame). mutable double queryTimeMs = 0.0; mutable uint32_t queryCallCount = 0; }; } // namespace rendering } // namespace wowee