terrainbased objectpaging

Signed-off-by: Bret Curtis <psi29a@gmail.com>
pull/578/head
bzzt 6 years ago committed by Bret Curtis
parent 87dba1fcfc
commit d684f1a78f

@ -21,7 +21,7 @@ add_openmw_dir (mwrender
actors objects renderingmanager animation rotatecontroller sky npcanimation vismask
creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation
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

@ -0,0 +1,302 @@
#include "objectpaging.hpp"
#include <osg/Version>
#include <osg/LOD>
#include <osg/Switch>
#include <osg/MatrixTransform>
#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 <osgParticle/ParticleProcessor>
#include <osgParticle/ParticleSystemUpdater>
#include <osgParticle/Emitter>
#include <components/sceneutil/morphgeometry.hpp>
#include <components/sceneutil/riggeometry.hpp>
#include <components/settings/settings.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 ? false : true;
default:
return false;
}
}
const 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: throw std::exception();
}
}
osg::ref_ptr<osg::Node> ObjectPaging::getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool far, const osg::Vec3f& viewPoint)
{
if (!far)return nullptr;
ChunkId id = std::make_tuple(center, size);
osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(id);
if (obj)
return obj->asNode();
else
{
osg::ref_ptr<osg::Node> node = createChunk(size, center, viewPoint);
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 true;
}
};
class CopyOp : public osg::CopyOp
{
public:
CopyOp() : mDistance(0.f) {
setCopyFlags(osg::CopyOp::DEEP_COPY_NODES|osg::CopyOp::DEEP_COPY_DRAWABLES
#if OSG_MIN_VERSION_REQUIRED(3,5,6)
|osg::CopyOp::DEEP_COPY_ARRAYS|osg::CopyOp::DEEP_COPY_PRIMITIVES // damned vbogarbage racing
#endif
);
}
float mDistance;
virtual osg::Node* operator() (const osg::Node* node) const
{
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) <= mDistance && mDistance < lod->getMaxRange(i))
n->addChild(operator()(lod->getChild(i)));
n->setDataVariance(osg::Object::STATIC);
return n;
}
osg::Node* n = osg::CopyOp::operator()(node);
if (n) {
n->setDataVariance(osg::Object::STATIC);
n->setUserDataContainer(nullptr);
n->setName("");
}
return n;
}
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 osg::CopyOp::operator()(rig->getSourceGeometry());
if (const SceneUtil::MorphGeometry* morph = dynamic_cast<const SceneUtil::MorphGeometry*>(drawable))
return osg::CopyOp::operator()(morph->getSourceGeometry());
return osg::CopyOp::operator()(drawable);
}
virtual osg::Callback* operator() (const osg::Callback* callback) const
{
return nullptr;
}
};
ObjectPaging::ObjectPaging(Resource::SceneManager* sceneManager)
: GenericResourceManager<ChunkId>(nullptr)
, mSceneManager(sceneManager)
{
mMergeGeometry = Settings::Manager::getBool("object paging merge geometry", "Terrain");
mMinSize = Settings::Manager::getFloat("object paging min size", "Terrain");
}
osg::ref_ptr<osg::Node> ObjectPaging::createChunk(float size, const osg::Vec2f& center, const osg::Vec3f& viewPoint)
{
osg::Vec2i startCell = osg::Vec2i(std::floor(center.x() - size/2.f), std::floor(center.y() - size/2.f));
osg::ref_ptr<osg::Group> group = new osg::Group;
osg::Vec3f worldCenter = osg::Vec3f(center.x(), center.y(), 0)*ESM::Land::REAL_SIZE;
osg::Vec3f relativeViewPoint = viewPoint - worldCenter;
std::vector<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))
{
if (std::find(cell->mMovedRefs.begin(), cell->mMovedRefs.end(), ref.mRefNum) != cell->mMovedRefs.end()) continue;
int type = store.findStatic(Misc::StringUtils::lowerCase(ref.mRefID));
if (!typeFilter(type,size>=2)) continue;
if (deleted) continue;
refs.push_back(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;
bool deleted = it->second;
if (deleted) continue;
int type = store.findStatic(Misc::StringUtils::lowerCase(ref.mRefID));
if (!typeFilter(type,size>=2)) continue;
refs.push_back(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));
for (const ESM::CellRef& ref : refs)
{
std::string id = Misc::StringUtils::lowerCase(ref.mRefID);
if (id == "prisonmarker" || id == "divinemarker" || id == "templemarker" || id == "northmarker")
continue; // marker objects that have a hardcoded function in the game logic, should be hidden from the player
int type = store.findStatic(id);
std::string model = "meshes/" + getModel(type, id, store);
/*
bool useAnim = type != ESM::REC_STAT;
if (useAnim)
model = Misc::ResourceHelpers::correctActorModelPath(model, mSceneManager->getVFS());
*/
if (model.empty()) continue;
osg::Vec3f pos = ref.mPos.asVec3();
if (size < 1.f)
{
osg::Vec3f cellPos = pos / ESM::Land::REAL_SIZE;
cellPos.x() = std::max(cellPos.x(), std::floor(minBound.x()));
cellPos.x() = std::min(cellPos.x(), std::ceil(maxBound.x()));
cellPos.y() = std::max(cellPos.y(), std::floor(minBound.y()));
cellPos.y() = std::min(cellPos.y(), std::ceil(maxBound.y()));
if (cellPos.x() < minBound.x() || cellPos.x() > maxBound.x() || cellPos.y() < minBound.y() || cellPos.y() > maxBound.y())
continue;
}
osg::ref_ptr<const osg::Node> cnode = mSceneManager->getTemplate(model, false);
float d = (viewPoint - pos).length();
if (cnode->getBound().radius() * ref.mScale < d*mMinSize)
continue;
CopyOp co = CopyOp();
co.mDistance = d;
osg::ref_ptr<osg::Node> node = osg::clone(cnode.get(), co);
node->setUserDataContainer(nullptr);
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->addChild(node);
trans->setDataVariance(osg::Object::STATIC);
group->addChild(trans);
}
if (mMergeGeometry)
{
SceneUtil::Optimizer optimizer;
if ((relativeViewPoint - group->getBound().center()).length2() > group->getBound().radius2())
{
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(group, options);
}
auto ico = mSceneManager->getIncrementalCompileOperation();
if (ico) ico->add(group);
else group->getBound();
group->setNodeMask(Mask_Static);
return group;
}
unsigned int ObjectPaging::getNodeMask()
{
return Mask_Static;
}
}

