mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-15 19:19:55 +00:00
9d17cece3a
mCanWaterWalk was set to false and updated during next frame's simulation mOnGround is set to true but then was updated as part of the scene loading logic.
1201 lines
46 KiB
C++
1201 lines
46 KiB
C++
#include "scene.hpp"
|
|
|
|
#include <limits>
|
|
#include <chrono>
|
|
#include <thread>
|
|
|
|
#include <BulletCollision/CollisionDispatch/btCollisionObject.h>
|
|
#include <BulletCollision/CollisionShapes/btCompoundShape.h>
|
|
|
|
#include <components/debug/debuglog.hpp>
|
|
#include <components/loadinglistener/loadinglistener.hpp>
|
|
#include <components/misc/resourcehelpers.hpp>
|
|
#include <components/settings/settings.hpp>
|
|
#include <components/resource/resourcesystem.hpp>
|
|
#include <components/resource/scenemanager.hpp>
|
|
#include <components/resource/bulletshape.hpp>
|
|
#include <components/sceneutil/unrefqueue.hpp>
|
|
#include <components/sceneutil/positionattitudetransform.hpp>
|
|
#include <components/detournavigator/navigator.hpp>
|
|
#include <components/detournavigator/debug.hpp>
|
|
#include <components/misc/convert.hpp>
|
|
|
|
#include "../mwbase/environment.hpp"
|
|
#include "../mwbase/world.hpp"
|
|
#include "../mwbase/soundmanager.hpp"
|
|
#include "../mwbase/mechanicsmanager.hpp"
|
|
#include "../mwbase/windowmanager.hpp"
|
|
|
|
#include "../mwrender/renderingmanager.hpp"
|
|
#include "../mwrender/landmanager.hpp"
|
|
|
|
#include "../mwphysics/physicssystem.hpp"
|
|
#include "../mwphysics/actor.hpp"
|
|
#include "../mwphysics/object.hpp"
|
|
#include "../mwphysics/heightfield.hpp"
|
|
|
|
#include "player.hpp"
|
|
#include "localscripts.hpp"
|
|
#include "esmstore.hpp"
|
|
#include "class.hpp"
|
|
#include "cellvisitors.hpp"
|
|
#include "cellstore.hpp"
|
|
#include "cellpreloader.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 makeObjectOsgQuat(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(zr, osg::Vec3(0, 0, -1))
|
|
* osg::Quat(yr, osg::Vec3(0, -1, 0))
|
|
* osg::Quat(xr, osg::Vec3(-1, 0, 0));
|
|
}
|
|
|
|
void setNodeRotation(const MWWorld::Ptr& ptr, MWRender::RenderingManager& rendering, RotationOrder order)
|
|
{
|
|
if (!ptr.getRefData().getBaseNode())
|
|
return;
|
|
|
|
rendering.rotateObject(ptr,
|
|
ptr.getClass().isActor()
|
|
? makeActorOsgQuat(ptr.getRefData().getPosition())
|
|
: (order == RotationOrder::inverse
|
|
? makeInversedOrderObjectOsgQuat(ptr.getRefData().getPosition())
|
|
: makeObjectOsgQuat(ptr.getRefData().getPosition()))
|
|
);
|
|
}
|
|
|
|
std::string getModel(const MWWorld::Ptr &ptr, const VFS::Manager *vfs)
|
|
{
|
|
bool useAnim = ptr.getClass().useAnim();
|
|
std::string model = ptr.getClass().getModel(ptr);
|
|
if (useAnim)
|
|
model = Misc::ResourceHelpers::correctActorModelPath(model, vfs);
|
|
|
|
const std::string &id = ptr.getCellRef().getRefId();
|
|
if (id == "prisonmarker" || id == "divinemarker" || id == "templemarker" || id == "northmarker")
|
|
model = ""; // marker objects that have a hardcoded function in the game logic, should be hidden from the player
|
|
return model;
|
|
}
|
|
|
|
void addObject(const MWWorld::Ptr& ptr, MWPhysics::PhysicsSystem& physics,
|
|
MWRender::RenderingManager& rendering, std::set<ESM::RefNum>& pagedRefs)
|
|
{
|
|
if (ptr.getRefData().getBaseNode() || physics.getActor(ptr))
|
|
{
|
|
Log(Debug::Warning) << "Warning: Tried to add " << ptr.getCellRef().getRefId() << " to the scene twice";
|
|
return;
|
|
}
|
|
|
|
bool useAnim = ptr.getClass().useAnim();
|
|
std::string model = getModel(ptr, rendering.getResourceSystem()->getVFS());
|
|
|
|
const ESM::RefNum& refnum = ptr.getCellRef().getRefNum();
|
|
if (!refnum.hasContentFile() || pagedRefs.find(refnum) == pagedRefs.end())
|
|
ptr.getClass().insertObjectRendering(ptr, model, rendering);
|
|
else
|
|
ptr.getRefData().setBaseNode(new SceneUtil::PositionAttitudeTransform); // FIXME remove this when physics code is fixed not to depend on basenode
|
|
setNodeRotation(ptr, rendering, RotationOrder::direct);
|
|
|
|
ptr.getClass().insertObject (ptr, model, physics);
|
|
|
|
if (useAnim)
|
|
MWBase::Environment::get().getMechanicsManager()->add(ptr);
|
|
|
|
if (ptr.getClass().isActor())
|
|
rendering.addWaterRippleEmitter(ptr);
|
|
|
|
// Restore effect particles
|
|
MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr);
|
|
}
|
|
|
|
void addObject(const MWWorld::Ptr& ptr, const MWPhysics::PhysicsSystem& physics, DetourNavigator::Navigator& navigator)
|
|
{
|
|
if (const auto object = physics.getObject(ptr))
|
|
{
|
|
if (ptr.getClass().isDoor() && !ptr.getCellRef().getTeleport())
|
|
{
|
|
const auto shape = object->getShapeInstance()->getCollisionShape();
|
|
|
|
btVector3 aabbMin;
|
|
btVector3 aabbMax;
|
|
shape->getAabb(btTransform::getIdentity(), aabbMin, aabbMax);
|
|
|
|
const auto center = (aabbMax + aabbMin) * 0.5f;
|
|
|
|
const auto distanceFromDoor = MWBase::Environment::get().getWorld()->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::toBullet(makeObjectOsgQuat(ptr.getCellRef().getPosition())),
|
|
transform.getOrigin()
|
|
);
|
|
|
|
const auto start = Misc::Convert::makeOsgVec3f(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::makeOsgVec3f(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(
|
|
*shape,
|
|
object->getShapeInstance()->getAvoidCollisionShape(),
|
|
connectionStart,
|
|
connectionEnd
|
|
),
|
|
transform
|
|
);
|
|
}
|
|
else
|
|
{
|
|
navigator.addObject(
|
|
DetourNavigator::ObjectId(object),
|
|
DetourNavigator::ObjectShapes {
|
|
*object->getShapeInstance()->getCollisionShape(),
|
|
object->getShapeInstance()->getAvoidCollisionShape()
|
|
},
|
|
object->getTransform()
|
|
);
|
|
}
|
|
}
|
|
else if (physics.getActor(ptr))
|
|
{
|
|
navigator.addAgent(MWBase::Environment::get().getWorld()->getPathfindingHalfExtents(ptr));
|
|
}
|
|
}
|
|
|
|
struct InsertVisitor
|
|
{
|
|
MWWorld::CellStore& mCell;
|
|
Loading::Listener& mLoadingListener;
|
|
bool mTest;
|
|
|
|
std::vector<MWWorld::Ptr> mToInsert;
|
|
|
|
InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool test);
|
|
|
|
bool operator() (const MWWorld::Ptr& ptr);
|
|
|
|
template <class AddObject>
|
|
void insert(AddObject&& addObject);
|
|
};
|
|
|
|
InsertVisitor::InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool test)
|
|
: mCell (cell), mLoadingListener (loadingListener), mTest(test)
|
|
{}
|
|
|
|
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.getRefData().isDeleted() && ptr.getRefData().isEnabled())
|
|
{
|
|
try
|
|
{
|
|
addObject(ptr);
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
std::string error ("failed to render '" + ptr.getCellRef().getRefId() + "': ");
|
|
Log(Debug::Error) << error + e.what();
|
|
}
|
|
}
|
|
|
|
if (!mTest)
|
|
mLoadingListener.increaseProgress (1);
|
|
}
|
|
}
|
|
|
|
struct PositionVisitor
|
|
{
|
|
bool operator() (const MWWorld::Ptr& ptr)
|
|
{
|
|
if (!ptr.getRefData().isDeleted() && ptr.getRefData().isEnabled())
|
|
ptr.getClass().adjustPosition (ptr, false);
|
|
return true;
|
|
}
|
|
};
|
|
|
|
int getCellPositionDistanceToOrigin(const std::pair<int, int>& cellPosition)
|
|
{
|
|
return std::abs(cellPosition.first) + std::abs(cellPosition.second);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
namespace MWWorld
|
|
{
|
|
|
|
void Scene::removeFromPagedRefs(const Ptr &ptr)
|
|
{
|
|
const ESM::RefNum& refnum = ptr.getCellRef().getRefNum();
|
|
if (refnum.hasContentFile() && mPagedRefs.erase(refnum))
|
|
{
|
|
if (!ptr.getRefData().getBaseNode()) return;
|
|
ptr.getClass().insertObjectRendering(ptr, getModel(ptr, mRendering.getResourceSystem()->getVFS()), mRendering);
|
|
setNodeRotation(ptr, mRendering, RotationOrder::direct);
|
|
reloadTerrain();
|
|
}
|
|
}
|
|
|
|
void Scene::updateObjectPosition(const Ptr &ptr, const osg::Vec3f &pos, bool movePhysics)
|
|
{
|
|
mRendering.moveObject(ptr, pos);
|
|
if (movePhysics)
|
|
{
|
|
mPhysics->updatePosition(ptr);
|
|
}
|
|
}
|
|
|
|
void Scene::updateObjectRotation(const Ptr &ptr, RotationOrder order)
|
|
{
|
|
setNodeRotation(ptr, mRendering, order);
|
|
mPhysics->updateRotation(ptr);
|
|
}
|
|
|
|
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, bool paused)
|
|
{
|
|
mPreloader->updateCache(mRendering.getReferenceTime());
|
|
preloadCells(duration);
|
|
|
|
mRendering.update (duration, paused);
|
|
}
|
|
|
|
void Scene::unloadCell (CellStoreCollection::iterator iter, bool test)
|
|
{
|
|
if (!test)
|
|
Log(Debug::Info) << "Unloading cell " << (*iter)->getCell()->getDescription();
|
|
|
|
const auto navigator = MWBase::Environment::get().getWorld()->getNavigator();
|
|
ListAndResetObjectsVisitor visitor;
|
|
|
|
(*iter)->forEach(visitor);
|
|
const auto world = MWBase::Environment::get().getWorld();
|
|
for (const auto& ptr : visitor.mObjects)
|
|
{
|
|
if (const auto object = mPhysics->getObject(ptr))
|
|
navigator->removeObject(DetourNavigator::ObjectId(object));
|
|
else if (mPhysics->getActor(ptr))
|
|
{
|
|
navigator->removeAgent(world->getPathfindingHalfExtents(ptr));
|
|
mRendering.removeActorPath(ptr);
|
|
}
|
|
mPhysics->remove(ptr);
|
|
}
|
|
|
|
const auto cellX = (*iter)->getCell()->getGridX();
|
|
const auto cellY = (*iter)->getCell()->getGridY();
|
|
|
|
if ((*iter)->getCell()->isExterior())
|
|
{
|
|
if (const auto heightField = mPhysics->getHeightField(cellX, cellY))
|
|
navigator->removeObject(DetourNavigator::ObjectId(heightField));
|
|
mPhysics->removeHeightField(cellX, cellY);
|
|
}
|
|
|
|
if ((*iter)->getCell()->hasWater())
|
|
navigator->removeWater(osg::Vec2i(cellX, cellY));
|
|
|
|
if (const auto pathgrid = world->getStore().get<ESM::Pathgrid>().search(*(*iter)->getCell()))
|
|
navigator->removePathgrid(*pathgrid);
|
|
|
|
const auto player = world->getPlayerPtr();
|
|
navigator->update(player.getRefData().getPosition().asVec3());
|
|
|
|
MWBase::Environment::get().getMechanicsManager()->drop (*iter);
|
|
|
|
mRendering.removeCell(*iter);
|
|
MWBase::Environment::get().getWindowManager()->removeCell(*iter);
|
|
|
|
MWBase::Environment::get().getWorld()->getLocalScripts().clearCell (*iter);
|
|
|
|
MWBase::Environment::get().getSoundManager()->stopSound (*iter);
|
|
mActiveCells.erase(*iter);
|
|
}
|
|
|
|
void Scene::loadCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn, bool test)
|
|
{
|
|
std::pair<CellStoreCollection::iterator, bool> result = mActiveCells.insert(cell);
|
|
|
|
if(result.second)
|
|
{
|
|
if (test)
|
|
Log(Debug::Info) << "Testing cell " << cell->getCell()->getDescription();
|
|
else
|
|
Log(Debug::Info) << "Loading cell " << cell->getCell()->getDescription();
|
|
|
|
float verts = ESM::Land::LAND_SIZE;
|
|
float worldsize = ESM::Land::REAL_SIZE;
|
|
|
|
const auto world = MWBase::Environment::get().getWorld();
|
|
const auto navigator = world->getNavigator();
|
|
|
|
const int cellX = cell->getCell()->getGridX();
|
|
const int cellY = cell->getCell()->getGridY();
|
|
|
|
// Load terrain physics first...
|
|
if (!test && cell->getCell()->isExterior())
|
|
{
|
|
osg::ref_ptr<const ESMTerrain::LandObject> land = mRendering.getLandManager()->getLand(cellX, cellY);
|
|
const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : nullptr;
|
|
if (data)
|
|
{
|
|
mPhysics->addHeightField (data->mHeights, cellX, cellY, worldsize / (verts-1), verts, data->mMinHeight, data->mMaxHeight, land.get());
|
|
}
|
|
else
|
|
{
|
|
static std::vector<float> defaultHeight;
|
|
defaultHeight.resize(verts*verts, ESM::Land::DEFAULT_HEIGHT);
|
|
mPhysics->addHeightField (&defaultHeight[0], cell->getCell()->getGridX(), cell->getCell()->getGridY(), worldsize / (verts-1), verts, ESM::Land::DEFAULT_HEIGHT, ESM::Land::DEFAULT_HEIGHT, land.get());
|
|
}
|
|
|
|
if (const auto heightField = mPhysics->getHeightField(cellX, cellY))
|
|
navigator->addObject(DetourNavigator::ObjectId(heightField), *heightField->getShape(),
|
|
heightField->getCollisionObject()->getWorldTransform());
|
|
}
|
|
|
|
if (const auto pathgrid = world->getStore().get<ESM::Pathgrid>().search(*cell->getCell()))
|
|
navigator->addPathgrid(*cell->getCell(), *pathgrid);
|
|
|
|
// register local scripts
|
|
// do this before insertCell, to make sure we don't add scripts from levelled creature spawning twice
|
|
MWBase::Environment::get().getWorld()->getLocalScripts().addCell (cell);
|
|
|
|
if (respawn)
|
|
cell->respawn();
|
|
|
|
// ... then references. This is important for adjustPosition to work correctly.
|
|
insertCell (*cell, loadingListener, test);
|
|
|
|
mRendering.addCell(cell);
|
|
if (!test)
|
|
{
|
|
MWBase::Environment::get().getWindowManager()->addCell(cell);
|
|
bool waterEnabled = cell->getCell()->hasWater() || cell->isExterior();
|
|
float waterLevel = cell->getWaterLevel();
|
|
mRendering.setWaterEnabled(waterEnabled);
|
|
if (waterEnabled)
|
|
{
|
|
mPhysics->enableWater(waterLevel);
|
|
mRendering.setWaterHeight(waterLevel);
|
|
|
|
if (cell->getCell()->isExterior())
|
|
{
|
|
if (const auto heightField = mPhysics->getHeightField(cellX, cellY))
|
|
navigator->addWater(osg::Vec2i(cellX, cellY), ESM::Land::REAL_SIZE,
|
|
cell->getWaterLevel(), heightField->getCollisionObject()->getWorldTransform());
|
|
}
|
|
else
|
|
{
|
|
navigator->addWater(osg::Vec2i(cellX, cellY), std::numeric_limits<int>::max(),
|
|
cell->getWaterLevel(), btTransform::getIdentity());
|
|
}
|
|
}
|
|
else
|
|
mPhysics->disableWater();
|
|
|
|
const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr();
|
|
|
|
navigator->update(player.getRefData().getPosition().asVec3());
|
|
|
|
if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx))
|
|
{
|
|
mRendering.configureAmbient(cell->getCell());
|
|
}
|
|
}
|
|
}
|
|
|
|
mPreloader->notifyLoaded(cell);
|
|
}
|
|
|
|
void Scene::clear()
|
|
{
|
|
CellStoreCollection::iterator active = mActiveCells.begin();
|
|
while (active!=mActiveCells.end())
|
|
unloadCell (active++);
|
|
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
|
|
{
|
|
if (currentGridCenter)
|
|
{
|
|
float centerX, centerY;
|
|
MWBase::Environment::get().getWorld()->indexToPosition(currentGridCenter->x(), currentGridCenter->y(), centerX, centerY, true);
|
|
float distance = std::max(std::abs(centerX-pos.x()), std::abs(centerY-pos.y()));
|
|
const float maxDistance = Constants::CellSizeInUnits / 2 + mCellLoadingThreshold; // 1/2 cell size + threshold
|
|
if (distance <= maxDistance)
|
|
return *currentGridCenter;
|
|
}
|
|
osg::Vec2i newCenter;
|
|
MWBase::Environment::get().getWorld()->positionToIndex(pos.x(), pos.y(), newCenter.x(), newCenter.y());
|
|
return newCenter;
|
|
}
|
|
|
|
void Scene::playerMoved(const osg::Vec3f &pos)
|
|
{
|
|
const auto navigator = MWBase::Environment::get().getWorld()->getNavigator();
|
|
const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr();
|
|
navigator->updatePlayerPosition(player.getRefData().getPosition().asVec3());
|
|
|
|
if (!mCurrentCell || !mCurrentCell->isExterior())
|
|
return;
|
|
|
|
osg::Vec2i newCell = getNewGridCenter(pos, &mCurrentGridCenter);
|
|
if (newCell != mCurrentGridCenter)
|
|
changeCellGrid(pos, newCell.x(), newCell.y());
|
|
}
|
|
|
|
void Scene::changeCellGrid (const osg::Vec3f &pos, int playerCellX, int playerCellY, bool changeEvent)
|
|
{
|
|
CellStoreCollection::iterator active = mActiveCells.begin();
|
|
while (active!=mActiveCells.end())
|
|
{
|
|
if ((*active)->getCell()->isExterior())
|
|
{
|
|
if (std::abs (playerCellX-(*active)->getCell()->getGridX())<=mHalfGridSize &&
|
|
std::abs (playerCellY-(*active)->getCell()->getGridY())<=mHalfGridSize)
|
|
{
|
|
// keep cells within the new grid
|
|
++active;
|
|
continue;
|
|
}
|
|
}
|
|
unloadCell (active++);
|
|
}
|
|
|
|
mCurrentGridCenter = osg::Vec2i(playerCellX, playerCellY);
|
|
osg::Vec4i newGrid = gridCenterToBounds(mCurrentGridCenter);
|
|
mRendering.setActiveGrid(newGrid);
|
|
|
|
preloadTerrain(pos, true);
|
|
mPagedRefs.clear();
|
|
mRendering.getPagedRefnums(newGrid, mPagedRefs);
|
|
|
|
std::size_t refsToLoad = 0;
|
|
std::vector<std::pair<int, int>> cellsPositionsToLoad;
|
|
// get the number of refs to load
|
|
for (int x = playerCellX - mHalfGridSize; x <= playerCellX + mHalfGridSize; ++x)
|
|
{
|
|
for (int y = playerCellY - mHalfGridSize; y <= playerCellY + mHalfGridSize; ++y)
|
|
{
|
|
CellStoreCollection::iterator iter = mActiveCells.begin();
|
|
|
|
while (iter!=mActiveCells.end())
|
|
{
|
|
assert ((*iter)->getCell()->isExterior());
|
|
|
|
if (x==(*iter)->getCell()->getGridX() &&
|
|
y==(*iter)->getCell()->getGridY())
|
|
break;
|
|
|
|
++iter;
|
|
}
|
|
|
|
if (iter==mActiveCells.end())
|
|
{
|
|
refsToLoad += MWBase::Environment::get().getWorld()->getExterior(x, y)->count();
|
|
cellsPositionsToLoad.emplace_back(x, y);
|
|
}
|
|
}
|
|
}
|
|
|
|
Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen();
|
|
Loading::ScopedLoad load(loadingListener);
|
|
std::string loadingExteriorText = "#{sLoadingMessage3}";
|
|
loadingListener->setLabel(loadingExteriorText);
|
|
loadingListener->setProgressRange(refsToLoad);
|
|
|
|
const auto getDistanceToPlayerCell = [&] (const std::pair<int, int>& cellPosition)
|
|
{
|
|
return std::abs(cellPosition.first - playerCellX) + std::abs(cellPosition.second - playerCellY);
|
|
};
|
|
|
|
const auto getCellPositionPriority = [&] (const std::pair<int, int>& cellPosition)
|
|
{
|
|
return std::make_pair(getDistanceToPlayerCell(cellPosition), getCellPositionDistanceToOrigin(cellPosition));
|
|
};
|
|
|
|
std::sort(cellsPositionsToLoad.begin(), cellsPositionsToLoad.end(),
|
|
[&] (const std::pair<int, int>& lhs, const std::pair<int, int>& rhs) {
|
|
return getCellPositionPriority(lhs) < getCellPositionPriority(rhs);
|
|
});
|
|
|
|
// Load cells
|
|
for (const auto& cellPosition : cellsPositionsToLoad)
|
|
{
|
|
const auto x = cellPosition.first;
|
|
const auto y = cellPosition.second;
|
|
|
|
CellStoreCollection::iterator iter = mActiveCells.begin();
|
|
|
|
while (iter != mActiveCells.end())
|
|
{
|
|
assert ((*iter)->getCell()->isExterior());
|
|
|
|
if (x == (*iter)->getCell()->getGridX() &&
|
|
y == (*iter)->getCell()->getGridY())
|
|
break;
|
|
|
|
++iter;
|
|
}
|
|
|
|
if (iter == mActiveCells.end())
|
|
{
|
|
CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(x, y);
|
|
|
|
loadCell (cell, loadingListener, changeEvent);
|
|
}
|
|
}
|
|
|
|
CellStore* current = MWBase::Environment::get().getWorld()->getExterior(playerCellX, playerCellY);
|
|
MWBase::Environment::get().getWindowManager()->changeCell(current);
|
|
|
|
if (changeEvent)
|
|
mCellChanged = true;
|
|
|
|
mNavigator.wait(*loadingListener, DetourNavigator::WaitConditionType::requiredTilesPresent);
|
|
}
|
|
|
|
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 = MWBase::Environment::get().getWorld()->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;
|
|
for (; it != cells.extEnd(); ++it)
|
|
{
|
|
loadingListener->setLabel("Testing exterior cells ("+std::to_string(i)+"/"+std::to_string(cells.getExtSize())+")...");
|
|
|
|
CellStoreCollection::iterator iter = mActiveCells.begin();
|
|
|
|
CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(it->mData.mX, it->mData.mY);
|
|
loadCell (cell, loadingListener, false, true);
|
|
|
|
iter = mActiveCells.begin();
|
|
while (iter != mActiveCells.end())
|
|
{
|
|
if (it->isExterior() && it->mData.mX == (*iter)->getCell()->getGridX() &&
|
|
it->mData.mY == (*iter)->getCell()->getGridY())
|
|
{
|
|
unloadCell(iter, true);
|
|
break;
|
|
}
|
|
|
|
++iter;
|
|
}
|
|
|
|
mRendering.getResourceSystem()->updateCache(mRendering.getReferenceTime());
|
|
mRendering.getUnrefQueue()->flush(mRendering.getWorkQueue());
|
|
|
|
loadingListener->increaseProgress (1);
|
|
i++;
|
|
}
|
|
|
|
mRendering.getResourceSystem()->getSceneManager()->setIncrementalCompileOperation(mRendering.getIncrementalCompileOperation());
|
|
mRendering.getResourceSystem()->setExpiryDelay(Settings::Manager::getFloat("cache expiry delay", "Cells"));
|
|
}
|
|
|
|
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 = MWBase::Environment::get().getWorld()->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();
|
|
for (; it != cells.intEnd(); ++it)
|
|
{
|
|
loadingListener->setLabel("Testing interior cells ("+std::to_string(i)+"/"+std::to_string(cells.getIntSize())+")...");
|
|
|
|
CellStore *cell = MWBase::Environment::get().getWorld()->getInterior(it->mName);
|
|
loadCell (cell, loadingListener, false, true);
|
|
|
|
CellStoreCollection::iterator iter = mActiveCells.begin();
|
|
while (iter != mActiveCells.end())
|
|
{
|
|
assert (!(*iter)->getCell()->isExterior());
|
|
|
|
if (it->mName == (*iter)->getCell()->mName)
|
|
{
|
|
unloadCell(iter, true);
|
|
break;
|
|
}
|
|
|
|
++iter;
|
|
}
|
|
|
|
mRendering.getResourceSystem()->updateCache(mRendering.getReferenceTime());
|
|
mRendering.getUnrefQueue()->flush(mRendering.getWorkQueue());
|
|
|
|
loadingListener->increaseProgress (1);
|
|
i++;
|
|
}
|
|
|
|
mRendering.getResourceSystem()->getSceneManager()->setIncrementalCompileOperation(mRendering.getIncrementalCompileOperation());
|
|
mRendering.getResourceSystem()->setExpiryDelay(Settings::Manager::getFloat("cache expiry delay", "Cells"));
|
|
}
|
|
|
|
void Scene::changePlayerCell(CellStore *cell, const ESM::Position &pos, bool adjustPlayerPos)
|
|
{
|
|
mCurrentCell = cell;
|
|
|
|
mRendering.enableTerrain(cell->isExterior());
|
|
|
|
MWBase::World *world = MWBase::Environment::get().getWorld();
|
|
MWWorld::Ptr old = world->getPlayerPtr();
|
|
world->getPlayer().setCell(cell);
|
|
|
|
MWWorld::Ptr player = world->getPlayerPtr();
|
|
mRendering.updatePlayerPtr(player);
|
|
|
|
if (adjustPlayerPos) {
|
|
world->moveObject(player, pos.pos[0], pos.pos[1], pos.pos[2]);
|
|
|
|
float x = pos.rot[0];
|
|
float y = pos.rot[1];
|
|
float z = pos.rot[2];
|
|
world->rotateObject(player, x, y, z);
|
|
|
|
player.getClass().adjustPosition(player, true);
|
|
}
|
|
|
|
MWBase::Environment::get().getMechanicsManager()->updateCell(old, player);
|
|
MWBase::Environment::get().getWindowManager()->watchActor(player);
|
|
|
|
mPhysics->updatePtr(old, player);
|
|
|
|
world->adjustSky();
|
|
|
|
mLastPlayerPos = player.getRefData().getPosition().asVec3();
|
|
}
|
|
|
|
Scene::Scene (MWRender::RenderingManager& rendering, MWPhysics::PhysicsSystem *physics,
|
|
DetourNavigator::Navigator& navigator)
|
|
: mCurrentCell (nullptr), mCellChanged (false), mPhysics(physics), mRendering(rendering), mNavigator(navigator)
|
|
, mCellLoadingThreshold(1024.f)
|
|
, mPreloadDistance(Settings::Manager::getInt("preload distance", "Cells"))
|
|
, mPreloadEnabled(Settings::Manager::getBool("preload enabled", "Cells"))
|
|
, mPreloadExteriorGrid(Settings::Manager::getBool("preload exterior grid", "Cells"))
|
|
, mPreloadDoors(Settings::Manager::getBool("preload doors", "Cells"))
|
|
, mPreloadFastTravel(Settings::Manager::getBool("preload fast travel", "Cells"))
|
|
, mPredictionTime(Settings::Manager::getFloat("prediction time", "Cells"))
|
|
{
|
|
mPreloader.reset(new CellPreloader(rendering.getResourceSystem(), physics->getShapeManager(), rendering.getTerrain(), rendering.getLandManager()));
|
|
mPreloader->setWorkQueue(mRendering.getWorkQueue());
|
|
|
|
mPreloader->setUnrefQueue(rendering.getUnrefQueue());
|
|
mPhysics->setUnrefQueue(rendering.getUnrefQueue());
|
|
|
|
rendering.getResourceSystem()->setExpiryDelay(Settings::Manager::getFloat("cache expiry delay", "Cells"));
|
|
|
|
mPreloader->setExpiryDelay(Settings::Manager::getFloat("preload cell expiry delay", "Cells"));
|
|
mPreloader->setMinCacheSize(Settings::Manager::getInt("preload cell cache min", "Cells"));
|
|
mPreloader->setMaxCacheSize(Settings::Manager::getInt("preload cell cache max", "Cells"));
|
|
mPreloader->setPreloadInstances(Settings::Manager::getBool("preload instances", "Cells"));
|
|
}
|
|
|
|
Scene::~Scene()
|
|
{
|
|
}
|
|
|
|
bool Scene::hasCellChanged() const
|
|
{
|
|
return mCellChanged;
|
|
}
|
|
|
|
const Scene::CellStoreCollection& Scene::getActiveCells() const
|
|
{
|
|
return mActiveCells;
|
|
}
|
|
|
|
void Scene::changeToInteriorCell (const std::string& cellName, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent)
|
|
{
|
|
CellStore *cell = MWBase::Environment::get().getWorld()->getInterior(cellName);
|
|
bool useFading = (mCurrentCell != nullptr);
|
|
if (useFading)
|
|
MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.5);
|
|
|
|
Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen();
|
|
std::string loadingInteriorText = "#{sLoadingMessage2}";
|
|
loadingListener->setLabel(loadingInteriorText);
|
|
Loading::ScopedLoad load(loadingListener);
|
|
|
|
if(mCurrentCell != nullptr && *mCurrentCell == *cell)
|
|
{
|
|
MWBase::World *world = MWBase::Environment::get().getWorld();
|
|
world->moveObject(world->getPlayerPtr(), position.pos[0], position.pos[1], position.pos[2]);
|
|
|
|
float x = position.rot[0];
|
|
float y = position.rot[1];
|
|
float z = position.rot[2];
|
|
world->rotateObject(world->getPlayerPtr(), x, y, z);
|
|
|
|
if (adjustPlayerPos)
|
|
world->getPlayerPtr().getClass().adjustPosition(world->getPlayerPtr(), true);
|
|
MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.5);
|
|
return;
|
|
}
|
|
|
|
Log(Debug::Info) << "Changing to interior";
|
|
|
|
// unload
|
|
CellStoreCollection::iterator active = mActiveCells.begin();
|
|
while (active!=mActiveCells.end())
|
|
unloadCell (active++);
|
|
|
|
loadingListener->setProgressRange(cell->count());
|
|
|
|
// Load cell.
|
|
mPagedRefs.clear();
|
|
loadCell (cell, loadingListener, changeEvent);
|
|
|
|
changePlayerCell(cell, position, adjustPlayerPos);
|
|
|
|
// adjust fog
|
|
mRendering.configureFog(mCurrentCell->getCell());
|
|
|
|
// Sky system
|
|
MWBase::Environment::get().getWorld()->adjustSky();
|
|
|
|
if (changeEvent)
|
|
mCellChanged = true;
|
|
|
|
if (useFading)
|
|
MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.5);
|
|
|
|
MWBase::Environment::get().getWindowManager()->changeCell(mCurrentCell);
|
|
|
|
mNavigator.wait(*loadingListener, DetourNavigator::WaitConditionType::requiredTilesPresent);
|
|
}
|
|
|
|
void Scene::changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos, bool changeEvent)
|
|
{
|
|
int x = 0;
|
|
int y = 0;
|
|
|
|
MWBase::Environment::get().getWorld()->positionToIndex (position.pos[0], position.pos[1], x, y);
|
|
|
|
if (changeEvent)
|
|
MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.5);
|
|
|
|
changeCellGrid(position.asVec3(), x, y, changeEvent);
|
|
|
|
CellStore* current = MWBase::Environment::get().getWorld()->getExterior(x, y);
|
|
changePlayerCell(current, position, adjustPlayerPos);
|
|
|
|
if (changeEvent)
|
|
MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.5);
|
|
}
|
|
|
|
CellStore* Scene::getCurrentCell ()
|
|
{
|
|
return mCurrentCell;
|
|
}
|
|
|
|
void Scene::markCellAsUnchanged()
|
|
{
|
|
mCellChanged = false;
|
|
}
|
|
|
|
void Scene::insertCell (CellStore &cell, Loading::Listener* loadingListener, bool test)
|
|
{
|
|
InsertVisitor insertVisitor (cell, *loadingListener, test);
|
|
cell.forEach (insertVisitor);
|
|
insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mRendering, mPagedRefs); });
|
|
insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mNavigator); });
|
|
|
|
// do adjustPosition (snapping actors to ground) after objects are loaded, so we don't depend on the loading order
|
|
PositionVisitor posVisitor;
|
|
cell.forEach (posVisitor);
|
|
}
|
|
|
|
void Scene::addObjectToScene (const Ptr& ptr)
|
|
{
|
|
try
|
|
{
|
|
addObject(ptr, *mPhysics, mRendering, mPagedRefs);
|
|
addObject(ptr, *mPhysics, mNavigator);
|
|
MWBase::Environment::get().getWorld()->scaleObject(ptr, ptr.getCellRef().getScale());
|
|
const auto navigator = MWBase::Environment::get().getWorld()->getNavigator();
|
|
const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr();
|
|
navigator->update(player.getRefData().getPosition().asVec3());
|
|
}
|
|
catch (std::exception& e)
|
|
{
|
|
Log(Debug::Error) << "failed to render '" << ptr.getCellRef().getRefId() << "': " << e.what();
|
|
}
|
|
}
|
|
|
|
void Scene::removeObjectFromScene (const Ptr& ptr)
|
|
{
|
|
MWBase::Environment::get().getMechanicsManager()->remove (ptr);
|
|
MWBase::Environment::get().getSoundManager()->stopSound3D (ptr);
|
|
const auto navigator = MWBase::Environment::get().getWorld()->getNavigator();
|
|
if (const auto object = mPhysics->getObject(ptr))
|
|
{
|
|
navigator->removeObject(DetourNavigator::ObjectId(object));
|
|
const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr();
|
|
navigator->update(player.getRefData().getPosition().asVec3());
|
|
}
|
|
else if (mPhysics->getActor(ptr))
|
|
{
|
|
navigator->removeAgent(MWBase::Environment::get().getWorld()->getPathfindingHalfExtents(ptr));
|
|
}
|
|
mPhysics->remove(ptr);
|
|
mRendering.removeObject (ptr);
|
|
if (ptr.getClass().isActor())
|
|
mRendering.removeWaterRippleEmitter(ptr);
|
|
ptr.getRefData().setBaseNode(nullptr);
|
|
}
|
|
|
|
bool Scene::isCellActive(const CellStore &cell)
|
|
{
|
|
CellStoreCollection::iterator active = mActiveCells.begin();
|
|
while (active != mActiveCells.end()) {
|
|
if (**active == cell) {
|
|
return true;
|
|
}
|
|
++active;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Ptr Scene::searchPtrViaActorId (int actorId)
|
|
{
|
|
for (CellStoreCollection::const_iterator iter (mActiveCells.begin());
|
|
iter!=mActiveCells.end(); ++iter)
|
|
if (Ptr ptr = (*iter)->searchViaActorId (actorId))
|
|
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
|
|
{
|
|
try
|
|
{
|
|
mSceneManager->getTemplate(mMesh);
|
|
}
|
|
catch (std::exception&)
|
|
{
|
|
}
|
|
}
|
|
private:
|
|
std::string mMesh;
|
|
Resource::SceneManager* mSceneManager;
|
|
};
|
|
|
|
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()))
|
|
mRendering.getWorkQueue()->addWorkItem(new PreloadMeshItem(mesh_, mRendering.getResourceSystem()->getSceneManager()));
|
|
}
|
|
|
|
void Scene::preloadCells(float dt)
|
|
{
|
|
if (dt<=1e-06) return;
|
|
std::vector<PositionCellGrid> exteriorPositions;
|
|
|
|
const MWWorld::ConstPtr player = MWBase::Environment::get().getWorld()->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
|
|
{
|
|
if (!door.getCellRef().getDestCell().empty())
|
|
preloadCell(MWBase::Environment::get().getWorld()->getInterior(door.getCellRef().getDestCell()));
|
|
else
|
|
{
|
|
osg::Vec3f pos = door.getCellRef().getDoorDest().asVec3();
|
|
int x,y;
|
|
MWBase::Environment::get().getWorld()->positionToIndex (pos.x(), pos.y(), x, y);
|
|
preloadCell(MWBase::Environment::get().getWorld()->getExterior(x,y), true);
|
|
exteriorPositions.emplace_back(pos, gridCenterToBounds(getNewGridCenter(pos)));
|
|
}
|
|
}
|
|
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 (!MWBase::Environment::get().getWorld()->isCellExterior())
|
|
return;
|
|
|
|
int halfGridSizePlusOne = mHalfGridSize + 1;
|
|
|
|
|
|
int cellX,cellY;
|
|
cellX = mCurrentGridCenter.x(); cellY = mCurrentGridCenter.y();
|
|
|
|
float centerX, centerY;
|
|
MWBase::Environment::get().getWorld()->indexToPosition(cellX, cellY, centerX, centerY, true);
|
|
|
|
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
|
|
|
|
float thisCellCenterX, thisCellCenterY;
|
|
MWBase::Environment::get().getWorld()->indexToPosition(cellX+dx, cellY+dy, thisCellCenterX, thisCellCenterY, true);
|
|
|
|
float dist = std::max(std::abs(thisCellCenterX - playerPos.x()), std::abs(thisCellCenterY - playerPos.y()));
|
|
dist = std::min(dist,std::max(std::abs(thisCellCenterX - predictedPos.x()), std::abs(thisCellCenterY - predictedPos.y())));
|
|
float loadDist = Constants::CellSizeInUnits / 2 + Constants::CellSizeInUnits - mCellLoadingThreshold + mPreloadDistance;
|
|
|
|
if (dist < loadDist)
|
|
preloadCell(MWBase::Environment::get().getWorld()->getExterior(cellX+dx, cellY+dy));
|
|
}
|
|
}
|
|
}
|
|
|
|
void Scene::preloadCell(CellStore *cell, bool preloadSurrounding)
|
|
{
|
|
if (preloadSurrounding && cell->isExterior())
|
|
{
|
|
int x = cell->getCell()->getGridX();
|
|
int y = cell->getCell()->getGridY();
|
|
unsigned int numpreloaded = 0;
|
|
for (int dx = -mHalfGridSize; dx <= mHalfGridSize; ++dx)
|
|
{
|
|
for (int dy = -mHalfGridSize; dy <= mHalfGridSize; ++dy)
|
|
{
|
|
mPreloader->preload(MWBase::Environment::get().getWorld()->getExterior(x+dx, y+dy), mRendering.getReferenceTime());
|
|
if (++numpreloaded >= mPreloader->getMaxCacheSize())
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
mPreloader->preload(cell, mRendering.getReferenceTime());
|
|
}
|
|
|
|
void Scene::preloadTerrain(const osg::Vec3f &pos, bool sync)
|
|
{
|
|
std::vector<PositionCellGrid> vec;
|
|
vec.emplace_back(pos, gridCenterToBounds(getNewGridCenter(pos)));
|
|
if (sync && mRendering.pagingUnlockCache())
|
|
mPreloader->abortTerrainPreloadExcept(nullptr);
|
|
else
|
|
mPreloader->abortTerrainPreloadExcept(&vec[0]);
|
|
mPreloader->setTerrainPreloadPositions(vec);
|
|
if (!sync) return;
|
|
|
|
Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen();
|
|
Loading::ScopedLoad load(loadingListener);
|
|
int progress = 0, initialProgress = -1, progressRange = 0;
|
|
while (!mPreloader->syncTerrainLoad(vec, progress, progressRange, mRendering.getReferenceTime()))
|
|
{
|
|
if (initialProgress == -1)
|
|
{
|
|
loadingListener->setLabel("#{sLoadingMessage4}");
|
|
initialProgress = progress;
|
|
}
|
|
if (progress)
|
|
{
|
|
loadingListener->setProgressRange(std::max(0, progressRange-initialProgress));
|
|
loadingListener->setProgress(progress-initialProgress);
|
|
}
|
|
else
|
|
loadingListener->setProgress(0);
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
|
}
|
|
}
|
|
|
|
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 = MWBase::Environment::get().getWorld()->getPlayerPtr();
|
|
ListFastTravelDestinationsVisitor listVisitor(mPreloadDistance, player.getRefData().getPosition().asVec3());
|
|
|
|
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(MWBase::Environment::get().getWorld()->getInterior(dest.mCellName));
|
|
else
|
|
{
|
|
osg::Vec3f pos = dest.mPos.asVec3();
|
|
int x,y;
|
|
MWBase::Environment::get().getWorld()->positionToIndex( pos.x(), pos.y(), x, y);
|
|
preloadCell(MWBase::Environment::get().getWorld()->getExterior(x,y), true);
|
|
exteriorPositions.emplace_back(pos, gridCenterToBounds(getNewGridCenter(pos)));
|
|
}
|
|
}
|
|
}
|
|
}
|