void
TerrainEngineNode::preInitialize( const Map* map, const TerrainOptions& options )
{
    _map = map;
    
    // fire up a terrain utility interface
    _terrainInterface = new Terrain( this, map->getProfile(), map->isGeocentric(), options );

    // set up the CSN values   
    _map->getProfile()->getSRS()->populateCoordinateSystemNode( this );
    
    // OSG's CSN likes a NULL ellipsoid to represent projected mode.
    if ( !_map->isGeocentric() )
        this->setEllipsoidModel( NULL );
    
    // install the proper layer composition technique:
    _texCompositor = new TextureCompositor();

    // then register the callback so we can process further map model changes
    _map->addMapCallback( new TerrainEngineNodeCallbackProxy( this ) );

    // enable backface culling
    osg::StateSet* set = getOrCreateStateSet();
    set->setMode( GL_CULL_FACE, 1 );

    if ( options.enableMercatorFastPath().isSet() )
    {
        OE_INFO 
            << LC << "Mercator fast path " 
            << (options.enableMercatorFastPath()==true? "enabled" : "DISABLED") << std::endl;
    }

    _initStage = INIT_PREINIT_COMPLETE;
}
void
TerrainEngineNode::setMap(const Map* map, const TerrainOptions& options)
{
    if (!map) return;

    _map = map;
    
    // Create a terrain utility interface. This interface can be used
    // to query the in-memory terrain graph, subscribe to tile events, etc.
    _terrainInterface = new Terrain( this, map->getProfile(), map->isGeocentric(), options );

    // Set up the CSN values. We support this because some manipulators look for it,
    // but osgEarth itself doesn't use it.
    _map->getProfile()->getSRS()->populateCoordinateSystemNode( this );
    
    // OSG's CSN likes a NULL ellipsoid to represent projected mode.
    if ( !_map->isGeocentric() )
        this->setEllipsoidModel( NULL );
    
    // Install an object to manage texture image unit usage:
    _textureResourceTracker = new TextureCompositor();
    std::set<int> offLimits = osgEarth::Registry::instance()->getOffLimitsTextureImageUnits();
    for(std::set<int>::const_iterator i = offLimits.begin(); i != offLimits.end(); ++i)
        _textureResourceTracker->setTextureImageUnitOffLimits( *i );

    // Register a callback so we can process further map model changes
    _map->addMapCallback( new TerrainEngineNodeCallbackProxy(this) );

    // Force a render bin if specified in the options
    if ( options.binNumber().isSet() )
    {
        osg::StateSet* set = getOrCreateStateSet();
        set->setRenderBinDetails( options.binNumber().get(), "RenderBin" );
    }
   
    // This is the object that creates the data model for each terrain tile.
    _tileModelFactory = new TerrainTileModelFactory(options);

    // Manually trigger the map callbacks the first time:
    if (_map->getProfile())
        onMapInfoEstablished(MapInfo(_map));

    // Create a layer controller. This object affects the uniforms
    // that control layer appearance properties
    _imageLayerController = new ImageLayerController(_map, this);

    // register the layer Controller it with all pre-existing image layers:
    MapFrame mapf(_map);
    ImageLayerVector imageLayers;
    mapf.getLayers(imageLayers);

    for (ImageLayerVector::const_iterator i = imageLayers.begin(); i != imageLayers.end(); ++i)
    {
        i->get()->addCallback(_imageLayerController.get());
    }

    _initStage = INIT_POSTINIT_COMPLETE;
}
TextureCompositorMultiTexture::TextureCompositorMultiTexture( bool useGPU, const TerrainOptions& options ) :
_lodTransitionTime( *options.lodTransitionTime() ),
_enableMipmapping ( *options.enableMipmapping() ),
_minFilter        ( *options.minFilter() ),
_magFilter        ( *options.magFilter() ),
_useGPU           ( useGPU )
{
    _enableMipmappingOnUpdatedTextures = Registry::capabilities().supportsMipmappedTextureUpdates();
}
Exemple #4
0
void
RexTerrainEngineNode::preInitialize( const Map* map, const TerrainOptions& options )
{
    // Force the mercator fast path off, since REX does not support it yet.
    TerrainOptions myOptions = options;
    myOptions.enableMercatorFastPath() = false;

    TerrainEngineNode::preInitialize( map, myOptions );
}
void
TerrainEngineNode::preInitialize( const Map* map, const TerrainOptions& options )
{
    _map = map;
    
    // fire up a terrain utility interface
    _terrainInterface = new Terrain( this, map->getProfile(), map->isGeocentric(), options );

    // set up the CSN values   
    _map->getProfile()->getSRS()->populateCoordinateSystemNode( this );
    
    // OSG's CSN likes a NULL ellipsoid to represent projected mode.
    if ( !_map->isGeocentric() )
        this->setEllipsoidModel( NULL );
    
    // install the proper layer composition technique:
    _texCompositor = new TextureCompositor( options );

    // prime the compositor with pre-existing image layers:
    MapFrame mapf(map, Map::IMAGE_LAYERS);
    for( unsigned i=0; i<mapf.imageLayers().size(); ++i )
    {
        _texCompositor->applyMapModelChange( MapModelChange(
            MapModelChange::ADD_IMAGE_LAYER,
            mapf.getRevision(),
            mapf.getImageLayerAt(i),
            i ) );
    }

    // then register the callback so we can process further map model changes
    _map->addMapCallback( new TerrainEngineNodeCallbackProxy( this ) );

    // enable backface culling
    osg::StateSet* set = getOrCreateStateSet();
    //set->setAttributeAndModes( new osg::CullFace( osg::CullFace::BACK ), osg::StateAttribute::ON );
    set->setMode( GL_CULL_FACE, 1 );

    // elevation uniform
    // NOTE: wrong...this should be per-CullVisitor...consider putting in the Culling::CullUserData
    _cameraElevationUniform = new osg::Uniform( osg::Uniform::FLOAT, "osgearth_CameraElevation" );
    _cameraElevationUniform->set( 0.0f );
    set->addUniform( _cameraElevationUniform.get() );
    
    set->getOrCreateUniform( "osgearth_ImageLayerAttenuation", osg::Uniform::FLOAT )->set(
        *options.attentuationDistance() );

    if ( options.enableMercatorFastPath().isSet() )
    {
        OE_INFO 
            << LC << "Mercator fast path " 
            << (options.enableMercatorFastPath()==true? "enabled" : "DISABLED") << std::endl;
    }

    _initStage = INIT_PREINIT_COMPLETE;
}
void
TerrainEngineNode::validateTerrainOptions( TerrainOptions& options )
{
    // make sure all the requested properties are compatible, and fall back as necessary.
    //const Capabilities& caps = Registry::instance()->getCapabilities();

    // warn against mixing multipass technique with preemptive/sequential mode:
    if (options.compositingTechnique() == TerrainOptions::COMPOSITING_MULTIPASS &&
        options.loadingPolicy()->mode() != LoadingPolicy::MODE_STANDARD )
    {
        OE_WARN << LC << "MULTIPASS compositor is incompatible with preemptive/sequential loading policy; "
            << "falling back on STANDARD mode" << std::endl;
        options.loadingPolicy()->mode() = LoadingPolicy::MODE_STANDARD;
    }
}
void
TerrainEngineNode::preInitialize( const Map* map, const TerrainOptions& options )
{
    _map = map;
    
    // fire up a terrain utility interface
    _terrainInterface = new Terrain( this, map->getProfile(), map->isGeocentric(), options );

    // set up the CSN values   
    _map->getProfile()->getSRS()->populateCoordinateSystemNode( this );
    
    // OSG's CSN likes a NULL ellipsoid to represent projected mode.
    if ( !_map->isGeocentric() )
        this->setEllipsoidModel( NULL );
    
    // install an object to manage texture image unit usage:
    _texCompositor = new TextureCompositor();
    std::set<int> offLimits = osgEarth::Registry::instance()->getOffLimitsTextureImageUnits();
    for(std::set<int>::const_iterator i = offLimits.begin(); i != offLimits.end(); ++i)
        _texCompositor->setTextureImageUnitOffLimits( *i );

    // then register the callback so we can process further map model changes
    _map->addMapCallback( new TerrainEngineNodeCallbackProxy( this ) );

    // apply render bin if necessary
    if ( options.binNumber().isSet() )
    {
        osg::StateSet* set = getOrCreateStateSet();
        set->setRenderBinDetails( options.binNumber().get(), "RenderBin" );
    }

    if ( options.enableMercatorFastPath().isSet() )
    {
        OE_INFO 
            << LC << "Mercator fast path " 
            << (options.enableMercatorFastPath()==true? "enabled" : "DISABLED") << std::endl;
    }
    
    // a default factory - this is the object that creates the data model for
    // each terrain tile.
    _tileModelFactory = new TerrainTileModelFactory(options);

    _initStage = INIT_PREINIT_COMPLETE;
}
void
MapNodeOptions::setTerrainOptions( const TerrainOptions& options )
{
    _terrainOptionsConf = options.getConfig();
    if ( _terrainOptions )
    {
        delete _terrainOptions;
        _terrainOptions = 0L;
    }
}
void
OSGTerrainEngineNode::preInitialize( const Map* map, const TerrainOptions& options )
{
    TerrainEngineNode::preInitialize( map, options );

    _isStreaming =
        options.loadingPolicy()->mode() == LoadingPolicy::MODE_PREEMPTIVE ||
        options.loadingPolicy()->mode() == LoadingPolicy::MODE_SEQUENTIAL;

    // in standard mode, try to set the number of OSG DatabasePager threads to use.
    if ( options.loadingPolicy().isSet() && !_isStreaming )
    {
        int numThreads = -1;

        if ( options.loadingPolicy()->numLoadingThreads().isSet() )
        {
            numThreads = osg::maximum( 1, *options.loadingPolicy()->numLoadingThreads() );
        }
        else if ( options.loadingPolicy()->numLoadingThreadsPerCore().isSet() )
        {
            float numThreadsPerCore = *options.loadingPolicy()->numLoadingThreadsPerCore();
            numThreads = osg::maximum( (int)1, (int)osg::round( 
                numThreadsPerCore * (float)OpenThreads::GetNumberOfProcessors() ) );
        }

        if ( numThreads > 0 )
        {
            OE_INFO << LC << "Requesting " << numThreads << " database pager threads in STANDARD mode" << std::endl;
            osg::DisplaySettings::instance()->setNumOfDatabaseThreadsHint( numThreads );
            //osg::DisplaySettings::instance()->setNumOfHttpDatabaseThreadsHint( numThreads );
        }
    }
}
void
TerrainEngineNode::preInitialize( const Map* map, const TerrainOptions& options )
{
    _map = map;

    // set up the CSN values   
    _map->getProfile()->getSRS()->populateCoordinateSystemNode( this );
    
    // OSG's CSN likes a NULL ellipsoid to represent projected mode.
    if ( !_map->isGeocentric() )
        this->setEllipsoidModel( NULL );
    
    // install the proper layer composition technique:
    _texCompositor = new TextureCompositor( options );

    // prime the compositor with pre-existing image layers:
    MapFrame mapf(map, Map::IMAGE_LAYERS);
    for( unsigned i=0; i<mapf.imageLayers().size(); ++i )
    {
        _texCompositor->applyMapModelChange( MapModelChange(
            MapModelChange::ADD_IMAGE_LAYER,
            mapf.getRevision(),
            mapf.getImageLayerAt(i),
            i ) );
    }

    // then register the callback so we can process further map model changes
    _map->addMapCallback( new TerrainEngineNodeCallbackProxy( this ) );

    // enable backface culling
    osg::StateSet* set = getOrCreateStateSet();
    set->setAttributeAndModes( new osg::CullFace( osg::CullFace::BACK ), osg::StateAttribute::ON );

    // elevation uniform
    _cameraElevationUniform = new osg::Uniform( osg::Uniform::FLOAT, "osgearth_CameraElevation" );
    _cameraElevationUniform->set( 0.0f );
    set->addUniform( _cameraElevationUniform.get() );
    
    set->getOrCreateUniform( "osgearth_ImageLayerAttenuation", osg::Uniform::FLOAT )->set(
        *options.attentuationDistance() );

    _initStage = INIT_PREINIT_COMPLETE;
}
TextureCompositor::TextureCompositor(const TerrainOptions& options) :
osg::Referenced( true ),
_tech( options.compositingTechnique().value() ),
_options( options ),
_forceTech( false )
{
    // for debugging:
    if ( _tech == TerrainOptions::COMPOSITING_AUTO && ::getenv( "OSGEARTH_COMPOSITOR_TECH" ) )
    {
        TerrainOptions::CompositingTechnique oldTech = _tech;
        std::string t( ::getenv( "OSGEARTH_COMPOSITOR_TECH" ) );
        if      ( t == "TEXTURE_ARRAY" )    _tech = TerrainOptions::COMPOSITING_TEXTURE_ARRAY;
        else if ( t == "MULTITEXTURE_GPU" ) _tech = TerrainOptions::COMPOSITING_MULTITEXTURE_GPU;
        else if ( t == "MULTIPASS" )        _tech = TerrainOptions::COMPOSITING_MULTIPASS;
        if ( oldTech != _tech )
            _forceTech = true;
    }

    init();
}
void
OSGTerrainEngineNode::preInitialize( const Map* map, const TerrainOptions& options )
{
    TerrainEngineNode::preInitialize( map, options );

    _isStreaming =
        options.loadingPolicy()->mode() == LoadingPolicy::MODE_PREEMPTIVE ||
        options.loadingPolicy()->mode() == LoadingPolicy::MODE_SEQUENTIAL;

    // in standard mode, try to set the number of OSG DatabasePager threads to use.
    if ( options.loadingPolicy().isSet() && !_isStreaming )
    {
        int numThreads = -1;

        if ( options.loadingPolicy()->numLoadingThreads().isSet() )
        {
            numThreads = osg::maximum( 1, *options.loadingPolicy()->numLoadingThreads() );
        }
        else if ( options.loadingPolicy()->numLoadingThreadsPerCore().isSet() )
        {
            float numThreadsPerCore = *options.loadingPolicy()->numLoadingThreadsPerCore();
            numThreads = osg::maximum( (int)1, (int)osg::round( 
                numThreadsPerCore * (float)OpenThreads::GetNumberOfProcessors() ) );
        }

        if ( numThreads > 0 )
        {
            // NOTE: this doesn't work. the pager gets created before we ever get here.
            numThreads = osg::maximum(numThreads, 2);
            int numHttpThreads = osg::clampBetween( numThreads/2, 1, numThreads-1 );

            //OE_INFO << LC << "Requesting pager threads in STANDARD mode: local=" << numThreads << ", http=" << numHttpThreads << std::endl;
            osg::DisplaySettings::instance()->setNumOfDatabaseThreadsHint( numThreads );
            osg::DisplaySettings::instance()->setNumOfHttpDatabaseThreadsHint( numHttpThreads );
        }
    }
}
TextureCompositorTexArray::TextureCompositorTexArray( const TerrainOptions& options ) :
_lodTransitionTime( *options.lodTransitionTime() )
{
    //nop
}
Exemple #14
0
void GlobePlugin::setupMap()
{
  QSettings settings;
  QString cacheDirectory = settings.value( "cache/directory", QgsApplication::qgisSettingsDirPath() + "cache" ).toString();
  TMSCacheOptions cacheOptions;
  cacheOptions.setPath( cacheDirectory.toStdString() );

  MapOptions mapOptions;
  mapOptions.cache() = cacheOptions;
  osgEarth::Map *map = new osgEarth::Map( mapOptions );

  //Default image layer
  GDALOptions driverOptions;
  driverOptions.url() = QDir::cleanPath( QgsApplication::pkgDataPath() + "/globe/world.tif" ).toStdString();
  ImageLayerOptions layerOptions( "world", driverOptions );
  layerOptions.cacheEnabled() = false;
  map->addImageLayer( new osgEarth::ImageLayer( layerOptions ) );

  MapNodeOptions nodeOptions;
  //nodeOptions.proxySettings() =
  //nodeOptions.enableLighting() = false;

  //LoadingPolicy loadingPolicy( LoadingPolicy::MODE_SEQUENTIAL );
  TerrainOptions terrainOptions;
  //terrainOptions.loadingPolicy() = loadingPolicy;
  terrainOptions.compositingTechnique() = TerrainOptions::COMPOSITING_MULTITEXTURE_FFP;
  nodeOptions.setTerrainOptions( terrainOptions );

  // The MapNode will render the Map object in the scene graph.
  mMapNode = new osgEarth::MapNode( map, nodeOptions );

  //prefill cache
  if ( !QFile::exists( cacheDirectory + "/worldwind_srtm" ) )
  {
    copyFolder( QgsApplication::pkgDataPath() + "/globe/data/worldwind_srtm", cacheDirectory + "/globe/worldwind_srtm" );
  }

  mRootNode = new osg::Group();
  mRootNode->addChild( mMapNode );

  // Add layers to the map
  layersChanged();

  // model placement utils
  mElevationManager = new osgEarth::Util::ElevationManager( mMapNode->getMap() );
  mElevationManager->setTechnique( osgEarth::Util::ElevationManager::TECHNIQUE_GEOMETRIC );
  mElevationManager->setMaxTilesToCache( 50 );

  mObjectPlacer = new osgEarth::Util::ObjectPlacer( mMapNode );

  // place 3D model on point layer
  if ( mSettingsDialog.modelLayer() && !mSettingsDialog.modelPath().isEmpty() )
  {
    osg::Node* model = osgDB::readNodeFile( mSettingsDialog.modelPath().toStdString() );
    if ( model )
    {
      QgsVectorLayer* layer = mSettingsDialog.modelLayer();
      QgsAttributeList fetchAttributes;
      layer->select( fetchAttributes ); //TODO: select only visible features
      QgsFeature feature;
      while ( layer->nextFeature( feature ) )
      {
        QgsPoint point = feature.geometry()->asPoint();
        placeNode( model, point.y(), point.x() );
      }
    }
  }

}
Exemple #15
0
void
RexTerrainEngineNode::postInitialize( const Map* map, const TerrainOptions& options )
{
    // Force the mercator fast path off, since REX does not support it yet.
    TerrainOptions myOptions = options;
    myOptions.enableMercatorFastPath() = false;

    TerrainEngineNode::postInitialize( map, myOptions );

    // Initialize the map frames. We need one for the update thread and one for the
    // cull thread. Someday we can detect whether these are actually the same thread
    // (depends on the viewer's threading mode).
    _update_mapf = new MapFrame( map, Map::ENTIRE_MODEL );

    // A callback for overriding bounding boxes for tiles
    _modifyBBoxCallback = new ModifyBoundingBoxCallback(*_update_mapf);

    // merge in the custom options:
    _terrainOptions.merge( myOptions );

    // morphing imagery LODs requires we bind parent textures to their own unit.
    if ( _terrainOptions.morphImagery() == true )
    {
        _requireParentTextures = true;
    }

    // Terrain morphing doesn't work in projected maps:
    if (map->getSRS()->isProjected())
    {
        _terrainOptions.morphTerrain() = false;
    }

    // if the envvar for tile expiration is set, overide the options setting
    const char* val = ::getenv("OSGEARTH_EXPIRATION_THRESHOLD");
    if ( val )
    {
        _terrainOptions.expirationThreshold() = as<unsigned>(val, _terrainOptions.expirationThreshold().get());
        OE_INFO << LC << "Expiration threshold set by env var = " << _terrainOptions.expirationThreshold().get() << "\n";
    }

    // if the envvar for hires prioritization is set, override the options setting
    const char* hiresFirst = ::getenv("OSGEARTH_HIGH_RES_FIRST");
    if ( hiresFirst )
    {
        _terrainOptions.highResolutionFirst() = true;
    }

    // check for normal map generation (required for lighting).
    if ( _terrainOptions.normalMaps() == true )
    {
        this->_requireNormalTextures = true;
    }

    // A shared registry for tile nodes in the scene graph. Enable revision tracking
    // if requested in the options. Revision tracking lets the registry notify all
    // live tiles of the current map revision so they can inrementally update
    // themselves if necessary.
    _liveTiles = new TileNodeRegistry("live");
    _liveTiles->setMapRevision( _update_mapf->getRevision() );

    // A resource releaser that will call releaseGLObjects() on expired objects.
    _releaser = new ResourceReleaser();
    this->addChild(_releaser.get());

    // A shared geometry pool.
    _geometryPool = new GeometryPool( _terrainOptions );
    _geometryPool->setReleaser( _releaser.get());
    this->addChild( _geometryPool.get() );

    // Make a tile loader
    PagerLoader* loader = new PagerLoader( this );
    loader->setNumLODs(_terrainOptions.maxLOD().getOrUse(DEFAULT_MAX_LOD));
    loader->setMergesPerFrame( _terrainOptions.mergesPerFrame().get() );
    for (std::vector<RexTerrainEngineOptions::LODOptions>::const_iterator i = _terrainOptions.lods().begin(); i != _terrainOptions.lods().end(); ++i) {
        if (i->_lod.isSet()) {
            loader->setLODPriorityScale(i->_lod.get(), i->_priorityScale.getOrUse(1.0f));
            loader->setLODPriorityOffset(i->_lod.get(), i->_priorityOffset.getOrUse(0.0f));
        }
    }

    _loader = loader;
    this->addChild( _loader.get() );

    // Make a tile unloader
    _unloader = new UnloaderGroup( _liveTiles.get() );
    _unloader->setThreshold( _terrainOptions.expirationThreshold().get() );
    _unloader->setReleaser(_releaser.get());
    this->addChild( _unloader.get() );
    
    // handle an already-established map profile:
    MapInfo mapInfo( map );
    if ( _update_mapf->getProfile() )
    {
        // NOTE: this will initialize the map with the startup layers
        onMapInfoEstablished( mapInfo );
    }

    // install a layer callback for processing further map actions:
    map->addMapCallback( new RexTerrainEngineNodeMapCallbackProxy(this) );

    // Prime with existing layers:
    _batchUpdateInProgress = true;

    ElevationLayerVector elevationLayers;
    map->getLayers( elevationLayers );
    for( ElevationLayerVector::const_iterator i = elevationLayers.begin(); i != elevationLayers.end(); ++i )
        addElevationLayer( i->get() );

    ImageLayerVector imageLayers;
    map->getLayers( imageLayers );
    for( ImageLayerVector::iterator i = imageLayers.begin(); i != imageLayers.end(); ++i )
        addTileLayer( i->get() );

    _batchUpdateInProgress = false;

    // set up the initial shaders
    updateState();

    // register this instance to the osgDB plugin can find it.
    registerEngine( this );

    // now that we have a map, set up to recompute the bounds
    dirtyBound();
}
Exemple #16
0
void GlobePlugin::setupMap()
{
  QSettings settings;
  /*
  QString cacheDirectory = settings.value( "cache/directory", QgsApplication::qgisSettingsDirPath() + "cache" ).toString();
  TMSCacheOptions cacheOptions;
  cacheOptions.setPath( cacheDirectory.toStdString() );
  */

  MapOptions mapOptions;
  //mapOptions.cache() = cacheOptions;
  osgEarth::Map *map = new osgEarth::Map( mapOptions );

  //Default image layer
  /*
  GDALOptions driverOptions;
  driverOptions.url() = QDir::cleanPath( QgsApplication::pkgDataPath() + "/globe/world.tif" ).toStdString();
  ImageLayerOptions layerOptions( "world", driverOptions );
  map->addImageLayer( new osgEarth::ImageLayer( layerOptions ) );
  */
  TMSOptions imagery;
  imagery.url() = "http://readymap.org/readymap/tiles/1.0.0/7/";
  map->addImageLayer( new ImageLayer( "Imagery", imagery ) );

  MapNodeOptions nodeOptions;
  //nodeOptions.proxySettings() =
  //nodeOptions.enableLighting() = false;

  //LoadingPolicy loadingPolicy( LoadingPolicy::MODE_SEQUENTIAL );
  TerrainOptions terrainOptions;
  //terrainOptions.loadingPolicy() = loadingPolicy;
  terrainOptions.compositingTechnique() = TerrainOptions::COMPOSITING_MULTITEXTURE_FFP;
  //terrainOptions.lodFallOff() = 6.0;
  nodeOptions.setTerrainOptions( terrainOptions );

  // The MapNode will render the Map object in the scene graph.
  mMapNode = new osgEarth::MapNode( map, nodeOptions );

  mRootNode = new osg::Group();
  mRootNode->addChild( mMapNode );

  // Add layers to the map
  imageLayersChanged();
  elevationLayersChanged();

  // model placement utils
#ifdef HAVE_OSGEARTH_ELEVATION_QUERY
#else
  mElevationManager = new osgEarth::Util::ElevationManager( mMapNode->getMap() );
  mElevationManager->setTechnique( osgEarth::Util::ElevationManager::TECHNIQUE_GEOMETRIC );
  mElevationManager->setMaxTilesToCache( 50 );

  mObjectPlacer = new osgEarth::Util::ObjectPlacer( mMapNode );

  // place 3D model on point layer
  if ( mSettingsDialog->modelLayer() && !mSettingsDialog->modelPath().isEmpty() )
  {
    osg::Node* model = osgDB::readNodeFile( mSettingsDialog->modelPath().toStdString() );
    if ( model )
    {
      QgsVectorLayer* layer = mSettingsDialog->modelLayer();
      QgsFeatureIterator fit = layer->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( QgsAttributeList() ) ); //TODO: select only visible features
      QgsFeature feature;
      while ( fit.nextFeature( feature ) )
      {
        QgsPoint point = feature.geometry()->asPoint();
        placeNode( model, point.y(), point.x() );
      }
    }
  }
#endif

}