"I added some calls to isOperationPermissibleForObject() so I can permit the flattten-pass with my specialized objects in my own code."
4103 lines
140 KiB
C++
4103 lines
140 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.
|
|
*/
|
|
#include <osgUtil/Optimizer>
|
|
|
|
#include <osg/ApplicationUsage>
|
|
#include <osg/Transform>
|
|
#include <osg/MatrixTransform>
|
|
#include <osg/PositionAttitudeTransform>
|
|
#include <osg/LOD>
|
|
#include <osg/Billboard>
|
|
#include <osg/Geometry>
|
|
#include <osg/Notify>
|
|
#include <osg/OccluderNode>
|
|
#include <osg/Sequence>
|
|
#include <osg/Switch>
|
|
#include <osg/Texture>
|
|
#include <osg/PagedLOD>
|
|
#include <osg/ProxyNode>
|
|
#include <osg/Timer>
|
|
#include <osg/TexMat>
|
|
#include <osg/io_utils>
|
|
|
|
#include <osgUtil/TransformAttributeFunctor>
|
|
#include <osgUtil/TriStripVisitor>
|
|
#include <osgUtil/Tesselator>
|
|
#include <osgUtil/Statistics>
|
|
|
|
#include <typeinfo>
|
|
#include <algorithm>
|
|
#include <numeric>
|
|
#include <sstream>
|
|
|
|
using namespace osgUtil;
|
|
|
|
|
|
void Optimizer::reset()
|
|
{
|
|
}
|
|
|
|
static osg::ApplicationUsageProxy Optimizer_e0(osg::ApplicationUsage::ENVIRONMENTAL_VARIABLE,"OSG_OPTIMIZER \"<type> [<type>]\"","OFF | DEFAULT | FLATTEN_STATIC_TRANSFORMS | REMOVE_REDUNDANT_NODES | COMBINE_ADJACENT_LODS | SHARE_DUPLICATE_STATE | MERGE_GEOMETRY | MERGE_GEODES | SPATIALIZE_GROUPS | COPY_SHARED_NODES | TRISTRIP_GEOMETRY | OPTIMIZE_TEXTURE_SETTINGS | REMOVE_LOADED_PROXY_NODES | TESSELATE_GEOMETRY | CHECK_GEOMETRY | FLATTEN_BILLBOARDS | TEXTURE_ATLAS_BUILDER");
|
|
|
|
void Optimizer::optimize(osg::Node* node)
|
|
{
|
|
unsigned int options = 0;
|
|
|
|
|
|
const char* env = getenv("OSG_OPTIMIZER");
|
|
if (env)
|
|
{
|
|
std::string str(env);
|
|
|
|
if(str.find("OFF")!=std::string::npos) options = 0;
|
|
|
|
if(str.find("~DEFAULT")!=std::string::npos) options ^= DEFAULT_OPTIMIZATIONS;
|
|
else if(str.find("DEFAULT")!=std::string::npos) options |= DEFAULT_OPTIMIZATIONS;
|
|
|
|
if(str.find("~FLATTEN_STATIC_TRANSFORMS")!=std::string::npos) options ^= FLATTEN_STATIC_TRANSFORMS;
|
|
else if(str.find("FLATTEN_STATIC_TRANSFORMS")!=std::string::npos) options |= FLATTEN_STATIC_TRANSFORMS;
|
|
|
|
if(str.find("~REMOVE_REDUNDANT_NODES")!=std::string::npos) options ^= REMOVE_REDUNDANT_NODES;
|
|
else if(str.find("REMOVE_REDUNDANT_NODES")!=std::string::npos) options |= REMOVE_REDUNDANT_NODES;
|
|
|
|
if(str.find("~REMOVE_LOADED_PROXY_NODES")!=std::string::npos) options ^= REMOVE_LOADED_PROXY_NODES;
|
|
else if(str.find("REMOVE_LOADED_PROXY_NODES")!=std::string::npos) options |= REMOVE_LOADED_PROXY_NODES;
|
|
|
|
if(str.find("~COMBINE_ADJACENT_LODS")!=std::string::npos) options ^= COMBINE_ADJACENT_LODS;
|
|
else if(str.find("COMBINE_ADJACENT_LODS")!=std::string::npos) options |= COMBINE_ADJACENT_LODS;
|
|
|
|
if(str.find("~SHARE_DUPLICATE_STATE")!=std::string::npos) options ^= SHARE_DUPLICATE_STATE;
|
|
else if(str.find("SHARE_DUPLICATE_STATE")!=std::string::npos) options |= SHARE_DUPLICATE_STATE;
|
|
|
|
if(str.find("~MERGE_GEODES")!=std::string::npos) options ^= MERGE_GEODES;
|
|
else if(str.find("MERGE_GEODES")!=std::string::npos) options |= MERGE_GEODES;
|
|
|
|
if(str.find("~MERGE_GEOMETRY")!=std::string::npos) options ^= MERGE_GEOMETRY;
|
|
else if(str.find("MERGE_GEOMETRY")!=std::string::npos) options |= MERGE_GEOMETRY;
|
|
|
|
if(str.find("~SPATIALIZE_GROUPS")!=std::string::npos) options ^= SPATIALIZE_GROUPS;
|
|
else if(str.find("SPATIALIZE_GROUPS")!=std::string::npos) options |= SPATIALIZE_GROUPS;
|
|
|
|
if(str.find("~COPY_SHARED_NODES")!=std::string::npos) options ^= COPY_SHARED_NODES;
|
|
else if(str.find("COPY_SHARED_NODES")!=std::string::npos) options |= COPY_SHARED_NODES;
|
|
|
|
if(str.find("~TESSELATE_GEOMETRY")!=std::string::npos) options ^= TESSELATE_GEOMETRY;
|
|
else if(str.find("TESSELATE_GEOMETRY")!=std::string::npos) options |= TESSELATE_GEOMETRY;
|
|
|
|
if(str.find("~TRISTRIP_GEOMETRY")!=std::string::npos) options ^= TRISTRIP_GEOMETRY;
|
|
else if(str.find("TRISTRIP_GEOMETRY")!=std::string::npos) options |= TRISTRIP_GEOMETRY;
|
|
|
|
if(str.find("~OPTIMIZE_TEXTURE_SETTINGS")!=std::string::npos) options ^= OPTIMIZE_TEXTURE_SETTINGS;
|
|
else if(str.find("OPTIMIZE_TEXTURE_SETTINGS")!=std::string::npos) options |= OPTIMIZE_TEXTURE_SETTINGS;
|
|
|
|
if(str.find("~CHECK_GEOMETRY")!=std::string::npos) options ^= CHECK_GEOMETRY;
|
|
else if(str.find("CHECK_GEOMETRY")!=std::string::npos) options |= CHECK_GEOMETRY;
|
|
|
|
if(str.find("~FLATTEN_BILLBOARDS")!=std::string::npos) options ^= FLATTEN_BILLBOARDS;
|
|
else if(str.find("FLATTEN_BILLBOARDS")!=std::string::npos) options |= FLATTEN_BILLBOARDS;
|
|
|
|
if(str.find("~TEXTURE_ATLAS_BUILDER")!=std::string::npos) options ^= TEXTURE_ATLAS_BUILDER;
|
|
else if(str.find("TEXTURE_ATLAS_BUILDER")!=std::string::npos) options |= TEXTURE_ATLAS_BUILDER;
|
|
|
|
}
|
|
else
|
|
{
|
|
options = DEFAULT_OPTIMIZATIONS;
|
|
}
|
|
|
|
optimize(node,options);
|
|
|
|
}
|
|
|
|
void Optimizer::optimize(osg::Node* node, unsigned int options)
|
|
{
|
|
StatsVisitor stats;
|
|
|
|
if (osg::getNotifyLevel()>=osg::INFO)
|
|
{
|
|
node->accept(stats);
|
|
stats.totalUpStats();
|
|
osg::notify(osg::NOTICE)<<std::endl<<"Stats before:"<<std::endl;
|
|
stats.print(osg::notify(osg::NOTICE));
|
|
}
|
|
|
|
if (options & TESSELATE_GEOMETRY)
|
|
{
|
|
osg::notify(osg::INFO)<<"Optimizer::optimize() doing TESSELATE_GEOMETRY"<<std::endl;
|
|
|
|
TesselateVisitor tsv;
|
|
node->accept(tsv);
|
|
}
|
|
|
|
if (options & REMOVE_LOADED_PROXY_NODES)
|
|
{
|
|
osg::notify(osg::INFO)<<"Optimizer::optimize() doing REMOVE_LOADED_PROXY_NODES"<<std::endl;
|
|
|
|
RemoveLoadedProxyNodesVisitor rlpnv(this);
|
|
node->accept(rlpnv);
|
|
rlpnv.removeRedundantNodes();
|
|
|
|
}
|
|
|
|
if (options & COMBINE_ADJACENT_LODS)
|
|
{
|
|
osg::notify(osg::INFO)<<"Optimizer::optimize() doing COMBINE_ADJACENT_LODS"<<std::endl;
|
|
|
|
CombineLODsVisitor clv(this);
|
|
node->accept(clv);
|
|
clv.combineLODs();
|
|
}
|
|
|
|
if (options & OPTIMIZE_TEXTURE_SETTINGS)
|
|
{
|
|
osg::notify(osg::INFO)<<"Optimizer::optimize() doing OPTIMIZE_TEXTURE_SETTINGS"<<std::endl;
|
|
|
|
TextureVisitor tv(true,true, // unref image
|
|
false,false, // client storage
|
|
false,1.0, // anisotropic filtering
|
|
this );
|
|
node->accept(tv);
|
|
}
|
|
|
|
if (options & SHARE_DUPLICATE_STATE)
|
|
{
|
|
osg::notify(osg::INFO)<<"Optimizer::optimize() doing SHARE_DUPLICATE_STATE"<<std::endl;
|
|
|
|
StateVisitor osv(this);
|
|
node->accept(osv);
|
|
osv.optimize();
|
|
}
|
|
|
|
if (options & TEXTURE_ATLAS_BUILDER)
|
|
{
|
|
osg::notify(osg::INFO)<<"Optimizer::optimize() doing TEXTURE_ATLAS_BUILDER"<<std::endl;
|
|
|
|
// traverse the scene collecting textures into texture atlas.
|
|
TextureAtlasVisitor tav(this);
|
|
node->accept(tav);
|
|
tav.optimize();
|
|
|
|
// now merge duplicate state, that may have been introduced by merge textures into texture atlas'
|
|
StateVisitor osv(this);
|
|
node->accept(osv);
|
|
osv.optimize();
|
|
}
|
|
|
|
if (options & COPY_SHARED_NODES)
|
|
{
|
|
osg::notify(osg::INFO)<<"Optimizer::optimize() doing COPY_SHARED_NODES"<<std::endl;
|
|
|
|
CopySharedSubgraphsVisitor cssv(this);
|
|
node->accept(cssv);
|
|
cssv.copySharedNodes();
|
|
}
|
|
|
|
if (options & FLATTEN_STATIC_TRANSFORMS)
|
|
{
|
|
osg::notify(osg::INFO)<<"Optimizer::optimize() doing FLATTEN_STATIC_TRANSFORMS"<<std::endl;
|
|
|
|
int i=0;
|
|
bool result = false;
|
|
do
|
|
{
|
|
osg::notify(osg::DEBUG_INFO) << "** RemoveStaticTransformsVisitor *** Pass "<<i<<std::endl;
|
|
FlattenStaticTransformsVisitor fstv(this);
|
|
node->accept(fstv);
|
|
result = fstv.removeTransforms(node);
|
|
++i;
|
|
} while (result);
|
|
|
|
// no combine any adjacent static transforms.
|
|
CombineStaticTransformsVisitor cstv(this);
|
|
node->accept(cstv);
|
|
cstv.removeTransforms(node);
|
|
|
|
}
|
|
|
|
if (options & MERGE_GEODES)
|
|
{
|
|
osg::notify(osg::INFO)<<"Optimizer::optimize() doing MERGE_GEODES"<<std::endl;
|
|
|
|
osg::Timer_t startTick = osg::Timer::instance()->tick();
|
|
|
|
MergeGeodesVisitor visitor;
|
|
node->accept(visitor);
|
|
|
|
osg::Timer_t endTick = osg::Timer::instance()->tick();
|
|
|
|
osg::notify(osg::INFO)<<"MERGE_GEODES took "<<osg::Timer::instance()->delta_s(startTick,endTick)<<std::endl;
|
|
}
|
|
|
|
if (options & CHECK_GEOMETRY)
|
|
{
|
|
osg::notify(osg::INFO)<<"Optimizer::optimize() doing CHECK_GEOMETRY"<<std::endl;
|
|
|
|
CheckGeometryVisitor mgv(this);
|
|
node->accept(mgv);
|
|
}
|
|
|
|
if (options & MERGE_GEOMETRY)
|
|
{
|
|
osg::notify(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::notify(osg::INFO)<<"MERGE_GEOMETRY took "<<osg::Timer::instance()->delta_s(startTick,endTick)<<std::endl;
|
|
}
|
|
|
|
if (options & TRISTRIP_GEOMETRY)
|
|
{
|
|
osg::notify(osg::INFO)<<"Optimizer::optimize() doing TRISTRIP_GEOMETRY"<<std::endl;
|
|
|
|
TriStripVisitor tsv(this);
|
|
node->accept(tsv);
|
|
tsv.stripify();
|
|
}
|
|
|
|
if (options & REMOVE_REDUNDANT_NODES)
|
|
{
|
|
osg::notify(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();
|
|
|
|
}
|
|
|
|
if (options & FLATTEN_BILLBOARDS)
|
|
{
|
|
FlattenBillboardVisitor fbv(this);
|
|
node->accept(fbv);
|
|
fbv.process();
|
|
}
|
|
|
|
if (options & SPATIALIZE_GROUPS)
|
|
{
|
|
osg::notify(osg::INFO)<<"Optimizer::optimize() doing SPATIALIZE_GROUPS"<<std::endl;
|
|
|
|
SpatializeGroupsVisitor sv(this);
|
|
node->accept(sv);
|
|
sv.divide();
|
|
}
|
|
|
|
if (osg::getNotifyLevel()>=osg::INFO)
|
|
{
|
|
stats.reset();
|
|
node->accept(stats);
|
|
stats.totalUpStats();
|
|
osg::notify(osg::NOTICE)<<std::endl<<"Stats after:"<<std::endl;
|
|
stats.print(osg::notify(osg::NOTICE));
|
|
}
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// Tesselate geometry - eg break complex POLYGONS into triangles, strips, fans..
|
|
////////////////////////////////////////////////////////////////////////////
|
|
void Optimizer::TesselateVisitor::apply(osg::Geode& geode)
|
|
{
|
|
for(unsigned int i=0;i<geode.getNumDrawables();++i)
|
|
{
|
|
osg::Geometry* geom = dynamic_cast<osg::Geometry*>(geode.getDrawable(i));
|
|
if (geom) {
|
|
osgUtil::Tesselator tesselator;
|
|
tesselator.retesselatePolygons(*geom);
|
|
}
|
|
}
|
|
traverse(geode);
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// Optimize State Visitor
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
template<typename T>
|
|
struct LessDerefFunctor
|
|
{
|
|
bool operator () (const T* lhs,const T* rhs) const
|
|
{
|
|
return (*lhs<*rhs);
|
|
}
|
|
};
|
|
|
|
struct LessStateSetFunctor
|
|
{
|
|
bool operator () (const osg::StateSet* lhs,const osg::StateSet* rhs) const
|
|
{
|
|
return (*lhs<*rhs);
|
|
}
|
|
};
|
|
|
|
|
|
|
|
void Optimizer::StateVisitor::reset()
|
|
{
|
|
_statesets.clear();
|
|
}
|
|
|
|
void Optimizer::StateVisitor::addStateSet(osg::StateSet* stateset,osg::Object* obj)
|
|
{
|
|
_statesets[stateset].insert(obj);
|
|
}
|
|
|
|
void Optimizer::StateVisitor::apply(osg::Node& node)
|
|
{
|
|
|
|
osg::StateSet* ss = node.getStateSet();
|
|
if (ss && ss->getDataVariance()==osg::Object::STATIC)
|
|
{
|
|
if (isOperationPermissibleForObject(&node) &&
|
|
isOperationPermissibleForObject(ss))
|
|
{
|
|
addStateSet(ss,&node);
|
|
}
|
|
}
|
|
|
|
traverse(node);
|
|
}
|
|
|
|
void Optimizer::StateVisitor::apply(osg::Geode& geode)
|
|
{
|
|
if (!isOperationPermissibleForObject(&geode)) return;
|
|
|
|
osg::StateSet* ss = geode.getStateSet();
|
|
|
|
|
|
if (ss && ss->getDataVariance()==osg::Object::STATIC)
|
|
{
|
|
if (isOperationPermissibleForObject(ss))
|
|
{
|
|
addStateSet(ss,&geode);
|
|
}
|
|
}
|
|
for(unsigned int i=0;i<geode.getNumDrawables();++i)
|
|
{
|
|
osg::Drawable* drawable = geode.getDrawable(i);
|
|
if (drawable)
|
|
{
|
|
ss = drawable->getStateSet();
|
|
if (ss && ss->getDataVariance()==osg::Object::STATIC)
|
|
{
|
|
if (isOperationPermissibleForObject(drawable) &&
|
|
isOperationPermissibleForObject(ss))
|
|
{
|
|
addStateSet(ss,drawable);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Optimizer::StateVisitor::optimize()
|
|
{
|
|
osg::notify(osg::INFO) << "Num of StateSet="<<_statesets.size()<< std::endl;
|
|
|
|
{
|
|
// create map from state attributes to stateset which contain them.
|
|
typedef std::pair<osg::StateSet*,unsigned int> StateSetUnitPair;
|
|
typedef std::set<StateSetUnitPair> StateSetList;
|
|
typedef std::map<osg::StateAttribute*,StateSetList> AttributeToStateSetMap;
|
|
AttributeToStateSetMap attributeToStateSetMap;
|
|
|
|
// create map from uniforms to stateset when contain them.
|
|
typedef std::set<osg::StateSet*> StateSetSet;
|
|
typedef std::map<osg::Uniform*,StateSetSet> UniformToStateSetMap;
|
|
|
|
const unsigned int NON_TEXTURE_ATTRIBUTE = 0xffffffff;
|
|
|
|
UniformToStateSetMap uniformToStateSetMap;
|
|
|
|
// NOTE - TODO will need to track state attribute override value too.
|
|
|
|
for(StateSetMap::iterator sitr=_statesets.begin();
|
|
sitr!=_statesets.end();
|
|
++sitr)
|
|
{
|
|
osg::StateSet::AttributeList& attributes = sitr->first->getAttributeList();
|
|
for(osg::StateSet::AttributeList::iterator aitr= attributes.begin();
|
|
aitr!=attributes.end();
|
|
++aitr)
|
|
{
|
|
if (aitr->second.first->getDataVariance()==osg::Object::STATIC)
|
|
{
|
|
attributeToStateSetMap[aitr->second.first.get()].insert(StateSetUnitPair(sitr->first,NON_TEXTURE_ATTRIBUTE));
|
|
}
|
|
}
|
|
|
|
|
|
osg::StateSet::TextureAttributeList& texAttributes = sitr->first->getTextureAttributeList();
|
|
for(unsigned int unit=0;unit<texAttributes.size();++unit)
|
|
{
|
|
osg::StateSet::AttributeList& attributes = texAttributes[unit];
|
|
for(osg::StateSet::AttributeList::iterator aitr= attributes.begin();
|
|
aitr!=attributes.end();
|
|
++aitr)
|
|
{
|
|
if (aitr->second.first->getDataVariance()==osg::Object::STATIC)
|
|
{
|
|
attributeToStateSetMap[aitr->second.first.get()].insert(StateSetUnitPair(sitr->first,unit));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
osg::StateSet::UniformList& uniforms = sitr->first->getUniformList();
|
|
for(osg::StateSet::UniformList::iterator uitr= uniforms.begin();
|
|
uitr!=uniforms.end();
|
|
++uitr)
|
|
{
|
|
if (uitr->second.first->getDataVariance()==osg::Object::STATIC)
|
|
{
|
|
uniformToStateSetMap[uitr->second.first.get()].insert(sitr->first);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if (attributeToStateSetMap.size()>=2)
|
|
{
|
|
// create unique set of state attribute pointers.
|
|
typedef std::vector<osg::StateAttribute*> AttributeList;
|
|
AttributeList attributeList;
|
|
|
|
for(AttributeToStateSetMap::iterator aitr=attributeToStateSetMap.begin();
|
|
aitr!=attributeToStateSetMap.end();
|
|
++aitr)
|
|
{
|
|
attributeList.push_back(aitr->first);
|
|
}
|
|
|
|
// sort the attributes so that equal attributes sit along side each
|
|
// other.
|
|
std::sort(attributeList.begin(),attributeList.end(),LessDerefFunctor<osg::StateAttribute>());
|
|
|
|
osg::notify(osg::INFO) << "state attribute list"<< std::endl;
|
|
for(AttributeList::iterator aaitr = attributeList.begin();
|
|
aaitr!=attributeList.end();
|
|
++aaitr)
|
|
{
|
|
osg::notify(osg::INFO) << " "<<*aaitr << " "<<(*aaitr)->className()<< std::endl;
|
|
}
|
|
|
|
osg::notify(osg::INFO) << "searching for duplicate attributes"<< std::endl;
|
|
// find the duplicates.
|
|
AttributeList::iterator first_unique = attributeList.begin();
|
|
AttributeList::iterator current = first_unique;
|
|
++current;
|
|
for(; current!=attributeList.end();++current)
|
|
{
|
|
if (**current==**first_unique)
|
|
{
|
|
osg::notify(osg::INFO) << " found duplicate "<<(*current)->className()<<" first="<<*first_unique<<" current="<<*current<< std::endl;
|
|
StateSetList& statesetlist = attributeToStateSetMap[*current];
|
|
for(StateSetList::iterator sitr=statesetlist.begin();
|
|
sitr!=statesetlist.end();
|
|
++sitr)
|
|
{
|
|
osg::notify(osg::INFO) << " replace duplicate "<<*current<<" with "<<*first_unique<< std::endl;
|
|
osg::StateSet* stateset = sitr->first;
|
|
unsigned int unit = sitr->second;
|
|
if (unit==NON_TEXTURE_ATTRIBUTE) stateset->setAttribute(*first_unique);
|
|
else stateset->setTextureAttribute(unit,*first_unique);
|
|
}
|
|
}
|
|
else first_unique = current;
|
|
}
|
|
}
|
|
|
|
|
|
if (uniformToStateSetMap.size()>=2)
|
|
{
|
|
// create unique set of uniform pointers.
|
|
typedef std::vector<osg::Uniform*> UniformList;
|
|
UniformList uniformList;
|
|
|
|
for(UniformToStateSetMap::iterator aitr=uniformToStateSetMap.begin();
|
|
aitr!=uniformToStateSetMap.end();
|
|
++aitr)
|
|
{
|
|
uniformList.push_back(aitr->first);
|
|
}
|
|
|
|
// sort the uniforms so that equal uniforms sit along side each
|
|
// other.
|
|
std::sort(uniformList.begin(),uniformList.end(),LessDerefFunctor<osg::Uniform>());
|
|
|
|
osg::notify(osg::INFO) << "state uniform list"<< std::endl;
|
|
for(UniformList::iterator uuitr = uniformList.begin();
|
|
uuitr!=uniformList.end();
|
|
++uuitr)
|
|
{
|
|
osg::notify(osg::INFO) << " "<<*uuitr << " "<<(*uuitr)->getName()<< std::endl;
|
|
}
|
|
|
|
osg::notify(osg::INFO) << "searching for duplicate uniforms"<< std::endl;
|
|
// find the duplicates.
|
|
UniformList::iterator first_unique_uniform = uniformList.begin();
|
|
UniformList::iterator current_uniform = first_unique_uniform;
|
|
++current_uniform;
|
|
for(; current_uniform!=uniformList.end();++current_uniform)
|
|
{
|
|
if ((**current_uniform)==(**first_unique_uniform))
|
|
{
|
|
osg::notify(osg::INFO) << " found duplicate uniform "<<(*current_uniform)->getName()<<" first_unique_uniform="<<*first_unique_uniform<<" current_uniform="<<*current_uniform<< std::endl;
|
|
StateSetSet& statesetset = uniformToStateSetMap[*current_uniform];
|
|
for(StateSetSet::iterator sitr=statesetset.begin();
|
|
sitr!=statesetset.end();
|
|
++sitr)
|
|
{
|
|
osg::notify(osg::INFO) << " replace duplicate "<<*current_uniform<<" with "<<*first_unique_uniform<< std::endl;
|
|
osg::StateSet* stateset = *sitr;
|
|
stateset->addUniform(*first_unique_uniform);
|
|
}
|
|
}
|
|
else first_unique_uniform = current_uniform;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// duplicate state attributes removed.
|
|
// now need to look at duplicate state sets.
|
|
if (_statesets.size()>=2)
|
|
{
|
|
// create the list of stateset's.
|
|
typedef std::vector<osg::StateSet*> StateSetSortList;
|
|
StateSetSortList statesetSortList;
|
|
for(StateSetMap::iterator ssitr=_statesets.begin();
|
|
ssitr!=_statesets.end();
|
|
++ssitr)
|
|
{
|
|
statesetSortList.push_back(ssitr->first);
|
|
}
|
|
|
|
|
|
// sort the StateSet's so that equal StateSet's sit along side each
|
|
// other.
|
|
std::sort(statesetSortList.begin(),statesetSortList.end(),LessDerefFunctor<osg::StateSet>());
|
|
|
|
osg::notify(osg::INFO) << "searching for duplicate attributes"<< std::endl;
|
|
// find the duplicates.
|
|
StateSetSortList::iterator first_unique = statesetSortList.begin();
|
|
StateSetSortList::iterator current = first_unique; ++current;
|
|
for(; current!=statesetSortList.end();++current)
|
|
{
|
|
if (**current==**first_unique)
|
|
{
|
|
osg::notify(osg::INFO) << " found duplicate "<<(*current)->className()<<" first="<<*first_unique<<" current="<<*current<< std::endl;
|
|
ObjectSet& objSet = _statesets[*current];
|
|
for(ObjectSet::iterator sitr=objSet.begin();
|
|
sitr!=objSet.end();
|
|
++sitr)
|
|
{
|
|
osg::notify(osg::INFO) << " replace duplicate "<<*current<<" with "<<*first_unique<< std::endl;
|
|
osg::Object* obj = *sitr;
|
|
osg::Drawable* drawable = dynamic_cast<osg::Drawable*>(obj);
|
|
if (drawable)
|
|
{
|
|
drawable->setStateSet(*first_unique);
|
|
}
|
|
else
|
|
{
|
|
osg::Node* node = dynamic_cast<osg::Node*>(obj);
|
|
if (node)
|
|
{
|
|
node->setStateSet(*first_unique);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else first_unique = current;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// 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::Drawable* drawable = dynamic_cast<const osg::Drawable*>(object);
|
|
if (drawable) return isOperationPermissibleForObject(drawable);
|
|
|
|
const osg::Node* node = dynamic_cast<const osg::Node*>(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<const osg::ProxyNode*>(node)) return false;
|
|
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::DYNAMIC) _moreThanOneMatrixRequired=true;
|
|
else if (transform->getReferenceFrame()==osg::Transform::ABSOLUTE_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!=_identity_matrix) _moreThanOneMatrixRequired=true;
|
|
}
|
|
|
|
}
|
|
_transformSet.insert(transform);
|
|
}
|
|
|
|
bool _canBeApplied;
|
|
bool _moreThanOneMatrixRequired;
|
|
osg::Matrix _firstMatrix;
|
|
TransformSet _transformSet;
|
|
osg::Matrix _identity_matrix;
|
|
|
|
};
|
|
|
|
|
|
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::Drawable* drawable = dynamic_cast<osg::Drawable*>(obj);
|
|
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->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());
|
|
for(unsigned int i=0;i<transform->getNumChildren();++i)
|
|
{
|
|
for(unsigned int j=0;j<transform->getNumParents();++j)
|
|
{
|
|
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<osg::MatrixTransform*>(titr->first);
|
|
if (mt) mt->setMatrix(osg::Matrix::identity());
|
|
else
|
|
{
|
|
osg::PositionAttitudeTransform* pat = dynamic_cast<osg::PositionAttitudeTransform*>(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::notify(osg::WARN)<<"Warning:: during Optimize::CollectLowestTransformsVisitor::removeTransforms(Node*)"<<std::endl;
|
|
osg::notify(osg::WARN)<<" unhandled of setting of indentity matrix on "<< titr->first->className()<<std::endl;
|
|
osg::notify(osg::WARN)<<" model will appear in the incorrect position."<<std::endl;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
_objectMap.clear();
|
|
_transformMap.clear();
|
|
|
|
return transformRemoved;
|
|
}
|
|
|
|
void Optimizer::FlattenStaticTransformsVisitor::apply(osg::Node& node)
|
|
{
|
|
if (strcmp(node.className(),"LightPointNode")==0)
|
|
{
|
|
_excludedNodeSet.insert(&node);
|
|
}
|
|
traverse(node);
|
|
}
|
|
|
|
|
|
void Optimizer::FlattenStaticTransformsVisitor::apply(osg::ProxyNode& node)
|
|
{
|
|
_excludedNodeSet.insert(&node);
|
|
|
|
traverse(node);
|
|
}
|
|
|
|
|
|
void Optimizer::FlattenStaticTransformsVisitor::apply(osg::Geode& geode)
|
|
{
|
|
if (!_transformStack.empty())
|
|
{
|
|
for(unsigned int i=0;i<geode.getNumDrawables();++i)
|
|
{
|
|
osg::Geometry *geometry = geode.getDrawable(i)->asGeometry();
|
|
if((geometry) && (isOperationPermissibleForObject(&geode)) && (isOperationPermissibleForObject(geometry)))
|
|
{
|
|
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(geode.getDrawable(i));
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// CombineStatucTransforms
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
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 if it exists in the transform set.
|
|
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);
|
|
|
|
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::Geode& geode)
|
|
{
|
|
for(int i=geode.getNumDrawables()-1;i>=0;--i)
|
|
{
|
|
osg::Geometry* geom = geode.getDrawable(i)->asGeometry();
|
|
if (geom && geom->empty() && isOperationPermissibleForObject(geom))
|
|
{
|
|
geode.removeDrawables(i,1);
|
|
}
|
|
}
|
|
|
|
if (geode.getNumParents()>0)
|
|
{
|
|
if (geode.getNumDrawables()==0 && isOperationPermissibleForObject(&geode)) _redundantNodeList.insert(&geode);
|
|
}
|
|
}
|
|
|
|
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<osg::Transform*>(&group)))
|
|
{
|
|
_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;
|
|
if (!dynamic_cast<osg::Sequence*>(parent) &&
|
|
!dynamic_cast<osg::Switch*>(parent) &&
|
|
strcmp(parent->className(),"MultiSwitch")!=0)
|
|
{
|
|
parent->removeChild(nodeToRemove.get());
|
|
if (parent->getNumChildren()==0) newEmptyGroups.insert(*pitr);
|
|
}
|
|
}
|
|
}
|
|
|
|
_redundantNodeList.clear();
|
|
_redundantNodeList.swap(newEmptyGroups);
|
|
}
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// RemoveRedundantNodes.
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
void Optimizer::RemoveRedundantNodesVisitor::apply(osg::Group& group)
|
|
{
|
|
if (group.getNumParents()>0)
|
|
{
|
|
if (group.getNumChildren()==1 && typeid(group)==typeid(osg::Group))
|
|
{
|
|
if (group.getNumParents()>0 && group.getNumChildren()<=1)
|
|
{
|
|
if (isOperationPermissibleForObject(&group))
|
|
{
|
|
_redundantNodeList.insert(&group);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
traverse(group);
|
|
}
|
|
|
|
void Optimizer::RemoveRedundantNodesVisitor::apply(osg::Transform& transform)
|
|
{
|
|
if (transform.getNumParents()>0 &&
|
|
transform.getDataVariance()==osg::Object::STATIC &&
|
|
isOperationPermissibleForObject(&transform))
|
|
{
|
|
static osg::Matrix identity;
|
|
osg::Matrix matrix;
|
|
transform.computeWorldToLocalMatrix(matrix,NULL);
|
|
if (matrix==identity)
|
|
{
|
|
_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 = dynamic_cast<osg::Group*>(*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::notify(osg::WARN)<<"Optimizer::RemoveRedundantNodesVisitor::removeRedundantNodes() - failed dynamic_cast"<<std::endl;
|
|
}
|
|
}
|
|
_redundantNodeList.clear();
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// RemoveLoadedProxyNodesVisitor.
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
void Optimizer::RemoveLoadedProxyNodesVisitor::apply(osg::ProxyNode& proxyNode)
|
|
{
|
|
if (proxyNode.getNumParents()>0 && proxyNode.getNumFileNames()==proxyNode.getNumChildren())
|
|
{
|
|
if (isOperationPermissibleForObject(&proxyNode))
|
|
{
|
|
_redundantNodeList.insert(&proxyNode);
|
|
}
|
|
}
|
|
traverse(proxyNode);
|
|
}
|
|
|
|
void Optimizer::RemoveLoadedProxyNodesVisitor::removeRedundantNodes()
|
|
{
|
|
|
|
for(NodeList::iterator itr=_redundantNodeList.begin();
|
|
itr!=_redundantNodeList.end();
|
|
++itr)
|
|
{
|
|
osg::ref_ptr<osg::Group> group = dynamic_cast<osg::Group*>(*itr);
|
|
if (group.valid())
|
|
{
|
|
|
|
// first check to see if data was attached to the ProxyNode that we need to keep.
|
|
bool keepData = false;
|
|
if (!group->getName().empty()) keepData = true;
|
|
if (!group->getDescriptions().empty()) keepData = true;
|
|
if (group->getNodeMask()) keepData = true;
|
|
if (group->getUpdateCallback()) keepData = true;
|
|
if (group->getEventCallback()) keepData = true;
|
|
if (group->getCullCallback()) keepData = true;
|
|
|
|
if (keepData)
|
|
{
|
|
// create a group to store all proxy's children and data.
|
|
osg::ref_ptr<osg::Group> newGroup = new osg::Group(*group,osg::CopyOp::SHALLOW_COPY);
|
|
|
|
|
|
// 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)
|
|
{
|
|
(*pitr)->replaceChild(group.get(),newGroup.get());
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
// take a copy of parents list since subsequent removes will modify the original one.
|
|
osg::Node::ParentList parents = group->getParents();
|
|
|
|
for(unsigned int i=0;i<group->getNumChildren();++i)
|
|
{
|
|
osg::Node* child = group->getChild(i);
|
|
for(osg::Node::ParentList::iterator pitr=parents.begin();
|
|
pitr!=parents.end();
|
|
++pitr)
|
|
{
|
|
(*pitr)->replaceChild(group.get(),child);
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
osg::notify(osg::WARN)<<"Optimizer::RemoveLoadedProxyNodesVisitor::removeRedundantNodes() - failed dynamic_cast"<<std::endl;
|
|
}
|
|
}
|
|
_redundantNodeList.clear();
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// combine LOD's.
|
|
////////////////////////////////////////////////////////////////////////////
|
|
void Optimizer::CombineLODsVisitor::apply(osg::LOD& lod)
|
|
{
|
|
if (dynamic_cast<osg::PagedLOD*>(&lod)==0)
|
|
{
|
|
for(unsigned int i=0;i<lod.getNumParents();++i)
|
|
{
|
|
if (typeid(*lod.getParent(i))==typeid(osg::Group))
|
|
{
|
|
if (isOperationPermissibleForObject(&lod))
|
|
{
|
|
_groupList.insert(lod.getParent(i));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
traverse(lod);
|
|
}
|
|
|
|
|
|
void Optimizer::CombineLODsVisitor::combineLODs()
|
|
{
|
|
for(GroupList::iterator itr=_groupList.begin();
|
|
itr!=_groupList.end();
|
|
++itr)
|
|
{
|
|
osg::Group* group = *itr;
|
|
|
|
typedef std::set<osg::LOD*> LODSet;
|
|
|
|
LODSet lodChildren;
|
|
|
|
for(unsigned int i=0;i<group->getNumChildren();++i)
|
|
{
|
|
osg::Node* child = group->getChild(i);
|
|
osg::LOD* lod = dynamic_cast<osg::LOD*>(child);
|
|
if (lod)
|
|
{
|
|
lodChildren.insert(lod);
|
|
}
|
|
}
|
|
|
|
if (lodChildren.size()>=2)
|
|
{
|
|
osg::BoundingBox bb;
|
|
LODSet::iterator lod_itr;
|
|
float smallestRadius=FLT_MAX;
|
|
for(lod_itr=lodChildren.begin();
|
|
lod_itr!=lodChildren.end();
|
|
++lod_itr)
|
|
{
|
|
float r = (*lod_itr)->getBound().radius();
|
|
if (r>=0 && r<smallestRadius) smallestRadius = r;
|
|
bb.expandBy((*lod_itr)->getCenter());
|
|
}
|
|
if (bb.radius()<smallestRadius*0.1f)
|
|
{
|
|
typedef std::pair<float,float> RangePair;
|
|
typedef std::multimap<RangePair,osg::Node*> RangeMap;
|
|
RangeMap rangeMap;
|
|
for(lod_itr=lodChildren.begin();
|
|
lod_itr!=lodChildren.end();
|
|
++lod_itr)
|
|
{
|
|
|
|
osg::LOD* lod = *lod_itr;
|
|
for(unsigned int i=0;i<lod->getNumRanges();++i)
|
|
{
|
|
rangeMap.insert(RangeMap::value_type(RangePair(lod->getMinRange(i),lod->getMaxRange(i)),lod->getChild(i)));
|
|
}
|
|
|
|
}
|
|
|
|
// create new LOD containing all other LOD's children.
|
|
osg::LOD* newLOD = new osg::LOD;
|
|
newLOD->setName("newLOD");
|
|
newLOD->setCenter(bb.center());
|
|
|
|
int i=0;
|
|
for(RangeMap::iterator c_itr=rangeMap.begin();
|
|
c_itr!=rangeMap.end();
|
|
++c_itr,++i)
|
|
{
|
|
newLOD->setRange(i,c_itr->first.first,c_itr->first.second);
|
|
newLOD->addChild(c_itr->second);
|
|
}
|
|
|
|
// add LOD into parent.
|
|
group->addChild(newLOD);
|
|
|
|
// remove all the old LOD's from group.
|
|
for(lod_itr=lodChildren.begin();
|
|
lod_itr!=lodChildren.end();
|
|
++lod_itr)
|
|
{
|
|
group->removeChild(*lod_itr);
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
_groupList.clear();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// code to merge geometry object which share, state, and attribute bindings.
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
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;
|
|
|
|
if (rhs->getVertexIndices()) { if (!lhs->getVertexIndices()) return true; }
|
|
else if (lhs->getVertexIndices()) return false;
|
|
|
|
if (lhs->getNormalBinding()<rhs->getNormalBinding()) return true;
|
|
if (rhs->getNormalBinding()<lhs->getNormalBinding()) return false;
|
|
|
|
if (rhs->getNormalIndices()) { if (!lhs->getNormalIndices()) return true; }
|
|
else if (lhs->getNormalIndices()) return false;
|
|
|
|
if (lhs->getColorBinding()<rhs->getColorBinding()) return true;
|
|
if (rhs->getColorBinding()<lhs->getColorBinding()) return false;
|
|
|
|
if (rhs->getColorIndices()) { if (!lhs->getColorIndices()) return true; }
|
|
else if (lhs->getColorIndices()) return false;
|
|
|
|
if (lhs->getSecondaryColorBinding()<rhs->getSecondaryColorBinding()) return true;
|
|
if (rhs->getSecondaryColorBinding()<lhs->getSecondaryColorBinding()) return false;
|
|
|
|
if (rhs->getSecondaryColorIndices()) { if (!lhs->getSecondaryColorIndices()) return true; }
|
|
else if (lhs->getSecondaryColorIndices()) return false;
|
|
|
|
if (lhs->getFogCoordBinding()<rhs->getFogCoordBinding()) return true;
|
|
if (rhs->getFogCoordBinding()<lhs->getFogCoordBinding()) return false;
|
|
|
|
if (rhs->getFogCoordIndices()) { if (!lhs->getFogCoordIndices()) return true; }
|
|
else if (lhs->getFogCoordIndices()) return false;
|
|
|
|
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;
|
|
|
|
if (rhs->getTexCoordIndices(i)) { if (!lhs->getTexCoordIndices(i)) return true; }
|
|
else if (lhs->getTexCoordIndices(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 (rhs->getVertexAttribIndices(i)) { if (!lhs->getVertexAttribIndices(i)) return true; }
|
|
else if (lhs->getVertexAttribIndices(i)) return false;
|
|
}
|
|
|
|
|
|
if (lhs->getNormalBinding()==osg::Geometry::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 (lhs->getColorBinding()==osg::Geometry::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();
|
|
}
|
|
};
|
|
|
|
void Optimizer::CheckGeometryVisitor::checkGeode(osg::Geode& geode)
|
|
{
|
|
if (isOperationPermissibleForObject(&geode))
|
|
{
|
|
for(unsigned int i=0;i<geode.getNumDrawables();++i)
|
|
{
|
|
osg::Geometry* geom = geode.getDrawable(i)->asGeometry();
|
|
if (geom && isOperationPermissibleForObject(geom))
|
|
{
|
|
geom->computeCorrectBindingsAndArraySizes();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Optimizer::MergeGeometryVisitor::mergeGeode(osg::Geode& geode)
|
|
{
|
|
if (!isOperationPermissibleForObject(&geode)) return false;
|
|
|
|
if (geode.getNumDrawables()>=2)
|
|
{
|
|
|
|
// osg::notify(osg::NOTICE)<<"Before "<<geode.getNumDrawables()<<std::endl;
|
|
|
|
typedef std::vector<osg::Geometry*> DuplicateList;
|
|
typedef std::map<osg::Geometry*,DuplicateList,LessGeometry> GeometryDuplicateMap;
|
|
|
|
typedef std::vector<DuplicateList> MergeList;
|
|
|
|
GeometryDuplicateMap geometryDuplicateMap;
|
|
osg::Geode::DrawableList standardDrawables;
|
|
|
|
unsigned int i;
|
|
for(i=0;i<geode.getNumDrawables();++i)
|
|
{
|
|
osg::Geometry* geom = geode.getDrawable(i)->asGeometry();
|
|
if (geom)
|
|
{
|
|
//geom->computeCorrectBindingsAndArraySizes();
|
|
|
|
if (!geometryContainsSharedArrays(*geom) && isOperationPermissibleForObject(geom))
|
|
{
|
|
geometryDuplicateMap[geom].push_back(geom);
|
|
}
|
|
else
|
|
{
|
|
standardDrawables.push_back(geode.getDrawable(i));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
standardDrawables.push_back(geode.getDrawable(i));
|
|
}
|
|
}
|
|
|
|
#if 1
|
|
bool needToDoMerge = false;
|
|
MergeList mergeList;
|
|
for(GeometryDuplicateMap::iterator itr=geometryDuplicateMap.begin();
|
|
itr!=geometryDuplicateMap.end();
|
|
++itr)
|
|
{
|
|
mergeList.push_back(DuplicateList());
|
|
DuplicateList* duplicateList = &mergeList.back();
|
|
|
|
if (itr->second.size()>1)
|
|
{
|
|
std::sort(itr->second.begin(),itr->second.end(),LessGeometryPrimitiveType());
|
|
osg::Geometry* lhs = itr->second[0];
|
|
|
|
duplicateList->push_back(lhs);
|
|
|
|
unsigned int numVertices = lhs->getVertexArray() ? lhs->getVertexArray()->getNumElements() : 0;
|
|
|
|
for(DuplicateList::iterator dupItr=itr->second.begin()+1;
|
|
dupItr!=itr->second.end();
|
|
++dupItr)
|
|
{
|
|
|
|
osg::Geometry* rhs = *dupItr;
|
|
|
|
unsigned int numRhsVertices = rhs->getVertexArray() ? rhs->getVertexArray()->getNumElements() : 0;
|
|
|
|
if (numVertices+numRhsVertices < _targetMaximumNumberOfVertices)
|
|
{
|
|
duplicateList->push_back(rhs);
|
|
numVertices += numRhsVertices;
|
|
needToDoMerge = true;
|
|
}
|
|
else
|
|
{
|
|
numVertices = numRhsVertices;
|
|
mergeList.push_back(DuplicateList());
|
|
duplicateList = &mergeList.back();
|
|
duplicateList->push_back(rhs);
|
|
}
|
|
|
|
}
|
|
}
|
|
else if (itr->second.size()>0)
|
|
{
|
|
duplicateList->push_back(itr->second[0]);
|
|
}
|
|
}
|
|
|
|
if (needToDoMerge)
|
|
{
|
|
// first take a reference to all the drawables to prevent them being deleted prematurely
|
|
osg::Geode::DrawableList keepDrawables;
|
|
keepDrawables.resize(geode.getNumDrawables());
|
|
for(i=0; i<geode.getNumDrawables(); ++i)
|
|
{
|
|
keepDrawables[i] = geode.getDrawable(i);
|
|
}
|
|
|
|
// now clear the drawable list of the Geode so we don't have to remove items one by one (which is slow)
|
|
geode.removeDrawables(0, geode.getNumDrawables());
|
|
|
|
// add back in the standard drawables which arn't possible to merge.
|
|
for(osg::Geode::DrawableList::iterator sitr = standardDrawables.begin();
|
|
sitr != standardDrawables.end();
|
|
++sitr)
|
|
{
|
|
geode.addDrawable(sitr->get());
|
|
}
|
|
|
|
// 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::notify(osg::INFO)<<"merged and removed Geometry "<<++co<<std::endl;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// osg::notify(osg::NOTICE)<<"After "<<geode.getNumDrawables()<<std::endl;
|
|
|
|
}
|
|
|
|
|
|
// convert all polygon primitives which has 3 indices into TRIANGLES, 4 indices into QUADS.
|
|
unsigned int i;
|
|
for(i=0;i<geode.getNumDrawables();++i)
|
|
{
|
|
osg::Geometry* geom = dynamic_cast<osg::Geometry*>(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.getNumDrawables();++i)
|
|
{
|
|
osg::Geometry* geom = dynamic_cast<osg::Geometry*>(geode.getDrawable(i));
|
|
if (geom)
|
|
{
|
|
if (geom->getNumPrimitiveSets()>0 &&
|
|
geom->getNormalBinding()!=osg::Geometry::BIND_PER_PRIMITIVE_SET &&
|
|
geom->getColorBinding()!=osg::Geometry::BIND_PER_PRIMITIVE_SET &&
|
|
geom->getSecondaryColorBinding()!=osg::Geometry::BIND_PER_PRIMITIVE_SET &&
|
|
geom->getFogCoordBinding()!=osg::Geometry::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 primtiveSet 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 incomming primitives to account for the pre exisiting 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
|
|
{
|
|
public:
|
|
|
|
osg::Array* _lhs;
|
|
int _offset;
|
|
|
|
MergeArrayVisitor() :
|
|
_lhs(0),
|
|
_offset(0) {}
|
|
|
|
|
|
void merge(osg::Array* lhs,osg::Array* rhs, int offset=0)
|
|
{
|
|
if (lhs==0 || rhs==0) return;
|
|
if (lhs->getType()!=rhs->getType()) return;
|
|
|
|
_lhs = lhs;
|
|
_offset = offset;
|
|
|
|
rhs->accept(*this);
|
|
}
|
|
|
|
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::notify(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::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;
|
|
unsigned int vbase = lhs.getVertexArray() ? lhs.getVertexArray()->getNumElements() : 0;
|
|
if (lhs.getVertexArray() && rhs.getVertexArray())
|
|
{
|
|
|
|
base = lhs.getVertexArray()->getNumElements();
|
|
merger.merge(lhs.getVertexArray(),rhs.getVertexArray());
|
|
|
|
}
|
|
else if (rhs.getVertexArray())
|
|
{
|
|
base = 0;
|
|
lhs.setVertexArray(rhs.getVertexArray());
|
|
}
|
|
|
|
if (lhs.getVertexIndices() && rhs.getVertexIndices())
|
|
{
|
|
|
|
base = lhs.getVertexIndices()->getNumElements();
|
|
merger.merge(lhs.getVertexIndices(),rhs.getVertexIndices(),vbase);
|
|
|
|
}
|
|
else if (rhs.getVertexIndices())
|
|
{
|
|
base = 0;
|
|
lhs.setVertexIndices(rhs.getVertexIndices());
|
|
}
|
|
|
|
|
|
unsigned int nbase = lhs.getNormalArray() ? lhs.getNormalArray()->getNumElements() : 0;
|
|
if (lhs.getNormalArray() && rhs.getNormalArray() && lhs.getNormalBinding()!=osg::Geometry::BIND_OVERALL)
|
|
{
|
|
merger.merge(lhs.getNormalArray(),rhs.getNormalArray());
|
|
}
|
|
else if (rhs.getNormalArray())
|
|
{
|
|
lhs.setNormalArray(rhs.getNormalArray());
|
|
}
|
|
|
|
if (lhs.getNormalIndices() && rhs.getNormalIndices() && lhs.getNormalBinding()!=osg::Geometry::BIND_OVERALL)
|
|
{
|
|
merger.merge(lhs.getNormalIndices(),rhs.getNormalIndices(),nbase);
|
|
}
|
|
else if (rhs.getNormalIndices())
|
|
{
|
|
// this assignment makes the assumption that lhs.NormalArray is empty as well and NormalIndices
|
|
lhs.setNormalIndices(rhs.getNormalIndices());
|
|
}
|
|
|
|
|
|
unsigned int cbase = lhs.getColorArray() ? lhs.getColorArray()->getNumElements() : 0;
|
|
if (lhs.getColorArray() && rhs.getColorArray() && lhs.getColorBinding()!=osg::Geometry::BIND_OVERALL)
|
|
{
|
|
merger.merge(lhs.getColorArray(),rhs.getColorArray());
|
|
}
|
|
else if (rhs.getColorArray())
|
|
{
|
|
lhs.setColorArray(rhs.getColorArray());
|
|
}
|
|
|
|
if (lhs.getColorIndices() && rhs.getColorIndices() && lhs.getColorBinding()!=osg::Geometry::BIND_OVERALL)
|
|
{
|
|
merger.merge(lhs.getColorIndices(),rhs.getColorIndices(),cbase);
|
|
}
|
|
else if (rhs.getColorIndices())
|
|
{
|
|
// this assignment makes the assumption that lhs.ColorArray is empty as well and ColorIndices
|
|
lhs.setColorIndices(rhs.getColorIndices());
|
|
}
|
|
|
|
unsigned int scbase = lhs.getSecondaryColorArray() ? lhs.getSecondaryColorArray()->getNumElements() : 0;
|
|
if (lhs.getSecondaryColorArray() && rhs.getSecondaryColorArray() && lhs.getSecondaryColorBinding()!=osg::Geometry::BIND_OVERALL)
|
|
{
|
|
merger.merge(lhs.getSecondaryColorArray(),rhs.getSecondaryColorArray());
|
|
}
|
|
else if (rhs.getSecondaryColorArray())
|
|
{
|
|
lhs.setSecondaryColorArray(rhs.getSecondaryColorArray());
|
|
}
|
|
|
|
if (lhs.getSecondaryColorIndices() && rhs.getSecondaryColorIndices() && lhs.getSecondaryColorBinding()!=osg::Geometry::BIND_OVERALL)
|
|
{
|
|
merger.merge(lhs.getSecondaryColorIndices(),rhs.getSecondaryColorIndices(),scbase);
|
|
}
|
|
else if (rhs.getSecondaryColorIndices())
|
|
{
|
|
// this assignment makes the assumption that lhs.SecondaryColorArray is empty as well and SecondaryColorIndices
|
|
lhs.setSecondaryColorIndices(rhs.getSecondaryColorIndices());
|
|
}
|
|
|
|
unsigned int fcbase = lhs.getFogCoordArray() ? lhs.getFogCoordArray()->getNumElements() : 0;
|
|
if (lhs.getFogCoordArray() && rhs.getFogCoordArray() && lhs.getFogCoordBinding()!=osg::Geometry::BIND_OVERALL)
|
|
{
|
|
merger.merge(lhs.getFogCoordArray(),rhs.getFogCoordArray());
|
|
}
|
|
else if (rhs.getFogCoordArray())
|
|
{
|
|
lhs.setFogCoordArray(rhs.getFogCoordArray());
|
|
}
|
|
|
|
if (lhs.getFogCoordIndices() && rhs.getFogCoordIndices() && lhs.getFogCoordBinding()!=osg::Geometry::BIND_OVERALL)
|
|
{
|
|
merger.merge(lhs.getFogCoordIndices(),rhs.getFogCoordIndices(),fcbase);
|
|
}
|
|
else if (rhs.getFogCoordIndices())
|
|
{
|
|
// this assignment makes the assumption that lhs.FogCoordArray is empty as well and FogCoordIndices
|
|
lhs.setFogCoordIndices(rhs.getFogCoordIndices());
|
|
}
|
|
|
|
|
|
unsigned int unit;
|
|
for(unit=0;unit<lhs.getNumTexCoordArrays();++unit)
|
|
{
|
|
unsigned int tcbase = lhs.getTexCoordArray(unit) ? lhs.getTexCoordArray(unit)->getNumElements() : 0;
|
|
merger.merge(lhs.getTexCoordArray(unit),rhs.getTexCoordArray(unit));
|
|
|
|
if (lhs.getTexCoordIndices(unit) && rhs.getTexCoordIndices(unit))
|
|
{
|
|
merger.merge(lhs.getTexCoordIndices(unit),rhs.getTexCoordIndices(unit),tcbase);
|
|
}
|
|
}
|
|
|
|
for(unit=0;unit<lhs.getNumVertexAttribArrays();++unit)
|
|
{
|
|
unsigned int vabase = lhs.getVertexAttribArray(unit) ? lhs.getVertexAttribArray(unit)->getNumElements() : 0;
|
|
merger.merge(lhs.getVertexAttribArray(unit),rhs.getVertexAttribArray(unit));
|
|
|
|
if (lhs.getVertexAttribIndices(unit) && rhs.getVertexAttribIndices(unit))
|
|
{
|
|
merger.merge(lhs.getVertexAttribIndices(unit),rhs.getVertexAttribIndices(unit),vabase);
|
|
}
|
|
}
|
|
|
|
|
|
// shift the indices of the incomming primitives to account for the pre exisiting geometry.
|
|
for(osg::Geometry::PrimitiveSetList::iterator 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;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
lhs.getPrimitiveSetList().insert(lhs.getPrimitiveSetList().end(),
|
|
rhs.getPrimitiveSetList().begin(),rhs.getPrimitiveSetList().end());
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Spatialize the scene to accelerate culling
|
|
//
|
|
|
|
void Optimizer::SpatializeGroupsVisitor::apply(osg::Group& group)
|
|
{
|
|
if (typeid(group)==typeid(osg::Group) || group.asTransform())
|
|
{
|
|
if (isOperationPermissibleForObject(&group))
|
|
{
|
|
_groupsToDivideList.insert(&group);
|
|
}
|
|
}
|
|
traverse(group);
|
|
}
|
|
|
|
bool Optimizer::SpatializeGroupsVisitor::divide(unsigned int maxNumTreesPerCell)
|
|
{
|
|
bool divided = false;
|
|
for(GroupsToDivideList::iterator itr=_groupsToDivideList.begin();
|
|
itr!=_groupsToDivideList.end();
|
|
++itr)
|
|
{
|
|
if (divide(*itr,maxNumTreesPerCell)) divided = true;
|
|
}
|
|
return divided;
|
|
}
|
|
|
|
bool Optimizer::SpatializeGroupsVisitor::divide(osg::Group* group, unsigned int maxNumTreesPerCell)
|
|
{
|
|
if (group->getNumChildren()<=maxNumTreesPerCell) return false;
|
|
|
|
// create the original box.
|
|
osg::BoundingBox bb;
|
|
unsigned int i;
|
|
for(i=0;i<group->getNumChildren();++i)
|
|
{
|
|
bb.expandBy(group->getChild(i)->getBound().center());
|
|
}
|
|
|
|
float radius = bb.radius();
|
|
float divide_distance = radius*0.7f;
|
|
bool xAxis = (bb.xMax()-bb.xMin())>divide_distance;
|
|
bool yAxis = (bb.yMax()-bb.yMin())>divide_distance;
|
|
bool zAxis = (bb.zMax()-bb.zMin())>divide_distance;
|
|
|
|
osg::notify(osg::INFO)<<"Dividing "<<group->className()<<" num children = "<<group->getNumChildren()<<" xAxis="<<xAxis<<" yAxis="<<yAxis<<" zAxis="<<zAxis<<std::endl;
|
|
|
|
if (!xAxis && !yAxis && !zAxis)
|
|
{
|
|
osg::notify(osg::INFO)<<" No axis to divide, stopping division."<<std::endl;
|
|
return false;
|
|
}
|
|
|
|
unsigned int numChildrenOnEntry = group->getNumChildren();
|
|
|
|
typedef std::pair< osg::BoundingBox, osg::ref_ptr<osg::Group> > BoxGroupPair;
|
|
typedef std::vector< BoxGroupPair > Boxes;
|
|
Boxes boxes;
|
|
boxes.push_back( BoxGroupPair(bb,new osg::Group) );
|
|
|
|
// divide up on each axis
|
|
if (xAxis)
|
|
{
|
|
unsigned int numCellsToDivide=boxes.size();
|
|
for(unsigned int i=0;i<numCellsToDivide;++i)
|
|
{
|
|
osg::BoundingBox& orig_cell = boxes[i].first;
|
|
osg::BoundingBox new_cell = orig_cell;
|
|
|
|
float xCenter = (orig_cell.xMin()+orig_cell.xMax())*0.5f;
|
|
orig_cell.xMax() = xCenter;
|
|
new_cell.xMin() = xCenter;
|
|
|
|
boxes.push_back(BoxGroupPair(new_cell,new osg::Group));
|
|
}
|
|
}
|
|
|
|
if (yAxis)
|
|
{
|
|
unsigned int numCellsToDivide=boxes.size();
|
|
for(unsigned int i=0;i<numCellsToDivide;++i)
|
|
{
|
|
osg::BoundingBox& orig_cell = boxes[i].first;
|
|
osg::BoundingBox new_cell = orig_cell;
|
|
|
|
float yCenter = (orig_cell.yMin()+orig_cell.yMax())*0.5f;
|
|
orig_cell.yMax() = yCenter;
|
|
new_cell.yMin() = yCenter;
|
|
|
|
boxes.push_back(BoxGroupPair(new_cell,new osg::Group));
|
|
}
|
|
}
|
|
|
|
if (zAxis)
|
|
{
|
|
unsigned int numCellsToDivide=boxes.size();
|
|
for(unsigned int i=0;i<numCellsToDivide;++i)
|
|
{
|
|
osg::BoundingBox& orig_cell = boxes[i].first;
|
|
osg::BoundingBox new_cell = orig_cell;
|
|
|
|
float zCenter = (orig_cell.zMin()+orig_cell.zMax())*0.5f;
|
|
orig_cell.zMax() = zCenter;
|
|
new_cell.zMin() = zCenter;
|
|
|
|
boxes.push_back(BoxGroupPair(new_cell,new osg::Group));
|
|
}
|
|
}
|
|
|
|
|
|
// create the groups to drop the children into
|
|
|
|
|
|
// bin each child into associated bb group
|
|
typedef std::vector< osg::ref_ptr<osg::Node> > NodeList;
|
|
NodeList unassignedList;
|
|
for(i=0;i<group->getNumChildren();++i)
|
|
{
|
|
bool assigned = false;
|
|
osg::Vec3 center = group->getChild(i)->getBound().center();
|
|
for(Boxes::iterator itr=boxes.begin();
|
|
itr!=boxes.end() && !assigned;
|
|
++itr)
|
|
{
|
|
if (itr->first.contains(center))
|
|
{
|
|
// move child from main group into bb group.
|
|
(itr->second)->addChild(group->getChild(i));
|
|
assigned = true;
|
|
}
|
|
}
|
|
if (!assigned)
|
|
{
|
|
unassignedList.push_back(group->getChild(i));
|
|
}
|
|
}
|
|
|
|
|
|
// now transfer nodes across, by :
|
|
// first removing from the original group,
|
|
// add in the bb groups
|
|
// add then the unassigned children.
|
|
|
|
|
|
// first removing from the original group,
|
|
group->removeChildren(0,group->getNumChildren());
|
|
|
|
// add in the bb groups
|
|
typedef std::vector< osg::ref_ptr<osg::Group> > GroupList;
|
|
GroupList groupsToDivideList;
|
|
for(Boxes::iterator itr=boxes.begin();
|
|
itr!=boxes.end();
|
|
++itr)
|
|
{
|
|
// move child from main group into bb group.
|
|
osg::Group* bb_group = (itr->second).get();
|
|
if (bb_group->getNumChildren()>0)
|
|
{
|
|
if (bb_group->getNumChildren()==1)
|
|
{
|
|
group->addChild(bb_group->getChild(0));
|
|
}
|
|
else
|
|
{
|
|
group->addChild(bb_group);
|
|
if (bb_group->getNumChildren()>maxNumTreesPerCell)
|
|
{
|
|
groupsToDivideList.push_back(bb_group);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// add then the unassigned children.
|
|
for(NodeList::iterator nitr=unassignedList.begin();
|
|
nitr!=unassignedList.end();
|
|
++nitr)
|
|
{
|
|
group->addChild(nitr->get());
|
|
}
|
|
|
|
// now call divide on all groups that require it.
|
|
for(GroupList::iterator gitr=groupsToDivideList.begin();
|
|
gitr!=groupsToDivideList.end();
|
|
++gitr)
|
|
{
|
|
divide(gitr->get(),maxNumTreesPerCell);
|
|
}
|
|
|
|
return (numChildrenOnEntry<group->getNumChildren());
|
|
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Duplicated subgraphs which are shared
|
|
//
|
|
|
|
void Optimizer::CopySharedSubgraphsVisitor::apply(osg::Node& node)
|
|
{
|
|
if (node.getNumParents()>1 && isOperationPermissibleForObject(&node))
|
|
{
|
|
_sharedNodeList.insert(&node);
|
|
}
|
|
traverse(node);
|
|
}
|
|
|
|
void Optimizer::CopySharedSubgraphsVisitor::copySharedNodes()
|
|
{
|
|
osg::notify(osg::INFO)<<"Shared node "<<_sharedNodeList.size()<<std::endl;
|
|
for(SharedNodeList::iterator itr=_sharedNodeList.begin();
|
|
itr!=_sharedNodeList.end();
|
|
++itr)
|
|
{
|
|
osg::notify(osg::INFO)<<" No parents "<<(*itr)->getNumParents()<<std::endl;
|
|
osg::Node* node = *itr;
|
|
for(unsigned int i=node->getNumParents()-1;i>0;--i)
|
|
{
|
|
// create a clone.
|
|
osg::ref_ptr<osg::Object> new_object = node->clone(osg::CopyOp::DEEP_COPY_NODES |
|
|
osg::CopyOp::DEEP_COPY_DRAWABLES);
|
|
// cast it to node.
|
|
osg::Node* new_node = dynamic_cast<osg::Node*>(new_object.get());
|
|
|
|
// replace the node by new_new
|
|
if (new_node) node->getParent(i)->replaceChild(node,new_node);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Set the attributes of textures up.
|
|
//
|
|
|
|
|
|
void Optimizer::TextureVisitor::apply(osg::Node& node)
|
|
{
|
|
|
|
osg::StateSet* ss = node.getStateSet();
|
|
if (ss &&
|
|
isOperationPermissibleForObject(&node) &&
|
|
isOperationPermissibleForObject(ss))
|
|
{
|
|
apply(*ss);
|
|
}
|
|
|
|
traverse(node);
|
|
}
|
|
|
|
void Optimizer::TextureVisitor::apply(osg::Geode& geode)
|
|
{
|
|
if (!isOperationPermissibleForObject(&geode)) return;
|
|
|
|
osg::StateSet* ss = geode.getStateSet();
|
|
|
|
if (ss && isOperationPermissibleForObject(ss))
|
|
{
|
|
apply(*ss);
|
|
}
|
|
|
|
for(unsigned int i=0;i<geode.getNumDrawables();++i)
|
|
{
|
|
osg::Drawable* drawable = geode.getDrawable(i);
|
|
if (drawable)
|
|
{
|
|
ss = drawable->getStateSet();
|
|
if (ss &&
|
|
isOperationPermissibleForObject(drawable) &&
|
|
isOperationPermissibleForObject(ss))
|
|
{
|
|
apply(*ss);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Optimizer::TextureVisitor::apply(osg::StateSet& stateset)
|
|
{
|
|
for(unsigned int i=0;i<stateset.getTextureAttributeList().size();++i)
|
|
{
|
|
osg::StateAttribute* sa = stateset.getTextureAttribute(i,osg::StateAttribute::TEXTURE);
|
|
osg::Texture* texture = dynamic_cast<osg::Texture*>(sa);
|
|
if (texture && isOperationPermissibleForObject(texture))
|
|
{
|
|
apply(*texture);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Optimizer::TextureVisitor::apply(osg::Texture& texture)
|
|
{
|
|
if (_changeAutoUnRef)
|
|
{
|
|
texture.setUnRefImageDataAfterApply(_valueAutoUnRef);
|
|
}
|
|
|
|
if (_changeClientImageStorage)
|
|
{
|
|
texture.setClientStorageHint(_valueClientImageStorage);
|
|
}
|
|
|
|
if (_changeAnisotropy)
|
|
{
|
|
texture.setMaxAnisotropy(_valueAnisotropy);
|
|
}
|
|
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// Merge geodes
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
void Optimizer::MergeGeodesVisitor::apply(osg::Group& group)
|
|
{
|
|
if (typeid(group)==typeid(osg::Group)) mergeGeodes(group);
|
|
traverse(group);
|
|
}
|
|
|
|
struct LessGeode
|
|
{
|
|
bool operator() (const osg::Geode* lhs,const osg::Geode* rhs) const
|
|
{
|
|
if (lhs->getStateSet()<rhs->getStateSet()) return true;
|
|
return false;
|
|
}
|
|
};
|
|
|
|
bool Optimizer::MergeGeodesVisitor::mergeGeodes(osg::Group& group)
|
|
{
|
|
if (!isOperationPermissibleForObject(&group)) return false;
|
|
|
|
typedef std::vector< osg::Geode* > DuplicateList;
|
|
typedef std::map<osg::Geode*,DuplicateList,LessGeode> GeodeDuplicateMap;
|
|
|
|
unsigned int i;
|
|
osg::NodeList children;
|
|
children.resize(group.getNumChildren());
|
|
for (i=0; i<group.getNumChildren(); ++i)
|
|
{
|
|
// keep a reference to this child so we can safely clear the group of all children
|
|
// this is done so we don't have to do a search and remove from the list later on.
|
|
children[i] = group.getChild(i);
|
|
}
|
|
|
|
// remove all children
|
|
group.removeChildren(0,group.getNumChildren());
|
|
|
|
GeodeDuplicateMap geodeDuplicateMap;
|
|
for (i=0; i<children.size(); ++i)
|
|
{
|
|
osg::Node* child = children[i].get();
|
|
|
|
if (typeid(*child)==typeid(osg::Geode))
|
|
{
|
|
osg::Geode* geode = static_cast<osg::Geode*>(child);
|
|
geodeDuplicateMap[geode].push_back(geode);
|
|
}
|
|
else
|
|
{
|
|
// not a geode so just add back into group as its a normal child
|
|
group.addChild(child);
|
|
}
|
|
}
|
|
|
|
// if no geodes then just return.
|
|
if (geodeDuplicateMap.empty()) return false;
|
|
|
|
osg::notify(osg::INFO)<<"mergeGeodes in group '"<<group.getName()<<"' "<<geodeDuplicateMap.size()<<std::endl;
|
|
|
|
// merge
|
|
for(GeodeDuplicateMap::iterator itr=geodeDuplicateMap.begin();
|
|
itr!=geodeDuplicateMap.end();
|
|
++itr)
|
|
{
|
|
if (itr->second.size()>1)
|
|
{
|
|
osg::Geode* lhs = itr->second[0];
|
|
|
|
// add geode back into group
|
|
group.addChild(lhs);
|
|
|
|
for(DuplicateList::iterator dupItr=itr->second.begin()+1;
|
|
dupItr!=itr->second.end();
|
|
++dupItr)
|
|
{
|
|
osg::Geode* rhs = *dupItr;
|
|
mergeGeode(*lhs,*rhs);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
osg::Geode* lhs = itr->second[0];
|
|
|
|
// add geode back into group
|
|
group.addChild(lhs);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Optimizer::MergeGeodesVisitor::mergeGeode(osg::Geode& lhs, osg::Geode& rhs)
|
|
{
|
|
for (unsigned int i=0; i<rhs.getNumDrawables(); ++i)
|
|
{
|
|
lhs.addDrawable(rhs.getDrawable(i));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// FlattenBillboardVisitor
|
|
////////////////////////////////////////////////////////////////////////////
|
|
void Optimizer::FlattenBillboardVisitor::reset()
|
|
{
|
|
_billboards.clear();
|
|
}
|
|
|
|
void Optimizer::FlattenBillboardVisitor::apply(osg::Billboard& billboard)
|
|
{
|
|
_billboards[&billboard].push_back(getNodePath());
|
|
}
|
|
|
|
void Optimizer::FlattenBillboardVisitor::process()
|
|
{
|
|
for(BillboardNodePathMap::iterator itr = _billboards.begin();
|
|
itr != _billboards.end();
|
|
++itr)
|
|
{
|
|
bool mergeAcceptable = true;
|
|
|
|
osg::ref_ptr<osg::Billboard> billboard = itr->first;
|
|
|
|
NodePathList& npl = itr->second;
|
|
osg::Group* mainGroup = 0;
|
|
if (npl.size()>1)
|
|
{
|
|
for(NodePathList::iterator nitr = npl.begin();
|
|
nitr != npl.end();
|
|
++nitr)
|
|
{
|
|
osg::NodePath& np = *nitr;
|
|
if (np.size()>=3)
|
|
{
|
|
osg::Group* group = dynamic_cast<osg::Group*>(np[np.size()-3]);
|
|
if (mainGroup==0) mainGroup = group;
|
|
|
|
osg::MatrixTransform* mt = dynamic_cast<osg::MatrixTransform*>(np[np.size()-2]);
|
|
|
|
if (group == mainGroup &&
|
|
np[np.size()-1]==billboard.get() &&
|
|
mt && mt->getDataVariance()==osg::Object::STATIC &&
|
|
mt->getNumChildren()==1)
|
|
{
|
|
const osg::Matrix& m = mt->getMatrix();
|
|
mergeAcceptable = (m(0,0)==1.0 && m(0,1)==0.0 && m(0,2)==0.0 && m(0,3)==0.0 &&
|
|
m(1,0)==0.0 && m(1,1)==1.0 && m(1,2)==0.0 && m(1,3)==0.0 &&
|
|
m(2,0)==0.0 && m(2,1)==0.0 && m(2,2)==1.0 && m(2,3)==0.0 &&
|
|
m(3,3)==1.0);
|
|
}
|
|
else
|
|
{
|
|
mergeAcceptable = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mergeAcceptable = false;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mergeAcceptable = false;
|
|
}
|
|
|
|
if (mergeAcceptable)
|
|
{
|
|
osg::Billboard* new_billboard = new osg::Billboard;
|
|
new_billboard->setMode(billboard->getMode());
|
|
new_billboard->setAxis(billboard->getAxis());
|
|
new_billboard->setStateSet(billboard->getStateSet());
|
|
new_billboard->setName(billboard->getName());
|
|
|
|
mainGroup->addChild(new_billboard);
|
|
|
|
typedef std::set<osg::MatrixTransform*> MatrixTransformSet;
|
|
MatrixTransformSet mts;
|
|
|
|
for(NodePathList::iterator nitr = npl.begin();
|
|
nitr != npl.end();
|
|
++nitr)
|
|
{
|
|
osg::NodePath& np = *nitr;
|
|
osg::MatrixTransform* mt = dynamic_cast<osg::MatrixTransform*>(np[np.size()-2]);
|
|
mts.insert(mt);
|
|
}
|
|
|
|
for(MatrixTransformSet::iterator mitr = mts.begin();
|
|
mitr != mts.end();
|
|
++mitr)
|
|
{
|
|
osg::MatrixTransform* mt = *mitr;
|
|
for(unsigned int i=0; i<billboard->getNumDrawables(); ++i)
|
|
{
|
|
new_billboard->addDrawable(billboard->getDrawable(i),
|
|
billboard->getPosition(i)*mt->getMatrix());
|
|
}
|
|
mainGroup->removeChild(mt);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// TextureAtlasBuilder
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
Optimizer::TextureAtlasBuilder::TextureAtlasBuilder():
|
|
_maximumAtlasWidth(2048),
|
|
_maximumAtlasHeight(2048),
|
|
_margin(8)
|
|
{
|
|
}
|
|
|
|
void Optimizer::TextureAtlasBuilder::reset()
|
|
{
|
|
_sourceList.clear();
|
|
_atlasList.clear();
|
|
}
|
|
|
|
void Optimizer::TextureAtlasBuilder::setMaximumAtlasSize(unsigned int width, unsigned int height)
|
|
{
|
|
_maximumAtlasWidth = width;
|
|
_maximumAtlasHeight = height;
|
|
}
|
|
|
|
void Optimizer::TextureAtlasBuilder::setMargin(unsigned int margin)
|
|
{
|
|
_margin = margin;
|
|
}
|
|
|
|
void Optimizer::TextureAtlasBuilder::addSource(const osg::Image* image)
|
|
{
|
|
if (!getSource(image)) _sourceList.push_back(new Source(image));
|
|
}
|
|
|
|
void Optimizer::TextureAtlasBuilder::addSource(const osg::Texture2D* texture)
|
|
{
|
|
if (!getSource(texture)) _sourceList.push_back(new Source(texture));
|
|
}
|
|
|
|
void Optimizer::TextureAtlasBuilder::buildAtlas()
|
|
{
|
|
// assign the source to the atlas
|
|
_atlasList.clear();
|
|
for(SourceList::iterator sitr = _sourceList.begin();
|
|
sitr != _sourceList.end();
|
|
++sitr)
|
|
{
|
|
Source* source = sitr->get();
|
|
if (source->suitableForAtlas(_maximumAtlasWidth,_maximumAtlasHeight,_margin))
|
|
{
|
|
bool addedSourceToAtlas = false;
|
|
for(AtlasList::iterator aitr = _atlasList.begin();
|
|
aitr != _atlasList.end() && !addedSourceToAtlas;
|
|
++aitr)
|
|
{
|
|
osg::notify(osg::INFO)<<"checking source "<<source->_image->getFileName()<<" to see it it'll fit in atlas "<<aitr->get()<<std::endl;
|
|
if ((*aitr)->doesSourceFit(source))
|
|
{
|
|
addedSourceToAtlas = true;
|
|
(*aitr)->addSource(source);
|
|
}
|
|
else
|
|
{
|
|
osg::notify(osg::INFO)<<"source "<<source->_image->getFileName()<<" does not fit in atlas "<<aitr->get()<<std::endl;
|
|
}
|
|
}
|
|
|
|
if (!addedSourceToAtlas)
|
|
{
|
|
osg::notify(osg::INFO)<<"creating new Atlas for "<<source->_image->getFileName()<<std::endl;
|
|
|
|
osg::ref_ptr<Atlas> atlas = new Atlas(_maximumAtlasWidth,_maximumAtlasHeight,_margin);
|
|
_atlasList.push_back(atlas.get());
|
|
|
|
atlas->addSource(source);
|
|
}
|
|
}
|
|
}
|
|
|
|
// build the atlas which are suitable for use, and discard the rest.
|
|
AtlasList activeAtlasList;
|
|
for(AtlasList::iterator aitr = _atlasList.begin();
|
|
aitr != _atlasList.end();
|
|
++aitr)
|
|
{
|
|
Atlas* atlas = aitr->get();
|
|
|
|
if (atlas->_sourceList.size()==1)
|
|
{
|
|
// no point building an atlas with only one entry
|
|
// so disconnect the source.
|
|
Source* source = atlas->_sourceList[0].get();
|
|
source->_atlas = 0;
|
|
atlas->_sourceList.clear();
|
|
}
|
|
|
|
if (!(atlas->_sourceList.empty()))
|
|
{
|
|
std::stringstream ostr;
|
|
ostr<<"atlas_"<<activeAtlasList.size()<<".rgb";
|
|
atlas->_image->setFileName(ostr.str());
|
|
|
|
activeAtlasList.push_back(atlas);
|
|
atlas->clampToNearestPowerOfTwoSize();
|
|
atlas->copySources();
|
|
|
|
}
|
|
}
|
|
|
|
// keep only the active atlas'
|
|
_atlasList.swap(activeAtlasList);
|
|
|
|
}
|
|
|
|
osg::Image* Optimizer::TextureAtlasBuilder::getImageAtlas(unsigned int i)
|
|
{
|
|
Source* source = _sourceList[i].get();
|
|
Atlas* atlas = source ? source->_atlas : 0;
|
|
return atlas ? atlas->_image.get() : 0;
|
|
}
|
|
|
|
osg::Texture2D* Optimizer::TextureAtlasBuilder::getTextureAtlas(unsigned int i)
|
|
{
|
|
Source* source = _sourceList[i].get();
|
|
Atlas* atlas = source ? source->_atlas : 0;
|
|
return atlas ? atlas->_texture.get() : 0;
|
|
}
|
|
|
|
osg::Matrix Optimizer::TextureAtlasBuilder::getTextureMatrix(unsigned int i)
|
|
{
|
|
Source* source = _sourceList[i].get();
|
|
return source ? source->computeTextureMatrix() : osg::Matrix();
|
|
}
|
|
|
|
osg::Image* Optimizer::TextureAtlasBuilder::getImageAtlas(const osg::Image* image)
|
|
{
|
|
Source* source = getSource(image);
|
|
Atlas* atlas = source ? source->_atlas : 0;
|
|
return atlas ? atlas->_image.get() : 0;
|
|
}
|
|
|
|
osg::Texture2D* Optimizer::TextureAtlasBuilder::getTextureAtlas(const osg::Image* image)
|
|
{
|
|
Source* source = getSource(image);
|
|
Atlas* atlas = source ? source->_atlas : 0;
|
|
return atlas ? atlas->_texture.get() : 0;
|
|
}
|
|
|
|
osg::Matrix Optimizer::TextureAtlasBuilder::getTextureMatrix(const osg::Image* image)
|
|
{
|
|
Source* source = getSource(image);
|
|
return source ? source->computeTextureMatrix() : osg::Matrix();
|
|
}
|
|
|
|
osg::Image* Optimizer::TextureAtlasBuilder::getImageAtlas(const osg::Texture2D* texture)
|
|
{
|
|
Source* source = getSource(texture);
|
|
Atlas* atlas = source ? source->_atlas : 0;
|
|
return atlas ? atlas->_image.get() : 0;
|
|
}
|
|
|
|
osg::Texture2D* Optimizer::TextureAtlasBuilder::getTextureAtlas(const osg::Texture2D* texture)
|
|
{
|
|
Source* source = getSource(texture);
|
|
Atlas* atlas = source ? source->_atlas : 0;
|
|
return atlas ? atlas->_texture.get() : 0;
|
|
}
|
|
|
|
osg::Matrix Optimizer::TextureAtlasBuilder::getTextureMatrix(const osg::Texture2D* texture)
|
|
{
|
|
Source* source = getSource(texture);
|
|
return source ? source->computeTextureMatrix() : osg::Matrix();
|
|
}
|
|
|
|
Optimizer::TextureAtlasBuilder::Source* Optimizer::TextureAtlasBuilder::getSource(const osg::Image* image)
|
|
{
|
|
for(SourceList::iterator itr = _sourceList.begin();
|
|
itr != _sourceList.end();
|
|
++itr)
|
|
{
|
|
if ((*itr)->_image == image) return itr->get();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
Optimizer::TextureAtlasBuilder::Source* Optimizer::TextureAtlasBuilder::getSource(const osg::Texture2D* texture)
|
|
{
|
|
for(SourceList::iterator itr = _sourceList.begin();
|
|
itr != _sourceList.end();
|
|
++itr)
|
|
{
|
|
if ((*itr)->_texture == texture) return itr->get();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool Optimizer::TextureAtlasBuilder::Source::suitableForAtlas(unsigned int maximumAtlasWidth, unsigned int maximumAtlasHeight, unsigned int margin)
|
|
{
|
|
if (!_image) return false;
|
|
|
|
// size too big?
|
|
if (_image->s()+margin*2 > maximumAtlasWidth) return false;
|
|
if (_image->t()+margin*2 > maximumAtlasHeight) return false;
|
|
|
|
switch(_image->getPixelFormat())
|
|
{
|
|
case(GL_COMPRESSED_ALPHA_ARB):
|
|
case(GL_COMPRESSED_INTENSITY_ARB):
|
|
case(GL_COMPRESSED_LUMINANCE_ALPHA_ARB):
|
|
case(GL_COMPRESSED_LUMINANCE_ARB):
|
|
case(GL_COMPRESSED_RGBA_ARB):
|
|
case(GL_COMPRESSED_RGB_ARB):
|
|
case(GL_COMPRESSED_RGB_S3TC_DXT1_EXT):
|
|
case(GL_COMPRESSED_RGBA_S3TC_DXT1_EXT):
|
|
case(GL_COMPRESSED_RGBA_S3TC_DXT3_EXT):
|
|
case(GL_COMPRESSED_RGBA_S3TC_DXT5_EXT):
|
|
// can't handle compressed textures inside an atlas
|
|
return false;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if ((_image->getPixelSizeInBits() % 8) != 0)
|
|
{
|
|
// pixel size not byte aligned so report as not suitable to prevent other atlas code from having problems with byte boundaries.
|
|
return false;
|
|
}
|
|
|
|
if (_texture.valid())
|
|
{
|
|
|
|
if (_texture->getWrap(osg::Texture2D::WRAP_S)==osg::Texture2D::REPEAT ||
|
|
_texture->getWrap(osg::Texture2D::WRAP_S)==osg::Texture2D::MIRROR)
|
|
{
|
|
// can't support repeating textures in texture atlas
|
|
return false;
|
|
}
|
|
|
|
if (_texture->getWrap(osg::Texture2D::WRAP_T)==osg::Texture2D::REPEAT ||
|
|
_texture->getWrap(osg::Texture2D::WRAP_T)==osg::Texture2D::MIRROR)
|
|
{
|
|
// can't support repeating textures in texture atlas
|
|
return false;
|
|
}
|
|
|
|
if (_texture->getReadPBuffer()!=0)
|
|
{
|
|
// pbuffer textures not suitable
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
osg::Matrix Optimizer::TextureAtlasBuilder::Source::computeTextureMatrix() const
|
|
{
|
|
if (!_atlas) return osg::Matrix();
|
|
if (!_image) return osg::Matrix();
|
|
if (!(_atlas->_image)) return osg::Matrix();
|
|
|
|
return osg::Matrix::scale(float(_image->s())/float(_atlas->_image->s()), float(_image->t())/float(_atlas->_image->t()), 1.0)*
|
|
osg::Matrix::translate(float(_x)/float(_atlas->_image->s()), float(_y)/float(_atlas->_image->t()), 0.0);
|
|
}
|
|
|
|
bool Optimizer::TextureAtlasBuilder::Atlas::doesSourceFit(Source* source)
|
|
{
|
|
// does the source have a valid image?
|
|
const osg::Image* sourceImage = source->_image.get();
|
|
if (!sourceImage) return false;
|
|
|
|
// does pixel format match?
|
|
if (_image.valid())
|
|
{
|
|
if (_image->getPixelFormat() != sourceImage->getPixelFormat()) return false;
|
|
if (_image->getDataType() != sourceImage->getDataType()) return false;
|
|
}
|
|
|
|
const osg::Texture2D* sourceTexture = source->_texture.get();
|
|
if (sourceTexture)
|
|
{
|
|
if (sourceTexture->getWrap(osg::Texture2D::WRAP_S)==osg::Texture2D::REPEAT ||
|
|
sourceTexture->getWrap(osg::Texture2D::WRAP_S)==osg::Texture2D::MIRROR)
|
|
{
|
|
// can't support repeating textures in texture atlas
|
|
return false;
|
|
}
|
|
|
|
if (sourceTexture->getWrap(osg::Texture2D::WRAP_T)==osg::Texture2D::REPEAT ||
|
|
sourceTexture->getWrap(osg::Texture2D::WRAP_T)==osg::Texture2D::MIRROR)
|
|
{
|
|
// can't support repeating textures in texture atlas
|
|
return false;
|
|
}
|
|
|
|
if (sourceTexture->getReadPBuffer()!=0)
|
|
{
|
|
// pbuffer textures not suitable
|
|
return false;
|
|
}
|
|
|
|
if (_texture.valid())
|
|
{
|
|
|
|
bool sourceUsesBorder = sourceTexture->getWrap(osg::Texture2D::WRAP_S)==osg::Texture2D::CLAMP_TO_BORDER ||
|
|
sourceTexture->getWrap(osg::Texture2D::WRAP_T)==osg::Texture2D::CLAMP_TO_BORDER;
|
|
|
|
bool atlasUsesBorder = sourceTexture->getWrap(osg::Texture2D::WRAP_S)==osg::Texture2D::CLAMP_TO_BORDER ||
|
|
sourceTexture->getWrap(osg::Texture2D::WRAP_T)==osg::Texture2D::CLAMP_TO_BORDER;
|
|
|
|
if (sourceUsesBorder!=atlasUsesBorder)
|
|
{
|
|
// border wrapping does not match
|
|
return false;
|
|
}
|
|
|
|
if (sourceUsesBorder)
|
|
{
|
|
// border colours don't match
|
|
if (_texture->getBorderColor() != sourceTexture->getBorderColor()) return false;
|
|
}
|
|
|
|
if (_texture->getFilter(osg::Texture2D::MIN_FILTER) != sourceTexture->getFilter(osg::Texture2D::MIN_FILTER))
|
|
{
|
|
// inconsitent min filters
|
|
return false;
|
|
}
|
|
|
|
if (_texture->getFilter(osg::Texture2D::MAG_FILTER) != sourceTexture->getFilter(osg::Texture2D::MAG_FILTER))
|
|
{
|
|
// inconsitent mag filters
|
|
return false;
|
|
}
|
|
|
|
if (_texture->getMaxAnisotropy() != sourceTexture->getMaxAnisotropy())
|
|
{
|
|
// anisotropy different.
|
|
return false;
|
|
}
|
|
|
|
if (_texture->getInternalFormat() != sourceTexture->getInternalFormat())
|
|
{
|
|
// internal formats inconistent
|
|
return false;
|
|
}
|
|
|
|
if (_texture->getShadowCompareFunc() != sourceTexture->getShadowCompareFunc())
|
|
{
|
|
// shadow functions inconsitent
|
|
return false;
|
|
}
|
|
|
|
if (_texture->getShadowTextureMode() != sourceTexture->getShadowTextureMode())
|
|
{
|
|
// shadow texture mode inconsitent
|
|
return false;
|
|
}
|
|
|
|
if (_texture->getShadowAmbient() != sourceTexture->getShadowAmbient())
|
|
{
|
|
// shadow ambient inconsitent
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (sourceImage->s() + 2*_margin > _maximumAtlasWidth)
|
|
{
|
|
// image too big for Atlas
|
|
return false;
|
|
}
|
|
|
|
if (sourceImage->t() + 2*_margin > _maximumAtlasHeight)
|
|
{
|
|
// image too big for Atlas
|
|
return false;
|
|
}
|
|
|
|
if ((_y + sourceImage->t() + 2*_margin) > _maximumAtlasHeight)
|
|
{
|
|
// image doesn't have up space in height axis.
|
|
return false;
|
|
}
|
|
|
|
// does the source fit in the current row?
|
|
if ((_x + sourceImage->s() + 2*_margin) <= _maximumAtlasWidth)
|
|
{
|
|
// yes it fits :-)
|
|
osg::notify(osg::INFO)<<"Fits in current row"<<std::endl;
|
|
return true;
|
|
}
|
|
|
|
// does the source fit in the new row up?
|
|
if ((_height + sourceImage->t() + 2*_margin) <= _maximumAtlasHeight)
|
|
{
|
|
// yes it fits :-)
|
|
osg::notify(osg::INFO)<<"Fits in next row"<<std::endl;
|
|
return true;
|
|
}
|
|
|
|
// no space for the texture
|
|
return false;
|
|
}
|
|
|
|
bool Optimizer::TextureAtlasBuilder::Atlas::addSource(Source* source)
|
|
{
|
|
// double check source is compatible
|
|
if (!doesSourceFit(source))
|
|
{
|
|
osg::notify(osg::INFO)<<"source "<<source->_image->getFileName()<<" does not fit in atlas "<<this<<std::endl;
|
|
return false;
|
|
}
|
|
|
|
const osg::Image* sourceImage = source->_image.get();
|
|
const osg::Texture2D* sourceTexture = source->_texture.get();
|
|
|
|
if (!_image)
|
|
{
|
|
// need to create an image of the same pixel format to store the atlas in
|
|
_image = new osg::Image;
|
|
_image->setPixelFormat(sourceImage->getPixelFormat());
|
|
_image->setDataType(sourceImage->getDataType());
|
|
}
|
|
|
|
if (!_texture && sourceTexture)
|
|
{
|
|
_texture = new osg::Texture2D(_image.get());
|
|
|
|
_texture->setWrap(osg::Texture2D::WRAP_S, sourceTexture->getWrap(osg::Texture2D::WRAP_S));
|
|
_texture->setWrap(osg::Texture2D::WRAP_T, sourceTexture->getWrap(osg::Texture2D::WRAP_T));
|
|
|
|
_texture->setBorderColor(sourceTexture->getBorderColor());
|
|
_texture->setBorderWidth(0);
|
|
|
|
_texture->setFilter(osg::Texture2D::MIN_FILTER, sourceTexture->getFilter(osg::Texture2D::MIN_FILTER));
|
|
_texture->setFilter(osg::Texture2D::MAG_FILTER, sourceTexture->getFilter(osg::Texture2D::MAG_FILTER));
|
|
|
|
_texture->setMaxAnisotropy(sourceTexture->getMaxAnisotropy());
|
|
|
|
_texture->setInternalFormat(sourceTexture->getInternalFormat());
|
|
|
|
_texture->setShadowCompareFunc(sourceTexture->getShadowCompareFunc());
|
|
_texture->setShadowTextureMode(sourceTexture->getShadowTextureMode());
|
|
_texture->setShadowAmbient(sourceTexture->getShadowAmbient());
|
|
|
|
}
|
|
|
|
// now work out where to fit it, first try current row.
|
|
if ((_x + sourceImage->s() + 2*_margin) <= _maximumAtlasWidth)
|
|
{
|
|
// yes it fits, so add the source to the atlas's list of sources it contains
|
|
_sourceList.push_back(source);
|
|
|
|
osg::notify(osg::INFO)<<"current row insertion, source "<<source->_image->getFileName()<<" "<<_x<<","<<_y<<" fits in row of atlas "<<this<<std::endl;
|
|
|
|
// set up the source so it knows where it is in the atlas
|
|
source->_x = _x + _margin;
|
|
source->_y = _y + _margin;
|
|
source->_atlas = this;
|
|
|
|
// move the atlas' cursor along to the right
|
|
_x += sourceImage->s() + 2*_margin;
|
|
|
|
if (_x > _width) _width = _x;
|
|
|
|
unsigned int localTop = _y + sourceImage->t() + 2*_margin;
|
|
if ( localTop > _height) _height = localTop;
|
|
|
|
return true;
|
|
}
|
|
|
|
// does the source fit in the new row up?
|
|
if ((_height + sourceImage->t() + 2*_margin) <= _maximumAtlasHeight)
|
|
{
|
|
// now row so first need to reset the atlas cursor
|
|
_x = 0;
|
|
_y = _height;
|
|
|
|
// yes it fits, so add the source to the atlas' list of sources it contains
|
|
_sourceList.push_back(source);
|
|
|
|
osg::notify(osg::INFO)<<"next row insertion, source "<<source->_image->getFileName()<<" "<<_x<<","<<_y<<" fits in row of atlas "<<this<<std::endl;
|
|
|
|
// set up the source so it knows where it is in the atlas
|
|
source->_x = _x + _margin;
|
|
source->_y = _y + _margin;
|
|
source->_atlas = this;
|
|
|
|
// move the atlas' cursor along to the right
|
|
_x += sourceImage->s() + 2*_margin;
|
|
|
|
if (_x > _width) _width = _x;
|
|
|
|
_height = _y + sourceImage->t() + 2*_margin;
|
|
|
|
osg::notify(osg::INFO)<<"source "<<source->_image->getFileName()<<" "<<_x<<","<<_y<<" fits in row of atlas "<<this<<std::endl;
|
|
|
|
return true;
|
|
}
|
|
|
|
osg::notify(osg::INFO)<<"source "<<source->_image->getFileName()<<" does not fit in atlas "<<this<<std::endl;
|
|
|
|
// shouldn't get here, unless doesSourceFit isn't working...
|
|
return false;
|
|
}
|
|
|
|
void Optimizer::TextureAtlasBuilder::Atlas::clampToNearestPowerOfTwoSize()
|
|
{
|
|
unsigned int w = 1;
|
|
while (w<_width) w *= 2;
|
|
|
|
unsigned int h = 1;
|
|
while (h<_height) h *= 2;
|
|
|
|
osg::notify(osg::INFO)<<"Clamping "<<_width<<", "<<_height<<" to "<<w<<","<<h<<std::endl;
|
|
|
|
_width = w;
|
|
_height = h;
|
|
}
|
|
|
|
#if 0
|
|
#include <osgDB/WriteFile>
|
|
#endif
|
|
|
|
void Optimizer::TextureAtlasBuilder::Atlas::copySources()
|
|
{
|
|
osg::notify(osg::INFO)<<"Allocated to "<<_width<<","<<_height<<std::endl;
|
|
_image->allocateImage(_width,_height,1,
|
|
_image->getPixelFormat(),_image->getDataType(),
|
|
_image->getPacking());
|
|
|
|
{
|
|
// clear memory
|
|
unsigned int size = _image->getTotalSizeInBytes();
|
|
unsigned char* str = _image->data();
|
|
for(unsigned int i=0; i<size; ++i) *(str++) = 0;
|
|
}
|
|
|
|
osg::notify(osg::INFO)<<"Atlas::copySources() "<<std::endl;
|
|
for(SourceList::iterator itr = _sourceList.begin();
|
|
itr !=_sourceList.end();
|
|
++itr)
|
|
{
|
|
Source* source = itr->get();
|
|
Atlas* atlas = source->_atlas;
|
|
|
|
if (atlas)
|
|
{
|
|
osg::notify(osg::INFO)<<"Copying image "<<source->_image->getFileName()<<" to "<<source->_x<<" ,"<<source->_y<<std::endl;
|
|
osg::notify(osg::INFO)<<" image size "<<source->_image->s()<<","<<source->_image->t()<<std::endl;
|
|
|
|
const osg::Image* sourceImage = source->_image.get();
|
|
osg::Image* atlasImage = atlas->_image.get();
|
|
|
|
unsigned int rowSize = sourceImage->getRowSizeInBytes();
|
|
unsigned int pixelSizeInBits = sourceImage->getPixelSizeInBits();
|
|
unsigned int pixelSizeInBytes = pixelSizeInBits/8;
|
|
unsigned int marginSizeInBytes = pixelSizeInBytes*_margin;
|
|
|
|
unsigned int x = source->_x;
|
|
unsigned int y = source->_y;
|
|
|
|
int t;
|
|
for(t=0; t<sourceImage->t(); ++t, ++y)
|
|
{
|
|
unsigned char* destPtr = atlasImage->data(x, y);
|
|
const unsigned char* sourcePtr = sourceImage->data(0, t);
|
|
for(unsigned int i=0; i<rowSize; i++)
|
|
{
|
|
*(destPtr++) = *(sourcePtr++);
|
|
}
|
|
}
|
|
|
|
// copy top row margin
|
|
y = source->_y + sourceImage->t();
|
|
unsigned int m;
|
|
for(m=0; m<_margin; ++m, ++y)
|
|
{
|
|
unsigned char* destPtr = atlasImage->data(x, y);
|
|
const unsigned char* sourcePtr = sourceImage->data(0, sourceImage->t()-1);
|
|
for(unsigned int i=0; i<rowSize; i++)
|
|
{
|
|
*(destPtr++) = *(sourcePtr++);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// copy bottom row margin
|
|
y = source->_y-1;
|
|
for(m=0; m<_margin; ++m, --y)
|
|
{
|
|
unsigned char* destPtr = atlasImage->data(x, y);
|
|
const unsigned char* sourcePtr = sourceImage->data(0, 0);
|
|
for(unsigned int i=0; i<rowSize; i++)
|
|
{
|
|
*(destPtr++) = *(sourcePtr++);
|
|
}
|
|
|
|
}
|
|
|
|
// copy left column margin
|
|
y = source->_y;
|
|
for(t=0; t<sourceImage->t(); ++t, ++y)
|
|
{
|
|
x = source->_x-1;
|
|
for(m=0; m<_margin; ++m, --x)
|
|
{
|
|
unsigned char* destPtr = atlasImage->data(x, y);
|
|
const unsigned char* sourcePtr = sourceImage->data(0, t);
|
|
for(unsigned int i=0; i<pixelSizeInBytes; i++)
|
|
{
|
|
*(destPtr++) = *(sourcePtr++);
|
|
}
|
|
}
|
|
}
|
|
|
|
// copy right column margin
|
|
y = source->_y;
|
|
for(t=0; t<sourceImage->t(); ++t, ++y)
|
|
{
|
|
x = source->_x + sourceImage->s();
|
|
for(m=0; m<_margin; ++m, ++x)
|
|
{
|
|
unsigned char* destPtr = atlasImage->data(x, y);
|
|
const unsigned char* sourcePtr = sourceImage->data(sourceImage->s()-1, t);
|
|
for(unsigned int i=0; i<pixelSizeInBytes; i++)
|
|
{
|
|
*(destPtr++) = *(sourcePtr++);
|
|
}
|
|
}
|
|
}
|
|
|
|
// copy top left corner margin
|
|
y = source->_y + sourceImage->t();
|
|
for(m=0; m<_margin; ++m, ++y)
|
|
{
|
|
unsigned char* destPtr = atlasImage->data(source->_x - _margin, y);
|
|
unsigned char* sourcePtr = atlasImage->data(source->_x - _margin, y-1); // copy from row below
|
|
for(unsigned int i=0; i<marginSizeInBytes; i++)
|
|
{
|
|
*(destPtr++) = *(sourcePtr++);
|
|
}
|
|
}
|
|
|
|
// copy top right corner margin
|
|
y = source->_y + sourceImage->t();
|
|
for(m=0; m<_margin; ++m, ++y)
|
|
{
|
|
unsigned char* destPtr = atlasImage->data(source->_x + sourceImage->s(), y);
|
|
unsigned char* sourcePtr = atlasImage->data(source->_x + sourceImage->s(), y-1); // copy from row below
|
|
for(unsigned int i=0; i<marginSizeInBytes; i++)
|
|
{
|
|
*(destPtr++) = *(sourcePtr++);
|
|
}
|
|
}
|
|
|
|
// copy bottom left corner margin
|
|
y = source->_y - 1;
|
|
for(m=0; m<_margin; ++m, --y)
|
|
{
|
|
unsigned char* destPtr = atlasImage->data(source->_x - _margin, y);
|
|
unsigned char* sourcePtr = atlasImage->data(source->_x - _margin, y+1); // copy from row below
|
|
for(unsigned int i=0; i<marginSizeInBytes; i++)
|
|
{
|
|
*(destPtr++) = *(sourcePtr++);
|
|
}
|
|
}
|
|
|
|
// copy bottom right corner margin
|
|
y = source->_y - 1;
|
|
for(m=0; m<_margin; ++m, --y)
|
|
{
|
|
unsigned char* destPtr = atlasImage->data(source->_x + sourceImage->s(), y);
|
|
unsigned char* sourcePtr = atlasImage->data(source->_x + sourceImage->s(), y+1); // copy from row below
|
|
for(unsigned int i=0; i<marginSizeInBytes; i++)
|
|
{
|
|
*(destPtr++) = *(sourcePtr++);
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
osg::notify(osg::NOTICE)<<"Writing atlas image "<<_image->getFileName()<<std::endl;
|
|
osgDB::writeImageFile(*_image,_image->getFileName());
|
|
#endif
|
|
}
|
|
|
|
|
|
void Optimizer::TextureAtlasVisitor::reset()
|
|
{
|
|
_statesetMap.clear();
|
|
_statesetStack.clear();
|
|
_textures.clear();
|
|
_builder.reset();
|
|
}
|
|
|
|
bool Optimizer::TextureAtlasVisitor::pushStateSet(osg::StateSet* stateset)
|
|
{
|
|
osg::StateSet::TextureAttributeList& tal = stateset->getTextureAttributeList();
|
|
|
|
// if no textures ignore
|
|
if (tal.empty()) return false;
|
|
|
|
bool pushStateState = false;
|
|
|
|
// if already in stateset list ignore
|
|
if (_statesetMap.count(stateset)>0)
|
|
{
|
|
pushStateState = true;
|
|
}
|
|
else
|
|
{
|
|
bool containsTexture2D = false;
|
|
for(unsigned int unit=0; unit<tal.size(); ++unit)
|
|
{
|
|
osg::Texture2D* texture2D = dynamic_cast<osg::Texture2D*>(stateset->getTextureAttribute(unit,osg::StateAttribute::TEXTURE));
|
|
if (texture2D)
|
|
{
|
|
containsTexture2D = true;
|
|
_textures.insert(texture2D);
|
|
}
|
|
}
|
|
|
|
if (containsTexture2D)
|
|
{
|
|
_statesetMap[stateset];
|
|
pushStateState = true;
|
|
}
|
|
}
|
|
|
|
if (pushStateState)
|
|
{
|
|
_statesetStack.push_back(stateset);
|
|
}
|
|
|
|
|
|
return pushStateState;
|
|
}
|
|
|
|
void Optimizer::TextureAtlasVisitor::popStateSet()
|
|
{
|
|
_statesetStack.pop_back();
|
|
}
|
|
|
|
void Optimizer::TextureAtlasVisitor::apply(osg::Node& node)
|
|
{
|
|
bool pushedStateState = false;
|
|
|
|
osg::StateSet* ss = node.getStateSet();
|
|
if (ss && ss->getDataVariance()==osg::Object::STATIC)
|
|
{
|
|
if (isOperationPermissibleForObject(&node) &&
|
|
isOperationPermissibleForObject(ss))
|
|
{
|
|
pushedStateState = pushStateSet(ss);
|
|
}
|
|
}
|
|
|
|
traverse(node);
|
|
|
|
if (pushedStateState) popStateSet();
|
|
}
|
|
|
|
void Optimizer::TextureAtlasVisitor::apply(osg::Geode& geode)
|
|
{
|
|
if (!isOperationPermissibleForObject(&geode)) return;
|
|
|
|
osg::StateSet* ss = geode.getStateSet();
|
|
|
|
|
|
bool pushedGeodeStateState = false;
|
|
|
|
if (ss && ss->getDataVariance()==osg::Object::STATIC)
|
|
{
|
|
if (isOperationPermissibleForObject(ss))
|
|
{
|
|
pushedGeodeStateState = pushStateSet(ss);
|
|
}
|
|
}
|
|
for(unsigned int i=0;i<geode.getNumDrawables();++i)
|
|
{
|
|
|
|
osg::Drawable* drawable = geode.getDrawable(i);
|
|
if (drawable)
|
|
{
|
|
bool pushedDrawableStateState = false;
|
|
|
|
ss = drawable->getStateSet();
|
|
if (ss && ss->getDataVariance()==osg::Object::STATIC)
|
|
{
|
|
if (isOperationPermissibleForObject(drawable) &&
|
|
isOperationPermissibleForObject(ss))
|
|
{
|
|
pushedDrawableStateState = pushStateSet(ss);
|
|
}
|
|
}
|
|
|
|
if (!_statesetStack.empty())
|
|
{
|
|
for(StateSetStack::iterator ssitr = _statesetStack.begin();
|
|
ssitr != _statesetStack.end();
|
|
++ssitr)
|
|
{
|
|
_statesetMap[*ssitr].insert(drawable);
|
|
}
|
|
}
|
|
|
|
if (pushedDrawableStateState) popStateSet();
|
|
}
|
|
|
|
}
|
|
|
|
if (pushedGeodeStateState) popStateSet();
|
|
}
|
|
|
|
void Optimizer::TextureAtlasVisitor::optimize()
|
|
{
|
|
_builder.reset();
|
|
|
|
if (_textures.size()<2)
|
|
{
|
|
// nothing to optimize
|
|
return;
|
|
}
|
|
|
|
Textures texturesThatRepeat;
|
|
Textures texturesThatRepeatAndAreOutOfRange;
|
|
|
|
StateSetMap::iterator sitr;
|
|
for(sitr = _statesetMap.begin();
|
|
sitr != _statesetMap.end();
|
|
++sitr)
|
|
{
|
|
osg::StateSet* stateset = sitr->first;
|
|
Drawables& drawables = sitr->second;
|
|
|
|
osg::StateSet::TextureAttributeList& tal = stateset->getTextureAttributeList();
|
|
for(unsigned int unit=0; unit<tal.size(); ++unit)
|
|
{
|
|
osg::Texture2D* texture = dynamic_cast<osg::Texture2D*>(stateset->getTextureAttribute(unit,osg::StateAttribute::TEXTURE));
|
|
if (texture)
|
|
{
|
|
bool s_repeat = texture->getWrap(osg::Texture2D::WRAP_S)==osg::Texture2D::REPEAT ||
|
|
texture->getWrap(osg::Texture2D::WRAP_S)==osg::Texture2D::MIRROR;
|
|
|
|
bool t_repeat = texture->getWrap(osg::Texture2D::WRAP_T)==osg::Texture2D::REPEAT ||
|
|
texture->getWrap(osg::Texture2D::WRAP_T)==osg::Texture2D::MIRROR;
|
|
|
|
if (s_repeat || t_repeat)
|
|
{
|
|
texturesThatRepeat.insert(texture);
|
|
|
|
bool s_outOfRange = false;
|
|
bool t_outOfRange = false;
|
|
|
|
float s_min = -0.001;
|
|
float s_max = 1.001;
|
|
|
|
float t_min = -0.001;
|
|
float t_max = 1.001;
|
|
|
|
for(Drawables::iterator ditr = drawables.begin();
|
|
ditr != drawables.end();
|
|
++ditr)
|
|
{
|
|
osg::Geometry* geom = (*ditr)->asGeometry();
|
|
osg::Vec2Array* texcoords = geom ? dynamic_cast<osg::Vec2Array*>(geom->getTexCoordArray(unit)) : 0;
|
|
if (texcoords && !texcoords->empty())
|
|
{
|
|
for(osg::Vec2Array::iterator titr = texcoords->begin();
|
|
titr != texcoords->end() /*&& !s_outOfRange && !t_outOfRange*/;
|
|
++titr)
|
|
{
|
|
osg::Vec2 tc = *titr;
|
|
if (tc[0]<s_min) { s_min = tc[0]; s_outOfRange = true; }
|
|
if (tc[0]>s_max) { s_max = tc[0]; s_outOfRange = true; }
|
|
|
|
if (tc[1]<t_min) { t_min = tc[1]; t_outOfRange = true; }
|
|
if (tc[1]>t_max) { t_max = tc[1]; t_outOfRange = true; }
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// if no texcoords then texgen must be being used, therefore must assume that texture is truely repeating
|
|
s_outOfRange = true;
|
|
t_outOfRange = true;
|
|
}
|
|
}
|
|
|
|
if (s_outOfRange || t_outOfRange)
|
|
{
|
|
texturesThatRepeatAndAreOutOfRange.insert(texture);
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// now change any texture that repeat but all texcoords to them
|
|
// are in 0 to 1 range than converting the to CLAMP mode, to allow them
|
|
// to be used in an atlas.
|
|
Textures::iterator titr;
|
|
for(titr = texturesThatRepeat.begin();
|
|
titr != texturesThatRepeat.end();
|
|
++titr)
|
|
{
|
|
osg::Texture2D* texture = *titr;
|
|
if (texturesThatRepeatAndAreOutOfRange.count(texture)==0)
|
|
{
|
|
// safe to convert into CLAMP wrap mode.
|
|
osg::notify(osg::INFO)<<"Changing wrap mode to CLAMP"<<std::endl;
|
|
texture->setWrap(osg::Texture2D::WRAP_S, osg::Texture::CLAMP);
|
|
texture->setWrap(osg::Texture2D::WRAP_T, osg::Texture::CLAMP);
|
|
}
|
|
}
|
|
|
|
// add the textures as sources for the TextureAtlasBuilder
|
|
for(titr = _textures.begin();
|
|
titr != _textures.end();
|
|
++titr)
|
|
{
|
|
osg::Texture2D* texture = *titr;
|
|
|
|
bool s_repeat = texture->getWrap(osg::Texture2D::WRAP_S)==osg::Texture2D::REPEAT ||
|
|
texture->getWrap(osg::Texture2D::WRAP_S)==osg::Texture2D::MIRROR;
|
|
|
|
bool t_repeat = texture->getWrap(osg::Texture2D::WRAP_T)==osg::Texture2D::REPEAT ||
|
|
texture->getWrap(osg::Texture2D::WRAP_T)==osg::Texture2D::MIRROR;
|
|
|
|
if (!s_repeat && !t_repeat)
|
|
{
|
|
_builder.addSource(*titr);
|
|
}
|
|
}
|
|
|
|
// build the atlas'
|
|
_builder.buildAtlas();
|
|
|
|
|
|
typedef std::set<osg::StateSet*> StateSetSet;
|
|
typedef std::map<osg::Drawable*, StateSetSet> DrawableStateSetMap;
|
|
DrawableStateSetMap dssm;
|
|
for(sitr = _statesetMap.begin();
|
|
sitr != _statesetMap.end();
|
|
++sitr)
|
|
{
|
|
Drawables& drawables = sitr->second;
|
|
for(Drawables::iterator ditr = drawables.begin();
|
|
ditr != drawables.end();
|
|
++ditr)
|
|
{
|
|
dssm[(*ditr)->asGeometry()].insert(sitr->first);
|
|
}
|
|
}
|
|
|
|
Drawables drawablesThatHaveMultipleTexturesOnOneUnit;
|
|
for(DrawableStateSetMap::iterator ditr = dssm.begin();
|
|
ditr != dssm.end();
|
|
++ditr)
|
|
{
|
|
osg::Drawable* drawable = ditr->first;
|
|
StateSetSet& ssm = ditr->second;
|
|
if (ssm.size()>1)
|
|
{
|
|
typedef std::map<unsigned int, Textures> UnitTextureMap;
|
|
UnitTextureMap unitTextureMap;
|
|
for(StateSetSet::iterator ssm_itr = ssm.begin();
|
|
ssm_itr != ssm.end();
|
|
++ssm_itr)
|
|
{
|
|
osg::StateSet* ss = *ssm_itr;
|
|
unsigned int numTextureUnits = ss->getTextureAttributeList().size();
|
|
for(unsigned int unit=0; unit<numTextureUnits; ++unit)
|
|
{
|
|
osg::Texture2D* texture = dynamic_cast<osg::Texture2D*>(ss->getTextureAttribute(unit, osg::StateAttribute::TEXTURE));
|
|
if (texture) unitTextureMap[unit].insert(texture);
|
|
}
|
|
}
|
|
bool drawablesHasMultiTextureOnOneUnit = false;
|
|
for(UnitTextureMap::iterator utm_itr = unitTextureMap.begin();
|
|
utm_itr != unitTextureMap.end() && !drawablesHasMultiTextureOnOneUnit;
|
|
++utm_itr)
|
|
{
|
|
if (utm_itr->second.size()>1)
|
|
{
|
|
drawablesHasMultiTextureOnOneUnit = true;
|
|
}
|
|
}
|
|
if (drawablesHasMultiTextureOnOneUnit)
|
|
{
|
|
drawablesThatHaveMultipleTexturesOnOneUnit.insert(drawable);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// remap the textures in the StateSet's
|
|
for(sitr = _statesetMap.begin();
|
|
sitr != _statesetMap.end();
|
|
++sitr)
|
|
{
|
|
osg::StateSet* stateset = sitr->first;
|
|
osg::StateSet::TextureAttributeList& tal = stateset->getTextureAttributeList();
|
|
for(unsigned int unit=0; unit<tal.size(); ++unit)
|
|
{
|
|
osg::Texture2D* texture = dynamic_cast<osg::Texture2D*>(stateset->getTextureAttribute(unit,osg::StateAttribute::TEXTURE));
|
|
if (texture)
|
|
{
|
|
bool s_repeat = texture->getWrap(osg::Texture2D::WRAP_S)==osg::Texture2D::REPEAT ||
|
|
texture->getWrap(osg::Texture2D::WRAP_S)==osg::Texture2D::MIRROR;
|
|
|
|
bool t_repeat = texture->getWrap(osg::Texture2D::WRAP_T)==osg::Texture2D::REPEAT ||
|
|
texture->getWrap(osg::Texture2D::WRAP_T)==osg::Texture2D::MIRROR;
|
|
|
|
osg::Texture2D* newTexture = _builder.getTextureAtlas(texture);
|
|
if (newTexture && newTexture!=texture)
|
|
{
|
|
if (s_repeat || t_repeat)
|
|
{
|
|
osg::notify(osg::NOTICE)<<"Warning!!! shouldn't get here"<<std::endl;
|
|
}
|
|
|
|
stateset->setTextureAttribute(unit, newTexture);
|
|
|
|
Drawables& drawables = sitr->second;
|
|
|
|
osg::Matrix matrix = _builder.getTextureMatrix(texture);
|
|
|
|
// first check to see if all drawables are ok for applying texturematrix to.
|
|
bool canTexMatBeFlattenedToAllDrawables = true;
|
|
for(Drawables::iterator ditr = drawables.begin();
|
|
ditr != drawables.end() && canTexMatBeFlattenedToAllDrawables;
|
|
++ditr)
|
|
{
|
|
osg::Geometry* geom = (*ditr)->asGeometry();
|
|
osg::Vec2Array* texcoords = geom ? dynamic_cast<osg::Vec2Array*>(geom->getTexCoordArray(unit)) : 0;
|
|
|
|
if (!texcoords)
|
|
{
|
|
canTexMatBeFlattenedToAllDrawables = false;
|
|
}
|
|
|
|
if (drawablesThatHaveMultipleTexturesOnOneUnit.count(*ditr)!=0)
|
|
{
|
|
canTexMatBeFlattenedToAllDrawables = false;
|
|
}
|
|
}
|
|
|
|
if (canTexMatBeFlattenedToAllDrawables)
|
|
{
|
|
// osg::notify(osg::NOTICE)<<"All drawables can be flattened "<<drawables.size()<<std::endl;
|
|
for(Drawables::iterator ditr = drawables.begin();
|
|
ditr != drawables.end();
|
|
++ditr)
|
|
{
|
|
osg::Geometry* geom = (*ditr)->asGeometry();
|
|
osg::Vec2Array* texcoords = geom ? dynamic_cast<osg::Vec2Array*>(geom->getTexCoordArray(unit)) : 0;
|
|
if (texcoords)
|
|
{
|
|
for(osg::Vec2Array::iterator titr = texcoords->begin();
|
|
titr != texcoords->end();
|
|
++titr)
|
|
{
|
|
osg::Vec2 tc = *titr;
|
|
(*titr).set(tc[0]*matrix(0,0) + tc[1]*matrix(1,0) + matrix(3,0),
|
|
tc[0]*matrix(0,1) + tc[1]*matrix(1,1) + matrix(3,1));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
osg::notify(osg::NOTICE)<<"Error, Optimizer::TextureAtlasVisitor::optimize() shouldn't ever get here..."<<std::endl;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// osg::notify(osg::NOTICE)<<"Applying TexMat "<<drawables.size()<<std::endl;
|
|
stateset->setTextureAttribute(unit, new osg::TexMat(matrix));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|