even if they have had DataVariance DYNAMIC explicitly specified. Then when an application attempts to dynamically update the geometry in the frame loop the primitive sets and data arrays are no longer as expected, leading to display and/or memory corruption. Attached is a simple fix. " Note from Robert Osfield, tweaked Colin's changes to that it use != DYNAMIC as the the test rather than == STATIC.
4582 lines
157 KiB
C++
4582 lines
157 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 <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <osgUtil/Optimizer>
|
|
|
|
#include <osg/ApplicationUsage>
|
|
#include <osg/Transform>
|
|
#include <osg/MatrixTransform>
|
|
#include <osg/PositionAttitudeTransform>
|
|
#include <osg/LOD>
|
|
#include <osg/Billboard>
|
|
#include <osg/CameraView>
|
|
#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/ImageStream>
|
|
#include <osg/Timer>
|
|
#include <osg/TexMat>
|
|
#include <osg/io_utils>
|
|
|
|
#include <osgUtil/TransformAttributeFunctor>
|
|
#include <osgUtil/TriStripVisitor>
|
|
#include <osgUtil/Tessellator>
|
|
#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 | FLATTEN_STATIC_TRANSFORMS_DUPLICATING_SHARED_SUBGRAPHS | 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 | TESSELLATE_GEOMETRY | CHECK_GEOMETRY | FLATTEN_BILLBOARDS | TEXTURE_ATLAS_BUILDER | STATIC_OBJECT_DETECTION");
|
|
|
|
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("~FLATTEN_STATIC_TRANSFORMS_DUPLICATING_SHARED_SUBGRAPHS")!=std::string::npos) options ^= FLATTEN_STATIC_TRANSFORMS_DUPLICATING_SHARED_SUBGRAPHS;
|
|
else if(str.find("FLATTEN_STATIC_TRANSFORMS_DUPLICATING_SHARED_SUBGRAPHS")!=std::string::npos) options |= FLATTEN_STATIC_TRANSFORMS_DUPLICATING_SHARED_SUBGRAPHS;
|
|
|
|
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("~TESSELLATE_GEOMETRY")!=std::string::npos) options ^= TESSELLATE_GEOMETRY;
|
|
else if(str.find("TESSELLATE_GEOMETRY")!=std::string::npos) options |= TESSELLATE_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("~MAKE_FAST_GEOMETRY")!=std::string::npos) options ^= MAKE_FAST_GEOMETRY;
|
|
else if(str.find("MAKE_FAST_GEOMETRY")!=std::string::npos) options |= MAKE_FAST_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;
|
|
|
|
if(str.find("~STATIC_OBJECT_DETECTION")!=std::string::npos) options ^= STATIC_OBJECT_DETECTION;
|
|
else if(str.find("STATIC_OBJECT_DETECTION")!=std::string::npos) options |= STATIC_OBJECT_DETECTION;
|
|
|
|
}
|
|
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 & STATIC_OBJECT_DETECTION)
|
|
{
|
|
StaticObjectDetectionVisitor sodv;
|
|
node->accept(sodv);
|
|
}
|
|
|
|
if (options & TESSELLATE_GEOMETRY)
|
|
{
|
|
osg::notify(osg::INFO)<<"Optimizer::optimize() doing TESSELLATE_GEOMETRY"<<std::endl;
|
|
|
|
TessellateVisitor 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;
|
|
|
|
bool combineDynamicState = false;
|
|
bool combineStaticState = true;
|
|
bool combineUnspecifiedState = true;
|
|
|
|
StateVisitor osv(combineDynamicState, combineStaticState, combineUnspecifiedState, 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'
|
|
bool combineDynamicState = false;
|
|
bool combineStaticState = true;
|
|
bool combineUnspecifiedState = true;
|
|
|
|
StateVisitor osv(combineDynamicState, combineStaticState, combineUnspecifiedState, 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 & FLATTEN_STATIC_TRANSFORMS_DUPLICATING_SHARED_SUBGRAPHS)
|
|
{
|
|
osg::notify(osg::INFO)<<"Optimizer::optimize() doing FLATTEN_STATIC_TRANSFORMS_DUPLICATING_SHARED_SUBGRAPHS"<<std::endl;
|
|
|
|
// no combine any adjacent static transforms.
|
|
FlattenStaticTransformsDuplicatingSharedSubgraphsVisitor fstdssv(this);
|
|
node->accept(fstdssv);
|
|
|
|
}
|
|
|
|
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 & MAKE_FAST_GEOMETRY)
|
|
{
|
|
osg::notify(osg::INFO)<<"Optimizer::optimize() doing MAKE_FAST_GEOMETRY"<<std::endl;
|
|
|
|
MakeFastGeometryVisitor 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));
|
|
}
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// Tessellate geometry - eg break complex POLYGONS into triangles, strips, fans..
|
|
////////////////////////////////////////////////////////////////////////////
|
|
void Optimizer::TessellateVisitor::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::Tessellator Tessellator;
|
|
Tessellator.retessellatePolygons(*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 (optimize(aitr->second.first->getDataVariance()))
|
|
{
|
|
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 (optimize(aitr->second.first->getDataVariance()))
|
|
{
|
|
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 (optimize(uitr->second.first->getDataVariance()))
|
|
{
|
|
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;
|
|
if (dynamic_cast<const osg::PagedLOD*>(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::STATIC) _moreThanOneMatrixRequired=true;
|
|
else if (transform->getReferenceFrame()!=osg::Transform::RELATIVE_RF) _moreThanOneMatrixRequired=true;
|
|
else
|
|
{
|
|
if (_transformSet.empty()) transform->computeLocalToWorldMatrix(_firstMatrix,0);
|
|
else
|
|
{
|
|
osg::Matrix matrix;
|
|
transform->computeLocalToWorldMatrix(matrix,0);
|
|
if (_firstMatrix!=matrix) _moreThanOneMatrixRequired=true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!_transformSet.empty())
|
|
{
|
|
if (!_firstMatrix.isIdentity()) _moreThanOneMatrixRequired=true;
|
|
}
|
|
|
|
}
|
|
_transformSet.insert(transform);
|
|
}
|
|
|
|
bool _canBeApplied;
|
|
bool _moreThanOneMatrixRequired;
|
|
osg::Matrix _firstMatrix;
|
|
TransformSet _transformSet;
|
|
};
|
|
|
|
|
|
void registerWithCurrentObjects(osg::Transform* transform)
|
|
{
|
|
for(ObjectList::iterator itr=_currentObjectList.begin();
|
|
itr!=_currentObjectList.end();
|
|
++itr)
|
|
{
|
|
_objectMap[*itr].add(transform);
|
|
}
|
|
}
|
|
|
|
typedef std::map<osg::Transform*,TransformStruct> TransformMap;
|
|
typedef std::map<osg::Object*,ObjectStruct> ObjectMap;
|
|
typedef std::vector<osg::Object*> ObjectList;
|
|
|
|
void disableObject(osg::Object* object)
|
|
{
|
|
disableObject(_objectMap.find(object));
|
|
}
|
|
|
|
void disableObject(ObjectMap::iterator itr);
|
|
void doTransform(osg::Object* obj,osg::Matrix& matrix);
|
|
|
|
osgUtil::TransformAttributeFunctor _transformFunctor;
|
|
TransformMap _transformMap;
|
|
ObjectMap _objectMap;
|
|
ObjectList _currentObjectList;
|
|
|
|
};
|
|
|
|
|
|
void CollectLowestTransformsVisitor::doTransform(osg::Object* obj,osg::Matrix& matrix)
|
|
{
|
|
osg::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());
|
|
group->setStateSet(transform->getStateSet());
|
|
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::PagedLOD& 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);
|
|
if (transform->getStateSet())
|
|
{
|
|
if(child->getStateSet()) child->getStateSet()->merge(*transform->getStateSet());
|
|
else child->setStateSet(transform->getStateSet());
|
|
}
|
|
|
|
transformRemoved = true;
|
|
|
|
osg::Node::ParentList parents = transform->getParents();
|
|
for(osg::Node::ParentList::iterator pitr=parents.begin();
|
|
pitr!=parents.end();
|
|
++pitr)
|
|
{
|
|
(*pitr)->replaceChild(transform.get(),child);
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
return transformRemoved;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// RemoveEmptyNodes.
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
void Optimizer::RemoveEmptyNodesVisitor::apply(osg::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) && !dynamic_cast<osg::CameraView*>(&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.
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool Optimizer::RemoveRedundantNodesVisitor::isOperationPermissible(osg::Node& node)
|
|
{
|
|
return node.getNumParents()>0 &&
|
|
!node.getStateSet() &&
|
|
!node.getUserData() &&
|
|
!node.getUpdateCallback() &&
|
|
!node.getEventCallback() &&
|
|
!node.getUpdateCallback() &&
|
|
node.getDescriptions().empty() &&
|
|
isOperationPermissibleForObject(&node);
|
|
}
|
|
|
|
void Optimizer::RemoveRedundantNodesVisitor::apply(osg::Group& group)
|
|
{
|
|
if (group.getNumChildren()==1 &&
|
|
typeid(group)==typeid(osg::Group) &&
|
|
isOperationPermissible(group))
|
|
{
|
|
_redundantNodeList.insert(&group);
|
|
}
|
|
|
|
traverse(group);
|
|
}
|
|
|
|
|
|
|
|
void Optimizer::RemoveRedundantNodesVisitor::apply(osg::Transform& transform)
|
|
{
|
|
if (transform.getDataVariance()==osg::Object::STATIC &&
|
|
isOperationPermissible(transform))
|
|
{
|
|
osg::Matrix matrix;
|
|
transform.computeWorldToLocalMatrix(matrix,NULL);
|
|
if (matrix.isIdentity())
|
|
{
|
|
_redundantNodeList.insert(&transform);
|
|
}
|
|
}
|
|
traverse(transform);
|
|
}
|
|
|
|
|
|
void Optimizer::RemoveRedundantNodesVisitor::removeRedundantNodes()
|
|
{
|
|
|
|
for(NodeList::iterator itr=_redundantNodeList.begin();
|
|
itr!=_redundantNodeList.end();
|
|
++itr)
|
|
{
|
|
osg::ref_ptr<osg::Group> group = 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(osg::Node::ParentList::iterator pitr=parents.begin();
|
|
pitr!=parents.end();
|
|
++pitr)
|
|
{
|
|
(*pitr)->removeChild(group.get());
|
|
for(unsigned int i=0;i<group->getNumChildren();++i)
|
|
{
|
|
osg::Node* child = group->getChild(i);
|
|
(*pitr)->addChild(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();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Optimizer::MakeFastGeometryVisitor::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))
|
|
{
|
|
if (!geom->areFastPathsUsed() && !geom->getInternalOptimizedGeometry())
|
|
{
|
|
geom->computeInternalOptimizedGeometry();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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) &&
|
|
geom->getDataVariance()!=osg::Object::DYNAMIC &&
|
|
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 primitiveSet to empty it completely.
|
|
osg::Geometry::PrimitiveSetList oldPrimitives;
|
|
primitives.swap(oldPrimitives);
|
|
|
|
// now add the active primitive sets
|
|
for(osg::Geometry::PrimitiveSetList::iterator pitr = oldPrimitives.begin();
|
|
pitr != oldPrimitives.end();
|
|
++pitr)
|
|
{
|
|
if ((*pitr)->getMode()!=0xffffff) primitives.push_back(*pitr);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#else
|
|
|
|
osg::Geometry::PrimitiveSetList& primitives = geom->getPrimitiveSetList();
|
|
unsigned int primNo=0;
|
|
while(primNo+1<primitives.size())
|
|
{
|
|
osg::PrimitiveSet* lhs = primitives[primNo].get();
|
|
osg::PrimitiveSet* rhs = primitives[primNo+1].get();
|
|
|
|
bool combine = false;
|
|
|
|
if (lhs->getType()==rhs->getType() &&
|
|
lhs->getMode()==rhs->getMode())
|
|
{
|
|
|
|
switch(lhs->getMode())
|
|
{
|
|
case(osg::PrimitiveSet::POINTS):
|
|
case(osg::PrimitiveSet::LINES):
|
|
case(osg::PrimitiveSet::TRIANGLES):
|
|
case(osg::PrimitiveSet::QUADS):
|
|
combine = true;
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
if (combine)
|
|
{
|
|
|
|
switch(lhs->getType())
|
|
{
|
|
case(osg::PrimitiveSet::DrawArraysPrimitiveType):
|
|
combine = mergePrimitive(*(static_cast<osg::DrawArrays*>(lhs)),*(static_cast<osg::DrawArrays*>(rhs)));
|
|
break;
|
|
case(osg::PrimitiveSet::DrawArrayLengthsPrimitiveType):
|
|
combine = mergePrimitive(*(static_cast<osg::DrawArrayLengths*>(lhs)),*(static_cast<osg::DrawArrayLengths*>(rhs)));
|
|
break;
|
|
case(osg::PrimitiveSet::DrawElementsUBytePrimitiveType):
|
|
combine = mergePrimitive(*(static_cast<osg::DrawElementsUByte*>(lhs)),*(static_cast<osg::DrawElementsUByte*>(rhs)));
|
|
break;
|
|
case(osg::PrimitiveSet::DrawElementsUShortPrimitiveType):
|
|
combine = mergePrimitive(*(static_cast<osg::DrawElementsUShort*>(lhs)),*(static_cast<osg::DrawElementsUShort*>(rhs)));
|
|
break;
|
|
case(osg::PrimitiveSet::DrawElementsUIntPrimitiveType):
|
|
combine = mergePrimitive(*(static_cast<osg::DrawElementsUInt*>(lhs)),*(static_cast<osg::DrawElementsUInt*>(rhs)));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
if (combine)
|
|
{
|
|
primitives.erase(primitives.begin()+primNo+1);
|
|
}
|
|
|
|
if (!combine)
|
|
{
|
|
primNo++;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
// geode.dirtyBound();
|
|
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Optimizer::MergeGeometryVisitor::geometryContainsSharedArrays(osg::Geometry& geom)
|
|
{
|
|
if (geom.getVertexArray() && geom.getVertexArray()->referenceCount()>1) return true;
|
|
if (geom.getNormalArray() && geom.getNormalArray()->referenceCount()>1) return true;
|
|
if (geom.getColorArray() && geom.getColorArray()->referenceCount()>1) return true;
|
|
if (geom.getSecondaryColorArray() && geom.getSecondaryColorArray()->referenceCount()>1) return true;
|
|
if (geom.getFogCoordArray() && geom.getFogCoordArray()->referenceCount()>1) return true;
|
|
|
|
|
|
for(unsigned int unit=0;unit<geom.getNumTexCoordArrays();++unit)
|
|
{
|
|
osg::Array* tex = geom.getTexCoordArray(unit);
|
|
if (tex && tex->referenceCount()>1) return true;
|
|
}
|
|
|
|
// shift the indices of the incoming primitives to account for the pre existing geometry.
|
|
for(osg::Geometry::PrimitiveSetList::iterator primItr=geom.getPrimitiveSetList().begin();
|
|
primItr!=geom.getPrimitiveSetList().end();
|
|
++primItr)
|
|
{
|
|
if ((*primItr)->referenceCount()>1) return true;
|
|
}
|
|
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
class MergeArrayVisitor : public osg::ArrayVisitor
|
|
{
|
|
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::DoubleArray& rhs) { _merge(rhs); }
|
|
virtual void apply(osg::Vec2dArray& rhs) { _merge(rhs); }
|
|
virtual void apply(osg::Vec3dArray& rhs) { _merge(rhs); }
|
|
virtual void apply(osg::Vec4dArray& rhs) { _merge(rhs); }
|
|
|
|
virtual void apply(osg::Vec2bArray& rhs) { _merge(rhs); }
|
|
virtual void apply(osg::Vec3bArray& rhs) { _merge(rhs); }
|
|
virtual void apply(osg::Vec4bArray& rhs) { _merge(rhs); }
|
|
virtual void apply(osg::Vec2sArray& rhs) { _merge(rhs); }
|
|
virtual void apply(osg::Vec3sArray& rhs) { _merge(rhs); }
|
|
virtual void apply(osg::Vec4sArray& rhs) { _merge(rhs); }
|
|
};
|
|
|
|
bool Optimizer::MergeGeometryVisitor::mergeGeometry(osg::Geometry& lhs,osg::Geometry& rhs)
|
|
{
|
|
|
|
MergeArrayVisitor merger;
|
|
|
|
unsigned int base = 0;
|
|
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 incoming primitives to account for the pre existing geometry.
|
|
osg::Geometry::PrimitiveSetList::iterator primItr;
|
|
for(primItr=rhs.getPrimitiveSetList().begin(); primItr!=rhs.getPrimitiveSetList().end(); ++primItr)
|
|
{
|
|
osg::PrimitiveSet* primitive = primItr->get();
|
|
|
|
switch(primitive->getType())
|
|
{
|
|
case(osg::PrimitiveSet::DrawElementsUBytePrimitiveType):
|
|
{
|
|
osg::DrawElementsUByte* primitiveUByte = static_cast<osg::DrawElementsUByte*>(primitive);
|
|
unsigned int currentMaximum = 0;
|
|
for(osg::DrawElementsUByte::iterator eitr=primitiveUByte->begin();
|
|
eitr!=primitiveUByte->end();
|
|
++eitr)
|
|
{
|
|
currentMaximum = osg::maximum(currentMaximum,(unsigned int)*eitr);
|
|
}
|
|
if ((base+currentMaximum)>=65536)
|
|
{
|
|
// must promote to a DrawElementsUInt
|
|
osg::DrawElementsUInt* new_primitive = new osg::DrawElementsUInt(primitive->getMode());
|
|
std::copy(primitiveUByte->begin(),primitiveUByte->end(),std::back_inserter(*new_primitive));
|
|
new_primitive->offsetIndices(base);
|
|
(*primItr) = new_primitive;
|
|
} else if ((base+currentMaximum)>=256)
|
|
{
|
|
// must promote to a DrawElementsUShort
|
|
osg::DrawElementsUShort* new_primitive = new osg::DrawElementsUShort(primitive->getMode());
|
|
std::copy(primitiveUByte->begin(),primitiveUByte->end(),std::back_inserter(*new_primitive));
|
|
new_primitive->offsetIndices(base);
|
|
(*primItr) = new_primitive;
|
|
}
|
|
else
|
|
{
|
|
primitive->offsetIndices(base);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case(osg::PrimitiveSet::DrawElementsUShortPrimitiveType):
|
|
{
|
|
osg::DrawElementsUShort* primitiveUShort = static_cast<osg::DrawElementsUShort*>(primitive);
|
|
unsigned int currentMaximum = 0;
|
|
for(osg::DrawElementsUShort::iterator eitr=primitiveUShort->begin();
|
|
eitr!=primitiveUShort->end();
|
|
++eitr)
|
|
{
|
|
currentMaximum = osg::maximum(currentMaximum,(unsigned int)*eitr);
|
|
}
|
|
if ((base+currentMaximum)>=65536)
|
|
{
|
|
// must promote to a DrawElementsUInt
|
|
osg::DrawElementsUInt* new_primitive = new osg::DrawElementsUInt(primitive->getMode());
|
|
std::copy(primitiveUShort->begin(),primitiveUShort->end(),std::back_inserter(*new_primitive));
|
|
new_primitive->offsetIndices(base);
|
|
(*primItr) = new_primitive;
|
|
}
|
|
else
|
|
{
|
|
primitive->offsetIndices(base);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case(osg::PrimitiveSet::DrawArraysPrimitiveType):
|
|
case(osg::PrimitiveSet::DrawArrayLengthsPrimitiveType):
|
|
case(osg::PrimitiveSet::DrawElementsUIntPrimitiveType):
|
|
default:
|
|
primitive->offsetIndices(base);
|
|
break;
|
|
}
|
|
}
|
|
|
|
for(primItr=rhs.getPrimitiveSetList().begin(); primItr!=rhs.getPrimitiveSetList().end(); ++primItr)
|
|
{
|
|
lhs.addPrimitiveSet(primItr->get());
|
|
}
|
|
|
|
lhs.dirtyBound();
|
|
lhs.dirtyDisplayList();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Optimizer::MergeGeometryVisitor::mergePrimitive(osg::DrawArrays& lhs,osg::DrawArrays& rhs)
|
|
{
|
|
if (lhs.getFirst()+lhs.getCount()==rhs.getFirst())
|
|
{
|
|
lhs.setCount(lhs.getCount()+rhs.getCount());
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Optimizer::MergeGeometryVisitor::mergePrimitive(osg::DrawArrayLengths& lhs,osg::DrawArrayLengths& rhs)
|
|
{
|
|
int lhs_count = std::accumulate(lhs.begin(),lhs.end(),0);
|
|
|
|
if (lhs.getFirst()+lhs_count==rhs.getFirst())
|
|
{
|
|
lhs.insert(lhs.end(),rhs.begin(),rhs.end());
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Optimizer::MergeGeometryVisitor::mergePrimitive(osg::DrawElementsUByte& lhs,osg::DrawElementsUByte& rhs)
|
|
{
|
|
lhs.insert(lhs.end(),rhs.begin(),rhs.end());
|
|
return true;
|
|
}
|
|
|
|
bool Optimizer::MergeGeometryVisitor::mergePrimitive(osg::DrawElementsUShort& lhs,osg::DrawElementsUShort& rhs)
|
|
{
|
|
lhs.insert(lhs.end(),rhs.begin(),rhs.end());
|
|
return true;
|
|
}
|
|
|
|
bool Optimizer::MergeGeometryVisitor::mergePrimitive(osg::DrawElementsUInt& lhs,osg::DrawElementsUInt& rhs)
|
|
{
|
|
lhs.insert(lhs.end(),rhs.begin(),rhs.end());
|
|
return true;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// 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);
|
|
}
|
|
|
|
void Optimizer::SpatializeGroupsVisitor::apply(osg::Geode& geode)
|
|
{
|
|
if (typeid(geode)==typeid(osg::Geode))
|
|
{
|
|
if (isOperationPermissibleForObject(&geode))
|
|
{
|
|
_geodesToDivideList.insert(&geode);
|
|
}
|
|
}
|
|
traverse(geode);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
for(GeodesToDivideList::iterator geode_itr=_geodesToDivideList.begin();
|
|
geode_itr!=_geodesToDivideList.end();
|
|
++geode_itr)
|
|
{
|
|
if (divide(*geode_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());
|
|
|
|
}
|
|
|
|
bool Optimizer::SpatializeGroupsVisitor::divide(osg::Geode* geode, unsigned int maxNumTreesPerCell)
|
|
{
|
|
|
|
if (geode->getNumDrawables()<=maxNumTreesPerCell) return false;
|
|
|
|
// create the original box.
|
|
osg::BoundingBox bb;
|
|
unsigned int i;
|
|
for(i=0; i<geode->getNumDrawables(); ++i)
|
|
{
|
|
bb.expandBy(geode->getDrawable(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)<<"INFO "<<geode->className()<<" num drawables = "<<geode->getNumDrawables()<<" 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;
|
|
}
|
|
|
|
osg::Node::ParentList parents = geode->getParents();
|
|
if (parents.empty())
|
|
{
|
|
osg::notify(osg::INFO)<<" Cannot perform spatialize on root Geode, add a Group above it to allow subdivision."<<std::endl;
|
|
return false;
|
|
}
|
|
|
|
osg::ref_ptr<osg::Group> group = new osg::Group;
|
|
group->setName(geode->getName());
|
|
group->setStateSet(geode->getStateSet());
|
|
for(i=0; i<geode->getNumDrawables(); ++i)
|
|
{
|
|
osg::Geode* newGeode = new osg::Geode;
|
|
newGeode->addDrawable(geode->getDrawable(i));
|
|
group->addChild(newGeode);
|
|
}
|
|
|
|
divide(group.get(), maxNumTreesPerCell);
|
|
|
|
// keep reference around to prevent it being deleted.
|
|
osg::ref_ptr<osg::Geode> keepRefGeode = geode;
|
|
|
|
for(osg::Node::ParentList::iterator itr = parents.begin();
|
|
itr != parents.end();
|
|
++itr)
|
|
{
|
|
(*itr)->replaceChild(geode, group.get());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// 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)
|
|
{
|
|
unsigned numImageStreams = 0;
|
|
for (unsigned int i=0; i<texture.getNumImages(); ++i)
|
|
{
|
|
osg::ImageStream* is = dynamic_cast<osg::ImageStream*>(texture.getImage(i));
|
|
if (is) ++numImageStreams;
|
|
}
|
|
|
|
if (numImageStreams==0)
|
|
{
|
|
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->getNodeMask()<rhs->getNodeMask()) return true;
|
|
if (lhs->getNodeMask()>rhs->getNodeMask()) return false;
|
|
|
|
return (lhs->getStateSet()<rhs->getStateSet());
|
|
}
|
|
};
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
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++);
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
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));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// StaticObjectDectionVisitor
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
void Optimizer::StaticObjectDetectionVisitor::apply(osg::Node& node)
|
|
{
|
|
if (node.getStateSet()) applyStateSet(*node.getStateSet());
|
|
|
|
traverse(node);
|
|
}
|
|
|
|
void Optimizer::StaticObjectDetectionVisitor::apply(osg::Geode& geode)
|
|
{
|
|
if (geode.getStateSet()) applyStateSet(*geode.getStateSet());
|
|
|
|
for(unsigned int i=0; i<geode.getNumDrawables(); ++i)
|
|
{
|
|
applyDrawable(*geode.getDrawable(i));
|
|
}
|
|
}
|
|
|
|
void Optimizer::StaticObjectDetectionVisitor::applyStateSet(osg::StateSet& stateset)
|
|
{
|
|
stateset.computeDataVariance();
|
|
}
|
|
|
|
|
|
void Optimizer::StaticObjectDetectionVisitor::applyDrawable(osg::Drawable& drawable)
|
|
{
|
|
|
|
if (drawable.getStateSet()) applyStateSet(*drawable.getStateSet());
|
|
|
|
drawable.computeDataVariance();
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// FlattenStaticTransformsDuplicatingSharedSubgraphsVisitor
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
void Optimizer::FlattenStaticTransformsDuplicatingSharedSubgraphsVisitor::reset()
|
|
{
|
|
_matrixStack.clear();
|
|
}
|
|
|
|
void Optimizer::FlattenStaticTransformsDuplicatingSharedSubgraphsVisitor::apply(osg::Group& group)
|
|
{
|
|
// only continue if there is a parent
|
|
const unsigned int nodepathsize = _nodePath.size();
|
|
if(!_matrixStack.empty() && group.getNumParents() > 1 && nodepathsize > 1)
|
|
{
|
|
// copy this Group
|
|
osg::ref_ptr<osg::Object> new_obj = group.clone(osg::CopyOp::DEEP_COPY_NODES | osg::CopyOp::DEEP_COPY_DRAWABLES | osg::CopyOp::DEEP_COPY_ARRAYS);
|
|
osg::Group* new_group = dynamic_cast<osg::Group*>(new_obj.get());
|
|
|
|
// New Group should only be added to parent through which this Group
|
|
// was traversed, not to all parents of this Group.
|
|
osg::Group* parent_group = dynamic_cast<osg::Group*>(_nodePath[nodepathsize-2]);
|
|
if(parent_group)
|
|
{
|
|
parent_group->replaceChild(&group, new_group);
|
|
// traverse the new Group
|
|
traverse(*(new_group));
|
|
}
|
|
else
|
|
{
|
|
osg::notify(osg::NOTICE) << "No parent for this Group" << std::endl;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// traverse original node
|
|
traverse(group);
|
|
}
|
|
}
|
|
|
|
|
|
void Optimizer::FlattenStaticTransformsDuplicatingSharedSubgraphsVisitor::apply(osg::Transform& transform)
|
|
{
|
|
bool pushed = false;
|
|
|
|
// only continue if there is a parent and this is a STATIC transform
|
|
const unsigned int nodepathsize = _nodePath.size();
|
|
if(transform.getDataVariance() == osg::Object::STATIC && nodepathsize > 1)
|
|
{
|
|
osg::Matrix matrix;
|
|
if(!_matrixStack.empty())
|
|
matrix = _matrixStack.back();
|
|
transform.computeLocalToWorldMatrix(matrix, this);
|
|
_matrixStack.push_back(matrix);
|
|
pushed = true;
|
|
|
|
// convert this Transform to a Group
|
|
osg::ref_ptr<osg::Group> group = new osg::Group(dynamic_cast<osg::Group&>(transform),
|
|
osg::CopyOp::DEEP_COPY_NODES | osg::CopyOp::DEEP_COPY_DRAWABLES | osg::CopyOp::DEEP_COPY_ARRAYS);
|
|
|
|
// New Group should only be added to parent through which this Transform
|
|
// was traversed, not to all parents of this Transform.
|
|
osg::Group* parent_group = dynamic_cast<osg::Group*>(_nodePath[nodepathsize-2]);
|
|
if(parent_group)
|
|
{
|
|
parent_group->replaceChild(&transform, group.get());
|
|
// traverse the new Group
|
|
traverse(*(group.get()));
|
|
}
|
|
else
|
|
{
|
|
osg::notify(osg::NOTICE) << "No parent for this Group" << std::endl;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// traverse original node
|
|
traverse(transform);
|
|
}
|
|
|
|
// pop matrix off of stack
|
|
if(pushed)
|
|
_matrixStack.pop_back();
|
|
}
|
|
|
|
|
|
void Optimizer::FlattenStaticTransformsDuplicatingSharedSubgraphsVisitor::apply(osg::LOD& lod)
|
|
{
|
|
const unsigned int nodepathsize = _nodePath.size();
|
|
if(!_matrixStack.empty() && lod.getNumParents() > 1 && nodepathsize > 1)
|
|
{
|
|
osg::ref_ptr<osg::LOD> new_lod = new osg::LOD(lod,
|
|
osg::CopyOp::DEEP_COPY_NODES | osg::CopyOp::DEEP_COPY_DRAWABLES | osg::CopyOp::DEEP_COPY_ARRAYS);
|
|
|
|
// New LOD should only be added to parent through which this LOD
|
|
// was traversed, not to all parents of this LOD.
|
|
osg::Group* parent_group = dynamic_cast<osg::Group*>(_nodePath[nodepathsize-2]);
|
|
if(parent_group)
|
|
{
|
|
parent_group->replaceChild(&lod, new_lod.get());
|
|
|
|
// move center point
|
|
if(!_matrixStack.empty())
|
|
new_lod->setCenter(new_lod->getCenter() * _matrixStack.back());
|
|
|
|
// traverse the new Group
|
|
traverse(*(new_lod.get()));
|
|
}
|
|
else
|
|
osg::notify(osg::NOTICE) << "No parent for this LOD" << std::endl;
|
|
}
|
|
else
|
|
{
|
|
// move center point
|
|
if(!_matrixStack.empty())
|
|
lod.setCenter(lod.getCenter() * _matrixStack.back());
|
|
|
|
traverse(lod);
|
|
}
|
|
}
|
|
|
|
|
|
void Optimizer::FlattenStaticTransformsDuplicatingSharedSubgraphsVisitor::apply(osg::Geode& geode)
|
|
{
|
|
if(!_matrixStack.empty())
|
|
{
|
|
// If there is only one parent, just transform all vertices and normals
|
|
if(geode.getNumParents() == 1)
|
|
{
|
|
transformGeode(geode);
|
|
}
|
|
else
|
|
{
|
|
// Else make a copy and then transform
|
|
const unsigned int nodepathsize = _nodePath.size();
|
|
if(nodepathsize > 1)
|
|
{
|
|
// convert this Transform to a Group
|
|
osg::ref_ptr<osg::Geode> new_geode = new osg::Geode(geode,
|
|
osg::CopyOp::DEEP_COPY_NODES | osg::CopyOp::DEEP_COPY_DRAWABLES | osg::CopyOp::DEEP_COPY_ARRAYS);
|
|
|
|
// New Group should only be added to parent through which this Transform
|
|
// was traversed, not to all parents of this Transform.
|
|
osg::Group* parent_group = dynamic_cast<osg::Group*>(_nodePath[nodepathsize-2]);
|
|
if(parent_group)
|
|
parent_group->replaceChild(&geode, new_geode.get());
|
|
else
|
|
osg::notify(osg::NOTICE) << "No parent for this Geode" << std::endl;
|
|
|
|
transformGeode(*(new_geode.get()));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void Optimizer::FlattenStaticTransformsDuplicatingSharedSubgraphsVisitor::apply(osg::Billboard& billboard)
|
|
{
|
|
if(!_matrixStack.empty())
|
|
{
|
|
// If there is only one parent, just transform this Billboard
|
|
if(billboard.getNumParents() == 1)
|
|
{
|
|
transformBillboard(billboard);
|
|
}
|
|
else
|
|
{
|
|
// Else make a copy and then transform
|
|
const unsigned int nodepathsize = _nodePath.size();
|
|
if(nodepathsize > 1)
|
|
{
|
|
// convert this Transform to a Group
|
|
osg::ref_ptr<osg::Billboard> new_billboard = new osg::Billboard(billboard,
|
|
osg::CopyOp::DEEP_COPY_NODES | osg::CopyOp::DEEP_COPY_DRAWABLES | osg::CopyOp::DEEP_COPY_ARRAYS);
|
|
|
|
// New Billboard should only be added to parent through which this Billboard
|
|
// was traversed, not to all parents of this Billboard.
|
|
osg::Group* parent_group = dynamic_cast<osg::Group*>(_nodePath[nodepathsize-2]);
|
|
if(parent_group)
|
|
parent_group->replaceChild(&billboard, new_billboard.get());
|
|
else
|
|
osg::notify(osg::NOTICE) << "No parent for this Billboard" << std::endl;
|
|
|
|
transformBillboard(*(new_billboard.get()));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void Optimizer::FlattenStaticTransformsDuplicatingSharedSubgraphsVisitor::transformGeode(osg::Geode& geode)
|
|
{
|
|
for(unsigned int i=0; i<geode.getNumDrawables(); i++)
|
|
{
|
|
transformDrawable(*geode.getDrawable(i));
|
|
}
|
|
|
|
geode.dirtyBound();
|
|
}
|
|
|
|
|
|
void Optimizer::FlattenStaticTransformsDuplicatingSharedSubgraphsVisitor::transformDrawable(osg::Drawable& drawable)
|
|
{
|
|
osg::Geometry* geometry = drawable.asGeometry();
|
|
if(geometry)
|
|
{
|
|
// transform all geometry
|
|
osg::Vec3Array* verts = dynamic_cast<osg::Vec3Array*>(geometry->getVertexArray());
|
|
if(verts)
|
|
{
|
|
for(unsigned int j=0; j<verts->size(); j++)
|
|
{
|
|
(*verts)[j] = (*verts)[j] * _matrixStack.back();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
osg::Vec4Array* verts = dynamic_cast<osg::Vec4Array*>(geometry->getVertexArray());
|
|
if(verts)
|
|
{
|
|
for(unsigned int j=0; j<verts->size(); j++)
|
|
{
|
|
(*verts)[j] = _matrixStack.back() * (*verts)[j];
|
|
}
|
|
}
|
|
}
|
|
osg::Vec3Array* normals = dynamic_cast<osg::Vec3Array*>(geometry->getNormalArray());
|
|
if(normals)
|
|
{
|
|
for(unsigned int j=0; j<normals->size(); j++)
|
|
(*normals)[j] = osg::Matrix::transform3x3((*normals)[j], _matrixStack.back());
|
|
}
|
|
|
|
geometry->dirtyBound();
|
|
geometry->dirtyDisplayList();
|
|
}
|
|
}
|
|
|
|
|
|
void Optimizer::FlattenStaticTransformsDuplicatingSharedSubgraphsVisitor::transformBillboard(osg::Billboard& billboard)
|
|
{
|
|
osg::Vec3 axis = osg::Matrix::transform3x3(billboard.getAxis(), _matrixStack.back());
|
|
axis.normalize();
|
|
billboard.setAxis(axis);
|
|
|
|
osg::Vec3 normal = osg::Matrix::transform3x3(billboard.getNormal(), _matrixStack.back());
|
|
normal.normalize();
|
|
billboard.setNormal(normal);
|
|
|
|
for(unsigned int i=0; i<billboard.getNumDrawables(); i++)
|
|
{
|
|
osg::Vec3 originalBillboardPosition = billboard.getPosition(i);
|
|
billboard.setPosition(i, originalBillboardPosition * _matrixStack.back());
|
|
|
|
osg::Matrix matrixForDrawable = _matrixStack.back();
|
|
matrixForDrawable.preMult(osg::Matrix::translate(originalBillboardPosition));
|
|
matrixForDrawable.postMult(osg::Matrix::translate(-billboard.getPosition(i)));
|
|
|
|
_matrixStack.push_back(matrixForDrawable);
|
|
transformDrawable(*billboard.getDrawable(i));
|
|
_matrixStack.pop_back();
|
|
}
|
|
|
|
billboard.dirtyBound();
|
|
}
|
|
|
|
|
|
|