From 732212070da308d97cbe1f01118e6b677867a8a0 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 23 Feb 2017 02:39:29 +0100 Subject: [PATCH 01/25] crashcatcher: complain when launching gdb fails --- apps/openmw/crashcatcher.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/openmw/crashcatcher.cpp b/apps/openmw/crashcatcher.cpp index a38d301ea..0078ef169 100644 --- a/apps/openmw/crashcatcher.cpp +++ b/apps/openmw/crashcatcher.cpp @@ -160,11 +160,12 @@ static void gdb_info(pid_t pid) printf("Executing: %s\n", cmd_buf); fflush(stdout); - { /* another special exception for "ignoring return value..." */ - int unused; - unused = system(cmd_buf); - UNUSED(unused); - } + int ret = system(cmd_buf); + + if (ret != 0) + printf("\nFailed to create a crash report. Please install 'gdb' and crash again!\n"); + fflush(stdout); + /* Clean up */ remove(respfile); } From d75a3fd0fbf0d4d0ca6da0487aa85245d105fee9 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 23 Feb 2017 14:49:12 +0100 Subject: [PATCH 02/25] Add SharedStateManager to the stats panel --- components/resource/scenemanager.cpp | 22 +++++++++++++++++++++- components/resource/scenemanager.hpp | 3 ++- components/resource/stats.cpp | 2 +- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 070882bb8..cc23469e1 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -105,6 +105,20 @@ namespace namespace Resource { + class SharedStateManager : public osgDB::SharedStateManager + { + public: + unsigned int getNumSharedTextures() const + { + return _sharedTextureList.size(); + } + + unsigned int getNumSharedStateSets() const + { + return _sharedStateSetList.size(); + } + }; + /// Set texture filtering settings on textures contained in a FlipController. class SetFilterSettingsControllerVisitor : public SceneUtil::ControllerVisitor { @@ -195,7 +209,7 @@ namespace Resource , mAutoUseNormalMaps(false) , mAutoUseSpecularMaps(false) , mInstanceCache(new MultiObjectCache) - , mSharedStateManager(new osgDB::SharedStateManager) + , mSharedStateManager(new SharedStateManager) , mImageManager(imageManager) , mNifFileManager(nifFileManager) , mMinFilter(osg::Texture::LINEAR_MIPMAP_LINEAR) @@ -592,6 +606,12 @@ namespace Resource stats->setAttribute(frameNumber, "Compiling", mIncrementalCompileOperation->getToCompile().size()); } + { + OpenThreads::ScopedLock lock(mSharedStateMutex); + stats->setAttribute(frameNumber, "Texture", mSharedStateManager->getNumSharedTextures()); + stats->setAttribute(frameNumber, "StateSet", mSharedStateManager->getNumSharedStateSets()); + } + stats->setAttribute(frameNumber, "Node", mCache->getCacheSize()); stats->setAttribute(frameNumber, "Node Instance", mInstanceCache->getCacheSize()); } diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 42da9b848..5d54ccb1c 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -15,6 +15,7 @@ namespace Resource { class ImageManager; class NifFileManager; + class SharedStateManager; } namespace osgUtil @@ -157,7 +158,7 @@ namespace Resource osg::ref_ptr mInstanceCache; - osg::ref_ptr mSharedStateManager; + osg::ref_ptr mSharedStateManager; OpenThreads::Mutex mSharedStateMutex; Resource::ImageManager* mImageManager; diff --git a/components/resource/stats.cpp b/components/resource/stats.cpp index 5b0f8b935..d696c9ecc 100644 --- a/components/resource/stats.cpp +++ b/components/resource/stats.cpp @@ -259,7 +259,7 @@ void StatsHandler::setUpScene(osgViewer::ViewerBase *viewer) _resourceStatsChildNum = _switch->getNumChildren(); _switch->addChild(group, false); - const char* statNames[] = {"Compiling", "WorkQueue", "WorkThread", "", "Node", "Node Instance", "Shape", "Shape Instance", "Image", "Nif", "Keyframe", "Terrain Cell", "Terrain Texture", "", "UnrefQueue"}; + const char* statNames[] = {"Compiling", "WorkQueue", "WorkThread", "", "Texture", "StateSet", "Node", "Node Instance", "Shape", "Shape Instance", "Image", "Nif", "Keyframe", "Terrain Cell", "Terrain Texture", "", "UnrefQueue"}; int numLines = sizeof(statNames) / sizeof(statNames[0]); From c231b06320ccffee77630ca8c52fe87fc6bab17e Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 23 Feb 2017 15:08:55 +0100 Subject: [PATCH 03/25] Remove boneOffset node after using it --- components/sceneutil/attach.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/sceneutil/attach.cpp b/components/sceneutil/attach.cpp index 2a8f94c7c..79defe196 100644 --- a/components/sceneutil/attach.cpp +++ b/components/sceneutil/attach.cpp @@ -121,6 +121,10 @@ namespace SceneUtil trans->setPosition(boneOffset->getMatrix().getTrans()); // The BoneOffset rotation seems to be incorrect trans->setAttitude(osg::Quat(osg::DegreesToRadians(-90.f), osg::Vec3f(1,0,0))); + + // Now that we used it, get rid of the redundant node. + if (boneOffset->getNumChildren() == 0 && boneOffset->getNumParents() == 1) + boneOffset->getParent(0)->removeChild(boneOffset); } if (attachNode->getName().find("Left") != std::string::npos) From 6e5d9efa9337e4b0311d3e863fbe255d0acd4333 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 23 Feb 2017 15:35:06 +0100 Subject: [PATCH 04/25] Remove NifLoader::optimize, to be replaced with something better --- components/nifosg/nifloader.cpp | 41 --------------------------------- 1 file changed, 41 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index d0febe2fb..656cc96bc 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -416,44 +416,6 @@ namespace NifOsg toSetup->setFunction(boost::shared_ptr(new ControllerFunction(ctrl))); } - void optimize (const Nif::Node* nifNode, osg::Group* node, bool skipMeshes) - { - // For nodes with an identity transform, remove the redundant Transform node - if (node->getDataVariance() == osg::Object::STATIC - // For TriShapes, we can only collapse the node, but not completely remove it, - // if the link to animated collision shapes is supposed to stay intact. - && (nifNode->recType != Nif::RC_NiTriShape || !skipMeshes) - // Don't optimize drawables with controllers, that creates issues when we want to deep copy controllers without deep copying the drawable that holds the controller. - // A deep copy of controllers may be needed to independently animate multiple copies of the same mesh. - && !node->getUpdateCallback()) - { - if (node->getNumParents() && nifNode->trafo.isIdentity()) - { - osg::Group* parent = node->getParent(0); - - // can be multiple children in case of ParticleSystems, with the extra ParticleSystemUpdater node - for (unsigned int i=0; igetNumChildren(); ++i) - { - osg::Node* child = node->getChild(i); - if (i == node->getNumChildren()-1) // FIXME: some nicer way to determine where our actual Drawable resides... - { - child->addUpdateCallback(node->getUpdateCallback()); - child->setStateSet(node->getStateSet()); - child->setName(node->getName()); - // make sure to copy the UserDataContainer with the record index, so that connections to an animated collision shape don't break - child->setUserDataContainer(node->getUserDataContainer()); - } - parent->addChild(child); - } - - node->removeChildren(0, node->getNumChildren()); - parent->removeChild(node); - } - } - // For NiTriShapes *with* a valid transform, perhaps we could apply the transform to the vertices. - // Need to make sure that won't break transparency sorting. Check what the original engine is doing? - } - osg::ref_ptr handleLodNode(const Nif::NiLODNode* niLodNode) { osg::ref_ptr lod (new osg::LOD); @@ -678,9 +640,6 @@ namespace NifOsg if (!nifNode->controller.empty() && node->getDataVariance() == osg::Object::DYNAMIC) handleNodeControllers(nifNode, static_cast(node.get()), animflags); - // Optimization pass - optimize(nifNode, node, skipMeshes); - if (nifNode->recType == Nif::RC_NiLODNode) { From 6e996bf2a3bd292e83104a1ca20058bcbd99ed35 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 23 Feb 2017 15:58:17 +0100 Subject: [PATCH 05/25] Create TriShapes with an identity transform as a Group --- components/nifosg/nifloader.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 656cc96bc..285719eef 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -527,6 +527,8 @@ namespace NifOsg case Nif::RC_NiRotatingParticles: // Leaf nodes in the NIF hierarchy, so won't be able to dynamically attach children. // No support for keyframe controllers (just crashes in the original engine). + if (nifNode->trafo.isIdentity()) + node = new osg::Group; node->setDataVariance(osg::Object::STATIC); break; default: From 25ca89b56098e2c117e9ccf64796b744a48a081a Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 23 Feb 2017 17:18:27 +0100 Subject: [PATCH 06/25] Avoid redundant allocations in Store::search --- apps/openmw/mwworld/store.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 922144814..cdf66f570 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -136,15 +136,14 @@ namespace MWWorld template const T *Store::search(const std::string &id) const { - T item; - item.mId = Misc::StringUtils::lowerCase(id); + std::string idLower = Misc::StringUtils::lowerCase(id); - typename Dynamic::const_iterator dit = mDynamic.find(item.mId); + typename Dynamic::const_iterator dit = mDynamic.find(idLower); if (dit != mDynamic.end()) { return &dit->second; } - typename std::map::const_iterator it = mStatic.find(item.mId); + typename std::map::const_iterator it = mStatic.find(idLower); if (it != mStatic.end() && Misc::StringUtils::ciEqual(it->second.mId, id)) { return &(it->second); @@ -274,10 +273,9 @@ namespace MWWorld template bool Store::eraseStatic(const std::string &id) { - T item; - item.mId = Misc::StringUtils::lowerCase(id); + std::string idLower = Misc::StringUtils::lowerCase(id); - typename std::map::iterator it = mStatic.find(item.mId); + typename std::map::iterator it = mStatic.find(idLower); if (it != mStatic.end() && Misc::StringUtils::ciEqual(it->second.mId, id)) { // delete from the static part of mShared @@ -285,7 +283,7 @@ namespace MWWorld typename std::vector::iterator end = sharedIter + mStatic.size(); while (sharedIter != mShared.end() && sharedIter != end) { - if((*sharedIter)->mId == item.mId) { + if((*sharedIter)->mId == idLower) { mShared.erase(sharedIter); break; } From 1d6b5b2a52443764c69f1936469f3f39321235b4 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 23 Feb 2017 17:49:37 +0100 Subject: [PATCH 07/25] Add StringUtils::CiComp operator --- components/misc/stringops.hpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/components/misc/stringops.hpp b/components/misc/stringops.hpp index 2723527f1..b14051a81 100644 --- a/components/misc/stringops.hpp +++ b/components/misc/stringops.hpp @@ -106,6 +106,14 @@ public: lowerCaseInPlace(out); return out; } + + struct CiComp + { + bool operator()(const std::string& left, const std::string& right) const + { + return ciLess(left, right); + } + }; }; } From 4e5a1e61233dd82749cdf4c4381e3a4716d1bafd Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 23 Feb 2017 18:00:20 +0100 Subject: [PATCH 08/25] nifloader: improve setting of DataVariance --- components/nifosg/nifloader.cpp | 51 ++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 285719eef..bdd104b3d 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -356,7 +356,7 @@ namespace NifOsg osg::ref_ptr textkeys (new TextKeyMapHolder); - osg::ref_ptr created = handleNode(nifNode, NULL, imageManager, std::vector(), 0, false, &textkeys->mTextKeys); + osg::ref_ptr created = handleNode(nifNode, NULL, imageManager, std::vector(), 0, false, false, &textkeys->mTextKeys); if (nif->getUseSkinning()) { @@ -511,15 +511,12 @@ namespace NifOsg stateset->addUniform(new osg::Uniform("envMapColor", osg::Vec4f(1,1,1,1))); } - osg::ref_ptr handleNode(const Nif::Node* nifNode, osg::Group* parentNode, Resource::ImageManager* imageManager, - std::vector boundTextures, int animflags, bool skipMeshes, TextKeyMap* textKeys, osg::Node* rootNode=NULL) + // Get a default dataVariance for this node to be used as a hint by optimization (post)routines + osg::Object::DataVariance getDataVariance(const Nif::Node* nifNode) { - if (rootNode != NULL && Misc::StringUtils::ciEqual(nifNode->name, "Bounding Box")) - return NULL; + if (nifNode->boneTrafo || nifNode->boneIndex != -1) + return osg::Object::DYNAMIC; - osg::ref_ptr node = new osg::MatrixTransform(nifNode->trafo.toMatrix()); - - // Set a default DataVariance (used as hint by optimization routines). switch (nifNode->recType) { case Nif::RC_NiTriShape: @@ -527,15 +524,29 @@ namespace NifOsg case Nif::RC_NiRotatingParticles: // Leaf nodes in the NIF hierarchy, so won't be able to dynamically attach children. // No support for keyframe controllers (just crashes in the original engine). - if (nifNode->trafo.isIdentity()) - node = new osg::Group; - node->setDataVariance(osg::Object::STATIC); - break; + return osg::Object::STATIC; default: - // could have new children attached at any time, or added external keyframe controllers from .kf files - node->setDataVariance(osg::Object::DYNAMIC); - break; + return osg::Object::DYNAMIC; } + } + + osg::ref_ptr handleNode(const Nif::Node* nifNode, osg::Group* parentNode, Resource::ImageManager* imageManager, + std::vector boundTextures, int animflags, bool skipMeshes, bool isAnimated, TextKeyMap* textKeys, osg::Node* rootNode=NULL) + { + if (rootNode != NULL && Misc::StringUtils::ciEqual(nifNode->name, "Bounding Box")) + return NULL; + + osg::Object::DataVariance dataVariance = getDataVariance(nifNode); + + osg::ref_ptr node; + if (dataVariance == osg::Object::STATIC && nifNode->trafo.isIdentity()) + node = new osg::Group; + else + node = new osg::MatrixTransform(nifNode->trafo.toMatrix()); + node->setDataVariance(dataVariance); + + if (nifNode->controller.empty()) + node->setDataVariance(osg::Object::STATIC); if (nifNode->recType == Nif::RC_NiBillboardNode) { @@ -550,6 +561,9 @@ namespace NifOsg node->setDataVariance(osg::Object::STATIC); } + if (!nifNode->controller.empty() && nifNode->controller->recType == Nif::RC_NiKeyframeController) + isAnimated = true; + node->setName(nifNode->name); if (parentNode) @@ -599,6 +613,11 @@ namespace NifOsg node->setNodeMask(0x1); } + if (skipMeshes && isAnimated) // make sure the empty node is not optimized away so the physicssystem can find it. + { + node->setDataVariance(osg::Object::DYNAMIC); + } + // We can skip creating meshes for hidden nodes if they don't have a VisController that // might make them visible later if (nifNode->flags & Nif::NiNode::Flag_Hidden) @@ -665,7 +684,7 @@ namespace NifOsg for(size_t i = 0;i < children.length();++i) { if(!children[i].empty()) - handleNode(children[i].getPtr(), node, imageManager, boundTextures, animflags, skipMeshes, textKeys, rootNode); + handleNode(children[i].getPtr(), node, imageManager, boundTextures, animflags, skipMeshes, isAnimated, textKeys, rootNode); } } From 305cccd2630831074347ad9878c371a03b939622 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 23 Feb 2017 18:31:49 +0100 Subject: [PATCH 09/25] Don't print Geometry data (vertices, triangles, etc.) in showscenegraph --- components/sceneutil/serialize.cpp | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/components/sceneutil/serialize.cpp b/components/sceneutil/serialize.cpp index 969fa6668..64094275c 100644 --- a/components/sceneutil/serialize.cpp +++ b/components/sceneutil/serialize.cpp @@ -78,18 +78,31 @@ osgDB::ObjectWrapper* makeDummySerializer(const std::string& classname) return new osgDB::ObjectWrapper(createInstanceFunc, classname, "osg::Object"); } +class GeometrySerializer : public osgDB::ObjectWrapper +{ +public: + GeometrySerializer() + : osgDB::ObjectWrapper(createInstanceFunc, "osg::Geometry", "osg::Object osg::Drawable osg::Geometry") + { + } +}; void registerSerializers() { static bool done = false; if (!done) { - osgDB::Registry::instance()->getObjectWrapperManager()->addWrapper(new PositionAttitudeTransformSerializer); - osgDB::Registry::instance()->getObjectWrapperManager()->addWrapper(new SkeletonSerializer); - osgDB::Registry::instance()->getObjectWrapperManager()->addWrapper(new FrameSwitchSerializer); - osgDB::Registry::instance()->getObjectWrapperManager()->addWrapper(new RigGeometrySerializer); - osgDB::Registry::instance()->getObjectWrapperManager()->addWrapper(new LightManagerSerializer); - osgDB::Registry::instance()->getObjectWrapperManager()->addWrapper(new CameraRelativeTransformSerializer); + osgDB::ObjectWrapperManager* mgr = osgDB::Registry::instance()->getObjectWrapperManager(); + mgr->addWrapper(new PositionAttitudeTransformSerializer); + mgr->addWrapper(new SkeletonSerializer); + mgr->addWrapper(new FrameSwitchSerializer); + mgr->addWrapper(new RigGeometrySerializer); + mgr->addWrapper(new LightManagerSerializer); + mgr->addWrapper(new CameraRelativeTransformSerializer); + + // Don't serialize Geometry data as we are more interested in the overall structure rather than tons of vertex data that would make the file large and hard to read. + mgr->removeWrapper(mgr->findWrapper("osg::Geometry")); + mgr->addWrapper(new GeometrySerializer); // ignore the below for now to avoid warning spam const char* ignore[] = { @@ -120,7 +133,7 @@ void registerSerializers() }; for (size_t i=0; igetObjectWrapperManager()->addWrapper(makeDummySerializer(ignore[i])); + mgr->addWrapper(makeDummySerializer(ignore[i])); } From 5e2335f2507cd7e0604ff5d7b4e9786863255e9a Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 23 Feb 2017 18:44:50 +0100 Subject: [PATCH 10/25] Use the osgUtil::Optimizer post loading in the SceneManager --- components/resource/scenemanager.cpp | 56 ++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index cc23469e1..afbbc1f0d 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -14,6 +15,8 @@ #include #include +#include + #include #include @@ -373,6 +376,49 @@ namespace Resource } } + class CanOptimizeCallback : public osgUtil::Optimizer::IsOperationPermissibleForObjectCallback + { + public: + bool isReservedName(const std::string& name) const + { + static std::set reservedNames; + if (reservedNames.empty()) + { + const char* reserved[] = {"Head", "Neck", "Chest", "Groin", "Right Hand", "Left Hand", "Right Wrist", "Left Wrist", "Shield Bone", "Right Forearm", "Left Forearm", "Right Upper Arm", "Left Upper Arm", "Right Foot", "Left Foot", "Right Ankle", "Left Ankle", "Right Knee", "Left Knee", "Right Upper Leg", "Left Upper Leg", "Right Clavicle", "Left Clavicle", "Weapon Bone", "Tail", + "Bip01 L Hand", "Bip01 R Hand", "Bip01 Head", "Bip01 Spine1", "Bip01 Spine2", "Bip01 L Clavicle", "Bip01 R Clavicle", "bip01", "Root Bone", "Bip01 Neck", + "BoneOffset", "AttachLight", "ArrowBone", "Camera"}; + reservedNames = std::set(reserved, reserved + sizeof(reserved)/sizeof(reserved[0])); + } + return reservedNames.find(name) != reservedNames.end(); + } + + virtual bool isOperationPermissibleForObjectImplementation(const osgUtil::Optimizer* optimizer, const osg::Node* node,unsigned int option) const + { + if (node->getNumDescriptions()>0) return false; + if (node->getDataVariance() == osg::Object::DYNAMIC) return false; + if (isReservedName(node->getName())) return false; + + return (option & optimizer->getPermissibleOptimizationsForObject(node))!=0; + } + }; + + bool canOptimize(const std::string& filename) + { + // xmesh.nif can not be optimized because there are keyframes added in post + size_t slashpos = filename.find_last_of("\\/"); + if (slashpos != std::string::npos && slashpos+1 < filename.size()) + { + std::string basename = filename.substr(slashpos+1); + if (!basename.empty() && basename[0] == 'x') + return false; + } + + // For spell VFX, DummyXX nodes must remain intact. Not adding those to reservedNames to avoid being overly cautious - instead, decide on filename + if (filename.find("vfx_pattern") != std::string::npos) + return false; + return true; + } + osg::ref_ptr SceneManager::getTemplate(const std::string &name) { std::string normalized = name; @@ -428,10 +474,20 @@ namespace Resource loaded->accept(shaderVisitor); // share state + // do this before optimizing so the optimizer will be able to combine nodes more aggressively + // note, because StateSets will be shared at this point, StateSets can not be modified inside the optimizer mSharedStateMutex.lock(); mSharedStateManager->share(loaded.get()); mSharedStateMutex.unlock(); + if (canOptimize(normalized)) + { + osgUtil::Optimizer optimizer; + optimizer.setIsOperationPermissibleForObjectCallback(new CanOptimizeCallback); + + optimizer.optimize(loaded, osgUtil::Optimizer::FLATTEN_STATIC_TRANSFORMS|osgUtil::Optimizer::REMOVE_REDUNDANT_NODES); //MERGE_GEOMETRY + } + if (mIncrementalCompileOperation) mIncrementalCompileOperation->add(loaded); From e33829d4933b9788e1ebafa5052f377ff1778bc2 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 23 Feb 2017 19:42:12 +0100 Subject: [PATCH 11/25] Add fork of osgUtil::Optimizer with backported fixes that have not been released yet Remove optimizers that won't be used. --- components/CMakeLists.txt | 2 +- components/resource/scenemanager.cpp | 10 +- components/sceneutil/optimizer.cpp | 1889 ++++++++++++++++++++++++++ components/sceneutil/optimizer.hpp | 415 ++++++ 4 files changed, 2310 insertions(+), 6 deletions(-) create mode 100644 components/sceneutil/optimizer.cpp create mode 100644 components/sceneutil/optimizer.hpp diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index f08086189..1035558af 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -50,7 +50,7 @@ add_component_dir (shader add_component_dir (sceneutil clone attach visitor util statesetupdater controller skeleton riggeometry lightcontroller - lightmanager lightutil positionattitudetransform workqueue unrefqueue pathgridutil waterutil writescene serialize + lightmanager lightutil positionattitudetransform workqueue unrefqueue pathgridutil waterutil writescene serialize optimizer ) add_component_dir (nif diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index afbbc1f0d..d0fd9f446 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -7,7 +7,6 @@ #include #include -#include #include #include @@ -22,6 +21,7 @@ #include #include #include +#include #include #include @@ -376,7 +376,7 @@ namespace Resource } } - class CanOptimizeCallback : public osgUtil::Optimizer::IsOperationPermissibleForObjectCallback + class CanOptimizeCallback : public SceneUtil::Optimizer::IsOperationPermissibleForObjectCallback { public: bool isReservedName(const std::string& name) const @@ -392,7 +392,7 @@ namespace Resource return reservedNames.find(name) != reservedNames.end(); } - virtual bool isOperationPermissibleForObjectImplementation(const osgUtil::Optimizer* optimizer, const osg::Node* node,unsigned int option) const + virtual bool isOperationPermissibleForObjectImplementation(const SceneUtil::Optimizer* optimizer, const osg::Node* node,unsigned int option) const { if (node->getNumDescriptions()>0) return false; if (node->getDataVariance() == osg::Object::DYNAMIC) return false; @@ -482,10 +482,10 @@ namespace Resource if (canOptimize(normalized)) { - osgUtil::Optimizer optimizer; + SceneUtil::Optimizer optimizer; optimizer.setIsOperationPermissibleForObjectCallback(new CanOptimizeCallback); - optimizer.optimize(loaded, osgUtil::Optimizer::FLATTEN_STATIC_TRANSFORMS|osgUtil::Optimizer::REMOVE_REDUNDANT_NODES); //MERGE_GEOMETRY + optimizer.optimize(loaded, SceneUtil::Optimizer::FLATTEN_STATIC_TRANSFORMS|SceneUtil::Optimizer::REMOVE_REDUNDANT_NODES); //MERGE_GEOMETRY } if (mIncrementalCompileOperation) diff --git a/components/sceneutil/optimizer.cpp b/components/sceneutil/optimizer.cpp new file mode 100644 index 000000000..36d589170 --- /dev/null +++ b/components/sceneutil/optimizer.cpp @@ -0,0 +1,1889 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * OpenSceneGraph Public License for more details. +*/ + +/* Modified for OpenMW */ + +#include +#include + +#include "optimizer.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +using namespace osgUtil; + +void Optimizer::reset() +{ +} + +void Optimizer::optimize(osg::Node* node, unsigned int options) +{ + StatsVisitor stats; + + if (osg::getNotifyLevel()>=osg::INFO) + { + node->accept(stats); + stats.totalUpStats(); + OSG_NOTICE<accept(fstv); + result = fstv.removeTransforms(node); + ++i; + } while (result); + + // now combine any adjacent static transforms. + CombineStaticTransformsVisitor cstv(this); + node->accept(cstv); + cstv.removeTransforms(node); + } + + if (options & MERGE_GEODES) + { + OSG_INFO<<"Optimizer::optimize() doing MERGE_GEODES"<tick(); + + MergeGeodesVisitor visitor; + node->accept(visitor); + + osg::Timer_t endTick = osg::Timer::instance()->tick(); + + OSG_INFO<<"MERGE_GEODES took "<delta_s(startTick,endTick)<tick(); + + MergeGeometryVisitor mgv(this); + mgv.setTargetMaximumNumberOfVertices(10000); + node->accept(mgv); + + osg::Timer_t endTick = osg::Timer::instance()->tick(); + + OSG_INFO<<"MERGE_GEOMETRY took "<delta_s(startTick,endTick)<accept(tsv); + tsv.stripify(); + } + + if (options & REMOVE_REDUNDANT_NODES) + { + OSG_INFO<<"Optimizer::optimize() doing REMOVE_REDUNDANT_NODES"<accept(renv); + renv.removeEmptyNodes(); + + RemoveRedundantNodesVisitor rrnv(this); + node->accept(rrnv); + rrnv.removeRedundantNodes(); + + } + + if (options & FLATTEN_BILLBOARDS) + { + FlattenBillboardVisitor fbv(this); + node->accept(fbv); + fbv.process(); + } + + if (options & SPATIALIZE_GROUPS) + { + OSG_INFO<<"Optimizer::optimize() doing SPATIALIZE_GROUPS"<accept(sv); + sv.divide(); + } + + if (options & INDEX_MESH) + { + OSG_INFO<<"Optimizer::optimize() doing INDEX_MESH"<accept(imv); + imv.makeMesh(); + } + + if (options & VERTEX_POSTTRANSFORM) + { + OSG_INFO<<"Optimizer::optimize() doing VERTEX_POSTTRANSFORM"<accept(vcv); + vcv.optimizeVertices(); + } + + if (options & VERTEX_PRETRANSFORM) + { + OSG_INFO<<"Optimizer::optimize() doing VERTEX_PRETRANSFORM"<accept(vaov); + vaov.optimizeOrder(); + } + + if (osg::getNotifyLevel()>=osg::INFO) + { + stats.reset(); + node->accept(stats); + stats.totalUpStats(); + OSG_NOTICE<accept(*this); + + _currentObjectList.pop_back(); + } + + void collectDataFor(osg::Billboard* billboard) + { + _currentObjectList.push_back(billboard); + + billboard->accept(*this); + + _currentObjectList.pop_back(); + } + + void collectDataFor(osg::Drawable* drawable) + { + _currentObjectList.push_back(drawable); + + const osg::Drawable::ParentList& parents = drawable->getParents(); + for(osg::Drawable::ParentList::const_iterator itr=parents.begin(); + itr!=parents.end(); + ++itr) + { + (*itr)->accept(*this); + } + + _currentObjectList.pop_back(); + } + + void setUpMaps(); + void disableTransform(osg::Transform* transform); + bool removeTransforms(osg::Node* nodeWeCannotRemove); + + inline bool isOperationPermissibleForObject(const osg::Object* object) const + { + const osg::Drawable* drawable = dynamic_cast(object); + if (drawable) return isOperationPermissibleForObject(drawable); + + const osg::Node* node = dynamic_cast(object); + if (node) return isOperationPermissibleForObject(node); + + return true; + } + + inline bool isOperationPermissibleForObject(const osg::Drawable* drawable) const + { + // disable if cannot apply transform functor. + if (drawable && !drawable->supports(_transformFunctor)) return false; + return BaseOptimizerVisitor::isOperationPermissibleForObject(drawable); + } + + inline bool isOperationPermissibleForObject(const osg::Node* node) const + { + // disable if object is a light point node. + if (strcmp(node->className(),"LightPointNode")==0) return false; + if (dynamic_cast(node)) return false; + if (dynamic_cast(node)) return false; + return BaseOptimizerVisitor::isOperationPermissibleForObject(node); + } + + protected: + + struct TransformStruct + { + typedef std::set ObjectSet; + + TransformStruct():_canBeApplied(true) {} + + void add(osg::Object* obj) + { + _objectSet.insert(obj); + } + + bool _canBeApplied; + ObjectSet _objectSet; + }; + + struct ObjectStruct + { + typedef std::set TransformSet; + + ObjectStruct():_canBeApplied(true),_moreThanOneMatrixRequired(false) {} + + void add(osg::Transform* transform) + { + if (transform) + { + if (transform->getDataVariance()!=osg::Transform::STATIC) _moreThanOneMatrixRequired=true; + else if (transform->getReferenceFrame()!=osg::Transform::RELATIVE_RF) _moreThanOneMatrixRequired=true; + else + { + if (_transformSet.empty()) transform->computeLocalToWorldMatrix(_firstMatrix,0); + else + { + osg::Matrix matrix; + transform->computeLocalToWorldMatrix(matrix,0); + if (_firstMatrix!=matrix) _moreThanOneMatrixRequired=true; + } + } + } + else + { + if (!_transformSet.empty()) + { + if (!_firstMatrix.isIdentity()) _moreThanOneMatrixRequired=true; + } + + } + _transformSet.insert(transform); + } + + bool _canBeApplied; + bool _moreThanOneMatrixRequired; + osg::Matrix _firstMatrix; + TransformSet _transformSet; + }; + + + void registerWithCurrentObjects(osg::Transform* transform) + { + for(ObjectList::iterator itr=_currentObjectList.begin(); + itr!=_currentObjectList.end(); + ++itr) + { + _objectMap[*itr].add(transform); + } + } + + typedef std::map TransformMap; + typedef std::map ObjectMap; + typedef std::vector ObjectList; + + void disableObject(osg::Object* object) + { + disableObject(_objectMap.find(object)); + } + + void disableObject(ObjectMap::iterator itr); + void doTransform(osg::Object* obj,osg::Matrix& matrix); + + osgUtil::TransformAttributeFunctor _transformFunctor; + TransformMap _transformMap; + ObjectMap _objectMap; + ObjectList _currentObjectList; + +}; + + +void CollectLowestTransformsVisitor::doTransform(osg::Object* obj,osg::Matrix& matrix) +{ + osg::Drawable* drawable = dynamic_cast(obj); + if (drawable) + { + osgUtil::TransformAttributeFunctor tf(matrix); + drawable->accept(tf); + drawable->dirtyBound(); + drawable->dirtyDisplayList(); + + return; + } + + osg::LOD* lod = dynamic_cast(obj); + if (lod) + { + osg::Matrix matrix_no_trans = matrix; + matrix_no_trans.setTrans(0.0f,0.0f,0.0f); + + osg::Vec3 v111(1.0f,1.0f,1.0f); + osg::Vec3 new_v111 = v111*matrix_no_trans; + float ratio = new_v111.length()/v111.length(); + + // move center point. + lod->setCenter(lod->getCenter()*matrix); + + // adjust ranges to new scale. + for(unsigned int i=0;igetNumRanges();++i) + { + lod->setRange(i,lod->getMinRange(i)*ratio,lod->getMaxRange(i)*ratio); + } + + lod->dirtyBound(); + return; + } + + osg::Billboard* billboard = dynamic_cast(obj); + if (billboard) + { + osg::Matrix matrix_no_trans = matrix; + matrix_no_trans.setTrans(0.0f,0.0f,0.0f); + + osgUtil::TransformAttributeFunctor tf(matrix_no_trans); + + osg::Vec3 axis = osg::Matrix::transform3x3(tf._im,billboard->getAxis()); + axis.normalize(); + billboard->setAxis(axis); + + osg::Vec3 normal = osg::Matrix::transform3x3(tf._im,billboard->getNormal()); + normal.normalize(); + billboard->setNormal(normal); + + + for(unsigned int i=0;igetNumDrawables();++i) + { + billboard->setPosition(i,billboard->getPosition(i)*matrix); + billboard->getDrawable(i)->accept(tf); + billboard->getDrawable(i)->dirtyBound(); + } + + billboard->dirtyBound(); + + return; + } +} + +void CollectLowestTransformsVisitor::disableObject(ObjectMap::iterator itr) +{ + if (itr==_objectMap.end()) + { + return; + } + + if (itr->second._canBeApplied) + { + // we havn't been disabled yet so we need to disable, + itr->second._canBeApplied = false; + + // and then inform everybody we have been disabled. + for(ObjectStruct::TransformSet::iterator titr = itr->second._transformSet.begin(); + titr != itr->second._transformSet.end(); + ++titr) + { + disableTransform(*titr); + } + } +} + +void CollectLowestTransformsVisitor::disableTransform(osg::Transform* transform) +{ + TransformMap::iterator itr=_transformMap.find(transform); + if (itr==_transformMap.end()) + { + return; + } + + if (itr->second._canBeApplied) + { + + // we havn't been disabled yet so we need to disable, + itr->second._canBeApplied = false; + // and then inform everybody we have been disabled. + for(TransformStruct::ObjectSet::iterator oitr = itr->second._objectSet.begin(); + oitr != itr->second._objectSet.end(); + ++oitr) + { + disableObject(*oitr); + } + } +} + +void CollectLowestTransformsVisitor::setUpMaps() +{ + // create the TransformMap from the ObjectMap + ObjectMap::iterator oitr; + for(oitr=_objectMap.begin(); + oitr!=_objectMap.end(); + ++oitr) + { + osg::Object* object = oitr->first; + ObjectStruct& os = oitr->second; + + for(ObjectStruct::TransformSet::iterator titr = os._transformSet.begin(); + titr != os._transformSet.end(); + ++titr) + { + _transformMap[*titr].add(object); + } + } + + // disable all the objects which have more than one matrix associated + // with them, and then disable all transforms which have an object associated + // them that can't be applied, and then disable all objects which have + // disabled transforms associated, recursing until all disabled + // associativity. + // and disable all objects that the operation is not permisable for) + for(oitr=_objectMap.begin(); + oitr!=_objectMap.end(); + ++oitr) + { + osg::Object* object = oitr->first; + ObjectStruct& os = oitr->second; + if (os._canBeApplied) + { + if (os._moreThanOneMatrixRequired || !isOperationPermissibleForObject(object)) + { + disableObject(oitr); + } + } + } + +} + +bool CollectLowestTransformsVisitor::removeTransforms(osg::Node* nodeWeCannotRemove) +{ + // transform the objects that can be applied. + for(ObjectMap::iterator oitr=_objectMap.begin(); + oitr!=_objectMap.end(); + ++oitr) + { + osg::Object* object = oitr->first; + ObjectStruct& os = oitr->second; + if (os._canBeApplied) + { + doTransform(object,os._firstMatrix); + } + } + + + bool transformRemoved = false; + + // clean up the transforms. + for(TransformMap::iterator titr=_transformMap.begin(); + titr!=_transformMap.end(); + ++titr) + { + if (titr->first!=0 && titr->second._canBeApplied) + { + if (titr->first!=nodeWeCannotRemove) + { + transformRemoved = true; + + osg::ref_ptr transform = titr->first; + osg::ref_ptr group = new osg::Group; + group->setName( transform->getName() ); + group->setDataVariance(osg::Object::STATIC); + group->setNodeMask(transform->getNodeMask()); + group->setStateSet(transform->getStateSet()); + group->setUserData(transform->getUserData()); + group->setDescriptions(transform->getDescriptions()); + for(unsigned int i=0;igetNumChildren();++i) + { + group->addChild(transform->getChild(i)); + } + + for(int i2=transform->getNumParents()-1;i2>=0;--i2) + { + transform->getParent(i2)->replaceChild(transform.get(),group.get()); + } + } + else + { + osg::MatrixTransform* mt = dynamic_cast(titr->first); + if (mt) mt->setMatrix(osg::Matrix::identity()); + else + { + osg::PositionAttitudeTransform* pat = dynamic_cast(titr->first); + if (pat) + { + pat->setPosition(osg::Vec3(0.0f,0.0f,0.0f)); + pat->setAttitude(osg::Quat()); + pat->setPivotPoint(osg::Vec3(0.0f,0.0f,0.0f)); + } + else + { + OSG_WARN<<"Warning:: during Optimize::CollectLowestTransformsVisitor::removeTransforms(Node*)"<first->className()<getVertexArray() && geometry->getVertexArray()->referenceCount() > 1) { + geometry->setVertexArray(dynamic_cast(geometry->getVertexArray()->clone(osg::CopyOp::DEEP_COPY_ALL))); + } + if(geometry->getNormalArray() && geometry->getNormalArray()->referenceCount() > 1) { + geometry->setNormalArray(dynamic_cast(geometry->getNormalArray()->clone(osg::CopyOp::DEEP_COPY_ALL))); + } + } + _drawableSet.insert(&drawable); +} + +void Optimizer::FlattenStaticTransformsVisitor::apply(osg::Billboard& billboard) +{ + if (!_transformStack.empty()) + { + _billboardSet.insert(&billboard); + } +} + +void Optimizer::FlattenStaticTransformsVisitor::apply(osg::Transform& transform) +{ + if (!_transformStack.empty()) + { + // we need to disable any transform higher in the list. + _transformSet.insert(_transformStack.back()); + } + + _transformStack.push_back(&transform); + + // simple traverse the children as if this Transform didn't exist. + traverse(transform); + + _transformStack.pop_back(); +} + +bool Optimizer::FlattenStaticTransformsVisitor::removeTransforms(osg::Node* nodeWeCannotRemove) +{ + CollectLowestTransformsVisitor cltv(_optimizer); + + for(NodeSet::iterator nitr=_excludedNodeSet.begin(); + nitr!=_excludedNodeSet.end(); + ++nitr) + { + cltv.collectDataFor(*nitr); + } + + for(DrawableSet::iterator ditr=_drawableSet.begin(); + ditr!=_drawableSet.end(); + ++ditr) + { + cltv.collectDataFor(*ditr); + } + + for(BillboardSet::iterator bitr=_billboardSet.begin(); + bitr!=_billboardSet.end(); + ++bitr) + { + cltv.collectDataFor(*bitr); + } + + cltv.setUpMaps(); + + for(TransformSet::iterator titr=_transformSet.begin(); + titr!=_transformSet.end(); + ++titr) + { + cltv.disableTransform(*titr); + } + + + return cltv.removeTransforms(nodeWeCannotRemove); +} + +//////////////////////////////////////////////////////////////////////////// +// CombineStaticTransforms +//////////////////////////////////////////////////////////////////////////// + +void Optimizer::CombineStaticTransformsVisitor::apply(osg::MatrixTransform& transform) +{ + if (transform.getDataVariance()==osg::Object::STATIC && + transform.getNumChildren()==1 && + transform.getChild(0)->asTransform()!=0 && + transform.getChild(0)->asTransform()->asMatrixTransform()!=0 && + transform.getChild(0)->asTransform()->getDataVariance()==osg::Object::STATIC && + isOperationPermissibleForObject(&transform) && isOperationPermissibleForObject(transform.getChild(0))) + { + _transformSet.insert(&transform); + } + + traverse(transform); +} + +bool Optimizer::CombineStaticTransformsVisitor::removeTransforms(osg::Node* nodeWeCannotRemove) +{ + if (nodeWeCannotRemove && nodeWeCannotRemove->asTransform()!=0 && nodeWeCannotRemove->asTransform()->asMatrixTransform()!=0) + { + // remove topmost node from transform set if it exists there. + TransformSet::iterator itr = _transformSet.find(nodeWeCannotRemove->asTransform()->asMatrixTransform()); + if (itr!=_transformSet.end()) _transformSet.erase(itr); + } + + bool transformRemoved = false; + + while (!_transformSet.empty()) + { + // get the first available transform to combine. + osg::ref_ptr transform = *_transformSet.begin(); + _transformSet.erase(_transformSet.begin()); + + if (transform->getNumChildren()==1 && + transform->getChild(0)->asTransform()!=0 && + transform->getChild(0)->asTransform()->asMatrixTransform()!=0 && + transform->getChild(0)->asTransform()->getDataVariance()==osg::Object::STATIC) + { + // now combine with its child. + osg::MatrixTransform* child = transform->getChild(0)->asTransform()->asMatrixTransform(); + + osg::Matrix newMatrix = child->getMatrix()*transform->getMatrix(); + child->setMatrix(newMatrix); + if (transform->getStateSet()) + { + if(child->getStateSet()) child->getStateSet()->merge(*transform->getStateSet()); + else child->setStateSet(transform->getStateSet()); + } + + transformRemoved = true; + + osg::Node::ParentList parents = transform->getParents(); + for(osg::Node::ParentList::iterator pitr=parents.begin(); + pitr!=parents.end(); + ++pitr) + { + (*pitr)->replaceChild(transform.get(),child); + } + + } + + } + return transformRemoved; +} + +//////////////////////////////////////////////////////////////////////////// +// RemoveEmptyNodes. +//////////////////////////////////////////////////////////////////////////// + +void Optimizer::RemoveEmptyNodesVisitor::apply(osg::Group& group) +{ + if (group.getNumParents()>0) + { + // only remove empty groups, but not empty occluders. + if (group.getNumChildren()==0 && isOperationPermissibleForObject(&group) && + (typeid(group)==typeid(osg::Group) || (dynamic_cast(&group) && !dynamic_cast(&group))) && + (group.getNumChildrenRequiringUpdateTraversal()==0 && group.getNumChildrenRequiringEventTraversal()==0) ) + { + _redundantNodeList.insert(&group); + } + } + traverse(group); +} + +void Optimizer::RemoveEmptyNodesVisitor::removeEmptyNodes() +{ + + NodeList newEmptyGroups; + + // keep iterator through until scene graph is cleaned of empty nodes. + while (!_redundantNodeList.empty()) + { + for(NodeList::iterator itr=_redundantNodeList.begin(); + itr!=_redundantNodeList.end(); + ++itr) + { + + osg::ref_ptr nodeToRemove = (*itr); + + // take a copy of parents list since subsequent removes will modify the original one. + osg::Node::ParentList parents = nodeToRemove->getParents(); + + for(osg::Node::ParentList::iterator pitr=parents.begin(); + pitr!=parents.end(); + ++pitr) + { + osg::Group* parent = *pitr; + if (!dynamic_cast(parent) && + !dynamic_cast(parent) && + strcmp(parent->className(),"MultiSwitch")!=0) + { + parent->removeChild(nodeToRemove.get()); + if (parent->getNumChildren()==0 && isOperationPermissibleForObject(parent)) newEmptyGroups.insert(parent); + } + } + } + + _redundantNodeList.clear(); + _redundantNodeList.swap(newEmptyGroups); + } +} + + +//////////////////////////////////////////////////////////////////////////// +// RemoveRedundantNodes. +//////////////////////////////////////////////////////////////////////////// + +bool Optimizer::RemoveRedundantNodesVisitor::isOperationPermissible(osg::Node& node) +{ + return node.getNumParents()>0 && + !node.getStateSet() && + node.getName().empty() && + !node.getUserDataContainer() && + !node.getCullCallback() && + !node.getEventCallback() && + !node.getUpdateCallback() && + isOperationPermissibleForObject(&node); +} + +void Optimizer::RemoveRedundantNodesVisitor::apply(osg::Group& group) +{ + if (group.getNumChildren()==1 && + typeid(group)==typeid(osg::Group) && + isOperationPermissible(group)) + { + _redundantNodeList.insert(&group); + } + + traverse(group); +} + + + +void Optimizer::RemoveRedundantNodesVisitor::apply(osg::Transform& transform) +{ + if (transform.getDataVariance()==osg::Object::STATIC && + isOperationPermissible(transform)) + { + osg::Matrix matrix; + transform.computeWorldToLocalMatrix(matrix,NULL); + if (matrix.isIdentity()) + { + _redundantNodeList.insert(&transform); + } + } + traverse(transform); +} + + +void Optimizer::RemoveRedundantNodesVisitor::removeRedundantNodes() +{ + + for(NodeList::iterator itr=_redundantNodeList.begin(); + itr!=_redundantNodeList.end(); + ++itr) + { + osg::ref_ptr group = dynamic_cast(*itr); + if (group.valid()) + { + // take a copy of parents list since subsequent removes will modify the original one. + osg::Node::ParentList parents = group->getParents(); + + if (group->getNumChildren()==1) + { + osg::Node* child = group->getChild(0); + for(osg::Node::ParentList::iterator pitr=parents.begin(); + pitr!=parents.end(); + ++pitr) + { + (*pitr)->replaceChild(group.get(),child); + } + + } + + } + else + { + OSG_WARN<<"Optimizer::RemoveRedundantNodesVisitor::removeRedundantNodes() - failed dynamic_cast"<getStateSet()getStateSet()) return true; + if (rhs->getStateSet()getStateSet()) return false; + + COMPARE_BINDING(lhs->getNormalArray(), rhs->getNormalArray()) + COMPARE_BINDING(lhs->getColorArray(), rhs->getColorArray()) + COMPARE_BINDING(lhs->getSecondaryColorArray(), rhs->getSecondaryColorArray()) + COMPARE_BINDING(lhs->getFogCoordArray(), rhs->getFogCoordArray()) + + + if (lhs->getNumTexCoordArrays()getNumTexCoordArrays()) return true; + if (rhs->getNumTexCoordArrays()getNumTexCoordArrays()) return false; + + // therefore lhs->getNumTexCoordArrays()==rhs->getNumTexCoordArrays() + + unsigned int i; + for(i=0;igetNumTexCoordArrays();++i) + { + if (rhs->getTexCoordArray(i)) + { + if (!lhs->getTexCoordArray(i)) return true; + } + else if (lhs->getTexCoordArray(i)) return false; + } + + for(i=0;igetNumVertexAttribArrays();++i) + { + if (rhs->getVertexAttribArray(i)) + { + if (!lhs->getVertexAttribArray(i)) return true; + } + else if (lhs->getVertexAttribArray(i)) return false; + } + + + if (osg::getBinding(lhs->getNormalArray())==osg::Array::BIND_OVERALL) + { + // assumes that the bindings and arrays are set up correctly, this + // should be the case after running computeCorrectBindingsAndArraySizes(); + const osg::Array* lhs_normalArray = lhs->getNormalArray(); + const osg::Array* rhs_normalArray = rhs->getNormalArray(); + if (lhs_normalArray->getType()getType()) return true; + if (rhs_normalArray->getType()getType()) return false; + switch(lhs_normalArray->getType()) + { + case(osg::Array::Vec3bArrayType): + if ((*static_cast(lhs_normalArray))[0]<(*static_cast(rhs_normalArray))[0]) return true; + if ((*static_cast(rhs_normalArray))[0]<(*static_cast(lhs_normalArray))[0]) return false; + break; + case(osg::Array::Vec3sArrayType): + if ((*static_cast(lhs_normalArray))[0]<(*static_cast(rhs_normalArray))[0]) return true; + if ((*static_cast(rhs_normalArray))[0]<(*static_cast(lhs_normalArray))[0]) return false; + break; + case(osg::Array::Vec3ArrayType): + if ((*static_cast(lhs_normalArray))[0]<(*static_cast(rhs_normalArray))[0]) return true; + if ((*static_cast(rhs_normalArray))[0]<(*static_cast(lhs_normalArray))[0]) return false; + break; + default: + break; + } + } + + if (osg::getBinding(lhs->getColorArray())==osg::Array::BIND_OVERALL) + { + const osg::Array* lhs_colorArray = lhs->getColorArray(); + const osg::Array* rhs_colorArray = rhs->getColorArray(); + if (lhs_colorArray->getType()getType()) return true; + if (rhs_colorArray->getType()getType()) return false; + switch(lhs_colorArray->getType()) + { + case(osg::Array::Vec4ubArrayType): + if ((*static_cast(lhs_colorArray))[0]<(*static_cast(rhs_colorArray))[0]) return true; + if ((*static_cast(rhs_colorArray))[0]<(*static_cast(lhs_colorArray))[0]) return false; + break; + case(osg::Array::Vec3ArrayType): + if ((*static_cast(lhs_colorArray))[0]<(*static_cast(rhs_colorArray))[0]) return true; + if ((*static_cast(rhs_colorArray))[0]<(*static_cast(lhs_colorArray))[0]) return false; + break; + case(osg::Array::Vec4ArrayType): + if ((*static_cast(lhs_colorArray))[0]<(*static_cast(rhs_colorArray))[0]) return true; + if ((*static_cast(rhs_colorArray))[0]<(*static_cast(lhs_colorArray))[0]) return false; + break; + default: + break; + } + + } + + return false; + + } +}; + +struct LessGeometryPrimitiveType +{ + bool operator() (const osg::Geometry* lhs,const osg::Geometry* rhs) const + { + for(unsigned int i=0; + igetNumPrimitiveSets() && igetNumPrimitiveSets(); + ++i) + { + if (lhs->getPrimitiveSet(i)->getType()getPrimitiveSet(i)->getType()) return true; + else if (rhs->getPrimitiveSet(i)->getType()getPrimitiveSet(i)->getType()) return false; + + if (lhs->getPrimitiveSet(i)->getMode()getPrimitiveSet(i)->getMode()) return true; + else if (rhs->getPrimitiveSet(i)->getMode()getPrimitiveSet(i)->getMode()) return false; + + } + return lhs->getNumPrimitiveSets()getNumPrimitiveSets(); + } +}; + + +/// Shortcut to get size of an array, even if pointer is NULL. +inline unsigned int getSize(const osg::Array * a) { return a ? a->getNumElements() : 0; } + +/// When merging geometries, tests if two arrays can be merged, regarding to their number of components, and the number of vertices. +bool isArrayCompatible(unsigned int numVertice1, unsigned int numVertice2, const osg::Array* compare1, const osg::Array* compare2) +{ + // Sumed up truth table: + // If array (1 or 2) not empty and vertices empty => error, should not happen (allows simplification in formulae below) + // If one side has both vertices and array, and the other side has only vertices => then arrays cannot be merged + // Else, arrays can be merged + //assert(numVertice1 || !getSize(compare1)); + //assert(numVertice2 || !getSize(compare2)); + return !( (numVertice1 && !getSize(compare1) && getSize(compare2)) + || (numVertice2 && !getSize(compare2) && getSize(compare1)) ); +} + +/// Return true only if both geometries have same array type and if arrays (such as TexCoords) are compatible (i.e. both empty or both filled) +bool isAbleToMerge(const osg::Geometry& g1, const osg::Geometry& g2) +{ + unsigned int numVertice1( getSize(g1.getVertexArray()) ); + unsigned int numVertice2( getSize(g2.getVertexArray()) ); + + // first verify arrays size + if (!isArrayCompatible(numVertice1,numVertice2,g1.getNormalArray(),g2.getNormalArray()) || + !isArrayCompatible(numVertice1,numVertice2,g1.getColorArray(),g2.getColorArray()) || + !isArrayCompatible(numVertice1,numVertice2,g1.getSecondaryColorArray(),g2.getSecondaryColorArray()) || + !isArrayCompatible(numVertice1,numVertice2,g1.getFogCoordArray(),g2.getFogCoordArray()) || + g1.getNumTexCoordArrays()!=g2.getNumTexCoordArrays()) return false; + + for (unsigned int eachTexCoordArray=0;eachTexCoordArraygetDataType()!=g2.getVertexArray()->getDataType()) return false; + if (g1.getNormalArray() && g2.getNormalArray() && g1.getNormalArray()->getDataType()!=g2.getNormalArray()->getDataType()) return false; + if (g1.getColorArray() && g2.getColorArray() && g1.getColorArray()->getDataType()!=g2.getColorArray()->getDataType()) return false; + if (g1.getSecondaryColorArray() && g2.getSecondaryColorArray() && g1.getSecondaryColorArray()->getDataType()!=g2.getSecondaryColorArray()->getDataType()) return false; + if (g1.getFogCoordArray() && g2.getNormalArray() && g1.getFogCoordArray()->getDataType()!=g2.getFogCoordArray()->getDataType()) return false; + return true; +} + +bool Optimizer::MergeGeometryVisitor::mergeGeode(osg::Geode& geode) +{ + if (!isOperationPermissibleForObject(&geode)) return false; + + if (geode.getNumDrawables()>=2) + { + + // OSG_NOTICE<<"Before "< DuplicateList; + typedef std::vector< osg::ref_ptr > DrawableList; + typedef std::map GeometryDuplicateMap; + + typedef std::vector MergeList; + + GeometryDuplicateMap geometryDuplicateMap; + DrawableList standardDrawables; + + unsigned int i; + for(i=0;iasGeometry(); + if (geom) + { + //geom->computeCorrectBindingsAndArraySizes(); + + if (!geometryContainsSharedArrays(*geom) && + geom->getDataVariance()!=osg::Object::DYNAMIC && + isOperationPermissibleForObject(geom)) + { + geometryDuplicateMap[geom].push_back(geom); + } + else + { + standardDrawables.push_back(drawable); + } + } + else + { + standardDrawables.push_back(drawable); + } + } + } + +#if 1 + // first try to group geometries with the same properties + // (i.e. array types) to avoid loss of data during merging + MergeList mergeListChecked; // List of drawables just before merging, grouped by "compatibility" and vertex limit + MergeList mergeList; // Intermediate list of drawables, grouped ony by "compatibility" + for(GeometryDuplicateMap::iterator itr=geometryDuplicateMap.begin(); + itr!=geometryDuplicateMap.end(); + ++itr) + { + if (itr->second.empty()) continue; + if (itr->second.size()==1) + { + mergeList.push_back(DuplicateList()); + DuplicateList* duplicateList = &mergeList.back(); + duplicateList->push_back(itr->second[0]); + continue; + } + + std::sort(itr->second.begin(),itr->second.end(),LessGeometryPrimitiveType()); + + // initialize the temporary list by pushing the first geometry + MergeList mergeListTmp; + mergeListTmp.push_back(DuplicateList()); + DuplicateList* duplicateList = &mergeListTmp.back(); + duplicateList->push_back(itr->second[0]); + + for(DuplicateList::iterator dupItr=itr->second.begin()+1; + dupItr!=itr->second.end(); + ++dupItr) + { + osg::Geometry* geomToPush = *dupItr; + + // try to group geomToPush with another geometry + MergeList::iterator eachMergeList=mergeListTmp.begin(); + for(;eachMergeList!=mergeListTmp.end();++eachMergeList) + { + if (!eachMergeList->empty() && eachMergeList->front()!=NULL + && isAbleToMerge(*eachMergeList->front(),*geomToPush)) + { + eachMergeList->push_back(geomToPush); + break; + } + } + + // if no suitable group was found, then a new one is created + if (eachMergeList==mergeListTmp.end()) + { + mergeListTmp.push_back(DuplicateList()); + duplicateList = &mergeListTmp.back(); + duplicateList->push_back(geomToPush); + } + } + + // copy the group in the mergeListChecked + for(MergeList::iterator eachMergeList=mergeListTmp.begin();eachMergeList!=mergeListTmp.end();++eachMergeList) + { + mergeListChecked.push_back(*eachMergeList); + } + } + + // then build merge list using _targetMaximumNumberOfVertices + bool needToDoMerge = false; + // dequeue each DuplicateList when vertices limit is reached or when all elements has been checked + for(;!mergeListChecked.empty();) + { + MergeList::iterator itr=mergeListChecked.begin(); + DuplicateList& duplicateList(*itr); + if (duplicateList.size()==0) + { + mergeListChecked.erase(itr); + continue; + } + + if (duplicateList.size()==1) + { + mergeList.push_back(duplicateList); + mergeListChecked.erase(itr); + continue; + } + + unsigned int numVertices(duplicateList.front()->getVertexArray() ? duplicateList.front()->getVertexArray()->getNumElements() : 0); + DuplicateList::iterator eachGeom(duplicateList.begin()+1); + // until all geometries have been checked or _targetMaximumNumberOfVertices is reached + for(;eachGeom!=duplicateList.end(); ++eachGeom) + { + unsigned int numAddVertices((*eachGeom)->getVertexArray() ? (*eachGeom)->getVertexArray()->getNumElements() : 0); + if ((numVertices+numAddVertices)>_targetMaximumNumberOfVertices) + { + break; + } + else + { + numVertices += numAddVertices; + } + } + + // push back if bellow the limit + if (eachGeom==duplicateList.end()) + { + if (duplicateList.size()>1) needToDoMerge = true; + mergeList.push_back(duplicateList); + mergeListChecked.erase(itr); + } + // else split the list to store what is below the limit and retry on what is above + else + { + mergeList.push_back(DuplicateList()); + DuplicateList* duplicateListResult = &mergeList.back(); + duplicateListResult->insert(duplicateListResult->end(),duplicateList.begin(),eachGeom); + duplicateList.erase(duplicateList.begin(),eachGeom); + if (duplicateListResult->size()>1) needToDoMerge = true; + } + } + + if (needToDoMerge) + { + // first take a reference to all the drawables to prevent them being deleted prematurely + typedef std::vector< osg::ref_ptr > DrawableList; + DrawableList keepDrawables; + keepDrawables.resize(geode.getNumDrawables()); + for(i=0; iget()); + } + + // now do the merging of geometries + for(MergeList::iterator mitr = mergeList.begin(); + mitr != mergeList.end(); + ++mitr) + { + DuplicateList& duplicateList = *mitr; + if (duplicateList.size()>1) + { + osg::Geometry* lhs = duplicateList.front(); + geode.addDrawable(lhs); + for(DuplicateList::iterator ditr = duplicateList.begin()+1; + ditr != duplicateList.end(); + ++ditr) + { + mergeGeometry(*lhs,**ditr); + } + } + else if (duplicateList.size()>0) + { + geode.addDrawable(duplicateList.front()); + } + } + } + +#else + // don't merge geometry if its above a maximum number of vertices. + for(GeometryDuplicateMap::iterator itr=geometryDuplicateMap.begin(); + itr!=geometryDuplicateMap.end(); + ++itr) + { + if (itr->second.size()>1) + { + std::sort(itr->second.begin(),itr->second.end(),LessGeometryPrimitiveType()); + osg::Geometry* lhs = itr->second[0]; + for(DuplicateList::iterator dupItr=itr->second.begin()+1; + dupItr!=itr->second.end(); + ++dupItr) + { + + osg::Geometry* rhs = *dupItr; + + if (lhs->getVertexArray() && lhs->getVertexArray()->getNumElements()>=_targetMaximumNumberOfVertices) + { + lhs = rhs; + continue; + } + + if (rhs->getVertexArray() && rhs->getVertexArray()->getNumElements()>=_targetMaximumNumberOfVertices) + { + continue; + } + + if (lhs->getVertexArray() && rhs->getVertexArray() && + (lhs->getVertexArray()->getNumElements()+rhs->getVertexArray()->getNumElements())>=_targetMaximumNumberOfVertices) + { + continue; + } + + if (mergeGeometry(*lhs,*rhs)) + { + geode.removeDrawable(rhs); + + static int co = 0; + OSG_INFO<<"merged and removed Geometry "<<++co<(geode.getDrawable(i)); + if (geom) + { + osg::Geometry::PrimitiveSetList& primitives = geom->getPrimitiveSetList(); + for(osg::Geometry::PrimitiveSetList::iterator itr=primitives.begin(); + itr!=primitives.end(); + ++itr) + { + osg::PrimitiveSet* prim = itr->get(); + if (prim->getMode()==osg::PrimitiveSet::POLYGON) + { + if (prim->getNumIndices()==3) + { + prim->setMode(osg::PrimitiveSet::TRIANGLES); + } + else if (prim->getNumIndices()==4) + { + prim->setMode(osg::PrimitiveSet::QUADS); + } + } + } + } + } + + // now merge any compatible primitives. + for(i=0;i(geode.getDrawable(i)); + if (geom) + { + if (geom->getNumPrimitiveSets()>0 && + osg::getBinding(geom->getNormalArray())!=osg::Array::BIND_PER_PRIMITIVE_SET && + osg::getBinding(geom->getColorArray())!=osg::Array::BIND_PER_PRIMITIVE_SET && + osg::getBinding(geom->getSecondaryColorArray())!=osg::Array::BIND_PER_PRIMITIVE_SET && + osg::getBinding(geom->getFogCoordArray())!=osg::Array::BIND_PER_PRIMITIVE_SET) + { + +#if 1 + bool doneCombine = false; + + osg::Geometry::PrimitiveSetList& primitives = geom->getPrimitiveSetList(); + unsigned int lhsNo=0; + unsigned int rhsNo=1; + while(rhsNogetType()==rhs->getType() && + lhs->getMode()==rhs->getMode()) + { + + switch(lhs->getMode()) + { + case(osg::PrimitiveSet::POINTS): + case(osg::PrimitiveSet::LINES): + case(osg::PrimitiveSet::TRIANGLES): + case(osg::PrimitiveSet::QUADS): + combine = true; + break; + } + + } + + if (combine) + { + + switch(lhs->getType()) + { + case(osg::PrimitiveSet::DrawArraysPrimitiveType): + combine = mergePrimitive(*(static_cast(lhs)),*(static_cast(rhs))); + break; + case(osg::PrimitiveSet::DrawArrayLengthsPrimitiveType): + combine = mergePrimitive(*(static_cast(lhs)),*(static_cast(rhs))); + break; + case(osg::PrimitiveSet::DrawElementsUBytePrimitiveType): + combine = mergePrimitive(*(static_cast(lhs)),*(static_cast(rhs))); + break; + case(osg::PrimitiveSet::DrawElementsUShortPrimitiveType): + combine = mergePrimitive(*(static_cast(lhs)),*(static_cast(rhs))); + break; + case(osg::PrimitiveSet::DrawElementsUIntPrimitiveType): + combine = mergePrimitive(*(static_cast(lhs)),*(static_cast(rhs))); + break; + default: + combine = false; + break; + } + } + + if (combine) + { + // make this primitive set as invalid and needing cleaning up. + rhs->setMode(0xffffff); + doneCombine = true; + ++rhsNo; + } + else + { + lhsNo = rhsNo; + ++rhsNo; + } + } + + #if 1 + if (doneCombine) + { + // now need to clean up primitiveset so it no longer contains the rhs combined primitives. + + // first swap with a empty primitiveSet to empty it completely. + osg::Geometry::PrimitiveSetList oldPrimitives; + primitives.swap(oldPrimitives); + + // now add the active primitive sets + for(osg::Geometry::PrimitiveSetList::iterator pitr = oldPrimitives.begin(); + pitr != oldPrimitives.end(); + ++pitr) + { + if ((*pitr)->getMode()!=0xffffff) primitives.push_back(*pitr); + } + } + #endif + +#else + + osg::Geometry::PrimitiveSetList& primitives = geom->getPrimitiveSetList(); + unsigned int primNo=0; + while(primNo+1getType()==rhs->getType() && + lhs->getMode()==rhs->getMode()) + { + + switch(lhs->getMode()) + { + case(osg::PrimitiveSet::POINTS): + case(osg::PrimitiveSet::LINES): + case(osg::PrimitiveSet::TRIANGLES): + case(osg::PrimitiveSet::QUADS): + combine = true; + break; + } + + } + + if (combine) + { + + switch(lhs->getType()) + { + case(osg::PrimitiveSet::DrawArraysPrimitiveType): + combine = mergePrimitive(*(static_cast(lhs)),*(static_cast(rhs))); + break; + case(osg::PrimitiveSet::DrawArrayLengthsPrimitiveType): + combine = mergePrimitive(*(static_cast(lhs)),*(static_cast(rhs))); + break; + case(osg::PrimitiveSet::DrawElementsUBytePrimitiveType): + combine = mergePrimitive(*(static_cast(lhs)),*(static_cast(rhs))); + break; + case(osg::PrimitiveSet::DrawElementsUShortPrimitiveType): + combine = mergePrimitive(*(static_cast(lhs)),*(static_cast(rhs))); + break; + case(osg::PrimitiveSet::DrawElementsUIntPrimitiveType): + combine = mergePrimitive(*(static_cast(lhs)),*(static_cast(rhs))); + break; + default: + break; + } + } + if (combine) + { + primitives.erase(primitives.begin()+primNo+1); + } + + if (!combine) + { + primNo++; + } + } +#endif + } + } + + + } + +// geode.dirtyBound(); + + + return false; +} + +bool Optimizer::MergeGeometryVisitor::geometryContainsSharedArrays(osg::Geometry& geom) +{ + if (geom.getVertexArray() && geom.getVertexArray()->referenceCount()>1) return true; + if (geom.getNormalArray() && geom.getNormalArray()->referenceCount()>1) return true; + if (geom.getColorArray() && geom.getColorArray()->referenceCount()>1) return true; + if (geom.getSecondaryColorArray() && geom.getSecondaryColorArray()->referenceCount()>1) return true; + if (geom.getFogCoordArray() && geom.getFogCoordArray()->referenceCount()>1) return true; + + + for(unsigned int unit=0;unitreferenceCount()>1) return true; + } + + // shift the indices of the incoming primitives to account for the pre existing geometry. + for(osg::Geometry::PrimitiveSetList::iterator primItr=geom.getPrimitiveSetList().begin(); + primItr!=geom.getPrimitiveSetList().end(); + ++primItr) + { + if ((*primItr)->referenceCount()>1) return true; + } + + + return false; +} + + +class MergeArrayVisitor : public osg::ArrayVisitor +{ + protected: + osg::Array* _lhs; + int _offset; + public: + MergeArrayVisitor() : + _lhs(0), + _offset(0) {} + + + /// try to merge the content of two arrays. + bool merge(osg::Array* lhs,osg::Array* rhs, int offset=0) + { + if (lhs==0 || rhs==0) return true; + if (lhs->getType()!=rhs->getType()) return false; + + _lhs = lhs; + _offset = offset; + + rhs->accept(*this); + return true; + } + + template + void _merge(T& rhs) + { + T* lhs = static_cast(_lhs); + lhs->insert(lhs->end(),rhs.begin(),rhs.end()); + } + + template + void _mergeAndOffset(T& rhs) + { + T* lhs = static_cast(_lhs); + + typename T::iterator itr; + for(itr = rhs.begin(); + itr != rhs.end(); + ++itr) + { + lhs->push_back(*itr + _offset); + } + } + + virtual void apply(osg::Array&) { OSG_WARN << "Warning: Optimizer's MergeArrayVisitor cannot merge Array type." << std::endl; } + + virtual void apply(osg::ByteArray& rhs) { if (_offset) _mergeAndOffset(rhs); else _merge(rhs); } + virtual void apply(osg::ShortArray& rhs) { if (_offset) _mergeAndOffset(rhs); else _merge(rhs); } + virtual void apply(osg::IntArray& rhs) { if (_offset) _mergeAndOffset(rhs); else _merge(rhs); } + virtual void apply(osg::UByteArray& rhs) { if (_offset) _mergeAndOffset(rhs); else _merge(rhs); } + virtual void apply(osg::UShortArray& rhs) { if (_offset) _mergeAndOffset(rhs); else _merge(rhs); } + virtual void apply(osg::UIntArray& rhs) { if (_offset) _mergeAndOffset(rhs); else _merge(rhs); } + + virtual void apply(osg::Vec4ubArray& rhs) { _merge(rhs); } + virtual void apply(osg::FloatArray& rhs) { _merge(rhs); } + virtual void apply(osg::Vec2Array& rhs) { _merge(rhs); } + virtual void apply(osg::Vec3Array& rhs) { _merge(rhs); } + virtual void apply(osg::Vec4Array& rhs) { _merge(rhs); } + + virtual void apply(osg::DoubleArray& rhs) { _merge(rhs); } + virtual void apply(osg::Vec2dArray& rhs) { _merge(rhs); } + virtual void apply(osg::Vec3dArray& rhs) { _merge(rhs); } + virtual void apply(osg::Vec4dArray& rhs) { _merge(rhs); } + + virtual void apply(osg::Vec2bArray& rhs) { _merge(rhs); } + virtual void apply(osg::Vec3bArray& rhs) { _merge(rhs); } + virtual void apply(osg::Vec4bArray& rhs) { _merge(rhs); } + virtual void apply(osg::Vec2sArray& rhs) { _merge(rhs); } + virtual void apply(osg::Vec3sArray& rhs) { _merge(rhs); } + virtual void apply(osg::Vec4sArray& rhs) { _merge(rhs); } +}; + +bool Optimizer::MergeGeometryVisitor::mergeGeometry(osg::Geometry& lhs,osg::Geometry& rhs) +{ + + MergeArrayVisitor merger; + + unsigned int base = 0; + if (lhs.getVertexArray() && rhs.getVertexArray()) + { + + base = lhs.getVertexArray()->getNumElements(); + if (!merger.merge(lhs.getVertexArray(),rhs.getVertexArray())) + { + OSG_DEBUG << "MergeGeometry: vertex array not merged. Some data may be lost." <getBinding()!=osg::Array::BIND_OVERALL) + { + if (!merger.merge(lhs.getNormalArray(),rhs.getNormalArray())) + { + OSG_DEBUG << "MergeGeometry: normal array not merged. Some data may be lost." <getBinding()!=osg::Array::BIND_OVERALL) + { + if (!merger.merge(lhs.getColorArray(),rhs.getColorArray())) + { + OSG_DEBUG << "MergeGeometry: color array not merged. Some data may be lost." <getBinding()!=osg::Array::BIND_OVERALL) + { + if (!merger.merge(lhs.getSecondaryColorArray(),rhs.getSecondaryColorArray())) + { + OSG_DEBUG << "MergeGeometry: secondary color array not merged. Some data may be lost." <getBinding()!=osg::Array::BIND_OVERALL) + { + if (!merger.merge(lhs.getFogCoordArray(),rhs.getFogCoordArray())) + { + OSG_DEBUG << "MergeGeometry: fog coord array not merged. Some data may be lost." <get(); + + switch(primitive->getType()) + { + case(osg::PrimitiveSet::DrawElementsUBytePrimitiveType): + { + osg::DrawElementsUByte* primitiveUByte = static_cast(primitive); + unsigned int currentMaximum = 0; + for(osg::DrawElementsUByte::iterator eitr=primitiveUByte->begin(); + eitr!=primitiveUByte->end(); + ++eitr) + { + currentMaximum = osg::maximum(currentMaximum,(unsigned int)*eitr); + } + if ((base+currentMaximum)>=65536) + { + // must promote to a DrawElementsUInt + osg::DrawElementsUInt* new_primitive = new osg::DrawElementsUInt(primitive->getMode()); + std::copy(primitiveUByte->begin(),primitiveUByte->end(),std::back_inserter(*new_primitive)); + new_primitive->offsetIndices(base); + (*primItr) = new_primitive; + } else if ((base+currentMaximum)>=256) + { + // must promote to a DrawElementsUShort + osg::DrawElementsUShort* new_primitive = new osg::DrawElementsUShort(primitive->getMode()); + std::copy(primitiveUByte->begin(),primitiveUByte->end(),std::back_inserter(*new_primitive)); + new_primitive->offsetIndices(base); + (*primItr) = new_primitive; + } + else + { + primitive->offsetIndices(base); + } + } + break; + + case(osg::PrimitiveSet::DrawElementsUShortPrimitiveType): + { + osg::DrawElementsUShort* primitiveUShort = static_cast(primitive); + unsigned int currentMaximum = 0; + for(osg::DrawElementsUShort::iterator eitr=primitiveUShort->begin(); + eitr!=primitiveUShort->end(); + ++eitr) + { + currentMaximum = osg::maximum(currentMaximum,(unsigned int)*eitr); + } + if ((base+currentMaximum)>=65536) + { + // must promote to a DrawElementsUInt + osg::DrawElementsUInt* new_primitive = new osg::DrawElementsUInt(primitive->getMode()); + std::copy(primitiveUShort->begin(),primitiveUShort->end(),std::back_inserter(*new_primitive)); + new_primitive->offsetIndices(base); + (*primItr) = new_primitive; + } + else + { + primitive->offsetIndices(base); + } + } + break; + + case(osg::PrimitiveSet::DrawArraysPrimitiveType): + case(osg::PrimitiveSet::DrawArrayLengthsPrimitiveType): + case(osg::PrimitiveSet::DrawElementsUIntPrimitiveType): + default: + primitive->offsetIndices(base); + break; + } + } + + for(primItr=rhs.getPrimitiveSetList().begin(); primItr!=rhs.getPrimitiveSetList().end(); ++primItr) + { + lhs.addPrimitiveSet(primItr->get()); + } + + lhs.dirtyBound(); + lhs.dirtyDisplayList(); + + return true; +} + +bool Optimizer::MergeGeometryVisitor::mergePrimitive(osg::DrawArrays& lhs,osg::DrawArrays& rhs) +{ + if (lhs.getFirst()+lhs.getCount()==rhs.getFirst()) + { + lhs.setCount(lhs.getCount()+rhs.getCount()); + return true; + } + return false; +} + +bool Optimizer::MergeGeometryVisitor::mergePrimitive(osg::DrawArrayLengths& lhs,osg::DrawArrayLengths& rhs) +{ + int lhs_count = std::accumulate(lhs.begin(),lhs.end(),0); + + if (lhs.getFirst()+lhs_count==rhs.getFirst()) + { + lhs.insert(lhs.end(),rhs.begin(),rhs.end()); + return true; + } + return false; +} + +bool Optimizer::MergeGeometryVisitor::mergePrimitive(osg::DrawElementsUByte& lhs,osg::DrawElementsUByte& rhs) +{ + lhs.insert(lhs.end(),rhs.begin(),rhs.end()); + return true; +} + +bool Optimizer::MergeGeometryVisitor::mergePrimitive(osg::DrawElementsUShort& lhs,osg::DrawElementsUShort& rhs) +{ + lhs.insert(lhs.end(),rhs.begin(),rhs.end()); + return true; +} + +bool Optimizer::MergeGeometryVisitor::mergePrimitive(osg::DrawElementsUInt& lhs,osg::DrawElementsUInt& rhs) +{ + lhs.insert(lhs.end(),rhs.begin(),rhs.end()); + return true; +} + diff --git a/components/sceneutil/optimizer.hpp b/components/sceneutil/optimizer.hpp new file mode 100644 index 000000000..c58b57b11 --- /dev/null +++ b/components/sceneutil/optimizer.hpp @@ -0,0 +1,415 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * OpenSceneGraph Public License for more details. +*/ + +/* Modified for OpenMW */ + +#ifndef OPENMW_OSGUTIL_OPTIMIZER +#define OPENMW_OSGUTIL_OPTIMIZER + +#include +#include +#include +#include +#include + +//#include + +#include + +//namespace osgUtil { +namespace SceneUtil { + +// forward declare +class Optimizer; + +/** Helper base class for implementing Optimizer techniques.*/ +class BaseOptimizerVisitor : public osg::NodeVisitor +{ + public: + + BaseOptimizerVisitor(Optimizer* optimizer, unsigned int operation): + osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN), + _optimizer(optimizer), + _operationType(operation) + { + setNodeMaskOverride(0xffffffff); + } + + inline bool isOperationPermissibleForObject(const osg::StateSet* object) const; + inline bool isOperationPermissibleForObject(const osg::StateAttribute* object) const; + inline bool isOperationPermissibleForObject(const osg::Drawable* object) const; + inline bool isOperationPermissibleForObject(const osg::Node* object) const; + + protected: + + Optimizer* _optimizer; + unsigned int _operationType; +}; + +/** Traverses scene graph to improve efficiency. See OptimizationOptions. + * For example of usage see examples/osgimpostor or osgviewer. + */ + +class Optimizer +{ + + public: + + Optimizer() {} + virtual ~Optimizer() {} + + enum OptimizationOptions + { + FLATTEN_STATIC_TRANSFORMS = (1 << 0), + REMOVE_REDUNDANT_NODES = (1 << 1), + REMOVE_LOADED_PROXY_NODES = (1 << 2), + COMBINE_ADJACENT_LODS = (1 << 3), + SHARE_DUPLICATE_STATE = (1 << 4), + MERGE_GEOMETRY = (1 << 5), + CHECK_GEOMETRY = (1 << 6), // deprecated, currently no-op + MAKE_FAST_GEOMETRY = (1 << 7), + SPATIALIZE_GROUPS = (1 << 8), + COPY_SHARED_NODES = (1 << 9), + TRISTRIP_GEOMETRY = (1 << 10), + TESSELLATE_GEOMETRY = (1 << 11), + OPTIMIZE_TEXTURE_SETTINGS = (1 << 12), + MERGE_GEODES = (1 << 13), + FLATTEN_BILLBOARDS = (1 << 14), + TEXTURE_ATLAS_BUILDER = (1 << 15), + STATIC_OBJECT_DETECTION = (1 << 16), + FLATTEN_STATIC_TRANSFORMS_DUPLICATING_SHARED_SUBGRAPHS = (1 << 17), + INDEX_MESH = (1 << 18), + VERTEX_POSTTRANSFORM = (1 << 19), + VERTEX_PRETRANSFORM = (1 << 20), + DEFAULT_OPTIMIZATIONS = FLATTEN_STATIC_TRANSFORMS | + REMOVE_REDUNDANT_NODES | + REMOVE_LOADED_PROXY_NODES | + COMBINE_ADJACENT_LODS | + SHARE_DUPLICATE_STATE | + MERGE_GEOMETRY | + MAKE_FAST_GEOMETRY | + CHECK_GEOMETRY | + OPTIMIZE_TEXTURE_SETTINGS | + STATIC_OBJECT_DETECTION, + ALL_OPTIMIZATIONS = FLATTEN_STATIC_TRANSFORMS_DUPLICATING_SHARED_SUBGRAPHS | + REMOVE_REDUNDANT_NODES | + REMOVE_LOADED_PROXY_NODES | + COMBINE_ADJACENT_LODS | + SHARE_DUPLICATE_STATE | + MERGE_GEODES | + MERGE_GEOMETRY | + MAKE_FAST_GEOMETRY | + CHECK_GEOMETRY | + SPATIALIZE_GROUPS | + COPY_SHARED_NODES | + TRISTRIP_GEOMETRY | + OPTIMIZE_TEXTURE_SETTINGS | + TEXTURE_ATLAS_BUILDER | + STATIC_OBJECT_DETECTION + }; + + /** Reset internal data to initial state - the getPermissibleOptionsMap is cleared.*/ + void reset(); + + /** Traverse the node and its subgraph with a series of optimization + * visitors, specified by the OptimizationOptions.*/ + virtual void optimize(osg::Node* node, unsigned int options); + + + /** Callback for customizing what operations are permitted on objects in the scene graph.*/ + struct IsOperationPermissibleForObjectCallback : public osg::Referenced + { + virtual bool isOperationPermissibleForObjectImplementation(const Optimizer* optimizer, const osg::StateSet* stateset,unsigned int option) const + { + return optimizer->isOperationPermissibleForObjectImplementation(stateset,option); + } + + virtual bool isOperationPermissibleForObjectImplementation(const Optimizer* optimizer, const osg::StateAttribute* attribute,unsigned int option) const + { + return optimizer->isOperationPermissibleForObjectImplementation(attribute,option); + } + + virtual bool isOperationPermissibleForObjectImplementation(const Optimizer* optimizer, const osg::Drawable* drawable,unsigned int option) const + { + return optimizer->isOperationPermissibleForObjectImplementation(drawable,option); + } + + virtual bool isOperationPermissibleForObjectImplementation(const Optimizer* optimizer, const osg::Node* node,unsigned int option) const + { + return optimizer->isOperationPermissibleForObjectImplementation(node,option); + } + + }; + + /** Set the callback for customizing what operations are permitted on objects in the scene graph.*/ + void setIsOperationPermissibleForObjectCallback(IsOperationPermissibleForObjectCallback* callback) { _isOperationPermissibleForObjectCallback=callback; } + + /** Get the callback for customizing what operations are permitted on objects in the scene graph.*/ + IsOperationPermissibleForObjectCallback* getIsOperationPermissibleForObjectCallback() { return _isOperationPermissibleForObjectCallback.get(); } + + /** Get the callback for customizing what operations are permitted on objects in the scene graph.*/ + const IsOperationPermissibleForObjectCallback* getIsOperationPermissibleForObjectCallback() const { return _isOperationPermissibleForObjectCallback.get(); } + + + inline void setPermissibleOptimizationsForObject(const osg::Object* object, unsigned int options) + { + _permissibleOptimizationsMap[object] = options; + } + + inline unsigned int getPermissibleOptimizationsForObject(const osg::Object* object) const + { + PermissibleOptimizationsMap::const_iterator itr = _permissibleOptimizationsMap.find(object); + if (itr!=_permissibleOptimizationsMap.end()) return itr->second; + else return 0xffffffff; + } + + + inline bool isOperationPermissibleForObject(const osg::StateSet* object, unsigned int option) const + { + if (_isOperationPermissibleForObjectCallback.valid()) + return _isOperationPermissibleForObjectCallback->isOperationPermissibleForObjectImplementation(this,object,option); + else + return isOperationPermissibleForObjectImplementation(object,option); + } + + inline bool isOperationPermissibleForObject(const osg::StateAttribute* object, unsigned int option) const + { + if (_isOperationPermissibleForObjectCallback.valid()) + return _isOperationPermissibleForObjectCallback->isOperationPermissibleForObjectImplementation(this,object,option); + else + return isOperationPermissibleForObjectImplementation(object,option); + } + + inline bool isOperationPermissibleForObject(const osg::Drawable* object, unsigned int option) const + { + if (_isOperationPermissibleForObjectCallback.valid()) + return _isOperationPermissibleForObjectCallback->isOperationPermissibleForObjectImplementation(this,object,option); + else + return isOperationPermissibleForObjectImplementation(object,option); + } + + inline bool isOperationPermissibleForObject(const osg::Node* object, unsigned int option) const + { + if (_isOperationPermissibleForObjectCallback.valid()) + return _isOperationPermissibleForObjectCallback->isOperationPermissibleForObjectImplementation(this,object,option); + else + return isOperationPermissibleForObjectImplementation(object,option); + } + + bool isOperationPermissibleForObjectImplementation(const osg::StateSet* stateset, unsigned int option) const + { + return (option & getPermissibleOptimizationsForObject(stateset))!=0; + } + + bool isOperationPermissibleForObjectImplementation(const osg::StateAttribute* attribute, unsigned int option) const + { + return (option & getPermissibleOptimizationsForObject(attribute))!=0; + } + + bool isOperationPermissibleForObjectImplementation(const osg::Drawable* drawable, unsigned int option) const + { + if (option & (REMOVE_REDUNDANT_NODES|MERGE_GEOMETRY)) + { + if (drawable->getUserData()) return false; + if (drawable->getUpdateCallback()) return false; + if (drawable->getEventCallback()) return false; + if (drawable->getCullCallback()) return false; + } + return (option & getPermissibleOptimizationsForObject(drawable))!=0; + } + + bool isOperationPermissibleForObjectImplementation(const osg::Node* node, unsigned int option) const + { + if (option & (REMOVE_REDUNDANT_NODES|COMBINE_ADJACENT_LODS|FLATTEN_STATIC_TRANSFORMS)) + { + if (node->getUserData()) return false; + if (node->getUpdateCallback()) return false; + if (node->getEventCallback()) return false; + if (node->getCullCallback()) return false; + if (node->getNumDescriptions()>0) return false; + if (node->getStateSet()) return false; + if (node->getNodeMask()!=0xffffffff) return false; + // if (!node->getName().empty()) return false; + } + + return (option & getPermissibleOptimizationsForObject(node))!=0; + } + + protected: + + osg::ref_ptr _isOperationPermissibleForObjectCallback; + + typedef std::map PermissibleOptimizationsMap; + PermissibleOptimizationsMap _permissibleOptimizationsMap; + + public: + + /** Flatten Static Transform nodes by applying their transform to the + * geometry on the leaves of the scene graph, then removing the + * now redundant transforms. Static transformed subgraphs that have multiple + * parental paths above them are not flattened, if you require this then + * the subgraphs have to be duplicated - for this use the + * FlattenStaticTransformsDuplicatingSharedSubgraphsVisitor. */ + class FlattenStaticTransformsVisitor : public BaseOptimizerVisitor + { + public: + + FlattenStaticTransformsVisitor(Optimizer* optimizer=0): + BaseOptimizerVisitor(optimizer, FLATTEN_STATIC_TRANSFORMS) {} + + virtual void apply(osg::Node& geode); + virtual void apply(osg::Drawable& drawable); + virtual void apply(osg::Billboard& geode); + virtual void apply(osg::ProxyNode& node); + virtual void apply(osg::PagedLOD& node); + virtual void apply(osg::Transform& transform); + + bool removeTransforms(osg::Node* nodeWeCannotRemove); + + protected: + + typedef std::vector TransformStack; + typedef std::set DrawableSet; + typedef std::set BillboardSet; + typedef std::set NodeSet; + typedef std::set TransformSet; + + TransformStack _transformStack; + NodeSet _excludedNodeSet; + DrawableSet _drawableSet; + BillboardSet _billboardSet; + TransformSet _transformSet; + }; + + + /** Combine Static Transform nodes that sit above one another.*/ + class CombineStaticTransformsVisitor : public BaseOptimizerVisitor + { + public: + + CombineStaticTransformsVisitor(Optimizer* optimizer=0): + BaseOptimizerVisitor(optimizer, FLATTEN_STATIC_TRANSFORMS) {} + + virtual void apply(osg::MatrixTransform& transform); + + bool removeTransforms(osg::Node* nodeWeCannotRemove); + + protected: + + typedef std::set TransformSet; + TransformSet _transformSet; + }; + + /** Remove rendundant nodes, such as groups with one single child.*/ + class RemoveEmptyNodesVisitor : public BaseOptimizerVisitor + { + public: + + + typedef std::set NodeList; + NodeList _redundantNodeList; + + RemoveEmptyNodesVisitor(Optimizer* optimizer=0): + BaseOptimizerVisitor(optimizer, REMOVE_REDUNDANT_NODES) {} + + virtual void apply(osg::Group& group); + + void removeEmptyNodes(); + + }; + + /** Remove redundant nodes, such as groups with one single child.*/ + class RemoveRedundantNodesVisitor : public BaseOptimizerVisitor + { + public: + + typedef std::set NodeList; + NodeList _redundantNodeList; + + RemoveRedundantNodesVisitor(Optimizer* optimizer=0): + BaseOptimizerVisitor(optimizer, REMOVE_REDUNDANT_NODES) {} + + virtual void apply(osg::Group& group); + virtual void apply(osg::Transform& transform); + + bool isOperationPermissible(osg::Node& node); + + void removeRedundantNodes(); + + }; + + class MergeGeometryVisitor : public BaseOptimizerVisitor + { + public: + + /// default to traversing all children. + MergeGeometryVisitor(Optimizer* optimizer=0) : + BaseOptimizerVisitor(optimizer, MERGE_GEOMETRY), + _targetMaximumNumberOfVertices(10000) {} + + void setTargetMaximumNumberOfVertices(unsigned int num) + { + _targetMaximumNumberOfVertices = num; + } + + unsigned int getTargetMaximumNumberOfVertices() const + { + return _targetMaximumNumberOfVertices; + } + + virtual void apply(osg::Geode& geode) { mergeGeode(geode); } + virtual void apply(osg::Billboard&) { /* don't do anything*/ } + + bool mergeGeode(osg::Geode& geode); + + static bool geometryContainsSharedArrays(osg::Geometry& geom); + + static bool mergeGeometry(osg::Geometry& lhs,osg::Geometry& rhs); + + static bool mergePrimitive(osg::DrawArrays& lhs,osg::DrawArrays& rhs); + static bool mergePrimitive(osg::DrawArrayLengths& lhs,osg::DrawArrayLengths& rhs); + static bool mergePrimitive(osg::DrawElementsUByte& lhs,osg::DrawElementsUByte& rhs); + static bool mergePrimitive(osg::DrawElementsUShort& lhs,osg::DrawElementsUShort& rhs); + static bool mergePrimitive(osg::DrawElementsUInt& lhs,osg::DrawElementsUInt& rhs); + + protected: + + unsigned int _targetMaximumNumberOfVertices; + + }; + +}; + +inline bool BaseOptimizerVisitor::isOperationPermissibleForObject(const osg::StateSet* object) const +{ + return _optimizer ? _optimizer->isOperationPermissibleForObject(object,_operationType) : true; +} + +inline bool BaseOptimizerVisitor::isOperationPermissibleForObject(const osg::StateAttribute* object) const +{ + return _optimizer ? _optimizer->isOperationPermissibleForObject(object,_operationType) : true; +} + +inline bool BaseOptimizerVisitor::isOperationPermissibleForObject(const osg::Drawable* object) const +{ + return _optimizer ? _optimizer->isOperationPermissibleForObject(object,_operationType) : true; +} + +inline bool BaseOptimizerVisitor::isOperationPermissibleForObject(const osg::Node* object) const +{ + return _optimizer ? _optimizer->isOperationPermissibleForObject(object,_operationType) : true; +} + +} + +#endif From f7cb4bd245f719772e2abdb637bb47d40849900b Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 23 Feb 2017 19:46:54 +0100 Subject: [PATCH 12/25] optimizer: remove some cruft --- components/sceneutil/optimizer.cpp | 97 +++--------------------------- components/sceneutil/optimizer.hpp | 2 - 2 files changed, 7 insertions(+), 92 deletions(-) diff --git a/components/sceneutil/optimizer.cpp b/components/sceneutil/optimizer.cpp index 36d589170..956c9053b 100644 --- a/components/sceneutil/optimizer.cpp +++ b/components/sceneutil/optimizer.cpp @@ -18,29 +18,18 @@ #include "optimizer.hpp" -#include #include #include #include #include #include -#include #include #include -#include -#include -#include #include -#include -#include -#include #include -#include #include #include -#include -#include #include #include @@ -53,6 +42,9 @@ using namespace osgUtil; +namespace SceneUtil +{ + void Optimizer::reset() { } @@ -90,20 +82,6 @@ void Optimizer::optimize(osg::Node* node, unsigned int options) cstv.removeTransforms(node); } - if (options & MERGE_GEODES) - { - OSG_INFO<<"Optimizer::optimize() doing MERGE_GEODES"<tick(); - - MergeGeodesVisitor visitor; - node->accept(visitor); - - osg::Timer_t endTick = osg::Timer::instance()->tick(); - - OSG_INFO<<"MERGE_GEODES took "<delta_s(startTick,endTick)<delta_s(startTick,endTick)<accept(tsv); - tsv.stripify(); - } - if (options & REMOVE_REDUNDANT_NODES) { OSG_INFO<<"Optimizer::optimize() doing REMOVE_REDUNDANT_NODES"<accept(fbv); - fbv.process(); - } - - if (options & SPATIALIZE_GROUPS) - { - OSG_INFO<<"Optimizer::optimize() doing SPATIALIZE_GROUPS"<accept(sv); - sv.divide(); - } - - if (options & INDEX_MESH) - { - OSG_INFO<<"Optimizer::optimize() doing INDEX_MESH"<accept(imv); - imv.makeMesh(); - } - if (options & VERTEX_POSTTRANSFORM) { OSG_INFO<<"Optimizer::optimize() doing VERTEX_POSTTRANSFORM"<supports(_transformFunctor)) return false; return BaseOptimizerVisitor::isOperationPermissibleForObject(drawable); } inline bool isOperationPermissibleForObject(const osg::Node* node) const { - // disable if object is a light point node. - if (strcmp(node->className(),"LightPointNode")==0) return false; - if (dynamic_cast(node)) return false; - if (dynamic_cast(node)) return false; return BaseOptimizerVisitor::isOperationPermissibleForObject(node); } @@ -634,28 +573,10 @@ bool CollectLowestTransformsVisitor::removeTransforms(osg::Node* nodeWeCannotRem void Optimizer::FlattenStaticTransformsVisitor::apply(osg::Node& node) { - if (strcmp(node.className(),"LightPointNode")==0) - { - _excludedNodeSet.insert(&node); - } traverse(node); } -void Optimizer::FlattenStaticTransformsVisitor::apply(osg::ProxyNode& node) -{ - _excludedNodeSet.insert(&node); - - traverse(node); -} - -void Optimizer::FlattenStaticTransformsVisitor::apply(osg::PagedLOD& node) -{ - _excludedNodeSet.insert(&node); - - traverse(node); -} - void Optimizer::FlattenStaticTransformsVisitor::apply(osg::Drawable& drawable) { osg::Geometry *geometry = drawable.asGeometry(); @@ -811,7 +732,7 @@ void Optimizer::RemoveEmptyNodesVisitor::apply(osg::Group& group) { // only remove empty groups, but not empty occluders. if (group.getNumChildren()==0 && isOperationPermissibleForObject(&group) && - (typeid(group)==typeid(osg::Group) || (dynamic_cast(&group) && !dynamic_cast(&group))) && + (typeid(group)==typeid(osg::Group) || (dynamic_cast(&group))) && (group.getNumChildrenRequiringUpdateTraversal()==0 && group.getNumChildrenRequiringEventTraversal()==0) ) { _redundantNodeList.insert(&group); @@ -843,13 +764,8 @@ void Optimizer::RemoveEmptyNodesVisitor::removeEmptyNodes() ++pitr) { osg::Group* parent = *pitr; - if (!dynamic_cast(parent) && - !dynamic_cast(parent) && - strcmp(parent->className(),"MultiSwitch")!=0) - { - parent->removeChild(nodeToRemove.get()); - if (parent->getNumChildren()==0 && isOperationPermissibleForObject(parent)) newEmptyGroups.insert(parent); - } + parent->removeChild(nodeToRemove.get()); + if (parent->getNumChildren()==0 && isOperationPermissibleForObject(parent)) newEmptyGroups.insert(parent); } } @@ -1887,3 +1803,4 @@ bool Optimizer::MergeGeometryVisitor::mergePrimitive(osg::DrawElementsUInt& lhs, return true; } +} diff --git a/components/sceneutil/optimizer.hpp b/components/sceneutil/optimizer.hpp index c58b57b11..d2a63a32a 100644 --- a/components/sceneutil/optimizer.hpp +++ b/components/sceneutil/optimizer.hpp @@ -270,8 +270,6 @@ class Optimizer virtual void apply(osg::Node& geode); virtual void apply(osg::Drawable& drawable); virtual void apply(osg::Billboard& geode); - virtual void apply(osg::ProxyNode& node); - virtual void apply(osg::PagedLOD& node); virtual void apply(osg::Transform& transform); bool removeTransforms(osg::Node* nodeWeCannotRemove); From 698738c649810c22582177b466e29db18c6f768e Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 23 Feb 2017 19:50:46 +0100 Subject: [PATCH 13/25] optimizer: use asXYZ() instead of dynamic_cast --- components/sceneutil/optimizer.cpp | 32 ++++++++++++++++++------------ 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/components/sceneutil/optimizer.cpp b/components/sceneutil/optimizer.cpp index 956c9053b..195439d7a 100644 --- a/components/sceneutil/optimizer.cpp +++ b/components/sceneutil/optimizer.cpp @@ -234,12 +234,15 @@ class CollectLowestTransformsVisitor : public BaseOptimizerVisitor inline bool isOperationPermissibleForObject(const osg::Object* object) const { - const osg::Drawable* drawable = dynamic_cast(object); - if (drawable) return isOperationPermissibleForObject(drawable); - - const osg::Node* node = dynamic_cast(object); - if (node) return isOperationPermissibleForObject(node); - + const osg::Node* node = object->asNode(); + if (node) + { + const osg::Drawable* drawable = node->asDrawable(); + if (drawable) + return isOperationPermissibleForObject(drawable); + else + return isOperationPermissibleForObject(node); + } return true; } @@ -343,7 +346,10 @@ class CollectLowestTransformsVisitor : public BaseOptimizerVisitor void CollectLowestTransformsVisitor::doTransform(osg::Object* obj,osg::Matrix& matrix) { - osg::Drawable* drawable = dynamic_cast(obj); + osg::Node* node = obj->asNode(); + if (!node) + return; + osg::Drawable* drawable = node->asDrawable(); if (drawable) { osgUtil::TransformAttributeFunctor tf(matrix); @@ -543,11 +549,11 @@ bool CollectLowestTransformsVisitor::removeTransforms(osg::Node* nodeWeCannotRem } else { - osg::MatrixTransform* mt = dynamic_cast(titr->first); + osg::MatrixTransform* mt = titr->first->asMatrixTransform(); if (mt) mt->setMatrix(osg::Matrix::identity()); else { - osg::PositionAttitudeTransform* pat = dynamic_cast(titr->first); + osg::PositionAttitudeTransform* pat = titr->first->asPositionAttitudeTransform(); if (pat) { pat->setPosition(osg::Vec3(0.0f,0.0f,0.0f)); @@ -732,7 +738,7 @@ void Optimizer::RemoveEmptyNodesVisitor::apply(osg::Group& group) { // only remove empty groups, but not empty occluders. if (group.getNumChildren()==0 && isOperationPermissibleForObject(&group) && - (typeid(group)==typeid(osg::Group) || (dynamic_cast(&group))) && + (typeid(group)==typeid(osg::Group) || (group.asTransform())) && (group.getNumChildrenRequiringUpdateTraversal()==0 && group.getNumChildrenRequiringEventTraversal()==0) ) { _redundantNodeList.insert(&group); @@ -828,7 +834,7 @@ void Optimizer::RemoveRedundantNodesVisitor::removeRedundantNodes() itr!=_redundantNodeList.end(); ++itr) { - osg::ref_ptr group = dynamic_cast(*itr); + osg::ref_ptr group = (*itr)->asGroup(); if (group.valid()) { // take a copy of parents list since subsequent removes will modify the original one. @@ -1288,7 +1294,7 @@ bool Optimizer::MergeGeometryVisitor::mergeGeode(osg::Geode& geode) unsigned int i; for(i=0;i(geode.getDrawable(i)); + osg::Geometry* geom = geode.getDrawable(i)->asGeometry(); if (geom) { osg::Geometry::PrimitiveSetList& primitives = geom->getPrimitiveSetList(); @@ -1315,7 +1321,7 @@ bool Optimizer::MergeGeometryVisitor::mergeGeode(osg::Geode& geode) // now merge any compatible primitives. for(i=0;i(geode.getDrawable(i)); + osg::Geometry* geom = geode.getDrawable(i)->asGeometry(); if (geom) { if (geom->getNumPrimitiveSets()>0 && From af716d4b61db39d3228f8edf268748af4569328c Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 23 Feb 2017 20:02:04 +0100 Subject: [PATCH 14/25] optimizer: remove hardcoded condition in RemoveRedundantNodesVisitor --- components/sceneutil/optimizer.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/components/sceneutil/optimizer.cpp b/components/sceneutil/optimizer.cpp index 195439d7a..510f05fbd 100644 --- a/components/sceneutil/optimizer.cpp +++ b/components/sceneutil/optimizer.cpp @@ -789,8 +789,6 @@ bool Optimizer::RemoveRedundantNodesVisitor::isOperationPermissible(osg::Node& n { return node.getNumParents()>0 && !node.getStateSet() && - node.getName().empty() && - !node.getUserDataContainer() && !node.getCullCallback() && !node.getEventCallback() && !node.getUpdateCallback() && From f2a323238f9948c2cce08a956f3d5b99cfc53d59 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 23 Feb 2017 20:06:56 +0100 Subject: [PATCH 15/25] optimizer: merge groups as part of REMOVE_REDUNDANT_NODES --- components/sceneutil/optimizer.cpp | 53 ++++++++++++++++++++++++++++++ components/sceneutil/optimizer.hpp | 14 ++++++++ 2 files changed, 67 insertions(+) diff --git a/components/sceneutil/optimizer.cpp b/components/sceneutil/optimizer.cpp index 510f05fbd..812a5ae6a 100644 --- a/components/sceneutil/optimizer.cpp +++ b/components/sceneutil/optimizer.cpp @@ -109,6 +109,8 @@ void Optimizer::optimize(osg::Node* node, unsigned int options) node->accept(rrnv); rrnv.removeRedundantNodes(); + MergeGroupsVisitor mgrp(this); + node->accept(mgrp); } if (options & VERTEX_POSTTRANSFORM) @@ -1807,4 +1809,55 @@ bool Optimizer::MergeGeometryVisitor::mergePrimitive(osg::DrawElementsUInt& lhs, return true; } + + +bool Optimizer::MergeGroupsVisitor::isOperationPermissible(osg::Group& node) +{ + return !node.asTransform() && + !node.getCullCallback() && + !node.getEventCallback() && + !node.getUpdateCallback() && + isOperationPermissibleForObject(&node); +} + +void Optimizer::MergeGroupsVisitor::apply(osg::Group &group) +{ + if (group.getNumChildren() <= 1) + traverse(group); + else + { + typedef std::map > GroupMap; + GroupMap childGroups; + for (unsigned int i=0; iasGroup(); + if (childGroup && isOperationPermissible(*childGroup)) + { + childGroups[childGroup->getStateSet()].insert(childGroup); + } + } + + for (GroupMap::iterator it = childGroups.begin(); it != childGroups.end(); ++it) + { + const std::set& groupSet = it->second; + if (groupSet.size() <= 1) + continue; + else + { + osg::Group* first = *groupSet.begin(); + for (std::set::const_iterator groupIt = ++groupSet.begin(); groupIt != groupSet.end(); ++groupIt) + { + osg::Group* toMerge = *groupIt; + for (unsigned int i=0; igetNumChildren(); ++i) + first->addChild(toMerge->getChild(i)); + toMerge->removeChildren(0, toMerge->getNumChildren()); + group.removeChild(toMerge); + } + } + } + traverse(group); + } +} + } diff --git a/components/sceneutil/optimizer.hpp b/components/sceneutil/optimizer.hpp index d2a63a32a..b43a339ca 100644 --- a/components/sceneutil/optimizer.hpp +++ b/components/sceneutil/optimizer.hpp @@ -346,6 +346,20 @@ class Optimizer }; + /** Merge adjacent Groups that have the same StateSet. */ + class MergeGroupsVisitor : public SceneUtil::BaseOptimizerVisitor + { + public: + MergeGroupsVisitor(SceneUtil::Optimizer* optimizer) + : BaseOptimizerVisitor(optimizer, REMOVE_REDUNDANT_NODES) + { + } + + bool isOperationPermissible(osg::Group& node); + + virtual void apply(osg::Group& group); + }; + class MergeGeometryVisitor : public BaseOptimizerVisitor { public: From 43f31d6e54daf9f06f52026fd369b56180128c42 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 23 Feb 2017 20:26:33 +0100 Subject: [PATCH 16/25] optimizer: fix MERGE_GEOMETRY to work with Geometries not attached to a Geode --- components/sceneutil/optimizer.cpp | 57 ++++++++++-------------------- components/sceneutil/optimizer.hpp | 4 +-- 2 files changed, 20 insertions(+), 41 deletions(-) diff --git a/components/sceneutil/optimizer.cpp b/components/sceneutil/optimizer.cpp index 812a5ae6a..7b37564eb 100644 --- a/components/sceneutil/optimizer.cpp +++ b/components/sceneutil/optimizer.cpp @@ -1031,15 +1031,14 @@ bool isAbleToMerge(const osg::Geometry& g1, const osg::Geometry& g2) return true; } -bool Optimizer::MergeGeometryVisitor::mergeGeode(osg::Geode& geode) + +bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group) { - if (!isOperationPermissibleForObject(&geode)) return false; + if (!isOperationPermissibleForObject(&group)) return false; - if (geode.getNumDrawables()>=2) + if (group.getNumChildren()>=2) { - // OSG_NOTICE<<"Before "< DuplicateList; typedef std::vector< osg::ref_ptr > DrawableList; typedef std::map GeometryDuplicateMap; @@ -1050,9 +1049,9 @@ bool Optimizer::MergeGeometryVisitor::mergeGeode(osg::Geode& geode) DrawableList standardDrawables; unsigned int i; - for(i=0;iasDrawable(); if (drawable) { osg::Geometry* geom = drawable->asGeometry(); @@ -1194,27 +1193,6 @@ bool Optimizer::MergeGeometryVisitor::mergeGeode(osg::Geode& geode) if (needToDoMerge) { - // first take a reference to all the drawables to prevent them being deleted prematurely - typedef std::vector< osg::ref_ptr > DrawableList; - DrawableList keepDrawables; - keepDrawables.resize(geode.getNumDrawables()); - for(i=0; iget()); - } - // now do the merging of geometries for(MergeList::iterator mitr = mergeList.begin(); mitr != mergeList.end(); @@ -1224,18 +1202,15 @@ bool Optimizer::MergeGeometryVisitor::mergeGeode(osg::Geode& geode) if (duplicateList.size()>1) { osg::Geometry* lhs = duplicateList.front(); - geode.addDrawable(lhs); for(DuplicateList::iterator ditr = duplicateList.begin()+1; ditr != duplicateList.end(); ++ditr) { mergeGeometry(*lhs,**ditr); + + group.removeChild(*ditr); } } - else if (duplicateList.size()>0) - { - geode.addDrawable(duplicateList.front()); - } } } @@ -1285,16 +1260,17 @@ bool Optimizer::MergeGeometryVisitor::mergeGeode(osg::Geode& geode) } #endif - // OSG_NOTICE<<"After "<asGeometry(); + osg::Drawable* drawable = group.getChild(i)->asDrawable(); + if (!drawable) + continue; + osg::Geometry* geom = drawable->asGeometry(); if (geom) { osg::Geometry::PrimitiveSetList& primitives = geom->getPrimitiveSetList(); @@ -1319,9 +1295,12 @@ bool Optimizer::MergeGeometryVisitor::mergeGeode(osg::Geode& geode) } // now merge any compatible primitives. - for(i=0;iasGeometry(); + osg::Drawable* drawable = group.getChild(i)->asDrawable(); + if (!drawable) + continue; + osg::Geometry* geom = drawable->asGeometry(); if (geom) { if (geom->getNumPrimitiveSets()>0 && diff --git a/components/sceneutil/optimizer.hpp b/components/sceneutil/optimizer.hpp index b43a339ca..e6ae6768b 100644 --- a/components/sceneutil/optimizer.hpp +++ b/components/sceneutil/optimizer.hpp @@ -379,10 +379,10 @@ class Optimizer return _targetMaximumNumberOfVertices; } - virtual void apply(osg::Geode& geode) { mergeGeode(geode); } + virtual void apply(osg::Group& group) { mergeGroup(group); traverse(group); } virtual void apply(osg::Billboard&) { /* don't do anything*/ } - bool mergeGeode(osg::Geode& geode); + bool mergeGroup(osg::Group& group); static bool geometryContainsSharedArrays(osg::Geometry& geom); From ebfd845eae978639d0b9bd5e3aef78696ffe89b1 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 23 Feb 2017 20:27:01 +0100 Subject: [PATCH 17/25] optimizer: run MERGE_GEOMETRY after removing redundant nodes --- components/resource/scenemanager.cpp | 2 +- components/sceneutil/optimizer.cpp | 30 ++++++++++++++-------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index d0fd9f446..f35ff69c1 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -485,7 +485,7 @@ namespace Resource SceneUtil::Optimizer optimizer; optimizer.setIsOperationPermissibleForObjectCallback(new CanOptimizeCallback); - optimizer.optimize(loaded, SceneUtil::Optimizer::FLATTEN_STATIC_TRANSFORMS|SceneUtil::Optimizer::REMOVE_REDUNDANT_NODES); //MERGE_GEOMETRY + optimizer.optimize(loaded, SceneUtil::Optimizer::FLATTEN_STATIC_TRANSFORMS|SceneUtil::Optimizer::REMOVE_REDUNDANT_NODES|SceneUtil::Optimizer::MERGE_GEOMETRY); } if (mIncrementalCompileOperation) diff --git a/components/sceneutil/optimizer.cpp b/components/sceneutil/optimizer.cpp index 7b37564eb..011669a54 100644 --- a/components/sceneutil/optimizer.cpp +++ b/components/sceneutil/optimizer.cpp @@ -82,21 +82,6 @@ void Optimizer::optimize(osg::Node* node, unsigned int options) cstv.removeTransforms(node); } - if (options & MERGE_GEOMETRY) - { - OSG_INFO<<"Optimizer::optimize() doing MERGE_GEOMETRY"<tick(); - - MergeGeometryVisitor mgv(this); - mgv.setTargetMaximumNumberOfVertices(10000); - node->accept(mgv); - - osg::Timer_t endTick = osg::Timer::instance()->tick(); - - OSG_INFO<<"MERGE_GEOMETRY took "<delta_s(startTick,endTick)<accept(mgrp); } + if (options & MERGE_GEOMETRY) + { + OSG_INFO<<"Optimizer::optimize() doing MERGE_GEOMETRY"<tick(); + + MergeGeometryVisitor mgv(this); + mgv.setTargetMaximumNumberOfVertices(10000); + node->accept(mgv); + + osg::Timer_t endTick = osg::Timer::instance()->tick(); + + OSG_INFO<<"MERGE_GEOMETRY took "<delta_s(startTick,endTick)< Date: Thu, 23 Feb 2017 20:41:07 +0100 Subject: [PATCH 18/25] optimizer: don't merge Geometry that has transparency sorting enabled --- components/sceneutil/optimizer.cpp | 45 ++++++++++++++++++++++++++++++ components/sceneutil/optimizer.hpp | 11 ++++++-- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/components/sceneutil/optimizer.cpp b/components/sceneutil/optimizer.cpp index 011669a54..b975e5463 100644 --- a/components/sceneutil/optimizer.cpp +++ b/components/sceneutil/optimizer.cpp @@ -1032,6 +1032,51 @@ bool isAbleToMerge(const osg::Geometry& g1, const osg::Geometry& g2) } +void Optimizer::MergeGeometryVisitor::pushStateSet(osg::StateSet *stateSet) +{ + _stateSetStack.push_back(stateSet); + checkAllowedToMerge(); +} + +void Optimizer::MergeGeometryVisitor::popStateSet() +{ + _stateSetStack.pop_back(); + checkAllowedToMerge(); +} + +void Optimizer::MergeGeometryVisitor::checkAllowedToMerge() +{ + int renderingHint = 0; + bool override = false; + for (std::vector::const_iterator it = _stateSetStack.begin(); it != _stateSetStack.end(); ++it) + { + osg::StateSet* stateSet = *it; + osg::StateSet::RenderBinMode mode = stateSet->getRenderBinMode(); + if (override && (!mode & osg::StateSet::PROTECTED_RENDERBIN_DETAILS)) + continue; + if (mode & osg::StateSet::USE_RENDERBIN_DETAILS) + renderingHint = stateSet->getRenderingHint(); + if (mode & osg::StateSet::OVERRIDE_RENDERBIN_DETAILS) + override = true; + } + // Can't merge Geometry that are using a transparent sorting bin as that would cause the sorting to break. + _allowedToMerge = renderingHint != osg::StateSet::TRANSPARENT_BIN; +} + +void Optimizer::MergeGeometryVisitor::apply(osg::Group &group) +{ + if (group.getStateSet()) + pushStateSet(group.getStateSet()); + + if (_allowedToMerge) + mergeGroup(group); + + traverse(group); + + if (group.getStateSet()) + popStateSet(); +} + bool Optimizer::MergeGeometryVisitor::mergeGroup(osg::Group& group) { if (!isOperationPermissibleForObject(&group)) return false; diff --git a/components/sceneutil/optimizer.hpp b/components/sceneutil/optimizer.hpp index e6ae6768b..804938074 100644 --- a/components/sceneutil/optimizer.hpp +++ b/components/sceneutil/optimizer.hpp @@ -367,7 +367,7 @@ class Optimizer /// default to traversing all children. MergeGeometryVisitor(Optimizer* optimizer=0) : BaseOptimizerVisitor(optimizer, MERGE_GEOMETRY), - _targetMaximumNumberOfVertices(10000) {} + _targetMaximumNumberOfVertices(10000), _allowedToMerge(true) {} void setTargetMaximumNumberOfVertices(unsigned int num) { @@ -379,7 +379,11 @@ class Optimizer return _targetMaximumNumberOfVertices; } - virtual void apply(osg::Group& group) { mergeGroup(group); traverse(group); } + void pushStateSet(osg::StateSet* stateSet); + void popStateSet(); + void checkAllowedToMerge(); + + virtual void apply(osg::Group& group); virtual void apply(osg::Billboard&) { /* don't do anything*/ } bool mergeGroup(osg::Group& group); @@ -397,7 +401,8 @@ class Optimizer protected: unsigned int _targetMaximumNumberOfVertices; - + std::vector _stateSetStack; + bool _allowedToMerge; }; }; From 3df7a8c4d8cb014a3aa693ded677bcacfd6ff433 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 23 Feb 2017 22:05:32 +0100 Subject: [PATCH 19/25] Avoid FLATTEN_STATIC_TRANSFORMS optimization for non-Geometry drawables --- components/resource/scenemanager.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index f35ff69c1..8d717f7ea 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -392,6 +392,18 @@ namespace Resource return reservedNames.find(name) != reservedNames.end(); } + virtual bool isOperationPermissibleForObjectImplementation(const SceneUtil::Optimizer* optimizer, const osg::Drawable* node,unsigned int option) const + { + if (option & SceneUtil::Optimizer::FLATTEN_STATIC_TRANSFORMS) + { + if (node->asGeometry() && node->className() == std::string("Geometry")) + return true; + else + return false; //ParticleSystem would have to convert space of all the processors, RigGeometry would have to convert bones... theoretically possible, but very complicated + } + return (option & optimizer->getPermissibleOptimizationsForObject(node))!=0; + } + virtual bool isOperationPermissibleForObjectImplementation(const SceneUtil::Optimizer* optimizer, const osg::Node* node,unsigned int option) const { if (node->getNumDescriptions()>0) return false; From 5198fc897dfd24cc67f1830ac3d383dd971e612b Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 23 Feb 2017 22:23:34 +0100 Subject: [PATCH 20/25] Fix collision glitch caused by Bullet AABB not being updated when an actor moves without turning --- apps/openmw/mwphysics/physicssystem.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index ebf51a66e..454dc611d 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -1423,6 +1423,7 @@ namespace MWPhysics waterlevel, slowFall, mCollisionWorld, mStandingCollisions); physicActor->setPosition(position); } + mCollisionWorld->updateSingleAabb(physicActor->getCollisionObject()); float interpolationFactor = mTimeAccum / physicsDt; osg::Vec3f interpolated = position * interpolationFactor + physicActor->getPreviousPosition() * (1.f - interpolationFactor); From fb073e5c1441e95f0b8ee3021ee9b64c28a53919 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 23 Feb 2017 22:34:42 +0100 Subject: [PATCH 21/25] Avoid unnecessary AABB update for rotationally invariant collision shapes --- apps/openmw/mwphysics/actor.cpp | 10 ++++++++++ apps/openmw/mwphysics/actor.hpp | 7 +++++++ apps/openmw/mwphysics/physicssystem.cpp | 7 +++++-- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index e0edd515d..2730bf4f0 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -32,9 +32,14 @@ Actor::Actor(const MWWorld::Ptr& ptr, osg::ref_ptr if (std::abs(mHalfExtents.x()-mHalfExtents.y())= mHalfExtents.x()) { mShape.reset(new btCapsuleShapeZ(mHalfExtents.x(), 2*mHalfExtents.z() - 2*mHalfExtents.x())); + mRotationallyInvariant = true; } else + { mShape.reset(new btBoxShape(toBullet(mHalfExtents))); + mRotationallyInvariant = false; + } + mConvexShape = static_cast(mShape.get()); mCollisionObject.reset(new btCollisionObject); @@ -144,6 +149,11 @@ void Actor::updateRotation () updateCollisionObjectPosition(); } +bool Actor::isRotationallyInvariant() const +{ + return mRotationallyInvariant; +} + void Actor::updateScale() { float scale = mPtr.getCellRef().getScale(); diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index a640640e2..88f7f5537 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -72,6 +72,11 @@ namespace MWPhysics void updateScale(); void updateRotation(); + /** + * Return true if the collision shape looks the same no matter how its Z rotated. + */ + bool isRotationallyInvariant() const; + /** * Set mPosition and mPreviousPosition to the position in the Ptr's RefData. This should be used * when an object is "instantly" moved/teleported as opposed to being moved by the physics simulation. @@ -155,6 +160,8 @@ namespace MWPhysics bool mCanWaterWalk; bool mWalkingOnWater; + bool mRotationallyInvariant; + std::auto_ptr mShape; btConvexShape* mConvexShape; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 454dc611d..c3e2d7c1b 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -1294,8 +1294,11 @@ namespace MWPhysics ActorMap::iterator foundActor = mActors.find(ptr); if (foundActor != mActors.end()) { - foundActor->second->updateRotation(); - mCollisionWorld->updateSingleAabb(foundActor->second->getCollisionObject()); + if (!foundActor->second->isRotationallyInvariant()) + { + foundActor->second->updateRotation(); + mCollisionWorld->updateSingleAabb(foundActor->second->getCollisionObject()); + } return; } } From a55604c549357c61890239168d64c23dcad276b8 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 23 Feb 2017 22:35:17 +0100 Subject: [PATCH 22/25] Avoid unnecessary AABB update when actor position has not changed --- apps/openmw/mwphysics/physicssystem.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index c3e2d7c1b..40a3070d8 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -1419,14 +1419,18 @@ namespace MWPhysics osg::Vec3f position = physicActor->getPosition(); float oldHeight = position.z(); + bool positionChanged = false; for (int i=0; igetPtr(), physicActor, iter->second, physicsDt, world->isFlying(iter->first), waterlevel, slowFall, mCollisionWorld, mStandingCollisions); - physicActor->setPosition(position); + if (position != physicActor->getPosition()) + positionChanged = true; + physicActor->setPosition(position); // always set even if unchanged to make sure interpolation is correct } - mCollisionWorld->updateSingleAabb(physicActor->getCollisionObject()); + if (positionChanged) + mCollisionWorld->updateSingleAabb(physicActor->getCollisionObject()); float interpolationFactor = mTimeAccum / physicsDt; osg::Vec3f interpolated = position * interpolationFactor + physicActor->getPreviousPosition() * (1.f - interpolationFactor); From afa39d121fe8c19e1b5112f71c4a6e6f05cca04a Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 23 Feb 2017 22:58:41 +0100 Subject: [PATCH 23/25] Fix 'part has no parent' warning caused by destructing in the wrong order --- apps/openmw/mwrender/npcanimation.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index e95626c82..aa53c41ac 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -269,6 +269,7 @@ const NpcAnimation::PartBoneMap NpcAnimation::sPartList = createPartListMap(); NpcAnimation::~NpcAnimation() { + mAmmunition.reset(); } NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem, @@ -880,6 +881,7 @@ void NpcAnimation::addControllers() void NpcAnimation::showWeapons(bool showWeapon) { mShowWeapons = showWeapon; + mAmmunition.reset(); if(showWeapon) { MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); @@ -898,11 +900,7 @@ void NpcAnimation::showWeapons(bool showWeapon) MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); if (ammo != inv.end() && ammo->get()->mBase->mData.mType == ESM::Weapon::Bolt) attachArrow(); - else - mAmmunition.reset(); } - else - mAmmunition.reset(); } } else From 75677f03e74c0b695d65ea02a3281ec8a5658074 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 24 Feb 2017 00:43:36 +0100 Subject: [PATCH 24/25] Remove SceneManager::notifyAttached --- apps/openmw/mwrender/creatureanimation.cpp | 1 - apps/openmw/mwrender/effectmanager.cpp | 1 - apps/openmw/mwrender/npcanimation.cpp | 1 - components/resource/scenemanager.cpp | 18 +++++++----------- components/resource/scenemanager.hpp | 3 --- 5 files changed, 7 insertions(+), 17 deletions(-) diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index a90de2589..24732f866 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -122,7 +122,6 @@ void CreatureWeaponAnimation::updatePart(PartHolderPtr& scene, int slot) if (found == nodeMap.end()) throw std::runtime_error("Can't find attachment node " + bonename); osg::ref_ptr attached = SceneUtil::attach(node, mObjectRoot, bonename, found->second.get()); - mResourceSystem->getSceneManager()->notifyAttached(attached); scene.reset(new PartHolder(attached)); diff --git a/apps/openmw/mwrender/effectmanager.cpp b/apps/openmw/mwrender/effectmanager.cpp index 340a855fb..3e785a769 100644 --- a/apps/openmw/mwrender/effectmanager.cpp +++ b/apps/openmw/mwrender/effectmanager.cpp @@ -52,7 +52,6 @@ void EffectManager::addEffect(const std::string &model, const std::string& textu overrideTexture(textureOverride, mResourceSystem, node); mParentNode->addChild(trans); - mResourceSystem->getSceneManager()->notifyAttached(node); mEffects[trans] = effect; } diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index aa53c41ac..6ead1611e 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -663,7 +663,6 @@ PartHolderPtr NpcAnimation::insertBoundedPart(const std::string& model, const st throw std::runtime_error("Can't find attachment node " + bonename); osg::ref_ptr attached = SceneUtil::attach(instance, mObjectRoot, bonefilter, found->second); - mResourceSystem->getSceneManager()->notifyAttached(attached); if (enchantedGlow) addGlow(attached, *glowColor); diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 8d717f7ea..85c27cbc2 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -540,6 +540,13 @@ namespace Resource // add a ref to the original template, to hint to the cache that it's still being used and should be kept in cache cloned->getOrCreateUserDataContainer()->addUserObject(new TemplateRef(scene)); + // we can skip any scene graphs without update callbacks since we know that particle emitters will have an update callback set + if (cloned->getNumChildrenRequiringUpdateTraversal() > 0) + { + InitParticlesVisitor visitor (mParticleSystemMask); + cloned->accept(visitor); + } + return cloned; } @@ -566,7 +573,6 @@ namespace Resource void SceneManager::attachTo(osg::Node *instance, osg::Group *parentNode) const { parentNode->addChild(instance); - notifyAttached(instance); } void SceneManager::releaseGLObjects(osg::State *state) @@ -580,16 +586,6 @@ namespace Resource mIncrementalCompileOperation = ico; } - void SceneManager::notifyAttached(osg::Node *node) const - { - // we can skip any scene graphs without update callbacks since we know that particle emitters will have an update callback set - if (node->getNumChildrenRequiringUpdateTraversal() > 0) - { - InitParticlesVisitor visitor (mParticleSystemMask); - node->accept(visitor); - } - } - Resource::ImageManager* SceneManager::getImageManager() { return mImageManager; diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 5d54ccb1c..62ffe0871 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -117,9 +117,6 @@ namespace Resource /// Set up an IncrementalCompileOperation for background compiling of loaded scenes. void setIncrementalCompileOperation(osgUtil::IncrementalCompileOperation* ico); - /// @note SceneManager::attachTo calls this method automatically, only needs to be called by users if manually attaching - void notifyAttached(osg::Node* node) const; - Resource::ImageManager* getImageManager(); /// @param mask The node mask to apply to loaded particle system nodes. From 330e5fefd1274b21e42af59f5ed2b01ebefb0f01 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 24 Feb 2017 02:43:31 +0100 Subject: [PATCH 25/25] optimizer: consider a Group with more than one child redundant as well While there could be some value in this hierarchy (i.e. improved culling), we don't know if this is being used sensibly; and using a 'flat' hierarchy helps other optimizations. --- components/sceneutil/optimizer.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/components/sceneutil/optimizer.cpp b/components/sceneutil/optimizer.cpp index b975e5463..b0686d524 100644 --- a/components/sceneutil/optimizer.cpp +++ b/components/sceneutil/optimizer.cpp @@ -840,18 +840,20 @@ void Optimizer::RemoveRedundantNodesVisitor::removeRedundantNodes() // take a copy of parents list since subsequent removes will modify the original one. osg::Node::ParentList parents = group->getParents(); - if (group->getNumChildren()==1) + for(osg::Node::ParentList::iterator pitr=parents.begin(); + pitr!=parents.end(); + ++pitr) { - osg::Node* child = group->getChild(0); - for(osg::Node::ParentList::iterator pitr=parents.begin(); - pitr!=parents.end(); - ++pitr) + for (unsigned int i=0; igetNumChildren(); ++i) { - (*pitr)->replaceChild(group.get(),child); + osg::Node* child = group->getChild(i); + (*pitr)->addChild(child); } + (*pitr)->removeChild(group); } + group->removeChildren(0, group->getNumChildren()); } else {