1 #include "objectpaging.hpp"
2 
3 #include <unordered_map>
4 
5 #include <osg/Version>
6 #include <osg/LOD>
7 #include <osg/Switch>
8 #include <osg/MatrixTransform>
9 #include <osg/Material>
10 #include <osgUtil/IncrementalCompileOperation>
11 
12 #include <components/esm/esmreader.hpp>
13 #include <components/misc/resourcehelpers.hpp>
14 #include <components/resource/scenemanager.hpp>
15 #include <components/sceneutil/optimizer.hpp>
16 #include <components/sceneutil/clone.hpp>
17 #include <components/sceneutil/util.hpp>
18 #include <components/vfs/manager.hpp>
19 
20 #include <osgParticle/ParticleProcessor>
21 #include <osgParticle/ParticleSystemUpdater>
22 
23 #include <components/sceneutil/lightmanager.hpp>
24 #include <components/sceneutil/morphgeometry.hpp>
25 #include <components/sceneutil/riggeometry.hpp>
26 #include <components/settings/settings.hpp>
27 #include <components/misc/rng.hpp>
28 
29 #include "apps/openmw/mwworld/esmstore.hpp"
30 #include "apps/openmw/mwbase/environment.hpp"
31 #include "apps/openmw/mwbase/world.hpp"
32 
33 #include "vismask.hpp"
34 
35 namespace MWRender
36 {
37 
typeFilter(int type,bool far)38     bool typeFilter(int type, bool far)
39     {
40         switch (type)
41         {
42           case ESM::REC_STAT:
43           case ESM::REC_ACTI:
44           case ESM::REC_DOOR:
45             return true;
46           case ESM::REC_CONT:
47             return !far;
48 
49         default:
50             return false;
51         }
52     }
53 
getModel(int type,const std::string & id,const MWWorld::ESMStore & store)54     std::string getModel(int type, const std::string& id, const MWWorld::ESMStore& store)
55     {
56         switch (type)
57         {
58           case ESM::REC_STAT:
59             return store.get<ESM::Static>().searchStatic(id)->mModel;
60           case ESM::REC_ACTI:
61             return store.get<ESM::Activator>().searchStatic(id)->mModel;
62           case ESM::REC_DOOR:
63             return store.get<ESM::Door>().searchStatic(id)->mModel;
64           case ESM::REC_CONT:
65             return store.get<ESM::Container>().searchStatic(id)->mModel;
66           default:
67             return std::string();
68         }
69     }
70 
getChunk(float size,const osg::Vec2f & center,unsigned char lod,unsigned int lodFlags,bool activeGrid,const osg::Vec3f & viewPoint,bool compile)71     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)
72     {
73         if (activeGrid && !mActiveGrid)
74             return nullptr;
75 
76         ChunkId id = std::make_tuple(center, size, activeGrid);
77 
78         osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(id);
79         if (obj)
80             return obj->asNode();
81         else
82         {
83             osg::ref_ptr<osg::Node> node = createChunk(size, center, activeGrid, viewPoint, compile);
84             mCache->addEntryToObjectCache(id, node.get());
85             return node;
86         }
87     }
88 
89     class CanOptimizeCallback : public SceneUtil::Optimizer::IsOperationPermissibleForObjectCallback
90     {
91     public:
isOperationPermissibleForObjectImplementation(const SceneUtil::Optimizer * optimizer,const osg::Drawable * node,unsigned int option) const92         bool isOperationPermissibleForObjectImplementation(const SceneUtil::Optimizer* optimizer, const osg::Drawable* node,unsigned int option) const override
93         {
94             return true;
95         }
isOperationPermissibleForObjectImplementation(const SceneUtil::Optimizer * optimizer,const osg::Node * node,unsigned int option) const96         bool isOperationPermissibleForObjectImplementation(const SceneUtil::Optimizer* optimizer, const osg::Node* node,unsigned int option) const override
97         {
98             return (node->getDataVariance() != osg::Object::DYNAMIC);
99         }
100     };
101 
102     class CopyOp : public osg::CopyOp
103     {
104     public:
105         bool mOptimizeBillboards = true;
106         float mSqrDistance = 0.f;
107         osg::Vec3f mViewVector;
108         osg::Node::NodeMask mCopyMask = ~0u;
109         mutable std::vector<const osg::Node*> mNodePath;
110 
copy(const osg::Node * toCopy,osg::Group * attachTo)111         void copy(const osg::Node* toCopy, osg::Group* attachTo)
112         {
113             const osg::Group* groupToCopy = toCopy->asGroup();
114             if (toCopy->getStateSet() || toCopy->asTransform() || !groupToCopy)
115                 attachTo->addChild(operator()(toCopy));
116             else
117             {
118                 for (unsigned int i=0; i<groupToCopy->getNumChildren(); ++i)
119                     attachTo->addChild(operator()(groupToCopy->getChild(i)));
120             }
121         }
122 
operator ()(const osg::Node * node) const123         osg::Node* operator() (const osg::Node* node) const override
124         {
125             if (!(node->getNodeMask() & mCopyMask))
126                 return nullptr;
127 
128             if (const osg::Drawable* d = node->asDrawable())
129                 return operator()(d);
130 
131             if (dynamic_cast<const osgParticle::ParticleProcessor*>(node))
132                 return nullptr;
133             if (dynamic_cast<const osgParticle::ParticleSystemUpdater*>(node))
134                 return nullptr;
135 
136             if (const osg::Switch* sw = node->asSwitch())
137             {
138                 osg::Group* n = new osg::Group;
139                 for (unsigned int i=0; i<sw->getNumChildren(); ++i)
140                     if (sw->getValue(i))
141                         n->addChild(operator()(sw->getChild(i)));
142                 n->setDataVariance(osg::Object::STATIC);
143                 return n;
144             }
145             if (const osg::LOD* lod = dynamic_cast<const osg::LOD*>(node))
146             {
147                 osg::Group* n = new osg::Group;
148                 for (unsigned int i=0; i<lod->getNumChildren(); ++i)
149                     if (lod->getMinRange(i) * lod->getMinRange(i) <= mSqrDistance && mSqrDistance < lod->getMaxRange(i) * lod->getMaxRange(i))
150                         n->addChild(operator()(lod->getChild(i)));
151                 n->setDataVariance(osg::Object::STATIC);
152                 return n;
153             }
154 
155             mNodePath.push_back(node);
156 
157             osg::Node* cloned = static_cast<osg::Node*>(node->clone(*this));
158             cloned->setDataVariance(osg::Object::STATIC);
159             cloned->setUserDataContainer(nullptr);
160             cloned->setName("");
161 
162             mNodePath.pop_back();
163 
164             handleCallbacks(node, cloned);
165 
166             return cloned;
167         }
handleCallbacks(const osg::Node * node,osg::Node * cloned) const168         void handleCallbacks(const osg::Node* node, osg::Node *cloned) const
169         {
170             for (const osg::Callback* callback = node->getCullCallback(); callback != nullptr; callback = callback->getNestedCallback())
171             {
172                 if (callback->className() == std::string("BillboardCallback"))
173                 {
174                     if (mOptimizeBillboards)
175                     {
176                         handleBillboard(cloned);
177                         continue;
178                     }
179                     else
180                         cloned->setDataVariance(osg::Object::DYNAMIC);
181                 }
182 
183                 if (node->getCullCallback()->getNestedCallback())
184                 {
185                     osg::Callback *clonedCallback = osg::clone(callback, osg::CopyOp::SHALLOW_COPY);
186                     clonedCallback->setNestedCallback(nullptr);
187                     cloned->addCullCallback(clonedCallback);
188                 }
189                 else
190                     cloned->addCullCallback(const_cast<osg::Callback*>(callback));
191             }
192         }
handleBillboard(osg::Node * node) const193         void handleBillboard(osg::Node* node) const
194         {
195             osg::Transform* transform = node->asTransform();
196             if (!transform) return;
197             osg::MatrixTransform* matrixTransform = transform->asMatrixTransform();
198             if (!matrixTransform) return;
199 
200             osg::Matrix worldToLocal = osg::Matrix::identity();
201             for (auto pathNode : mNodePath)
202                 if (const osg::Transform* t = pathNode->asTransform())
203                     t->computeWorldToLocalMatrix(worldToLocal, nullptr);
204             worldToLocal = osg::Matrix::orthoNormal(worldToLocal);
205 
206             osg::Matrix billboardMatrix;
207             osg::Vec3f viewVector = -(mViewVector + worldToLocal.getTrans());
208             viewVector.normalize();
209             osg::Vec3f right = viewVector ^ osg::Vec3f(0,0,1);
210             right.normalize();
211             osg::Vec3f up = right ^ viewVector;
212             up.normalize();
213             billboardMatrix.makeLookAt(osg::Vec3f(0,0,0), viewVector, up);
214             billboardMatrix.invert(billboardMatrix);
215 
216             const osg::Matrix& oldMatrix = matrixTransform->getMatrix();
217             float mag[3]; // attempt to preserve scale
218             for (int i=0;i<3;++i)
219                 mag[i] = std::sqrt(oldMatrix(0,i) * oldMatrix(0,i) + oldMatrix(1,i) * oldMatrix(1,i) + oldMatrix(2,i) * oldMatrix(2,i));
220             osg::Matrix newMatrix;
221             worldToLocal.setTrans(0,0,0);
222             newMatrix *= worldToLocal;
223             newMatrix.preMult(billboardMatrix);
224             newMatrix.preMultScale(osg::Vec3f(mag[0], mag[1], mag[2]));
225             newMatrix.setTrans(oldMatrix.getTrans());
226 
227             matrixTransform->setMatrix(newMatrix);
228         }
operator ()(const osg::Drawable * drawable) const229         osg::Drawable* operator() (const osg::Drawable* drawable) const override
230         {
231             if (!(drawable->getNodeMask() & mCopyMask))
232                 return nullptr;
233 
234             if (dynamic_cast<const osgParticle::ParticleSystem*>(drawable))
235                 return nullptr;
236 
237             if (const SceneUtil::RigGeometry* rig = dynamic_cast<const SceneUtil::RigGeometry*>(drawable))
238                 return operator()(rig->getSourceGeometry());
239             if (const SceneUtil::MorphGeometry* morph = dynamic_cast<const SceneUtil::MorphGeometry*>(drawable))
240                 return operator()(morph->getSourceGeometry());
241 
242             if (getCopyFlags() & DEEP_COPY_DRAWABLES)
243             {
244                 osg::Drawable* d = static_cast<osg::Drawable*>(drawable->clone(*this));
245                 d->setDataVariance(osg::Object::STATIC);
246                 d->setUserDataContainer(nullptr);
247                 d->setName("");
248                 return d;
249             }
250             else
251                 return const_cast<osg::Drawable*>(drawable);
252         }
operator ()(const osg::Callback * callback) const253         osg::Callback* operator() (const osg::Callback* callback) const override
254         {
255             return nullptr;
256         }
257     };
258 
259     class RefnumSet : public osg::Object
260     {
261     public:
RefnumSet()262         RefnumSet(){}
RefnumSet(const RefnumSet & copy,const osg::CopyOp &)263         RefnumSet(const RefnumSet& copy, const osg::CopyOp&) : mRefnums(copy.mRefnums) {}
264         META_Object(MWRender, RefnumSet)
265         std::set<ESM::RefNum> mRefnums;
266     };
267 
268     class AnalyzeVisitor : public osg::NodeVisitor
269     {
270     public:
AnalyzeVisitor(osg::Node::NodeMask analyzeMask)271         AnalyzeVisitor(osg::Node::NodeMask analyzeMask)
272          : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
273          , mCurrentStateSet(nullptr)
274          , mCurrentDistance(0.f)
275          , mAnalyzeMask(analyzeMask) {}
276 
277         typedef std::unordered_map<osg::StateSet*, unsigned int> StateSetCounter;
278         struct Result
279         {
280             StateSetCounter mStateSetCounter;
281             unsigned int mNumVerts = 0;
282         };
283 
apply(osg::Node & node)284         void apply(osg::Node& node) override
285         {
286             if (!(node.getNodeMask() & mAnalyzeMask))
287                 return;
288 
289             if (node.getStateSet())
290                 mCurrentStateSet = node.getStateSet();
291 
292             if (osg::Switch* sw = node.asSwitch())
293             {
294                 for (unsigned int i=0; i<sw->getNumChildren(); ++i)
295                     if (sw->getValue(i))
296                         traverse(*sw->getChild(i));
297                 return;
298             }
299             if (osg::LOD* lod = dynamic_cast<osg::LOD*>(&node))
300             {
301                 for (unsigned int i=0; i<lod->getNumChildren(); ++i)
302                     if (lod->getMinRange(i) * lod->getMinRange(i) <= mCurrentDistance && mCurrentDistance < lod->getMaxRange(i) * lod->getMaxRange(i))
303                         traverse(*lod->getChild(i));
304                 return;
305             }
306 
307             traverse(node);
308         }
apply(osg::Geometry & geom)309         void apply(osg::Geometry& geom) override
310         {
311             if (!(geom.getNodeMask() & mAnalyzeMask))
312                 return;
313 
314             if (osg::Array* array = geom.getVertexArray())
315                 mResult.mNumVerts += array->getNumElements();
316 
317             ++mResult.mStateSetCounter[mCurrentStateSet];
318             ++mGlobalStateSetCounter[mCurrentStateSet];
319         }
retrieveResult()320         Result retrieveResult()
321         {
322             Result result = mResult;
323             mResult = Result();
324             mCurrentStateSet = nullptr;
325             return result;
326         }
addInstance(const Result & result)327         void addInstance(const Result& result)
328         {
329             for (auto pair : result.mStateSetCounter)
330                 mGlobalStateSetCounter[pair.first] += pair.second;
331         }
getMergeBenefit(const Result & result)332         float getMergeBenefit(const Result& result)
333         {
334             if (result.mStateSetCounter.empty()) return 1;
335             float mergeBenefit = 0;
336             for (auto pair : result.mStateSetCounter)
337             {
338                 mergeBenefit += mGlobalStateSetCounter[pair.first];
339             }
340             mergeBenefit /= result.mStateSetCounter.size();
341             return mergeBenefit;
342         }
343 
344         Result mResult;
345         osg::StateSet* mCurrentStateSet;
346         StateSetCounter mGlobalStateSetCounter;
347         float mCurrentDistance;
348         osg::Node::NodeMask mAnalyzeMask;
349     };
350 
351     class DebugVisitor : public osg::NodeVisitor
352     {
353     public:
DebugVisitor()354         DebugVisitor() : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) {}
apply(osg::Drawable & node)355         void apply(osg::Drawable& node) override
356         {
357             osg::ref_ptr<osg::Material> m (new osg::Material);
358             osg::Vec4f color(Misc::Rng::rollProbability(), Misc::Rng::rollProbability(), Misc::Rng::rollProbability(), 0.f);
359             color.normalize();
360             m->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.1f,0.1f,0.1f,1.f));
361             m->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.1f,0.1f,0.1f,1.f));
362             m->setColorMode(osg::Material::OFF);
363             m->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(color));
364             osg::ref_ptr<osg::StateSet> stateset = node.getStateSet() ? osg::clone(node.getStateSet(), osg::CopyOp::SHALLOW_COPY) : new osg::StateSet;
365             stateset->setAttribute(m);
366             stateset->addUniform(new osg::Uniform("colorMode", 0));
367             stateset->addUniform(new osg::Uniform("emissiveMult", 1.f));
368             node.setStateSet(stateset);
369         }
370     };
371 
372     class AddRefnumMarkerVisitor : public osg::NodeVisitor
373     {
374     public:
AddRefnumMarkerVisitor(const ESM::RefNum & refnum)375         AddRefnumMarkerVisitor(const ESM::RefNum &refnum) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN), mRefnum(refnum) {}
376         ESM::RefNum mRefnum;
apply(osg::Geometry & node)377         void apply(osg::Geometry &node) override
378         {
379             osg::ref_ptr<RefnumMarker> marker (new RefnumMarker);
380             marker->mRefnum = mRefnum;
381             if (osg::Array* array = node.getVertexArray())
382                 marker->mNumVertices = array->getNumElements();
383             node.getOrCreateUserDataContainer()->addUserObject(marker);
384         }
385     };
386 
ObjectPaging(Resource::SceneManager * sceneManager)387     ObjectPaging::ObjectPaging(Resource::SceneManager* sceneManager)
388             : GenericResourceManager<ChunkId>(nullptr)
389          , mSceneManager(sceneManager)
390          , mRefTrackerLocked(false)
391     {
392         mActiveGrid = Settings::Manager::getBool("object paging active grid", "Terrain");
393         mDebugBatches = Settings::Manager::getBool("object paging debug batches", "Terrain");
394         mMergeFactor = Settings::Manager::getFloat("object paging merge factor", "Terrain");
395         mMinSize = Settings::Manager::getFloat("object paging min size", "Terrain");
396         mMinSizeMergeFactor = Settings::Manager::getFloat("object paging min size merge factor", "Terrain");
397         mMinSizeCostMultiplier = Settings::Manager::getFloat("object paging min size cost multiplier", "Terrain");
398     }
399 
createChunk(float size,const osg::Vec2f & center,bool activeGrid,const osg::Vec3f & viewPoint,bool compile)400     osg::ref_ptr<osg::Node> ObjectPaging::createChunk(float size, const osg::Vec2f& center, bool activeGrid, const osg::Vec3f& viewPoint, bool compile)
401     {
402         osg::Vec2i startCell = osg::Vec2i(std::floor(center.x() - size/2.f), std::floor(center.y() - size/2.f));
403 
404         osg::Vec3f worldCenter = osg::Vec3f(center.x(), center.y(), 0)*ESM::Land::REAL_SIZE;
405         osg::Vec3f relativeViewPoint = viewPoint - worldCenter;
406 
407         std::map<ESM::RefNum, ESM::CellRef> refs;
408         std::vector<ESM::ESMReader> esm;
409         const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
410 
411         for (int cellX = startCell.x(); cellX < startCell.x() + size; ++cellX)
412         {
413             for (int cellY = startCell.y(); cellY < startCell.y() + size; ++cellY)
414             {
415                 const ESM::Cell* cell = store.get<ESM::Cell>().searchStatic(cellX, cellY);
416                 if (!cell) continue;
417                 for (size_t i=0; i<cell->mContextList.size(); ++i)
418                 {
419                     try
420                     {
421                         unsigned int index = cell->mContextList[i].index;
422                         if (esm.size()<=index)
423                             esm.resize(index+1);
424                         cell->restore(esm[index], i);
425                         ESM::CellRef ref;
426                         ref.mRefNum.mContentFile = ESM::RefNum::RefNum_NoContentFile;
427                         bool deleted = false;
428                         while(cell->getNextRef(esm[index], ref, deleted))
429                         {
430                             if (std::find(cell->mMovedRefs.begin(), cell->mMovedRefs.end(), ref.mRefNum) != cell->mMovedRefs.end()) continue;
431                             Misc::StringUtils::lowerCaseInPlace(ref.mRefID);
432                             int type = store.findStatic(ref.mRefID);
433                             if (!typeFilter(type,size>=2)) continue;
434                             if (deleted) { refs.erase(ref.mRefNum); continue; }
435                             if (ref.mRefNum.fromGroundcoverFile()) continue;
436                             refs[ref.mRefNum] = std::move(ref);
437                         }
438                     }
439                     catch (std::exception&)
440                     {
441                         continue;
442                     }
443                 }
444                 for (auto [ref, deleted] : cell->mLeasedRefs)
445                 {
446                     if (deleted) { refs.erase(ref.mRefNum); continue; }
447                     Misc::StringUtils::lowerCaseInPlace(ref.mRefID);
448                     int type = store.findStatic(ref.mRefID);
449                     if (!typeFilter(type,size>=2)) continue;
450                     refs[ref.mRefNum] = std::move(ref);
451                 }
452             }
453         }
454 
455         if (activeGrid)
456         {
457             std::lock_guard<std::mutex> lock(mRefTrackerMutex);
458             for (auto ref : getRefTracker().mBlacklist)
459                 refs.erase(ref);
460         }
461 
462         osg::Vec2f minBound = (center - osg::Vec2f(size/2.f, size/2.f));
463         osg::Vec2f maxBound = (center + osg::Vec2f(size/2.f, size/2.f));
464         struct InstanceList
465         {
466             std::vector<const ESM::CellRef*> mInstances;
467             AnalyzeVisitor::Result mAnalyzeResult;
468             bool mNeedCompile = false;
469         };
470         typedef std::map<osg::ref_ptr<const osg::Node>, InstanceList> NodeMap;
471         NodeMap nodes;
472         osg::ref_ptr<RefnumSet> refnumSet = activeGrid ? new RefnumSet : nullptr;
473 
474         // Mask_UpdateVisitor is used in such cases in NIF loader:
475         // 1. For collision nodes, which is not supposed to be rendered.
476         // 2. For nodes masked via Flag_Hidden (VisController can change this flag value at runtime).
477         // Since ObjectPaging does not handle VisController, we can just ignore both types of nodes.
478         constexpr auto copyMask = ~Mask_UpdateVisitor;
479 
480         AnalyzeVisitor analyzeVisitor(copyMask);
481         analyzeVisitor.mCurrentDistance = (viewPoint - worldCenter).length2();
482         float minSize = mMinSize;
483         if (mMinSizeMergeFactor)
484             minSize *= mMinSizeMergeFactor;
485         for (const auto& pair : refs)
486         {
487             const ESM::CellRef& ref = pair.second;
488 
489             osg::Vec3f pos = ref.mPos.asVec3();
490             if (size < 1.f)
491             {
492                 osg::Vec3f cellPos = pos / ESM::Land::REAL_SIZE;
493                 if ((minBound.x() > std::floor(minBound.x()) && cellPos.x() < minBound.x()) || (minBound.y() > std::floor(minBound.y()) && cellPos.y() < minBound.y())
494                  || (maxBound.x() < std::ceil(maxBound.x()) && cellPos.x() >= maxBound.x()) || (minBound.y() < std::ceil(maxBound.y()) && cellPos.y() >= maxBound.y()))
495                     continue;
496             }
497 
498             float dSqr = (viewPoint - pos).length2();
499             if (!activeGrid)
500             {
501                 std::lock_guard<std::mutex> lock(mSizeCacheMutex);
502                 SizeCache::iterator found = mSizeCache.find(pair.first);
503                 if (found != mSizeCache.end() && found->second < dSqr*minSize*minSize)
504                     continue;
505             }
506 
507             if (ref.mRefID == "prisonmarker" || ref.mRefID == "divinemarker" || ref.mRefID == "templemarker" || ref.mRefID == "northmarker")
508                 continue; // marker objects that have a hardcoded function in the game logic, should be hidden from the player
509 
510             int type = store.findStatic(ref.mRefID);
511             std::string model = getModel(type, ref.mRefID, store);
512             if (model.empty()) continue;
513             model = "meshes/" + model;
514 
515             if (activeGrid && type != ESM::REC_STAT)
516             {
517                 model = Misc::ResourceHelpers::correctActorModelPath(model, mSceneManager->getVFS());
518                 std::string kfname = Misc::StringUtils::lowerCase(model);
519                 if(kfname.size() > 4 && kfname.compare(kfname.size()-4, 4, ".nif") == 0)
520                 {
521                     kfname.replace(kfname.size()-4, 4, ".kf");
522                     if (mSceneManager->getVFS()->exists(kfname))
523                         continue;
524                 }
525             }
526 
527             osg::ref_ptr<const osg::Node> cnode = mSceneManager->getTemplate(model, false);
528 
529             if (activeGrid)
530             {
531                 if (cnode->getNumChildrenRequiringUpdateTraversal() > 0 || SceneUtil::hasUserDescription(cnode, Constants::NightDayLabel) || SceneUtil::hasUserDescription(cnode, Constants::HerbalismLabel))
532                     continue;
533                 else
534                     refnumSet->mRefnums.insert(pair.first);
535             }
536 
537             {
538                 std::lock_guard<std::mutex> lock(mRefTrackerMutex);
539                 if (getRefTracker().mDisabled.count(pair.first))
540                     continue;
541             }
542 
543             float radius2 = cnode->getBound().radius2() * ref.mScale*ref.mScale;
544             if (radius2 < dSqr*minSize*minSize && !activeGrid)
545             {
546                 std::lock_guard<std::mutex> lock(mSizeCacheMutex);
547                 mSizeCache[pair.first] = radius2;
548                 continue;
549             }
550 
551             auto emplaced = nodes.emplace(cnode, InstanceList());
552             if (emplaced.second)
553             {
554                 const_cast<osg::Node*>(cnode.get())->accept(analyzeVisitor); // const-trickery required because there is no const version of NodeVisitor
555                 emplaced.first->second.mAnalyzeResult = analyzeVisitor.retrieveResult();
556                 emplaced.first->second.mNeedCompile = compile && cnode->referenceCount() <= 3;
557             }
558             else
559                 analyzeVisitor.addInstance(emplaced.first->second.mAnalyzeResult);
560             emplaced.first->second.mInstances.push_back(&ref);
561         }
562 
563         osg::ref_ptr<osg::Group> group = new osg::Group;
564         osg::ref_ptr<osg::Group> mergeGroup = new osg::Group;
565         osg::ref_ptr<Resource::TemplateMultiRef> templateRefs = new Resource::TemplateMultiRef;
566         osgUtil::StateToCompile stateToCompile(0, nullptr);
567         CopyOp copyop;
568         copyop.mCopyMask = copyMask;
569         for (const auto& pair : nodes)
570         {
571             const osg::Node* cnode = pair.first;
572 
573             const AnalyzeVisitor::Result& analyzeResult = pair.second.mAnalyzeResult;
574 
575             float mergeCost = analyzeResult.mNumVerts * size;
576             float mergeBenefit = analyzeVisitor.getMergeBenefit(analyzeResult) * mMergeFactor;
577             bool merge = mergeBenefit > mergeCost;
578 
579             float minSizeMerged = mMinSize;
580             float factor2 = mergeBenefit > 0 ? std::min(1.f, mergeCost * mMinSizeCostMultiplier / mergeBenefit) : 1;
581             float minSizeMergeFactor2 = (1-factor2) * mMinSizeMergeFactor + factor2;
582             if (minSizeMergeFactor2 > 0)
583                 minSizeMerged *= minSizeMergeFactor2;
584 
585             unsigned int numinstances = 0;
586             for (auto cref : pair.second.mInstances)
587             {
588                 const ESM::CellRef& ref = *cref;
589                 osg::Vec3f pos = ref.mPos.asVec3();
590 
591                 if (!activeGrid && minSizeMerged != minSize && cnode->getBound().radius2() * cref->mScale*cref->mScale < (viewPoint-pos).length2()*minSizeMerged*minSizeMerged)
592                     continue;
593 
594                 osg::Matrixf matrix;
595                 matrix.preMultTranslate(pos - worldCenter);
596                 matrix.preMultRotate( osg::Quat(ref.mPos.rot[2], osg::Vec3f(0,0,-1)) *
597                                         osg::Quat(ref.mPos.rot[1], osg::Vec3f(0,-1,0)) *
598                                         osg::Quat(ref.mPos.rot[0], osg::Vec3f(-1,0,0)) );
599                 matrix.preMultScale(osg::Vec3f(ref.mScale, ref.mScale, ref.mScale));
600                 osg::ref_ptr<osg::MatrixTransform> trans = new osg::MatrixTransform(matrix);
601                 trans->setDataVariance(osg::Object::STATIC);
602 
603                 copyop.setCopyFlags(merge ? osg::CopyOp::DEEP_COPY_NODES|osg::CopyOp::DEEP_COPY_DRAWABLES : osg::CopyOp::DEEP_COPY_NODES);
604                 copyop.mOptimizeBillboards = (size > 1/4.f);
605                 copyop.mNodePath.push_back(trans);
606                 copyop.mSqrDistance = (viewPoint - pos).length2();
607                 copyop.mViewVector = (viewPoint - worldCenter);
608                 copyop.copy(cnode, trans);
609                 copyop.mNodePath.pop_back();
610 
611                 if (activeGrid)
612                 {
613                     if (merge)
614                     {
615                         AddRefnumMarkerVisitor visitor(ref.mRefNum);
616                         trans->accept(visitor);
617                     }
618                     else
619                     {
620                         osg::ref_ptr<RefnumMarker> marker = new RefnumMarker; marker->mRefnum = ref.mRefNum;
621                         trans->getOrCreateUserDataContainer()->addUserObject(marker);
622                     }
623                 }
624 
625                 osg::Group* attachTo = merge ? mergeGroup : group;
626                 attachTo->addChild(trans);
627                 ++numinstances;
628             }
629             if (numinstances > 0)
630             {
631                 // add a ref to the original template, to hint to the cache that it's still being used and should be kept in cache
632                 templateRefs->addRef(cnode);
633 
634                 if (pair.second.mNeedCompile)
635                 {
636                     int mode = osgUtil::GLObjectsVisitor::COMPILE_STATE_ATTRIBUTES;
637                     if (!merge)
638                         mode |= osgUtil::GLObjectsVisitor::COMPILE_DISPLAY_LISTS;
639                     stateToCompile._mode = mode;
640                     const_cast<osg::Node*>(cnode)->accept(stateToCompile);
641                 }
642             }
643         }
644 
645         if (mergeGroup->getNumChildren())
646         {
647             SceneUtil::Optimizer optimizer;
648             if (size > 1/8.f)
649             {
650                 optimizer.setViewPoint(relativeViewPoint);
651                 optimizer.setMergeAlphaBlending(true);
652             }
653             optimizer.setIsOperationPermissibleForObjectCallback(new CanOptimizeCallback);
654             unsigned int options = SceneUtil::Optimizer::FLATTEN_STATIC_TRANSFORMS|SceneUtil::Optimizer::REMOVE_REDUNDANT_NODES|SceneUtil::Optimizer::MERGE_GEOMETRY;
655             mSceneManager->shareState(mergeGroup);
656             optimizer.optimize(mergeGroup, options);
657 
658             group->addChild(mergeGroup);
659 
660             if (mDebugBatches)
661             {
662                 DebugVisitor dv;
663                 mergeGroup->accept(dv);
664             }
665             if (compile)
666             {
667                 stateToCompile._mode = osgUtil::GLObjectsVisitor::COMPILE_DISPLAY_LISTS;
668                 mergeGroup->accept(stateToCompile);
669             }
670         }
671 
672         auto ico = mSceneManager->getIncrementalCompileOperation();
673         if (!stateToCompile.empty() && ico)
674         {
675             auto compileSet = new osgUtil::IncrementalCompileOperation::CompileSet(group);
676             compileSet->buildCompileMap(ico->getContextSet(), stateToCompile);
677             ico->add(compileSet, false);
678         }
679 
680         group->getBound();
681         group->setNodeMask(Mask_Static);
682         osg::UserDataContainer* udc = group->getOrCreateUserDataContainer();
683         if (activeGrid)
684         {
685             udc->addUserObject(refnumSet);
686             group->addCullCallback(new SceneUtil::LightListCallback);
687         }
688         udc->addUserObject(templateRefs);
689 
690         return group;
691     }
692 
getNodeMask()693     unsigned int ObjectPaging::getNodeMask()
694     {
695         return Mask_Static;
696     }
697 
698     struct ClearCacheFunctor
699     {
operator ()MWRender::ClearCacheFunctor700         void operator()(MWRender::ChunkId id, osg::Object* obj)
701         {
702             if (intersects(id, mPosition))
703                 mToClear.insert(id);
704         }
intersectsMWRender::ClearCacheFunctor705         bool intersects(ChunkId id, osg::Vec3f pos)
706         {
707             if (mActiveGridOnly && !std::get<2>(id)) return false;
708             pos /= ESM::Land::REAL_SIZE;
709             clampToCell(pos);
710             osg::Vec2f center = std::get<0>(id);
711             float halfSize = std::get<1>(id)/2;
712             return pos.x() >= center.x()-halfSize && pos.y() >= center.y()-halfSize && pos.x() <= center.x()+halfSize && pos.y() <= center.y()+halfSize;
713         }
clampToCellMWRender::ClearCacheFunctor714         void clampToCell(osg::Vec3f& cellPos)
715         {
716             osg::Vec2i min (mCell.x(), mCell.y());
717             osg::Vec2i max (mCell.x()+1, mCell.y()+1);
718             if (cellPos.x() < min.x()) cellPos.x() = min.x();
719             if (cellPos.x() > max.x()) cellPos.x() = max.x();
720             if (cellPos.y() < min.y()) cellPos.y() = min.y();
721             if (cellPos.y() > max.y()) cellPos.y() = max.y();
722         }
723         osg::Vec3f mPosition;
724         osg::Vec2i mCell;
725         std::set<MWRender::ChunkId> mToClear;
726         bool mActiveGridOnly = false;
727     };
728 
enableObject(int type,const ESM::RefNum & refnum,const osg::Vec3f & pos,const osg::Vec2i & cell,bool enabled)729     bool ObjectPaging::enableObject(int type, const ESM::RefNum & refnum, const osg::Vec3f& pos, const osg::Vec2i& cell, bool enabled)
730     {
731         if (!typeFilter(type, false))
732             return false;
733 
734         {
735             std::lock_guard<std::mutex> lock(mRefTrackerMutex);
736             if (enabled && !getWritableRefTracker().mDisabled.erase(refnum)) return false;
737             if (!enabled && !getWritableRefTracker().mDisabled.insert(refnum).second) return false;
738             if (mRefTrackerLocked) return false;
739         }
740 
741         ClearCacheFunctor ccf;
742         ccf.mPosition = pos;
743         ccf.mCell = cell;
744         mCache->call(ccf);
745         if (ccf.mToClear.empty()) return false;
746         for (const auto& chunk : ccf.mToClear)
747             mCache->removeFromObjectCache(chunk);
748         return true;
749     }
750 
blacklistObject(int type,const ESM::RefNum & refnum,const osg::Vec3f & pos,const osg::Vec2i & cell)751     bool ObjectPaging::blacklistObject(int type, const ESM::RefNum & refnum, const osg::Vec3f& pos, const osg::Vec2i& cell)
752     {
753         if (!typeFilter(type, false))
754             return false;
755 
756         {
757             std::lock_guard<std::mutex> lock(mRefTrackerMutex);
758             if (!getWritableRefTracker().mBlacklist.insert(refnum).second) return false;
759             if (mRefTrackerLocked) return false;
760         }
761 
762         ClearCacheFunctor ccf;
763         ccf.mPosition = pos;
764         ccf.mCell = cell;
765         ccf.mActiveGridOnly = true;
766         mCache->call(ccf);
767         if (ccf.mToClear.empty()) return false;
768         for (const auto& chunk : ccf.mToClear)
769             mCache->removeFromObjectCache(chunk);
770         return true;
771     }
772 
773 
clear()774     void ObjectPaging::clear()
775     {
776         std::lock_guard<std::mutex> lock(mRefTrackerMutex);
777         mRefTrackerNew.mDisabled.clear();
778         mRefTrackerNew.mBlacklist.clear();
779         mRefTrackerLocked = true;
780     }
781 
unlockCache()782     bool ObjectPaging::unlockCache()
783     {
784         if (!mRefTrackerLocked) return false;
785         {
786             std::lock_guard<std::mutex> lock(mRefTrackerMutex);
787             mRefTrackerLocked = false;
788             if (mRefTracker == mRefTrackerNew)
789                 return false;
790             else
791                 mRefTracker = mRefTrackerNew;
792         }
793         mCache->clear();
794         return true;
795     }
796 
797     struct GetRefnumsFunctor
798     {
GetRefnumsFunctorMWRender::GetRefnumsFunctor799         GetRefnumsFunctor(std::set<ESM::RefNum>& output) : mOutput(output) {}
operator ()MWRender::GetRefnumsFunctor800         void operator()(MWRender::ChunkId chunkId, osg::Object* obj)
801         {
802             if (!std::get<2>(chunkId)) return;
803             const osg::Vec2f& center = std::get<0>(chunkId);
804             bool activeGrid = (center.x() > mActiveGrid.x() || center.y() > mActiveGrid.y() || center.x() < mActiveGrid.z() || center.y() < mActiveGrid.w());
805             if (!activeGrid) return;
806 
807             osg::UserDataContainer* udc = obj->getUserDataContainer();
808             if (udc && udc->getNumUserObjects())
809             {
810                 RefnumSet* refnums = dynamic_cast<RefnumSet*>(udc->getUserObject(0));
811                 if (!refnums) return;
812                 mOutput.insert(refnums->mRefnums.begin(), refnums->mRefnums.end());
813             }
814         }
815         osg::Vec4i mActiveGrid;
816         std::set<ESM::RefNum>& mOutput;
817     };
818 
getPagedRefnums(const osg::Vec4i & activeGrid,std::set<ESM::RefNum> & out)819     void ObjectPaging::getPagedRefnums(const osg::Vec4i &activeGrid, std::set<ESM::RefNum> &out)
820     {
821         GetRefnumsFunctor grf(out);
822         grf.mActiveGrid = activeGrid;
823         mCache->call(grf);
824     }
825 
reportStats(unsigned int frameNumber,osg::Stats * stats) const826     void ObjectPaging::reportStats(unsigned int frameNumber, osg::Stats *stats) const
827     {
828         stats->setAttribute(frameNumber, "Object Chunk", mCache->getCacheSize());
829     }
830 
831 }
832