diff --git a/include/pipeline/wmo_loader.hpp b/include/pipeline/wmo_loader.hpp index fecb287d..ed2cf149 100644 --- a/include/pipeline/wmo_loader.hpp +++ b/include/pipeline/wmo_loader.hpp @@ -157,6 +157,7 @@ struct WMOGroup { std::vector vertices; std::vector indices; std::vector batches; + std::vector triFlags; // Per-triangle MOPY flags (0x04 = detail/no-collide) // Portals std::vector portals; diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 71a65c92..714d870b 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -8812,8 +8812,9 @@ void GameHandler::checkAreaTriggers() { // 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 + 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_) { diff --git a/src/pipeline/wmo_loader.cpp b/src/pipeline/wmo_loader.cpp index e90a79de..22a4df42 100644 --- a/src/pipeline/wmo_loader.cpp +++ b/src/pipeline/wmo_loader.cpp @@ -498,6 +498,16 @@ bool WMOLoader::loadGroup(const std::vector& groupData, group.indices.push_back(read(groupData, mogpOffset)); } } + else if (subChunkId == 0x4D4F5059) { // MOPY - Triangle material info + // 2 bytes per triangle: flags (uint8) + materialId (uint8) + // flag 0x04 = detail/decorative geometry (no collision) + uint32_t triCount = subChunkSize / 2; + group.triFlags.resize(triCount); + for (uint32_t i = 0; i < triCount; i++) { + group.triFlags[i] = read(groupData, mogpOffset); + read(groupData, mogpOffset); // materialId (skip) + } + } else if (subChunkId == 0x4D4F4E52) { // MONR - Normals uint32_t normalCount = subChunkSize / 12; core::Logger::getInstance().debug(" MONR: ", normalCount, " normals for ", group.vertices.size(), " vertices"); diff --git a/src/rendering/wmo_renderer.cpp b/src/rendering/wmo_renderer.cpp index 7c2558d5..ee050856 100644 --- a/src/rendering/wmo_renderer.cpp +++ b/src/rendering/wmo_renderer.cpp @@ -1871,12 +1871,27 @@ bool WMORenderer::createGroupResources(const pipeline::WMOGroup& group, GroupRes resources.indexBuffer = idxBuf.buffer; resources.indexAlloc = idxBuf.allocation; - // Store collision geometry for floor raycasting + // Store collision geometry for floor raycasting. + // Use MOPY per-triangle flags to exclude detail/decorative geometry (flag 0x04) + // from collision — these are things like gears, railings, etc. resources.collisionVertices.reserve(group.vertices.size()); for (const auto& v : group.vertices) { resources.collisionVertices.push_back(v.position); } - resources.collisionIndices = group.indices; + if (!group.triFlags.empty()) { + // Filter out non-collidable triangles + resources.collisionIndices.reserve(group.indices.size()); + size_t numTris = group.indices.size() / 3; + 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]); + } + } else { + resources.collisionIndices = group.indices; + } // Compute actual bounding box from vertices (WMO header bboxes can be unreliable) if (!resources.collisionVertices.empty()) {