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