@ -0,0 +1,44 @@
#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>
namespace Resource
{
class SceneManager;
}
namespace MWWorld
{
class ESMStore;
}
namespace MWRender
{
typedef std::tuple<osg::Vec2f, float> ChunkId; // Center, Size
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 far, const osg::Vec3f& viewPoint) override;
osg::ref_ptr<osg::Node> createChunk(float size, const osg::Vec2f& center, const osg::Vec3f& viewPoint);
virtual void setExpiryDelay(double expiryDelay) override { mExpiryDelay = 0.5f; }
virtual unsigned int getNodeMask() override;
private:
Resource::SceneManager* mSceneManager;
bool mMergeGeometry;
float mMinSize;
};
}
#endif

@ -70,6 +70,8 @@
#include "actorspaths.hpp"
#include "recastmesh.hpp"
#include "fogmanager.hpp"
#include "objectpaging.hpp"
namespace MWRender
{
@ -286,6 +288,12 @@ namespace MWRender
mTerrain.reset(new Terrain::QuadTreeWorld(
sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile, Mask_Debug,
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
mTerrain.reset(new Terrain::TerrainGrid(sceneRoot, mRootNode, mResourceSystem, mTerrainStorage, Mask_Terrain, Mask_PreCompile, Mask_Debug));
@ -1467,4 +1475,9 @@ namespace MWRender
mRecastMesh->update(mNavigator.getRecastMeshTiles(), mNavigator.getSettings());
}
void RenderingManager::setActiveGrid(const osg::Vec4i &grid)
{
mTerrain->setActiveGrid(grid);
}
}

@ -84,6 +84,7 @@ namespace MWRender
class NavMesh;
class ActorsPaths;
class RecastMesh;
class ObjectPaging;
class RenderingManager : public MWRender::RenderingInterface
{
@ -237,6 +238,8 @@ namespace MWRender
void setNavMeshNumber(const std::size_t value);
void setActiveGrid(const osg::Vec4i &grid);
private:
void updateProjectionMatrix();
void updateTextureFiltering();
@ -275,6 +278,7 @@ namespace MWRender
std::unique_ptr<Water> mWater;
std::unique_ptr<Terrain::World> mTerrain;
TerrainStorage* mTerrainStorage;
std::unique_ptr<ObjectPaging> mObjectPaging;
std::unique_ptr<SkyManager> mSky;
std::unique_ptr<FogManager> mFog;
std::unique_ptr<EffectManager> mEffectManager;

@ -172,7 +172,7 @@ namespace MWWorld
class TerrainPreloadItem : public SceneUtil::WorkItem
{
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)
, mTerrainViews(views)
, mWorld(world)
@ -191,7 +191,7 @@ namespace MWWorld
for (unsigned int i=0; i<mTerrainViews.size() && i<mPreloadPositions.size() && !mAbort; ++i)
{
mTerrainViews[i]->reset();
mWorld->preload(mTerrainViews[i], mPreloadPositions[i], mAbort);
mWorld->preload(mTerrainViews[i], mPreloadPositions[i].first, mPreloadPositions[i].second, mAbort);
}
}
@ -204,7 +204,7 @@ namespace MWWorld
std::atomic<bool> mAbort;
std::vector<osg::ref_ptr<Terrain::View> > mTerrainViews;
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.
@ -415,7 +415,7 @@ namespace MWWorld
mUnrefQueue = unrefQueue;
}
void CellPreloader::setTerrainPreloadPositions(const std::vector<osg::Vec3f> &positions)
void CellPreloader::setTerrainPreloadPositions(const std::vector<CellPreloader::PositionCellGrid> &positions)
{
if (mTerrainPreloadItem && !mTerrainPreloadItem->isDone())
return;

@ -4,6 +4,7 @@
#include <map>
#include <osg/ref_ptr>
#include <osg/Vec3f>
#include <osg/Vec4i>
#include <components/sceneutil/workqueue.hpp>
namespace Resource
@ -68,7 +69,8 @@ namespace MWWorld
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);
private:
Resource::ResourceSystem* mResourceSystem;
@ -105,7 +107,7 @@ namespace MWWorld
PreloadMap mPreloadCells;
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<SceneUtil::WorkItem> mUpdateCacheItem;
};

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

