Merge branch 'object_paging_retry' into 'master'

Object Paging

See merge request OpenMW/openmw!209
pull/2911/head
psi29a 5 years ago
commit 0dc7715c35

@ -21,7 +21,7 @@ add_openmw_dir (mwrender
actors objects renderingmanager animation rotatecontroller sky npcanimation vismask actors objects renderingmanager animation rotatecontroller sky npcanimation vismask
creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation
bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation
renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging
) )
add_openmw_dir (mwinput add_openmw_dir (mwinput

@ -16,6 +16,7 @@
#include <components/myguiplatform/myguitexture.hpp> #include <components/myguiplatform/myguitexture.hpp>
#include <components/settings/settings.hpp> #include <components/settings/settings.hpp>
#include <components/vfs/manager.hpp> #include <components/vfs/manager.hpp>
#include <components/resource/resourcesystem.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/statemanager.hpp" #include "../mwbase/statemanager.hpp"
@ -29,9 +30,9 @@
namespace MWGui namespace MWGui
{ {
LoadingScreen::LoadingScreen(const VFS::Manager* vfs, osgViewer::Viewer* viewer) LoadingScreen::LoadingScreen(Resource::ResourceSystem* resourceSystem, osgViewer::Viewer* viewer)
: WindowBase("openmw_loading_screen.layout") : WindowBase("openmw_loading_screen.layout")
, mVFS(vfs) , mResourceSystem(resourceSystem)
, mViewer(viewer) , mViewer(viewer)
, mTargetFrameRate(120.0) , mTargetFrameRate(120.0)
, mLastWallpaperChangeTime(0.0) , mLastWallpaperChangeTime(0.0)
@ -64,9 +65,9 @@ namespace MWGui
void LoadingScreen::findSplashScreens() void LoadingScreen::findSplashScreens()
{ {
const std::map<std::string, VFS::File*>& index = mVFS->getIndex(); const std::map<std::string, VFS::File*>& index = mResourceSystem->getVFS()->getIndex();
std::string pattern = "Splash/"; std::string pattern = "Splash/";
mVFS->normalizeFilename(pattern); mResourceSystem->getVFS()->normalizeFilename(pattern);
/* priority given to the left */ /* priority given to the left */
const std::array<std::string, 7> supported_extensions {{".tga", ".dds", ".ktx", ".png", ".bmp", ".jpeg", ".jpg"}}; const std::array<std::string, 7> supported_extensions {{".tga", ".dds", ".ktx", ".png", ".bmp", ".jpeg", ".jpg"}};
@ -171,6 +172,11 @@ namespace MWGui
// We are already using node masks to avoid the scene from being updated/rendered, but node masks don't work for computeBound() // We are already using node masks to avoid the scene from being updated/rendered, but node masks don't work for computeBound()
mViewer->getSceneData()->setComputeBoundingSphereCallback(new DontComputeBoundCallback); mViewer->getSceneData()->setComputeBoundingSphereCallback(new DontComputeBoundCallback);
if (const osgUtil::IncrementalCompileOperation* ico = mViewer->getIncrementalCompileOperation()) {
mOldIcoMin = ico->getMinimumTimeAvailableForGLCompileAndDeletePerFrame();
mOldIcoMax = ico->getMaximumNumOfObjectsToCompilePerFrame();
}
mVisible = visible; mVisible = visible;
mLoadingBox->setVisible(mVisible); mLoadingBox->setVisible(mVisible);
setVisible(true); setVisible(true);
@ -215,6 +221,12 @@ namespace MWGui
//std::cout << "loading took " << mTimer.time_m() - mLoadingOnTime << std::endl; //std::cout << "loading took " << mTimer.time_m() - mLoadingOnTime << std::endl;
setVisible(false); setVisible(false);
if (osgUtil::IncrementalCompileOperation* ico = mViewer->getIncrementalCompileOperation())
{
ico->setMinimumTimeAvailableForGLCompileAndDeletePerFrame(mOldIcoMin);
ico->setMaximumNumOfObjectsToCompilePerFrame(mOldIcoMax);
}
MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Loading); MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Loading);
MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_LoadingWallpaper); MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_LoadingWallpaper);
} }
@ -336,7 +348,13 @@ namespace MWGui
MWBase::Environment::get().getInputManager()->update(0, true, true); MWBase::Environment::get().getInputManager()->update(0, true, true);
//osg::Timer timer; mResourceSystem->reportStats(mViewer->getFrameStamp()->getFrameNumber(), mViewer->getViewerStats());
if (osgUtil::IncrementalCompileOperation* ico = mViewer->getIncrementalCompileOperation())
{
ico->setMinimumTimeAvailableForGLCompileAndDeletePerFrame(1.f/getTargetFrameRate());
ico->setMaximumNumOfObjectsToCompilePerFrame(1000);
}
// at the time this function is called we are in the middle of a frame, // at the time this function is called we are in the middle of a frame,
// so out of order calls are necessary to get a correct frameNumber for the next frame. // so out of order calls are necessary to get a correct frameNumber for the next frame.
// refer to the advance() and frame() order in Engine::go() // refer to the advance() and frame() order in Engine::go()
@ -344,10 +362,6 @@ namespace MWGui
mViewer->updateTraversal(); mViewer->updateTraversal();
mViewer->renderingTraversals(); mViewer->renderingTraversals();
mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); mViewer->advance(mViewer->getFrameStamp()->getSimulationTime());
//std::cout << "frame took " << timer.time_m() << std::endl;
//if (mViewer->getIncrementalCompileOperation())
//std::cout << "num to compile " << mViewer->getIncrementalCompileOperation()->getToCompile().size() << std::endl;
mLastRenderTime = mTimer.time_m(); mLastRenderTime = mTimer.time_m();
} }

@ -20,9 +20,9 @@ namespace osg
class Texture2D; class Texture2D;
} }
namespace VFS namespace Resource
{ {
class Manager; class ResourceSystem;
} }
namespace MWGui namespace MWGui
@ -32,7 +32,7 @@ namespace MWGui
class LoadingScreen : public WindowBase, public Loading::Listener class LoadingScreen : public WindowBase, public Loading::Listener
{ {
public: public:
LoadingScreen(const VFS::Manager* vfs, osgViewer::Viewer* viewer); LoadingScreen(Resource::ResourceSystem* resourceSystem, osgViewer::Viewer* viewer);
virtual ~LoadingScreen(); virtual ~LoadingScreen();
/// Overridden from Loading::Listener, see the Loading::Listener documentation for usage details /// Overridden from Loading::Listener, see the Loading::Listener documentation for usage details
@ -53,7 +53,7 @@ namespace MWGui
void setupCopyFramebufferToTextureCallback(); void setupCopyFramebufferToTextureCallback();
const VFS::Manager* mVFS; Resource::ResourceSystem* mResourceSystem;
osg::ref_ptr<osgViewer::Viewer> mViewer; osg::ref_ptr<osgViewer::Viewer> mViewer;
double mTargetFrameRate; double mTargetFrameRate;
@ -70,6 +70,8 @@ namespace MWGui
size_t mProgress; size_t mProgress;
bool mShowWallpaper; bool mShowWallpaper;
float mOldIcoMin = 0.f;
unsigned int mOldIcoMax = 0;
MyGUI::Widget* mLoadingBox; MyGUI::Widget* mLoadingBox;

@ -230,7 +230,7 @@ namespace MWGui
mKeyboardNavigation->setEnabled(keyboardNav); mKeyboardNavigation->setEnabled(keyboardNav);
Gui::ImageButton::setDefaultNeedKeyFocus(keyboardNav); Gui::ImageButton::setDefaultNeedKeyFocus(keyboardNav);
mLoadingScreen = new LoadingScreen(mResourceSystem->getVFS(), mViewer); mLoadingScreen = new LoadingScreen(mResourceSystem, mViewer);
mWindows.push_back(mLoadingScreen); mWindows.push_back(mLoadingScreen);
//set up the hardware cursor manager //set up the hardware cursor manager

@ -7,6 +7,8 @@
#include <components/esm/esmwriter.hpp> #include <components/esm/esmwriter.hpp>
#include <components/esm/stolenitems.hpp> #include <components/esm/stolenitems.hpp>
#include <components/detournavigator/navigator.hpp>
#include <components/sceneutil/positionattitudetransform.hpp> #include <components/sceneutil/positionattitudetransform.hpp>
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
@ -900,6 +902,12 @@ namespace MWMechanics
bool MechanicsManager::toggleAI() bool MechanicsManager::toggleAI()
{ {
mAI = !mAI; mAI = !mAI;
MWBase::World* world = MWBase::Environment::get().getWorld();
world->getNavigator()->setUpdatesEnabled(mAI);
if (mAI)
world->getNavigator()->update(world->getPlayerPtr().getRefData().getPosition().asVec3());
return mAI; return mAI;
} }

@ -397,6 +397,15 @@ namespace MWPhysics
return osg::Vec3f(); return osg::Vec3f();
} }
osg::BoundingBox PhysicsSystem::getBoundingBox(const MWWorld::ConstPtr &object) const
{
const Object * physobject = getObject(object);
if (!physobject) return osg::BoundingBox();
btVector3 min, max;
physobject->getCollisionObject()->getCollisionShape()->getAabb(physobject->getCollisionObject()->getWorldTransform(), min, max);
return osg::BoundingBox(Misc::Convert::toOsg(min), Misc::Convert::toOsg(max));
}
osg::Vec3f PhysicsSystem::getCollisionObjectPosition(const MWWorld::ConstPtr &actor) const osg::Vec3f PhysicsSystem::getCollisionObjectPosition(const MWWorld::ConstPtr &actor) const
{ {
const Actor* physactor = getActor(actor); const Actor* physactor = getActor(actor);

@ -7,6 +7,7 @@
#include <algorithm> #include <algorithm>
#include <osg/Quat> #include <osg/Quat>
#include <osg/BoundingBox>
#include <osg/ref_ptr> #include <osg/ref_ptr>
#include "../mwworld/ptr.hpp" #include "../mwworld/ptr.hpp"
@ -144,6 +145,9 @@ namespace MWPhysics
/// @note The collision shape's origin is in its center, so the position returned can be described as center of the actor collision box in world space. /// @note The collision shape's origin is in its center, so the position returned can be described as center of the actor collision box in world space.
osg::Vec3f getCollisionObjectPosition(const MWWorld::ConstPtr& actor) const; osg::Vec3f getCollisionObjectPosition(const MWWorld::ConstPtr& actor) const;
/// Get bounding box in world space of the given object.
osg::BoundingBox getBoundingBox(const MWWorld::ConstPtr &object) const;
/// Queues velocity movement for a Ptr. If a Ptr is already queued, its velocity will /// Queues velocity movement for a Ptr. If a Ptr is already queued, its velocity will
/// be overwritten. Valid until the next call to applyQueuedMovement. /// be overwritten. Valid until the next call to applyQueuedMovement.
void queueObjectMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &velocity); void queueObjectMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &velocity);

@ -513,6 +513,9 @@ namespace MWRender
if (mShadowUniform) if (mShadowUniform)
stateset->addUniform(mShadowUniform); stateset->addUniform(mShadowUniform);
stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
stateset->setRenderBinMode(osg::StateSet::OVERRIDE_RENDERBIN_DETAILS);
// FIXME: overriding diffuse/ambient/emissive colors // FIXME: overriding diffuse/ambient/emissive colors
osg::Material* material = new osg::Material; osg::Material* material = new osg::Material;
material->setColorMode(osg::Material::OFF); material->setColorMode(osg::Material::OFF);
@ -1369,7 +1372,7 @@ namespace MWRender
osg::Group* sheathParent = findVisitor.mFoundNode; osg::Group* sheathParent = findVisitor.mFoundNode;
if (sheathParent) if (sheathParent)
{ {
osg::Node* copy = osg::clone(nodePair.first, osg::CopyOp::DEEP_COPY_NODES); osg::Node* copy = static_cast<osg::Node*>(nodePair.first->clone(osg::CopyOp::DEEP_COPY_NODES));
sheathParent->addChild(copy); sheathParent->addChild(copy);
} }
} }
@ -1741,31 +1744,16 @@ namespace MWRender
if (mTransparencyUpdater == nullptr) if (mTransparencyUpdater == nullptr)
{ {
mTransparencyUpdater = new TransparencyUpdater(alpha, mResourceSystem->getSceneManager()->getShaderManager().getShadowMapAlphaTestEnableUniform()); mTransparencyUpdater = new TransparencyUpdater(alpha, mResourceSystem->getSceneManager()->getShaderManager().getShadowMapAlphaTestEnableUniform());
mObjectRoot->addUpdateCallback(mTransparencyUpdater); mObjectRoot->addCullCallback(mTransparencyUpdater);
} }
else else
mTransparencyUpdater->setAlpha(alpha); mTransparencyUpdater->setAlpha(alpha);
} }
else else
{ {
mObjectRoot->removeUpdateCallback(mTransparencyUpdater); mObjectRoot->removeCullCallback(mTransparencyUpdater);
mTransparencyUpdater = nullptr; mTransparencyUpdater = nullptr;
mObjectRoot->setStateSet(nullptr);
}
setRenderBin();
}
void Animation::setRenderBin()
{
if (mAlpha != 1.f)
{
osg::StateSet* stateset = mObjectRoot->getOrCreateStateSet();
stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
stateset->setRenderBinMode(osg::StateSet::OVERRIDE_RENDERBIN_DETAILS);
} }
else if (osg::StateSet* stateset = mObjectRoot->getStateSet())
stateset->setRenderBinToInherit();
} }
void Animation::setLightEffect(float effect) void Animation::setLightEffect(float effect)

@ -336,9 +336,6 @@ protected:
*/ */
virtual void addControllers(); virtual void addControllers();
/// Set the render bin for this animation's object root. May be customized by subclasses.
virtual void setRenderBin();
public: public:
Animation(const MWWorld::Ptr &ptr, osg::ref_ptr<osg::Group> parentNode, Resource::ResourceSystem* resourceSystem); Animation(const MWWorld::Ptr &ptr, osg::ref_ptr<osg::Group> parentNode, Resource::ResourceSystem* resourceSystem);

@ -168,11 +168,10 @@ void LocalMap::saveFogOfWar(MWWorld::CellStore* cell)
osg::ref_ptr<osg::Camera> LocalMap::createOrthographicCamera(float x, float y, float width, float height, const osg::Vec3d& upVector, float zmin, float zmax) osg::ref_ptr<osg::Camera> LocalMap::createOrthographicCamera(float x, float y, float width, float height, const osg::Vec3d& upVector, float zmin, float zmax)
{ {
osg::ref_ptr<osg::Camera> camera (new osg::Camera); osg::ref_ptr<osg::Camera> camera (new osg::Camera);
camera->setProjectionMatrixAsOrtho(-width/2, width/2, -height/2, height/2, 5, (zmax-zmin) + 10); camera->setProjectionMatrixAsOrtho(-width/2, width/2, -height/2, height/2, 5, (zmax-zmin) + 10);
camera->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR); camera->setComputeNearFarMode(osg::Camera::DO_NOT_COMPUTE_NEAR_FAR);
camera->setViewMatrixAsLookAt(osg::Vec3d(x, y, zmax + 5), osg::Vec3d(x, y, zmin), upVector); camera->setViewMatrixAsLookAt(osg::Vec3d(x, y, zmax + 5), osg::Vec3d(x, y, zmin), upVector);
camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT);
camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT); camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT, osg::Camera::PIXEL_BUFFER_RTT);
camera->setClearColor(osg::Vec4(0.f, 0.f, 0.f, 1.f)); camera->setClearColor(osg::Vec4(0.f, 0.f, 0.f, 1.f));
camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
@ -360,11 +359,6 @@ void LocalMap::requestExteriorMap(const MWWorld::CellStore* cell)
osg::ref_ptr<osg::Camera> camera = createOrthographicCamera(x*mMapWorldSize + mMapWorldSize/2.f, y*mMapWorldSize + mMapWorldSize/2.f, mMapWorldSize, mMapWorldSize, osg::ref_ptr<osg::Camera> camera = createOrthographicCamera(x*mMapWorldSize + mMapWorldSize/2.f, y*mMapWorldSize + mMapWorldSize/2.f, mMapWorldSize, mMapWorldSize,
osg::Vec3d(0,1,0), zmin, zmax); osg::Vec3d(0,1,0), zmin, zmax);
camera->getOrCreateUserDataContainer()->addDescription("NoTerrainLod");
std::ostringstream stream;
stream << x << " " << y;
camera->getOrCreateUserDataContainer()->addDescription(stream.str());
setupRenderToTexture(camera, cell->getCell()->getGridX(), cell->getCell()->getGridY()); setupRenderToTexture(camera, cell->getCell()->getGridX(), cell->getCell()->getGridY());
MapSegment& segment = mSegments[std::make_pair(cell->getCell()->getGridX(), cell->getCell()->getGridY())]; MapSegment& segment = mSegments[std::make_pair(cell->getCell()->getGridX(), cell->getCell()->getGridY())];

@ -435,12 +435,10 @@ void NpcAnimation::setRenderBin()
osgUtil::RenderBin::addRenderBinPrototype("DepthClear", depthClearBin); osgUtil::RenderBin::addRenderBinPrototype("DepthClear", depthClearBin);
prototypeAdded = true; prototypeAdded = true;
} }
mObjectRoot->getOrCreateStateSet()->setRenderBinDetails(RenderBin_FirstPerson, "DepthClear", osg::StateSet::OVERRIDE_RENDERBIN_DETAILS);
osg::StateSet* stateset = mObjectRoot->getOrCreateStateSet();
stateset->setRenderBinDetails(RenderBin_FirstPerson, "DepthClear", osg::StateSet::OVERRIDE_RENDERBIN_DETAILS);
} }
else else if (osg::StateSet* stateset = mObjectRoot->getStateSet())
Animation::setRenderBin(); stateset->setRenderBinToInherit();
} }
void NpcAnimation::rebuild() void NpcAnimation::rebuild()

@ -88,7 +88,7 @@ private:
void addPartGroup(int group, int priority, const std::vector<ESM::PartReference> &parts, void addPartGroup(int group, int priority, const std::vector<ESM::PartReference> &parts,
bool enchantedGlow=false, osg::Vec4f* glowColor=nullptr); bool enchantedGlow=false, osg::Vec4f* glowColor=nullptr);
virtual void setRenderBin(); void setRenderBin();
osg::ref_ptr<NeckController> mFirstPersonNeckController; osg::ref_ptr<NeckController> mFirstPersonNeckController;

