From e33829d4933b9788e1ebafa5052f377ff1778bc2 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 23 Feb 2017 19:42:12 +0100 Subject: [PATCH] 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