mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-25 16:30:15 +00:00
Fix interior WMO floor gaps, water exit stair clipping, NPC equipment, portal animation
- Fix Stormwind barracks floor: interior WMO groups named "facade" were incorrectly marked as LOD shells and hidden when close. Add !isIndoor guard to all LOD detection conditions so interior groups always render. - Fix water exit stair clipping: anchor lastGroundZ to current position on swim exit, set grounded=true for full step-up budget, add upward velocity boost to clear stair lip geometry. - Re-enable NPC humanoid equipment geosets (kEnableNpcHumanoidOverrides) so guards render with proper armor instead of underwear. - Keep instance portal GameObjects animated (spinning/glowing) instead of freezing all GO animations indiscriminately. - Fix equipment disappearing after instance round-trip by resetting dirty tracking on world reload. - Fix multi-doodad-set loading: load both set 0 (global) and placement- specific doodad set, with dedup to avoid double-loading. - Clear placedWmoIds in softReset/unloadAll to prevent stale dedup. - Apply MODF rotation to instance WMOs, snap player to WMO floor. - Re-enable rebuildSpatialIndex in setInstanceTransform. - Store precomputeFloorCache results in precomputed grid. - Add F8 debug key for WMO floor diagnostics at player position. - Expand mapIdToName with all Classic/TBC/WotLK instance map IDs.
This commit is contained in:
parent
bec7a678aa
commit
16d44c5bb3
6 changed files with 285 additions and 28 deletions
|
|
@ -659,7 +659,17 @@ void CameraController::update(float deltaTime) {
|
|||
|
||||
grounded = false;
|
||||
} else {
|
||||
// Exiting water — give a small upward boost to help climb onto shore.
|
||||
// Exiting water — boost upward to help climb onto shore/stairs.
|
||||
if (wasSwimming) {
|
||||
// Anchor lastGroundZ to current position so WMO floor probes
|
||||
// start from a sensible height instead of stale pre-swim values.
|
||||
lastGroundZ = targetPos.z;
|
||||
grounded = true; // Treat as grounded so step-up budget is full
|
||||
// Small upward boost to clear stair lip geometry
|
||||
if (verticalVelocity < 1.5f) {
|
||||
verticalVelocity = 1.5f;
|
||||
}
|
||||
}
|
||||
swimming = false;
|
||||
|
||||
if (glm::length(movement) > 0.001f) {
|
||||
|
|
|
|||
|
|
@ -522,10 +522,18 @@ std::shared_ptr<PendingTile> TerrainManager::prepareTile(int x, int y) {
|
|||
wmoMatrix = glm::rotate(wmoMatrix, rot.y, glm::vec3(0, 1, 0));
|
||||
wmoMatrix = glm::rotate(wmoMatrix, rot.x, glm::vec3(1, 0, 0));
|
||||
|
||||
const auto& doodadSet = wmoModel.doodadSets[0];
|
||||
// Load doodads from set 0 (global) + placement-specific set
|
||||
std::vector<uint32_t> setsToLoad = {0};
|
||||
if (placement.doodadSet > 0 && placement.doodadSet < wmoModel.doodadSets.size()) {
|
||||
setsToLoad.push_back(placement.doodadSet);
|
||||
}
|
||||
std::unordered_set<uint32_t> loadedDoodadIndices;
|
||||
for (uint32_t setIdx : setsToLoad) {
|
||||
const auto& doodadSet = wmoModel.doodadSets[setIdx];
|
||||
for (uint32_t di = 0; di < doodadSet.count; di++) {
|
||||
uint32_t doodadIdx = doodadSet.startIndex + di;
|
||||
if (doodadIdx >= wmoModel.doodads.size()) break;
|
||||
if (!loadedDoodadIndices.insert(doodadIdx).second) continue;
|
||||
|
||||
const auto& doodad = wmoModel.doodads[doodadIdx];
|
||||
auto nameIt = wmoModel.doodadNames.find(doodad.nameIndex);
|
||||
|
|
@ -623,6 +631,7 @@ std::shared_ptr<PendingTile> TerrainManager::prepareTile(int x, int y) {
|
|||
doodadReady.modelMatrix = worldMatrix;
|
||||
pending->wmoDoodads.push_back(std::move(doodadReady));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PendingTile::WMOReady ready;
|
||||
|
|
@ -1311,6 +1320,7 @@ void TerrainManager::unloadAll() {
|
|||
pendingTiles.clear();
|
||||
finalizingTiles_.clear();
|
||||
placedDoodadIds.clear();
|
||||
placedWmoIds.clear();
|
||||
|
||||
LOG_INFO("Unloading all terrain tiles");
|
||||
loadedTiles.clear();
|
||||
|
|
@ -1358,6 +1368,7 @@ void TerrainManager::softReset() {
|
|||
pendingTiles.clear();
|
||||
finalizingTiles_.clear();
|
||||
placedDoodadIds.clear();
|
||||
placedWmoIds.clear();
|
||||
|
||||
// Clear tile cache — keys are (x,y) without map name, so stale entries from
|
||||
// a different map with overlapping coordinates would produce wrong geometry.
|
||||
|
|
|
|||
|
|
@ -533,10 +533,8 @@ bool WMORenderer::loadModel(const pipeline::WMOModel& model, uint32_t id) {
|
|||
// "city01" etc are exterior cityscape shells in large WMOs
|
||||
isCityShell = (lower.find("city") == 0 && lower.size() <= 8);
|
||||
}
|
||||
// Flag 0x80 on INDOOR groups in large WMOs = interior cathedral shell
|
||||
bool hasFlag80 = (wmoGroup.flags & 0x80) != 0;
|
||||
bool isIndoor = (wmoGroup.flags & 0x2000) != 0;
|
||||
if ((nVerts < 100 && isLargeWmo) || (alwaysDraw && nVerts < 5000) || (isFacade && isLargeWmo) || (isCityShell && isLargeWmo) || (hasFlag80 && isIndoor && isLargeWmo)) {
|
||||
if ((nVerts < 100 && isLargeWmo && !isIndoor) || (alwaysDraw && nVerts < 5000 && isLargeWmo && !isIndoor) || (isFacade && isLargeWmo && !isIndoor) || (isCityShell && !isIndoor && isLargeWmo)) {
|
||||
resources.isLOD = true;
|
||||
}
|
||||
modelData.groups.push_back(resources);
|
||||
|
|
@ -954,9 +952,7 @@ void WMORenderer::setInstanceTransform(uint32_t instanceId, const glm::mat4& tra
|
|||
}
|
||||
}
|
||||
|
||||
// NOTE: Don't rebuild spatial index on every transform update - causes flickering
|
||||
// Spatial grid is only used for collision queries, render iterates all instances
|
||||
// rebuildSpatialIndex();
|
||||
rebuildSpatialIndex();
|
||||
}
|
||||
|
||||
void WMORenderer::addDoodadToInstance(uint32_t instanceId, uint32_t m2InstanceId, const glm::mat4& localTransform) {
|
||||
|
|
@ -1228,8 +1224,11 @@ void WMORenderer::precomputeFloorCache() {
|
|||
|
||||
samplesChecked++;
|
||||
|
||||
// getFloorHeight will compute and cache the result
|
||||
getFloorHeight(sampleX, sampleY, refZ);
|
||||
// Query floor height and store result in the precomputed grid
|
||||
auto h = getFloorHeight(sampleX, sampleY, refZ);
|
||||
if (h) {
|
||||
precomputedFloorGrid[key] = *h;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2902,6 +2901,90 @@ std::optional<float> WMORenderer::getFloorHeight(float glX, float glY, float glZ
|
|||
return bestFloor;
|
||||
}
|
||||
|
||||
void WMORenderer::debugDumpGroupsAtPosition(float glX, float glY, float glZ) const {
|
||||
LOG_WARNING("=== WMO Floor Debug at render(", glX, ", ", glY, ", ", glZ, ") ===");
|
||||
|
||||
glm::vec3 worldOrigin(glX, glY, glZ + 500.0f);
|
||||
glm::vec3 worldDir(0.0f, 0.0f, -1.0f);
|
||||
|
||||
int totalInstancesChecked = 0;
|
||||
int totalGroupsOverlapping = 0;
|
||||
int totalFloorHits = 0;
|
||||
|
||||
for (const auto& instance : instances) {
|
||||
auto it = loadedModels.find(instance.modelId);
|
||||
if (it == loadedModels.end()) continue;
|
||||
const ModelData& model = it->second;
|
||||
|
||||
// Check instance world bounds
|
||||
if (glX < instance.worldBoundsMin.x || glX > instance.worldBoundsMax.x ||
|
||||
glY < instance.worldBoundsMin.y || glY > instance.worldBoundsMax.y ||
|
||||
glZ < instance.worldBoundsMin.z - 20.0f || glZ > instance.worldBoundsMax.z + 20.0f) {
|
||||
continue;
|
||||
}
|
||||
totalInstancesChecked++;
|
||||
LOG_WARNING(" Instance modelId=", instance.modelId,
|
||||
" worldBounds=(", instance.worldBoundsMin.x, ",", instance.worldBoundsMin.y, ",", instance.worldBoundsMin.z,
|
||||
")-(", instance.worldBoundsMax.x, ",", instance.worldBoundsMax.y, ",", instance.worldBoundsMax.z,
|
||||
") groups=", model.groups.size());
|
||||
|
||||
glm::vec3 localOrigin = glm::vec3(instance.invModelMatrix * glm::vec4(worldOrigin, 1.0f));
|
||||
glm::vec3 localDir = glm::normalize(glm::vec3(instance.invModelMatrix * glm::vec4(worldDir, 0.0f)));
|
||||
|
||||
for (size_t gi = 0; gi < model.groups.size(); ++gi) {
|
||||
// Check world-space group bounds
|
||||
if (gi < instance.worldGroupBounds.size()) {
|
||||
const auto& [gMin, gMax] = instance.worldGroupBounds[gi];
|
||||
if (glX < gMin.x || glX > gMax.x ||
|
||||
glY < gMin.y || glY > gMax.y) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
totalGroupsOverlapping++;
|
||||
const auto& group = model.groups[gi];
|
||||
|
||||
// Count floor triangles in this group under the player
|
||||
int floorTris = 0;
|
||||
float bestHitZ = -999999.0f;
|
||||
const auto& verts = group.collisionVertices;
|
||||
const auto& indices = group.collisionIndices;
|
||||
for (size_t ti = 0; ti + 2 < indices.size(); ti += 3) {
|
||||
const glm::vec3& v0 = verts[indices[ti]];
|
||||
const glm::vec3& v1 = verts[indices[ti + 1]];
|
||||
const glm::vec3& v2 = verts[indices[ti + 2]];
|
||||
float t = rayTriangleIntersect(localOrigin, localDir, v0, v1, v2);
|
||||
if (t <= 0.0f) t = rayTriangleIntersect(localOrigin, localDir, v0, v2, v1);
|
||||
if (t > 0.0f) {
|
||||
glm::vec3 hitLocal = localOrigin + localDir * t;
|
||||
glm::vec3 hitWorld = glm::vec3(instance.modelMatrix * glm::vec4(hitLocal, 1.0f));
|
||||
floorTris++;
|
||||
totalFloorHits++;
|
||||
if (hitWorld.z > bestHitZ) bestHitZ = hitWorld.z;
|
||||
}
|
||||
}
|
||||
|
||||
glm::vec3 gWorldMin(0), gWorldMax(0);
|
||||
if (gi < instance.worldGroupBounds.size()) {
|
||||
gWorldMin = instance.worldGroupBounds[gi].first;
|
||||
gWorldMax = instance.worldGroupBounds[gi].second;
|
||||
}
|
||||
LOG_WARNING(" Group[", gi, "] flags=0x", std::hex, group.groupFlags, std::dec,
|
||||
" verts=", group.collisionVertices.size(),
|
||||
" tris=", group.collisionIndices.size()/3,
|
||||
" batches=", group.mergedBatches.size(),
|
||||
" isLOD=", group.isLOD,
|
||||
" floorHits=", floorTris,
|
||||
" bestHitZ=", bestHitZ,
|
||||
" wBounds=(", gWorldMin.x, ",", gWorldMin.y, ",", gWorldMin.z,
|
||||
")-(", gWorldMax.x, ",", gWorldMax.y, ",", gWorldMax.z, ")");
|
||||
}
|
||||
}
|
||||
|
||||
LOG_WARNING("=== Total: ", totalInstancesChecked, " instances, ",
|
||||
totalGroupsOverlapping, " overlapping groups, ",
|
||||
totalFloorHits, " floor hits ===");
|
||||
}
|
||||
|
||||
bool WMORenderer::checkWallCollision(const glm::vec3& from, const glm::vec3& to, glm::vec3& adjustedPos, bool insideWMO) const {
|
||||
QueryTimer timer(&queryTimeMs, &queryCallCount);
|
||||
adjustedPos = to;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue