You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
openmw/components/sceneutil/optimizer.cpp

1894 lines
66 KiB
C++

/* -*-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 <stdlib.h>
#include <string.h>
#include "optimizer.hpp"
#include <osg/Transform>
#include <osg/MatrixTransform>
#include <osg/PositionAttitudeTransform>
#include <osg/LOD>
#include <osg/Billboard>
#include <osg/Geometry>
#include <osg/Notify>
#include <osg/Texture>
#include <osg/Timer>
#include <osg/io_utils>
#include <osgUtil/TransformAttributeFunctor>
#include <osgUtil/Statistics>
#include <osgUtil/MeshOptimizers>
#include <typeinfo>
#include <algorithm>
#include <numeric>
#include <sstream>
#include <iterator>
using namespace osgUtil;
namespace SceneUtil
{
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<<std::endl<<"Stats before:"<<std::endl;
stats.print(osg::notify(osg::NOTICE));
}
if (options & FLATTEN_STATIC_TRANSFORMS)
{
OSG_INFO<<"Optimizer::optimize() doing FLATTEN_STATIC_TRANSFORMS"<<std::endl;
int i=0;
bool result = false;
do
{
OSG_DEBUG << "** RemoveStaticTransformsVisitor *** Pass "<<i<<std::endl;
FlattenStaticTransformsVisitor fstv(this);
node->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 & REMOVE_REDUNDANT_NODES)
{
OSG_INFO<<"Optimizer::optimize() doing REMOVE_REDUNDANT_NODES"<<std::endl;
RemoveEmptyNodesVisitor renv(this);
node->accept(renv);
renv.removeEmptyNodes();
RemoveRedundantNodesVisitor rrnv(this);
node->accept(rrnv);
rrnv.removeRedundantNodes();
MergeGroupsVisitor mgrp(this);
node->accept(mgrp);
}
if (options & MERGE_GEOMETRY)
{
OSG_INFO<<"Optimizer::optimize() doing MERGE_GEOMETRY"<<std::endl;
osg::Timer_t startTick = osg::Timer::instance()->tick();
MergeGeometryVisitor mgv(this);
mgv.setTargetMaximumNumberOfVertices(10000);
node->accept(mgv);
osg::Timer_t endTick = osg::Timer::instance()->tick();
OSG_INFO<<"MERGE_GEOMETRY took "<<osg::Timer::instance()->delta_s(startTick,endTick)<<std::endl;
}
if (options & VERTEX_POSTTRANSFORM)
{
OSG_INFO<<"Optimizer::optimize() doing VERTEX_POSTTRANSFORM"<<std::endl;
VertexCacheVisitor vcv;
node->accept(vcv);
vcv.optimizeVertices();
}
if (options & VERTEX_PRETRANSFORM)
{
OSG_INFO<<"Optimizer::optimize() doing VERTEX_PRETRANSFORM"<<std::endl;
VertexAccessOrderVisitor vaov;
node->accept(vaov);
vaov.optimizeOrder();
}
if (osg::getNotifyLevel()>=osg::INFO)
{
stats.reset();
node->accept(stats);
stats.totalUpStats();
OSG_NOTICE<<std::endl<<"Stats after:"<<std::endl;
stats.print(osg::notify(osg::NOTICE));
}
}
////////////////////////////////////////////////////////////////////////////
// Flatten static transforms
////////////////////////////////////////////////////////////////////////////
class CollectLowestTransformsVisitor : public BaseOptimizerVisitor
{
public:
CollectLowestTransformsVisitor(Optimizer* optimizer=0):
BaseOptimizerVisitor(optimizer,Optimizer::FLATTEN_STATIC_TRANSFORMS),
_transformFunctor(osg::Matrix())
{
setTraversalMode(osg::NodeVisitor::TRAVERSE_PARENTS);
}
virtual void apply(osg::Node& node)
{
if (node.getNumParents())
{
traverse(node);
}
else
{
// for all current objects mark a NULL transform for them.
registerWithCurrentObjects(0);
}
}
virtual void apply(osg::LOD& lod)
{
_currentObjectList.push_back(&lod);
traverse(lod);
_currentObjectList.pop_back();
}
virtual void apply(osg::Transform& transform)
{
// for all current objects associated this transform with them.
registerWithCurrentObjects(&transform);
}
virtual void apply(osg::Geode& geode)
{
traverse(geode);
}
virtual void apply(osg::Billboard& geode)
{
traverse(geode);
}
void collectDataFor(osg::Node* node)
{
_currentObjectList.push_back(node);
node->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::Node* node = object->asNode();
if (node)
{
const osg::Drawable* drawable = node->asDrawable();
if (drawable)
return isOperationPermissibleForObject(drawable);
else
return isOperationPermissibleForObject(node);
}
return true;
}
inline bool isOperationPermissibleForObject(const osg::Drawable* drawable) const
{
return BaseOptimizerVisitor::isOperationPermissibleForObject(drawable);
}
inline bool isOperationPermissibleForObject(const osg::Node* node) const
{
return BaseOptimizerVisitor::isOperationPermissibleForObject(node);
}
protected:
struct TransformStruct
{
typedef std::set<osg::Object*> ObjectSet;
TransformStruct():_canBeApplied(true) {}
void add(osg::Object* obj)
{
_objectSet.insert(obj);
}
bool _canBeApplied;
ObjectSet _objectSet;
};
struct ObjectStruct
{
typedef std::set<osg::Transform*> 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<osg::Transform*,TransformStruct> TransformMap;
typedef std::map<osg::Object*,ObjectStruct> ObjectMap;
typedef std::vector<osg::Object*> 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::Node* node = obj->asNode();
if (!node)
return;
osg::Drawable* drawable = node->asDrawable();
if (drawable)
{
osgUtil::TransformAttributeFunctor tf(matrix);
drawable->accept(tf);
drawable->dirtyBound();
drawable->dirtyDisplayList();
return;
}
osg::LOD* lod = dynamic_cast<osg::LOD*>(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;i<lod->getNumRanges();++i)
{
lod->setRange(i,lod->getMinRange(i)*ratio,lod->getMaxRange(i)*ratio);
}
lod->dirtyBound();
return;
}
osg::Billboard* billboard = dynamic_cast<osg::Billboard*>(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;i<billboard->getNumDrawables();++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<osg::Transform> transform = titr->first;
osg::ref_ptr<osg::Group> group = new osg::Group;
group->setName( transform->getName() );
group->setDataVariance(osg::Object::STATIC);
group->setNodeMask(transform->getNodeMask());
group->setStateSet(transform->getStateSet());
group->setUpdateCallback(transform->getUpdateCallback());
group->setEventCallback(transform->getEventCallback());
group->setCullCallback(transform->getCullCallback());
group->setUserDataContainer(transform->getUserDataContainer());
group->setDescriptions(transform->getDescriptions());
for(unsigned int i=0;i<transform->getNumChildren();++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 = titr->first->asMatrixTransform();
if (mt) mt->setMatrix(osg::Matrix::identity());
else
{
osg::PositionAttitudeTransform* pat = titr->first->asPositionAttitudeTransform();
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*)"<<std::endl;
OSG_WARN<<" unhandled of setting of indentity matrix on "<< titr->first->className()<<std::endl;
OSG_WARN<<" model will appear in the incorrect position."<<std::endl;
}
}
}
}
}
_objectMap.clear();
_transformMap.clear();
return transformRemoved;
}
void Optimizer::FlattenStaticTransformsVisitor::apply(osg::Node& node)
{
traverse(node);
}
void Optimizer::FlattenStaticTransformsVisitor::apply(osg::Drawable& drawable)
{
osg::Geometry *geometry = drawable.asGeometry();
if((geometry) && (isOperationPermissibleForObject(&drawable)))
{
if(geometry->getVertexArray() && geometry->getVertexArray()->referenceCount() > 1) {
geometry->setVertexArray(dynamic_cast<osg::Array*>(geometry->getVertexArray()->clone(osg::CopyOp::DEEP_COPY_ALL)));
}
if(geometry->getNormalArray() && geometry->getNormalArray()->referenceCount() > 1) {
geometry->setNormalArray(dynamic_cast<osg::Array*>(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<osg::MatrixTransform> 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) || (group.asTransform())) &&
(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<osg::Node> 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;
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.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<osg::Group> group = (*itr)->asGroup();
if (group.valid())
{
// take a copy of parents list since subsequent removes will modify the original one.
osg::Node::ParentList parents = group->getParents();
for(osg::Node::ParentList::iterator pitr=parents.begin();
pitr!=parents.end();
++pitr)
{
unsigned int childIndex = (*pitr)->getChildIndex(group);
for (unsigned int i=0; i<group->getNumChildren(); ++i)
{
osg::Node* child = group->getChild(i);
(*pitr)->insertChild(childIndex++, child);
}
(*pitr)->removeChild(group);
}
group->removeChildren(0, group->getNumChildren());
}
else
{
OSG_WARN<<"Optimizer::RemoveRedundantNodesVisitor::removeRedundantNodes() - failed dynamic_cast"<<std::endl;
}
}
_redundantNodeList.clear();
}
////////////////////////////////////////////////////////////////////////////
// code to merge geometry object which share, state, and attribute bindings.
////////////////////////////////////////////////////////////////////////////
#define COMPARE_BINDING(lhs, rhs) \
if (osg::getBinding(lhs)<osg::getBinding(rhs)) return true; \
if (osg::getBinding(rhs)<osg::getBinding(lhs)) return false;
struct LessGeometry
{
bool operator() (const osg::Geometry* lhs,const osg::Geometry* rhs) const
{
if (lhs->getStateSet()<rhs->getStateSet()) return true;
if (rhs->getStateSet()<lhs->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()<rhs->getNumTexCoordArrays()) return true;
if (rhs->getNumTexCoordArrays()<lhs->getNumTexCoordArrays()) return false;
// therefore lhs->getNumTexCoordArrays()==rhs->getNumTexCoordArrays()
unsigned int i;
for(i=0;i<lhs->getNumTexCoordArrays();++i)
{
if (rhs->getTexCoordArray(i))
{
if (!lhs->getTexCoordArray(i)) return true;
}
else if (lhs->getTexCoordArray(i)) return false;
}
for(i=0;i<lhs->getNumVertexAttribArrays();++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()<rhs_normalArray->getType()) return true;
if (rhs_normalArray->getType()<lhs_normalArray->getType()) return false;
switch(lhs_normalArray->getType())
{
case(osg::Array::Vec3bArrayType):
if ((*static_cast<const osg::Vec3bArray*>(lhs_normalArray))[0]<(*static_cast<const osg::Vec3bArray*>(rhs_normalArray))[0]) return true;
if ((*static_cast<const osg::Vec3bArray*>(rhs_normalArray))[0]<(*static_cast<const osg::Vec3bArray*>(lhs_normalArray))[0]) return false;
break;
case(osg::Array::Vec3sArrayType):
if ((*static_cast<const osg::Vec3sArray*>(lhs_normalArray))[0]<(*static_cast<const osg::Vec3sArray*>(rhs_normalArray))[0]) return true;
if ((*static_cast<const osg::Vec3sArray*>(rhs_normalArray))[0]<(*static_cast<const osg::Vec3sArray*>(lhs_normalArray))[0]) return false;
break;
case(osg::Array::Vec3ArrayType):
if ((*static_cast<const osg::Vec3Array*>(lhs_normalArray))[0]<(*static_cast<const osg::Vec3Array*>(rhs_normalArray))[0]) return true;
if ((*static_cast<const osg::Vec3Array*>(rhs_normalArray))[0]<(*static_cast<const osg::Vec3Array*>(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()<rhs_colorArray->getType()) return true;
if (rhs_colorArray->getType()<lhs_colorArray->getType()) return false;
switch(lhs_colorArray->getType())
{
case(osg::Array::Vec4ubArrayType):
if ((*static_cast<const osg::Vec4ubArray*>(lhs_colorArray))[0]<(*static_cast<const osg::Vec4ubArray*>(rhs_colorArray))[0]) return true;
if ((*static_cast<const osg::Vec4ubArray*>(rhs_colorArray))[0]<(*static_cast<const osg::Vec4ubArray*>(lhs_colorArray))[0]) return false;
break;
case(osg::Array::Vec3ArrayType):
if ((*static_cast<const osg::Vec3Array*>(lhs_colorArray))[0]<(*static_cast<const osg::Vec3Array*>(rhs_colorArray))[0]) return true;
if ((*static_cast<const osg::Vec3Array*>(rhs_colorArray))[0]<(*static_cast<const osg::Vec3Array*>(lhs_colorArray))[0]) return false;
break;
case(osg::Array::Vec4ArrayType):
if ((*static_cast<const osg::Vec4Array*>(lhs_colorArray))[0]<(*static_cast<const osg::Vec4Array*>(rhs_colorArray))[0]) return true;
if ((*static_cast<const osg::Vec4Array*>(rhs_colorArray))[0]<(*static_cast<const osg::Vec4Array*>(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;
i<lhs->getNumPrimitiveSets() && i<rhs->getNumPrimitiveSets();
++i)
{
if (lhs->getPrimitiveSet(i)->getType()<rhs->getPrimitiveSet(i)->getType()) return true;
else if (rhs->getPrimitiveSet(i)->getType()<lhs->getPrimitiveSet(i)->getType()) return false;
if (lhs->getPrimitiveSet(i)->getMode()<rhs->getPrimitiveSet(i)->getMode()) return true;
else if (rhs->getPrimitiveSet(i)->getMode()<lhs->getPrimitiveSet(i)->getMode()) return false;
}
return lhs->getNumPrimitiveSets()<rhs->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;eachTexCoordArray<g1.getNumTexCoordArrays();++eachTexCoordArray)
{
if (!isArrayCompatible(numVertice1,numVertice2,g1.getTexCoordArray(eachTexCoordArray),g2.getTexCoordArray(eachTexCoordArray))) return false;
}
// then verify data type compatibility
if (g1.getVertexArray() && g2.getVertexArray() && g1.getVertexArray()->getDataType()!=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;
}
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<osg::StateSet*>::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;
if (group.getNumChildren()>=2)
{
typedef std::vector<osg::Geometry*> DuplicateList;
typedef std::vector< osg::ref_ptr<osg::Drawable> > DrawableList;
typedef std::map<osg::Geometry*,DuplicateList,LessGeometry> GeometryDuplicateMap;
typedef std::vector<DuplicateList> MergeList;
GeometryDuplicateMap geometryDuplicateMap;
DrawableList standardDrawables;
unsigned int i;
for(i=0;i<group.getNumChildren();++i)
{
osg::Drawable* drawable = group.getChild(i)->asDrawable();
if (drawable)
{
osg::Geometry* geom = drawable->asGeometry();
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)
{
// 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();
for(DuplicateList::iterator ditr = duplicateList.begin()+1;
ditr != duplicateList.end();
++ditr)
{
mergeGeometry(*lhs,**ditr);
group.removeChild(*ditr);
}
}
}
}
#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<<std::endl;
}
}
}
}
#endif
}
// convert all polygon primitives which has 3 indices into TRIANGLES, 4 indices into QUADS.
unsigned int i;
for(i=0;i<group.getNumChildren();++i)
{
osg::Drawable* drawable = group.getChild(i)->asDrawable();
if (!drawable)
continue;
osg::Geometry* geom = drawable->asGeometry();
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<group.getNumChildren();++i)
{
osg::Drawable* drawable = group.getChild(i)->asDrawable();
if (!drawable)
continue;
osg::Geometry* geom = drawable->asGeometry();
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(rhsNo<primitives.size())
{
osg::PrimitiveSet* lhs = primitives[lhsNo].get();
osg::PrimitiveSet* rhs = primitives[rhsNo].get();
bool combine = false;
if (lhs->getType()==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<osg::DrawArrays*>(lhs)),*(static_cast<osg::DrawArrays*>(rhs)));
break;
case(osg::PrimitiveSet::DrawArrayLengthsPrimitiveType):
combine = mergePrimitive(*(static_cast<osg::DrawArrayLengths*>(lhs)),*(static_cast<osg::DrawArrayLengths*>(rhs)));
break;
case(osg::PrimitiveSet::DrawElementsUBytePrimitiveType):
combine = mergePrimitive(*(static_cast<osg::DrawElementsUByte*>(lhs)),*(static_cast<osg::DrawElementsUByte*>(rhs)));
break;
case(osg::PrimitiveSet::DrawElementsUShortPrimitiveType):
combine = mergePrimitive(*(static_cast<osg::DrawElementsUShort*>(lhs)),*(static_cast<osg::DrawElementsUShort*>(rhs)));
break;
case(osg::PrimitiveSet::DrawElementsUIntPrimitiveType):
combine = mergePrimitive(*(static_cast<osg::DrawElementsUInt*>(lhs)),*(static_cast<osg::DrawElementsUInt*>(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+1<primitives.size())
{
osg::PrimitiveSet* lhs = primitives[primNo].get();
osg::PrimitiveSet* rhs = primitives[primNo+1].get();
bool combine = false;
if (lhs->getType()==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<osg::DrawArrays*>(lhs)),*(static_cast<osg::DrawArrays*>(rhs)));
break;
case(osg::PrimitiveSet::DrawArrayLengthsPrimitiveType):
combine = mergePrimitive(*(static_cast<osg::DrawArrayLengths*>(lhs)),*(static_cast<osg::DrawArrayLengths*>(rhs)));
break;
case(osg::PrimitiveSet::DrawElementsUBytePrimitiveType):
combine = mergePrimitive(*(static_cast<osg::DrawElementsUByte*>(lhs)),*(static_cast<osg::DrawElementsUByte*>(rhs)));
break;
case(osg::PrimitiveSet::DrawElementsUShortPrimitiveType):
combine = mergePrimitive(*(static_cast<osg::DrawElementsUShort*>(lhs)),*(static_cast<osg::DrawElementsUShort*>(rhs)));
break;
case(osg::PrimitiveSet::DrawElementsUIntPrimitiveType):
combine = mergePrimitive(*(static_cast<osg::DrawElementsUInt*>(lhs)),*(static_cast<osg::DrawElementsUInt*>(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;unit<geom.getNumTexCoordArrays();++unit)
{
osg::Array* tex = geom.getTexCoordArray(unit);
if (tex && tex->referenceCount()>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<typename T>
void _merge(T& rhs)
{
T* lhs = static_cast<T*>(_lhs);
lhs->insert(lhs->end(),rhs.begin(),rhs.end());
}
template<typename T>
void _mergeAndOffset(T& rhs)
{
T* lhs = static_cast<T*>(_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." <<std::endl;
}
}
else if (rhs.getVertexArray())
{
base = 0;
lhs.setVertexArray(rhs.getVertexArray());
}
if (lhs.getNormalArray() && rhs.getNormalArray() && lhs.getNormalArray()->getBinding()!=osg::Array::BIND_OVERALL)
{
if (!merger.merge(lhs.getNormalArray(),rhs.getNormalArray()))
{
OSG_DEBUG << "MergeGeometry: normal array not merged. Some data may be lost." <<std::endl;
}
}
else if (rhs.getNormalArray())
{
lhs.setNormalArray(rhs.getNormalArray());
}
if (lhs.getColorArray() && rhs.getColorArray() && lhs.getColorArray()->getBinding()!=osg::Array::BIND_OVERALL)
{
if (!merger.merge(lhs.getColorArray(),rhs.getColorArray()))
{
OSG_DEBUG << "MergeGeometry: color array not merged. Some data may be lost." <<std::endl;
}
}
else if (rhs.getColorArray())
{
lhs.setColorArray(rhs.getColorArray());
}
if (lhs.getSecondaryColorArray() && rhs.getSecondaryColorArray() && lhs.getSecondaryColorArray()->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." <<std::endl;
}
}
else if (rhs.getSecondaryColorArray())
{
lhs.setSecondaryColorArray(rhs.getSecondaryColorArray());
}
if (lhs.getFogCoordArray() && rhs.getFogCoordArray() && lhs.getFogCoordArray()->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." <<std::endl;
}
}
else if (rhs.getFogCoordArray())
{
lhs.setFogCoordArray(rhs.getFogCoordArray());
}
unsigned int unit;
for(unit=0;unit<lhs.getNumTexCoordArrays();++unit)
{
if (!merger.merge(lhs.getTexCoordArray(unit),rhs.getTexCoordArray(unit)))
{
OSG_DEBUG << "MergeGeometry: tex coord array not merged. Some data may be lost." <<std::endl;
}
}
for(unit=0;unit<lhs.getNumVertexAttribArrays();++unit)
{
if (!merger.merge(lhs.getVertexAttribArray(unit),rhs.getVertexAttribArray(unit)))
{
OSG_DEBUG << "MergeGeometry: vertex attrib array not merged. Some data may be lost." <<std::endl;
}
}
// shift the indices of the incoming primitives to account for the pre existing geometry.
osg::Geometry::PrimitiveSetList::iterator primItr;
for(primItr=rhs.getPrimitiveSetList().begin(); primItr!=rhs.getPrimitiveSetList().end(); ++primItr)
{
osg::PrimitiveSet* primitive = primItr->get();
switch(primitive->getType())
{
case(osg::PrimitiveSet::DrawElementsUBytePrimitiveType):
{
osg::DrawElementsUByte* primitiveUByte = static_cast<osg::DrawElementsUByte*>(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<osg::DrawElementsUShort*>(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;
}
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<osg::StateSet*, std::set<osg::Group*> > GroupMap;
GroupMap childGroups;
for (unsigned int i=0; i<group.getNumChildren(); ++i)
{
osg::Node* child = group.getChild(i);
osg::Group* childGroup = child->asGroup();
if (childGroup && isOperationPermissible(*childGroup))
{
childGroups[childGroup->getStateSet()].insert(childGroup);
}
}
for (GroupMap::iterator it = childGroups.begin(); it != childGroups.end(); ++it)
{
const std::set<osg::Group*>& groupSet = it->second;
if (groupSet.size() <= 1)
continue;
else
{
osg::Group* first = *groupSet.begin();
for (std::set<osg::Group*>::const_iterator groupIt = ++groupSet.begin(); groupIt != groupSet.end(); ++groupIt)
{
osg::Group* toMerge = *groupIt;
for (unsigned int i=0; i<toMerge->getNumChildren(); ++i)
first->addChild(toMerge->getChild(i));
toMerge->removeChildren(0, toMerge->getNumChildren());
group.removeChild(toMerge);
}
}
}
traverse(group);
}
}
}