1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-03-12 22:14:42 +00:00
elsid 404940b6e0
Make sure proper number of cells is preloaded
Limit the number of exterior cells around a cell to be preloded based on current
and max cache size not just max cache size. Avoid doing break from inner loop
only.

Log when truncation happens but only once during process lifetime to warn a user.
2024-04-29 02:03:32 +02:00

1318 lines
52 KiB
C++

#include "scene.hpp"
#include <atomic>
#include <chrono>
#include <limits>
#include <BulletCollision/CollisionDispatch/btCollisionObject.h>
#include <components/debug/debuglog.hpp>
#include <components/detournavigator/agentbounds.hpp>
#include <components/detournavigator/debug.hpp>
#include <components/detournavigator/heightfieldshape.hpp>
#include <components/detournavigator/navigator.hpp>
#include <components/detournavigator/updateguard.hpp>
#include <components/esm/esmterrain.hpp>
#include <components/esm/records.hpp>
#include <components/esm3/loadcell.hpp>
#include <components/loadinglistener/loadinglistener.hpp>
#include <components/misc/convert.hpp>
#include <components/misc/resourcehelpers.hpp>
#include <components/resource/resourcesystem.hpp>
#include <components/resource/scenemanager.hpp>
#include <components/sceneutil/positionattitudetransform.hpp>
#include <components/settings/values.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/luamanager.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/world.hpp"
#include "../mwrender/landmanager.hpp"
#include "../mwrender/postprocessor.hpp"
#include "../mwrender/renderingmanager.hpp"
#include "../mwphysics/actor.hpp"
#include "../mwphysics/heightfield.hpp"
#include "../mwphysics/object.hpp"
#include "../mwphysics/physicssystem.hpp"
#include "cellpreloader.hpp"
#include "cellstore.hpp"
#include "cellvisitors.hpp"
#include "class.hpp"
#include "esmstore.hpp"
#include "localscripts.hpp"
#include "player.hpp"
#include "worldimp.hpp"
namespace
{
using MWWorld::RotationOrder;
osg::Quat makeActorOsgQuat(const ESM::Position& position)
{
return osg::Quat(position.rot[2], osg::Vec3(0, 0, -1));
}
osg::Quat makeInversedOrderObjectOsgQuat(const ESM::Position& position)
{
const float xr = position.rot[0];
const float yr = position.rot[1];
const float zr = position.rot[2];
return osg::Quat(xr, osg::Vec3(-1, 0, 0)) * osg::Quat(yr, osg::Vec3(0, -1, 0))
* osg::Quat(zr, osg::Vec3(0, 0, -1));
}
osg::Quat makeInverseNodeRotation(const MWWorld::Ptr& ptr)
{
const auto& pos = ptr.getRefData().getPosition();
return ptr.getClass().isActor() ? makeActorOsgQuat(pos) : makeInversedOrderObjectOsgQuat(pos);
}
osg::Quat makeDirectNodeRotation(const MWWorld::Ptr& ptr)
{
const auto& pos = ptr.getRefData().getPosition();
return ptr.getClass().isActor() ? makeActorOsgQuat(pos) : Misc::Convert::makeOsgQuat(pos);
}
osg::Quat makeNodeRotation(const MWWorld::Ptr& ptr, RotationOrder order)
{
if (order == RotationOrder::inverse)
return makeInverseNodeRotation(ptr);
return makeDirectNodeRotation(ptr);
}
void setNodeRotation(const MWWorld::Ptr& ptr, MWRender::RenderingManager& rendering, const osg::Quat& rotation)
{
if (ptr.getRefData().getBaseNode())
rendering.rotateObject(ptr, rotation);
}
std::string getModel(const MWWorld::Ptr& ptr)
{
if (Misc::ResourceHelpers::isHiddenMarker(ptr.getCellRef().getRefId()))
return {};
return ptr.getClass().getCorrectedModel(ptr);
}
// Null node meant to distinguish objects that aren't in the scene from paged objects
// TODO: find a more clever way to make paging exclusion more reliable?
static osg::ref_ptr<SceneUtil::PositionAttitudeTransform> pagedNode = new SceneUtil::PositionAttitudeTransform;
void addObject(const MWWorld::Ptr& ptr, const MWWorld::World& world, const std::vector<ESM::RefNum>& pagedRefs,
MWPhysics::PhysicsSystem& physics, MWRender::RenderingManager& rendering)
{
if (ptr.getRefData().getBaseNode() || physics.getActor(ptr))
{
Log(Debug::Warning) << "Warning: Tried to add " << ptr.getCellRef().getRefId() << " to the scene twice";
return;
}
std::string model = getModel(ptr);
const auto rotation = makeDirectNodeRotation(ptr);
ESM::RefNum refnum = ptr.getCellRef().getRefNum();
if (!refnum.hasContentFile() || !std::binary_search(pagedRefs.begin(), pagedRefs.end(), refnum))
ptr.getClass().insertObjectRendering(ptr, model, rendering);
else
ptr.getRefData().setBaseNode(pagedNode);
setNodeRotation(ptr, rendering, rotation);
if (ptr.getClass().useAnim())
MWBase::Environment::get().getMechanicsManager()->add(ptr);
if (ptr.getClass().isActor())
rendering.addWaterRippleEmitter(ptr);
// Restore effect particles
world.applyLoopingParticles(ptr);
if (!model.empty())
ptr.getClass().insertObject(ptr, model, rotation, physics);
MWBase::Environment::get().getLuaManager()->objectAddedToScene(ptr);
}
void addObject(const MWWorld::Ptr& ptr, const MWWorld::World& world, const MWPhysics::PhysicsSystem& physics,
DetourNavigator::Navigator& navigator, const DetourNavigator::UpdateGuard* navigatorUpdateGuard = nullptr)
{
if (const auto object = physics.getObject(ptr))
{
const DetourNavigator::ObjectTransform objectTransform{ ptr.getRefData().getPosition(),
ptr.getCellRef().getScale() };
if (ptr.getClass().isDoor() && !ptr.getCellRef().getTeleport())
{
btVector3 aabbMin;
btVector3 aabbMax;
object->getShapeInstance()->mCollisionShape->getAabb(btTransform::getIdentity(), aabbMin, aabbMax);
const auto center = (aabbMax + aabbMin) * 0.5f;
const auto distanceFromDoor = world.getMaxActivationDistance() * 0.5f;
const auto toPoint = aabbMax.x() - aabbMin.x() < aabbMax.y() - aabbMin.y()
? btVector3(distanceFromDoor, 0, 0)
: btVector3(0, distanceFromDoor, 0);
const auto transform = object->getTransform();
const btTransform closedDoorTransform(
Misc::Convert::makeBulletQuaternion(ptr.getCellRef().getPosition()), transform.getOrigin());
const auto start = Misc::Convert::toOsg(closedDoorTransform(center + toPoint));
const auto startPoint = physics.castRay(start, start - osg::Vec3f(0, 0, 1000), { ptr }, {},
MWPhysics::CollisionType_World | MWPhysics::CollisionType_HeightMap
| MWPhysics::CollisionType_Water);
const auto connectionStart = startPoint.mHit ? startPoint.mHitPos : start;
const auto end = Misc::Convert::toOsg(closedDoorTransform(center - toPoint));
const auto endPoint = physics.castRay(end, end - osg::Vec3f(0, 0, 1000), { ptr }, {},
MWPhysics::CollisionType_World | MWPhysics::CollisionType_HeightMap
| MWPhysics::CollisionType_Water);
const auto connectionEnd = endPoint.mHit ? endPoint.mHitPos : end;
navigator.addObject(DetourNavigator::ObjectId(object),
DetourNavigator::DoorShapes(
object->getShapeInstance(), objectTransform, connectionStart, connectionEnd),
transform, navigatorUpdateGuard);
}
else if (object->getShapeInstance()->mVisualCollisionType == Resource::VisualCollisionType::None)
{
navigator.addObject(DetourNavigator::ObjectId(object),
DetourNavigator::ObjectShapes(object->getShapeInstance(), objectTransform), object->getTransform(),
navigatorUpdateGuard);
}
}
else if (physics.getActor(ptr))
{
const DetourNavigator::AgentBounds agentBounds = world.getPathfindingAgentBounds(ptr);
if (!navigator.addAgent(agentBounds))
Log(Debug::Warning) << "Agent bounds are not supported by navigator for " << ptr.toString() << ": "
<< agentBounds;
}
}
struct InsertVisitor
{
MWWorld::CellStore& mCell;
Loading::Listener* mLoadingListener;
std::vector<MWWorld::Ptr> mToInsert;
InsertVisitor(MWWorld::CellStore& cell, Loading::Listener* loadingListener);
bool operator()(const MWWorld::Ptr& ptr);
template <class AddObject>
void insert(AddObject&& addObject);
};
InsertVisitor::InsertVisitor(MWWorld::CellStore& cell, Loading::Listener* loadingListener)
: mCell(cell)
, mLoadingListener(loadingListener)
{
}
bool InsertVisitor::operator()(const MWWorld::Ptr& ptr)
{
// do not insert directly as we can't modify the cell from within the visitation
// CreatureLevList::insertObjectRendering may spawn a new creature
mToInsert.push_back(ptr);
return true;
}
template <class AddObject>
void InsertVisitor::insert(AddObject&& addObject)
{
for (MWWorld::Ptr& ptr : mToInsert)
{
if (!ptr.mRef->isDeleted() && ptr.getRefData().isEnabled())
{
try
{
addObject(ptr);
}
catch (const std::exception& e)
{
Log(Debug::Error) << "failed to render '" << ptr.getCellRef().getRefId() << "': " << e.what();
}
}
if (mLoadingListener != nullptr)
mLoadingListener->increaseProgress(1);
}
}
int getCellPositionDistanceToOrigin(const std::pair<int, int>& cellPosition)
{
return std::abs(cellPosition.first) + std::abs(cellPosition.second);
}
bool isCellInCollection(ESM::ExteriorCellLocation cellIndex, MWWorld::Scene::CellStoreCollection& collection)
{
for (auto* cell : collection)
{
assert(cell->getCell()->isExterior());
if (cellIndex == cell->getCell()->getExteriorCellLocation())
return true;
}
return false;
}
bool removeFromSorted(ESM::RefNum refNum, std::vector<ESM::RefNum>& pagedRefs)
{
const auto it = std::lower_bound(pagedRefs.begin(), pagedRefs.end(), refNum);
if (it == pagedRefs.end() || *it != refNum)
return false;
pagedRefs.erase(it);
return true;
}
template <class Function>
void iterateOverCellsAround(int cellX, int cellY, int range, Function&& f)
{
for (int x = cellX - range, lastX = cellX + range; x <= lastX; ++x)
for (int y = cellY - range, lastY = cellY + range; y <= lastY; ++y)
f(x, y);
}
void sortCellsToLoad(int centerX, int centerY, std::vector<std::pair<int, int>>& cells)
{
const auto getDistanceToPlayerCell = [&](const std::pair<int, int>& cellPosition) {
return std::abs(cellPosition.first - centerX) + std::abs(cellPosition.second - centerY);
};
const auto getCellPositionPriority = [&](const std::pair<int, int>& cellPosition) {
return std::make_pair(getDistanceToPlayerCell(cellPosition), getCellPositionDistanceToOrigin(cellPosition));
};
std::sort(cells.begin(), cells.end(), [&](const std::pair<int, int>& lhs, const std::pair<int, int>& rhs) {
return getCellPositionPriority(lhs) < getCellPositionPriority(rhs);
});
}
}
namespace MWWorld
{
void Scene::removeFromPagedRefs(const Ptr& ptr)
{
ESM::RefNum refnum = ptr.getCellRef().getRefNum();
if (refnum.hasContentFile() && removeFromSorted(refnum, mPagedRefs))
{
if (!ptr.getRefData().getBaseNode())
return;
ptr.getClass().insertObjectRendering(ptr, getModel(ptr), mRendering);
setNodeRotation(ptr, mRendering, makeNodeRotation(ptr, RotationOrder::direct));
reloadTerrain();
}
}
bool Scene::isPagedRef(const Ptr& ptr) const
{
return ptr.getRefData().getBaseNode() == pagedNode.get();
}
void Scene::updateObjectRotation(const Ptr& ptr, RotationOrder order)
{
const auto rot = makeNodeRotation(ptr, order);
setNodeRotation(ptr, mRendering, rot);
mPhysics->updateRotation(ptr, rot);
}
void Scene::updateObjectScale(const Ptr& ptr)
{
float scale = ptr.getCellRef().getScale();
osg::Vec3f scaleVec(scale, scale, scale);
ptr.getClass().adjustScale(ptr, scaleVec, true);
mRendering.scaleObject(ptr, scaleVec);
mPhysics->updateScale(ptr);
}
void Scene::update(float duration)
{
if (mChangeCellGridRequest.has_value())
{
changeCellGrid(mChangeCellGridRequest->mPosition, mChangeCellGridRequest->mCellIndex,
mChangeCellGridRequest->mChangeEvent);
mChangeCellGridRequest.reset();
}
mPreloader->updateCache(mRendering.getReferenceTime());
preloadCells(duration);
}
void Scene::unloadCell(CellStore* cell, const DetourNavigator::UpdateGuard* navigatorUpdateGuard)
{
if (mActiveCells.find(cell) == mActiveCells.end())
return;
Log(Debug::Info) << "Unloading cell " << cell->getCell()->getDescription();
ListAndResetObjectsVisitor visitor;
cell->forEach(visitor);
for (const auto& ptr : visitor.mObjects)
{
if (const auto object = mPhysics->getObject(ptr))
{
if (object->getShapeInstance()->mVisualCollisionType == Resource::VisualCollisionType::None)
mNavigator.removeObject(DetourNavigator::ObjectId(object), navigatorUpdateGuard);
mPhysics->remove(ptr);
ptr.mRef->mData.mPhysicsPostponed = false;
}
else if (mPhysics->getActor(ptr))
{
mNavigator.removeAgent(mWorld.getPathfindingAgentBounds(ptr));
mRendering.removeActorPath(ptr);
mPhysics->remove(ptr);
}
MWBase::Environment::get().getLuaManager()->objectRemovedFromScene(ptr);
}
const auto cellX = cell->getCell()->getGridX();
const auto cellY = cell->getCell()->getGridY();
if (cell->getCell()->isExterior())
{
mNavigator.removeHeightfield(osg::Vec2i(cellX, cellY), navigatorUpdateGuard);
mPhysics->removeHeightField(cellX, cellY);
}
if (cell->getCell()->hasWater())
mNavigator.removeWater(osg::Vec2i(cellX, cellY), navigatorUpdateGuard);
ESM::visit(ESM::VisitOverload{
[&](const ESM::Cell& cell) {
if (const auto pathgrid = mWorld.getStore().get<ESM::Pathgrid>().search(cell))
mNavigator.removePathgrid(*pathgrid);
},
[&](const ESM4::Cell& cell) {},
},
*cell->getCell());
MWBase::Environment::get().getMechanicsManager()->drop(cell);
mRendering.removeCell(cell);
MWBase::Environment::get().getWindowManager()->removeCell(cell);
mWorld.getLocalScripts().clearCell(cell);
MWBase::Environment::get().getSoundManager()->stopSound(cell);
mActiveCells.erase(cell);
// Clean up any effects that may have been spawned while unloading all cells
if (mActiveCells.empty())
mRendering.notifyWorldSpaceChanged();
}
void Scene::loadCell(CellStore& cell, Loading::Listener* loadingListener, bool respawn, const osg::Vec3f& position,
const DetourNavigator::UpdateGuard* navigatorUpdateGuard)
{
using DetourNavigator::HeightfieldShape;
assert(mActiveCells.find(&cell) == mActiveCells.end());
mActiveCells.insert(&cell);
Log(Debug::Info) << "Loading cell " << cell.getCell()->getDescription();
const int cellX = cell.getCell()->getGridX();
const int cellY = cell.getCell()->getGridY();
const MWWorld::Cell& cellVariant = *cell.getCell();
ESM::RefId worldspace = cellVariant.getWorldSpace();
ESM::ExteriorCellLocation cellIndex(cellX, cellY, worldspace);
if (cellVariant.isExterior())
{
osg::ref_ptr<const ESMTerrain::LandObject> land = mRendering.getLandManager()->getLand(cellIndex);
const ESM::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : nullptr;
const int verts = ESM::getLandSize(worldspace);
const int worldsize = ESM::getCellSize(worldspace);
if (data)
{
mPhysics->addHeightField(data->getHeights().data(), cellX, cellY, worldsize, verts,
data->getMinHeight(), data->getMaxHeight(), land.get());
}
else if (!ESM::isEsm4Ext(worldspace))
{
static std::vector<float> defaultHeight;
defaultHeight.resize(verts * verts, ESM::Land::DEFAULT_HEIGHT);
mPhysics->addHeightField(defaultHeight.data(), cellX, cellY, worldsize, verts,
ESM::Land::DEFAULT_HEIGHT, ESM::Land::DEFAULT_HEIGHT, land.get());
}
if (const auto heightField = mPhysics->getHeightField(cellX, cellY))
{
const osg::Vec2i cellPosition(cellX, cellY);
const btVector3& origin = heightField->getCollisionObject()->getWorldTransform().getOrigin();
const osg::Vec3f shift(origin.x(), origin.y(), origin.z());
const HeightfieldShape shape = [&]() -> HeightfieldShape {
if (data == nullptr)
{
return DetourNavigator::HeightfieldPlane{ static_cast<float>(ESM::Land::DEFAULT_HEIGHT) };
}
else
{
DetourNavigator::HeightfieldSurface heights;
heights.mHeights = data->getHeights().data();
heights.mSize = static_cast<std::size_t>(data->getLandSize());
heights.mMinHeight = data->getMinHeight();
heights.mMaxHeight = data->getMaxHeight();
return heights;
}
}();
mNavigator.addHeightfield(cellPosition, worldsize, shape, navigatorUpdateGuard);
}
}
ESM::visit(ESM::VisitOverload{
[&](const ESM::Cell& cell) {
if (const auto pathgrid = mWorld.getStore().get<ESM::Pathgrid>().search(cell))
mNavigator.addPathgrid(cell, *pathgrid);
},
[&](const ESM4::Cell& cell) {},
},
*cell.getCell());
// register local scripts
// do this before insertCell, to make sure we don't add scripts from levelled creature spawning twice
mWorld.getLocalScripts().addCell(&cell);
if (respawn)
cell.respawn();
insertCell(cell, loadingListener, navigatorUpdateGuard);
mRendering.addCell(&cell);
MWBase::Environment::get().getWindowManager()->addCell(&cell);
bool waterEnabled = cellVariant.hasWater() || cell.isExterior();
float waterLevel = cell.getWaterLevel();
mRendering.setWaterEnabled(waterEnabled);
if (waterEnabled)
{
mPhysics->enableWater(waterLevel);
mRendering.setWaterHeight(waterLevel);
if (cellVariant.isExterior())
{
if (const auto heightField = mPhysics->getHeightField(cellX, cellY))
mNavigator.addWater(
osg::Vec2i(cellX, cellY), ESM::Land::REAL_SIZE, waterLevel, navigatorUpdateGuard);
}
else
{
mNavigator.addWater(
osg::Vec2i(cellX, cellY), std::numeric_limits<int>::max(), waterLevel, navigatorUpdateGuard);
}
}
else
mPhysics->disableWater();
if (!cell.isExterior() && !cellVariant.isQuasiExterior())
mRendering.configureAmbient(cellVariant);
mPreloader->notifyLoaded(&cell);
}
void Scene::clear()
{
auto navigatorUpdateGuard = mNavigator.makeUpdateGuard();
for (auto iter = mActiveCells.begin(); iter != mActiveCells.end();)
{
auto* cell = *iter++;
unloadCell(cell, navigatorUpdateGuard.get());
}
navigatorUpdateGuard.reset();
assert(mActiveCells.empty());
mCurrentCell = nullptr;
mPreloader->clear();
}
osg::Vec4i Scene::gridCenterToBounds(const osg::Vec2i& centerCell) const
{
return osg::Vec4i(centerCell.x() - mHalfGridSize, centerCell.y() - mHalfGridSize,
centerCell.x() + mHalfGridSize + 1, centerCell.y() + mHalfGridSize + 1);
}
osg::Vec2i Scene::getNewGridCenter(const osg::Vec3f& pos, const osg::Vec2i* currentGridCenter) const
{
ESM::RefId worldspace
= mCurrentCell ? mCurrentCell->getCell()->getWorldSpace() : ESM::Cell::sDefaultWorldspaceId;
if (currentGridCenter)
{
osg::Vec2 center = ESM::indexToPosition(
ESM::ExteriorCellLocation(currentGridCenter->x(), currentGridCenter->y(), worldspace), true);
float distance = std::max(std::abs(center.x() - pos.x()), std::abs(center.y() - pos.y()));
float cellSize = ESM::getCellSize(worldspace);
const float maxDistance = cellSize / 2 + mCellLoadingThreshold; // 1/2 cell size + threshold
if (distance <= maxDistance)
return *currentGridCenter;
}
ESM::ExteriorCellLocation cellPos = ESM::positionToExteriorCellLocation(pos.x(), pos.y(), worldspace);
return { cellPos.mX, cellPos.mY };
}
void Scene::playerMoved(const osg::Vec3f& pos)
{
if (!mCurrentCell || !mCurrentCell->isExterior())
return;
osg::Vec2i newCell = getNewGridCenter(pos, &mCurrentGridCenter);
if (newCell != mCurrentGridCenter)
requestChangeCellGrid(pos, newCell);
}
void Scene::requestChangeCellGrid(const osg::Vec3f& position, const osg::Vec2i& cell, bool changeEvent)
{
mChangeCellGridRequest = ChangeCellGridRequest{ position,
ESM::ExteriorCellLocation(cell.x(), cell.y(), mCurrentCell->getCell()->getWorldSpace()), changeEvent };
}
void Scene::changeCellGrid(const osg::Vec3f& pos, ESM::ExteriorCellLocation playerCellIndex, bool changeEvent)
{
mHalfGridSize
= isEsm4Ext(playerCellIndex.mWorldspace) ? Constants::ESM4CellGridRadius : Constants::CellGridRadius;
auto navigatorUpdateGuard = mNavigator.makeUpdateGuard();
int playerCellX = playerCellIndex.mX;
int playerCellY = playerCellIndex.mY;
for (auto iter = mActiveCells.begin(); iter != mActiveCells.end();)
{
auto* cell = *iter++;
if (cell->getCell()->isExterior() && cell->getCell()->getWorldSpace() == playerCellIndex.mWorldspace)
{
const auto dx = std::abs(playerCellX - cell->getCell()->getGridX());
const auto dy = std::abs(playerCellY - cell->getCell()->getGridY());
if (dx > mHalfGridSize || dy > mHalfGridSize)
unloadCell(cell, navigatorUpdateGuard.get());
}
else
unloadCell(cell, navigatorUpdateGuard.get());
}
mNavigator.setWorldspace(
mWorld.getWorldModel().getExterior(playerCellIndex).getCell()->getWorldSpace().serializeText(),
navigatorUpdateGuard.get());
mNavigator.updateBounds(pos, navigatorUpdateGuard.get());
mCurrentGridCenter = osg::Vec2i(playerCellX, playerCellY);
osg::Vec4i newGrid = gridCenterToBounds(mCurrentGridCenter);
mRendering.setActiveGrid(newGrid);
mRendering.enableTerrain(true, playerCellIndex.mWorldspace);
mPreloader->setTerrain(mRendering.getTerrain());
if (mRendering.pagingUnlockCache())
mPreloader->abortTerrainPreloadExcept(nullptr);
if (!mPreloader->isTerrainLoaded(std::make_pair(pos, newGrid), mRendering.getReferenceTime()))
preloadTerrain(pos, playerCellIndex.mWorldspace, true);
mPagedRefs.clear();
mRendering.getPagedRefnums(newGrid, mPagedRefs);
addPostponedPhysicsObjects();
std::size_t refsToLoad = 0;
std::vector<std::pair<int, int>> cellsPositionsToLoad;
iterateOverCellsAround(playerCellX, playerCellY, mHalfGridSize, [&](int x, int y) {
const ESM::ExteriorCellLocation location(x, y, playerCellIndex.mWorldspace);
if (isCellInCollection(location, mActiveCells))
return;
refsToLoad += mWorld.getWorldModel().getExterior(location).count();
cellsPositionsToLoad.emplace_back(x, y);
});
Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen();
Loading::ScopedLoad load(loadingListener);
loadingListener->setLabel("#{OMWEngine:LoadingExterior}");
loadingListener->setProgressRange(refsToLoad);
sortCellsToLoad(playerCellX, playerCellY, cellsPositionsToLoad);
for (const auto& [x, y] : cellsPositionsToLoad)
{
ESM::ExteriorCellLocation indexToLoad = { x, y, playerCellIndex.mWorldspace };
if (!isCellInCollection(indexToLoad, mActiveCells))
{
CellStore& cell = mWorld.getWorldModel().getExterior(indexToLoad);
loadCell(cell, loadingListener, changeEvent, pos, navigatorUpdateGuard.get());
}
}
mNavigator.update(pos, navigatorUpdateGuard.get());
navigatorUpdateGuard.reset();
CellStore& current = mWorld.getWorldModel().getExterior(playerCellIndex);
MWBase::Environment::get().getWindowManager()->changeCell(&current);
if (changeEvent)
mCellChanged = true;
mCellLoaded = true;
}
void Scene::addPostponedPhysicsObjects()
{
for (const auto& cell : mActiveCells)
{
cell->forEach([&](const MWWorld::Ptr& ptr) {
if (ptr.mRef->mData.mPhysicsPostponed)
{
ptr.mRef->mData.mPhysicsPostponed = false;
if (ptr.mRef->mData.isEnabled() && ptr.mRef->mRef.getCount() > 0)
{
std::string model = getModel(ptr);
if (!model.empty())
{
const auto rotation = makeNodeRotation(ptr, RotationOrder::direct);
ptr.getClass().insertObjectPhysics(ptr, model, rotation, *mPhysics);
}
}
}
return true;
});
}
}
void Scene::testExteriorCells()
{
// Note: temporary disable ICO to decrease memory usage
mRendering.getResourceSystem()->getSceneManager()->setIncrementalCompileOperation(nullptr);
mRendering.getResourceSystem()->setExpiryDelay(1.f);
const MWWorld::Store<ESM::Cell>& cells = mWorld.getStore().get<ESM::Cell>();
Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen();
Loading::ScopedLoad load(loadingListener);
loadingListener->setProgressRange(cells.getExtSize());
MWWorld::Store<ESM::Cell>::iterator it = cells.extBegin();
int i = 1;
auto navigatorUpdateGuard = mNavigator.makeUpdateGuard();
for (; it != cells.extEnd(); ++it)
{
loadingListener->setLabel("#{OMWEngine:TestingExteriorCells} (" + std::to_string(i) + "/"
+ std::to_string(cells.getExtSize()) + ")...");
CellStore& cell = mWorld.getWorldModel().getExterior(
ESM::ExteriorCellLocation(it->mData.mX, it->mData.mY, ESM::Cell::sDefaultWorldspaceId));
mNavigator.setWorldspace(cell.getCell()->getWorldSpace().serializeText(), navigatorUpdateGuard.get());
const osg::Vec3f position
= osg::Vec3f(it->mData.mX + 0.5f, it->mData.mY + 0.5f, 0) * Constants::CellSizeInUnits;
mNavigator.updateBounds(position, navigatorUpdateGuard.get());
loadCell(cell, nullptr, false, position, navigatorUpdateGuard.get());
mNavigator.update(position, navigatorUpdateGuard.get());
navigatorUpdateGuard.reset();
mNavigator.wait(DetourNavigator::WaitConditionType::requiredTilesPresent, nullptr);
navigatorUpdateGuard = mNavigator.makeUpdateGuard();
auto iter = mActiveCells.begin();
while (iter != mActiveCells.end())
{
if (it->isExterior() && it->mData.mX == (*iter)->getCell()->getGridX()
&& it->mData.mY == (*iter)->getCell()->getGridY())
{
unloadCell(*iter, navigatorUpdateGuard.get());
break;
}
++iter;
}
mRendering.getResourceSystem()->updateCache(mRendering.getReferenceTime());
loadingListener->increaseProgress(1);
i++;
}
mRendering.getResourceSystem()->getSceneManager()->setIncrementalCompileOperation(
mRendering.getIncrementalCompileOperation());
mRendering.getResourceSystem()->setExpiryDelay(Settings::cells().mCacheExpiryDelay);
}
void Scene::testInteriorCells()
{
// Note: temporary disable ICO to decrease memory usage
mRendering.getResourceSystem()->getSceneManager()->setIncrementalCompileOperation(nullptr);
mRendering.getResourceSystem()->setExpiryDelay(1.f);
const MWWorld::Store<ESM::Cell>& cells = mWorld.getStore().get<ESM::Cell>();
Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen();
Loading::ScopedLoad load(loadingListener);
loadingListener->setProgressRange(cells.getIntSize());
int i = 1;
MWWorld::Store<ESM::Cell>::iterator it = cells.intBegin();
auto navigatorUpdateGuard = mNavigator.makeUpdateGuard();
for (; it != cells.intEnd(); ++it)
{
loadingListener->setLabel("#{OMWEngine:TestingInteriorCells} (" + std::to_string(i) + "/"
+ std::to_string(cells.getIntSize()) + ")...");
CellStore& cell = mWorld.getWorldModel().getInterior(it->mName);
mNavigator.setWorldspace(cell.getCell()->getWorldSpace().serializeText(), navigatorUpdateGuard.get());
ESM::Position position;
mWorld.findInteriorPosition(it->mName, position);
mNavigator.updateBounds(position.asVec3(), navigatorUpdateGuard.get());
loadCell(cell, nullptr, false, position.asVec3(), navigatorUpdateGuard.get());
mNavigator.update(position.asVec3(), navigatorUpdateGuard.get());
navigatorUpdateGuard.reset();
mNavigator.wait(DetourNavigator::WaitConditionType::requiredTilesPresent, nullptr);
navigatorUpdateGuard = mNavigator.makeUpdateGuard();
auto iter = mActiveCells.begin();
while (iter != mActiveCells.end())
{
assert(!(*iter)->getCell()->isExterior());
if (it->mName == (*iter)->getCell()->getNameId())
{
unloadCell(*iter, navigatorUpdateGuard.get());
break;
}
++iter;
}
mRendering.getResourceSystem()->updateCache(mRendering.getReferenceTime());
loadingListener->increaseProgress(1);
i++;
}
mRendering.getResourceSystem()->getSceneManager()->setIncrementalCompileOperation(
mRendering.getIncrementalCompileOperation());
mRendering.getResourceSystem()->setExpiryDelay(Settings::cells().mCacheExpiryDelay);
}
void Scene::changePlayerCell(CellStore& cell, const ESM::Position& pos, bool adjustPlayerPos)
{
mHalfGridSize = cell.getCell()->isEsm4() ? Constants::ESM4CellGridRadius : Constants::CellGridRadius;
mCurrentCell = &cell;
mRendering.enableTerrain(cell.isExterior(), cell.getCell()->getWorldSpace());
MWWorld::Ptr old = mWorld.getPlayerPtr();
mWorld.getPlayer().setCell(&cell);
MWWorld::Ptr player = mWorld.getPlayerPtr();
mRendering.updatePlayerPtr(player);
// The player is loaded before the scene and by default it is grounded, with the scene fully loaded,
// we validate and correct this. Only run once, during initial cell load.
if (old.mCell == &cell)
mPhysics->traceDown(player, player.getRefData().getPosition().asVec3(), 10.f);
if (adjustPlayerPos)
{
mWorld.moveObject(player, pos.asVec3());
mWorld.rotateObject(player, pos.asRotationVec3());
player.getClass().adjustPosition(player, true);
}
MWBase::Environment::get().getMechanicsManager()->updateCell(old, player);
MWBase::Environment::get().getWindowManager()->watchActor(player);
mPhysics->updatePtr(old, player);
mWorld.adjustSky();
mLastPlayerPos = player.getRefData().getPosition().asVec3();
}
Scene::Scene(MWWorld::World& world, MWRender::RenderingManager& rendering, MWPhysics::PhysicsSystem* physics,
DetourNavigator::Navigator& navigator)
: mCurrentCell(nullptr)
, mCellChanged(false)
, mWorld(world)
, mPhysics(physics)
, mRendering(rendering)
, mNavigator(navigator)
, mCellLoadingThreshold(1024.f)
, mPreloadDistance(Settings::cells().mPreloadDistance)
, mPreloadEnabled(Settings::cells().mPreloadEnabled)
, mPreloadExteriorGrid(Settings::cells().mPreloadExteriorGrid)
, mPreloadDoors(Settings::cells().mPreloadDoors)
, mPreloadFastTravel(Settings::cells().mPreloadFastTravel)
, mPredictionTime(Settings::cells().mPredictionTime)
{
mPreloader = std::make_unique<CellPreloader>(rendering.getResourceSystem(), physics->getShapeManager(),
rendering.getTerrain(), rendering.getLandManager());
mPreloader->setWorkQueue(mRendering.getWorkQueue());
mPreloader->setExpiryDelay(Settings::cells().mPreloadCellExpiryDelay);
mPreloader->setMinCacheSize(Settings::cells().mPreloadCellCacheMin);
mPreloader->setMaxCacheSize(Settings::cells().mPreloadCellCacheMax);
mPreloader->setPreloadInstances(Settings::cells().mPreloadInstances);
}
Scene::~Scene()
{
for (const osg::ref_ptr<SceneUtil::WorkItem>& v : mWorkItems)
v->abort();
for (const osg::ref_ptr<SceneUtil::WorkItem>& v : mWorkItems)
v->waitTillDone();
}
bool Scene::hasCellChanged() const
{
return mCellChanged;
}
const Scene::CellStoreCollection& Scene::getActiveCells() const
{
return mActiveCells;
}
void Scene::changeToInteriorCell(
std::string_view cellName, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent)
{
CellStore& cell = mWorld.getWorldModel().getInterior(cellName);
bool useFading = (mCurrentCell != nullptr);
if (useFading)
MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.5);
Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen();
loadingListener->setLabel("#{OMWEngine:LoadingInterior}");
Loading::ScopedLoad load(loadingListener);
if (mCurrentCell == &cell)
{
mWorld.moveObject(mWorld.getPlayerPtr(), position.asVec3());
mWorld.rotateObject(mWorld.getPlayerPtr(), position.asRotationVec3());
if (adjustPlayerPos)
mWorld.getPlayerPtr().getClass().adjustPosition(mWorld.getPlayerPtr(), true);
MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.5);
return;
}
Log(Debug::Info) << "Changing to interior";
auto navigatorUpdateGuard = mNavigator.makeUpdateGuard();
// unload
for (auto iter = mActiveCells.begin(); iter != mActiveCells.end();)
{
auto* cellToUnload = *iter++;
unloadCell(cellToUnload, navigatorUpdateGuard.get());
}
assert(mActiveCells.empty());
loadingListener->setProgressRange(cell.count());
mNavigator.setWorldspace(cell.getCell()->getWorldSpace().serializeText(), navigatorUpdateGuard.get());
mNavigator.updateBounds(position.asVec3(), navigatorUpdateGuard.get());
// Load cell.
mPagedRefs.clear();
loadCell(cell, loadingListener, changeEvent, position.asVec3(), navigatorUpdateGuard.get());
navigatorUpdateGuard.reset();
changePlayerCell(cell, position, adjustPlayerPos);
// adjust fog
mRendering.configureFog(*mCurrentCell->getCell());
// Sky system
mWorld.adjustSky();
if (changeEvent)
mCellChanged = true;
mCellLoaded = true;
if (useFading)
MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.5);
MWBase::Environment::get().getWindowManager()->changeCell(mCurrentCell);
MWBase::Environment::get().getWorld()->getPostProcessor()->setExteriorFlag(cell.getCell()->isQuasiExterior());
}
void Scene::changeToExteriorCell(
const ESM::RefId& extCellId, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent)
{
if (changeEvent)
MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.5);
CellStore& current = mWorld.getWorldModel().getCell(extCellId);
const osg::Vec2i cellIndex(current.getCell()->getGridX(), current.getCell()->getGridY());
changeCellGrid(position.asVec3(),
ESM::ExteriorCellLocation(cellIndex.x(), cellIndex.y(), current.getCell()->getWorldSpace()), changeEvent);
changePlayerCell(current, position, adjustPlayerPos);
if (changeEvent)
MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.5);
MWBase::Environment::get().getWorld()->getPostProcessor()->setExteriorFlag(true);
}
CellStore* Scene::getCurrentCell()
{
return mCurrentCell;
}
void Scene::markCellAsUnchanged()
{
mCellChanged = false;
}
void Scene::insertCell(
CellStore& cell, Loading::Listener* loadingListener, const DetourNavigator::UpdateGuard* navigatorUpdateGuard)
{
InsertVisitor insertVisitor(cell, loadingListener);
cell.forEach(insertVisitor);
insertVisitor.insert(
[&](const MWWorld::Ptr& ptr) { addObject(ptr, mWorld, mPagedRefs, *mPhysics, mRendering); });
insertVisitor.insert(
[&](const MWWorld::Ptr& ptr) { addObject(ptr, mWorld, *mPhysics, mNavigator, navigatorUpdateGuard); });
}
void Scene::addObjectToScene(const Ptr& ptr)
{
try
{
addObject(ptr, mWorld, mPagedRefs, *mPhysics, mRendering);
addObject(ptr, mWorld, *mPhysics, mNavigator);
mWorld.scaleObject(ptr, ptr.getCellRef().getScale());
}
catch (std::exception& e)
{
Log(Debug::Error) << "failed to render '" << ptr.getCellRef().getRefId() << "': " << e.what();
}
}
void Scene::removeObjectFromScene(const Ptr& ptr, bool keepActive)
{
MWBase::Environment::get().getMechanicsManager()->remove(ptr, keepActive);
// You'd expect the sounds attached to the object to be stopped here
// because the object is nowhere to be heard, but in Morrowind, they're not.
// They're still stopped when the cell is unloaded
// or if the player moves away far from the object's position.
// Todd Howard, Who art in Bethesda, hallowed be Thy name.
MWBase::Environment::get().getLuaManager()->objectRemovedFromScene(ptr);
if (const auto object = mPhysics->getObject(ptr))
{
if (object->getShapeInstance()->mVisualCollisionType == Resource::VisualCollisionType::None)
mNavigator.removeObject(DetourNavigator::ObjectId(object), nullptr);
}
else if (mPhysics->getActor(ptr))
{
mNavigator.removeAgent(mWorld.getPathfindingAgentBounds(ptr));
}
mPhysics->remove(ptr);
mRendering.removeObject(ptr);
if (ptr.getClass().isActor())
mRendering.removeWaterRippleEmitter(ptr);
ptr.getRefData().setBaseNode(nullptr);
}
bool Scene::isCellActive(const CellStore& cell)
{
return mActiveCells.contains(&cell);
}
Ptr Scene::searchPtrViaActorId(int actorId)
{
for (CellStoreCollection::const_iterator iter(mActiveCells.begin()); iter != mActiveCells.end(); ++iter)
{
Ptr ptr = (*iter)->searchViaActorId(actorId);
if (!ptr.isEmpty())
return ptr;
}
return Ptr();
}
class PreloadMeshItem : public SceneUtil::WorkItem
{
public:
PreloadMeshItem(const std::string& mesh, Resource::SceneManager* sceneManager)
: mMesh(mesh)
, mSceneManager(sceneManager)
{
}
void doWork() override
{
if (mAborted)
return;
try
{
mSceneManager->getTemplate(mMesh);
}
catch (std::exception&)
{
}
}
void abort() override { mAborted = true; }
private:
std::string mMesh;
Resource::SceneManager* mSceneManager;
std::atomic_bool mAborted{ false };
};
void Scene::preload(const std::string& mesh, bool useAnim)
{
std::string mesh_ = mesh;
if (useAnim)
mesh_ = Misc::ResourceHelpers::correctActorModelPath(mesh_, mRendering.getResourceSystem()->getVFS());
if (!mRendering.getResourceSystem()->getSceneManager()->checkLoaded(mesh_, mRendering.getReferenceTime()))
{
osg::ref_ptr<PreloadMeshItem> item(
new PreloadMeshItem(mesh_, mRendering.getResourceSystem()->getSceneManager()));
mRendering.getWorkQueue()->addWorkItem(item);
const auto isDone = [](const osg::ref_ptr<SceneUtil::WorkItem>& v) { return v->isDone(); };
mWorkItems.erase(std::remove_if(mWorkItems.begin(), mWorkItems.end(), isDone), mWorkItems.end());
mWorkItems.emplace_back(std::move(item));
}
}
void Scene::preloadCells(float dt)
{
if (dt <= 1e-06)
return;
std::vector<PositionCellGrid> exteriorPositions;
const MWWorld::ConstPtr player = mWorld.getPlayerPtr();
osg::Vec3f playerPos = player.getRefData().getPosition().asVec3();
osg::Vec3f moved = playerPos - mLastPlayerPos;
osg::Vec3f predictedPos = playerPos + moved / dt * mPredictionTime;
if (mCurrentCell->isExterior())
exteriorPositions.emplace_back(
predictedPos, gridCenterToBounds(getNewGridCenter(predictedPos, &mCurrentGridCenter)));
mLastPlayerPos = playerPos;
if (mPreloadEnabled)
{
if (mPreloadDoors)
preloadTeleportDoorDestinations(playerPos, predictedPos, exteriorPositions);
if (mPreloadExteriorGrid)
preloadExteriorGrid(playerPos, predictedPos);
if (mPreloadFastTravel)
preloadFastTravelDestinations(playerPos, predictedPos, exteriorPositions);
}
mPreloader->setTerrainPreloadPositions(exteriorPositions);
}
void Scene::preloadTeleportDoorDestinations(
const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos, std::vector<PositionCellGrid>& exteriorPositions)
{
std::vector<MWWorld::ConstPtr> teleportDoors;
for (const MWWorld::CellStore* cellStore : mActiveCells)
{
typedef MWWorld::CellRefList<ESM::Door>::List DoorList;
const DoorList& doors = cellStore->getReadOnlyDoors().mList;
for (auto& door : doors)
{
if (!door.mRef.getTeleport())
{
continue;
}
teleportDoors.emplace_back(&door, cellStore);
}
}
for (const MWWorld::ConstPtr& door : teleportDoors)
{
float sqrDistToPlayer = (playerPos - door.getRefData().getPosition().asVec3()).length2();
sqrDistToPlayer
= std::min(sqrDistToPlayer, (predictedPos - door.getRefData().getPosition().asVec3()).length2());
if (sqrDistToPlayer < mPreloadDistance * mPreloadDistance)
{
try
{
preloadCellWithSurroundings(mWorld.getWorldModel().getCell(door.getCellRef().getDestCell()));
}
catch (std::exception&)
{
// ignore error for now, would spam the log too much
}
}
}
}
void Scene::preloadExteriorGrid(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos)
{
if (!mWorld.isCellExterior())
return;
int halfGridSizePlusOne = mHalfGridSize + 1;
int cellX, cellY;
cellX = mCurrentGridCenter.x();
cellY = mCurrentGridCenter.y();
ESM::RefId extWorldspace = mWorld.getCurrentWorldspace();
float cellSize = ESM::getCellSize(extWorldspace);
for (int dx = -halfGridSizePlusOne; dx <= halfGridSizePlusOne; ++dx)
{
for (int dy = -halfGridSizePlusOne; dy <= halfGridSizePlusOne; ++dy)
{
if (dy != halfGridSizePlusOne && dy != -halfGridSizePlusOne && dx != halfGridSizePlusOne
&& dx != -halfGridSizePlusOne)
continue; // only care about the outer (not yet loaded) part of the grid
ESM::ExteriorCellLocation cellIndex(cellX + dx, cellY + dy, extWorldspace);
osg::Vec2 thisCellCenter = ESM::indexToPosition(cellIndex, true);
float dist = std::max(
std::abs(thisCellCenter.x() - playerPos.x()), std::abs(thisCellCenter.y() - playerPos.y()));
dist = std::min(dist,
std::max(std::abs(thisCellCenter.x() - predictedPos.x()),
std::abs(thisCellCenter.y() - predictedPos.y())));
float loadDist = cellSize / 2 + cellSize - mCellLoadingThreshold + mPreloadDistance;
if (dist < loadDist)
preloadCell(mWorld.getWorldModel().getExterior(cellIndex));
}
}
}
void Scene::preloadCellWithSurroundings(CellStore& cell)
{
if (!cell.isExterior())
{
mPreloader->preload(cell, mRendering.getReferenceTime());
return;
}
const int cellX = cell.getCell()->getGridX();
const int cellY = cell.getCell()->getGridY();
std::vector<std::pair<int, int>> cells;
const std::size_t gridSize = static_cast<std::size_t>(2 * mHalfGridSize + 1);
cells.reserve(gridSize * gridSize);
iterateOverCellsAround(cellX, cellY, mHalfGridSize, [&](int x, int y) { cells.emplace_back(x, y); });
sortCellsToLoad(cellX, cellY, cells);
const std::size_t leftCapacity = mPreloader->getMaxCacheSize() - mPreloader->getCacheSize();
if (cells.size() > leftCapacity)
{
static bool logged = [&] {
Log(Debug::Warning) << "Not enough cell preloader cache capacity to preload exterior cells, consider "
"increasing \"preload cell cache max\" up to "
<< (mPreloader->getCacheSize() + cells.size());
return true;
}();
(void)logged;
cells.resize(leftCapacity);
}
const ESM::RefId worldspace = cell.getCell()->getWorldSpace();
for (const auto& [x, y] : cells)
mPreloader->preload(mWorld.getWorldModel().getExterior(ESM::ExteriorCellLocation(x, y, worldspace)),
mRendering.getReferenceTime());
}
void Scene::preloadCell(CellStore& cell)
{
mPreloader->preload(cell, mRendering.getReferenceTime());
}
void Scene::preloadTerrain(const osg::Vec3f& pos, ESM::RefId worldspace, bool sync)
{
ESM::ExteriorCellLocation cellPos = ESM::positionToExteriorCellLocation(pos.x(), pos.y(), worldspace);
std::vector<PositionCellGrid> vec;
vec.emplace_back(pos, gridCenterToBounds({ cellPos.mX, cellPos.mY }));
mPreloader->abortTerrainPreloadExcept(vec.data());
mPreloader->setTerrainPreloadPositions(vec);
if (!sync)
return;
Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen();
Loading::ScopedLoad load(loadingListener);
loadingListener->setLabel("#{OMWEngine:InitializingData}");
mPreloader->syncTerrainLoad(*loadingListener);
}
void Scene::reloadTerrain()
{
mPreloader->setTerrainPreloadPositions(std::vector<PositionCellGrid>());
}
struct ListFastTravelDestinationsVisitor
{
ListFastTravelDestinationsVisitor(float preloadDist, const osg::Vec3f& playerPos)
: mPreloadDist(preloadDist)
, mPlayerPos(playerPos)
{
}
bool operator()(const MWWorld::Ptr& ptr)
{
if ((ptr.getRefData().getPosition().asVec3() - mPlayerPos).length2() > mPreloadDist * mPreloadDist)
return true;
if (ptr.getClass().isNpc())
{
const std::vector<ESM::Transport::Dest>& transport = ptr.get<ESM::NPC>()->mBase->mTransport.mList;
mList.insert(mList.begin(), transport.begin(), transport.end());
}
else
{
const std::vector<ESM::Transport::Dest>& transport = ptr.get<ESM::Creature>()->mBase->mTransport.mList;
mList.insert(mList.begin(), transport.begin(), transport.end());
}
return true;
}
float mPreloadDist;
osg::Vec3f mPlayerPos;
std::vector<ESM::Transport::Dest> mList;
};
void Scene::preloadFastTravelDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& /*predictedPos*/,
std::vector<PositionCellGrid>& exteriorPositions) // ignore predictedPos here since opening dialogue with
// travel service takes extra time
{
const MWWorld::ConstPtr player = mWorld.getPlayerPtr();
ListFastTravelDestinationsVisitor listVisitor(mPreloadDistance, player.getRefData().getPosition().asVec3());
ESM::RefId extWorldspace = mWorld.getCurrentWorldspace();
for (MWWorld::CellStore* cellStore : mActiveCells)
{
cellStore->forEachType<ESM::NPC>(listVisitor);
cellStore->forEachType<ESM::Creature>(listVisitor);
}
for (ESM::Transport::Dest& dest : listVisitor.mList)
{
if (!dest.mCellName.empty())
preloadCell(mWorld.getWorldModel().getInterior(dest.mCellName));
else
{
osg::Vec3f pos = dest.mPos.asVec3();
const ESM::ExteriorCellLocation cellIndex
= ESM::positionToExteriorCellLocation(pos.x(), pos.y(), extWorldspace);
preloadCellWithSurroundings(mWorld.getWorldModel().getExterior(cellIndex));
exteriorPositions.emplace_back(pos, gridCenterToBounds(getNewGridCenter(pos)));
}
}
}
void Scene::reportStats(unsigned int frameNumber, osg::Stats& stats) const
{
mPreloader->reportStats(frameNumber, stats);
}
}