diff --git a/src/rendering/m2_renderer.cpp b/src/rendering/m2_renderer.cpp index 0ca3f940..cbe26302 100644 --- a/src/rendering/m2_renderer.cpp +++ b/src/rendering/m2_renderer.cpp @@ -1036,10 +1036,9 @@ bool M2Renderer::loadModel(const pipeline::M2Model& model, uint32_t modelId) { (lowerName.find("trunk") != std::string::npos) || (lowerName.find("stump") != std::string::npos) || (lowerName.find("log") != std::string::npos); - // Only large trees (canopy > 20 model units wide) get trunk collision. - // Small/mid trees are walkthrough to avoid getting stuck between them. - // Only large trees get trunk collision; all smaller trees are walkthrough. - bool treeWithTrunk = treeLike && !hardTreePart && !foliageName && horiz > 40.0f; + // Trees with visible trunks get collision. Threshold: canopy wider than 6 + // model units AND taller than 4 units (filters out small bushes/saplings). + bool treeWithTrunk = treeLike && !hardTreePart && !foliageName && horiz > 6.0f && vert > 4.0f; bool softTree = treeLike && !hardTreePart && !treeWithTrunk; bool forceSolidCurb = gpuModel.collisionSteppedLowPlatform || knownStormwindPlanter || likelyCurbName || gpuModel.collisionPlanter; bool narrowVerticalName = diff --git a/src/rendering/terrain_manager.cpp b/src/rendering/terrain_manager.cpp index f15541ea..89a77d29 100644 --- a/src/rendering/terrain_manager.cpp +++ b/src/rendering/terrain_manager.cpp @@ -199,13 +199,25 @@ void TerrainManager::update(const Camera& camera, float deltaTime) { currentTile = newTile; } - // Stream tiles if we've moved significantly or initial load + // Stream tiles when player crosses a tile boundary if (newTile.x != lastStreamTile.x || newTile.y != lastStreamTile.y) { LOG_DEBUG("Streaming: cam=(", camPos.x, ",", camPos.y, ",", camPos.z, ") tile=[", newTile.x, ",", newTile.y, "] loaded=", loadedTiles.size()); streamTiles(); lastStreamTile = newTile; + } else { + // Proactive loading: when workers are idle, re-check for unloaded tiles + // within range. This catches tiles that weren't queued on the initial + // streamTiles pass (e.g. cache eviction, late-arriving ADT availability). + bool workersIdle; + { + std::lock_guard lock(queueMutex); + workersIdle = loadQueue.empty(); + } + if (workersIdle) { + streamTiles(); + } } } @@ -830,11 +842,19 @@ bool TerrainManager::advanceFinalization(FinalizingTile& ft) { } case FinalizationPhase::M2_MODELS: { - // Upload multiple M2 models per call (batched GPU uploads) + // Upload multiple M2 models per call (batched GPU uploads). + // When no more tiles are queued for background parsing, increase the + // per-frame budget so idle workers don't waste time waiting for the + // main thread to trickle-upload models. if (m2Renderer && ft.m2ModelIndex < pending->m2Models.size()) { // Set pre-decoded BLP cache so loadTexture() skips main-thread BLP decode m2Renderer->setPredecodedBLPCache(&pending->preloadedM2Textures); - constexpr size_t kModelsPerStep = 4; + bool workersIdle; + { + std::lock_guard lk(queueMutex); + workersIdle = loadQueue.empty() && readyQueue.empty(); + } + const size_t kModelsPerStep = workersIdle ? 16 : 4; size_t uploaded = 0; while (ft.m2ModelIndex < pending->m2Models.size() && uploaded < kModelsPerStep) { auto& m2Ready = pending->m2Models[ft.m2ModelIndex]; @@ -896,7 +916,12 @@ bool TerrainManager::advanceFinalization(FinalizingTile& ft) { wmoRenderer->setPredecodedBLPCache(&pending->preloadedWMOTextures); wmoRenderer->setDeferNormalMaps(true); - constexpr size_t kWmosPerStep = 1; + bool wmoWorkersIdle; + { + std::lock_guard lk(queueMutex); + wmoWorkersIdle = loadQueue.empty() && readyQueue.empty(); + } + const size_t kWmosPerStep = wmoWorkersIdle ? 4 : 1; size_t uploaded = 0; while (ft.wmoModelIndex < pending->wmoModels.size() && uploaded < kWmosPerStep) { auto& wmoReady = pending->wmoModels[ft.wmoModelIndex];