@ -68,6 +68,8 @@ namespace MWWorld
// Lookup of all IDs. Makes looking up references faster. Just
// maps the id name to the record type.
std::map<std::string, int> mIds;
std::map<std::string, int> mStaticIds;
std::map<int, StoreBase *> mStores;
ESM::NPC mPlayerTemplate;
@ -99,6 +101,14 @@ namespace MWWorld
}
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()
: mDynamicCount(0)

@ -286,28 +286,6 @@ namespace MWWorld
::updateObjectScale(ptr, *mPhysics, mRendering);
}
void Scene::getGridCenter(int &cellX, int &cellY)
{
int maxX = std::numeric_limits<int>::min();
int maxY = std::numeric_limits<int>::min();
int minX = std::numeric_limits<int>::max();
int minY = std::numeric_limits<int>::max();
CellStoreCollection::iterator iter = mActiveCells.begin();
while (iter!=mActiveCells.end())
{
assert ((*iter)->getCell()->isExterior());
int x = (*iter)->getCell()->getGridX();
int y = (*iter)->getCell()->getGridY();
maxX = std::max(x, maxX);
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)
{
mPreloadTimer += duration;
@ -488,6 +466,27 @@ namespace MWWorld
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(centerY-pos.x()), std::abs(centerY-pos.y()));
const float maxDistance = Constants::CellSizeInUnits / 2 + mCellLoadingThreshold; // 1/2 cell size + threshold
if (distance <= maxDistance)
return *currentGridCenter;
}
osg::Vec2i newCenter;
MWBase::Environment::get().getWorld()->positionToIndex(pos.x(), pos.y(), newCenter.x(), newCenter.y());
return newCenter;
}
void Scene::playerMoved(const osg::Vec3f &pos)
{
const auto navigator = MWBase::Environment::get().getWorld()->getNavigator();
@ -497,19 +496,9 @@ namespace MWWorld
if (!mCurrentCell || !mCurrentCell->isExterior())
return;
// figure out the center of the current cell grid (*not* necessarily mCurrentCell, which is the cell the player is in)
int cellX, cellY;
getGridCenter(cellX, cellY);
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);
}
osg::Vec2i newCell = getNewGridCenter(pos, &mCurrentGridCenter);
if (newCell != mCurrentGridCenter)
changeCellGrid(newCell.x(), newCell.y());
}
void Scene::changeCellGrid (int playerCellX, int playerCellY, bool changeEvent)
@ -612,6 +601,9 @@ namespace MWWorld
CellStore* current = MWBase::Environment::get().getWorld()->getExterior(playerCellX, playerCellY);
MWBase::Environment::get().getWindowManager()->changeCell(current);
mCurrentGridCenter = osg::Vec2i(playerCellX, playerCellY);
mRendering.setActiveGrid(gridCenterToBounds(mCurrentGridCenter));
if (changeEvent)
mCellChanged = true;
}
@ -983,7 +975,7 @@ namespace MWWorld
void Scene::preloadCells(float dt)
{
std::vector<osg::Vec3f> exteriorPositions;
std::vector<PositionCellGrid> exteriorPositions;
const MWWorld::ConstPtr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
osg::Vec3f playerPos = player.getRefData().getPosition().asVec3();
@ -991,7 +983,7 @@ namespace MWWorld
osg::Vec3f predictedPos = playerPos + moved / dt * mPredictionTime;
if (mCurrentCell->isExterior())
exteriorPositions.push_back(predictedPos);
exteriorPositions.emplace_back(predictedPos, gridCenterToBounds(getNewGridCenter(predictedPos, &mCurrentGridCenter)));
mLastPlayerPos = playerPos;
@ -1008,7 +1000,7 @@ namespace MWWorld
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;
for (const MWWorld::CellStore* cellStore : mActiveCells)
@ -1042,7 +1034,7 @@ namespace MWWorld
int x,y;
MWBase::Environment::get().getWorld()->positionToIndex (pos.x(), pos.y(), x, y);
preloadCell(MWBase::Environment::get().getWorld()->getExterior(x,y), true);
exteriorPositions.push_back(pos);
exteriorPositions.emplace_back(pos, gridCenterToBounds(getNewGridCenter(pos)));
}
}
catch (std::exception& e)
@ -1062,7 +1054,7 @@ namespace MWWorld
int cellX,cellY;
getGridCenter(cellX,cellY);
cellX = mCurrentGridCenter.x(); cellY = mCurrentGridCenter.y();
float centerX, centerY;
MWBase::Environment::get().getWorld()->indexToPosition(cellX, cellY, centerX, centerY, true);
@ -1110,8 +1102,8 @@ namespace MWWorld
void Scene::preloadTerrain(const osg::Vec3f &pos)
{
std::vector<osg::Vec3f> vec;
vec.push_back(pos);
std::vector<PositionCellGrid> vec;
vec.emplace_back(pos, gridCenterToBounds(getNewGridCenter(pos)));
mPreloader->setTerrainPreloadPositions(vec);
}
@ -1145,7 +1137,7 @@ namespace MWWorld
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();
ListFastTravelDestinationsVisitor listVisitor(mPreloadDistance, player.getRefData().getPosition().asVec3());
@ -1166,7 +1158,7 @@ namespace MWWorld
int x,y;
MWBase::Environment::get().getWorld()->positionToIndex( pos.x(), pos.y(), x, y);
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
#define GAME_MWWORLD_SCENE_H
#include <osg/Vec4i>
#include <osg/Vec2i>
#include "ptr.hpp"
#include "globals.hpp"
@ -86,16 +89,20 @@ namespace MWWorld
osg::Vec3f mLastPlayerPos;
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
void changeCellGrid (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 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 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:

@ -151,6 +151,19 @@ namespace MWWorld
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() && Misc::StringUtils::ciEqual(it->second.mId, id)) {
return &(it->second);
}
return 0;
}
template<typename T>
bool Store<T>::isDynamic(const std::string &id) const
{
@ -582,6 +595,18 @@ namespace MWWorld
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)
{
std::pair<int, int> key(x, y);

@ -167,6 +167,7 @@ namespace MWWorld
void setUp();
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?
@ -297,6 +298,7 @@ namespace MWWorld
const ESM::Cell *search(const std::string &id) 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 *find(const std::string &id) const;

@ -466,7 +466,7 @@ namespace Resource
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;
mVFS->normalizeFilename(normalized);
@ -529,7 +529,7 @@ namespace Resource
optimizer.optimize(loaded, options);
}
if (mIncrementalCompileOperation)
if (compile && mIncrementalCompileOperation)
mIncrementalCompileOperation->add(loaded);
else
loaded->getBound();
@ -713,6 +713,13 @@ namespace Resource
mSharedStateMutex.lock();
mSharedStateManager->prune();
mSharedStateMutex.unlock();
if (mIncrementalCompileOperation)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(*mIncrementalCompileOperation->getToCompiledMutex());
while (mIncrementalCompileOperation->getToCompile().size() > 1000)
mIncrementalCompileOperation->getToCompile().pop_front();
}
}
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.
/// If even the error marker mesh can not be found, an exception is thrown.
/// @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
/// return this cached object instead of creating a new one.