@ -0,0 +1,781 @@
#include "objectpaging.hpp"
#include <unordered_map>
#include <osg/Version>
#include <osg/LOD>
#include <osg/Switch>
#include <osg/MatrixTransform>
#include <osg/Material>
#include <osgUtil/IncrementalCompileOperation>
#include <components/esm/esmreader.hpp>
#include <components/misc/resourcehelpers.hpp>
#include <components/resource/scenemanager.hpp>
#include <components/sceneutil/optimizer.hpp>
#include <components/sceneutil/clone.hpp>
#include <components/sceneutil/util.hpp>
#include <components/vfs/manager.hpp>
#include <osgParticle/ParticleProcessor>
#include <osgParticle/ParticleSystemUpdater>
#include <components/sceneutil/lightmanager.hpp>
#include <components/sceneutil/morphgeometry.hpp>
#include <components/sceneutil/riggeometry.hpp>
#include <components/settings/settings.hpp>
#include <components/misc/rng.hpp>
#include "apps/openmw/mwworld/esmstore.hpp"
#include "apps/openmw/mwbase/environment.hpp"
#include "apps/openmw/mwbase/world.hpp"
#include "vismask.hpp"
namespace MWRender
{
bool typeFilter(int type, bool far)
{
switch (type)
{
case ESM::REC_STAT:
case ESM::REC_ACTI:
case ESM::REC_DOOR:
return true;
case ESM::REC_CONT:
return !far;
default:
return false;
}
}
std::string getModel(int type, const std::string& id, const MWWorld::ESMStore& store)
{
switch (type)
{
case ESM::REC_STAT:
return store.get<ESM::Static>().searchStatic(id)->mModel;
case ESM::REC_ACTI:
return store.get<ESM::Activator>().searchStatic(id)->mModel;
case ESM::REC_DOOR:
return store.get<ESM::Door>().searchStatic(id)->mModel;
case ESM::REC_CONT:
return store.get<ESM::Container>().searchStatic(id)->mModel;
default:
return std::string();
}
}
osg::ref_ptr<osg::Node> ObjectPaging::getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile)
{
if (activeGrid && !mActiveGrid)
return nullptr;
ChunkId id = std::make_tuple(center, size, activeGrid);
osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(id);
if (obj)
return obj->asNode();
else
{
osg::ref_ptr<osg::Node> node = createChunk(size, center, activeGrid, viewPoint, compile);
mCache->addEntryToObjectCache(id, node.get());
return node;
}
}
class CanOptimizeCallback : public SceneUtil::Optimizer::IsOperationPermissibleForObjectCallback
{
public:
virtual bool isOperationPermissibleForObjectImplementation(const SceneUtil::Optimizer* optimizer, const osg::Drawable* node,unsigned int option) const
{
return true;
}
virtual bool isOperationPermissibleForObjectImplementation(const SceneUtil::Optimizer* optimizer, const osg::Node* node,unsigned int option) const
{
return (node->getDataVariance() != osg::Object::DYNAMIC);
}
};
class CopyOp : public osg::CopyOp
{
public:
bool mOptimizeBillboards = true;
float mSqrDistance = 0.f;
osg::Vec3f mViewVector;
mutable std::vector<const osg::Node*> mNodePath;
void copy(const osg::Node* toCopy, osg::Group* attachTo)
{
const osg::Group* groupToCopy = toCopy->asGroup();
if (toCopy->getStateSet() || toCopy->asTransform() || !groupToCopy)
attachTo->addChild(operator()(toCopy));
else
{
for (unsigned int i=0; i<groupToCopy->getNumChildren(); ++i)
attachTo->addChild(operator()(groupToCopy->getChild(i)));
}
}
virtual osg::Node* operator() (const osg::Node* node) const
{
if (const osg::Drawable* d = node->asDrawable())
return operator()(d);
if (dynamic_cast<const osgParticle::ParticleProcessor*>(node))
return nullptr;
if (dynamic_cast<const osgParticle::ParticleSystemUpdater*>(node))
return nullptr;
if (const osg::Switch* sw = node->asSwitch())
{
osg::Group* n = new osg::Group;
for (unsigned int i=0; i<sw->getNumChildren(); ++i)
if (sw->getValue(i))
n->addChild(operator()(sw->getChild(i)));
n->setDataVariance(osg::Object::STATIC);
return n;
}
if (const osg::LOD* lod = dynamic_cast<const osg::LOD*>(node))
{
osg::Group* n = new osg::Group;
for (unsigned int i=0; i<lod->getNumChildren(); ++i)
if (lod->getMinRange(i) * lod->getMinRange(i) <= mSqrDistance && mSqrDistance < lod->getMaxRange(i) * lod->getMaxRange(i))
n->addChild(operator()(lod->getChild(i)));
n->setDataVariance(osg::Object::STATIC);
return n;
}
mNodePath.push_back(node);
osg::Node* cloned = static_cast<osg::Node*>(node->clone(*this));
cloned->setDataVariance(osg::Object::STATIC);
cloned->setUserDataContainer(nullptr);
cloned->setName("");
mNodePath.pop_back();
handleCallbacks(node, cloned);
return cloned;
}
void handleCallbacks(const osg::Node* node, osg::Node *cloned) const
{
for (const osg::Callback* callback = node->getCullCallback(); callback != nullptr; callback = callback->getNestedCallback())
{
if (callback->className() == std::string("BillboardCallback"))
{
if (mOptimizeBillboards)
{
handleBillboard(cloned);
continue;
}
else
cloned->setDataVariance(osg::Object::DYNAMIC);
}
if (node->getCullCallback()->getNestedCallback())
{
osg::Callback *clonedCallback = osg::clone(callback, osg::CopyOp::SHALLOW_COPY);
clonedCallback->setNestedCallback(nullptr);
cloned->addCullCallback(clonedCallback);
}
else
cloned->addCullCallback(const_cast<osg::Callback*>(callback));
}
}
void handleBillboard(osg::Node* node) const
{
osg::Transform* transform = node->asTransform();
if (!transform) return;
osg::MatrixTransform* matrixTransform = transform->asMatrixTransform();
if (!matrixTransform) return;
osg::Matrix worldToLocal = osg::Matrix::identity();
for (auto node : mNodePath)
if (const osg::Transform* t = node->asTransform())
t->computeWorldToLocalMatrix(worldToLocal, nullptr);
worldToLocal = osg::Matrix::orthoNormal(worldToLocal);
osg::Matrix billboardMatrix;
osg::Vec3f viewVector = -(mViewVector + worldToLocal.getTrans());
viewVector.normalize();
osg::Vec3f right = viewVector ^ osg::Vec3f(0,0,1);
right.normalize();
osg::Vec3f up = right ^ viewVector;
up.normalize();
billboardMatrix.makeLookAt(osg::Vec3f(0,0,0), viewVector, up);
billboardMatrix.invert(billboardMatrix);
const osg::Matrix& oldMatrix = matrixTransform->getMatrix();
float mag[3]; // attempt to preserve scale
for (int i=0;i<3;++i)
mag[i] = std::sqrt(oldMatrix(0,i) * oldMatrix(0,i) + oldMatrix(1,i) * oldMatrix(1,i) + oldMatrix(2,i) * oldMatrix(2,i));
osg::Matrix newMatrix;
worldToLocal.setTrans(0,0,0);
newMatrix *= worldToLocal;
newMatrix.preMult(billboardMatrix);
newMatrix.preMultScale(osg::Vec3f(mag[0], mag[1], mag[2]));
newMatrix.setTrans(oldMatrix.getTrans());
matrixTransform->setMatrix(newMatrix);
}
virtual osg::Drawable* operator() (const osg::Drawable* drawable) const
{
if (dynamic_cast<const osgParticle::ParticleSystem*>(drawable))
return nullptr;
if (const SceneUtil::RigGeometry* rig = dynamic_cast<const SceneUtil::RigGeometry*>(drawable))
return operator()(rig->getSourceGeometry());
if (const SceneUtil::MorphGeometry* morph = dynamic_cast<const SceneUtil::MorphGeometry*>(drawable))
return operator()(morph->getSourceGeometry());
if (getCopyFlags() & DEEP_COPY_DRAWABLES)
{
osg::Drawable* d = static_cast<osg::Drawable*>(drawable->clone(*this));
d->setDataVariance(osg::Object::STATIC);
d->setUserDataContainer(nullptr);
d->setName("");
return d;
}
else
return const_cast<osg::Drawable*>(drawable);
}
virtual osg::Callback* operator() (const osg::Callback* callback) const
{
return nullptr;
}
};
class TemplateRef : public osg::Object
{
public:
TemplateRef() {}
TemplateRef(const TemplateRef& copy, const osg::CopyOp&) : mObjects(copy.mObjects) {}
META_Object(MWRender, TemplateRef)
std::vector<osg::ref_ptr<const Object>> mObjects;
};
class RefnumSet : public osg::Object
{
public:
RefnumSet(){}
RefnumSet(const RefnumSet& copy, const osg::CopyOp&) : mRefnums(copy.mRefnums) {}
META_Object(MWRender, RefnumSet)
std::set<ESM::RefNum> mRefnums;
};
class AnalyzeVisitor : public osg::NodeVisitor
{
public:
AnalyzeVisitor()
: osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
, mCurrentStateSet(nullptr) {}
typedef std::unordered_map<osg::StateSet*, unsigned int> StateSetCounter;
struct Result
{
StateSetCounter mStateSetCounter;
unsigned int mNumVerts = 0;
};
virtual void apply(osg::Node& node)
{
if (node.getStateSet())
mCurrentStateSet = node.getStateSet();
traverse(node);
}
virtual void apply(osg::Geometry& geom)
{
mResult.mNumVerts += geom.getVertexArray()->getNumElements();
++mResult.mStateSetCounter[mCurrentStateSet];
++mGlobalStateSetCounter[mCurrentStateSet];
}
Result retrieveResult()
{
Result result = mResult;
mResult = Result();
mCurrentStateSet = nullptr;
return result;
}
void addInstance(const Result& result)
{
for (auto pair : result.mStateSetCounter)
mGlobalStateSetCounter[pair.first] += pair.second;
}
float getMergeBenefit(const Result& result)
{
if (result.mStateSetCounter.empty()) return 1;
float mergeBenefit = 0;
for (auto pair : result.mStateSetCounter)
{
mergeBenefit += mGlobalStateSetCounter[pair.first];
}
mergeBenefit /= result.mStateSetCounter.size();
return mergeBenefit;
}
Result mResult;
osg::StateSet* mCurrentStateSet;
StateSetCounter mGlobalStateSetCounter;
};
class DebugVisitor : public osg::NodeVisitor
{
public:
DebugVisitor() : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) {}
virtual void apply(osg::Drawable& node)
{
osg::ref_ptr<osg::Material> m (new osg::Material);
osg::Vec4f color(Misc::Rng::rollProbability(), Misc::Rng::rollProbability(), Misc::Rng::rollProbability(), 0.f);
color.normalize();
m->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.1f,0.1f,0.1f,1.f));
m->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.1f,0.1f,0.1f,1.f));
m->setColorMode(osg::Material::OFF);
m->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(color));
osg::ref_ptr<osg::StateSet> stateset = node.getStateSet() ? osg::clone(node.getStateSet(), osg::CopyOp::SHALLOW_COPY) : new osg::StateSet;
stateset->setAttribute(m);
stateset->addUniform(new osg::Uniform("colorMode", 0));
node.setStateSet(stateset);
}
};
class AddRefnumMarkerVisitor : public osg::NodeVisitor
{
public:
AddRefnumMarkerVisitor(const ESM::RefNum &refnum) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN), mRefnum(refnum) {}
ESM::RefNum mRefnum;
virtual void apply(osg::Geometry &node)
{
osg::ref_ptr<RefnumMarker> marker (new RefnumMarker);
marker->mRefnum = mRefnum;
if (osg::Array* array = node.getVertexArray())
marker->mNumVertices = array->getNumElements();
node.getOrCreateUserDataContainer()->addUserObject(marker);
}
};
ObjectPaging::ObjectPaging(Resource::SceneManager* sceneManager)
: GenericResourceManager<ChunkId>(nullptr)
, mSceneManager(sceneManager)
, mRefTrackerLocked(false)
{
mActiveGrid = Settings::Manager::getBool("object paging active grid", "Terrain");
mDebugBatches = Settings::Manager::getBool("object paging debug batches", "Terrain");
mMergeFactor = Settings::Manager::getFloat("object paging merge factor", "Terrain");
mMinSize = Settings::Manager::getFloat("object paging min size", "Terrain");
mMinSizeMergeFactor = Settings::Manager::getFloat("object paging min size merge factor", "Terrain");
mMinSizeCostMultiplier = Settings::Manager::getFloat("object paging min size cost multiplier", "Terrain");
}
osg::ref_ptr<osg::Node> ObjectPaging::createChunk(float size, const osg::Vec2f& center, bool activeGrid, const osg::Vec3f& viewPoint, bool compile)
{
osg::Vec2i startCell = osg::Vec2i(std::floor(center.x() - size/2.f), std::floor(center.y() - size/2.f));
osg::Vec3f worldCenter = osg::Vec3f(center.x(), center.y(), 0)*ESM::Land::REAL_SIZE;
osg::Vec3f relativeViewPoint = viewPoint - worldCenter;
std::map<ESM::RefNum, ESM::CellRef> refs;
std::vector<ESM::ESMReader> esm;
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
for (int cellX = startCell.x(); cellX < startCell.x() + size; ++cellX)
{
for (int cellY = startCell.y(); cellY < startCell.y() + size; ++cellY)
{
const ESM::Cell* cell = store.get<ESM::Cell>().searchStatic(cellX, cellY);
if (!cell) continue;
for (size_t i=0; i<cell->mContextList.size(); ++i)
{
try
{
unsigned int index = cell->mContextList.at(i).index;
if (esm.size()<=index)
esm.resize(index+1);
cell->restore(esm[index], i);
ESM::CellRef ref;
ref.mRefNum.mContentFile = ESM::RefNum::RefNum_NoContentFile;
bool deleted = false;
while(cell->getNextRef(esm[index], ref, deleted))
{
Misc::StringUtils::lowerCaseInPlace(ref.mRefID);
if (std::find(cell->mMovedRefs.begin(), cell->mMovedRefs.end(), ref.mRefNum) != cell->mMovedRefs.end()) continue;
int type = store.findStatic(ref.mRefID);
if (!typeFilter(type,size>=2)) continue;
if (deleted) { refs.erase(ref.mRefNum); continue; }
refs[ref.mRefNum] = ref;
}
}
catch (std::exception& e)
{
continue;
}
}
for (ESM::CellRefTracker::const_iterator it = cell->mLeasedRefs.begin(); it != cell->mLeasedRefs.end(); ++it)
{
ESM::CellRef ref = it->first;
Misc::StringUtils::lowerCaseInPlace(ref.mRefID);
bool deleted = it->second;
if (deleted) { refs.erase(ref.mRefNum); continue; }
int type = store.findStatic(ref.mRefID);
if (!typeFilter(type,size>=2)) continue;
refs[ref.mRefNum] = ref;
}
}
}
if (activeGrid)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mRefTrackerMutex);
for (auto ref : getRefTracker().mBlacklist)
refs.erase(ref);
}
osg::Vec2f minBound = (center - osg::Vec2f(size/2.f, size/2.f));
osg::Vec2f maxBound = (center + osg::Vec2f(size/2.f, size/2.f));
struct InstanceList
{
std::vector<const ESM::CellRef*> mInstances;
AnalyzeVisitor::Result mAnalyzeResult;
bool mNeedCompile = false;
};
typedef std::map<osg::ref_ptr<const osg::Node>, InstanceList> NodeMap;
NodeMap nodes;
osg::ref_ptr<RefnumSet> refnumSet = activeGrid ? new RefnumSet : nullptr;
AnalyzeVisitor analyzeVisitor;
float minSize = mMinSize;
if (mMinSizeMergeFactor)
minSize *= mMinSizeMergeFactor;
for (const auto& pair : refs)
{
const ESM::CellRef& ref = pair.second;
osg::Vec3f pos = ref.mPos.asVec3();
if (size < 1.f)
{
osg::Vec3f cellPos = pos / ESM::Land::REAL_SIZE;
if ((minBound.x() > std::floor(minBound.x()) && cellPos.x() < minBound.x()) || (minBound.y() > std::floor(minBound.y()) && cellPos.y() < minBound.y())
|| (maxBound.x() < std::ceil(maxBound.x()) && cellPos.x() >= maxBound.x()) || (minBound.y() < std::ceil(maxBound.y()) && cellPos.y() >= maxBound.y()))
continue;
}
float dSqr = (viewPoint - pos).length2();
if (!activeGrid)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mSizeCacheMutex);
SizeCache::iterator found = mSizeCache.find(pair.first);
if (found != mSizeCache.end() && found->second < dSqr*minSize*minSize)
continue;
}
if (ref.mRefID == "prisonmarker" || ref.mRefID == "divinemarker" || ref.mRefID == "templemarker" || ref.mRefID == "northmarker")
continue; // marker objects that have a hardcoded function in the game logic, should be hidden from the player
int type = store.findStatic(ref.mRefID);
std::string model = getModel(type, ref.mRefID, store);
if (model.empty()) continue;
model = "meshes/" + model;
if (activeGrid && type != ESM::REC_STAT)
{
model = Misc::ResourceHelpers::correctActorModelPath(model, mSceneManager->getVFS());
std::string kfname = Misc::StringUtils::lowerCase(model);
if(kfname.size() > 4 && kfname.compare(kfname.size()-4, 4, ".nif") == 0)
{
kfname.replace(kfname.size()-4, 4, ".kf");
if (mSceneManager->getVFS()->exists(kfname))
continue;
}
}
osg::ref_ptr<const osg::Node> cnode = mSceneManager->getTemplate(model, false);
if (activeGrid)
{
if (cnode->getNumChildrenRequiringUpdateTraversal() > 0 || SceneUtil::hasUserDescription(cnode, Constants::NightDayLabel) || SceneUtil::hasUserDescription(cnode, Constants::HerbalismLabel))
continue;
else
refnumSet->mRefnums.insert(pair.first);
}
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mRefTrackerMutex);
if (getRefTracker().mDisabled.count(pair.first))
continue;
}
float radius2 = cnode->getBound().radius2() * ref.mScale*ref.mScale;
if (radius2 < dSqr*minSize*minSize && !activeGrid)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mSizeCacheMutex);
mSizeCache[pair.first] = radius2;
continue;
}
auto emplaced = nodes.emplace(cnode, InstanceList());
if (emplaced.second)
{
const_cast<osg::Node*>(cnode.get())->accept(analyzeVisitor); // const-trickery required because there is no const version of NodeVisitor
emplaced.first->second.mAnalyzeResult = analyzeVisitor.retrieveResult();
emplaced.first->second.mNeedCompile = compile && cnode->referenceCount() <= 3;
}
else
analyzeVisitor.addInstance(emplaced.first->second.mAnalyzeResult);
emplaced.first->second.mInstances.push_back(&ref);
}
osg::ref_ptr<osg::Group> group = new osg::Group;
osg::ref_ptr<osg::Group> mergeGroup = new osg::Group;
osg::ref_ptr<TemplateRef> templateRefs = new TemplateRef;
osgUtil::StateToCompile stateToCompile(0, nullptr);
CopyOp copyop;
for (const auto& pair : nodes)
{
const osg::Node* cnode = pair.first;
const AnalyzeVisitor::Result& analyzeResult = pair.second.mAnalyzeResult;
float mergeCost = analyzeResult.mNumVerts * size;
float mergeBenefit = analyzeVisitor.getMergeBenefit(analyzeResult) * mMergeFactor;
bool merge = mergeBenefit > mergeCost;
float minSizeMerged = mMinSize;
float factor2 = mergeBenefit > 0 ? std::min(1.f, mergeCost * mMinSizeCostMultiplier / mergeBenefit) : 1;
float minSizeMergeFactor2 = (1-factor2) * mMinSizeMergeFactor + factor2;
if (minSizeMergeFactor2 > 0)
minSizeMerged *= minSizeMergeFactor2;
unsigned int numinstances = 0;
for (auto cref : pair.second.mInstances)
{
const ESM::CellRef& ref = *cref;
osg::Vec3f pos = ref.mPos.asVec3();
if (!activeGrid && minSizeMerged != minSize && cnode->getBound().radius2() * cref->mScale*cref->mScale < (viewPoint-pos).length2()*minSizeMerged*minSizeMerged)
continue;
osg::Matrixf matrix;
matrix.preMultTranslate(pos - worldCenter);
matrix.preMultRotate( osg::Quat(ref.mPos.rot[2], osg::Vec3f(0,0,-1)) *
osg::Quat(ref.mPos.rot[1], osg::Vec3f(0,-1,0)) *
osg::Quat(ref.mPos.rot[0], osg::Vec3f(-1,0,0)) );
matrix.preMultScale(osg::Vec3f(ref.mScale, ref.mScale, ref.mScale));
osg::ref_ptr<osg::MatrixTransform> trans = new osg::MatrixTransform(matrix);
trans->setDataVariance(osg::Object::STATIC);
copyop.setCopyFlags(merge ? osg::CopyOp::DEEP_COPY_NODES|osg::CopyOp::DEEP_COPY_DRAWABLES : osg::CopyOp::DEEP_COPY_NODES);
copyop.mOptimizeBillboards = (size > 1/4.f);
copyop.mNodePath.push_back(trans);
copyop.mSqrDistance = (viewPoint - pos).length2();
copyop.mViewVector = (viewPoint - worldCenter);
copyop.copy(cnode, trans);
if (activeGrid)
{
if (merge)
{
AddRefnumMarkerVisitor visitor(ref.mRefNum);
trans->accept(visitor);
}
else
{
osg::ref_ptr<RefnumMarker> marker = new RefnumMarker; marker->mRefnum = ref.mRefNum;
trans->getOrCreateUserDataContainer()->addUserObject(marker);
}
}
osg::Group* attachTo = merge ? mergeGroup : group;
attachTo->addChild(trans);
++numinstances;
}
if (numinstances > 0)
{
// add a ref to the original template, to hint to the cache that it's still being used and should be kept in cache
templateRefs->mObjects.push_back(cnode);
if (pair.second.mNeedCompile)
{
int mode = osgUtil::GLObjectsVisitor::COMPILE_STATE_ATTRIBUTES;
if (!merge)
mode |= osgUtil::GLObjectsVisitor::COMPILE_DISPLAY_LISTS;
stateToCompile._mode = mode;
const_cast<osg::Node*>(cnode)->accept(stateToCompile);
}
}
}
if (mergeGroup->getNumChildren())
{
SceneUtil::Optimizer optimizer;
if (size > 1/8.f)
{
optimizer.setViewPoint(relativeViewPoint);
optimizer.setMergeAlphaBlending(true);
}
optimizer.setIsOperationPermissibleForObjectCallback(new CanOptimizeCallback);
unsigned int options = SceneUtil::Optimizer::FLATTEN_STATIC_TRANSFORMS|SceneUtil::Optimizer::REMOVE_REDUNDANT_NODES|SceneUtil::Optimizer::MERGE_GEOMETRY;
optimizer.optimize(mergeGroup, options);
group->addChild(mergeGroup);
if (mDebugBatches)
{
DebugVisitor dv;
mergeGroup->accept(dv);
}
if (compile)
{
stateToCompile._mode = osgUtil::GLObjectsVisitor::COMPILE_DISPLAY_LISTS;
mergeGroup->accept(stateToCompile);
}
}
auto ico = mSceneManager->getIncrementalCompileOperation();
if (!stateToCompile.empty() && ico)
{
auto compileSet = new osgUtil::IncrementalCompileOperation::CompileSet(group);
compileSet->buildCompileMap(ico->getContextSet(), stateToCompile);
ico->add(compileSet, false);
}
group->getBound();
group->setNodeMask(Mask_Static);
osg::UserDataContainer* udc = group->getOrCreateUserDataContainer();
if (activeGrid)
{
udc->addUserObject(refnumSet);
group->addCullCallback(new SceneUtil::LightListCallback);
}
udc->addUserObject(templateRefs);
return group;
}
unsigned int ObjectPaging::getNodeMask()
{
return Mask_Static;
}
struct ClearCacheFunctor
{
void operator()(MWRender::ChunkId id, osg::Object* obj)
{
if (intersects(id, mPosition))
mToClear.insert(id);
}
bool intersects(ChunkId id, osg::Vec3f pos)
{
if (mActiveGridOnly && !std::get<2>(id)) return false;
pos /= ESM::Land::REAL_SIZE;
osg::Vec2f center = std::get<0>(id);
float halfSize = std::get<1>(id)/2;
return pos.x() >= center.x()-halfSize && pos.y() >= center.y()-halfSize && pos.x() <= center.x()+halfSize && pos.y() <= center.y()+halfSize;
}
osg::Vec3f mPosition;
std::set<MWRender::ChunkId> mToClear;
bool mActiveGridOnly = false;
};
bool ObjectPaging::enableObject(int type, const ESM::RefNum & refnum, const osg::Vec3f& pos, bool enabled)
{
if (!typeFilter(type, false))
return false;
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mRefTrackerMutex);
if (enabled && !getWritableRefTracker().mDisabled.erase(refnum)) return false;
if (!enabled && !getWritableRefTracker().mDisabled.insert(refnum).second) return false;
if (mRefTrackerLocked) return false;
}
ClearCacheFunctor ccf;
ccf.mPosition = pos;
mCache->call(ccf);
if (ccf.mToClear.empty()) return false;
for (auto chunk : ccf.mToClear)
mCache->removeFromObjectCache(chunk);
return true;
}
bool ObjectPaging::blacklistObject(int type, const ESM::RefNum & refnum, const osg::Vec3f& pos)
{
if (!typeFilter(type, false))
return false;
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mRefTrackerMutex);
if (!getWritableRefTracker().mBlacklist.insert(refnum).second) return false;
if (mRefTrackerLocked) return false;
}
ClearCacheFunctor ccf;
ccf.mPosition = pos;
ccf.mActiveGridOnly = true;
mCache->call(ccf);
if (ccf.mToClear.empty()) return false;
for (auto chunk : ccf.mToClear)
mCache->removeFromObjectCache(chunk);
return true;
}
void ObjectPaging::clear()
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mRefTrackerMutex);
mRefTrackerNew.mDisabled.clear();
mRefTrackerNew.mBlacklist.clear();
mRefTrackerLocked = true;
}
bool ObjectPaging::unlockCache()
{
if (!mRefTrackerLocked) return false;
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mRefTrackerMutex);
mRefTrackerLocked = false;
if (mRefTracker == mRefTrackerNew)
return false;
else
mRefTracker = mRefTrackerNew;
}
mCache->clear();
return true;
}
struct GetRefnumsFunctor
{
GetRefnumsFunctor(std::set<ESM::RefNum>& output) : mOutput(output) {}
void operator()(MWRender::ChunkId chunkId, osg::Object* obj)
{
if (!std::get<2>(chunkId)) return;
const osg::Vec2f& center = std::get<0>(chunkId);
bool activeGrid = (center.x() > mActiveGrid.x() || center.y() > mActiveGrid.y() || center.x() < mActiveGrid.z() || center.y() < mActiveGrid.w());
if (!activeGrid) return;
osg::UserDataContainer* udc = obj->getUserDataContainer();
if (udc && udc->getNumUserObjects())
{
RefnumSet* refnums = dynamic_cast<RefnumSet*>(udc->getUserObject(0));
if (!refnums) return;
mOutput.insert(refnums->mRefnums.begin(), refnums->mRefnums.end());
}
}
osg::Vec4i mActiveGrid;
std::set<ESM::RefNum>& mOutput;
};
void ObjectPaging::getPagedRefnums(const osg::Vec4i &activeGrid, std::set<ESM::RefNum> &out)
{
GetRefnumsFunctor grf(out);
grf.mActiveGrid = activeGrid;
mCache->call(grf);
}
void ObjectPaging::reportStats(unsigned int frameNumber, osg::Stats *stats) const
{
stats->setAttribute(frameNumber, "Object Chunk", mCache->getCacheSize());
}
}

@ -0,0 +1,92 @@
#ifndef OPENMW_COMPONENTS_ESMPAGING_CHUNKMANAGER_H
#define OPENMW_COMPONENTS_ESMPAGING_CHUNKMANAGER_H
#include <components/terrain/quadtreeworld.hpp>
#include <components/resource/resourcemanager.hpp>
#include <components/esm/loadcell.hpp>
#include <OpenThreads/Mutex>
namespace Resource
{
class SceneManager;
}
namespace MWWorld
{
class ESMStore;
}
namespace MWRender
{
typedef std::tuple<osg::Vec2f, float, bool> ChunkId; // Center, Size, ActiveGrid
class ObjectPaging : public Resource::GenericResourceManager<ChunkId>, public Terrain::QuadTreeWorld::ChunkManager
{
public:
ObjectPaging(Resource::SceneManager* sceneManager);
~ObjectPaging() = default;
osg::ref_ptr<osg::Node> getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) override;
osg::ref_ptr<osg::Node> createChunk(float size, const osg::Vec2f& center, bool activeGrid, const osg::Vec3f& viewPoint, bool compile);
virtual unsigned int getNodeMask() override;
/// @return true if view needs rebuild
bool enableObject(int type, const ESM::RefNum & refnum, const osg::Vec3f& pos, bool enabled);
/// @return true if view needs rebuild
bool blacklistObject(int type, const ESM::RefNum & refnum, const osg::Vec3f& pos);
void clear();
/// Must be called after clear() before rendering starts.
/// @return true if view needs rebuild
bool unlockCache();
void reportStats(unsigned int frameNumber, osg::Stats* stats) const override;
void getPagedRefnums(const osg::Vec4i &activeGrid, std::set<ESM::RefNum> &out);
private:
Resource::SceneManager* mSceneManager;
bool mActiveGrid;
bool mDebugBatches;
float mMergeFactor;
float mMinSize;
float mMinSizeMergeFactor;
float mMinSizeCostMultiplier;
OpenThreads::Mutex mRefTrackerMutex;
struct RefTracker
{
std::set<ESM::RefNum> mDisabled;
std::set<ESM::RefNum> mBlacklist;
bool operator==(const RefTracker&other) { return mDisabled == other.mDisabled && mBlacklist == other.mBlacklist; }
};
RefTracker mRefTracker;
RefTracker mRefTrackerNew;
bool mRefTrackerLocked;
const RefTracker& getRefTracker() const { return mRefTracker; }
RefTracker& getWritableRefTracker() { return mRefTrackerLocked ? mRefTrackerNew : mRefTracker; }
OpenThreads::Mutex mSizeCacheMutex;
typedef std::map<ESM::RefNum, float> SizeCache;
SizeCache mSizeCache;
};
class RefnumMarker : public osg::Object
{
public:
RefnumMarker() : mNumVertices(0) {}
RefnumMarker(const RefnumMarker &copy, osg::CopyOp co) : mRefnum(copy.mRefnum), mNumVertices(copy.mNumVertices) {}
META_Object(MWRender, RefnumMarker)
ESM::RefNum mRefnum;
unsigned int mNumVertices;
};
}
#endif

@ -70,6 +70,8 @@
#include "actorspaths.hpp" #include "actorspaths.hpp"
#include "recastmesh.hpp" #include "recastmesh.hpp"
#include "fogmanager.hpp" #include "fogmanager.hpp"
#include "objectpaging.hpp"
namespace MWRender namespace MWRender
{ {
@ -258,7 +260,6 @@ namespace MWRender
{ {
mViewer->setIncrementalCompileOperation(new osgUtil::IncrementalCompileOperation); mViewer->setIncrementalCompileOperation(new osgUtil::IncrementalCompileOperation);
mViewer->getIncrementalCompileOperation()->setTargetFrameRate(Settings::Manager::getFloat("target framerate", "Cells")); mViewer->getIncrementalCompileOperation()->setTargetFrameRate(Settings::Manager::getFloat("target framerate", "Cells"));
mViewer->getIncrementalCompileOperation()->setMaximumNumOfObjectsToCompilePerFrame(100);
} }
mResourceSystem->getSceneManager()->setIncrementalCompileOperation(mViewer->getIncrementalCompileOperation()); mResourceSystem->getSceneManager()->setIncrementalCompileOperation(mViewer->getIncrementalCompileOperation());
@ -286,6 +287,12 @@ namespace MWRender
mTerrain.reset(new Terrain::QuadTreeWorld( mTerrain.reset(new Terrain::QuadTreeWorld(
sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile, Mask_Debug, sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile, Mask_Debug,
compMapResolution, compMapLevel, lodFactor, vertexLodMod, maxCompGeometrySize)); compMapResolution, compMapLevel, lodFactor, vertexLodMod, maxCompGeometrySize));
if (Settings::Manager::getBool("object paging", "Terrain"))
{
mObjectPaging.reset(new ObjectPaging(mResourceSystem->getSceneManager()));
static_cast<Terrain::QuadTreeWorld*>(mTerrain.get())->addChunkManager(mObjectPaging.get());
mResourceSystem->addResourceManager(mObjectPaging.get());
}
} }
else else
mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile, Mask_Debug)); mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile, Mask_Debug));
@ -970,20 +977,14 @@ namespace MWRender
renderCameraToImage(rttCamera.get(),image,w,h); renderCameraToImage(rttCamera.get(),image,w,h);
} }
osg::Vec4f RenderingManager::getScreenBounds(const MWWorld::Ptr& ptr) osg::Vec4f RenderingManager::getScreenBounds(const osg::BoundingBox &worldbb)
{ {
if (!ptr.getRefData().getBaseNode()) if (!worldbb.valid()) return osg::Vec4f();
return osg::Vec4f();
osg::ComputeBoundsVisitor computeBoundsVisitor;
computeBoundsVisitor.setTraversalMask(~(Mask_ParticleSystem|Mask_Effect));
ptr.getRefData().getBaseNode()->accept(computeBoundsVisitor);
osg::Matrix viewProj = mViewer->getCamera()->getViewMatrix() * mViewer->getCamera()->getProjectionMatrix(); osg::Matrix viewProj = mViewer->getCamera()->getViewMatrix() * mViewer->getCamera()->getProjectionMatrix();
float min_x = 1.0f, max_x = 0.0f, min_y = 1.0f, max_y = 0.0f; float min_x = 1.0f, max_x = 0.0f, min_y = 1.0f, max_y = 0.0f;
for (int i=0; i<8; ++i) for (int i=0; i<8; ++i)
{ {
osg::Vec3f corner = computeBoundsVisitor.getBoundingBox().corner(i); osg::Vec3f corner = worldbb.corner(i);
corner = corner * viewProj; corner = corner * viewProj;
float x = (corner.x() + 1.f) * 0.5f; float x = (corner.x() + 1.f) * 0.5f;
@ -1009,6 +1010,7 @@ namespace MWRender
{ {
RenderingManager::RayResult result; RenderingManager::RayResult result;
result.mHit = false; result.mHit = false;
result.mHitRefnum.mContentFile = -1;
result.mRatio = 0; result.mRatio = 0;
if (intersector->containsIntersections()) if (intersector->containsIntersections())
{ {
@ -1020,6 +1022,7 @@ namespace MWRender
result.mRatio = intersection.ratio; result.mRatio = intersection.ratio;
PtrHolder* ptrHolder = nullptr; PtrHolder* ptrHolder = nullptr;
std::vector<RefnumMarker*> refnumMarkers;
for (osg::NodePath::const_iterator it = intersection.nodePath.begin(); it != intersection.nodePath.end(); ++it) for (osg::NodePath::const_iterator it = intersection.nodePath.begin(); it != intersection.nodePath.end(); ++it)
{ {
osg::UserDataContainer* userDataContainer = (*it)->getUserDataContainer(); osg::UserDataContainer* userDataContainer = (*it)->getUserDataContainer();
@ -1029,11 +1032,25 @@ namespace MWRender
{ {
if (PtrHolder* p = dynamic_cast<PtrHolder*>(userDataContainer->getUserObject(i))) if (PtrHolder* p = dynamic_cast<PtrHolder*>(userDataContainer->getUserObject(i)))
ptrHolder = p; ptrHolder = p;
if (RefnumMarker* r = dynamic_cast<RefnumMarker*>(userDataContainer->getUserObject(i)))
refnumMarkers.push_back(r);
} }
} }
if (ptrHolder) if (ptrHolder)
result.mHitObject = ptrHolder->mPtr; result.mHitObject = ptrHolder->mPtr;
unsigned int vertexCounter = 0;
for (unsigned int i=0; i<refnumMarkers.size(); ++i)
{
unsigned int intersectionIndex = intersection.indexList.empty() ? 0 : intersection.indexList[0];
if (!refnumMarkers[i]->mNumVertices || (intersectionIndex >= vertexCounter && intersectionIndex < vertexCounter + refnumMarkers[i]->mNumVertices))
{
result.mHitRefnum = refnumMarkers[i]->mRefnum;
break;
}
vertexCounter += refnumMarkers[i]->mNumVertices;
}
} }
return result; return result;
@ -1046,6 +1063,7 @@ namespace MWRender
mIntersectionVisitor = new osgUtil::IntersectionVisitor; mIntersectionVisitor = new osgUtil::IntersectionVisitor;
mIntersectionVisitor->setTraversalNumber(mViewer->getFrameStamp()->getFrameNumber()); mIntersectionVisitor->setTraversalNumber(mViewer->getFrameStamp()->getFrameNumber());
mIntersectionVisitor->setFrameStamp(mViewer->getFrameStamp());
mIntersectionVisitor->setIntersector(intersector); mIntersectionVisitor->setIntersector(intersector);
int mask = ~0; int mask = ~0;
@ -1111,6 +1129,8 @@ namespace MWRender
mSky->setMoonColour(false); mSky->setMoonColour(false);
notifyWorldSpaceChanged(); notifyWorldSpaceChanged();
if (mObjectPaging)
mObjectPaging->clear();
} }
MWRender::Animation* RenderingManager::getAnimation(const MWWorld::Ptr &ptr) MWRender::Animation* RenderingManager::getAnimation(const MWWorld::Ptr &ptr)
@ -1467,4 +1487,43 @@ namespace MWRender
mRecastMesh->update(mNavigator.getRecastMeshTiles(), mNavigator.getSettings()); mRecastMesh->update(mNavigator.getRecastMeshTiles(), mNavigator.getSettings());
} }
void RenderingManager::setActiveGrid(const osg::Vec4i &grid)
{
mTerrain->setActiveGrid(grid);
}
bool RenderingManager::pagingEnableObject(int type, const MWWorld::ConstPtr& ptr, bool enabled)
{
if (!ptr.isInCell() || !ptr.getCell()->isExterior() || !mObjectPaging)
return false;
if (mObjectPaging->enableObject(type, ptr.getCellRef().getRefNum(), ptr.getCellRef().getPosition().asVec3(), enabled))
{
mTerrain->rebuildViews();
return true;
}
return false;
}
void RenderingManager::pagingBlacklistObject(int type, const MWWorld::ConstPtr &ptr)
{
if (!ptr.isInCell() || !ptr.getCell()->isExterior() || !mObjectPaging)
return;
const ESM::RefNum & refnum = ptr.getCellRef().getRefNum();
if (!refnum.hasContentFile()) return;
if (mObjectPaging->blacklistObject(type, refnum, ptr.getCellRef().getPosition().asVec3()))
mTerrain->rebuildViews();
}
bool RenderingManager::pagingUnlockCache()
{
if (mObjectPaging && mObjectPaging->unlockCache())
{
mTerrain->rebuildViews();
return true;
}
return false;
}
void RenderingManager::getPagedRefnums(const osg::Vec4i &activeGrid, std::set<ESM::RefNum> &out)
{
if (mObjectPaging)
mObjectPaging->getPagedRefnums(activeGrid, out);
}
} }

