|
|
|
@ -1,5 +1,7 @@
|
|
|
|
|
#include "objectpaging.hpp"
|
|
|
|
|
|
|
|
|
|
#include <unordered_map>
|
|
|
|
|
|
|
|
|
|
#include <osg/Version>
|
|
|
|
|
#include <osg/LOD>
|
|
|
|
|
#include <osg/Switch>
|
|
|
|
@ -14,7 +16,6 @@
|
|
|
|
|
|
|
|
|
|
#include <osgParticle/ParticleProcessor>
|
|
|
|
|
#include <osgParticle/ParticleSystemUpdater>
|
|
|
|
|
#include <osgParticle/Emitter>
|
|
|
|
|
|
|
|
|
|
#include <components/sceneutil/morphgeometry.hpp>
|
|
|
|
|
#include <components/sceneutil/riggeometry.hpp>
|
|
|
|
@ -60,7 +61,7 @@ namespace MWRender
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
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, bool compile)
|
|
|
|
|
{
|
|
|
|
|
if (!far)return nullptr;
|
|
|
|
|
ChunkId id = std::make_tuple(center, size);
|
|
|
|
@ -70,7 +71,7 @@ namespace MWRender
|
|
|
|
|
return obj->asNode();
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
osg::ref_ptr<osg::Node> node = createChunk(size, center, viewPoint);
|
|
|
|
|
osg::ref_ptr<osg::Node> node = createChunk(size, center, viewPoint, compile);
|
|
|
|
|
mCache->addEntryToObjectCache(id, node.get());
|
|
|
|
|
return node;
|
|
|
|
|
}
|
|
|
|
@ -92,12 +93,11 @@ namespace MWRender
|
|
|
|
|
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
|
|
|
|
|
);
|
|
|
|
|
CopyOp(bool deep) : mDistance(0.f) {
|
|
|
|
|
unsigned int flags = osg::CopyOp::DEEP_COPY_NODES;
|
|
|
|
|
if (deep)
|
|
|
|
|
flags |= osg::CopyOp::DEEP_COPY_DRAWABLES;
|
|
|
|
|
setCopyFlags(flags);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float mDistance;
|
|
|
|
@ -128,12 +128,13 @@ namespace MWRender
|
|
|
|
|
return n;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
osg::Node* n = osg::CopyOp::operator()(node);
|
|
|
|
|
if (n) {
|
|
|
|
|
n->setDataVariance(osg::Object::STATIC);
|
|
|
|
|
n->setUserDataContainer(nullptr);
|
|
|
|
|
n->setName("");
|
|
|
|
|
}
|
|
|
|
|
if (const osg::Drawable* d = node->asDrawable())
|
|
|
|
|
return operator()(d);
|
|
|
|
|
|
|
|
|
|
osg::Node* n = osg::clone(node, *this);
|
|
|
|
|
n->setDataVariance(osg::Object::STATIC);
|
|
|
|
|
n->setUserDataContainer(nullptr);
|
|
|
|
|
n->setName("");
|
|
|
|
|
return n;
|
|
|
|
|
}
|
|
|
|
|
virtual osg::Drawable* operator() (const osg::Drawable* drawable) const
|
|
|
|
@ -142,11 +143,20 @@ namespace MWRender
|
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
|
|
if (const SceneUtil::RigGeometry* rig = dynamic_cast<const SceneUtil::RigGeometry*>(drawable))
|
|
|
|
|
return osg::CopyOp::operator()(rig->getSourceGeometry());
|
|
|
|
|
return operator()(rig->getSourceGeometry());
|
|
|
|
|
if (const SceneUtil::MorphGeometry* morph = dynamic_cast<const SceneUtil::MorphGeometry*>(drawable))
|
|
|
|
|
return osg::CopyOp::operator()(morph->getSourceGeometry());
|
|
|
|
|
return operator()(morph->getSourceGeometry());
|
|
|
|
|
|
|
|
|
|
return osg::CopyOp::operator()(drawable);
|
|
|
|
|
if (getCopyFlags() & DEEP_COPY_DRAWABLES)
|
|
|
|
|
{
|
|
|
|
|
osg::Drawable* d = osg::clone(drawable, *this);
|
|
|
|
|
d->setDataVariance(osg::Object::STATIC);
|
|
|
|
|
d->setUserDataContainer(nullptr);
|
|
|
|
|
d->setName("");
|
|
|
|
|
return d;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
return osg::CopyOp::operator()(drawable);
|
|
|
|
|
}
|
|
|
|
|
virtual osg::Callback* operator() (const osg::Callback* callback) const
|
|
|
|
|
{
|
|
|
|
@ -154,20 +164,77 @@ namespace MWRender
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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 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;
|
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
ObjectPaging::ObjectPaging(Resource::SceneManager* sceneManager)
|
|
|
|
|
: GenericResourceManager<ChunkId>(nullptr)
|
|
|
|
|
, mSceneManager(sceneManager)
|
|
|
|
|
{
|
|
|
|
|
mMergeGeometry = Settings::Manager::getBool("object paging merge geometry", "Terrain");
|
|
|
|
|
mMergeFactor = Settings::Manager::getFloat("object paging merge factor", "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::ref_ptr<osg::Node> ObjectPaging::createChunk(float size, const osg::Vec2f& center, 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::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;
|
|
|
|
|
|
|
|
|
@ -226,21 +293,18 @@ namespace MWRender
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
};
|
|
|
|
|
typedef std::map<osg::ref_ptr<const osg::Node>, InstanceList> NodeMap;
|
|
|
|
|
NodeMap nodes;
|
|
|
|
|
AnalyzeVisitor analyzeVisitor;
|
|
|
|
|
|
|
|
|
|
for (const auto& pair : refs)
|
|
|
|
|
{
|
|
|
|
|
const ESM::CellRef& ref = pair.second;
|
|
|
|
|
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)
|
|
|
|
@ -254,50 +318,101 @@ namespace MWRender
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
osg::ref_ptr<const osg::Node> cnode = mSceneManager->getTemplate(model, false);
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
float d = (viewPoint - pos).length();
|
|
|
|
|
int type = store.findStatic(id);
|
|
|
|
|
std::string model = getModel(type, id, store);
|
|
|
|
|
if (model.empty()) continue;
|
|
|
|
|
model = "meshes/" + model;
|
|
|
|
|
/*
|
|
|
|
|
bool useAnim = type != ESM::REC_STAT;
|
|
|
|
|
if (useAnim)
|
|
|
|
|
model = Misc::ResourceHelpers::correctActorModelPath(model, mSceneManager->getVFS());
|
|
|
|
|
*/
|
|
|
|
|
osg::ref_ptr<const osg::Node> cnode = mSceneManager->getTemplate(model, compile);
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
auto emplaced = nodes.emplace(cnode, InstanceList());
|
|
|
|
|
if (emplaced.second)
|
|
|
|
|
{
|
|
|
|
|
const_cast<osg::Node*>(cnode.get())->accept(analyzeVisitor);
|
|
|
|
|
emplaced.first->second.mAnalyzeResult = analyzeVisitor.retrieveResult();
|
|
|
|
|
}
|
|
|
|
|
emplaced.first->second.mInstances.push_back(&ref);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (mMergeGeometry)
|
|
|
|
|
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;
|
|
|
|
|
for (const auto& pair : nodes)
|
|
|
|
|
{
|
|
|
|
|
const osg::Node* cnode = pair.first;
|
|
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
|
|
|
|
|
const AnalyzeVisitor::Result& analyzeResult = pair.second.mAnalyzeResult;
|
|
|
|
|
|
|
|
|
|
float mergeCost = analyzeResult.mNumVerts * size;
|
|
|
|
|
float mergeBenefit = analyzeVisitor.getMergeBenefit(analyzeResult) * mMergeFactor;
|
|
|
|
|
bool merge = mergeBenefit > mergeCost;
|
|
|
|
|
|
|
|
|
|
for (auto cref : pair.second.mInstances)
|
|
|
|
|
{
|
|
|
|
|
const ESM::CellRef& ref = *cref;
|
|
|
|
|
osg::Vec3f pos = ref.mPos.asVec3();
|
|
|
|
|
float d = (viewPoint - pos).length();
|
|
|
|
|
|
|
|
|
|
CopyOp co = CopyOp(merge);
|
|
|
|
|
co.mDistance = d;
|
|
|
|
|
osg::ref_ptr<osg::Node> node = osg::clone(cnode, 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);
|
|
|
|
|
|
|
|
|
|
if (merge)
|
|
|
|
|
mergeGroup->addChild(trans);
|
|
|
|
|
else
|
|
|
|
|
group->addChild(trans);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (mergeGroup->getNumChildren())
|
|
|
|
|
{
|
|
|
|
|
SceneUtil::Optimizer optimizer;
|
|
|
|
|
if ((relativeViewPoint - group->getBound().center()).length2() > group->getBound().radius2())
|
|
|
|
|
if ((relativeViewPoint - mergeGroup->getBound().center()).length2() > mergeGroup->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);
|
|
|
|
|
}
|
|
|
|
|
optimizer.optimize(mergeGroup, options);
|
|
|
|
|
|
|
|
|
|
group->addChild(mergeGroup);
|
|
|
|
|
|
|
|
|
|
auto ico = mSceneManager->getIncrementalCompileOperation();
|
|
|
|
|
if (ico) ico->add(group);
|
|
|
|
|
else group->getBound();
|
|
|
|
|
auto ico = mSceneManager->getIncrementalCompileOperation();
|
|
|
|
|
if (compile && ico) ico->add(mergeGroup);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
group->getBound();
|
|
|
|
|
group->setNodeMask(Mask_Static);
|
|
|
|
|
|
|
|
|
|
group->getOrCreateUserDataContainer()->addUserObject(templateRefs);
|
|
|
|
|
|
|
|
|
|
return group;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|