@ -103,7 +103,9 @@ void Optimizer::optimize(osg::Node* node, unsigned int options)
osg::Timer_t startTick = osg::Timer::instance()->tick();
MergeGeometryVisitor mgv(this);
mgv.setTargetMaximumNumberOfVertices(10000);
mgv.setTargetMaximumNumberOfVertices(1000000);
mgv.setMergeAlphaBlending(_mergeAlphaBlending);
mgv.setViewPoint(_viewPoint);
node->accept(mgv);
osg::Timer_t endTick = osg::Timer::instance()->tick();
@ -988,6 +990,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
{
bool operator() (const osg::ref_ptr<osg::Geometry>& lhs,const osg::ref_ptr<osg::Geometry>& rhs) const
@ -1055,16 +1068,16 @@ bool isAbleToMerge(const osg::Geometry& g1, const osg::Geometry& g2)
void Optimizer::MergeGeometryVisitor::pushStateSet(osg::StateSet *stateSet)
{
_stateSetStack.push_back(stateSet);
checkAllowedToMerge();
checkAlphaBlendingActive();
}
void Optimizer::MergeGeometryVisitor::popStateSet()
{
_stateSetStack.pop_back();
checkAllowedToMerge();
checkAlphaBlendingActive();
}
void Optimizer::MergeGeometryVisitor::checkAllowedToMerge()
void Optimizer::MergeGeometryVisitor::checkAlphaBlendingActive()
{
int renderingHint = 0;
bool override = false;
@ -1080,7 +1093,7 @@ void Optimizer::MergeGeometryVisitor::checkAllowedToMerge()
override = true;
}
// 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)
@ -1088,7 +1101,7 @@ void Optimizer::MergeGeometryVisitor::apply(osg::Group &group)
if (group.getStateSet())
pushStateSet(group.getStateSet());
if (_allowedToMerge)
if (!_alphaBlendingActive || _mergeAlphaBlending)
mergeGroup(group);
traverse(group);
@ -1097,6 +1110,14 @@ void Optimizer::MergeGeometryVisitor::apply(osg::Group &group)
popStateSet();
}
osg::PrimitiveSet* clonePrimitive(osg::PrimitiveSet* ps)
{
if (ps->referenceCount() <= 1)
return ps;
ps = dynamic_cast<osg::PrimitiveSet*>(ps->clone(osg::CopyOp::DEEP_COPY_ALL));
return ps;
}
bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group)
{
if (!isOperationPermissibleForObject(&group)) return false;
@ -1120,7 +1141,7 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group)
osg::Geometry* geom = child->asGeometry();
if (geom)
{
if (!geometryContainsSharedArrays(*geom) &&
if (
geom->getDataVariance()!=osg::Object::DYNAMIC &&
isOperationPermissibleForObject(geom))
{
@ -1254,6 +1275,12 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group)
DuplicateList& duplicateList = *mitr;
if (!duplicateList.empty())
{
if (_alphaBlendingActive)
{
LessGeometryViewPoint lgvp;
lgvp._viewPoint = _viewPoint;
std::sort(duplicateList.begin(), duplicateList.end(), lgvp);
}
DuplicateList::iterator ditr = duplicateList.begin();
osg::ref_ptr<osg::Geometry> lhs = *ditr++;
group.addChild(lhs.get());
@ -1290,10 +1317,12 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group)
{
if (prim->getNumIndices()==3)
{
prim = clonePrimitive(prim); (*itr) = prim;
prim->setMode(osg::PrimitiveSet::TRIANGLES);
}
else if (prim->getNumIndices()==4)
{
prim = clonePrimitive(prim); (*itr) = prim;
prim->setMode(osg::PrimitiveSet::QUADS);
}
}
@ -1320,6 +1349,8 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group)
#if 1
bool doneCombine = false;
std::set<osg::PrimitiveSet*> toremove;
osg::Geometry::PrimitiveSetList& primitives = geom->getPrimitiveSetList();
unsigned int lhsNo=0;
unsigned int rhsNo=1;
@ -1348,6 +1379,8 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group)
if (combine)
{
lhs = clonePrimitive(lhs);
primitives[lhsNo] = lhs;
switch(lhs->getType())
{
@ -1375,7 +1408,7 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group)
if (combine)
{
// make this primitive set as invalid and needing cleaning up.
rhs->setMode(0xffffff);
toremove.insert(rhs);
doneCombine = true;
++rhsNo;
}
@ -1390,7 +1423,6 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group)
if (doneCombine)
{
// 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.
osg::Geometry::PrimitiveSetList oldPrimitives;
primitives.swap(oldPrimitives);
@ -1400,7 +1432,7 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group)
pitr != oldPrimitives.end();
++pitr)
{
if ((*pitr)->getMode()!=0xffffff) primitives.push_back(*pitr);
if (!toremove.count(*pitr)) primitives.push_back(*pitr);
}
}
#endif
@ -1479,34 +1511,6 @@ bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group)
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
{
protected:
@ -1574,6 +1578,8 @@ class MergeArrayVisitor : public osg::ArrayVisitor
bool Optimizer::MergeGeometryVisitor::mergeGeometry(osg::Geometry& lhs,osg::Geometry& rhs)
{
if (lhs.containsSharedArrays())
lhs.duplicateSharedArrays();
MergeArrayVisitor merger;
@ -1661,7 +1667,6 @@ bool Optimizer::MergeGeometryVisitor::mergeGeometry(osg::Geometry& lhs,osg::Geom
}
}
// shift the indices of the incoming primitives to account for the pre existing geometry.
osg::Geometry::PrimitiveSetList::iterator primItr;
for(primItr=rhs.getPrimitiveSetList().begin(); primItr!=rhs.getPrimitiveSetList().end(); ++primItr)
@ -1697,7 +1702,8 @@ bool Optimizer::MergeGeometryVisitor::mergeGeometry(osg::Geometry& lhs,osg::Geom
}
else
{
primitive->offsetIndices(base);
(*primItr) = clonePrimitive(primitive);
(*primItr)->offsetIndices(base);
}
}
break;
@ -1722,7 +1728,8 @@ bool Optimizer::MergeGeometryVisitor::mergeGeometry(osg::Geometry& lhs,osg::Geom
}
else
{
primitive->offsetIndices(base);
(*primItr) = clonePrimitive(primitive);
(*primItr)->offsetIndices(base);
}
}
break;
@ -1731,7 +1738,8 @@ bool Optimizer::MergeGeometryVisitor::mergeGeometry(osg::Geometry& lhs,osg::Geom
case(osg::PrimitiveSet::DrawArrayLengthsPrimitiveType):
case(osg::PrimitiveSet::DrawElementsUIntPrimitiveType):
default:
primitive->offsetIndices(base);
(*primItr) = clonePrimitive(primitive);
(*primItr)->offsetIndices(base);
break;
}
}

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