@ -42,6 +42,7 @@ namespace osgViewer
namespace ESM namespace ESM
{ {
struct Cell; struct Cell;
struct RefNum;
} }
namespace Terrain namespace Terrain
@ -84,6 +85,7 @@ namespace MWRender
class NavMesh; class NavMesh;
class ActorsPaths; class ActorsPaths;
class RecastMesh; class RecastMesh;
class ObjectPaging;
class RenderingManager : public MWRender::RenderingInterface class RenderingManager : public MWRender::RenderingInterface
{ {
@ -155,6 +157,7 @@ namespace MWRender
osg::Vec3f mHitNormalWorld; osg::Vec3f mHitNormalWorld;
osg::Vec3f mHitPointWorld; osg::Vec3f mHitPointWorld;
MWWorld::Ptr mHitObject; MWWorld::Ptr mHitObject;
ESM::RefNum mHitRefnum;
float mRatio; float mRatio;
}; };
@ -165,7 +168,7 @@ namespace MWRender
RayResult castCameraToViewportRay(const float nX, const float nY, float maxDistance, bool ignorePlayer, bool ignoreActors=false); RayResult castCameraToViewportRay(const float nX, const float nY, float maxDistance, bool ignorePlayer, bool ignoreActors=false);
/// Get the bounding box of the given object in screen coordinates as (minX, minY, maxX, maxY), with (0,0) being the top left corner. /// Get the bounding box of the given object in screen coordinates as (minX, minY, maxX, maxY), with (0,0) being the top left corner.
osg::Vec4f getScreenBounds(const MWWorld::Ptr& ptr); osg::Vec4f getScreenBounds(const osg::BoundingBox &worldbb);
void setSkyEnabled(bool enabled); void setSkyEnabled(bool enabled);
@ -237,6 +240,13 @@ namespace MWRender
void setNavMeshNumber(const std::size_t value); void setNavMeshNumber(const std::size_t value);
void setActiveGrid(const osg::Vec4i &grid);
bool pagingEnableObject(int type, const MWWorld::ConstPtr& ptr, bool enabled);
void pagingBlacklistObject(int type, const MWWorld::ConstPtr &ptr);
bool pagingUnlockCache();
void getPagedRefnums(const osg::Vec4i &activeGrid, std::set<ESM::RefNum> &out);
private: private:
void updateProjectionMatrix(); void updateProjectionMatrix();
void updateTextureFiltering(); void updateTextureFiltering();
@ -275,6 +285,7 @@ namespace MWRender
std::unique_ptr<Water> mWater; std::unique_ptr<Water> mWater;
std::unique_ptr<Terrain::World> mTerrain; std::unique_ptr<Terrain::World> mTerrain;
TerrainStorage* mTerrainStorage; TerrainStorage* mTerrainStorage;
std::unique_ptr<ObjectPaging> mObjectPaging;
std::unique_ptr<SkyManager> mSky; std::unique_ptr<SkyManager> mSky;
std::unique_ptr<FogManager> mFog; std::unique_ptr<FogManager> mFog;
std::unique_ptr<EffectManager> mEffectManager; std::unique_ptr<EffectManager> mEffectManager;

@ -8,6 +8,7 @@
#include <components/resource/resourcesystem.hpp> #include <components/resource/resourcesystem.hpp>
#include <components/resource/bulletshapemanager.hpp> #include <components/resource/bulletshapemanager.hpp>
#include <components/resource/keyframemanager.hpp> #include <components/resource/keyframemanager.hpp>
#include <components/vfs/manager.hpp>
#include <components/misc/resourcehelpers.hpp> #include <components/misc/resourcehelpers.hpp>
#include <components/misc/stringops.hpp> #include <components/misc/stringops.hpp>
#include <components/terrain/world.hpp> #include <components/terrain/world.hpp>
@ -65,23 +66,7 @@ namespace MWWorld
mTerrainView = mTerrain->createView(); mTerrainView = mTerrain->createView();
ListModelsVisitor visitor (mMeshes); ListModelsVisitor visitor (mMeshes);
if (cell->getState() == MWWorld::CellStore::State_Loaded) cell->forEach(visitor);
{
cell->forEach(visitor);
}
else
{
const std::vector<std::string>& objectIds = cell->getPreloadedIds();
// could possibly build the model list in the worker thread if we manage to make the Store thread safe
for (const std::string& id : objectIds)
{
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), id);
std::string model = ref.getPtr().getClass().getModel(ref.getPtr());
if (!model.empty())
mMeshes.push_back(model);
}
}
} }
virtual void abort() virtual void abort()
@ -97,7 +82,7 @@ namespace MWWorld
try try
{ {
mTerrain->cacheCell(mTerrainView.get(), mX, mY); mTerrain->cacheCell(mTerrainView.get(), mX, mY);
mPreloadedObjects.push_back(mLandManager->getLand(mX, mY)); mPreloadedObjects.insert(mLandManager->getLand(mX, mY));
} }
catch(std::exception& e) catch(std::exception& e)
{ {
@ -113,17 +98,7 @@ namespace MWWorld
{ {
mesh = Misc::ResourceHelpers::correctActorModelPath(mesh, mSceneManager->getVFS()); mesh = Misc::ResourceHelpers::correctActorModelPath(mesh, mSceneManager->getVFS());
if (mPreloadInstances) bool animated = false;
{
mPreloadedObjects.push_back(mSceneManager->cacheInstance(mesh));
mPreloadedObjects.push_back(mBulletShapeManager->cacheInstance(mesh));
}
else
{
mPreloadedObjects.push_back(mSceneManager->getTemplate(mesh));
mPreloadedObjects.push_back(mBulletShapeManager->getShape(mesh));
}
size_t slashpos = mesh.find_last_of("/\\"); size_t slashpos = mesh.find_last_of("/\\");
if (slashpos != std::string::npos && slashpos != mesh.size()-1) if (slashpos != std::string::npos && slashpos != mesh.size()-1)
{ {
@ -134,11 +109,23 @@ namespace MWWorld
if(kfname.size() > 4 && kfname.compare(kfname.size()-4, 4, ".nif") == 0) if(kfname.size() > 4 && kfname.compare(kfname.size()-4, 4, ".nif") == 0)
{ {
kfname.replace(kfname.size()-4, 4, ".kf"); kfname.replace(kfname.size()-4, 4, ".kf");
mPreloadedObjects.push_back(mKeyframeManager->get(kfname)); if (mSceneManager->getVFS()->exists(kfname))
{
mPreloadedObjects.insert(mKeyframeManager->get(kfname));
animated = true;
}
} }
} }
} }
if (mPreloadInstances && animated)
mPreloadedObjects.insert(mSceneManager->cacheInstance(mesh));
else
mPreloadedObjects.insert(mSceneManager->getTemplate(mesh));
if (mPreloadInstances)
mPreloadedObjects.insert(mBulletShapeManager->cacheInstance(mesh));
else
mPreloadedObjects.insert(mBulletShapeManager->getShape(mesh));
} }
catch (std::exception& e) catch (std::exception& e)
{ {
@ -166,24 +153,28 @@ namespace MWWorld
osg::ref_ptr<Terrain::View> mTerrainView; osg::ref_ptr<Terrain::View> mTerrainView;
// keep a ref to the loaded objects to make sure it stays loaded as long as this cell is in the preloaded state // keep a ref to the loaded objects to make sure it stays loaded as long as this cell is in the preloaded state
std::vector<osg::ref_ptr<const osg::Object> > mPreloadedObjects; std::set<osg::ref_ptr<const osg::Object> > mPreloadedObjects;
}; };
class TerrainPreloadItem : public SceneUtil::WorkItem class TerrainPreloadItem : public SceneUtil::WorkItem
{ {
public: public:
TerrainPreloadItem(const std::vector<osg::ref_ptr<Terrain::View> >& views, Terrain::World* world, const std::vector<osg::Vec3f>& preloadPositions) TerrainPreloadItem(const std::vector<osg::ref_ptr<Terrain::View> >& views, Terrain::World* world, const std::vector<CellPreloader::PositionCellGrid>& preloadPositions)
: mAbort(false) : mAbort(false)
, mProgress(views.size())
, mProgressRange(0)
, mTerrainViews(views) , mTerrainViews(views)
, mWorld(world) , mWorld(world)
, mPreloadPositions(preloadPositions) , mPreloadPositions(preloadPositions)
{ {
} }
void storeViews(double referenceTime) bool storeViews(double referenceTime)
{ {
for (unsigned int i=0; i<mTerrainViews.size() && i<mPreloadPositions.size(); ++i) for (unsigned int i=0; i<mTerrainViews.size() && i<mPreloadPositions.size(); ++i)
mWorld->storeView(mTerrainViews[i], referenceTime); if (!mWorld->storeView(mTerrainViews[i], referenceTime))
return false;
return true;
} }
virtual void doWork() virtual void doWork()
@ -191,7 +182,7 @@ namespace MWWorld
for (unsigned int i=0; i<mTerrainViews.size() && i<mPreloadPositions.size() && !mAbort; ++i) for (unsigned int i=0; i<mTerrainViews.size() && i<mPreloadPositions.size() && !mAbort; ++i)
{ {
mTerrainViews[i]->reset(); mTerrainViews[i]->reset();
mWorld->preload(mTerrainViews[i], mPreloadPositions[i], mAbort); mWorld->preload(mTerrainViews[i], mPreloadPositions[i].first, mPreloadPositions[i].second, mAbort, mProgress[i], mProgressRange);
} }
} }
@ -200,11 +191,16 @@ namespace MWWorld
mAbort = true; mAbort = true;
} }
int getProgress() const { return !mProgress.empty() ? mProgress[0].load() : 0; }
int getProgressRange() const { return !mProgress.empty() && mProgress[0].load() ? mProgressRange : 0; }
private: private:
std::atomic<bool> mAbort; std::atomic<bool> mAbort;
std::vector<std::atomic<int>> mProgress;
int mProgressRange;
std::vector<osg::ref_ptr<Terrain::View> > mTerrainViews; std::vector<osg::ref_ptr<Terrain::View> > mTerrainViews;
Terrain::World* mWorld; Terrain::World* mWorld;
std::vector<osg::Vec3f> mPreloadPositions; std::vector<CellPreloader::PositionCellGrid> mPreloadPositions;
}; };
/// Worker thread item: update the resource system's cache, effectively deleting unused entries. /// Worker thread item: update the resource system's cache, effectively deleting unused entries.
@ -237,6 +233,7 @@ namespace MWWorld
, mMaxCacheSize(0) , mMaxCacheSize(0)
, mPreloadInstances(true) , mPreloadInstances(true)
, mLastResourceCacheUpdate(0.0) , mLastResourceCacheUpdate(0.0)
, mStoreViewsFailCount(0)
{ {
} }
@ -328,9 +325,6 @@ namespace MWWorld
} }
mPreloadCells.erase(found); mPreloadCells.erase(found);
if (cell->isExterior() && mTerrainPreloadItem && mTerrainPreloadItem->isDone())
mTerrainPreloadItem->storeViews(0.0);
} }
} }
@ -375,7 +369,17 @@ namespace MWWorld
if (mTerrainPreloadItem && mTerrainPreloadItem->isDone()) if (mTerrainPreloadItem && mTerrainPreloadItem->isDone())
{ {
mTerrainPreloadItem->storeViews(timestamp); if (!mTerrainPreloadItem->storeViews(timestamp))
{
if (++mStoreViewsFailCount > 100)
{
OSG_ALWAYS << "paging views are rebuilt every frame, please check for faulty enable/disable scripts." << std::endl;
mStoreViewsFailCount = 0;
}
setTerrainPreloadPositions(std::vector<PositionCellGrid>());
}
else
mStoreViewsFailCount = 0;
mTerrainPreloadItem = nullptr; mTerrainPreloadItem = nullptr;
} }
} }
@ -415,11 +419,71 @@ namespace MWWorld
mUnrefQueue = unrefQueue; mUnrefQueue = unrefQueue;
} }
void CellPreloader::setTerrainPreloadPositions(const std::vector<osg::Vec3f> &positions) bool CellPreloader::syncTerrainLoad(const std::vector<CellPreloader::PositionCellGrid> &positions, int& progress, int& progressRange, double timestamp)
{ {
if (!mTerrainPreloadItem)
return true;
else if (mTerrainPreloadItem->isDone())
{
if (mTerrainPreloadItem->storeViews(timestamp))
{
mTerrainPreloadItem = nullptr;
return true;
}
else
{
setTerrainPreloadPositions(std::vector<CellPreloader::PositionCellGrid>());
setTerrainPreloadPositions(positions);
return false;
}
}
else
{
progress = mTerrainPreloadItem->getProgress();
progressRange = mTerrainPreloadItem->getProgressRange();
return false;
}
}
void CellPreloader::abortTerrainPreloadExcept(const CellPreloader::PositionCellGrid *exceptPos)
{
const float resetThreshold = ESM::Land::REAL_SIZE;
for (auto pos : mTerrainPreloadPositions)
if (exceptPos && (pos.first-exceptPos->first).length2() < resetThreshold*resetThreshold && pos.second == exceptPos->second)
return;
if (mTerrainPreloadItem && !mTerrainPreloadItem->isDone()) if (mTerrainPreloadItem && !mTerrainPreloadItem->isDone())
{
mTerrainPreloadItem->abort();
mTerrainPreloadItem->waitTillDone();
}
setTerrainPreloadPositions(std::vector<CellPreloader::PositionCellGrid>());
}
bool contains(const std::vector<CellPreloader::PositionCellGrid>& container, const std::vector<CellPreloader::PositionCellGrid>& contained)
{
for (auto pos : contained)
{
bool found = false;
for (auto pos2 : container)
{
if ((pos.first-pos2.first).length2() < 1 && pos.second == pos2.second)
{
found = true;
break;
}
}
if (!found) return false;
}
return true;
}
void CellPreloader::setTerrainPreloadPositions(const std::vector<CellPreloader::PositionCellGrid> &positions)
{
if (positions.empty())
mTerrainPreloadPositions.clear();
else if (contains(mTerrainPreloadPositions, positions))
return; return;
else if (positions == mTerrainPreloadPositions) if (mTerrainPreloadItem && !mTerrainPreloadItem->isDone())
return; return;
else else
{ {
@ -436,8 +500,11 @@ namespace MWWorld
} }
mTerrainPreloadPositions = positions; mTerrainPreloadPositions = positions;
mTerrainPreloadItem = new TerrainPreloadItem(mTerrainViews, mTerrain, positions); if (!positions.empty())
mWorkQueue->addWorkItem(mTerrainPreloadItem); {
mTerrainPreloadItem = new TerrainPreloadItem(mTerrainViews, mTerrain, positions);
mWorkQueue->addWorkItem(mTerrainPreloadItem);
}
} }
} }

@ -4,6 +4,7 @@
#include <map> #include <map>
#include <osg/ref_ptr> #include <osg/ref_ptr>
#include <osg/Vec3f> #include <osg/Vec3f>
#include <osg/Vec4i>
#include <components/sceneutil/workqueue.hpp> #include <components/sceneutil/workqueue.hpp>
namespace Resource namespace Resource
@ -68,7 +69,11 @@ namespace MWWorld
void setUnrefQueue(SceneUtil::UnrefQueue* unrefQueue); void setUnrefQueue(SceneUtil::UnrefQueue* unrefQueue);
void setTerrainPreloadPositions(const std::vector<osg::Vec3f>& positions); typedef std::pair<osg::Vec3f, osg::Vec4i> PositionCellGrid;
void setTerrainPreloadPositions(const std::vector<PositionCellGrid>& positions);
bool syncTerrainLoad(const std::vector<CellPreloader::PositionCellGrid> &positions, int& progress, int& progressRange, double timestamp);
void abortTerrainPreloadExcept(const PositionCellGrid *exceptPos);
private: private:
Resource::ResourceSystem* mResourceSystem; Resource::ResourceSystem* mResourceSystem;
@ -83,6 +88,7 @@ namespace MWWorld
bool mPreloadInstances; bool mPreloadInstances;
double mLastResourceCacheUpdate; double mLastResourceCacheUpdate;
int mStoreViewsFailCount;
struct PreloadEntry struct PreloadEntry
{ {
@ -105,7 +111,7 @@ namespace MWWorld
PreloadMap mPreloadCells; PreloadMap mPreloadCells;
std::vector<osg::ref_ptr<Terrain::View> > mTerrainViews; std::vector<osg::ref_ptr<Terrain::View> > mTerrainViews;
std::vector<osg::Vec3f> mTerrainPreloadPositions; std::vector<PositionCellGrid> mTerrainPreloadPositions;
osg::ref_ptr<TerrainPreloadItem> mTerrainPreloadItem; osg::ref_ptr<TerrainPreloadItem> mTerrainPreloadItem;
osg::ref_ptr<SceneUtil::WorkItem> mUpdateCacheItem; osg::ref_ptr<SceneUtil::WorkItem> mUpdateCacheItem;
}; };

@ -106,7 +106,7 @@ namespace
template<typename RecordType, typename T> template<typename RecordType, typename T>
void readReferenceCollection (ESM::ESMReader& reader, void readReferenceCollection (ESM::ESMReader& reader,
MWWorld::CellRefList<T>& collection, const ESM::CellRef& cref, const std::map<int, int>& contentFileMap) MWWorld::CellRefList<T>& collection, const ESM::CellRef& cref, const std::map<int, int>& contentFileMap, MWWorld::CellStore* cellstore)
{ {
const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore();
@ -141,7 +141,18 @@ namespace
if (iter->mRef.getRefNum()==state.mRef.mRefNum && *iter->mRef.getRefIdPtr() == state.mRef.mRefID) if (iter->mRef.getRefNum()==state.mRef.mRefNum && *iter->mRef.getRefIdPtr() == state.mRef.mRefID)
{ {
// overwrite existing reference // overwrite existing reference
float oldscale = iter->mRef.getScale();
iter->load (state); iter->load (state);
const ESM::Position & oldpos = iter->mRef.getPosition();
const ESM::Position & newpos = iter->mData.getPosition();
const MWWorld::Ptr ptr(&*iter, cellstore);
if ((oldscale != iter->mRef.getScale() || oldpos.asVec3() != newpos.asVec3() || oldpos.rot[0] != newpos.rot[0] || oldpos.rot[1] != newpos.rot[1] || oldpos.rot[2] != newpos.rot[2]) && !ptr.getClass().isActor())
MWBase::Environment::get().getWorld()->moveObject(ptr, newpos.pos[0], newpos.pos[1], newpos.pos[2]);
if (!iter->mData.isEnabled())
{
iter->mData.enable();
MWBase::Environment::get().getWorld()->disable(MWWorld::Ptr(&*iter, cellstore));
}
return; return;
} }
@ -154,28 +165,6 @@ namespace
ref.load (state); ref.load (state);
collection.mList.push_back (ref); collection.mList.push_back (ref);
} }
struct SearchByRefNumVisitor
{
MWWorld::LiveCellRefBase* mFound;
ESM::RefNum mRefNumToFind;
SearchByRefNumVisitor(const ESM::RefNum& toFind)
: mFound(nullptr)
, mRefNumToFind(toFind)
{
}
bool operator()(const MWWorld::Ptr& ptr)
{
if (ptr.getCellRef().getRefNum() == mRefNumToFind)
{
mFound = ptr.getBase();
return false;
}
return true;
}
};
} }
namespace MWWorld namespace MWWorld
@ -252,9 +241,7 @@ namespace MWWorld
throw std::runtime_error("moveTo: can't move object from a non-loaded cell (how did you get this object anyway?)"); throw std::runtime_error("moveTo: can't move object from a non-loaded cell (how did you get this object anyway?)");
// Ensure that the object actually exists in the cell // Ensure that the object actually exists in the cell
SearchByRefNumVisitor searchVisitor(object.getCellRef().getRefNum()); if (searchViaRefNum(object.getCellRef().getRefNum()).isEmpty())
forEach(searchVisitor);
if (!searchVisitor.mFound)
throw std::runtime_error("moveTo: object is not in this cell"); throw std::runtime_error("moveTo: object is not in this cell");
@ -809,107 +796,107 @@ namespace MWWorld
{ {
case ESM::REC_ACTI: case ESM::REC_ACTI:
readReferenceCollection<ESM::ObjectState> (reader, mActivators, cref, contentFileMap); readReferenceCollection<ESM::ObjectState> (reader, mActivators, cref, contentFileMap, this);
break; break;
case ESM::REC_ALCH: case ESM::REC_ALCH:
readReferenceCollection<ESM::ObjectState> (reader, mPotions, cref, contentFileMap); readReferenceCollection<ESM::ObjectState> (reader, mPotions, cref, contentFileMap, this);
break; break;
case ESM::REC_APPA: case ESM::REC_APPA:
readReferenceCollection<ESM::ObjectState> (reader, mAppas, cref, contentFileMap); readReferenceCollection<ESM::ObjectState> (reader, mAppas, cref, contentFileMap, this);
break; break;
case ESM::REC_ARMO: case ESM::REC_ARMO:
readReferenceCollection<ESM::ObjectState> (reader, mArmors, cref, contentFileMap); readReferenceCollection<ESM::ObjectState> (reader, mArmors, cref, contentFileMap, this);
break; break;
case ESM::REC_BOOK: case ESM::REC_BOOK:
readReferenceCollection<ESM::ObjectState> (reader, mBooks, cref, contentFileMap); readReferenceCollection<ESM::ObjectState> (reader, mBooks, cref, contentFileMap, this);
break; break;
case ESM::REC_CLOT: case ESM::REC_CLOT:
readReferenceCollection<ESM::ObjectState> (reader, mClothes, cref, contentFileMap); readReferenceCollection<ESM::ObjectState> (reader, mClothes, cref, contentFileMap, this);
break; break;
case ESM::REC_CONT: case ESM::REC_CONT:
readReferenceCollection<ESM::ContainerState> (reader, mContainers, cref, contentFileMap); readReferenceCollection<ESM::ContainerState> (reader, mContainers, cref, contentFileMap, this);
break; break;
case ESM::REC_CREA: case ESM::REC_CREA:
readReferenceCollection<ESM::CreatureState> (reader, mCreatures, cref, contentFileMap); readReferenceCollection<ESM::CreatureState> (reader, mCreatures, cref, contentFileMap, this);
break; break;
case ESM::REC_DOOR: case ESM::REC_DOOR:
readReferenceCollection<ESM::DoorState> (reader, mDoors, cref, contentFileMap); readReferenceCollection<ESM::DoorState> (reader, mDoors, cref, contentFileMap, this);
break; break;
case ESM::REC_INGR: case ESM::REC_INGR:
readReferenceCollection<ESM::ObjectState> (reader, mIngreds, cref, contentFileMap); readReferenceCollection<ESM::ObjectState> (reader, mIngreds, cref, contentFileMap, this);
break; break;
case ESM::REC_LEVC: case ESM::REC_LEVC:
readReferenceCollection<ESM::CreatureLevListState> (reader, mCreatureLists, cref, contentFileMap); readReferenceCollection<ESM::CreatureLevListState> (reader, mCreatureLists, cref, contentFileMap, this);
break; break;
case ESM::REC_LEVI: case ESM::REC_LEVI:
readReferenceCollection<ESM::ObjectState> (reader, mItemLists, cref, contentFileMap); readReferenceCollection<ESM::ObjectState> (reader, mItemLists, cref, contentFileMap, this);
break; break;
case ESM::REC_LIGH: case ESM::REC_LIGH:
readReferenceCollection<ESM::ObjectState> (reader, mLights, cref, contentFileMap); readReferenceCollection<ESM::ObjectState> (reader, mLights, cref, contentFileMap, this);
break; break;
case ESM::REC_LOCK: case ESM::REC_LOCK:
readReferenceCollection<ESM::ObjectState> (reader, mLockpicks, cref, contentFileMap); readReferenceCollection<ESM::ObjectState> (reader, mLockpicks, cref, contentFileMap, this);
break; break;
case ESM::REC_MISC: case ESM::REC_MISC:
readReferenceCollection<ESM::ObjectState> (reader, mMiscItems, cref, contentFileMap); readReferenceCollection<ESM::ObjectState> (reader, mMiscItems, cref, contentFileMap, this);
break; break;
case ESM::REC_NPC_: case ESM::REC_NPC_:
readReferenceCollection<ESM::NpcState> (reader, mNpcs, cref, contentFileMap); readReferenceCollection<ESM::NpcState> (reader, mNpcs, cref, contentFileMap, this);
break; break;
case ESM::REC_PROB: case ESM::REC_PROB:
readReferenceCollection<ESM::ObjectState> (reader, mProbes, cref, contentFileMap); readReferenceCollection<ESM::ObjectState> (reader, mProbes, cref, contentFileMap, this);
break; break;
case ESM::REC_REPA: case ESM::REC_REPA:
readReferenceCollection<ESM::ObjectState> (reader, mRepairs, cref, contentFileMap); readReferenceCollection<ESM::ObjectState> (reader, mRepairs, cref, contentFileMap, this);
break; break;
case ESM::REC_STAT: case ESM::REC_STAT:
readReferenceCollection<ESM::ObjectState> (reader, mStatics, cref, contentFileMap); readReferenceCollection<ESM::ObjectState> (reader, mStatics, cref, contentFileMap, this);
break; break;
case ESM::REC_WEAP: case ESM::REC_WEAP:
readReferenceCollection<ESM::ObjectState> (reader, mWeapons, cref, contentFileMap); readReferenceCollection<ESM::ObjectState> (reader, mWeapons, cref, contentFileMap, this);
break; break;
case ESM::REC_BODY: case ESM::REC_BODY:
readReferenceCollection<ESM::ObjectState> (reader, mBodyParts, cref, contentFileMap); readReferenceCollection<ESM::ObjectState> (reader, mBodyParts, cref, contentFileMap, this);
break; break;
default: default:
@ -931,26 +918,22 @@ namespace MWWorld
movedTo.load(reader); movedTo.load(reader);
// Search for the reference. It might no longer exist if its content file was removed. // Search for the reference. It might no longer exist if its content file was removed.
SearchByRefNumVisitor visitor(refnum); Ptr movedRef = searchViaRefNum(refnum);
forEachInternal(visitor); if (movedRef.isEmpty())
if (!visitor.mFound)
{ {
Log(Debug::Warning) << "Warning: Dropping moved ref tag for " << refnum.mIndex << " (moved object no longer exists)"; Log(Debug::Warning) << "Warning: Dropping moved ref tag for " << refnum.mIndex << " (moved object no longer exists)";
continue; continue;
} }
MWWorld::LiveCellRefBase* movedRef = visitor.mFound;
CellStore* otherCell = callback->getCellStore(movedTo); CellStore* otherCell = callback->getCellStore(movedTo);
if (otherCell == nullptr) if (otherCell == nullptr)
{ {
Log(Debug::Warning) << "Warning: Dropping moved ref tag for " << movedRef->mRef.getRefId() Log(Debug::Warning) << "Warning: Dropping moved ref tag for " << movedRef.getCellRef().getRefId()
<< " (target cell " << movedTo.mWorldspace << " no longer exists). Reference moved back to its original location."; << " (target cell " << movedTo.mWorldspace << " no longer exists). Reference moved back to its original location.";
// Note by dropping tag the object will automatically re-appear in its original cell, though potentially at inapproriate coordinates. // Note by dropping tag the object will automatically re-appear in its original cell, though potentially at inapproriate coordinates.
// Restore original coordinates: // Restore original coordinates:
movedRef->mData.setPosition(movedRef->mRef.getPosition()); movedRef.getRefData().setPosition(movedRef.getCellRef().getPosition());
continue; continue;
} }
@ -961,7 +944,7 @@ namespace MWWorld
continue; continue;
} }
moveTo(MWWorld::Ptr(movedRef, this), otherCell); moveTo(movedRef, otherCell);
} }
} }

@ -136,6 +136,10 @@ void ESMStore::setUp(bool validateRecords)
mIds[*record] = storeIt->first; mIds[*record] = storeIt->first;
} }
} }
if (mStaticIds.empty())
mStaticIds = mIds;
mSkills.setUp(); mSkills.setUp();
mMagicEffects.setUp(); mMagicEffects.setUp();
mAttributes.setUp(); mAttributes.setUp();

@ -68,6 +68,8 @@ namespace MWWorld
// Lookup of all IDs. Makes looking up references faster. Just // Lookup of all IDs. Makes looking up references faster. Just
// maps the id name to the record type. // maps the id name to the record type.
std::map<std::string, int> mIds; std::map<std::string, int> mIds;
std::map<std::string, int> mStaticIds;
std::map<int, StoreBase *> mStores; std::map<int, StoreBase *> mStores;
ESM::NPC mPlayerTemplate; ESM::NPC mPlayerTemplate;
@ -99,6 +101,14 @@ namespace MWWorld
} }
return it->second; return it->second;
} }
int findStatic(const std::string &id) const
{
std::map<std::string, int>::const_iterator it = mStaticIds.find(id);
if (it == mStaticIds.end()) {
return 0;
}
return it->second;
}
ESMStore() ESMStore()
: mDynamicCount(0) : mDynamicCount(0)

