Filter WMO decorative geometry from collision, fix tram portal trigger IDs

Parse MOPY per-triangle flags in WMO groups and exclude detail/decorative
triangles (flag 0x04) from collision detection. This prevents invisible
walls from objects like gears and railings in WMO interiors.

Add WotLK area trigger IDs 2173/2175 to extended-range tram triggers.
This commit is contained in:
Kelsi 2026-03-06 10:37:32 -08:00
parent cefb05c027
commit ee4e6a31ce
4 changed files with 30 additions and 3 deletions

View file

@ -157,6 +157,7 @@ struct WMOGroup {
std::vector<WMOVertex> vertices; std::vector<WMOVertex> vertices;
std::vector<uint16_t> indices; std::vector<uint16_t> indices;
std::vector<WMOBatch> batches; std::vector<WMOBatch> batches;
std::vector<uint8_t> triFlags; // Per-triangle MOPY flags (0x04 = detail/no-collide)
// Portals // Portals
std::vector<WMOPortal> portals; std::vector<WMOPortal> portals;

View file

@ -8812,8 +8812,9 @@ void GameHandler::checkAreaTriggers() {
// Deeprun Tram entrance triggers need extended range because WMO // Deeprun Tram entrance triggers need extended range because WMO
// collision walls block the player from reaching the trigger center. // collision walls block the player from reaching the trigger center.
static const std::unordered_set<uint32_t> extendedRangeTriggers = { static const std::unordered_set<uint32_t> extendedRangeTriggers = {
712, 713, // Stormwind/Ironforge → Deeprun Tram 712, 713, // Stormwind/Ironforge → Deeprun Tram (classic IDs)
2166, 2171, // Tram interior exit triggers 2166, 2171, // Tram interior exit triggers
2173, 2175, // Stormwind/Ironforge tram entrance (WotLK IDs)
}; };
for (const auto& at : areaTriggers_) { for (const auto& at : areaTriggers_) {

View file

@ -498,6 +498,16 @@ bool WMOLoader::loadGroup(const std::vector<uint8_t>& groupData,
group.indices.push_back(read<uint16_t>(groupData, mogpOffset)); group.indices.push_back(read<uint16_t>(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<uint8_t>(groupData, mogpOffset);
read<uint8_t>(groupData, mogpOffset); // materialId (skip)
}
}
else if (subChunkId == 0x4D4F4E52) { // MONR - Normals else if (subChunkId == 0x4D4F4E52) { // MONR - Normals
uint32_t normalCount = subChunkSize / 12; uint32_t normalCount = subChunkSize / 12;
core::Logger::getInstance().debug(" MONR: ", normalCount, " normals for ", group.vertices.size(), " vertices"); core::Logger::getInstance().debug(" MONR: ", normalCount, " normals for ", group.vertices.size(), " vertices");

View file

@ -1871,12 +1871,27 @@ bool WMORenderer::createGroupResources(const pipeline::WMOGroup& group, GroupRes
resources.indexBuffer = idxBuf.buffer; resources.indexBuffer = idxBuf.buffer;
resources.indexAlloc = idxBuf.allocation; 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()); resources.collisionVertices.reserve(group.vertices.size());
for (const auto& v : group.vertices) { for (const auto& v : group.vertices) {
resources.collisionVertices.push_back(v.position); 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) // Compute actual bounding box from vertices (WMO header bboxes can be unreliable)
if (!resources.collisionVertices.empty()) { if (!resources.collisionVertices.empty()) {