@ -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;
}

@ -44,7 +44,7 @@ namespace SceneUtil
/// @note The source geometry will not be modified.
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 bool supports(const osg::PrimitiveFunctor&) const { return true; }

@ -10,7 +10,6 @@
#include <components/resource/objectcache.hpp>
#include <components/resource/scenemanager.hpp>
#include <components/sceneutil/positionattitudetransform.hpp>
#include <components/sceneutil/lightmanager.hpp>
#include "terraindrawable.hpp"
@ -35,7 +34,7 @@ ChunkManager::ChunkManager(Storage *storage, Resource::SceneManager *sceneMgr, T
}
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)
{
ChunkId id = std::make_tuple(center, lod, lodFlags);
osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(id);
@ -163,10 +162,6 @@ std::vector<osg::ref_ptr<osg::StateSet> > ChunkManager::createPasses(float chunk
osg::ref_ptr<osg::Node> ChunkManager::createChunk(float chunkSize, const osg::Vec2f &chunkCenter, unsigned char lod, unsigned int lodFlags)
{
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> normals (new osg::Vec3Array);
osg::ref_ptr<osg::Vec4ubArray> colors (new osg::Vec4ubArray);
@ -224,16 +219,15 @@ osg::ref_ptr<osg::Node> ChunkManager::createChunk(float chunkSize, const osg::Ve
geometry->setPasses(createPasses(chunkSize, chunkCenter, false));
}
transform->addChild(geometry);
transform->getBound();
geometry->setupWaterBoundingBox(-1, chunkSize * mStorage->getCellWorldSize() / numVerts);
if (mSceneManager->getIncrementalCompileOperation())
{
mSceneManager->getIncrementalCompileOperation()->add(geometry);
}
return transform;
geometry->setNodeMask(mNodeMask);
return geometry;
}
}

@ -6,6 +6,7 @@
#include <components/resource/resourcemanager.hpp>
#include "buffercache.hpp"
#include "quadtreeworld.hpp"
namespace osg
{
@ -29,23 +30,28 @@ namespace Terrain
typedef std::tuple<osg::Vec2f, unsigned char, unsigned int> ChunkId; // Center, Lod, Lod Flags
/// @brief Handles loading and caching of terrain chunks
class ChunkManager : public Resource::GenericResourceManager<ChunkId>
class ChunkManager : public Resource::GenericResourceManager<ChunkId>, public QuadTreeWorld::ChunkManager
{
public:
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);
void setCompositeMapSize(unsigned int size) { mCompositeMapSize = size; }
void setCompositeMapLevel(float level) { mCompositeMapLevel = level; }
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 clearCache() override;
void releaseGLObjects(osg::State* state) override;
virtual void setExpiryDelay(double expiryDelay) override { mExpiryDelay = 0.5f; }
private:
osg::ref_ptr<osg::Node> createChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags);
@ -61,6 +67,8 @@ namespace Terrain
CompositeMapRenderer* mCompositeMapRenderer;
BufferCache mBufferCache;
unsigned int mNodeMask;
unsigned int mCompositeMapSize;
float mCompositeMapLevel;
float mMaxCompGeometrySize;

@ -9,6 +9,7 @@
#include <components/misc/constants.hpp>
#include <components/sceneutil/mwshadowtechnique.hpp>
#include <components/sceneutil/positionattitudetransform.hpp>
#include "quadtreenode.hpp"
#include "storage.hpp"
@ -63,6 +64,12 @@ public:
int nativeLodLevel = Log2(static_cast<unsigned int>(node->getSize()/mMinSize));
int lodLevel = Log2(static_cast<unsigned int>(dist/(Constants::CellSizeInUnits*mMinSize*mFactor)));
if (node->getSize()>1 && dist < (8192+1024)*1.41421356237)
{
// to prevent making chunks who will cross the activegrid border
return false;
}
return nativeLodLevel <= lodLevel;
}
@ -231,6 +238,7 @@ QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resour
mChunkManager->setCompositeMapSize(compMapResolution);
mChunkManager->setCompositeMapLevel(compMapLevel);
mChunkManager->setMaxCompositeGeometrySize(maxCompGeometrySize);
mChunkManagers.push_back(mChunkManager.get());
}
QuadTreeWorld::~QuadTreeWorld()
@ -289,7 +297,7 @@ unsigned int getLodFlags(QuadTreeNode* node, int ourLod, int vertexLodMod, ViewD
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)
{
if (!vd->hasChanged() && entry.mRenderingNode)
return;
@ -308,7 +316,20 @@ void loadRenderingNode(ViewData::Entry& entry, ViewData* vd, int vertexLodMod, C
}
if (!entry.mRenderingNode)
entry.mRenderingNode = chunkManager->getChunk(entry.mNode->getSize(), entry.mNode->getCenter(), ourLod, entry.mLodFlags);
{
auto 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 far = (center.x() <= gridbounds.x() || center.y() <= gridbounds.y() || center.x() >= gridbounds.z() || center.y() >= gridbounds.w());
for (QuadTreeWorld::ChunkManager* m : chunkManagers)
{
osg::Node* n = m->getChunk(entry.mNode->getSize(), entry.mNode->getCenter(), ourLod, entry.mLodFlags, far, vd->getViewPoint());
if (n) pat->addChild(n);
}
entry.mRenderingNode = pat;
}
}
void updateWaterCullingView(HeightCullCallback* callback, ViewData* vd, osgUtil::CullVisitor* cv, float cellworldsize, bool outofworld)
@ -385,7 +406,7 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv)
bool needsUpdate = true;
ViewData* vd = nullptr;
if (isCullVisitor)
vd = mViewDataMap->getViewData(static_cast<osgUtil::CullVisitor*>(&nv)->getCurrentCamera(), nv.getViewPoint(), needsUpdate);
vd = mViewDataMap->getViewData(static_cast<osgUtil::CullVisitor*>(&nv)->getCurrentCamera(), nv.getViewPoint(), mActiveGrid, needsUpdate);
else
{
static ViewData sIntersectionViewData;
@ -432,12 +453,12 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv)
}
}
const float cellWorldSize = mStorage->getCellWorldSize();
for (unsigned int i=0; i<vd->getNumEntries(); ++i)
{
ViewData::Entry& entry = vd->getEntry(i);
loadRenderingNode(entry, vd, mVertexLodMod, mChunkManager.get());
loadRenderingNode(entry, vd, mVertexLodMod, cellWorldSize, mActiveGrid, mChunkManagers);
entry.mRenderingNode->accept(nv);
}
@ -490,13 +511,17 @@ void QuadTreeWorld::enable(bool enabled)
void QuadTreeWorld::cacheCell(View *view, int x, int y)
{
ensureQuadTreeBuilt();
osg::Vec4i grid (x,y,x+1,y+1);
ViewData* vd = static_cast<ViewData*>(view);
vd->setActiveGrid(grid);
mRootNode->traverseTo(vd, 1, osg::Vec2f(x+0.5f,y+0.5f));
const float cellWorldSize = mStorage->getCellWorldSize();
for (unsigned int i=0; i<vd->getNumEntries(); ++i)
{
ViewData::Entry& entry = vd->getEntry(i);
loadRenderingNode(entry, vd, mVertexLodMod, mChunkManager.get());
loadRenderingNode(entry, vd, mVertexLodMod, cellWorldSize, grid, mChunkManagers);
}
}
@ -505,18 +530,21 @@ View* QuadTreeWorld::createView()
return new ViewData;
}
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)
{
ensureQuadTreeBuilt();
ViewData* vd = static_cast<ViewData*>(view);
vd->setViewPoint(viewPoint);
vd->setActiveGrid(grid);
mRootNode->traverseNodes(vd, viewPoint, mLodCallback, mViewDistance);
const float cellWorldSize = mStorage->getCellWorldSize();
for (unsigned int i=0; i<vd->getNumEntries() && !abort; ++i)
{
ViewData::Entry& entry = vd->getEntry(i);
loadRenderingNode(entry, vd, mVertexLodMod, mChunkManager.get());
loadRenderingNode(entry, vd, mVertexLodMod, cellWorldSize, grid, mChunkManagers);
}
vd->markUnchanged();
}
@ -526,7 +554,7 @@ void QuadTreeWorld::storeView(const View* view, double referenceTime)
osg::ref_ptr<osg::Object> dummy = new osg::DummyObject;
const ViewData* vd = static_cast<const ViewData*>(view);
bool needsUpdate = false;
ViewData* stored = mViewDataMap->getViewData(dummy, vd->getViewPoint(), needsUpdate);
ViewData* stored = mViewDataMap->getViewData(dummy, vd->getViewPoint(), vd->getActiveGrid(), needsUpdate);
stored->copyFrom(*vd);
stored->setLastUsageTimeStamp(referenceTime);
}
@ -556,5 +584,10 @@ void QuadTreeWorld::unloadCell(int x, int y)
World::unloadCell(x,y);
}
void QuadTreeWorld::addChunkManager(QuadTreeWorld::ChunkManager* m)
{
mChunkManagers.push_back(m);
mTerrainRoot->setNodeMask(mTerrainRoot->getNodeMask()|m->getNodeMask());
}
}