@ -13,6 +13,7 @@
#include <components/resource/scenemanager.hpp> #include <components/resource/scenemanager.hpp>
#include <components/resource/bulletshape.hpp> #include <components/resource/bulletshape.hpp>
#include <components/sceneutil/unrefqueue.hpp> #include <components/sceneutil/unrefqueue.hpp>
#include <components/sceneutil/positionattitudetransform.hpp>
#include <components/detournavigator/navigator.hpp> #include <components/detournavigator/navigator.hpp>
#include <components/detournavigator/debug.hpp> #include <components/detournavigator/debug.hpp>
#include <components/misc/convert.hpp> #include <components/misc/convert.hpp>
@ -86,8 +87,21 @@ namespace
); );
} }
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, void addObject(const MWWorld::Ptr& ptr, MWPhysics::PhysicsSystem& physics,
MWRender::RenderingManager& rendering) MWRender::RenderingManager& rendering, std::set<ESM::RefNum>& pagedRefs)
{ {
if (ptr.getRefData().getBaseNode() || physics.getActor(ptr)) if (ptr.getRefData().getBaseNode() || physics.getActor(ptr))
{ {
@ -96,15 +110,13 @@ namespace
} }
bool useAnim = ptr.getClass().useAnim(); bool useAnim = ptr.getClass().useAnim();
std::string model = ptr.getClass().getModel(ptr); std::string model = getModel(ptr, rendering.getResourceSystem()->getVFS());
if (useAnim)
model = Misc::ResourceHelpers::correctActorModelPath(model, rendering.getResourceSystem()->getVFS());
std::string id = ptr.getCellRef().getRefId(); const ESM::RefNum& refnum = ptr.getCellRef().getRefNum();
if (id == "prisonmarker" || id == "divinemarker" || id == "templemarker" || id == "northmarker") if (!refnum.hasContentFile() || pagedRefs.find(refnum) == pagedRefs.end())
model = ""; // marker objects that have a hardcoded function in the game logic, should be hidden from the player ptr.getClass().insertObjectRendering(ptr, model, rendering);
else
ptr.getClass().insertObjectRendering(ptr, model, rendering); ptr.getRefData().setBaseNode(new SceneUtil::PositionAttitudeTransform); // FIXME remove this when physics code is fixed not to depend on basenode
setNodeRotation(ptr, rendering, RotationOrder::direct); setNodeRotation(ptr, rendering, RotationOrder::direct);
ptr.getClass().insertObject (ptr, model, physics); ptr.getClass().insertObject (ptr, model, physics);
@ -183,27 +195,6 @@ namespace
} }
} }
void updateObjectRotation (const MWWorld::Ptr& ptr, MWPhysics::PhysicsSystem& physics,
MWRender::RenderingManager& rendering, RotationOrder order)
{
setNodeRotation(ptr, rendering, order);
physics.updateRotation(ptr);
}
void updateObjectScale(const MWWorld::Ptr& ptr, MWPhysics::PhysicsSystem& physics,
MWRender::RenderingManager& rendering)
{
if (ptr.getRefData().getBaseNode() != nullptr)
{
float scale = ptr.getCellRef().getScale();
osg::Vec3f scaleVec (scale, scale, scale);
ptr.getClass().adjustScale(ptr, scaleVec, true);
rendering.scaleObject(ptr, scaleVec);
physics.updateScale(ptr);
}
}
struct InsertVisitor struct InsertVisitor
{ {
MWWorld::CellStore& mCell; MWWorld::CellStore& mCell;
@ -276,50 +267,48 @@ namespace
namespace MWWorld namespace MWWorld
{ {
void Scene::updateObjectRotation(const Ptr& ptr, RotationOrder order) void Scene::removeFromPagedRefs(const Ptr &ptr)
{ {
::updateObjectRotation(ptr, *mPhysics, mRendering, order); 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::updateObjectScale(const Ptr &ptr) void Scene::updateObjectPosition(const Ptr &ptr, const osg::Vec3f &pos, bool movePhysics)
{ {
::updateObjectScale(ptr, *mPhysics, mRendering); mRendering.moveObject(ptr, pos);
if (movePhysics)
{
mPhysics->updatePosition(ptr);
}
} }
void Scene::getGridCenter(int &cellX, int &cellY) void Scene::updateObjectRotation(const Ptr &ptr, RotationOrder order)
{ {
int maxX = std::numeric_limits<int>::min(); setNodeRotation(ptr, mRendering, order);
int maxY = std::numeric_limits<int>::min(); mPhysics->updateRotation(ptr);
int minX = std::numeric_limits<int>::max(); }
int minY = std::numeric_limits<int>::max();
CellStoreCollection::iterator iter = mActiveCells.begin(); void Scene::updateObjectScale(const Ptr &ptr)
while (iter!=mActiveCells.end()) {
{ float scale = ptr.getCellRef().getScale();
assert ((*iter)->getCell()->isExterior()); osg::Vec3f scaleVec (scale, scale, scale);
int x = (*iter)->getCell()->getGridX(); ptr.getClass().adjustScale(ptr, scaleVec, true);
int y = (*iter)->getCell()->getGridY(); mRendering.scaleObject(ptr, scaleVec);
maxX = std::max(x, maxX); mPhysics->updateScale(ptr);
maxY = std::max(y, maxY);
minX = std::min(x, minX);
minY = std::min(y, minY);
++iter;
}
cellX = (minX + maxX) / 2;
cellY = (minY + maxY) / 2;
} }
void Scene::update (float duration, bool paused) void Scene::update (float duration, bool paused)
{ {
mPreloadTimer += duration; mPreloader->updateCache(mRendering.getReferenceTime());
if (mPreloadTimer > 0.1f) preloadCells(duration);
{
preloadCells(0.1f);
mPreloadTimer = 0.f;
}
mRendering.update (duration, paused); mRendering.update (duration, paused);
mPreloader->updateCache(mRendering.getReferenceTime());
} }
void Scene::unloadCell (CellStoreCollection::iterator iter, bool test) void Scene::unloadCell (CellStoreCollection::iterator iter, bool test)
@ -488,6 +477,27 @@ namespace MWWorld
mPreloader->clear(); 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) void Scene::playerMoved(const osg::Vec3f &pos)
{ {
const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); const auto navigator = MWBase::Environment::get().getWorld()->getNavigator();
@ -497,30 +507,13 @@ namespace MWWorld
if (!mCurrentCell || !mCurrentCell->isExterior()) if (!mCurrentCell || !mCurrentCell->isExterior())
return; return;
// figure out the center of the current cell grid (*not* necessarily mCurrentCell, which is the cell the player is in) osg::Vec2i newCell = getNewGridCenter(pos, &mCurrentGridCenter);
int cellX, cellY; if (newCell != mCurrentGridCenter)
getGridCenter(cellX, cellY); changeCellGrid(pos, newCell.x(), newCell.y());
float centerX, centerY;
MWBase::Environment::get().getWorld()->indexToPosition(cellX, cellY, centerX, centerY, true);
const float maxDistance = Constants::CellSizeInUnits / 2 + mCellLoadingThreshold; // 1/2 cell size + threshold
float distance = std::max(std::abs(centerX-pos.x()), std::abs(centerY-pos.y()));
if (distance > maxDistance)
{
int newX, newY;
MWBase::Environment::get().getWorld()->positionToIndex(pos.x(), pos.y(), newX, newY);
changeCellGrid(newX, newY);
}
} }
void Scene::changeCellGrid (int playerCellX, int playerCellY, bool changeEvent) void Scene::changeCellGrid (const osg::Vec3f &pos, int playerCellX, int playerCellY, bool changeEvent)
{ {
Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen();
Loading::ScopedLoad load(loadingListener);
int messagesCount = MWBase::Environment::get().getWindowManager()->getMessagesCount();
std::string loadingExteriorText = "#{sLoadingMessage3}";
loadingListener->setLabel(loadingExteriorText, false, messagesCount > 0);
CellStoreCollection::iterator active = mActiveCells.begin(); CellStoreCollection::iterator active = mActiveCells.begin();
while (active!=mActiveCells.end()) while (active!=mActiveCells.end())
{ {
@ -537,6 +530,14 @@ namespace MWWorld
unloadCell (active++); 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::size_t refsToLoad = 0;
std::vector<std::pair<int, int>> cellsPositionsToLoad; std::vector<std::pair<int, int>> cellsPositionsToLoad;
// get the number of refs to load // get the number of refs to load
@ -565,6 +566,11 @@ namespace MWWorld
} }
} }
Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen();
Loading::ScopedLoad load(loadingListener);
int messagesCount = MWBase::Environment::get().getWindowManager()->getMessagesCount();
std::string loadingExteriorText = "#{sLoadingMessage3}";
loadingListener->setLabel(loadingExteriorText, false, messagesCount > 0);
loadingListener->setProgressRange(refsToLoad); loadingListener->setProgressRange(refsToLoad);
const auto getDistanceToPlayerCell = [&] (const std::pair<int, int>& cellPosition) const auto getDistanceToPlayerCell = [&] (const std::pair<int, int>& cellPosition)
@ -745,13 +751,12 @@ namespace MWWorld
MWBase::Environment::get().getWorld()->adjustSky(); MWBase::Environment::get().getWorld()->adjustSky();
mLastPlayerPos = pos.asVec3(); mLastPlayerPos = player.getRefData().getPosition().asVec3();
} }
Scene::Scene (MWRender::RenderingManager& rendering, MWPhysics::PhysicsSystem *physics, Scene::Scene (MWRender::RenderingManager& rendering, MWPhysics::PhysicsSystem *physics,
DetourNavigator::Navigator& navigator) DetourNavigator::Navigator& navigator)
: mCurrentCell (0), mCellChanged (false), mPhysics(physics), mRendering(rendering), mNavigator(navigator) : mCurrentCell (0), mCellChanged (false), mPhysics(physics), mRendering(rendering), mNavigator(navigator)
, mPreloadTimer(0.f)
, mHalfGridSize(Settings::Manager::getInt("exterior cell load distance", "Cells")) , mHalfGridSize(Settings::Manager::getInt("exterior cell load distance", "Cells"))
, mCellLoadingThreshold(1024.f) , mCellLoadingThreshold(1024.f)
, mPreloadDistance(Settings::Manager::getInt("preload distance", "Cells")) , mPreloadDistance(Settings::Manager::getInt("preload distance", "Cells"))
@ -828,6 +833,7 @@ namespace MWWorld
loadingListener->setProgressRange(cell->count()); loadingListener->setProgressRange(cell->count());
// Load cell. // Load cell.
mPagedRefs.clear();
loadCell (cell, loadingListener, changeEvent); loadCell (cell, loadingListener, changeEvent);
changePlayerCell(cell, position, adjustPlayerPos); changePlayerCell(cell, position, adjustPlayerPos);
@ -857,7 +863,7 @@ namespace MWWorld
if (changeEvent) if (changeEvent)
MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.5); MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.5);
changeCellGrid(x, y, changeEvent); changeCellGrid(position.asVec3(), x, y, changeEvent);
CellStore* current = MWBase::Environment::get().getWorld()->getExterior(x, y); CellStore* current = MWBase::Environment::get().getWorld()->getExterior(x, y);
changePlayerCell(current, position, adjustPlayerPos); changePlayerCell(current, position, adjustPlayerPos);
@ -880,7 +886,7 @@ namespace MWWorld
{ {
InsertVisitor insertVisitor (cell, *loadingListener, test); InsertVisitor insertVisitor (cell, *loadingListener, test);
cell.forEach (insertVisitor); cell.forEach (insertVisitor);
insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mRendering); }); insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mRendering, mPagedRefs); });
insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mNavigator); }); 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 // do adjustPosition (snapping actors to ground) after objects are loaded, so we don't depend on the loading order
@ -892,7 +898,7 @@ namespace MWWorld
{ {
try try
{ {
addObject(ptr, *mPhysics, mRendering); addObject(ptr, *mPhysics, mRendering, mPagedRefs);
addObject(ptr, *mPhysics, mNavigator); addObject(ptr, *mPhysics, mNavigator);
MWBase::Environment::get().getWorld()->scaleObject(ptr, ptr.getCellRef().getScale()); MWBase::Environment::get().getWorld()->scaleObject(ptr, ptr.getCellRef().getScale());
const auto navigator = MWBase::Environment::get().getWorld()->getNavigator(); const auto navigator = MWBase::Environment::get().getWorld()->getNavigator();
@ -924,6 +930,7 @@ namespace MWWorld
mRendering.removeObject (ptr); mRendering.removeObject (ptr);
if (ptr.getClass().isActor()) if (ptr.getClass().isActor())
mRendering.removeWaterRippleEmitter(ptr); mRendering.removeWaterRippleEmitter(ptr);
ptr.getRefData().setBaseNode(nullptr);
} }
bool Scene::isCellActive(const CellStore &cell) bool Scene::isCellActive(const CellStore &cell)
@ -983,7 +990,8 @@ namespace MWWorld
void Scene::preloadCells(float dt) void Scene::preloadCells(float dt)
{ {
std::vector<osg::Vec3f> exteriorPositions; if (dt<=1e-06) return;
std::vector<PositionCellGrid> exteriorPositions;
const MWWorld::ConstPtr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); const MWWorld::ConstPtr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
osg::Vec3f playerPos = player.getRefData().getPosition().asVec3(); osg::Vec3f playerPos = player.getRefData().getPosition().asVec3();
@ -991,7 +999,7 @@ namespace MWWorld
osg::Vec3f predictedPos = playerPos + moved / dt * mPredictionTime; osg::Vec3f predictedPos = playerPos + moved / dt * mPredictionTime;
if (mCurrentCell->isExterior()) if (mCurrentCell->isExterior())
exteriorPositions.push_back(predictedPos); exteriorPositions.emplace_back(predictedPos, gridCenterToBounds(getNewGridCenter(predictedPos, &mCurrentGridCenter)));
mLastPlayerPos = playerPos; mLastPlayerPos = playerPos;
@ -1008,7 +1016,7 @@ namespace MWWorld
mPreloader->setTerrainPreloadPositions(exteriorPositions); mPreloader->setTerrainPreloadPositions(exteriorPositions);
} }
void Scene::preloadTeleportDoorDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos, std::vector<osg::Vec3f>& exteriorPositions) void Scene::preloadTeleportDoorDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos, std::vector<PositionCellGrid>& exteriorPositions)
{ {
std::vector<MWWorld::ConstPtr> teleportDoors; std::vector<MWWorld::ConstPtr> teleportDoors;
for (const MWWorld::CellStore* cellStore : mActiveCells) for (const MWWorld::CellStore* cellStore : mActiveCells)
@ -1042,7 +1050,7 @@ namespace MWWorld
int x,y; int x,y;
MWBase::Environment::get().getWorld()->positionToIndex (pos.x(), pos.y(), x, y); MWBase::Environment::get().getWorld()->positionToIndex (pos.x(), pos.y(), x, y);
preloadCell(MWBase::Environment::get().getWorld()->getExterior(x,y), true); preloadCell(MWBase::Environment::get().getWorld()->getExterior(x,y), true);
exteriorPositions.push_back(pos); exteriorPositions.emplace_back(pos, gridCenterToBounds(getNewGridCenter(pos)));
} }
} }
catch (std::exception& e) catch (std::exception& e)
@ -1062,7 +1070,7 @@ namespace MWWorld
int cellX,cellY; int cellX,cellY;
getGridCenter(cellX,cellY); cellX = mCurrentGridCenter.x(); cellY = mCurrentGridCenter.y();
float centerX, centerY; float centerX, centerY;
MWBase::Environment::get().getWorld()->indexToPosition(cellX, cellY, centerX, centerY, true); MWBase::Environment::get().getWorld()->indexToPosition(cellX, cellY, centerX, centerY, true);
@ -1108,11 +1116,41 @@ namespace MWWorld
mPreloader->preload(cell, mRendering.getReferenceTime()); mPreloader->preload(cell, mRendering.getReferenceTime());
} }
void Scene::preloadTerrain(const osg::Vec3f &pos) void Scene::preloadTerrain(const osg::Vec3f &pos, bool sync)
{ {
std::vector<osg::Vec3f> vec; std::vector<PositionCellGrid> vec;
vec.push_back(pos); vec.emplace_back(pos, gridCenterToBounds(getNewGridCenter(pos)));
if (sync && mRendering.pagingUnlockCache())
mPreloader->abortTerrainPreloadExcept(nullptr);
else
mPreloader->abortTerrainPreloadExcept(&vec[0]);
mPreloader->setTerrainPreloadPositions(vec); 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);
OpenThreads::Thread::microSleep(5000);
}
}
void Scene::reloadTerrain()
{
mPreloader->setTerrainPreloadPositions(std::vector<PositionCellGrid>());
} }
struct ListFastTravelDestinationsVisitor struct ListFastTravelDestinationsVisitor
@ -1145,7 +1183,7 @@ namespace MWWorld
std::vector<ESM::Transport::Dest> mList; std::vector<ESM::Transport::Dest> mList;
}; };
void Scene::preloadFastTravelDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& /*predictedPos*/, std::vector<osg::Vec3f>& exteriorPositions) // ignore predictedPos here since opening dialogue with travel service takes extra time 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(); const MWWorld::ConstPtr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
ListFastTravelDestinationsVisitor listVisitor(mPreloadDistance, player.getRefData().getPosition().asVec3()); ListFastTravelDestinationsVisitor listVisitor(mPreloadDistance, player.getRefData().getPosition().asVec3());
@ -1166,7 +1204,7 @@ namespace MWWorld
int x,y; int x,y;
MWBase::Environment::get().getWorld()->positionToIndex( pos.x(), pos.y(), x, y); MWBase::Environment::get().getWorld()->positionToIndex( pos.x(), pos.y(), x, y);
preloadCell(MWBase::Environment::get().getWorld()->getExterior(x,y), true); preloadCell(MWBase::Environment::get().getWorld()->getExterior(x,y), true);
exteriorPositions.push_back(pos); exteriorPositions.emplace_back(pos, gridCenterToBounds(getNewGridCenter(pos)));
} }
} }
} }

@ -1,6 +1,9 @@
#ifndef GAME_MWWORLD_SCENE_H #ifndef GAME_MWWORLD_SCENE_H
#define GAME_MWWORLD_SCENE_H #define GAME_MWWORLD_SCENE_H
#include <osg/Vec4i>
#include <osg/Vec2i>
#include "ptr.hpp" #include "ptr.hpp"
#include "globals.hpp" #include "globals.hpp"
@ -72,7 +75,6 @@ namespace MWWorld
MWRender::RenderingManager& mRendering; MWRender::RenderingManager& mRendering;
DetourNavigator::Navigator& mNavigator; DetourNavigator::Navigator& mNavigator;
std::unique_ptr<CellPreloader> mPreloader; std::unique_ptr<CellPreloader> mPreloader;
float mPreloadTimer;
int mHalfGridSize; int mHalfGridSize;
float mCellLoadingThreshold; float mCellLoadingThreshold;
float mPreloadDistance; float mPreloadDistance;
@ -85,17 +87,23 @@ namespace MWWorld
osg::Vec3f mLastPlayerPos; osg::Vec3f mLastPlayerPos;
std::set<ESM::RefNum> mPagedRefs;
void insertCell (CellStore &cell, Loading::Listener* loadingListener, bool test = false); void insertCell (CellStore &cell, Loading::Listener* loadingListener, bool test = false);
osg::Vec2i mCurrentGridCenter;
// Load and unload cells as necessary to create a cell grid with "X" and "Y" in the center // Load and unload cells as necessary to create a cell grid with "X" and "Y" in the center
void changeCellGrid (int playerCellX, int playerCellY, bool changeEvent = true); void changeCellGrid (const osg::Vec3f &pos, int playerCellX, int playerCellY, bool changeEvent = true);
void getGridCenter(int& cellX, int& cellY); typedef std::pair<osg::Vec3f, osg::Vec4i> PositionCellGrid;
void preloadCells(float dt); void preloadCells(float dt);
void preloadTeleportDoorDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos, std::vector<osg::Vec3f>& exteriorPositions); void preloadTeleportDoorDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos, std::vector<PositionCellGrid>& exteriorPositions);
void preloadExteriorGrid(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos); void preloadExteriorGrid(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos);
void preloadFastTravelDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos, std::vector<osg::Vec3f>& exteriorPositions); void preloadFastTravelDestinations(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos, std::vector<PositionCellGrid>& exteriorPositions);
osg::Vec4i gridCenterToBounds(const osg::Vec2i &centerCell) const;
osg::Vec2i getNewGridCenter(const osg::Vec3f &pos, const osg::Vec2i *currentGridCenter = nullptr) const;
public: public:
@ -105,7 +113,8 @@ namespace MWWorld
~Scene(); ~Scene();
void preloadCell(MWWorld::CellStore* cell, bool preloadSurrounding=false); void preloadCell(MWWorld::CellStore* cell, bool preloadSurrounding=false);
void preloadTerrain(const osg::Vec3f& pos); void preloadTerrain(const osg::Vec3f& pos, bool sync=false);
void reloadTerrain();
void unloadCell (CellStoreCollection::iterator iter, bool test = false); void unloadCell (CellStoreCollection::iterator iter, bool test = false);
@ -143,8 +152,11 @@ namespace MWWorld
void removeObjectFromScene (const Ptr& ptr); void removeObjectFromScene (const Ptr& ptr);
///< Remove an object from the scene, but not from the world model. ///< Remove an object from the scene, but not from the world model.
void removeFromPagedRefs(const Ptr &ptr);
void updateObjectRotation(const Ptr& ptr, RotationOrder order); void updateObjectRotation(const Ptr& ptr, RotationOrder order);
void updateObjectScale(const Ptr& ptr); void updateObjectScale(const Ptr& ptr);
void updateObjectPosition(const Ptr &ptr, const osg::Vec3f &pos, bool movePhysics);
bool isCellActive(const CellStore &cell); bool isCellActive(const CellStore &cell);

@ -139,18 +139,26 @@ namespace MWWorld
std::string idLower = Misc::StringUtils::lowerCase(id); std::string idLower = Misc::StringUtils::lowerCase(id);
typename Dynamic::const_iterator dit = mDynamic.find(idLower); typename Dynamic::const_iterator dit = mDynamic.find(idLower);
if (dit != mDynamic.end()) { if (dit != mDynamic.end())
return &dit->second; return &dit->second;
}
typename std::map<std::string, T>::const_iterator it = mStatic.find(idLower); typename std::map<std::string, T>::const_iterator it = mStatic.find(idLower);
if (it != mStatic.end())
return &(it->second);
if (it != mStatic.end() && Misc::StringUtils::ciEqual(it->second.mId, id)) { return 0;
}
template<typename T>
const T *Store<T>::searchStatic(const std::string &id) const
{
std::string idLower = Misc::StringUtils::lowerCase(id);
typename std::map<std::string, T>::const_iterator it = mStatic.find(idLower);
if (it != mStatic.end())
return &(it->second); return &(it->second);
}
return 0; return 0;
} }
template<typename T> template<typename T>
bool Store<T>::isDynamic(const std::string &id) const bool Store<T>::isDynamic(const std::string &id) const
{ {
@ -276,7 +284,7 @@ namespace MWWorld
typename std::map<std::string, T>::iterator it = mStatic.find(idLower); typename std::map<std::string, T>::iterator it = mStatic.find(idLower);
if (it != mStatic.end() && Misc::StringUtils::ciEqual(it->second.mId, id)) { if (it != mStatic.end()) {
// delete from the static part of mShared // delete from the static part of mShared
typename std::vector<T *>::iterator sharedIter = mShared.begin(); typename std::vector<T *>::iterator sharedIter = mShared.begin();
typename std::vector<T *>::iterator end = sharedIter + mStatic.size(); typename std::vector<T *>::iterator end = sharedIter + mStatic.size();
@ -553,7 +561,7 @@ namespace MWWorld
std::map<std::string, ESM::Cell>::const_iterator it = mInt.find(cell.mName); std::map<std::string, ESM::Cell>::const_iterator it = mInt.find(cell.mName);
if (it != mInt.end() && Misc::StringUtils::ciEqual(it->second.mName, id)) { if (it != mInt.end()) {
return &(it->second); return &(it->second);
} }
@ -582,6 +590,18 @@ namespace MWWorld
return 0; return 0;
} }
const ESM::Cell *Store<ESM::Cell>::searchStatic(int x, int y) const
{
ESM::Cell cell;
cell.mData.mX = x, cell.mData.mY = y;
std::pair<int, int> key(x, y);
DynamicExt::const_iterator it = mExt.find(key);
if (it != mExt.end()) {
return &(it->second);
}
return 0;
}
const ESM::Cell *Store<ESM::Cell>::searchOrCreate(int x, int y) const ESM::Cell *Store<ESM::Cell>::searchOrCreate(int x, int y)
{ {
std::pair<int, int> key(x, y); std::pair<int, int> key(x, y);
@ -1104,9 +1124,8 @@ namespace MWWorld
{ {
auto it = mStatic.find(Misc::StringUtils::lowerCase(id)); auto it = mStatic.find(Misc::StringUtils::lowerCase(id));
if (it != mStatic.end() && Misc::StringUtils::ciEqual(it->second.mId, id)) { if (it != mStatic.end())
mStatic.erase(it); mStatic.erase(it);
}
return true; return true;
} }

@ -167,6 +167,7 @@ namespace MWWorld
void setUp(); void setUp();
const T *search(const std::string &id) const; const T *search(const std::string &id) const;
const T *searchStatic(const std::string &id) const;
/** /**
* Does the record with this ID come from the dynamic store? * Does the record with this ID come from the dynamic store?
@ -297,6 +298,7 @@ namespace MWWorld
const ESM::Cell *search(const std::string &id) const; const ESM::Cell *search(const std::string &id) const;
const ESM::Cell *search(int x, int y) const; const ESM::Cell *search(int x, int y) const;
const ESM::Cell *searchStatic(int x, int y) const;
const ESM::Cell *searchOrCreate(int x, int y); const ESM::Cell *searchOrCreate(int x, int y);
const ESM::Cell *find(const std::string &id) const; const ESM::Cell *find(const std::string &id) const;

@ -243,7 +243,6 @@ namespace MWWorld
if (bypass && !mStartCell.empty()) if (bypass && !mStartCell.empty())
{ {
ESM::Position pos; ESM::Position pos;
if (findExteriorPosition (mStartCell, pos)) if (findExteriorPosition (mStartCell, pos))
{ {
changeToExteriorCell (pos, true); changeToExteriorCell (pos, true);
@ -384,9 +383,9 @@ namespace MWWorld
mPlayer->readRecord(reader, type); mPlayer->readRecord(reader, type);
if (getPlayerPtr().isInCell()) if (getPlayerPtr().isInCell())
{ {
mWorldScene->preloadCell(getPlayerPtr().getCell(), true);
if (getPlayerPtr().getCell()->isExterior()) if (getPlayerPtr().getCell()->isExterior())
mWorldScene->preloadTerrain(getPlayerPtr().getRefData().getPosition().asVec3()); mWorldScene->preloadTerrain(getPlayerPtr().getRefData().getPosition().asVec3());
mWorldScene->preloadCell(getPlayerPtr().getCell(), true);
} }
break; break;
default: default:
@ -814,6 +813,13 @@ namespace MWWorld
if(mWorldScene->getActiveCells().find (reference.getCell()) != mWorldScene->getActiveCells().end() && reference.getRefData().getCount()) if(mWorldScene->getActiveCells().find (reference.getCell()) != mWorldScene->getActiveCells().end() && reference.getRefData().getCount())
mWorldScene->addObjectToScene (reference); mWorldScene->addObjectToScene (reference);
if (reference.getCellRef().getRefNum().hasContentFile())
{
int type = mStore.find(Misc::StringUtils::lowerCase(reference.getCellRef().getRefId()));
if (mRendering->pagingEnableObject(type, reference, true))
mWorldScene->reloadTerrain();
}
} }
} }
@ -838,20 +844,27 @@ namespace MWWorld
void World::disable (const Ptr& reference) void World::disable (const Ptr& reference)
{ {
if (!reference.getRefData().isEnabled())
return;
// disable is a no-op for items in containers // disable is a no-op for items in containers
if (!reference.isInCell()) if (!reference.isInCell())
return; return;
if (reference.getRefData().isEnabled()) if (reference == getPlayerPtr())
{ throw std::runtime_error("can not disable player object");
if (reference == getPlayerPtr())
throw std::runtime_error("can not disable player object");
reference.getRefData().disable(); reference.getRefData().disable();
if(mWorldScene->getActiveCells().find (reference.getCell())!=mWorldScene->getActiveCells().end() && reference.getRefData().getCount()) if (reference.getCellRef().getRefNum().hasContentFile())
mWorldScene->removeObjectFromScene (reference); {
int type = mStore.find(Misc::StringUtils::lowerCase(reference.getCellRef().getRefId()));
if (mRendering->pagingEnableObject(type, reference, false))
mWorldScene->reloadTerrain();
} }
if(mWorldScene->getActiveCells().find (reference.getCell())!=mWorldScene->getActiveCells().end() && reference.getRefData().getCount())
mWorldScene->removeObjectFromScene (reference);
} }
void World::advanceTime (double hours, bool incremental) void World::advanceTime (double hours, bool incremental)
@ -1191,20 +1204,22 @@ namespace MWWorld
} }
if (haveToMove && newPtr.getRefData().getBaseNode()) if (haveToMove && newPtr.getRefData().getBaseNode())
{ {
mRendering->moveObject(newPtr, vec); mWorldScene->updateObjectPosition(newPtr, vec, movePhysics);
if (movePhysics) if (movePhysics)
{ {
mPhysics->updatePosition(newPtr); if (const auto object = mPhysics->getObject(ptr))
mPhysics->updatePtr(ptr, newPtr);
if (const auto object = mPhysics->getObject(newPtr))
updateNavigatorObject(object); updateNavigatorObject(object);
} }
} }
if (isPlayer) if (isPlayer)
{
mWorldScene->playerMoved(vec); mWorldScene->playerMoved(vec);
else
{
mRendering->pagingBlacklistObject(mStore.find(ptr.getCellRef().getRefId()), ptr);
mWorldScene->removeFromPagedRefs(newPtr);
} }
return newPtr; return newPtr;
} }
@ -1233,9 +1248,15 @@ namespace MWWorld
if (mPhysics->getActor(ptr)) if (mPhysics->getActor(ptr))
mNavigator->removeAgent(getPathfindingHalfExtents(ptr)); mNavigator->removeAgent(getPathfindingHalfExtents(ptr));
ptr.getCellRef().setScale(scale); if (scale != ptr.getCellRef().getScale())
{
ptr.getCellRef().setScale(scale);
mRendering->pagingBlacklistObject(mStore.find(ptr.getCellRef().getRefId()), ptr);
mWorldScene->removeFromPagedRefs(ptr);
}
mWorldScene->updateObjectScale(ptr); if(ptr.getRefData().getBaseNode() != 0)
mWorldScene->updateObjectScale(ptr);
if (mPhysics->getActor(ptr)) if (mPhysics->getActor(ptr))
mNavigator->addAgent(getPathfindingHalfExtents(ptr)); mNavigator->addAgent(getPathfindingHalfExtents(ptr));
@ -1279,6 +1300,9 @@ namespace MWWorld
ptr.getRefData().setPosition(pos); ptr.getRefData().setPosition(pos);
mRendering->pagingBlacklistObject(mStore.find(ptr.getCellRef().getRefId()), ptr);
mWorldScene->removeFromPagedRefs(ptr);
if(ptr.getRefData().getBaseNode() != 0) if(ptr.getRefData().getBaseNode() != 0)
{ {
const auto order = flags & MWBase::RotationFlag_inverseOrder const auto order = flags & MWBase::RotationFlag_inverseOrder
@ -1910,8 +1934,15 @@ namespace MWWorld
// retrieve object dimensions so we know where to place the floating label // retrieve object dimensions so we know where to place the floating label
if (!object.isEmpty ()) if (!object.isEmpty ())
{ {
osg::Vec4f screenBounds = mRendering->getScreenBounds(object); osg::BoundingBox bb = mPhysics->getBoundingBox(object);
if (!bb.valid() && object.getRefData().getBaseNode())
{
osg::ComputeBoundsVisitor computeBoundsVisitor;
computeBoundsVisitor.setTraversalMask(~(MWRender::Mask_ParticleSystem|MWRender::Mask_Effect));
object.getRefData().getBaseNode()->accept(computeBoundsVisitor);
bb = computeBoundsVisitor.getBoundingBox();
}
osg::Vec4f screenBounds = mRendering->getScreenBounds(bb);
MWBase::Environment::get().getWindowManager()->setFocusObjectScreenCoords( MWBase::Environment::get().getWindowManager()->setFocusObjectScreenCoords(
screenBounds.x(), screenBounds.y(), screenBounds.z(), screenBounds.w()); screenBounds.x(), screenBounds.y(), screenBounds.z(), screenBounds.w());
} }
@ -1941,6 +1972,14 @@ namespace MWWorld
rayToObject = mRendering->castCameraToViewportRay(0.5f, 0.5f, maxDistance, ignorePlayer); rayToObject = mRendering->castCameraToViewportRay(0.5f, 0.5f, maxDistance, ignorePlayer);
facedObject = rayToObject.mHitObject; facedObject = rayToObject.mHitObject;
if (facedObject.isEmpty() && rayToObject.mHitRefnum.hasContentFile())
{
for (CellStore* cellstore : mWorldScene->getActiveCells())
{
facedObject = cellstore->searchViaRefNum(rayToObject.mHitRefnum);
if (!facedObject.isEmpty()) break;
}
}
if (rayToObject.mHit) if (rayToObject.mHit)
mDistanceToFacedObject = (rayToObject.mRatio * maxDistance) - camDist; mDistanceToFacedObject = (rayToObject.mRatio * maxDistance) - camDist;
else else
@ -3552,6 +3591,8 @@ namespace MWWorld
std::string World::exportSceneGraph(const Ptr &ptr) std::string World::exportSceneGraph(const Ptr &ptr)
{ {
std::string file = mUserDataPath + "/openmw.osgt"; std::string file = mUserDataPath + "/openmw.osgt";
mRendering->pagingBlacklistObject(mStore.find(ptr.getCellRef().getRefId()), ptr);
mWorldScene->removeFromPagedRefs(ptr);
mRendering->exportSceneGraph(ptr, file, "Ascii"); mRendering->exportSceneGraph(ptr, file, "Ascii");
return file; return file;
} }

@ -155,6 +155,11 @@ namespace DetourNavigator
*/ */
virtual void update(const osg::Vec3f& playerPosition) = 0; virtual void update(const osg::Vec3f& playerPosition) = 0;
/**
* @brief disable navigator updates
*/
virtual void setUpdatesEnabled(bool enabled) = 0;
/** /**
* @brief wait locks thread until all tiles are updated from last update call. * @brief wait locks thread until all tiles are updated from last update call.
*/ */

@ -12,6 +12,7 @@ namespace DetourNavigator
NavigatorImpl::NavigatorImpl(const Settings& settings) NavigatorImpl::NavigatorImpl(const Settings& settings)
: mSettings(settings) : mSettings(settings)
, mNavMeshManager(mSettings) , mNavMeshManager(mSettings)
, mUpdatesEnabled(true)
{ {
} }
@ -138,11 +139,18 @@ namespace DetourNavigator
void NavigatorImpl::update(const osg::Vec3f& playerPosition) void NavigatorImpl::update(const osg::Vec3f& playerPosition)
{ {
if (!mUpdatesEnabled)
return;
removeUnusedNavMeshes(); removeUnusedNavMeshes();
for (const auto& v : mAgents) for (const auto& v : mAgents)
mNavMeshManager.update(playerPosition, v.first); mNavMeshManager.update(playerPosition, v.first);
} }
void NavigatorImpl::setUpdatesEnabled(bool enabled)
{
mUpdatesEnabled = enabled;
}
void NavigatorImpl::wait() void NavigatorImpl::wait()
{ {
mNavMeshManager.wait(); mNavMeshManager.wait();

@ -46,6 +46,8 @@ namespace DetourNavigator
void update(const osg::Vec3f& playerPosition) override; void update(const osg::Vec3f& playerPosition) override;
void setUpdatesEnabled(bool enabled) override;
void wait() override; void wait() override;
SharedNavMeshCacheItem getNavMesh(const osg::Vec3f& agentHalfExtents) const override; SharedNavMeshCacheItem getNavMesh(const osg::Vec3f& agentHalfExtents) const override;
@ -61,6 +63,7 @@ namespace DetourNavigator
private: private:
Settings mSettings; Settings mSettings;
NavMeshManager mNavMeshManager; NavMeshManager mNavMeshManager;
bool mUpdatesEnabled;
std::map<osg::Vec3f, std::size_t> mAgents; std::map<osg::Vec3f, std::size_t> mAgents;
std::unordered_map<ObjectId, ObjectId> mAvoidIds; std::unordered_map<ObjectId, ObjectId> mAvoidIds;
std::unordered_map<ObjectId, ObjectId> mWaterIds; std::unordered_map<ObjectId, ObjectId> mWaterIds;

@ -66,6 +66,8 @@ namespace DetourNavigator
void update(const osg::Vec3f& /*playerPosition*/) override {} void update(const osg::Vec3f& /*playerPosition*/) override {}
void setUpdatesEnabled(bool enabled) override {}
void wait() override {} void wait() override {}
SharedNavMeshCacheItem getNavMesh(const osg::Vec3f& /*agentHalfExtents*/) const override SharedNavMeshCacheItem getNavMesh(const osg::Vec3f& /*agentHalfExtents*/) const override

@ -232,19 +232,3 @@ void ESM::CellRef::blank()
mPos.rot[i] = 0; mPos.rot[i] = 0;
} }
} }
bool ESM::operator== (const RefNum& left, const RefNum& right)
{
return left.mIndex==right.mIndex && left.mContentFile==right.mContentFile;
}
bool ESM::operator< (const RefNum& left, const RefNum& right)
{
if (left.mIndex<right.mIndex)
return true;
if (left.mIndex>right.mIndex)
return false;
return left.mContentFile<right.mContentFile;
}

@ -114,8 +114,20 @@ namespace ESM
void blank(); void blank();
}; };
bool operator== (const RefNum& left, const RefNum& right); inline bool operator== (const RefNum& left, const RefNum& right)
bool operator< (const RefNum& left, const RefNum& right); {
return left.mIndex==right.mIndex && left.mContentFile==right.mContentFile;
}
inline bool operator< (const RefNum& left, const RefNum& right)
{
if (left.mIndex<right.mIndex)
return true;
if (left.mIndex>right.mIndex)
return false;
return left.mContentFile<right.mContentFile;
}
} }
#endif #endif

