diff --git a/include/rendering/m2_renderer.hpp b/include/rendering/m2_renderer.hpp index 83762a5c..6b8ebc15 100644 --- a/include/rendering/m2_renderer.hpp +++ b/include/rendering/m2_renderer.hpp @@ -182,6 +182,7 @@ struct M2Instance { bool cachedIsGroundDetail = false; bool cachedIsInvisibleTrap = false; bool cachedIsValid = false; + bool skipCollision = false; // WMO interior doodads — skip player wall collision float cachedBoundRadius = 0.0f; // Frame-skip optimization (update distant animations less frequently) @@ -287,6 +288,7 @@ public: void setInstanceAnimationFrozen(uint32_t instanceId, bool frozen); void removeInstance(uint32_t instanceId); void removeInstances(const std::vector& instanceIds); + void setSkipCollision(uint32_t instanceId, bool skip); void clear(); void cleanupUnusedModels(); diff --git a/include/rendering/wmo_renderer.hpp b/include/rendering/wmo_renderer.hpp index 87c60a66..4587d0b7 100644 --- a/include/rendering/wmo_renderer.hpp +++ b/include/rendering/wmo_renderer.hpp @@ -409,6 +409,9 @@ private: // Pre-computed per-triangle normals (unit length, indexed by triStart/3) std::vector triNormals; + // Per-collision-triangle MOPY flags (indexed by collision tri index, i.e. triStart/3) + std::vector triMopyFlags; + // Scratch bitset for deduplicating triangle queries (sized to numTriangles) mutable std::vector triVisited; diff --git a/src/core/application.cpp b/src/core/application.cpp index a162dba3..8c94289a 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -3719,7 +3719,8 @@ void Application::loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float uint32_t doodadModelId = static_cast(std::hash{}(m2Path)); m2Renderer->loadModel(m2Model, doodadModelId); - m2Renderer->createInstance(doodadModelId, worldPos, glm::vec3(0.0f), doodad.scale); + uint32_t doodadInstId = m2Renderer->createInstance(doodadModelId, worldPos, glm::vec3(0.0f), doodad.scale); + if (doodadInstId) m2Renderer->setSkipCollision(doodadInstId, true); loadedDoodads++; } LOG_INFO("Loaded ", loadedDoodads, " instance WMO doodads"); @@ -6735,6 +6736,7 @@ void Application::processPendingTransportDoodads() { m2Renderer->loadModel(m2Model, doodadModelId); uint32_t m2InstanceId = m2Renderer->createInstance(doodadModelId, glm::vec3(0.0f), glm::vec3(0.0f), 1.0f); if (m2InstanceId == 0) continue; + m2Renderer->setSkipCollision(m2InstanceId, true); wmoRenderer->addDoodadToInstance(it->instanceId, m2InstanceId, doodadTemplate.localTransform); it->spawnedDoodads++; diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 714d870b..5ff720b3 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -8809,22 +8809,13 @@ void GameHandler::checkAreaTriggers() { areaTriggerSuppressFirst_ = false; } - // Deeprun Tram entrance triggers need extended range because WMO - // collision walls block the player from reaching the trigger center. - static const std::unordered_set extendedRangeTriggers = { - 712, 713, // Stormwind/Ironforge → Deeprun Tram (classic IDs) - 2166, 2171, // Tram interior exit triggers - 2173, 2175, // Stormwind/Ironforge tram entrance (WotLK IDs) - }; - for (const auto& at : areaTriggers_) { if (at.mapId != currentMapId_) continue; - const bool extended = extendedRangeTriggers.count(at.id) > 0; bool inside = false; if (at.radius > 0.0f) { // Sphere trigger — small minimum so player must be near the portal - float effectiveRadius = std::max(at.radius, extended ? 45.0f : 12.0f); + float effectiveRadius = std::max(at.radius, 12.0f); float dx = px - at.x; float dy = py - at.y; float dz = pz - at.z; @@ -8832,7 +8823,7 @@ void GameHandler::checkAreaTriggers() { inside = (distSq <= effectiveRadius * effectiveRadius); } else if (at.boxLength > 0.0f || at.boxWidth > 0.0f || at.boxHeight > 0.0f) { // Box trigger — small minimum so player must walk into the portal area - float boxMin = extended ? 60.0f : 16.0f; + float boxMin = 16.0f; float effLength = std::max(at.boxLength, boxMin); float effWidth = std::max(at.boxWidth, boxMin); float effHeight = std::max(at.boxHeight, boxMin); diff --git a/src/rendering/m2_renderer.cpp b/src/rendering/m2_renderer.cpp index 2ea99420..925c020b 100644 --- a/src/rendering/m2_renderer.cpp +++ b/src/rendering/m2_renderer.cpp @@ -3321,6 +3321,15 @@ void M2Renderer::removeInstance(uint32_t instanceId) { } } +void M2Renderer::setSkipCollision(uint32_t instanceId, bool skip) { + for (auto& inst : instances) { + if (inst.id == instanceId) { + inst.skipCollision = skip; + return; + } + } +} + void M2Renderer::removeInstances(const std::vector& instanceIds) { if (instanceIds.empty() || instances.empty()) { return; @@ -3649,6 +3658,7 @@ std::optional M2Renderer::getFloorHeight(float glX, float glY, float glZ, const M2ModelGPU& model = it->second; if (model.collisionNoBlock || model.isInvisibleTrap || model.isSpellEffect) continue; + if (instance.skipCollision) continue; // --- Mesh-based floor: vertical ray vs collision triangles --- // Does NOT skip the AABB path — both contribute and highest wins. @@ -3803,6 +3813,7 @@ bool M2Renderer::checkCollision(const glm::vec3& from, const glm::vec3& to, const M2ModelGPU& model = it->second; if (model.collisionNoBlock || model.isInvisibleTrap || model.isSpellEffect) continue; + if (instance.skipCollision) continue; if (instance.scale <= 0.001f) continue; // --- Mesh-based wall collision: closest-point push --- diff --git a/src/rendering/terrain_manager.cpp b/src/rendering/terrain_manager.cpp index ccef1dae..9ecd3df9 100644 --- a/src/rendering/terrain_manager.cpp +++ b/src/rendering/terrain_manager.cpp @@ -865,7 +865,10 @@ bool TerrainManager::advanceFinalization(FinalizingTile& ft) { m2Renderer->loadModel(doodad.model, doodad.modelId); uint32_t wmoDoodadInstId = m2Renderer->createInstanceWithMatrix( doodad.modelId, doodad.modelMatrix, doodad.worldPosition); - if (wmoDoodadInstId) ft.m2InstanceIds.push_back(wmoDoodadInstId); + if (wmoDoodadInstId) { + m2Renderer->setSkipCollision(wmoDoodadInstId, true); + ft.m2InstanceIds.push_back(wmoDoodadInstId); + } ft.wmoDoodadIndex++; if (ft.wmoDoodadIndex < pending->wmoDoodads.size()) return false; } diff --git a/src/rendering/wmo_renderer.cpp b/src/rendering/wmo_renderer.cpp index ee050856..ce4d4ab1 100644 --- a/src/rendering/wmo_renderer.cpp +++ b/src/rendering/wmo_renderer.cpp @@ -1879,15 +1879,12 @@ bool WMORenderer::createGroupResources(const pipeline::WMOGroup& group, GroupRes resources.collisionVertices.push_back(v.position); } if (!group.triFlags.empty()) { - // Filter out non-collidable triangles - resources.collisionIndices.reserve(group.indices.size()); + // Store all triangles but tag each with MOPY flags for collision filtering + resources.collisionIndices = group.indices; size_t numTris = group.indices.size() / 3; + resources.triMopyFlags.resize(numTris, 0); for (size_t t = 0; t < numTris; t++) { - uint8_t flags = (t < group.triFlags.size()) ? group.triFlags[t] : 0; - if (flags & 0x04) continue; // detail/decorative — skip collision - resources.collisionIndices.push_back(group.indices[t * 3 + 0]); - resources.collisionIndices.push_back(group.indices[t * 3 + 1]); - resources.collisionIndices.push_back(group.indices[t * 3 + 2]); + resources.triMopyFlags[t] = (t < group.triFlags.size()) ? group.triFlags[t] : 0; } } else { resources.collisionIndices = group.indices; @@ -3102,6 +3099,21 @@ bool WMORenderer::checkWallCollision(const glm::vec3& from, const glm::vec3& to, float triHeight = tb.maxZ - tb.minZ; if (triHeight < 1.0f && tb.maxZ <= localFeetZ + 1.2f) continue; + // Use MOPY flags to filter wall collision. + // Only RENDERED triangles (flag 0x20) with collision intent (0x01) + // should block the player. Skip invisible collision hulls (0x08/0x48) + // and non-collidable render-only geometry. + uint32_t triIdx = triStart / 3; + if (!group.triMopyFlags.empty() && triIdx < group.triMopyFlags.size()) { + uint8_t mopy = group.triMopyFlags[triIdx]; + // Must be rendered (0x20) AND have base collision flag (0x01) + bool rendered = (mopy & 0x20) != 0; + bool collidable = (mopy & 0x01) != 0; + if (mopy != 0 && !(rendered && collidable)) { + continue; + } + } + const glm::vec3& v0 = verts[indices[triStart]]; const glm::vec3& v1 = verts[indices[triStart + 1]]; const glm::vec3& v2 = verts[indices[triStart + 2]];