@ -38,11 +38,20 @@ namespace Terrain
virtual void unloadCell(int x, int y);
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);
void storeView(const View* view, double referenceTime);
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) = 0;
virtual unsigned int getNodeMask() { return 0; }
};
void addChunkManager(ChunkManager*);
private:
void ensureQuadTreeBuilt();
@ -51,6 +60,8 @@ namespace Terrain
osg::ref_ptr<ViewDataMap> mViewDataMap;
osg::ref_ptr<LodCallback> mLodCallback;
std::vector<ChunkManager*> mChunkManagers;
OpenThreads::Mutex mQuadTreeMutex;
bool mQuadTreeBuilt;
float mLodFactor;

@ -5,9 +5,10 @@
#include <osg/Group>
#include <osg/ComputeBoundsVisitor>
#include <components/sceneutil/positionattitudetransform.hpp>
#include "chunkmanager.hpp"
#include "compositemaprenderer.hpp"
#include "storage.hpp"
namespace Terrain
{
@ -57,12 +58,17 @@ osg::ref_ptr<osg::Node> TerrainGrid::buildTerrain (osg::Group* parent, float chu
}
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());
if (!node)
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)
parent->addChild(node);
return node;
parent->addChild(pat);
return pat;
}
}

@ -24,6 +24,7 @@ void ViewData::copyFrom(const ViewData& other)
mChanged = other.mChanged;
mHasViewPoint = other.mHasViewPoint;
mViewPoint = other.mViewPoint;
mActiveGrid = other.mActiveGrid;
}
void ViewData::add(QuadTreeNode *node)
@ -118,12 +119,12 @@ bool ViewData::Entry::set(QuadTreeNode *node)
}
}
bool suitable(ViewData* vd, const osg::Vec3f& viewPoint, float& maxDist)
bool suitable(ViewData* vd, const osg::Vec3f& viewPoint, float& maxDist, const osg::Vec4i& activeGrid)
{
return vd->hasViewPoint() && (vd->getViewPoint() - viewPoint).length2() < maxDist*maxDist;
return vd->hasViewPoint() && (vd->getViewPoint() - viewPoint).length2() < maxDist*maxDist && vd->getActiveGrid() == activeGrid;
}
ViewData *ViewDataMap::getViewData(osg::Object *viewer, const osg::Vec3f& viewPoint, bool& needsUpdate)
ViewData *ViewDataMap::getViewData(osg::Object *viewer, const osg::Vec3f& viewPoint, const osg::Vec4i &activeGrid, bool& needsUpdate)
{
Map::const_iterator found = mViews.find(viewer);
ViewData* vd = nullptr;
@ -135,11 +136,11 @@ ViewData *ViewDataMap::getViewData(osg::Object *viewer, const osg::Vec3f& viewPo
else
vd = found->second;
if (!suitable(vd, viewPoint, mReuseDistance))
if (!suitable(vd, viewPoint, mReuseDistance, activeGrid))
{
for (Map::const_iterator other = mViews.begin(); other != mViews.end(); ++other)
{
if (suitable(other->second, viewPoint, mReuseDistance) && other->second->getNumEntries())
if (suitable(other->second, viewPoint, mReuseDistance, activeGrid) && other->second->getNumEntries())
{
vd->copyFrom(*other->second);
needsUpdate = false;
@ -147,6 +148,7 @@ ViewData *ViewDataMap::getViewData(osg::Object *viewer, const osg::Vec3f& viewPo
}
}
vd->setViewPoint(viewPoint);
vd->setActiveGrid(activeGrid);
needsUpdate = true;
}
else

@ -57,6 +57,9 @@ namespace Terrain
void setViewPoint(const osg::Vec3f& viewPoint);
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;}
private:
std::vector<Entry> mEntries;
unsigned int mNumEntries;
@ -64,6 +67,7 @@ namespace Terrain
bool mChanged;
osg::Vec3f mViewPoint;
bool mHasViewPoint;
osg::Vec4i mActiveGrid;
};
class ViewDataMap : public osg::Referenced
@ -75,7 +79,7 @@ namespace Terrain
, mExpiryDelay(1.f)
{}
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();

@ -23,7 +23,6 @@ World::World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSyst
{
mTerrainRoot = new osg::Group;
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);
@ -48,6 +47,7 @@ World::World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSyst
mTextureManager.reset(new TextureManager(mResourceSystem->getSceneManager()));
mChunkManager.reset(new ChunkManager(mStorage, mResourceSystem->getSceneManager(), mTextureManager.get(), mCompositeMapRenderer));
mChunkManager->setNodeMask(nodeMask);
mCellBorder.reset(new CellBorder(this,mTerrainRoot.get(),borderMask));
mResourceSystem->addResourceManager(mChunkManager.get());

@ -147,7 +147,7 @@ namespace Terrain
/// @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) {}
/// Store a preloaded view into the cache with the intent that the next rendering traversal can use it.
/// @note Not thread safe.
@ -161,6 +161,8 @@ namespace Terrain
osg::Callback* getHeightCullCallback(float highz, unsigned int mask);
void setActiveGrid(const osg::Vec4i &grid) { mActiveGrid = grid; }
protected:
Storage* mStorage;
@ -181,6 +183,8 @@ namespace Terrain
std::set<std::pair<int,int>> mLoadedCells;
osg::ref_ptr<HeightCullCallback> mHeightCullCallback;
osg::Vec4i mActiveGrid;
};
}

@ -106,6 +106,15 @@ 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.
max composite geometry size = 4.0
# Load far objects on terrain
object paging = true
# Turn off to save memory but worse FPS
object paging merge geometry = true
# Cull objects smaller than this size divided by distance
object paging min size = 0.01
[Fog]
# If true, use extended fog parameters for distant terrain not controlled by

Loading…
Cancel
Save