@ -352,8 +352,9 @@ void RollController::operator() (osg::Node* node, osg::NodeVisitor* nv)
} }
} }
AlphaController::AlphaController(const Nif::NiFloatData *data) AlphaController::AlphaController(const Nif::NiFloatData *data, const osg::Material* baseMaterial)
: mData(data->mKeyList, 1.f) : mData(data->mKeyList, 1.f)
, mBaseMaterial(baseMaterial)
{ {
} }
@ -365,14 +366,13 @@ AlphaController::AlphaController()
AlphaController::AlphaController(const AlphaController &copy, const osg::CopyOp &copyop) AlphaController::AlphaController(const AlphaController &copy, const osg::CopyOp &copyop)
: StateSetUpdater(copy, copyop), Controller(copy) : StateSetUpdater(copy, copyop), Controller(copy)
, mData(copy.mData) , mData(copy.mData)
, mBaseMaterial(copy.mBaseMaterial)
{ {
} }
void AlphaController::setDefaults(osg::StateSet *stateset) void AlphaController::setDefaults(osg::StateSet *stateset)
{ {
// need to create a deep copy of StateAttributes we will modify stateset->setAttribute(static_cast<osg::Material*>(mBaseMaterial->clone(osg::CopyOp::DEEP_COPY_ALL)), osg::StateAttribute::ON);
osg::Material* mat = static_cast<osg::Material*>(stateset->getAttribute(osg::StateAttribute::MATERIAL));
stateset->setAttribute(osg::clone(mat, osg::CopyOp::DEEP_COPY_ALL), osg::StateAttribute::ON);
} }
void AlphaController::apply(osg::StateSet *stateset, osg::NodeVisitor *nv) void AlphaController::apply(osg::StateSet *stateset, osg::NodeVisitor *nv)
@ -387,9 +387,10 @@ void AlphaController::apply(osg::StateSet *stateset, osg::NodeVisitor *nv)
} }
} }
MaterialColorController::MaterialColorController(const Nif::NiPosData *data, TargetColor color) MaterialColorController::MaterialColorController(const Nif::NiPosData *data, TargetColor color, const osg::Material* baseMaterial)
: mData(data->mKeyList, osg::Vec3f(1,1,1)) : mData(data->mKeyList, osg::Vec3f(1,1,1))
, mTargetColor(color) , mTargetColor(color)
, mBaseMaterial(baseMaterial)
{ {
} }
@ -401,14 +402,13 @@ MaterialColorController::MaterialColorController(const MaterialColorController &
: StateSetUpdater(copy, copyop), Controller(copy) : StateSetUpdater(copy, copyop), Controller(copy)
, mData(copy.mData) , mData(copy.mData)
, mTargetColor(copy.mTargetColor) , mTargetColor(copy.mTargetColor)
, mBaseMaterial(copy.mBaseMaterial)
{ {
} }
void MaterialColorController::setDefaults(osg::StateSet *stateset) void MaterialColorController::setDefaults(osg::StateSet *stateset)
{ {
// need to create a deep copy of StateAttributes we will modify stateset->setAttribute(static_cast<osg::Material*>(mBaseMaterial->clone(osg::CopyOp::DEEP_COPY_ALL)), osg::StateAttribute::ON);
osg::Material* mat = static_cast<osg::Material*>(stateset->getAttribute(osg::StateAttribute::MATERIAL));
stateset->setAttribute(osg::clone(mat, osg::CopyOp::DEEP_COPY_ALL), osg::StateAttribute::ON);
} }
void MaterialColorController::apply(osg::StateSet *stateset, osg::NodeVisitor *nv) void MaterialColorController::apply(osg::StateSet *stateset, osg::NodeVisitor *nv)

@ -24,6 +24,7 @@ namespace osg
{ {
class Node; class Node;
class StateSet; class StateSet;
class Material;
} }
namespace osgParticle namespace osgParticle
@ -268,9 +269,9 @@ namespace NifOsg
{ {
private: private:
FloatInterpolator mData; FloatInterpolator mData;
osg::ref_ptr<const osg::Material> mBaseMaterial;
public: public:
AlphaController(const Nif::NiFloatData *data); AlphaController(const Nif::NiFloatData *data, const osg::Material* baseMaterial);
AlphaController(); AlphaController();
AlphaController(const AlphaController& copy, const osg::CopyOp& copyop); AlphaController(const AlphaController& copy, const osg::CopyOp& copyop);
@ -291,7 +292,7 @@ namespace NifOsg
Specular = 2, Specular = 2,
Emissive = 3 Emissive = 3
}; };
MaterialColorController(const Nif::NiPosData *data, TargetColor color); MaterialColorController(const Nif::NiPosData *data, TargetColor color, const osg::Material* baseMaterial);
MaterialColorController(); MaterialColorController();
MaterialColorController(const MaterialColorController& copy, const osg::CopyOp& copyop); MaterialColorController(const MaterialColorController& copy, const osg::CopyOp& copyop);
@ -304,6 +305,7 @@ namespace NifOsg
private: private:
Vec3Interpolator mData; Vec3Interpolator mData;
TargetColor mTargetColor = Ambient; TargetColor mTargetColor = Ambient;
osg::ref_ptr<const osg::Material> mBaseMaterial;
}; };
class FlipController : public SceneUtil::StateSetUpdater, public SceneUtil::Controller class FlipController : public SceneUtil::StateSetUpdater, public SceneUtil::Controller

@ -639,7 +639,15 @@ namespace NifOsg
handleParticleSystem(nifNode, node, composite, animflags, rootNode); handleParticleSystem(nifNode, node, composite, animflags, rootNode);
if (composite->getNumControllers() > 0) if (composite->getNumControllers() > 0)
node->addUpdateCallback(composite); {
osg::Callback *cb = composite;
if (composite->getNumControllers() == 1)
cb = composite->getController(0);
if (animflags & Nif::NiNode::AnimFlag_AutoPlay)
node->addCullCallback(cb);
else
node->addUpdateCallback(cb); // have to remain as UpdateCallback so AssignControllerSourcesVisitor can find it.
}
bool isAnimated = false; bool isAnimated = false;
handleNodeControllers(nifNode, node, animflags, isAnimated); handleNodeControllers(nifNode, node, animflags, isAnimated);
@ -778,7 +786,7 @@ namespace NifOsg
} }
} }
void handleMaterialControllers(const Nif::Property *materialProperty, SceneUtil::CompositeStateSetUpdater* composite, int animflags) void handleMaterialControllers(const Nif::Property *materialProperty, SceneUtil::CompositeStateSetUpdater* composite, int animflags, const osg::Material* baseMaterial)
{ {
for (Nif::ControllerPtr ctrl = materialProperty->controller; !ctrl.empty(); ctrl = ctrl->next) for (Nif::ControllerPtr ctrl = materialProperty->controller; !ctrl.empty(); ctrl = ctrl->next)
{ {
@ -789,7 +797,7 @@ namespace NifOsg
const Nif::NiAlphaController* alphactrl = static_cast<const Nif::NiAlphaController*>(ctrl.getPtr()); const Nif::NiAlphaController* alphactrl = static_cast<const Nif::NiAlphaController*>(ctrl.getPtr());
if (alphactrl->data.empty()) if (alphactrl->data.empty())
continue; continue;
osg::ref_ptr<AlphaController> osgctrl(new AlphaController(alphactrl->data.getPtr())); osg::ref_ptr<AlphaController> osgctrl(new AlphaController(alphactrl->data.getPtr(), baseMaterial));
setupController(alphactrl, osgctrl, animflags); setupController(alphactrl, osgctrl, animflags);
composite->addController(osgctrl); composite->addController(osgctrl);
} }
@ -799,7 +807,7 @@ namespace NifOsg
if (matctrl->data.empty()) if (matctrl->data.empty())
continue; continue;
auto targetColor = static_cast<MaterialColorController::TargetColor>(matctrl->targetColor); auto targetColor = static_cast<MaterialColorController::TargetColor>(matctrl->targetColor);
osg::ref_ptr<MaterialColorController> osgctrl(new MaterialColorController(matctrl->data.getPtr(), targetColor)); osg::ref_ptr<MaterialColorController> osgctrl(new MaterialColorController(matctrl->data.getPtr(), targetColor, baseMaterial));
setupController(matctrl, osgctrl, animflags); setupController(matctrl, osgctrl, animflags);
composite->addController(osgctrl); composite->addController(osgctrl);
} }
@ -1767,7 +1775,7 @@ namespace NifOsg
if (!matprop->controller.empty()) if (!matprop->controller.empty())
{ {
hasMatCtrl = true; hasMatCtrl = true;
handleMaterialControllers(matprop, composite, animflags); handleMaterialControllers(matprop, composite, animflags, mat);
} }
break; break;

@ -277,7 +277,7 @@ Emitter::Emitter(const Emitter &copy, const osg::CopyOp &copyop)
, mPlacer(copy.mPlacer) , mPlacer(copy.mPlacer)
, mShooter(copy.mShooter) , mShooter(copy.mShooter)
// need a deep copy because the remainder is stored in the object // need a deep copy because the remainder is stored in the object
, mCounter(osg::clone(copy.mCounter.get(), osg::CopyOp::DEEP_COPY_ALL)) , mCounter(static_cast<osgParticle::Counter*>(copy.mCounter->clone(osg::CopyOp::DEEP_COPY_ALL)))
{ {
} }

@ -161,13 +161,13 @@ class GenericObjectCache : public osg::Referenced
} }
} }
/** call operator()(osg::Object*) for each object in the cache. */ /** call operator()(KeyType, osg::Object*) for each object in the cache. */
template <class Functor> template <class Functor>
void call(Functor& f) void call(Functor& f)
{ {
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex); OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
for (typename ObjectCacheMap::iterator it = _objectCache.begin(); it != _objectCache.end(); ++it) for (typename ObjectCacheMap::iterator it = _objectCache.begin(); it != _objectCache.end(); ++it)
f(it->second.first.get()); f(it->first, it->second.first.get());
} }
/** Get the number of objects in the cache. */ /** Get the number of objects in the cache. */

@ -466,7 +466,7 @@ namespace Resource
return options; return options;
} }
osg::ref_ptr<const osg::Node> SceneManager::getTemplate(const std::string &name) osg::ref_ptr<const osg::Node> SceneManager::getTemplate(const std::string &name, bool compile)
{ {
std::string normalized = name; std::string normalized = name;
mVFS->normalizeFilename(normalized); mVFS->normalizeFilename(normalized);
@ -529,7 +529,7 @@ namespace Resource
optimizer.optimize(loaded, options); optimizer.optimize(loaded, options);
} }
if (mIncrementalCompileOperation) if (compile && mIncrementalCompileOperation)
mIncrementalCompileOperation->add(loaded); mIncrementalCompileOperation->add(loaded);
else else
loaded->getBound(); loaded->getBound();
@ -577,7 +577,7 @@ namespace Resource
osg::ref_ptr<osg::Node> SceneManager::createInstance(const osg::Node *base) osg::ref_ptr<osg::Node> SceneManager::createInstance(const osg::Node *base)
{ {
osg::ref_ptr<osg::Node> cloned = osg::clone(base, SceneUtil::CopyOp()); osg::ref_ptr<osg::Node> cloned = static_cast<osg::Node*>(base->clone(SceneUtil::CopyOp()));
// add a ref to the original template, to hint to the cache that it's still being used and should be kept in cache // add a ref to the original template, to hint to the cache that it's still being used and should be kept in cache
cloned->getOrCreateUserDataContainer()->addUserObject(new TemplateRef(base)); cloned->getOrCreateUserDataContainer()->addUserObject(new TemplateRef(base));
@ -713,6 +713,24 @@ namespace Resource
mSharedStateMutex.lock(); mSharedStateMutex.lock();
mSharedStateManager->prune(); mSharedStateManager->prune();
mSharedStateMutex.unlock(); mSharedStateMutex.unlock();
if (mIncrementalCompileOperation)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(*mIncrementalCompileOperation->getToCompiledMutex());
osgUtil::IncrementalCompileOperation::CompileSets& sets = mIncrementalCompileOperation->getToCompile();
for(osgUtil::IncrementalCompileOperation::CompileSets::iterator it = sets.begin(); it != sets.end();)
{
int refcount = (*it)->_subgraphToCompile->referenceCount();
if ((*it)->_subgraphToCompile->asDrawable()) refcount -= 1; // ref by CompileList.
if (refcount <= 2) // ref by ObjectCache + ref by _subgraphToCompile.
{
// no other ref = not needed anymore.
it = sets.erase(it);
}
else
++it;
}
}
} }
void SceneManager::clearCache() void SceneManager::clearCache()

@ -81,7 +81,7 @@ namespace Resource
/// @note If the given filename does not exist or fails to load, an error marker mesh will be used instead. /// @note If the given filename does not exist or fails to load, an error marker mesh will be used instead.
/// If even the error marker mesh can not be found, an exception is thrown. /// If even the error marker mesh can not be found, an exception is thrown.
/// @note Thread safe. /// @note Thread safe.
osg::ref_ptr<const osg::Node> getTemplate(const std::string& name); osg::ref_ptr<const osg::Node> getTemplate(const std::string& name, bool compile=true);
/// Create an instance of the given scene template and cache it for later use, so that future calls to getInstance() can simply /// Create an instance of the given scene template and cache it for later use, so that future calls to getInstance() can simply
/// return this cached object instead of creating a new one. /// return this cached object instead of creating a new one.

@ -292,6 +292,7 @@ void StatsHandler::setUpScene(osgViewer::ViewerBase *viewer)
"Nif", "Nif",
"Keyframe", "Keyframe",
"", "",
"Object Chunk",
"Terrain Chunk", "Terrain Chunk",
"Terrain Texture", "Terrain Texture",
"Land", "Land",

