mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
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:
parent
ee4e6a31ce
commit
4cbceced67
7 changed files with 44 additions and 20 deletions
|
|
@ -182,6 +182,7 @@ struct M2Instance {
|
||||||
bool cachedIsGroundDetail = false;
|
bool cachedIsGroundDetail = false;
|
||||||
bool cachedIsInvisibleTrap = false;
|
bool cachedIsInvisibleTrap = false;
|
||||||
bool cachedIsValid = false;
|
bool cachedIsValid = false;
|
||||||
|
bool skipCollision = false; // WMO interior doodads — skip player wall collision
|
||||||
float cachedBoundRadius = 0.0f;
|
float cachedBoundRadius = 0.0f;
|
||||||
|
|
||||||
// Frame-skip optimization (update distant animations less frequently)
|
// Frame-skip optimization (update distant animations less frequently)
|
||||||
|
|
@ -287,6 +288,7 @@ public:
|
||||||
void setInstanceAnimationFrozen(uint32_t instanceId, bool frozen);
|
void setInstanceAnimationFrozen(uint32_t instanceId, bool frozen);
|
||||||
void removeInstance(uint32_t instanceId);
|
void removeInstance(uint32_t instanceId);
|
||||||
void removeInstances(const std::vector<uint32_t>& instanceIds);
|
void removeInstances(const std::vector<uint32_t>& instanceIds);
|
||||||
|
void setSkipCollision(uint32_t instanceId, bool skip);
|
||||||
void clear();
|
void clear();
|
||||||
void cleanupUnusedModels();
|
void cleanupUnusedModels();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -409,6 +409,9 @@ private:
|
||||||
// Pre-computed per-triangle normals (unit length, indexed by triStart/3)
|
// Pre-computed per-triangle normals (unit length, indexed by triStart/3)
|
||||||
std::vector<glm::vec3> triNormals;
|
std::vector<glm::vec3> triNormals;
|
||||||
|
|
||||||
|
// Per-collision-triangle MOPY flags (indexed by collision tri index, i.e. triStart/3)
|
||||||
|
std::vector<uint8_t> triMopyFlags;
|
||||||
|
|
||||||
// Scratch bitset for deduplicating triangle queries (sized to numTriangles)
|
// Scratch bitset for deduplicating triangle queries (sized to numTriangles)
|
||||||
mutable std::vector<uint8_t> triVisited;
|
mutable std::vector<uint8_t> triVisited;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3719,7 +3719,8 @@ void Application::loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float
|
||||||
|
|
||||||
uint32_t doodadModelId = static_cast<uint32_t>(std::hash<std::string>{}(m2Path));
|
uint32_t doodadModelId = static_cast<uint32_t>(std::hash<std::string>{}(m2Path));
|
||||||
m2Renderer->loadModel(m2Model, doodadModelId);
|
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++;
|
loadedDoodads++;
|
||||||
}
|
}
|
||||||
LOG_INFO("Loaded ", loadedDoodads, " instance WMO doodads");
|
LOG_INFO("Loaded ", loadedDoodads, " instance WMO doodads");
|
||||||
|
|
@ -6735,6 +6736,7 @@ void Application::processPendingTransportDoodads() {
|
||||||
m2Renderer->loadModel(m2Model, doodadModelId);
|
m2Renderer->loadModel(m2Model, doodadModelId);
|
||||||
uint32_t m2InstanceId = m2Renderer->createInstance(doodadModelId, glm::vec3(0.0f), glm::vec3(0.0f), 1.0f);
|
uint32_t m2InstanceId = m2Renderer->createInstance(doodadModelId, glm::vec3(0.0f), glm::vec3(0.0f), 1.0f);
|
||||||
if (m2InstanceId == 0) continue;
|
if (m2InstanceId == 0) continue;
|
||||||
|
m2Renderer->setSkipCollision(m2InstanceId, true);
|
||||||
|
|
||||||
wmoRenderer->addDoodadToInstance(it->instanceId, m2InstanceId, doodadTemplate.localTransform);
|
wmoRenderer->addDoodadToInstance(it->instanceId, m2InstanceId, doodadTemplate.localTransform);
|
||||||
it->spawnedDoodads++;
|
it->spawnedDoodads++;
|
||||||
|
|
|
||||||
|
|
@ -8809,22 +8809,13 @@ void GameHandler::checkAreaTriggers() {
|
||||||
areaTriggerSuppressFirst_ = false;
|
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<uint32_t> 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_) {
|
for (const auto& at : areaTriggers_) {
|
||||||
if (at.mapId != currentMapId_) continue;
|
if (at.mapId != currentMapId_) continue;
|
||||||
|
|
||||||
const bool extended = extendedRangeTriggers.count(at.id) > 0;
|
|
||||||
bool inside = false;
|
bool inside = false;
|
||||||
if (at.radius > 0.0f) {
|
if (at.radius > 0.0f) {
|
||||||
// Sphere trigger — small minimum so player must be near the portal
|
// 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 dx = px - at.x;
|
||||||
float dy = py - at.y;
|
float dy = py - at.y;
|
||||||
float dz = pz - at.z;
|
float dz = pz - at.z;
|
||||||
|
|
@ -8832,7 +8823,7 @@ void GameHandler::checkAreaTriggers() {
|
||||||
inside = (distSq <= effectiveRadius * effectiveRadius);
|
inside = (distSq <= effectiveRadius * effectiveRadius);
|
||||||
} else if (at.boxLength > 0.0f || at.boxWidth > 0.0f || at.boxHeight > 0.0f) {
|
} 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
|
// 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 effLength = std::max(at.boxLength, boxMin);
|
||||||
float effWidth = std::max(at.boxWidth, boxMin);
|
float effWidth = std::max(at.boxWidth, boxMin);
|
||||||
float effHeight = std::max(at.boxHeight, boxMin);
|
float effHeight = std::max(at.boxHeight, boxMin);
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
void M2Renderer::removeInstances(const std::vector<uint32_t>& instanceIds) {
|
||||||
if (instanceIds.empty() || instances.empty()) {
|
if (instanceIds.empty() || instances.empty()) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -3649,6 +3658,7 @@ std::optional<float> M2Renderer::getFloorHeight(float glX, float glY, float glZ,
|
||||||
|
|
||||||
const M2ModelGPU& model = it->second;
|
const M2ModelGPU& model = it->second;
|
||||||
if (model.collisionNoBlock || model.isInvisibleTrap || model.isSpellEffect) continue;
|
if (model.collisionNoBlock || model.isInvisibleTrap || model.isSpellEffect) continue;
|
||||||
|
if (instance.skipCollision) continue;
|
||||||
|
|
||||||
// --- Mesh-based floor: vertical ray vs collision triangles ---
|
// --- Mesh-based floor: vertical ray vs collision triangles ---
|
||||||
// Does NOT skip the AABB path — both contribute and highest wins.
|
// 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;
|
const M2ModelGPU& model = it->second;
|
||||||
if (model.collisionNoBlock || model.isInvisibleTrap || model.isSpellEffect) continue;
|
if (model.collisionNoBlock || model.isInvisibleTrap || model.isSpellEffect) continue;
|
||||||
|
if (instance.skipCollision) continue;
|
||||||
if (instance.scale <= 0.001f) continue;
|
if (instance.scale <= 0.001f) continue;
|
||||||
|
|
||||||
// --- Mesh-based wall collision: closest-point push ---
|
// --- Mesh-based wall collision: closest-point push ---
|
||||||
|
|
|
||||||
|
|
@ -865,7 +865,10 @@ bool TerrainManager::advanceFinalization(FinalizingTile& ft) {
|
||||||
m2Renderer->loadModel(doodad.model, doodad.modelId);
|
m2Renderer->loadModel(doodad.model, doodad.modelId);
|
||||||
uint32_t wmoDoodadInstId = m2Renderer->createInstanceWithMatrix(
|
uint32_t wmoDoodadInstId = m2Renderer->createInstanceWithMatrix(
|
||||||
doodad.modelId, doodad.modelMatrix, doodad.worldPosition);
|
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++;
|
ft.wmoDoodadIndex++;
|
||||||
if (ft.wmoDoodadIndex < pending->wmoDoodads.size()) return false;
|
if (ft.wmoDoodadIndex < pending->wmoDoodads.size()) return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1879,15 +1879,12 @@ bool WMORenderer::createGroupResources(const pipeline::WMOGroup& group, GroupRes
|
||||||
resources.collisionVertices.push_back(v.position);
|
resources.collisionVertices.push_back(v.position);
|
||||||
}
|
}
|
||||||
if (!group.triFlags.empty()) {
|
if (!group.triFlags.empty()) {
|
||||||
// Filter out non-collidable triangles
|
// Store all triangles but tag each with MOPY flags for collision filtering
|
||||||
resources.collisionIndices.reserve(group.indices.size());
|
resources.collisionIndices = group.indices;
|
||||||
size_t numTris = group.indices.size() / 3;
|
size_t numTris = group.indices.size() / 3;
|
||||||
|
resources.triMopyFlags.resize(numTris, 0);
|
||||||
for (size_t t = 0; t < numTris; t++) {
|
for (size_t t = 0; t < numTris; t++) {
|
||||||
uint8_t flags = (t < group.triFlags.size()) ? group.triFlags[t] : 0;
|
resources.triMopyFlags[t] = (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 {
|
} else {
|
||||||
resources.collisionIndices = group.indices;
|
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;
|
float triHeight = tb.maxZ - tb.minZ;
|
||||||
if (triHeight < 1.0f && tb.maxZ <= localFeetZ + 1.2f) continue;
|
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& v0 = verts[indices[triStart]];
|
||||||
const glm::vec3& v1 = verts[indices[triStart + 1]];
|
const glm::vec3& v1 = verts[indices[triStart + 1]];
|
||||||
const glm::vec3& v2 = verts[indices[triStart + 2]];
|
const glm::vec3& v2 = verts[indices[triStart + 2]];
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue