mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-01-27 03:35:27 +00:00
c9c8d02332
This PR fixes a crash caused by the improperly ensured lifetime of RigGeometry::mSourceGeometry. mSourceGeometry was not adequate to ensure mSourceGeometry would outlive mGeometry because we extend mGeometrys lifetime beyond this lifetime by passing mGeometry to the draw traversal instead of this. In addition, We add important comments. We detect and prevent generally unsafe operations in high level code. We add a sprinkling of const to help clarify intentions.
854 lines
34 KiB
C++
854 lines
34 KiB
C++
#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/positionattitudetransform.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 static_cast<osg::Node*>(obj.get());
|
|
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:
|
|
bool isOperationPermissibleForObjectImplementation(const SceneUtil::Optimizer* optimizer, const osg::Drawable* node,unsigned int option) const override
|
|
{
|
|
return true;
|
|
}
|
|
bool isOperationPermissibleForObjectImplementation(const SceneUtil::Optimizer* optimizer, const osg::Node* node,unsigned int option) const override
|
|
{
|
|
return (node->getDataVariance() != osg::Object::DYNAMIC);
|
|
}
|
|
};
|
|
|
|
class CopyOp : public osg::CopyOp
|
|
{
|
|
public:
|
|
bool mOptimizeBillboards = true;
|
|
float mSqrDistance = 0.f;
|
|
osg::Vec3f mViewVector;
|
|
osg::Node::NodeMask mCopyMask = ~0u;
|
|
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)));
|
|
}
|
|
}
|
|
|
|
osg::Node* operator() (const osg::Node* node) const override
|
|
{
|
|
if (!(node->getNodeMask() & mCopyMask))
|
|
return nullptr;
|
|
|
|
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 pathNode : mNodePath)
|
|
if (const osg::Transform* t = pathNode->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);
|
|
}
|
|
osg::Drawable* operator() (const osg::Drawable* drawable) const override
|
|
{
|
|
if (!(drawable->getNodeMask() & mCopyMask))
|
|
return nullptr;
|
|
|
|
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);
|
|
}
|
|
osg::Callback* operator() (const osg::Callback* callback) const override
|
|
{
|
|
return nullptr;
|
|
}
|
|
};
|
|
|
|
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::Node::NodeMask analyzeMask)
|
|
: osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
|
|
, mCurrentStateSet(nullptr)
|
|
, mCurrentDistance(0.f)
|
|
{ setTraversalMask(analyzeMask); }
|
|
|
|
typedef std::unordered_map<osg::StateSet*, unsigned int> StateSetCounter;
|
|
struct Result
|
|
{
|
|
StateSetCounter mStateSetCounter;
|
|
unsigned int mNumVerts = 0;
|
|
};
|
|
|
|
void apply(osg::Node& node) override
|
|
{
|
|
if (node.getStateSet())
|
|
mCurrentStateSet = node.getStateSet();
|
|
|
|
if (osg::Switch* sw = node.asSwitch())
|
|
{
|
|
for (unsigned int i=0; i<sw->getNumChildren(); ++i)
|
|
if (sw->getValue(i))
|
|
traverse(*sw->getChild(i));
|
|
return;
|
|
}
|
|
if (osg::LOD* lod = dynamic_cast<osg::LOD*>(&node))
|
|
{
|
|
for (unsigned int i=0; i<lod->getNumChildren(); ++i)
|
|
if (lod->getMinRange(i) * lod->getMinRange(i) <= mCurrentDistance && mCurrentDistance < lod->getMaxRange(i) * lod->getMaxRange(i))
|
|
traverse(*lod->getChild(i));
|
|
return;
|
|
}
|
|
|
|
traverse(node);
|
|
}
|
|
void apply(osg::Geometry& geom) override
|
|
{
|
|
if (osg::Array* array = geom.getVertexArray())
|
|
mResult.mNumVerts += array->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;
|
|
float mCurrentDistance;
|
|
};
|
|
|
|
class DebugVisitor : public osg::NodeVisitor
|
|
{
|
|
public:
|
|
DebugVisitor() : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) {}
|
|
void apply(osg::Drawable& node) override
|
|
{
|
|
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));
|
|
stateset->addUniform(new osg::Uniform("emissiveMult", 1.f));
|
|
node.setStateSet(stateset);
|
|
}
|
|
};
|
|
|
|
class AddRefnumMarkerVisitor : public osg::NodeVisitor
|
|
{
|
|
public:
|
|
AddRefnumMarkerVisitor(const ESM::RefNum &refnum) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN), mRefnum(refnum) {}
|
|
ESM::RefNum mRefnum;
|
|
void apply(osg::Geometry &node) override
|
|
{
|
|
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("debug chunks", "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[i].index;
|
|
if (esm.size()<=index)
|
|
esm.resize(index+1);
|
|
cell->restore(esm[index], i);
|
|
ESM::CellRef ref;
|
|
ref.mRefNum.unset();
|
|
ESM::MovedCellRef cMRef;
|
|
cMRef.mRefNum.mIndex = 0;
|
|
bool deleted = false;
|
|
bool moved = false;
|
|
while(cell->getNextRef(esm[index], ref, deleted, cMRef, moved))
|
|
{
|
|
if (moved)
|
|
continue;
|
|
|
|
if (std::find(cell->mMovedRefs.begin(), cell->mMovedRefs.end(), ref.mRefNum) != cell->mMovedRefs.end()) continue;
|
|
Misc::StringUtils::lowerCaseInPlace(ref.mRefID);
|
|
int type = store.findStatic(ref.mRefID);
|
|
if (!typeFilter(type,size>=2)) continue;
|
|
if (deleted) { refs.erase(ref.mRefNum); continue; }
|
|
if (ref.mRefNum.fromGroundcoverFile()) continue;
|
|
refs[ref.mRefNum] = std::move(ref);
|
|
}
|
|
}
|
|
catch (std::exception&)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
for (auto [ref, deleted] : cell->mLeasedRefs)
|
|
{
|
|
if (deleted) { refs.erase(ref.mRefNum); continue; }
|
|
Misc::StringUtils::lowerCaseInPlace(ref.mRefID);
|
|
int type = store.findStatic(ref.mRefID);
|
|
if (!typeFilter(type,size>=2)) continue;
|
|
refs[ref.mRefNum] = std::move(ref);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (activeGrid)
|
|
{
|
|
std::lock_guard<std::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;
|
|
|
|
// Mask_UpdateVisitor is used in such cases in NIF loader:
|
|
// 1. For collision nodes, which is not supposed to be rendered.
|
|
// 2. For nodes masked via Flag_Hidden (VisController can change this flag value at runtime).
|
|
// Since ObjectPaging does not handle VisController, we can just ignore both types of nodes.
|
|
constexpr auto copyMask = ~Mask_UpdateVisitor;
|
|
|
|
AnalyzeVisitor analyzeVisitor(copyMask);
|
|
analyzeVisitor.mCurrentDistance = (viewPoint - worldCenter).length2();
|
|
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()) || (maxBound.y() < std::ceil(maxBound.y()) && cellPos.y() >= maxBound.y()))
|
|
continue;
|
|
}
|
|
|
|
float dSqr = (viewPoint - pos).length2();
|
|
if (!activeGrid)
|
|
{
|
|
std::lock_guard<std::mutex> lock(mSizeCacheMutex);
|
|
SizeCache::iterator found = mSizeCache.find(pair.first);
|
|
if (found != mSizeCache.end() && found->second < dSqr*minSize*minSize)
|
|
continue;
|
|
}
|
|
|
|
if (Misc::ResourceHelpers::isHiddenMarker(ref.mRefID))
|
|
continue;
|
|
|
|
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);
|
|
}
|
|
|
|
{
|
|
std::lock_guard<std::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)
|
|
{
|
|
std::lock_guard<std::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<Resource::TemplateMultiRef> templateRefs = new Resource::TemplateMultiRef;
|
|
osgUtil::StateToCompile stateToCompile(0, nullptr);
|
|
CopyOp copyop;
|
|
copyop.mCopyMask = copyMask;
|
|
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::Vec3f nodePos = pos - worldCenter;
|
|
osg::Quat nodeAttitude = 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));
|
|
osg::Vec3f nodeScale = osg::Vec3f(ref.mScale, ref.mScale, ref.mScale);
|
|
|
|
osg::ref_ptr<osg::Group> trans;
|
|
if (merge)
|
|
{
|
|
// Optimizer currently supports only MatrixTransforms.
|
|
osg::Matrixf matrix;
|
|
matrix.preMultTranslate(nodePos);
|
|
matrix.preMultRotate(nodeAttitude);
|
|
matrix.preMultScale(nodeScale);
|
|
trans = new osg::MatrixTransform(matrix);
|
|
trans->setDataVariance(osg::Object::STATIC);
|
|
}
|
|
else
|
|
{
|
|
trans = new SceneUtil::PositionAttitudeTransform;
|
|
SceneUtil::PositionAttitudeTransform* pat = static_cast<SceneUtil::PositionAttitudeTransform*>(trans.get());
|
|
pat->setPosition(nodePos);
|
|
pat->setScale(nodeScale);
|
|
pat->setAttitude(nodeAttitude);
|
|
}
|
|
|
|
// DO NOT COPY AND PASTE THIS CODE. Cloning osg::Geometry without also cloning its contained Arrays is generally unsafe.
|
|
// In this specific case the operation is safe under the following two assumptions:
|
|
// - When Arrays are removed or replaced in the cloned geometry, the original Arrays in their place must outlive the cloned geometry regardless. (ensured by TemplateMultiRef)
|
|
// - Arrays that we add or replace in the cloned geometry must be explicitely forbidden from reusing BufferObjects of the original geometry. (ensured by needvbo() in optimizer.cpp)
|
|
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);
|
|
copyop.mNodePath.pop_back();
|
|
|
|
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 help verify the safety of shallow cloning operations
|
|
// in addition, we hint to the cache that it's still being used and should be kept in cache
|
|
templateRefs->addRef(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;
|
|
clampToCell(pos);
|
|
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;
|
|
}
|
|
void clampToCell(osg::Vec3f& cellPos)
|
|
{
|
|
osg::Vec2i min (mCell.x(), mCell.y());
|
|
osg::Vec2i max (mCell.x()+1, mCell.y()+1);
|
|
if (cellPos.x() < min.x()) cellPos.x() = min.x();
|
|
if (cellPos.x() > max.x()) cellPos.x() = max.x();
|
|
if (cellPos.y() < min.y()) cellPos.y() = min.y();
|
|
if (cellPos.y() > max.y()) cellPos.y() = max.y();
|
|
}
|
|
osg::Vec3f mPosition;
|
|
osg::Vec2i mCell;
|
|
std::set<MWRender::ChunkId> mToClear;
|
|
bool mActiveGridOnly = false;
|
|
};
|
|
|
|
bool ObjectPaging::enableObject(int type, const ESM::RefNum & refnum, const osg::Vec3f& pos, const osg::Vec2i& cell, bool enabled)
|
|
{
|
|
if (!typeFilter(type, false))
|
|
return false;
|
|
|
|
{
|
|
std::lock_guard<std::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;
|
|
ccf.mCell = cell;
|
|
mCache->call(ccf);
|
|
if (ccf.mToClear.empty()) return false;
|
|
for (const auto& chunk : ccf.mToClear)
|
|
mCache->removeFromObjectCache(chunk);
|
|
return true;
|
|
}
|
|
|
|
bool ObjectPaging::blacklistObject(int type, const ESM::RefNum & refnum, const osg::Vec3f& pos, const osg::Vec2i& cell)
|
|
{
|
|
if (!typeFilter(type, false))
|
|
return false;
|
|
|
|
{
|
|
std::lock_guard<std::mutex> lock(mRefTrackerMutex);
|
|
if (!getWritableRefTracker().mBlacklist.insert(refnum).second) return false;
|
|
if (mRefTrackerLocked) return false;
|
|
}
|
|
|
|
ClearCacheFunctor ccf;
|
|
ccf.mPosition = pos;
|
|
ccf.mCell = cell;
|
|
ccf.mActiveGridOnly = true;
|
|
mCache->call(ccf);
|
|
if (ccf.mToClear.empty()) return false;
|
|
for (const auto& chunk : ccf.mToClear)
|
|
mCache->removeFromObjectCache(chunk);
|
|
return true;
|
|
}
|
|
|
|
|
|
void ObjectPaging::clear()
|
|
{
|
|
std::lock_guard<std::mutex> lock(mRefTrackerMutex);
|
|
mRefTrackerNew.mDisabled.clear();
|
|
mRefTrackerNew.mBlacklist.clear();
|
|
mRefTrackerLocked = true;
|
|
}
|
|
|
|
bool ObjectPaging::unlockCache()
|
|
{
|
|
if (!mRefTrackerLocked) return false;
|
|
{
|
|
std::lock_guard<std::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());
|
|
}
|
|
|
|
}
|