@ -22,20 +22,11 @@ namespace SceneUtil
| osg::CopyOp::DEEP_COPY_USERDATA); | osg::CopyOp::DEEP_COPY_USERDATA);
} }
osg::StateSet* CopyOp::operator ()(const osg::StateSet* stateset) const
{
if (!stateset)
return nullptr;
if (stateset->getDataVariance() == osg::StateSet::DYNAMIC)
return osg::clone(stateset, *this);
return const_cast<osg::StateSet*>(stateset);
}
osg::Object* CopyOp::operator ()(const osg::Object* node) const osg::Object* CopyOp::operator ()(const osg::Object* node) const
{ {
// We should copy node transformations when we copy node // We should copy node transformations when we copy node
if (const NifOsg::NodeUserData* data = dynamic_cast<const NifOsg::NodeUserData*>(node)) if (dynamic_cast<const NifOsg::NodeUserData*>(node))
return osg::clone(data, *this); return static_cast<NifOsg::NodeUserData*>(node->clone(*this));
return osg::CopyOp::operator()(node); return osg::CopyOp::operator()(node);
} }
@ -60,7 +51,7 @@ namespace SceneUtil
if (dynamic_cast<const SceneUtil::RigGeometry*>(drawable) || dynamic_cast<const SceneUtil::MorphGeometry*>(drawable)) if (dynamic_cast<const SceneUtil::RigGeometry*>(drawable) || dynamic_cast<const SceneUtil::MorphGeometry*>(drawable))
{ {
return osg::clone(drawable, *this); return static_cast<osg::Drawable*>(drawable->clone(*this));
} }
return osg::CopyOp::operator()(drawable); return osg::CopyOp::operator()(drawable);
@ -68,7 +59,7 @@ namespace SceneUtil
osgParticle::ParticleProcessor* CopyOp::operator() (const osgParticle::ParticleProcessor* processor) const osgParticle::ParticleProcessor* CopyOp::operator() (const osgParticle::ParticleProcessor* processor) const
{ {
osgParticle::ParticleProcessor* cloned = osg::clone(processor, osg::CopyOp::DEEP_COPY_CALLBACKS); osgParticle::ParticleProcessor* cloned = static_cast<osgParticle::ParticleProcessor*>(processor->clone(osg::CopyOp::DEEP_COPY_CALLBACKS));
for (const auto& oldPsNewPsPair : mOldPsToNewPs) for (const auto& oldPsNewPsPair : mOldPsToNewPs)
{ {
if (processor->getParticleSystem() == oldPsNewPsPair.first) if (processor->getParticleSystem() == oldPsNewPsPair.first)
@ -84,7 +75,7 @@ namespace SceneUtil
osgParticle::ParticleSystem* CopyOp::operator ()(const osgParticle::ParticleSystem* partsys) const osgParticle::ParticleSystem* CopyOp::operator ()(const osgParticle::ParticleSystem* partsys) const
{ {
osgParticle::ParticleSystem* cloned = osg::clone(partsys, *this); osgParticle::ParticleSystem* cloned = static_cast<osgParticle::ParticleSystem*>(partsys->clone(*this));
for (const auto& processorPsPair : mProcessorToOldPs) for (const auto& processorPsPair : mProcessorToOldPs)
{ {

@ -17,7 +17,6 @@ namespace SceneUtil
/// @par Defines the cloning behaviour we need: /// @par Defines the cloning behaviour we need:
/// * Assigns updated ParticleSystem pointers on cloned emitters and programs. /// * Assigns updated ParticleSystem pointers on cloned emitters and programs.
/// * Creates deep copy of StateSets if they have a DYNAMIC data variance.
/// * Deep copies RigGeometry and MorphGeometry so they can animate without affecting clones. /// * Deep copies RigGeometry and MorphGeometry so they can animate without affecting clones.
/// @warning Do not use an object of this class for more than one copy operation. /// @warning Do not use an object of this class for more than one copy operation.
class CopyOp : public osg::CopyOp class CopyOp : public osg::CopyOp
@ -31,7 +30,6 @@ namespace SceneUtil
virtual osg::Node* operator() (const osg::Node* node) const; virtual osg::Node* operator() (const osg::Node* node) const;
virtual osg::Drawable* operator() (const osg::Drawable* drawable) const; virtual osg::Drawable* operator() (const osg::Drawable* drawable) const;
virtual osg::StateSet* operator() (const osg::StateSet* stateset) const;
virtual osg::Object* operator ()(const osg::Object* node) const; virtual osg::Object* operator ()(const osg::Object* node) const;
private: private:

@ -44,7 +44,7 @@ void MorphGeometry::setSourceGeometry(osg::ref_ptr<osg::Geometry> sourceGeom)
osg::ref_ptr<osg::VertexBufferObject> vbo (new osg::VertexBufferObject); osg::ref_ptr<osg::VertexBufferObject> vbo (new osg::VertexBufferObject);
vbo->setUsage(GL_DYNAMIC_DRAW_ARB); vbo->setUsage(GL_DYNAMIC_DRAW_ARB);
osg::ref_ptr<osg::Array> vertexArray = osg::clone(from.getVertexArray(), osg::CopyOp::DEEP_COPY_ALL); osg::ref_ptr<osg::Array> vertexArray = static_cast<osg::Array*>(from.getVertexArray()->clone(osg::CopyOp::DEEP_COPY_ALL));
if (vertexArray) if (vertexArray)
{ {
vertexArray->setVertexBufferObject(vbo); vertexArray->setVertexBufferObject(vbo);

@ -22,6 +22,7 @@
#include <osg/CullFace> #include <osg/CullFace>
#include <osg/Geometry> #include <osg/Geometry>
#include <osg/io_utils> #include <osg/io_utils>
#include <osg/Depth>
#include <sstream> #include <sstream>
@ -1580,7 +1581,9 @@ void MWShadowTechnique::createShaders()
_shadowCastingStateSet->setTextureAttributeAndModes(0, _fallbackBaseTexture.get(), osg::StateAttribute::ON); _shadowCastingStateSet->setTextureAttributeAndModes(0, _fallbackBaseTexture.get(), osg::StateAttribute::ON);
_shadowCastingStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", false)); _shadowCastingStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", false));
_shadowCastingStateSet->addUniform(_shadowMapAlphaTestDisableUniform); _shadowCastingStateSet->addUniform(_shadowMapAlphaTestDisableUniform);
osg::ref_ptr<osg::Depth> depth = new osg::Depth;
depth->setWriteMask(true);
_shadowCastingStateSet->setAttribute(depth, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
_shadowCastingStateSet->setMode(GL_DEPTH_CLAMP, osg::StateAttribute::ON); _shadowCastingStateSet->setMode(GL_DEPTH_CLAMP, osg::StateAttribute::ON);
_shadowCastingStateSet->setRenderBinDetails(osg::StateSet::OPAQUE_BIN, "RenderBin", osg::StateSet::OVERRIDE_PROTECTED_RENDERBIN_DETAILS); _shadowCastingStateSet->setRenderBinDetails(osg::StateSet::OPAQUE_BIN, "RenderBin", osg::StateSet::OVERRIDE_PROTECTED_RENDERBIN_DETAILS);

@ -18,6 +18,7 @@
#include "optimizer.hpp" #include "optimizer.hpp"
#include <osg/Version>
#include <osg/Transform> #include <osg/Transform>
#include <osg/MatrixTransform> #include <osg/MatrixTransform>
#include <osg/PositionAttitudeTransform> #include <osg/PositionAttitudeTransform>
@ -27,6 +28,7 @@
#include <osg/Notify> #include <osg/Notify>
#include <osg/Timer> #include <osg/Timer>
#include <osg/io_utils> #include <osg/io_utils>
#include <osg/Depth>
#include <osgUtil/TransformAttributeFunctor> #include <osgUtil/TransformAttributeFunctor>
#include <osgUtil/Statistics> #include <osgUtil/Statistics>
@ -103,7 +105,9 @@ void Optimizer::optimize(osg::Node* node, unsigned int options)
osg::Timer_t startTick = osg::Timer::instance()->tick(); osg::Timer_t startTick = osg::Timer::instance()->tick();
MergeGeometryVisitor mgv(this); MergeGeometryVisitor mgv(this);
mgv.setTargetMaximumNumberOfVertices(10000); mgv.setTargetMaximumNumberOfVertices(1000000);
mgv.setMergeAlphaBlending(_mergeAlphaBlending);
mgv.setViewPoint(_viewPoint);
node->accept(mgv); node->accept(mgv);
osg::Timer_t endTick = osg::Timer::instance()->tick(); osg::Timer_t endTick = osg::Timer::instance()->tick();
@ -585,17 +589,36 @@ void Optimizer::FlattenStaticTransformsVisitor::apply(osg::Node& node)
traverse(node); traverse(node);
} }
bool needvbo(const osg::Geometry* geom)
{
#if OSG_MIN_VERSION_REQUIRED(3,5,6)
return true;
#else
return geom->getUseVertexBufferObjects();
#endif
}
osg::Array* cloneArray(osg::Array* array, osg::VertexBufferObject*& vbo, const osg::Geometry* geom)
{
array = static_cast<osg::Array*>(array->clone(osg::CopyOp::DEEP_COPY_ALL));
if (!vbo && needvbo(geom))
vbo = new osg::VertexBufferObject;
if (vbo)
array->setVertexBufferObject(vbo);
return array;
}
void Optimizer::FlattenStaticTransformsVisitor::apply(osg::Drawable& drawable) void Optimizer::FlattenStaticTransformsVisitor::apply(osg::Drawable& drawable)
{ {
osg::Geometry *geometry = drawable.asGeometry(); osg::Geometry *geometry = drawable.asGeometry();
if((geometry) && (isOperationPermissibleForObject(&drawable))) if((geometry) && (isOperationPermissibleForObject(&drawable)))
{ {
osg::VertexBufferObject* vbo = nullptr;
if(geometry->getVertexArray() && geometry->getVertexArray()->referenceCount() > 1) { if(geometry->getVertexArray() && geometry->getVertexArray()->referenceCount() > 1) {
geometry->setVertexArray(dynamic_cast<osg::Array*>(geometry->getVertexArray()->clone(osg::CopyOp::DEEP_COPY_ALL))); geometry->setVertexArray(cloneArray(geometry->getVertexArray(), vbo, geometry));
} }
if(geometry->getNormalArray() && geometry->getNormalArray()->referenceCount() > 1) { if(geometry->getNormalArray() && geometry->getNormalArray()->referenceCount() > 1) {
geometry->setNormalArray(dynamic_cast<osg::Array*>(geometry->getNormalArray()->clone(osg::CopyOp::DEEP_COPY_ALL))); geometry->setNormalArray(cloneArray(geometry->getNormalArray(), vbo, geometry));
} }
} }
_drawableSet.insert(&drawable); _drawableSet.insert(&drawable);
@ -988,6 +1011,17 @@ struct LessGeometry
} }
}; };
struct LessGeometryViewPoint
{
osg::Vec3f _viewPoint;
bool operator() (const osg::ref_ptr<osg::Geometry>& lhs,const osg::ref_ptr<osg::Geometry>& rhs) const
{
float len1 = (lhs->getBoundingBox().center() - _viewPoint).length2();
float len2 = (rhs->getBoundingBox().center() - _viewPoint).length2();
return len2 < len1;
}
};
struct LessGeometryPrimitiveType struct LessGeometryPrimitiveType
{ {
bool operator() (const osg::ref_ptr<osg::Geometry>& lhs,const osg::ref_ptr<osg::Geometry>& rhs) const bool operator() (const osg::ref_ptr<osg::Geometry>& lhs,const osg::ref_ptr<osg::Geometry>& rhs) const
@ -1055,16 +1089,16 @@ bool isAbleToMerge(const osg::Geometry& g1, const osg::Geometry& g2)
void Optimizer::MergeGeometryVisitor::pushStateSet(osg::StateSet *stateSet) void Optimizer::MergeGeometryVisitor::pushStateSet(osg::StateSet *stateSet)
{ {
_stateSetStack.push_back(stateSet); _stateSetStack.push_back(stateSet);
checkAllowedToMerge(); checkAlphaBlendingActive();
} }
void Optimizer::MergeGeometryVisitor::popStateSet() void Optimizer::MergeGeometryVisitor::popStateSet()
{ {
_stateSetStack.pop_back(); _stateSetStack.pop_back();
checkAllowedToMerge(); checkAlphaBlendingActive();
} }
void Optimizer::MergeGeometryVisitor::checkAllowedToMerge() void Optimizer::MergeGeometryVisitor::checkAlphaBlendingActive()
{ {
int renderingHint = 0; int renderingHint = 0;
bool override = false; bool override = false;
@ -1080,7 +1114,7 @@ void Optimizer::MergeGeometryVisitor::checkAllowedToMerge()
override = true; override = true;
} }
// Can't merge Geometry that are using a transparent sorting bin as that would cause the sorting to break. // Can't merge Geometry that are using a transparent sorting bin as that would cause the sorting to break.
_allowedToMerge = renderingHint != osg::StateSet::TRANSPARENT_BIN; _alphaBlendingActive = renderingHint == osg::StateSet::TRANSPARENT_BIN;
} }
void Optimizer::MergeGeometryVisitor::apply(osg::Group &group) void Optimizer::MergeGeometryVisitor::apply(osg::Group &group)
@ -1088,7 +1122,7 @@ void Optimizer::MergeGeometryVisitor::apply(osg::Group &group)
if (group.getStateSet()) if (group.getStateSet())
pushStateSet(group.getStateSet()); pushStateSet(group.getStateSet());
if (_allowedToMerge) if (!_alphaBlendingActive || _mergeAlphaBlending)
mergeGroup(group); mergeGroup(group);
traverse(group); traverse(group);
@ -1097,6 +1131,30 @@ void Optimizer::MergeGeometryVisitor::apply(osg::Group &group)
popStateSet(); popStateSet();
} }
osg::PrimitiveSet* clonePrimitive(osg::PrimitiveSet* ps, osg::ElementBufferObject*& ebo, const osg::Geometry* geom)
{
if (ps->referenceCount() <= 1)
return ps;
ps = static_cast<osg::PrimitiveSet*>(ps->clone(osg::CopyOp::DEEP_COPY_ALL));
osg::DrawElements* drawElements = ps->getDrawElements();
if (!drawElements) return ps;
if (!ebo && needvbo(geom))
ebo = new osg::ElementBufferObject;
if (ebo)
drawElements->setElementBufferObject(ebo);
return ps;
}
bool containsSharedPrimitives(const osg::Geometry* geom)
{
for (unsigned int i=0; i<geom->getNumPrimitiveSets(); ++i)
if (geom->getPrimitiveSet(i)->referenceCount() > 1) return true;
return false;
}
bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group) bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group)
{ {
if (!isOperationPermissibleForObject(&group)) return false; if (!isOperationPermissibleForObject(&group)) return false;
@ -1120,7 +1178,7 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group)
osg::Geometry* geom = child->asGeometry(); osg::Geometry* geom = child->asGeometry();
if (geom) if (geom)
{ {
if (!geometryContainsSharedArrays(*geom) && if (
geom->getDataVariance()!=osg::Object::DYNAMIC && geom->getDataVariance()!=osg::Object::DYNAMIC &&
isOperationPermissibleForObject(geom)) isOperationPermissibleForObject(geom))
{ {
@ -1254,6 +1312,12 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group)
DuplicateList& duplicateList = *mitr; DuplicateList& duplicateList = *mitr;
if (!duplicateList.empty()) if (!duplicateList.empty())
{ {
if (_alphaBlendingActive)
{
LessGeometryViewPoint lgvp;
lgvp._viewPoint = _viewPoint;
std::sort(duplicateList.begin(), duplicateList.end(), lgvp);
}
DuplicateList::iterator ditr = duplicateList.begin(); DuplicateList::iterator ditr = duplicateList.begin();
osg::ref_ptr<osg::Geometry> lhs = *ditr++; osg::ref_ptr<osg::Geometry> lhs = *ditr++;
group.addChild(lhs.get()); group.addChild(lhs.get());
@ -1278,6 +1342,7 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group)
if (!drawable) if (!drawable)
continue; continue;
osg::Geometry* geom = drawable->asGeometry(); osg::Geometry* geom = drawable->asGeometry();
osg::ElementBufferObject* ebo = nullptr;
if (geom) if (geom)
{ {
osg::Geometry::PrimitiveSetList& primitives = geom->getPrimitiveSetList(); osg::Geometry::PrimitiveSetList& primitives = geom->getPrimitiveSetList();
@ -1290,10 +1355,12 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group)
{ {
if (prim->getNumIndices()==3) if (prim->getNumIndices()==3)
{ {
prim = clonePrimitive(prim, ebo, geom); (*itr) = prim;
prim->setMode(osg::PrimitiveSet::TRIANGLES); prim->setMode(osg::PrimitiveSet::TRIANGLES);
} }
else if (prim->getNumIndices()==4) else if (prim->getNumIndices()==4)
{ {
prim = clonePrimitive(prim, ebo, geom); (*itr) = prim;
prim->setMode(osg::PrimitiveSet::QUADS); prim->setMode(osg::PrimitiveSet::QUADS);
} }
} }
@ -1308,6 +1375,7 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group)
if (!drawable) if (!drawable)
continue; continue;
osg::Geometry* geom = drawable->asGeometry(); osg::Geometry* geom = drawable->asGeometry();
osg::ElementBufferObject* ebo = nullptr;
if (geom) if (geom)
{ {
if (geom->getNumPrimitiveSets()>0 && if (geom->getNumPrimitiveSets()>0 &&
@ -1320,6 +1388,8 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group)
#if 1 #if 1
bool doneCombine = false; bool doneCombine = false;
std::set<osg::PrimitiveSet*> toremove;
osg::Geometry::PrimitiveSetList& primitives = geom->getPrimitiveSetList(); osg::Geometry::PrimitiveSetList& primitives = geom->getPrimitiveSetList();
unsigned int lhsNo=0; unsigned int lhsNo=0;
unsigned int rhsNo=1; unsigned int rhsNo=1;
@ -1348,6 +1418,8 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group)
if (combine) if (combine)
{ {
lhs = clonePrimitive(lhs, ebo, geom);
primitives[lhsNo] = lhs;
switch(lhs->getType()) switch(lhs->getType())
{ {
@ -1375,7 +1447,7 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group)
if (combine) if (combine)
{ {
// make this primitive set as invalid and needing cleaning up. // make this primitive set as invalid and needing cleaning up.
rhs->setMode(0xffffff); toremove.insert(rhs);
doneCombine = true; doneCombine = true;
++rhsNo; ++rhsNo;
} }
@ -1390,7 +1462,6 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group)
if (doneCombine) if (doneCombine)
{ {
// now need to clean up primitiveset so it no longer contains the rhs combined primitives. // now need to clean up primitiveset so it no longer contains the rhs combined primitives.
// first swap with a empty primitiveSet to empty it completely. // first swap with a empty primitiveSet to empty it completely.
osg::Geometry::PrimitiveSetList oldPrimitives; osg::Geometry::PrimitiveSetList oldPrimitives;
primitives.swap(oldPrimitives); primitives.swap(oldPrimitives);
@ -1400,7 +1471,7 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group)
pitr != oldPrimitives.end(); pitr != oldPrimitives.end();
++pitr) ++pitr)
{ {
if ((*pitr)->getMode()!=0xffffff) primitives.push_back(*pitr); if (!toremove.count(*pitr)) primitives.push_back(*pitr);
} }
} }
#endif #endif
@ -1467,6 +1538,18 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group)
} }
} }
#endif #endif
if (doneCombine && !geom->containsSharedArrays() && !containsSharedPrimitives(geom))
{
// prefer to use vbo for merged geometries as vbo uses less memory than display lists.
geom->setUseVertexBufferObjects(true);
geom->setUseDisplayList(false);
}
if (_alphaBlendingActive && _mergeAlphaBlending && !geom->getStateSet())
{
osg::Depth* d = new osg::Depth;
d->setWriteMask(0);
geom->getOrCreateStateSet()->setAttribute(d);
}
} }
} }
@ -1479,34 +1562,6 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group)
return false; return false;
} }
bool Optimizer::MergeGeometryVisitor::geometryContainsSharedArrays(osg::Geometry& geom)
{
if (geom.getVertexArray() && geom.getVertexArray()->referenceCount()>1) return true;
if (geom.getNormalArray() && geom.getNormalArray()->referenceCount()>1) return true;
if (geom.getColorArray() && geom.getColorArray()->referenceCount()>1) return true;
if (geom.getSecondaryColorArray() && geom.getSecondaryColorArray()->referenceCount()>1) return true;
if (geom.getFogCoordArray() && geom.getFogCoordArray()->referenceCount()>1) return true;
for(unsigned int unit=0;unit<geom.getNumTexCoordArrays();++unit)
{
osg::Array* tex = geom.getTexCoordArray(unit);
if (tex && tex->referenceCount()>1) return true;
}
// shift the indices of the incoming primitives to account for the pre existing geometry.
for(osg::Geometry::PrimitiveSetList::iterator primItr=geom.getPrimitiveSetList().begin();
primItr!=geom.getPrimitiveSetList().end();
++primItr)
{
if ((*primItr)->referenceCount()>1) return true;
}
return false;
}
class MergeArrayVisitor : public osg::ArrayVisitor class MergeArrayVisitor : public osg::ArrayVisitor
{ {
protected: protected:
@ -1574,14 +1629,14 @@ class MergeArrayVisitor : public osg::ArrayVisitor
bool Optimizer::MergeGeometryVisitor::mergeGeometry(osg::Geometry& lhs,osg::Geometry& rhs) bool Optimizer::MergeGeometryVisitor::mergeGeometry(osg::Geometry& lhs,osg::Geometry& rhs)
{ {
MergeArrayVisitor merger; MergeArrayVisitor merger;
osg::VertexBufferObject* vbo = nullptr;
unsigned int base = 0; unsigned int base = 0;
if (lhs.getVertexArray() && rhs.getVertexArray()) if (lhs.getVertexArray() && rhs.getVertexArray())
{ {
base = lhs.getVertexArray()->getNumElements(); base = lhs.getVertexArray()->getNumElements();
if (lhs.getVertexArray()->referenceCount() > 1)
lhs.setVertexArray(cloneArray(lhs.getVertexArray(), vbo, &lhs));
if (!merger.merge(lhs.getVertexArray(),rhs.getVertexArray())) if (!merger.merge(lhs.getVertexArray(),rhs.getVertexArray()))
{ {
OSG_DEBUG << "MergeGeometry: vertex array not merged. Some data may be lost." <<std::endl; OSG_DEBUG << "MergeGeometry: vertex array not merged. Some data may be lost." <<std::endl;
@ -1596,6 +1651,8 @@ bool Optimizer::MergeGeometryVisitor::mergeGeometry(osg::Geometry& lhs,osg::Geom
if (lhs.getNormalArray() && rhs.getNormalArray() && lhs.getNormalArray()->getBinding()!=osg::Array::BIND_OVERALL) if (lhs.getNormalArray() && rhs.getNormalArray() && lhs.getNormalArray()->getBinding()!=osg::Array::BIND_OVERALL)
{ {
if (lhs.getNormalArray()->referenceCount() > 1)
lhs.setNormalArray(cloneArray(lhs.getNormalArray(), vbo, &lhs));
if (!merger.merge(lhs.getNormalArray(),rhs.getNormalArray())) if (!merger.merge(lhs.getNormalArray(),rhs.getNormalArray()))
{ {
OSG_DEBUG << "MergeGeometry: normal array not merged. Some data may be lost." <<std::endl; OSG_DEBUG << "MergeGeometry: normal array not merged. Some data may be lost." <<std::endl;
@ -1609,6 +1666,8 @@ bool Optimizer::MergeGeometryVisitor::mergeGeometry(osg::Geometry& lhs,osg::Geom
if (lhs.getColorArray() && rhs.getColorArray() && lhs.getColorArray()->getBinding()!=osg::Array::BIND_OVERALL) if (lhs.getColorArray() && rhs.getColorArray() && lhs.getColorArray()->getBinding()!=osg::Array::BIND_OVERALL)
{ {
if (lhs.getColorArray()->referenceCount() > 1)
lhs.setColorArray(cloneArray(lhs.getColorArray(), vbo, &lhs));
if (!merger.merge(lhs.getColorArray(),rhs.getColorArray())) if (!merger.merge(lhs.getColorArray(),rhs.getColorArray()))
{ {
OSG_DEBUG << "MergeGeometry: color array not merged. Some data may be lost." <<std::endl; OSG_DEBUG << "MergeGeometry: color array not merged. Some data may be lost." <<std::endl;
@ -1621,6 +1680,8 @@ bool Optimizer::MergeGeometryVisitor::mergeGeometry(osg::Geometry& lhs,osg::Geom
if (lhs.getSecondaryColorArray() && rhs.getSecondaryColorArray() && lhs.getSecondaryColorArray()->getBinding()!=osg::Array::BIND_OVERALL) if (lhs.getSecondaryColorArray() && rhs.getSecondaryColorArray() && lhs.getSecondaryColorArray()->getBinding()!=osg::Array::BIND_OVERALL)
{ {
if (lhs.getSecondaryColorArray()->referenceCount() > 1)
lhs.setSecondaryColorArray(cloneArray(lhs.getSecondaryColorArray(), vbo, &lhs));
if (!merger.merge(lhs.getSecondaryColorArray(),rhs.getSecondaryColorArray())) if (!merger.merge(lhs.getSecondaryColorArray(),rhs.getSecondaryColorArray()))
{ {
OSG_DEBUG << "MergeGeometry: secondary color array not merged. Some data may be lost." <<std::endl; OSG_DEBUG << "MergeGeometry: secondary color array not merged. Some data may be lost." <<std::endl;
@ -1633,6 +1694,8 @@ bool Optimizer::MergeGeometryVisitor::mergeGeometry(osg::Geometry& lhs,osg::Geom
if (lhs.getFogCoordArray() && rhs.getFogCoordArray() && lhs.getFogCoordArray()->getBinding()!=osg::Array::BIND_OVERALL) if (lhs.getFogCoordArray() && rhs.getFogCoordArray() && lhs.getFogCoordArray()->getBinding()!=osg::Array::BIND_OVERALL)
{ {
if (lhs.getFogCoordArray()->referenceCount() > 1)
lhs.setFogCoordArray(cloneArray(lhs.getFogCoordArray(), vbo, &lhs));
if (!merger.merge(lhs.getFogCoordArray(),rhs.getFogCoordArray())) if (!merger.merge(lhs.getFogCoordArray(),rhs.getFogCoordArray()))
{ {
OSG_DEBUG << "MergeGeometry: fog coord array not merged. Some data may be lost." <<std::endl; OSG_DEBUG << "MergeGeometry: fog coord array not merged. Some data may be lost." <<std::endl;
@ -1647,6 +1710,9 @@ bool Optimizer::MergeGeometryVisitor::mergeGeometry(osg::Geometry& lhs,osg::Geom
unsigned int unit; unsigned int unit;
for(unit=0;unit<lhs.getNumTexCoordArrays();++unit) for(unit=0;unit<lhs.getNumTexCoordArrays();++unit)
{ {
if (!lhs.getTexCoordArray(unit)) continue;
if (lhs.getTexCoordArray(unit)->referenceCount() > 1)
lhs.setTexCoordArray(unit, cloneArray(lhs.getTexCoordArray(unit), vbo, &lhs));
if (!merger.merge(lhs.getTexCoordArray(unit),rhs.getTexCoordArray(unit))) if (!merger.merge(lhs.getTexCoordArray(unit),rhs.getTexCoordArray(unit)))
{ {
OSG_DEBUG << "MergeGeometry: tex coord array not merged. Some data may be lost." <<std::endl; OSG_DEBUG << "MergeGeometry: tex coord array not merged. Some data may be lost." <<std::endl;
@ -1655,14 +1721,17 @@ bool Optimizer::MergeGeometryVisitor::mergeGeometry(osg::Geometry& lhs,osg::Geom
for(unit=0;unit<lhs.getNumVertexAttribArrays();++unit) for(unit=0;unit<lhs.getNumVertexAttribArrays();++unit)
{ {
if (!lhs.getVertexAttribArray(unit)) continue;
if (lhs.getVertexAttribArray(unit)->referenceCount() > 1)
lhs.setVertexAttribArray(unit, cloneArray(lhs.getVertexAttribArray(unit), vbo, &lhs));
if (!merger.merge(lhs.getVertexAttribArray(unit),rhs.getVertexAttribArray(unit))) if (!merger.merge(lhs.getVertexAttribArray(unit),rhs.getVertexAttribArray(unit)))
{ {
OSG_DEBUG << "MergeGeometry: vertex attrib array not merged. Some data may be lost." <<std::endl; OSG_DEBUG << "MergeGeometry: vertex attrib array not merged. Some data may be lost." <<std::endl;
} }
} }
// shift the indices of the incoming primitives to account for the pre existing geometry. // shift the indices of the incoming primitives to account for the pre existing geometry.
osg::ElementBufferObject* ebo = nullptr;
osg::Geometry::PrimitiveSetList::iterator primItr; osg::Geometry::PrimitiveSetList::iterator primItr;
for(primItr=rhs.getPrimitiveSetList().begin(); primItr!=rhs.getPrimitiveSetList().end(); ++primItr) for(primItr=rhs.getPrimitiveSetList().begin(); primItr!=rhs.getPrimitiveSetList().end(); ++primItr)
{ {
@ -1684,6 +1753,11 @@ bool Optimizer::MergeGeometryVisitor::mergeGeometry(osg::Geometry& lhs,osg::Geom
{ {
// must promote to a DrawElementsUInt // must promote to a DrawElementsUInt
osg::DrawElementsUInt* new_primitive = new osg::DrawElementsUInt(primitive->getMode()); osg::DrawElementsUInt* new_primitive = new osg::DrawElementsUInt(primitive->getMode());
if (needvbo(&lhs))
{
if (!ebo) ebo = new osg::ElementBufferObject;
new_primitive->setElementBufferObject(ebo);
}
std::copy(primitiveUByte->begin(),primitiveUByte->end(),std::back_inserter(*new_primitive)); std::copy(primitiveUByte->begin(),primitiveUByte->end(),std::back_inserter(*new_primitive));
new_primitive->offsetIndices(base); new_primitive->offsetIndices(base);
(*primItr) = new_primitive; (*primItr) = new_primitive;
@ -1691,13 +1765,19 @@ bool Optimizer::MergeGeometryVisitor::mergeGeometry(osg::Geometry& lhs,osg::Geom
{ {
// must promote to a DrawElementsUShort // must promote to a DrawElementsUShort
osg::DrawElementsUShort* new_primitive = new osg::DrawElementsUShort(primitive->getMode()); osg::DrawElementsUShort* new_primitive = new osg::DrawElementsUShort(primitive->getMode());
if (needvbo(&lhs))
{
if (!ebo) ebo = new osg::ElementBufferObject;
new_primitive->setElementBufferObject(ebo);
}
std::copy(primitiveUByte->begin(),primitiveUByte->end(),std::back_inserter(*new_primitive)); std::copy(primitiveUByte->begin(),primitiveUByte->end(),std::back_inserter(*new_primitive));
new_primitive->offsetIndices(base); new_primitive->offsetIndices(base);
(*primItr) = new_primitive; (*primItr) = new_primitive;
} }
else else
{ {
primitive->offsetIndices(base); (*primItr) = clonePrimitive(primitive, ebo, &lhs);
(*primItr)->offsetIndices(base);
} }
} }
break; break;
@ -1716,13 +1796,19 @@ bool Optimizer::MergeGeometryVisitor::mergeGeometry(osg::Geometry& lhs,osg::Geom
{ {
// must promote to a DrawElementsUInt // must promote to a DrawElementsUInt
osg::DrawElementsUInt* new_primitive = new osg::DrawElementsUInt(primitive->getMode()); osg::DrawElementsUInt* new_primitive = new osg::DrawElementsUInt(primitive->getMode());
if (needvbo(&lhs))
{
if (!ebo) ebo = new osg::ElementBufferObject;
new_primitive->setElementBufferObject(ebo);
}
std::copy(primitiveUShort->begin(),primitiveUShort->end(),std::back_inserter(*new_primitive)); std::copy(primitiveUShort->begin(),primitiveUShort->end(),std::back_inserter(*new_primitive));
new_primitive->offsetIndices(base); new_primitive->offsetIndices(base);
(*primItr) = new_primitive; (*primItr) = new_primitive;
} }
else else
{ {
primitive->offsetIndices(base); (*primItr) = clonePrimitive(primitive, ebo, &lhs);
(*primItr)->offsetIndices(base);
} }
} }
break; break;
@ -1731,7 +1817,8 @@ bool Optimizer::MergeGeometryVisitor::mergeGeometry(osg::Geometry& lhs,osg::Geom
case(osg::PrimitiveSet::DrawArrayLengthsPrimitiveType): case(osg::PrimitiveSet::DrawArrayLengthsPrimitiveType):
case(osg::PrimitiveSet::DrawElementsUIntPrimitiveType): case(osg::PrimitiveSet::DrawElementsUIntPrimitiveType):
default: default:
primitive->offsetIndices(base); (*primItr) = clonePrimitive(primitive, ebo, &lhs);
(*primItr)->offsetIndices(base);
break; break;
} }
} }
@ -1744,6 +1831,10 @@ bool Optimizer::MergeGeometryVisitor::mergeGeometry(osg::Geometry& lhs,osg::Geom
lhs.dirtyBound(); lhs.dirtyBound();
lhs.dirtyDisplayList(); lhs.dirtyDisplayList();
if (osg::UserDataContainer* rhsUserData = rhs.getUserDataContainer())
for (unsigned int i=0; i<rhsUserData->getNumUserObjects(); ++i)
lhs.getOrCreateUserDataContainer()->addUserObject(rhsUserData->getUserObject(i));
return true; return true;
} }

@ -65,7 +65,7 @@ class Optimizer
public: public:
Optimizer() {} Optimizer() : _mergeAlphaBlending(false) {}
virtual ~Optimizer() {} virtual ~Optimizer() {}
enum OptimizationOptions enum OptimizationOptions
@ -118,6 +118,9 @@ class Optimizer
STATIC_OBJECT_DETECTION STATIC_OBJECT_DETECTION
}; };
void setMergeAlphaBlending(bool merge) { _mergeAlphaBlending = merge; }
void setViewPoint(const osg::Vec3f& viewPoint) { _viewPoint = viewPoint; }
/** Reset internal data to initial state - the getPermissibleOptionsMap is cleared.*/ /** Reset internal data to initial state - the getPermissibleOptionsMap is cleared.*/
void reset(); void reset();
@ -252,6 +255,9 @@ class Optimizer
typedef std::map<const osg::Object*,unsigned int> PermissibleOptimizationsMap; typedef std::map<const osg::Object*,unsigned int> PermissibleOptimizationsMap;
PermissibleOptimizationsMap _permissibleOptimizationsMap; PermissibleOptimizationsMap _permissibleOptimizationsMap;
osg::Vec3f _viewPoint;
bool _mergeAlphaBlending;
public: public:
/** Flatten Static Transform nodes by applying their transform to the /** Flatten Static Transform nodes by applying their transform to the
@ -371,7 +377,16 @@ class Optimizer
/// default to traversing all children. /// default to traversing all children.
MergeGeometryVisitor(Optimizer* optimizer=0) : MergeGeometryVisitor(Optimizer* optimizer=0) :
BaseOptimizerVisitor(optimizer, MERGE_GEOMETRY), BaseOptimizerVisitor(optimizer, MERGE_GEOMETRY),
_targetMaximumNumberOfVertices(10000), _allowedToMerge(true) {} _targetMaximumNumberOfVertices(10000), _alphaBlendingActive(false), _mergeAlphaBlending(false) {}
void setMergeAlphaBlending(bool merge)
{
_mergeAlphaBlending = merge;
}
void setViewPoint(const osg::Vec3f& viewPoint)
{
_viewPoint = viewPoint;
}
void setTargetMaximumNumberOfVertices(unsigned int num) void setTargetMaximumNumberOfVertices(unsigned int num)
{ {
@ -385,15 +400,13 @@ class Optimizer
void pushStateSet(osg::StateSet* stateSet); void pushStateSet(osg::StateSet* stateSet);
void popStateSet(); void popStateSet();
void checkAllowedToMerge(); void checkAlphaBlendingActive();
virtual void apply(osg::Group& group); virtual void apply(osg::Group& group);
virtual void apply(osg::Billboard&) { /* don't do anything*/ } virtual void apply(osg::Billboard&) { /* don't do anything*/ }
bool mergeGroup(osg::Group& group); bool mergeGroup(osg::Group& group);
static bool geometryContainsSharedArrays(osg::Geometry& geom);
static bool mergeGeometry(osg::Geometry& lhs,osg::Geometry& rhs); static bool mergeGeometry(osg::Geometry& lhs,osg::Geometry& rhs);
static bool mergePrimitive(osg::DrawArrays& lhs,osg::DrawArrays& rhs); static bool mergePrimitive(osg::DrawArrays& lhs,osg::DrawArrays& rhs);
@ -406,7 +419,9 @@ class Optimizer
unsigned int _targetMaximumNumberOfVertices; unsigned int _targetMaximumNumberOfVertices;
std::vector<osg::StateSet*> _stateSetStack; std::vector<osg::StateSet*> _stateSetStack;
bool _allowedToMerge; bool _alphaBlendingActive;
bool _mergeAlphaBlending;
osg::Vec3f _viewPoint;
}; };
}; };

@ -77,7 +77,7 @@ void RigGeometry::setSourceGeometry(osg::ref_ptr<osg::Geometry> sourceGeometry)
osg::ref_ptr<osg::VertexBufferObject> vbo (new osg::VertexBufferObject); osg::ref_ptr<osg::VertexBufferObject> vbo (new osg::VertexBufferObject);
vbo->setUsage(GL_DYNAMIC_DRAW_ARB); vbo->setUsage(GL_DYNAMIC_DRAW_ARB);
osg::ref_ptr<osg::Array> vertexArray = osg::clone(from.getVertexArray(), osg::CopyOp::DEEP_COPY_ALL); osg::ref_ptr<osg::Array> vertexArray = static_cast<osg::Array*>(from.getVertexArray()->clone(osg::CopyOp::DEEP_COPY_ALL));
if (vertexArray) if (vertexArray)
{ {
vertexArray->setVertexBufferObject(vbo); vertexArray->setVertexBufferObject(vbo);
@ -86,7 +86,7 @@ void RigGeometry::setSourceGeometry(osg::ref_ptr<osg::Geometry> sourceGeometry)
if (const osg::Array* normals = from.getNormalArray()) if (const osg::Array* normals = from.getNormalArray())
{ {
osg::ref_ptr<osg::Array> normalArray = osg::clone(normals, osg::CopyOp::DEEP_COPY_ALL); osg::ref_ptr<osg::Array> normalArray = static_cast<osg::Array*>(normals->clone(osg::CopyOp::DEEP_COPY_ALL));
if (normalArray) if (normalArray)
{ {
normalArray->setVertexBufferObject(vbo); normalArray->setVertexBufferObject(vbo);
@ -97,7 +97,7 @@ void RigGeometry::setSourceGeometry(osg::ref_ptr<osg::Geometry> sourceGeometry)
if (const osg::Vec4Array* tangents = dynamic_cast<const osg::Vec4Array*>(from.getTexCoordArray(7))) if (const osg::Vec4Array* tangents = dynamic_cast<const osg::Vec4Array*>(from.getTexCoordArray(7)))
{ {
mSourceTangents = tangents; mSourceTangents = tangents;
osg::ref_ptr<osg::Array> tangentArray = osg::clone(tangents, osg::CopyOp::DEEP_COPY_ALL); osg::ref_ptr<osg::Array> tangentArray = static_cast<osg::Array*>(tangents->clone(osg::CopyOp::DEEP_COPY_ALL));
tangentArray->setVertexBufferObject(vbo); tangentArray->setVertexBufferObject(vbo);
to.setTexCoordArray(7, tangentArray, osg::Array::BIND_PER_VERTEX); to.setTexCoordArray(7, tangentArray, osg::Array::BIND_PER_VERTEX);
} }
@ -106,7 +106,7 @@ void RigGeometry::setSourceGeometry(osg::ref_ptr<osg::Geometry> sourceGeometry)
} }
} }
osg::ref_ptr<osg::Geometry> RigGeometry::getSourceGeometry() osg::ref_ptr<osg::Geometry> RigGeometry::getSourceGeometry() const
{ {
return mSourceGeometry; return mSourceGeometry;
} }

@ -44,7 +44,7 @@ namespace SceneUtil
/// @note The source geometry will not be modified. /// @note The source geometry will not be modified.
void setSourceGeometry(osg::ref_ptr<osg::Geometry> sourceGeom); void setSourceGeometry(osg::ref_ptr<osg::Geometry> sourceGeom);
osg::ref_ptr<osg::Geometry> getSourceGeometry(); osg::ref_ptr<osg::Geometry> getSourceGeometry() const;
virtual void accept(osg::NodeVisitor &nv); virtual void accept(osg::NodeVisitor &nv);
virtual bool supports(const osg::PrimitiveFunctor&) const { return true; } virtual bool supports(const osg::PrimitiveFunctor&) const { return true; }

@ -2,30 +2,38 @@
#include <osg/Node> #include <osg/Node>
#include <osg/NodeVisitor> #include <osg/NodeVisitor>
#include <osgUtil/CullVisitor>
namespace SceneUtil namespace SceneUtil
{ {
void StateSetUpdater::operator()(osg::Node* node, osg::NodeVisitor* nv) void StateSetUpdater::operator()(osg::Node* node, osg::NodeVisitor* nv)
{ {
bool isCullVisitor = nv->getVisitorType() == osg::NodeVisitor::CULL_VISITOR;
if (!mStateSets[0]) if (!mStateSets[0])
{ {
// first time setup for (int i=0; i<2; ++i)
osg::StateSet* src = node->getOrCreateStateSet();
for (int i=0; i<2; ++i) // Using SHALLOW_COPY for StateAttributes, if users want to modify it is their responsibility to set a non-shared one first
// This can be done conveniently in user implementations of the setDefaults() method
{ {
mStateSets[i] = new osg::StateSet(*src, osg::CopyOp::SHALLOW_COPY); if (!isCullVisitor)
mStateSets[i] = new osg::StateSet(*node->getOrCreateStateSet(), osg::CopyOp::SHALLOW_COPY); // Using SHALLOW_COPY for StateAttributes, if users want to modify it is their responsibility to set a non-shared one first in setDefaults
else
mStateSets[i] = new osg::StateSet;
setDefaults(mStateSets[i]); setDefaults(mStateSets[i]);
} }
} }
osg::StateSet* stateset = mStateSets[nv->getTraversalNumber()%2]; osg::StateSet* stateset = mStateSets[nv->getTraversalNumber()%2];
node->setStateSet(stateset);
apply(stateset, nv); apply(stateset, nv);
if (!isCullVisitor)
node->setStateSet(stateset);
else
static_cast<osgUtil::CullVisitor*>(nv)->pushStateSet(stateset);
traverse(node, nv); traverse(node, nv);
if (isCullVisitor)
static_cast<osgUtil::CullVisitor*>(nv)->popStateSet();
} }
void StateSetUpdater::reset() void StateSetUpdater::reset()

@ -13,7 +13,7 @@ namespace SceneUtil
/// traversals run in parallel can yield up to 200% framerates. /// traversals run in parallel can yield up to 200% framerates.
/// @par Race conditions are prevented using a "double buffering" scheme - we have two StateSets that take turns, /// @par Race conditions are prevented using a "double buffering" scheme - we have two StateSets that take turns,
/// one StateSet we can write to, the second one is currently in use by the draw traversal of the last frame. /// one StateSet we can write to, the second one is currently in use by the draw traversal of the last frame.
/// @par Must be set as UpdateCallback on a Node. /// @par Must be set as UpdateCallback or CullCallback on a Node. If set as a CullCallback, the StateSetUpdater operates on an empty StateSet, otherwise it operates on a clone of the node's existing StateSet.
/// @note Do not add the same StateSetUpdater to multiple nodes. /// @note Do not add the same StateSetUpdater to multiple nodes.
/// @note Do not add multiple StateSetControllers on the same Node as they will conflict - instead use the CompositeStateSetUpdater. /// @note Do not add multiple StateSetControllers on the same Node as they will conflict - instead use the CompositeStateSetUpdater.
class StateSetUpdater : public osg::NodeCallback class StateSetUpdater : public osg::NodeCallback

@ -4,13 +4,13 @@
#include <osg/Texture2D> #include <osg/Texture2D>
#include <osg/ClusterCullingCallback> #include <osg/ClusterCullingCallback>
#include <osg/Material>
#include <osgUtil/IncrementalCompileOperation> #include <osgUtil/IncrementalCompileOperation>
#include <components/resource/objectcache.hpp> #include <components/resource/objectcache.hpp>
#include <components/resource/scenemanager.hpp> #include <components/resource/scenemanager.hpp>
#include <components/sceneutil/positionattitudetransform.hpp>
#include <components/sceneutil/lightmanager.hpp> #include <components/sceneutil/lightmanager.hpp>
#include "terraindrawable.hpp" #include "terraindrawable.hpp"
@ -32,10 +32,14 @@ ChunkManager::ChunkManager(Storage *storage, Resource::SceneManager *sceneMgr, T
, mCompositeMapLevel(1.f) , mCompositeMapLevel(1.f)
, mMaxCompGeometrySize(1.f) , mMaxCompGeometrySize(1.f)
{ {
mMultiPassRoot = new osg::StateSet;
mMultiPassRoot->setRenderingHint(osg::StateSet::OPAQUE_BIN);
osg::ref_ptr<osg::Material> material (new osg::Material);
material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE);
mMultiPassRoot->setAttributeAndModes(material, osg::StateAttribute::ON);
} }
osg::ref_ptr<osg::Node> ChunkManager::getChunk(float size, const osg::Vec2f &center, unsigned char lod, unsigned int lodFlags) osg::ref_ptr<osg::Node> ChunkManager::getChunk(float size, const osg::Vec2f &center, unsigned char lod, unsigned int lodFlags, bool far, const osg::Vec3f& viewPoint, bool compile)
{ {
ChunkId id = std::make_tuple(center, lod, lodFlags); ChunkId id = std::make_tuple(center, lod, lodFlags);
osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(id); osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(id);
@ -43,7 +47,7 @@ osg::ref_ptr<osg::Node> ChunkManager::getChunk(float size, const osg::Vec2f &cen
return obj->asNode(); return obj->asNode();
else else
{ {
osg::ref_ptr<osg::Node> node = createChunk(size, center, lod, lodFlags); osg::ref_ptr<osg::Node> node = createChunk(size, center, lod, lodFlags, compile);
mCache->addEntryToObjectCache(id, node.get()); mCache->addEntryToObjectCache(id, node.get());
return node; return node;
} }
@ -161,12 +165,8 @@ std::vector<osg::ref_ptr<osg::StateSet> > ChunkManager::createPasses(float chunk
return ::Terrain::createPasses(useShaders, &mSceneManager->getShaderManager(), layers, blendmapTextures, blendmapScale, blendmapScale); return ::Terrain::createPasses(useShaders, &mSceneManager->getShaderManager(), layers, blendmapTextures, blendmapScale, blendmapScale);
} }
osg::ref_ptr<osg::Node> ChunkManager::createChunk(float chunkSize, const osg::Vec2f &chunkCenter, unsigned char lod, unsigned int lodFlags) osg::ref_ptr<osg::Node> ChunkManager::createChunk(float chunkSize, const osg::Vec2f &chunkCenter, unsigned char lod, unsigned int lodFlags, bool compile)
{ {
osg::Vec2f worldCenter = chunkCenter*mStorage->getCellWorldSize();
osg::ref_ptr<SceneUtil::PositionAttitudeTransform> transform (new SceneUtil::PositionAttitudeTransform);
transform->setPosition(osg::Vec3f(worldCenter.x(), worldCenter.y(), 0.f));
osg::ref_ptr<osg::Vec3Array> positions (new osg::Vec3Array); osg::ref_ptr<osg::Vec3Array> positions (new osg::Vec3Array);
osg::ref_ptr<osg::Vec3Array> normals (new osg::Vec3Array); osg::ref_ptr<osg::Vec3Array> normals (new osg::Vec3Array);
osg::ref_ptr<osg::Vec4ubArray> colors (new osg::Vec4ubArray); osg::ref_ptr<osg::Vec4ubArray> colors (new osg::Vec4ubArray);
@ -201,6 +201,8 @@ osg::ref_ptr<osg::Node> ChunkManager::createChunk(float chunkSize, const osg::Ve
geometry->createClusterCullingCallback(); geometry->createClusterCullingCallback();
geometry->setStateSet(mMultiPassRoot);
if (useCompositeMap) if (useCompositeMap)
{ {
osg::ref_ptr<CompositeMap> compositeMap = new CompositeMap; osg::ref_ptr<CompositeMap> compositeMap = new CompositeMap;
@ -224,16 +226,15 @@ osg::ref_ptr<osg::Node> ChunkManager::createChunk(float chunkSize, const osg::Ve
geometry->setPasses(createPasses(chunkSize, chunkCenter, false)); geometry->setPasses(createPasses(chunkSize, chunkCenter, false));
} }
transform->addChild(geometry);
transform->getBound();
geometry->setupWaterBoundingBox(-1, chunkSize * mStorage->getCellWorldSize() / numVerts); geometry->setupWaterBoundingBox(-1, chunkSize * mStorage->getCellWorldSize() / numVerts);
if (mSceneManager->getIncrementalCompileOperation()) if (compile && mSceneManager->getIncrementalCompileOperation())
{ {
mSceneManager->getIncrementalCompileOperation()->add(geometry); mSceneManager->getIncrementalCompileOperation()->add(geometry);
} }
return transform; geometry->setNodeMask(mNodeMask);
return geometry;
} }
} }

@ -6,6 +6,7 @@
#include <components/resource/resourcemanager.hpp> #include <components/resource/resourcemanager.hpp>
#include "buffercache.hpp" #include "buffercache.hpp"
#include "quadtreeworld.hpp"
namespace osg namespace osg
{ {
@ -29,17 +30,20 @@ namespace Terrain
typedef std::tuple<osg::Vec2f, unsigned char, unsigned int> ChunkId; // Center, Lod, Lod Flags typedef std::tuple<osg::Vec2f, unsigned char, unsigned int> ChunkId; // Center, Lod, Lod Flags
/// @brief Handles loading and caching of terrain chunks /// @brief Handles loading and caching of terrain chunks
class ChunkManager : public Resource::GenericResourceManager<ChunkId> class ChunkManager : public Resource::GenericResourceManager<ChunkId>, public QuadTreeWorld::ChunkManager
{ {
public: public:
ChunkManager(Storage* storage, Resource::SceneManager* sceneMgr, TextureManager* textureManager, CompositeMapRenderer* renderer); ChunkManager(Storage* storage, Resource::SceneManager* sceneMgr, TextureManager* textureManager, CompositeMapRenderer* renderer);
osg::ref_ptr<osg::Node> getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags); osg::ref_ptr<osg::Node> getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool far, const osg::Vec3f& viewPoint, bool compile);
void setCompositeMapSize(unsigned int size) { mCompositeMapSize = size; } void setCompositeMapSize(unsigned int size) { mCompositeMapSize = size; }
void setCompositeMapLevel(float level) { mCompositeMapLevel = level; } void setCompositeMapLevel(float level) { mCompositeMapLevel = level; }
void setMaxCompositeGeometrySize(float maxCompGeometrySize) { mMaxCompGeometrySize = maxCompGeometrySize; } void setMaxCompositeGeometrySize(float maxCompGeometrySize) { mMaxCompGeometrySize = maxCompGeometrySize; }
void setNodeMask(unsigned int mask) { mNodeMask = mask; }
virtual unsigned int getNodeMask() override { return mNodeMask; }
void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; void reportStats(unsigned int frameNumber, osg::Stats* stats) const override;
void clearCache() override; void clearCache() override;
@ -47,7 +51,7 @@ namespace Terrain
void releaseGLObjects(osg::State* state) override; void releaseGLObjects(osg::State* state) override;
private: private:
osg::ref_ptr<osg::Node> createChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags); osg::ref_ptr<osg::Node> createChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool compile);
osg::ref_ptr<osg::Texture2D> createCompositeMapRTT(); osg::ref_ptr<osg::Texture2D> createCompositeMapRTT();
@ -61,6 +65,10 @@ namespace Terrain
CompositeMapRenderer* mCompositeMapRenderer; CompositeMapRenderer* mCompositeMapRenderer;
BufferCache mBufferCache; BufferCache mBufferCache;
osg::ref_ptr<osg::StateSet> mMultiPassRoot;
unsigned int mNodeMask;
unsigned int mCompositeMapSize; unsigned int mCompositeMapSize;
float mCompositeMapLevel; float mCompositeMapLevel;
float mMaxCompGeometrySize; float mMaxCompGeometrySize;

@ -183,17 +183,20 @@ namespace Terrain
osg::ref_ptr<osg::StateSet> stateset (new osg::StateSet); osg::ref_ptr<osg::StateSet> stateset (new osg::StateSet);
stateset->setMode(GL_BLEND, osg::StateAttribute::ON); if (!blendmaps.empty())
if (!firstLayer)
{ {
stateset->setAttributeAndModes(BlendFunc::value(), osg::StateAttribute::ON); stateset->setMode(GL_BLEND, osg::StateAttribute::ON);
stateset->setAttributeAndModes(EqualDepth::value(), osg::StateAttribute::ON); stateset->setRenderBinDetails(passIndex++, "RenderBin");
} if (!firstLayer)
else {
{ stateset->setAttributeAndModes(BlendFunc::value(), osg::StateAttribute::ON);
stateset->setAttributeAndModes(BlendFuncFirst::value(), osg::StateAttribute::ON); stateset->setAttributeAndModes(EqualDepth::value(), osg::StateAttribute::ON);
stateset->setAttributeAndModes(LequalDepth::value(), osg::StateAttribute::ON); }
else
{
stateset->setAttributeAndModes(BlendFuncFirst::value(), osg::StateAttribute::ON);
stateset->setAttributeAndModes(LequalDepth::value(), osg::StateAttribute::ON);
}
} }
int texunit = 0; int texunit = 0;
@ -268,8 +271,6 @@ namespace Terrain
} }
stateset->setRenderBinDetails(passIndex++, "RenderBin");
passes.push_back(stateset); passes.push_back(stateset);
} }
return passes; return passes;

@ -108,71 +108,26 @@ void QuadTreeNode::initNeighbours()
getChild(i)->initNeighbours(); getChild(i)->initNeighbours();
} }
void QuadTreeNode::traverseNodes(ViewData* vd, const osg::Vec3f& viewPoint, LodCallback* lodCallback, float maxDist) void QuadTreeNode::traverseNodes(ViewData* vd, const osg::Vec3f& viewPoint, LodCallback* lodCallback)
{ {
if (!hasValidBounds()) if (!hasValidBounds())
return; return;
LodCallback::ReturnValue lodResult = lodCallback->isSufficientDetail(this, distance(viewPoint));
float dist = distance(viewPoint); if (lodResult == LodCallback::StopTraversal)
if (dist > maxDist)
return; return;
else if (lodResult == LodCallback::Deeper && getNumChildren())
bool stopTraversal = (lodCallback->isSufficientDetail(this, dist)) || !getNumChildren();
if (stopTraversal)
vd->add(this);
else
{ {
for (unsigned int i=0; i<getNumChildren(); ++i) for (unsigned int i=0; i<getNumChildren(); ++i)
getChild(i)->traverseNodes(vd, viewPoint, lodCallback, maxDist); getChild(i)->traverseNodes(vd, viewPoint, lodCallback);
} }
}
void QuadTreeNode::traverseTo(ViewData* vd, float size, const osg::Vec2f& center)
{
if (!hasValidBounds())
return;
if (getCenter().x() + getSize()/2.f <= center.x() - size/2.f
|| getCenter().x() - getSize()/2.f >= center.x() + size/2.f
|| getCenter().y() + getSize()/2.f <= center.y() - size/2.f
|| getCenter().y() - getSize()/2.f >= center.y() + size/2.f)
return;
bool stopTraversal = (getSize() == size);
if (stopTraversal)
vd->add(this);
else else
{
for (unsigned int i=0; i<getNumChildren(); ++i)
getChild(i)->traverseTo(vd, size, center);
}
}
void QuadTreeNode::intersect(ViewData* vd, TerrainLineIntersector& intersector)
{
if (!hasValidBounds())
return;
if (!intersector.intersectAndClip(getBoundingBox()))
return;
if (getNumChildren() == 0)
vd->add(this); vd->add(this);
else
{
for (unsigned int i=0; i<getNumChildren(); ++i)
getChild(i)->intersect(vd, intersector);
}
} }
void QuadTreeNode::setBoundingBox(const osg::BoundingBox &boundingBox) void QuadTreeNode::setBoundingBox(const osg::BoundingBox &boundingBox)
{ {
mBoundingBox = boundingBox; mBoundingBox = boundingBox;
mValidBounds = boundingBox.valid(); mValidBounds = boundingBox.valid();
dirtyBound();
getBound();
} }
const osg::BoundingBox &QuadTreeNode::getBoundingBox() const const osg::BoundingBox &QuadTreeNode::getBoundingBox() const
@ -180,11 +135,6 @@ const osg::BoundingBox &QuadTreeNode::getBoundingBox() const
return mBoundingBox; return mBoundingBox;
} }
osg::BoundingSphere QuadTreeNode::computeBound() const
{
return osg::BoundingSphere(mBoundingBox);
}
float QuadTreeNode::getSize() const float QuadTreeNode::getSize() const
{ {
return mSize; return mSize;

@ -2,39 +2,12 @@
#define OPENMW_COMPONENTS_TERRAIN_QUADTREENODE_H #define OPENMW_COMPONENTS_TERRAIN_QUADTREENODE_H
#include <osg/Group> #include <osg/Group>
#include <osgUtil/LineSegmentIntersector>
#include "defs.hpp" #include "defs.hpp"
namespace Terrain namespace Terrain
{ {
class TerrainLineIntersector : public osgUtil::LineSegmentIntersector
{
public:
TerrainLineIntersector(osgUtil::LineSegmentIntersector* intersector, osg::Matrix& matrix) :
osgUtil::LineSegmentIntersector(intersector->getStart() * matrix, intersector->getEnd() * matrix)
{
setPrecisionHint(intersector->getPrecisionHint());
_intersectionLimit = intersector->getIntersectionLimit();
_parent = intersector;
}
TerrainLineIntersector(osgUtil::LineSegmentIntersector* intersector) :
osgUtil::LineSegmentIntersector(intersector->getStart(), intersector->getEnd())
{
setPrecisionHint(intersector->getPrecisionHint());
_intersectionLimit = intersector->getIntersectionLimit();
_parent = intersector;
}
bool intersectAndClip(const osg::BoundingBox& bbInput)
{
osg::Vec3d s(_start), e(_end);
return osgUtil::LineSegmentIntersector::intersectAndClip(s, e, bbInput);
}
};
enum ChildDirection enum ChildDirection
{ {
NW = 0, NW = 0,
@ -50,10 +23,15 @@ namespace Terrain
public: public:
virtual ~LodCallback() {} virtual ~LodCallback() {}
virtual bool isSufficientDetail(QuadTreeNode *node, float dist) = 0; enum ReturnValue
{
Deeper,
StopTraversal,
StopTraversalAndUse
};
virtual ReturnValue isSufficientDetail(QuadTreeNode *node, float dist) = 0;
}; };
class ViewDataMap;
class ViewData; class ViewData;
class QuadTreeNode : public osg::Group class QuadTreeNode : public osg::Group
@ -91,8 +69,6 @@ namespace Terrain
const osg::BoundingBox& getBoundingBox() const; const osg::BoundingBox& getBoundingBox() const;
bool hasValidBounds() const { return mValidBounds; } bool hasValidBounds() const { return mValidBounds; }
virtual osg::BoundingSphere computeBound() const;
/// size in cell coordinates /// size in cell coordinates
float getSize() const; float getSize() const;
@ -100,13 +76,7 @@ namespace Terrain
const osg::Vec2f& getCenter() const; const osg::Vec2f& getCenter() const;
/// Traverse nodes according to LOD selection. /// Traverse nodes according to LOD selection.
void traverseNodes(ViewData* vd, const osg::Vec3f& viewPoint, LodCallback* lodCallback, float maxDist); void traverseNodes(ViewData* vd, const osg::Vec3f& viewPoint, LodCallback* lodCallback);
/// Traverse to a specific node and add only that node.
void traverseTo(ViewData* vd, float size, const osg::Vec2f& center);
/// Adds all leaf nodes which intersect the line from start to end
void intersect(ViewData* vd, TerrainLineIntersector& intersector);
private: private:
QuadTreeNode* mParent; QuadTreeNode* mParent;

@ -9,6 +9,7 @@
#include <components/misc/constants.hpp> #include <components/misc/constants.hpp>
#include <components/sceneutil/mwshadowtechnique.hpp> #include <components/sceneutil/mwshadowtechnique.hpp>
#include <components/sceneutil/positionattitudetransform.hpp>
#include "quadtreenode.hpp" #include "quadtreenode.hpp"
#include "storage.hpp" #include "storage.hpp"
@ -52,25 +53,45 @@ namespace Terrain
class DefaultLodCallback : public LodCallback class DefaultLodCallback : public LodCallback
{ {
public: public:
DefaultLodCallback(float factor, float minSize) DefaultLodCallback(float factor, float minSize, float viewDistance, const osg::Vec4i& grid)
: mFactor(factor) : mFactor(factor)
, mMinSize(minSize) , mMinSize(minSize)
, mViewDistance(viewDistance)
, mActiveGrid(grid)
{ {
} }
virtual bool isSufficientDetail(QuadTreeNode* node, float dist) virtual ReturnValue isSufficientDetail(QuadTreeNode* node, float dist)
{ {
const osg::Vec2f& center = node->getCenter();
bool activeGrid = (center.x() > mActiveGrid.x() && center.y() > mActiveGrid.y() && center.x() < mActiveGrid.z() && center.y() < mActiveGrid.w());
if (dist > mViewDistance && !activeGrid) // for Scene<->ObjectPaging sync the activegrid must remain loaded
return StopTraversal;
if (node->getSize()>1)
{
float halfSize = node->getSize()/2;
osg::Vec4i nodeBounds (static_cast<int>(center.x() - halfSize), static_cast<int>(center.y() - halfSize), static_cast<int>(center.x() + halfSize), static_cast<int>(center.y() + halfSize));
bool intersects = (std::max(nodeBounds.x(), mActiveGrid.x()) < std::min(nodeBounds.z(), mActiveGrid.z()) && std::max(nodeBounds.y(), mActiveGrid.y()) < std::min(nodeBounds.w(), mActiveGrid.w()));
// to prevent making chunks who will cross the activegrid border
if (intersects)
return Deeper;
}
int nativeLodLevel = Log2(static_cast<unsigned int>(node->getSize()/mMinSize)); int nativeLodLevel = Log2(static_cast<unsigned int>(node->getSize()/mMinSize));
int lodLevel = Log2(static_cast<unsigned int>(dist/(Constants::CellSizeInUnits*mMinSize*mFactor))); int lodLevel = Log2(static_cast<unsigned int>(dist/(Constants::CellSizeInUnits*mMinSize*mFactor)));
return nativeLodLevel <= lodLevel; return nativeLodLevel <= lodLevel ? StopTraversalAndUse : Deeper;
} }
private: private:
float mFactor; float mFactor;
float mMinSize; float mMinSize;
float mViewDistance;
osg::Vec4i mActiveGrid;
}; };
const float MIN_SIZE = 1/8.f;
class RootNode : public QuadTreeNode class RootNode : public QuadTreeNode
{ {
public: public:
@ -125,6 +146,8 @@ public:
addChildren(mRootNode); addChildren(mRootNode);
mRootNode->initNeighbours(); mRootNode->initNeighbours();
float cellWorldSize = mStorage->getCellWorldSize();
mRootNode->setInitialBound(osg::BoundingSphere(osg::BoundingBox(osg::Vec3(mMinX*cellWorldSize, mMinY*cellWorldSize, 0), osg::Vec3(mMaxX*cellWorldSize, mMaxY*cellWorldSize, 0))));
} }
void addChildren(QuadTreeNode* parent) void addChildren(QuadTreeNode* parent)
@ -231,11 +254,11 @@ QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resour
mChunkManager->setCompositeMapSize(compMapResolution); mChunkManager->setCompositeMapSize(compMapResolution);
mChunkManager->setCompositeMapLevel(compMapLevel); mChunkManager->setCompositeMapLevel(compMapLevel);
mChunkManager->setMaxCompositeGeometrySize(maxCompGeometrySize); mChunkManager->setMaxCompositeGeometrySize(maxCompGeometrySize);
mChunkManagers.push_back(mChunkManager.get());
} }
QuadTreeWorld::~QuadTreeWorld() QuadTreeWorld::~QuadTreeWorld()
{ {
mViewDataMap->clear();
} }
/// get the level of vertex detail to render this node at, expressed relative to the native resolution of the data set. /// get the level of vertex detail to render this node at, expressed relative to the native resolution of the data set.
@ -261,7 +284,7 @@ unsigned int getVertexLod(QuadTreeNode* node, int vertexLodMod)
} }
/// get the flags to use for stitching in the index buffer so that chunks of different LOD connect seamlessly /// get the flags to use for stitching in the index buffer so that chunks of different LOD connect seamlessly
unsigned int getLodFlags(QuadTreeNode* node, int ourLod, int vertexLodMod, ViewData* vd) unsigned int getLodFlags(QuadTreeNode* node, int ourLod, int vertexLodMod, const ViewData* vd)
{ {
unsigned int lodFlags = 0; unsigned int lodFlags = 0;
for (unsigned int i=0; i<4; ++i) for (unsigned int i=0; i<4; ++i)
@ -289,7 +312,7 @@ unsigned int getLodFlags(QuadTreeNode* node, int ourLod, int vertexLodMod, ViewD
return lodFlags; return lodFlags;
} }
void loadRenderingNode(ViewData::Entry& entry, ViewData* vd, int vertexLodMod, ChunkManager* chunkManager) void loadRenderingNode(ViewData::Entry& entry, ViewData* vd, int vertexLodMod, float cellWorldSize, const osg::Vec4i &gridbounds, const std::vector<QuadTreeWorld::ChunkManager*>& chunkManagers, bool compile)
{ {
if (!vd->hasChanged() && entry.mRenderingNode) if (!vd->hasChanged() && entry.mRenderingNode)
return; return;
@ -308,7 +331,20 @@ void loadRenderingNode(ViewData::Entry& entry, ViewData* vd, int vertexLodMod, C
} }
if (!entry.mRenderingNode) if (!entry.mRenderingNode)
entry.mRenderingNode = chunkManager->getChunk(entry.mNode->getSize(), entry.mNode->getCenter(), ourLod, entry.mLodFlags); {
osg::ref_ptr<SceneUtil::PositionAttitudeTransform> pat = new SceneUtil::PositionAttitudeTransform;
pat->setPosition(osg::Vec3f(entry.mNode->getCenter().x()*cellWorldSize, entry.mNode->getCenter().y()*cellWorldSize, 0.f));
const osg::Vec2f& center = entry.mNode->getCenter();
bool activeGrid = (center.x() > gridbounds.x() && center.y() > gridbounds.y() && center.x() < gridbounds.z() && center.y() < gridbounds.w());
for (QuadTreeWorld::ChunkManager* m : chunkManagers)
{
osg::ref_ptr<osg::Node> n = m->getChunk(entry.mNode->getSize(), entry.mNode->getCenter(), ourLod, entry.mLodFlags, activeGrid, vd->getViewPoint(), compile);
if (n) pat->addChild(n);
}
entry.mRenderingNode = pat;
}
} }
void updateWaterCullingView(HeightCullCallback* callback, ViewData* vd, osgUtil::CullVisitor* cv, float cellworldsize, bool outofworld) void updateWaterCullingView(HeightCullCallback* callback, ViewData* vd, osgUtil::CullVisitor* cv, float cellworldsize, bool outofworld)
@ -382,71 +418,29 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv)
return; return;
} }
osg::Object * viewer = isCullVisitor ? static_cast<osgUtil::CullVisitor*>(&nv)->getCurrentCamera() : nullptr;
bool needsUpdate = true; bool needsUpdate = true;
ViewData* vd = nullptr; ViewData *vd = mViewDataMap->getViewData(viewer, nv.getViewPoint(), mActiveGrid, needsUpdate);
if (isCullVisitor)
vd = mViewDataMap->getViewData(static_cast<osgUtil::CullVisitor*>(&nv)->getCurrentCamera(), nv.getViewPoint(), needsUpdate);
else
{
static ViewData sIntersectionViewData;
vd = &sIntersectionViewData;
}
if (needsUpdate) if (needsUpdate)
{ {
vd->reset(); vd->reset();
if (isCullVisitor) DefaultLodCallback lodCallback(mLodFactor, MIN_SIZE, mViewDistance, mActiveGrid);
{ mRootNode->traverseNodes(vd, nv.getViewPoint(), &lodCallback);
osgUtil::CullVisitor* cv = static_cast<osgUtil::CullVisitor*>(&nv);
osg::UserDataContainer* udc = cv->getCurrentCamera()->getUserDataContainer();
if (udc && udc->getNumDescriptions() >= 2 && udc->getDescriptions()[0] == "NoTerrainLod")
{
std::istringstream stream(udc->getDescriptions()[1]);
int x,y;
stream >> x;
stream >> y;
mRootNode->traverseTo(vd, 1, osg::Vec2f(x+0.5,y+0.5));
}
else
mRootNode->traverseNodes(vd, cv->getViewPoint(), mLodCallback, mViewDistance);
}
else
{
osgUtil::IntersectionVisitor* iv = static_cast<osgUtil::IntersectionVisitor*>(&nv);
osgUtil::LineSegmentIntersector* lineIntersector = dynamic_cast<osgUtil::LineSegmentIntersector*>(iv->getIntersector());
if (!lineIntersector)
throw std::runtime_error("Cannot update QuadTreeWorld: node visitor is not LineSegmentIntersector");
if (lineIntersector->getCoordinateFrame() == osgUtil::Intersector::CoordinateFrame::MODEL && iv->getModelMatrix() == 0)
{
TerrainLineIntersector terrainIntersector(lineIntersector);
mRootNode->intersect(vd, terrainIntersector);
}
else
{
osg::Matrix matrix(lineIntersector->getTransformation(*iv, lineIntersector->getCoordinateFrame()));
TerrainLineIntersector terrainIntersector(lineIntersector, matrix);
mRootNode->intersect(vd, terrainIntersector);
}
}
} }
const float cellWorldSize = mStorage->getCellWorldSize();
for (unsigned int i=0; i<vd->getNumEntries(); ++i) for (unsigned int i=0; i<vd->getNumEntries(); ++i)
{ {
ViewData::Entry& entry = vd->getEntry(i); ViewData::Entry& entry = vd->getEntry(i);
loadRenderingNode(entry, vd, mVertexLodMod, cellWorldSize, mActiveGrid, mChunkManagers, false);
loadRenderingNode(entry, vd, mVertexLodMod, mChunkManager.get());
entry.mRenderingNode->accept(nv); entry.mRenderingNode->accept(nv);
} }
if (isCullVisitor) if (isCullVisitor)
updateWaterCullingView(mHeightCullCallback, vd, static_cast<osgUtil::CullVisitor*>(&nv), mStorage->getCellWorldSize(), !isGridEmpty()); updateWaterCullingView(mHeightCullCallback, vd, static_cast<osgUtil::CullVisitor*>(&nv), mStorage->getCellWorldSize(), !isGridEmpty());
if (!isCullVisitor)
vd->clear(); // we can't reuse intersection views in the next frame because they only contain what is touched by the intersection ray.
vd->markUnchanged(); vd->markUnchanged();
double referenceTime = nv.getFrameStamp() ? nv.getFrameStamp()->getReferenceTime() : 0.0; double referenceTime = nv.getFrameStamp() ? nv.getFrameStamp()->getReferenceTime() : 0.0;
@ -463,9 +457,7 @@ void QuadTreeWorld::ensureQuadTreeBuilt()
if (mQuadTreeBuilt) if (mQuadTreeBuilt)
return; return;
const float minSize = 1/8.f; QuadTreeBuilder builder(mStorage, MIN_SIZE);
mLodCallback = new DefaultLodCallback(mLodFactor, minSize);
QuadTreeBuilder builder(mStorage, minSize);
builder.build(); builder.build();
mRootNode = builder.getRootNode(); mRootNode = builder.getRootNode();
@ -487,48 +479,38 @@ void QuadTreeWorld::enable(bool enabled)
mRootNode->setNodeMask(enabled ? ~0 : 0); mRootNode->setNodeMask(enabled ? ~0 : 0);
} }
void QuadTreeWorld::cacheCell(View *view, int x, int y)
{
ensureQuadTreeBuilt();
ViewData* vd = static_cast<ViewData*>(view);
mRootNode->traverseTo(vd, 1, osg::Vec2f(x+0.5f,y+0.5f));
for (unsigned int i=0; i<vd->getNumEntries(); ++i)
{
ViewData::Entry& entry = vd->getEntry(i);
loadRenderingNode(entry, vd, mVertexLodMod, mChunkManager.get());
}
}
View* QuadTreeWorld::createView() View* QuadTreeWorld::createView()
{ {
return new ViewData; return mViewDataMap->createIndependentView();
} }
void QuadTreeWorld::preload(View *view, const osg::Vec3f &viewPoint, std::atomic<bool> &abort) void QuadTreeWorld::preload(View *view, const osg::Vec3f &viewPoint, const osg::Vec4i &grid, std::atomic<bool> &abort, std::atomic<int> &progress, int& progressTotal)
{ {
ensureQuadTreeBuilt(); ensureQuadTreeBuilt();
ViewData* vd = static_cast<ViewData*>(view); ViewData* vd = static_cast<ViewData*>(view);
vd->setViewPoint(viewPoint); vd->setViewPoint(viewPoint);
mRootNode->traverseNodes(vd, viewPoint, mLodCallback, mViewDistance); vd->setActiveGrid(grid);
DefaultLodCallback lodCallback(mLodFactor, MIN_SIZE, mViewDistance, grid);
mRootNode->traverseNodes(vd, viewPoint, &lodCallback);
if (!progressTotal)
for (unsigned int i=0; i<vd->getNumEntries(); ++i)
progressTotal += vd->getEntry(i).mNode->getSize();
const float cellWorldSize = mStorage->getCellWorldSize();
for (unsigned int i=0; i<vd->getNumEntries() && !abort; ++i) for (unsigned int i=0; i<vd->getNumEntries() && !abort; ++i)
{ {
ViewData::Entry& entry = vd->getEntry(i); ViewData::Entry& entry = vd->getEntry(i);
loadRenderingNode(entry, vd, mVertexLodMod, mChunkManager.get()); loadRenderingNode(entry, vd, mVertexLodMod, cellWorldSize, grid, mChunkManagers, true);
progress += entry.mNode->getSize();
} }
vd->markUnchanged(); vd->markUnchanged();
} }
void QuadTreeWorld::storeView(const View* view, double referenceTime) bool QuadTreeWorld::storeView(const View* view, double referenceTime)
{ {
osg::ref_ptr<osg::Object> dummy = new osg::DummyObject; return mViewDataMap->storeView(static_cast<const ViewData*>(view), referenceTime);
const ViewData* vd = static_cast<const ViewData*>(view);
bool needsUpdate = false;
ViewData* stored = mViewDataMap->getViewData(dummy, vd->getViewPoint(), needsUpdate);
stored->copyFrom(*vd);
stored->setLastUsageTimeStamp(referenceTime);
} }
void QuadTreeWorld::reportStats(unsigned int frameNumber, osg::Stats *stats) void QuadTreeWorld::reportStats(unsigned int frameNumber, osg::Stats *stats)
@ -556,5 +538,15 @@ void QuadTreeWorld::unloadCell(int x, int y)
World::unloadCell(x,y); World::unloadCell(x,y);
} }
void QuadTreeWorld::addChunkManager(QuadTreeWorld::ChunkManager* m)
{
mChunkManagers.push_back(m);
mTerrainRoot->setNodeMask(mTerrainRoot->getNodeMask()|m->getNodeMask());
}
void QuadTreeWorld::rebuildViews()
{
mViewDataMap->rebuildViews();
}
} }

@ -15,7 +15,6 @@ namespace Terrain
{ {
class RootNode; class RootNode;
class ViewDataMap; class ViewDataMap;
class LodCallback;
/// @brief Terrain implementation that loads cells into a Quad Tree, with geometry LOD and texture LOD. /// @brief Terrain implementation that loads cells into a Quad Tree, with geometry LOD and texture LOD.
class QuadTreeWorld : public TerrainGrid // note: derived from TerrainGrid is only to render default cells (see loadCell) class QuadTreeWorld : public TerrainGrid // note: derived from TerrainGrid is only to render default cells (see loadCell)
@ -31,25 +30,36 @@ namespace Terrain
virtual void setViewDistance(float distance) { mViewDistance = distance; } virtual void setViewDistance(float distance) { mViewDistance = distance; }
void cacheCell(View *view, int x, int y); void cacheCell(View *view, int x, int y) {}
/// @note Not thread safe. /// @note Not thread safe.
virtual void loadCell(int x, int y); virtual void loadCell(int x, int y);
/// @note Not thread safe. /// @note Not thread safe.
virtual void unloadCell(int x, int y); virtual void unloadCell(int x, int y);
View* createView(); View* createView();
void preload(View* view, const osg::Vec3f& eyePoint, std::atomic<bool>& abort); void preload(View* view, const osg::Vec3f& eyePoint, const osg::Vec4i &cellgrid, std::atomic<bool>& abort, std::atomic<int>& progress, int& progressRange);
void storeView(const View* view, double referenceTime); bool storeView(const View* view, double referenceTime);
void rebuildViews() override;
void reportStats(unsigned int frameNumber, osg::Stats* stats); void reportStats(unsigned int frameNumber, osg::Stats* stats);
class ChunkManager
{
public:
virtual ~ChunkManager(){}
virtual osg::ref_ptr<osg::Node> getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool far, const osg::Vec3f& viewPoint, bool compile) = 0;
virtual unsigned int getNodeMask() { return 0; }
};
void addChunkManager(ChunkManager*);
private: private:
void ensureQuadTreeBuilt(); void ensureQuadTreeBuilt();
osg::ref_ptr<RootNode> mRootNode; osg::ref_ptr<RootNode> mRootNode;
osg::ref_ptr<ViewDataMap> mViewDataMap; osg::ref_ptr<ViewDataMap> mViewDataMap;
osg::ref_ptr<LodCallback> mLodCallback;
std::vector<ChunkManager*> mChunkManagers;
OpenThreads::Mutex mQuadTreeMutex; OpenThreads::Mutex mQuadTreeMutex;
bool mQuadTreeBuilt; bool mQuadTreeBuilt;

@ -102,6 +102,10 @@ void TerrainDrawable::cull(osgUtil::CullVisitor *cv)
bool pushedLight = mLightListCallback && mLightListCallback->pushLightState(this, cv); bool pushedLight = mLightListCallback && mLightListCallback->pushLightState(this, cv);
osg::StateSet* stateset = getStateSet();
if (stateset)
cv->pushStateSet(stateset);
for (PassVector::const_iterator it = mPasses.begin(); it != mPasses.end(); ++it) for (PassVector::const_iterator it = mPasses.begin(); it != mPasses.end(); ++it)
{ {
cv->pushStateSet(*it); cv->pushStateSet(*it);
@ -109,6 +113,8 @@ void TerrainDrawable::cull(osgUtil::CullVisitor *cv)
cv->popStateSet(); cv->popStateSet();
} }
if (stateset)
cv->popStateSet();
if (pushedLight) if (pushedLight)
cv->popStateSet(); cv->popStateSet();
} }

@ -5,9 +5,10 @@
#include <osg/Group> #include <osg/Group>
#include <osg/ComputeBoundsVisitor> #include <osg/ComputeBoundsVisitor>
#include <components/sceneutil/positionattitudetransform.hpp>
#include "chunkmanager.hpp" #include "chunkmanager.hpp"
#include "compositemaprenderer.hpp" #include "compositemaprenderer.hpp"
#include "storage.hpp"
namespace Terrain namespace Terrain
{ {
@ -57,12 +58,17 @@ osg::ref_ptr<osg::Node> TerrainGrid::buildTerrain (osg::Group* parent, float chu
} }
else else
{ {
osg::ref_ptr<osg::Node> node = mChunkManager->getChunk(chunkSize, chunkCenter, 0, 0); osg::ref_ptr<osg::Node> node = mChunkManager->getChunk(chunkSize, chunkCenter, 0, 0, false, osg::Vec3f(), true);
if (!node) if (!node)
return nullptr; return nullptr;
const float cellWorldSize = mStorage->getCellWorldSize();
osg::ref_ptr<SceneUtil::PositionAttitudeTransform> pat = new SceneUtil::PositionAttitudeTransform;
pat->setPosition(osg::Vec3f(chunkCenter.x()*cellWorldSize, chunkCenter.y()*cellWorldSize, 0.f));
pat->addChild(node);
if (parent) if (parent)
parent->addChild(node); parent->addChild(pat);
return node; return pat;
} }
} }

@ -25,7 +25,7 @@ struct UpdateTextureFilteringFunctor
} }
Resource::SceneManager* mSceneManager; Resource::SceneManager* mSceneManager;
void operator()(osg::Object* obj) void operator()(std::string, osg::Object* obj)
{ {
mSceneManager->applyFilterSettings(static_cast<osg::Texture2D*>(obj)); mSceneManager->applyFilterSettings(static_cast<osg::Texture2D*>(obj));
} }

@ -1,5 +1,7 @@
#include "viewdata.hpp" #include "viewdata.hpp"
#include "quadtreenode.hpp"
namespace Terrain namespace Terrain
{ {
@ -8,6 +10,7 @@ ViewData::ViewData()
, mLastUsageTimeStamp(0.0) , mLastUsageTimeStamp(0.0)
, mChanged(false) , mChanged(false)
, mHasViewPoint(false) , mHasViewPoint(false)
, mWorldUpdateRevision(0)
{ {
} }
@ -24,6 +27,8 @@ void ViewData::copyFrom(const ViewData& other)
mChanged = other.mChanged; mChanged = other.mChanged;
mHasViewPoint = other.mHasViewPoint; mHasViewPoint = other.mHasViewPoint;
mViewPoint = other.mViewPoint; mViewPoint = other.mViewPoint;
mActiveGrid = other.mActiveGrid;
mWorldUpdateRevision = other.mWorldUpdateRevision;
} }
void ViewData::add(QuadTreeNode *node) void ViewData::add(QuadTreeNode *node)
@ -90,7 +95,12 @@ void ViewData::clear()
mHasViewPoint = false; mHasViewPoint = false;
} }
bool ViewData::contains(QuadTreeNode *node) bool ViewData::suitableToUse(const osg::Vec4i &activeGrid) const
{
return hasViewPoint() && activeGrid == mActiveGrid && getNumEntries();
}
bool ViewData::contains(QuadTreeNode *node) const
{ {
for (unsigned int i=0; i<mNumEntries; ++i) for (unsigned int i=0; i<mNumEntries; ++i)
if (mEntries[i].mNode == node) if (mEntries[i].mNode == node)
@ -118,79 +128,110 @@ bool ViewData::Entry::set(QuadTreeNode *node)
} }
} }
bool suitable(ViewData* vd, const osg::Vec3f& viewPoint, float& maxDist) ViewData *ViewDataMap::getViewData(osg::Object *viewer, const osg::Vec3f& viewPoint, const osg::Vec4i &activeGrid, bool& needsUpdate)
{
return vd->hasViewPoint() && (vd->getViewPoint() - viewPoint).length2() < maxDist*maxDist;
}
ViewData *ViewDataMap::getViewData(osg::Object *viewer, const osg::Vec3f& viewPoint, bool& needsUpdate)
{ {
Map::const_iterator found = mViews.find(viewer); ViewerMap::const_iterator found = mViewers.find(viewer);
ViewData* vd = nullptr; ViewData* vd = nullptr;
if (found == mViews.end()) if (found == mViewers.end())
{ {
vd = createOrReuseView(); vd = createOrReuseView();
mViews[viewer] = vd; mViewers[viewer] = vd;
} }
else else
vd = found->second; vd = found->second;
needsUpdate = false;
if (!suitable(vd, viewPoint, mReuseDistance)) if (!(vd->suitableToUse(activeGrid) && (vd->getViewPoint()-viewPoint).length2() < mReuseDistance*mReuseDistance && vd->getWorldUpdateRevision() >= mWorldUpdateRevision))
{ {
for (Map::const_iterator other = mViews.begin(); other != mViews.end(); ++other) float shortestDist = std::numeric_limits<float>::max();
const ViewData* mostSuitableView = nullptr;
for (const ViewData* other : mUsedViews)
{ {
if (suitable(other->second, viewPoint, mReuseDistance) && other->second->getNumEntries()) if (other->suitableToUse(activeGrid) && other->getWorldUpdateRevision() >= mWorldUpdateRevision)
{ {
vd->copyFrom(*other->second); float dist = (viewPoint-other->getViewPoint()).length2();
needsUpdate = false; if (dist < shortestDist)
return vd; {
shortestDist = dist;
mostSuitableView = other;
}
} }
} }
if (mostSuitableView && mostSuitableView != vd)
{
vd->copyFrom(*mostSuitableView);
return vd;
}
}
if (!vd->suitableToUse(activeGrid))
{
vd->setViewPoint(viewPoint); vd->setViewPoint(viewPoint);
vd->setActiveGrid(activeGrid);
needsUpdate = true; needsUpdate = true;
} }
else
needsUpdate = false;
return vd; return vd;
} }
bool ViewDataMap::storeView(const ViewData* view, double referenceTime)
{
if (view->getWorldUpdateRevision() < mWorldUpdateRevision)
return false;
ViewData* store = createOrReuseView();
store->copyFrom(*view);
store->setLastUsageTimeStamp(referenceTime);
return true;
}
ViewData *ViewDataMap::createOrReuseView() ViewData *ViewDataMap::createOrReuseView()
{ {
ViewData* vd = nullptr;
if (mUnusedViews.size()) if (mUnusedViews.size())
{ {
ViewData* vd = mUnusedViews.front(); vd = mUnusedViews.front();
mUnusedViews.pop_front(); mUnusedViews.pop_front();
return vd;
} }
else else
{ {
mViewVector.push_back(ViewData()); mViewVector.push_back(ViewData());
return &mViewVector.back(); vd = &mViewVector.back();
} }
mUsedViews.push_back(vd);
vd->setWorldUpdateRevision(mWorldUpdateRevision);
return vd;
}
ViewData *ViewDataMap::createIndependentView() const
{
ViewData* vd = new ViewData;
vd->setWorldUpdateRevision(mWorldUpdateRevision);
return vd;
} }
void ViewDataMap::clearUnusedViews(double referenceTime) void ViewDataMap::clearUnusedViews(double referenceTime)
{ {
for (Map::iterator it = mViews.begin(); it != mViews.end(); ) for (ViewerMap::iterator it = mViewers.begin(); it != mViewers.end(); )
{
if (it->second->getLastUsageTimeStamp() + mExpiryDelay < referenceTime)
mViewers.erase(it++);
else
++it;
}
for (std::deque<ViewData*>::iterator it = mUsedViews.begin(); it != mUsedViews.end(); )
{ {
ViewData* vd = it->second; if ((*it)->getLastUsageTimeStamp() + mExpiryDelay < referenceTime)
if (vd->getLastUsageTimeStamp() + mExpiryDelay < referenceTime)
{ {
vd->clear(); (*it)->clear();
mUnusedViews.push_back(vd); mUnusedViews.push_back(*it);
mViews.erase(it++); it = mUsedViews.erase(it);
} }
else else
++it; ++it;
} }
} }
void ViewDataMap::clear() void ViewDataMap::rebuildViews()
{ {
mViews.clear(); ++mWorldUpdateRevision;
mUnusedViews.clear();
mViewVector.clear();
} }
} }

@ -23,9 +23,11 @@ namespace Terrain
void reset(); void reset();
bool suitableToUse(const osg::Vec4i& activeGrid) const;
void clear(); void clear();
bool contains(QuadTreeNode* node); bool contains(QuadTreeNode* node) const;
void copyFrom(const ViewData& other); void copyFrom(const ViewData& other);
@ -57,6 +59,12 @@ namespace Terrain
void setViewPoint(const osg::Vec3f& viewPoint); void setViewPoint(const osg::Vec3f& viewPoint);
const osg::Vec3f& getViewPoint() const; const osg::Vec3f& getViewPoint() const;
void setActiveGrid(const osg::Vec4i &grid) { if (grid != mActiveGrid) {mActiveGrid = grid;mEntries.clear();mNumEntries=0;} }
const osg::Vec4i &getActiveGrid() const { return mActiveGrid;}
unsigned int getWorldUpdateRevision() const { return mWorldUpdateRevision; }
void setWorldUpdateRevision(int updateRevision) { mWorldUpdateRevision = updateRevision; }
private: private:
std::vector<Entry> mEntries; std::vector<Entry> mEntries;
unsigned int mNumEntries; unsigned int mNumEntries;
@ -64,34 +72,41 @@ namespace Terrain
bool mChanged; bool mChanged;
osg::Vec3f mViewPoint; osg::Vec3f mViewPoint;
bool mHasViewPoint; bool mHasViewPoint;
osg::Vec4i mActiveGrid;
unsigned int mWorldUpdateRevision;
}; };
class ViewDataMap : public osg::Referenced class ViewDataMap : public osg::Referenced
{ {
public: public:
ViewDataMap() ViewDataMap()
: mReuseDistance(300) // large value should be safe because the visibility of each node is still updated individually for each camera even if the base view was reused. : mReuseDistance(150) // large value should be safe because the visibility of each node is still updated individually for each camera even if the base view was reused.
// this value also serves as a threshold for when a newly loaded LOD gets unloaded again so that if you hover around an LOD transition point the LODs won't keep loading and unloading all the time. // this value also serves as a threshold for when a newly loaded LOD gets unloaded again so that if you hover around an LOD transition point the LODs won't keep loading and unloading all the time.
, mExpiryDelay(1.f) , mExpiryDelay(1.f)
, mWorldUpdateRevision(0)
{} {}
ViewData* getViewData(osg::Object* viewer, const osg::Vec3f& viewPoint, bool& needsUpdate); ViewData* getViewData(osg::Object* viewer, const osg::Vec3f& viewPoint, const osg::Vec4i &activeGrid, bool& needsUpdate);
ViewData* createOrReuseView(); ViewData* createOrReuseView();
ViewData* createIndependentView() const;
void clearUnusedViews(double referenceTime); void clearUnusedViews(double referenceTime);
void rebuildViews();
void clear(); bool storeView(const ViewData* view, double referenceTime);
private: private:
std::list<ViewData> mViewVector; std::list<ViewData> mViewVector;
typedef std::map<osg::ref_ptr<osg::Object>, ViewData*> Map; typedef std::map<osg::ref_ptr<osg::Object>, ViewData*> ViewerMap;
Map mViews; ViewerMap mViewers;
float mReuseDistance; float mReuseDistance;
float mExpiryDelay; // time in seconds for unused view to be removed float mExpiryDelay; // time in seconds for unused view to be removed
unsigned int mWorldUpdateRevision;
std::deque<ViewData*> mUsedViews;
std::deque<ViewData*> mUnusedViews; std::deque<ViewData*> mUnusedViews;
}; };

@ -1,7 +1,6 @@
#include "world.hpp" #include "world.hpp"
#include <osg/Group> #include <osg/Group>
#include <osg/Material>
#include <osg/Camera> #include <osg/Camera>
#include <components/resource/resourcesystem.hpp> #include <components/resource/resourcesystem.hpp>
@ -23,11 +22,6 @@ World::World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSyst
{ {
mTerrainRoot = new osg::Group; mTerrainRoot = new osg::Group;
mTerrainRoot->setNodeMask(nodeMask); mTerrainRoot->setNodeMask(nodeMask);
mTerrainRoot->getOrCreateStateSet()->setRenderingHint(osg::StateSet::OPAQUE_BIN);
osg::ref_ptr<osg::Material> material (new osg::Material);
material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE);
mTerrainRoot->getOrCreateStateSet()->setAttributeAndModes(material, osg::StateAttribute::ON);
mTerrainRoot->setName("Terrain Root"); mTerrainRoot->setName("Terrain Root");
osg::ref_ptr<osg::Camera> compositeCam = new osg::Camera; osg::ref_ptr<osg::Camera> compositeCam = new osg::Camera;
@ -48,6 +42,7 @@ World::World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSyst
mTextureManager.reset(new TextureManager(mResourceSystem->getSceneManager())); mTextureManager.reset(new TextureManager(mResourceSystem->getSceneManager()));
mChunkManager.reset(new ChunkManager(mStorage, mResourceSystem->getSceneManager(), mTextureManager.get(), mCompositeMapRenderer)); mChunkManager.reset(new ChunkManager(mStorage, mResourceSystem->getSceneManager(), mTextureManager.get(), mCompositeMapRenderer));
mChunkManager->setNodeMask(nodeMask);
mCellBorder.reset(new CellBorder(this,mTerrainRoot.get(),borderMask)); mCellBorder.reset(new CellBorder(this,mTerrainRoot.get(),borderMask));
mResourceSystem->addResourceManager(mChunkManager.get()); mResourceSystem->addResourceManager(mChunkManager.get());

@ -147,11 +147,13 @@ namespace Terrain
/// @note Thread safe, as long as you do not attempt to load into the same view from multiple threads. /// @note Thread safe, as long as you do not attempt to load into the same view from multiple threads.
virtual void preload(View* view, const osg::Vec3f& viewPoint, std::atomic<bool>& abort) {} virtual void preload(View* view, const osg::Vec3f& viewPoint, const osg::Vec4i &cellgrid, std::atomic<bool>& abort, std::atomic<int>& progress, int& progressRange) {}
/// Store a preloaded view into the cache with the intent that the next rendering traversal can use it. /// Store a preloaded view into the cache with the intent that the next rendering traversal can use it.
/// @note Not thread safe. /// @note Not thread safe.
virtual void storeView(const View* view, double referenceTime) {} virtual bool storeView(const View* view, double referenceTime) {return true;}
virtual void rebuildViews() {}
virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) {} virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) {}
@ -161,6 +163,8 @@ namespace Terrain
osg::Callback* getHeightCullCallback(float highz, unsigned int mask); osg::Callback* getHeightCullCallback(float highz, unsigned int mask);
void setActiveGrid(const osg::Vec4i &grid) { mActiveGrid = grid; }
protected: protected:
Storage* mStorage; Storage* mStorage;
@ -181,6 +185,8 @@ namespace Terrain
std::set<std::pair<int,int>> mLoadedCells; std::set<std::pair<int,int>> mLoadedCells;
osg::ref_ptr<HeightCullCallback> mHeightCullCallback; osg::ref_ptr<HeightCullCallback> mHeightCullCallback;
osg::Vec4i mActiveGrid;
}; };
} }

