Fix invisible walls from WMO doodad M2 collision and MOPY filtering

WMO interior doodads (gears, decorations) were blocking player movement
via M2 collision. Skip collision for all WMO doodad M2 instances since
the WMO itself handles wall collision.

Also filter WMO wall collision using MOPY per-triangle flags: only
rendered+collidable triangles block the player, skipping invisible
collision hulls.

Revert tram portal extended range (no longer needed with collision fix).
This commit is contained in:
Kelsi 2026-03-06 12:26:17 -08:00
parent ee4e6a31ce
commit 4cbceced67
7 changed files with 44 additions and 20 deletions

View file

@ -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<uint32_t>& instanceIds) {
if (instanceIds.empty() || instances.empty()) {
return;
@ -3649,6 +3658,7 @@ std::optional<float> 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 ---

View file

@ -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;
}

View file

@ -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]];