1 /* -*-c++-*- */ 2 /* osgEarth - Geospatial SDK for OpenSceneGraph 3 * Copyright 2019 Pelican Mapping 4 * http://osgearth.org 5 * 6 * osgEarth is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU Lesser General Public License as published by 8 * the Free Software Foundation; either version 2 of the License, or 9 * (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU Lesser General Public License for more details. 15 * 16 * You should have received a copy of the GNU Lesser General Public License 17 * along with this program. If not, see <http://www.gnu.org/licenses/> 18 */ 19 20 #include "SimpleModelOptions" 21 #include <osgEarth/ModelSource> 22 #include <osgEarth/Registry> 23 #include <osgEarth/Map> 24 #include <osgEarth/ShaderGenerator> 25 #include <osgEarth/FileUtils> 26 #include <osgEarth/StateSetCache> 27 #include <osg/CullStack> 28 #include <osg/LOD> 29 #include <osg/ProxyNode> 30 #include <osg/Notify> 31 #include <osg/MatrixTransform> 32 #include <osg/io_utils> 33 #include <osgDB/FileNameUtils> 34 35 using namespace osgEarth; 36 using namespace osgEarth::Drivers; 37 38 //-------------------------------------------------------------------------- 39 40 namespace 41 { 42 class LODScaleOverrideNode : public osg::Group 43 { 44 public: LODScaleOverrideNode()45 LODScaleOverrideNode() : m_lodScale(1.0f) {} ~LODScaleOverrideNode()46 virtual ~LODScaleOverrideNode() {} 47 public: setLODScale(float scale)48 void setLODScale(float scale) { m_lodScale = scale; } getLODScale() const49 float getLODScale() const { return m_lodScale; } 50 traverse(osg::NodeVisitor & nv)51 virtual void traverse(osg::NodeVisitor& nv) 52 { 53 if(nv.getVisitorType() == osg::NodeVisitor::CULL_VISITOR) 54 { 55 osg::CullStack* cullStack = dynamic_cast<osg::CullStack*>(&nv); 56 if(cullStack) 57 { 58 float oldLODScale = cullStack->getLODScale(); 59 cullStack->setLODScale(oldLODScale * m_lodScale); 60 osg::Group::traverse(nv); 61 cullStack->setLODScale(oldLODScale); 62 } 63 else 64 osg::Group::traverse(nv); 65 } 66 else 67 osg::Group::traverse(nv); 68 } 69 70 private: 71 float m_lodScale; 72 }; 73 74 class SetLoadPriorityVisitor : public osg::NodeVisitor 75 { 76 public: SetLoadPriorityVisitor(float scale=1.0f,float offset=0.0f)77 SetLoadPriorityVisitor(float scale=1.0f, float offset=0.0f) 78 : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) 79 , m_scale(scale) 80 , m_offset(offset) 81 { 82 setNodeMaskOverride( ~0 ); 83 } 84 apply(osg::PagedLOD & node)85 virtual void apply(osg::PagedLOD& node) 86 { 87 for(unsigned n = 0; n < node.getNumFileNames(); n++) 88 { 89 float old; 90 old = node.getPriorityScale(n); 91 node.setPriorityScale(n, old * m_scale); 92 old = node.getPriorityOffset(n); 93 node.setPriorityOffset(n, old + m_offset); 94 } 95 traverse(node); 96 } 97 98 private: 99 float m_scale; 100 float m_offset; 101 }; 102 103 /** 104 * Visitor that sets the DBOptions on deferred-loading nodes. 105 */ 106 class SetDBOptionsVisitor : public osg::NodeVisitor 107 { 108 private: 109 osg::ref_ptr<osgDB::Options> _dbOptions; 110 111 public: SetDBOptionsVisitor(const osgDB::Options * dbOptions)112 SetDBOptionsVisitor(const osgDB::Options* dbOptions) 113 { 114 setTraversalMode( TRAVERSE_ALL_CHILDREN ); 115 setNodeMaskOverride( ~0 ); 116 _dbOptions = Registry::cloneOrCreateOptions( dbOptions ); 117 } 118 119 public: // osg::NodeVisitor 120 apply(osg::PagedLOD & node)121 void apply(osg::PagedLOD& node) 122 { 123 node.setDatabaseOptions( _dbOptions.get() ); 124 traverse(node); 125 } 126 apply(osg::ProxyNode & node)127 void apply(osg::ProxyNode& node) 128 { 129 node.setDatabaseOptions( _dbOptions.get() ); 130 traverse(node); 131 } 132 }; 133 } 134 135 //-------------------------------------------------------------------------- 136 137 class SimpleModelSource : public ModelSource 138 { 139 public: SimpleModelSource(const ModelSourceOptions & options)140 SimpleModelSource( const ModelSourceOptions& options ) 141 : ModelSource( options ), _options(options) { } 142 143 //override initialize(const osgDB::Options * dbOptions)144 Status initialize( const osgDB::Options* dbOptions ) 145 { 146 _dbOptions = dbOptions; 147 //ModelSource::initialize( dbOptions ); 148 return Status::OK(); 149 } 150 151 // override createNodeImplementation(const Map * map,ProgressCallback * progress)152 osg::Node* createNodeImplementation(const Map* map, ProgressCallback* progress) 153 { 154 osg::ref_ptr<osg::Node> result; 155 156 // Set up the DB Options for possible paged or proxy loading. 157 osg::ref_ptr<osgDB::Options> localDBOptions = 158 Registry::instance()->cloneOrCreateOptions( _dbOptions.get() ); 159 160 localDBOptions->getDatabasePathList().push_back( osgDB::getFilePath(_options.url()->full()) ); 161 162 163 // Only support paging if they've enabled it and provided a min/max range 164 bool usePagedLOD = *_options.paged() && 165 (_options.minRange().isSet() || _options.maxRange().isSet()); 166 167 if (_options.node() != NULL) 168 { 169 result = _options.node(); 170 } 171 else 172 { 173 // Only load the model if it's not paged or we don't have a location set. 174 if (!usePagedLOD || !_options.location().isSet()) 175 { 176 result = _options.url()->getNode( localDBOptions.get(), progress ); 177 } 178 } 179 180 // Always create a matrix transform 181 osg::MatrixTransform* mt = new osg::MatrixTransform; 182 183 if (_options.location().isSet() && map != 0L) 184 { 185 GeoPoint geoPoint( 186 map->getProfile()->getSRS(), 187 (*_options.location()).x(), 188 (*_options.location()).y(), 189 (*_options.location()).z(), 190 ALTMODE_ABSOLUTE ); 191 192 osg::Matrixd matrix; 193 geoPoint.createLocalToWorld( matrix ); 194 195 if (_options.orientation().isSet()) 196 { 197 //Apply the rotation 198 osg::Matrix rot_mat; 199 rot_mat.makeRotate( 200 osg::DegreesToRadians((*_options.orientation()).y()), osg::Vec3(1,0,0), 201 osg::DegreesToRadians((*_options.orientation()).x()), osg::Vec3(0,0,1), 202 osg::DegreesToRadians((*_options.orientation()).z()), osg::Vec3(0,1,0) ); 203 matrix.preMult(rot_mat); 204 } 205 mt->setMatrix( matrix ); 206 } 207 208 if ( _options.minRange().isSet() || _options.maxRange().isSet() ) 209 { 210 float minRange = _options.minRange().isSet() ? (*_options.minRange()) : 0.0f; 211 float maxRange = _options.maxRange().isSet() ? (*_options.maxRange()) : FLT_MAX; 212 213 osg::LOD* lod = 0; 214 215 if (!usePagedLOD) 216 { 217 // Just use a regular LOD 218 lod = new osg::LOD(); 219 lod->addChild(result.release(), minRange, maxRange); 220 } 221 else 222 { 223 // Use a PagedLOD 224 osg::PagedLOD* plod =new osg::PagedLOD(); 225 plod->setFileName(0, _options.url()->full()); 226 227 // If they want the model to be paged but haven't given us a location we have to load 228 // up the node up front and figure out what it's center and radius are or it won't page in. 229 if (!_options.location().isSet() && result.valid()) 230 { 231 osg::Vec3d center = result->getBound().center(); 232 OE_DEBUG << "Radius=" << result->getBound().radius() << " center=" << center.x() << "," << center.y() << "," << center.z() << std::endl; 233 plod->setCenter(result->getBound().center()); 234 plod->setRadius(osg::maximum(result->getBound().radius(), 235 static_cast<osg::BoundingSphere::value_type>(maxRange))); 236 237 } 238 lod = plod; 239 } 240 lod->setRange(0, minRange, maxRange); 241 mt->addChild(lod); 242 } 243 else 244 { 245 // Simply add the node to the matrix transform 246 if (result.valid()) 247 { 248 mt->addChild( result.get() ); 249 } 250 } 251 252 result = mt; 253 254 // generate a shader program to render the model. 255 if ( result.valid() ) 256 { 257 if(_options.loadingPriorityScale().isSet() || _options.loadingPriorityOffset().isSet()) 258 { 259 SetLoadPriorityVisitor slpv(_options.loadingPriorityScale().value(), _options.loadingPriorityOffset().value()); 260 result->accept(slpv); 261 } 262 263 if(_options.lodScale().isSet()) 264 { 265 LODScaleOverrideNode * node = new LODScaleOverrideNode; 266 node->setLODScale(_options.lodScale().value()); 267 node->addChild(result.release()); 268 result = node; 269 } 270 271 if ( _options.shaderPolicy() == SHADERPOLICY_GENERATE ) 272 { 273 osg::ref_ptr<StateSetCache> cache = new StateSetCache(); 274 275 Registry::shaderGenerator().run( 276 result.get(), 277 _options.url()->base(), 278 cache.get() ); 279 } 280 else if ( _options.shaderPolicy() == SHADERPOLICY_DISABLE ) 281 { 282 result->getOrCreateStateSet()->setAttributeAndModes( 283 new osg::Program(), 284 osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE ); 285 } 286 287 // apply the DB options if there are any, so that deferred nodes like PagedLOD et al 288 // will inherit the loading options. 289 if ( _dbOptions.valid() ) 290 { 291 SetDBOptionsVisitor setDBO( localDBOptions.get() ); 292 result->accept( setDBO ); 293 } 294 } 295 296 297 return result.release(); 298 } 299 300 protected: 301 302 const SimpleModelOptions _options; 303 osg::ref_ptr<const osgDB::Options> _dbOptions; 304 }; 305 306 307 class SimpleModelSourceFactory : public ModelSourceDriver 308 { 309 public: SimpleModelSourceFactory()310 SimpleModelSourceFactory() 311 { 312 supportsExtension( "osgearth_model_simple", "osgEarth simple model plugin" ); 313 } 314 className() const315 virtual const char* className() const 316 { 317 return "osgEarth Simple Model Plugin"; 318 } 319 readObject(const std::string & file_name,const Options * options) const320 virtual ReadResult readObject(const std::string& file_name, const Options* options) const 321 { 322 if ( !acceptsExtension(osgDB::getLowerCaseFileExtension( file_name ))) 323 return ReadResult::FILE_NOT_HANDLED; 324 325 return ReadResult( new SimpleModelSource( getModelSourceOptions(options) ) ); 326 } 327 }; 328 329 REGISTER_OSGPLUGIN(osgearth_model_simple, SimpleModelSourceFactory) 330