@ -106,6 +106,27 @@ composite map resolution = 512
# Controls the maximum size of composite geometry, should be >= 1.0. With low values there will be many small chunks, with high values - lesser count of bigger chunks. # Controls the maximum size of composite geometry, should be >= 1.0. With low values there will be many small chunks, with high values - lesser count of bigger chunks.
max composite geometry size = 4.0 max composite geometry size = 4.0
# Use object paging for non active cells
object paging = true
# Use object paging for active cells grid
object paging active grid = true
# Affects the likelyhood of objects being merged. A higher value means merging is more likely and may improve FPS at the cost of memory.
object paging merge factor = 250
# Cull objects smaller than this size divided by distance
object paging min size = 0.01
# Adjusts 'min size' based on merging decision. Allows inexpensive objects to be rendered from a greater distance.
object paging min size merge factor = 0.3
# Controls how inexpensive an object needs to be to utilize 'min size merge factor'.
object paging min size cost multiplier = 25
# Assign a random color to merged batches.
object paging debug batches = false
[Fog] [Fog]
# If true, use extended fog parameters for distant terrain not controlled by # If true, use extended fog parameters for distant terrain not controlled by
@ -808,8 +829,8 @@ enable debug hud = false
# Enable the debug overlay to see where each shadow map affects. # Enable the debug overlay to see where each shadow map affects.
enable debug overlay = false enable debug overlay = false
# Attempt to better use the shadow map by making them cover a smaller area. May have a minor to major performance impact. # Attempt to better use the shadow map by making them cover a smaller area. May have a major performance impact.
compute tight scene bounds = true compute tight scene bounds = false
# How large to make the shadow map(s). Higher values increase GPU load, but can produce better-looking results. Power-of-two values may turn out to be faster on some GPU/driver combinations. # How large to make the shadow map(s). Higher values increase GPU load, but can produce better-looking results. Power-of-two values may turn out to be faster on some GPU/driver combinations.
shadow map resolution = 1024 shadow map resolution = 1024

Loading…
Cancel
Save