void StreamingTerrainNode::updateTraversal( osg::NodeVisitor& nv ) { // this stamp keeps track of when requests are dispatched. If a request's stamp gets too // old, it is considered "expired" and subject to cancelation int stamp = nv.getFrameStamp()->getFrameNumber(); // update the frame stamp on the task services. This is necessary to support // automatic request cancelation for image requests. { ScopedLock<Mutex> lock( _taskServiceMutex ); for (TaskServiceMap::iterator i = _taskServices.begin(); i != _taskServices.end(); ++i) { i->second->setStamp( stamp ); } } // next, go through the live tiles and process update-traversal requests. This // requires a read-lock on the master tiles table. { Threading::ScopedReadLock tileTableReadLock( _tilesMutex ); for( TileTable::const_iterator i = _tiles.begin(); i != _tiles.end(); ++i ) { StreamingTile* tile = static_cast<StreamingTile*>( i->second.get() ); // update the neighbor list for each tile. refreshFamily( _update_mapf.getMapInfo(), tile->getKey(), tile->getFamily(), true ); tile->servicePendingElevationRequests( _update_mapf, stamp, true ); tile->serviceCompletedRequests( _update_mapf, true ); } } }
void OSGTerrainEngineNode::updateElevation( Tile* tile ) { Threading::ScopedWriteLock exclusiveLock( tile->getTileLayersMutex() ); const TileKey& key = tile->getKey(); bool hasElevation = _update_mapf->elevationLayers().size() > 0; osgTerrain::HeightFieldLayer* heightFieldLayer = dynamic_cast<osgTerrain::HeightFieldLayer*>(tile->getElevationLayer()); if (heightFieldLayer) { // In standard mode, just load the elevation data and dirty the tile. if ( !_isStreaming ) { osg::ref_ptr<osg::HeightField> hf; if (hasElevation) _update_mapf->getHeightField( key, true, hf, 0L); if (!hf.valid()) hf = OSGTileFactory::createEmptyHeightField( key ); heightFieldLayer->setHeightField( hf.get() ); hf->setSkirtHeight( tile->getBound().radius() * _terrainOptions.heightFieldSkirtRatio().value() ); //TODO: review this in favor of a tile update... tile->setDirty( true ); } else // if ( isStreaming ) { StreamingTile* stile = static_cast<StreamingTile*>(tile); //Update the elevation hint stile->setHasElevationHint( hasElevation ); //In seq/pre mode, if there is no elevation, just clear out all the elevation on the tiles if ( !hasElevation ) { osg::ref_ptr<osg::HeightField> hf = OSGTileFactory::createEmptyHeightField( key ); heightFieldLayer->setHeightField( hf.get() ); hf->setSkirtHeight( stile->getBound().radius() * _terrainOptions.heightFieldSkirtRatio().value() ); stile->setElevationLOD( key.getLevelOfDetail() ); stile->resetElevationRequests( *_update_mapf ); stile->queueTileUpdate( TileUpdate::UPDATE_ELEVATION ); } else { //Always load the first LOD so the children tiles can have something to use for placeholders if (stile->getKey().getLevelOfDetail() == 1) { osg::ref_ptr<osg::HeightField> hf; _update_mapf->getHeightField( key, true, hf, 0L); if (!hf.valid()) hf = OSGTileFactory::createEmptyHeightField( key ); heightFieldLayer->setHeightField( hf.get() ); hf->setSkirtHeight( stile->getBound().radius() * _terrainOptions.heightFieldSkirtRatio().value() ); stile->setElevationLOD(tile->getKey().getLevelOfDetail()); stile->queueTileUpdate( TileUpdate::UPDATE_ELEVATION ); } else { //Set the elevation LOD to -1 stile->setElevationLOD(-1); stile->resetElevationRequests( *_update_mapf ); } } } } }
void OSGTerrainEngineNode::addImageLayer( ImageLayer* layerAdded ) { if ( !layerAdded ) return; if (!_isStreaming) { refresh(); } else { // visit all existing terrain tiles and inform each one of the new image layer: TileVector tiles; _terrain->getTiles( tiles ); for( TileVector::iterator itr = tiles.begin(); itr != tiles.end(); ++itr ) { Tile* tile = itr->get(); StreamingTile* streamingTile = 0L; GeoImage geoImage; bool needToUpdateImagery = false; int imageLOD = -1; if ( !_isStreaming || tile->getKey().getLevelOfDetail() == 1 ) { // in standard mode, or at the first LOD in seq/pre mode, fetch the image immediately. TileKey geoImageKey = tile->getKey(); _tileFactory->createValidGeoImage( layerAdded, tile->getKey(), geoImage, geoImageKey ); imageLOD = tile->getKey().getLevelOfDetail(); } else { // in seq/pre mode, set up a placeholder and mark the tile as dirty. geoImage = GeoImage(ImageUtils::createEmptyImage(), tile->getKey().getExtent() ); needToUpdateImagery = true; streamingTile = static_cast<StreamingTile*>(tile); } if (geoImage.valid()) { const MapInfo& mapInfo = _update_mapf->getMapInfo(); double img_min_lon, img_min_lat, img_max_lon, img_max_lat; geoImage.getExtent().getBounds(img_min_lon, img_min_lat, img_max_lon, img_max_lat); //Specify a new locator for the color with the coordinates of the TileKey that was actually used to create the image osg::ref_ptr<GeoLocator> img_locator = tile->getKey().getProfile()->getSRS()->createLocator( img_min_lon, img_min_lat, img_max_lon, img_max_lat, !mapInfo.isGeocentric() ); //Set the CS to geocentric if we are dealing with a geocentric map if ( mapInfo.isGeocentric() ) { img_locator->setCoordinateSystemType( osgTerrain::Locator::GEOCENTRIC ); } tile->setCustomColorLayer( CustomColorLayer( layerAdded, geoImage.getImage(), img_locator.get(), imageLOD, tile->getKey() ) ); // if necessary, tell the tile to queue up a new imagery request (since we // just installed a placeholder) if ( needToUpdateImagery ) { streamingTile->updateImagery( layerAdded, *_update_mapf, _tileFactory.get() ); } } else { // this can happen if there's no data in the new layer for the given tile. // we will rely on the driver to dump out a warning if this is an error. } tile->applyImmediateTileUpdate( TileUpdate::ADD_IMAGE_LAYER, layerAdded->getUID() ); } updateTextureCombining(); } }
osg::Node* OSGTileFactory::createPlaceholderTile(const MapFrame& mapf, StreamingTerrain* terrain, const TileKey& key ) { // Start out by finding the nearest registered ancestor tile, since the placeholder is // going to be based on inherited data. Note- the ancestor may not be the immediate // parent, b/c the parent may or may not be in the scene graph. TileKey ancestorKey = key.createParentKey(); osg::ref_ptr<StreamingTile> ancestorTile; while( !ancestorTile.valid() && ancestorKey.valid() ) { terrain->getTile( ancestorKey.getTileId(), ancestorTile ); if ( !ancestorTile.valid() ) ancestorKey = ancestorKey.createParentKey(); } if ( !ancestorTile.valid() ) { OE_WARN << LC << "cannot find ancestor tile for (" << key.str() << ")" <<std::endl; return 0L; } OE_DEBUG << LC << "Creating placeholder for " << key.str() << std::endl; const MapInfo& mapInfo = mapf.getMapInfo(); bool hasElevation = mapf.elevationLayers().size() > 0; // Build a "placeholder" tile. double xmin, ymin, xmax, ymax; key.getExtent().getBounds( xmin, ymin, xmax, ymax ); // A locator will place the tile on the globe: osg::ref_ptr<GeoLocator> locator = GeoLocator::createForKey( key, mapInfo ); // The empty tile: StreamingTile* tile = new StreamingTile( key, locator.get(), terrain->getQuickReleaseGLObjects() ); tile->setTerrainTechnique( terrain->cloneTechnique() ); tile->setVerticalScale( _terrainOptions.verticalScale().value() ); tile->setDataVariance( osg::Object::DYNAMIC ); //tile->setLocator( locator.get() ); // Attach an updatecallback to normalize the edges of TerrainTiles. #if 0 if ( hasElevation && _terrainOptions.normalizeEdges().get() ) { tile->setUpdateCallback(new TerrainTileEdgeNormalizerUpdateCallback()); tile->setDataVariance(osg::Object::DYNAMIC); } #endif // Generate placeholder imagery and elevation layers. These "inherit" data from an // ancestor tile. { //Threading::ScopedReadLock parentLock( ancestorTile->getTileLayersMutex() ); addPlaceholderImageLayers ( tile, ancestorTile.get() ); addPlaceholderHeightfieldLayer( tile, ancestorTile.get(), locator.get(), key, ancestorKey ); } // calculate the switching distances: osg::BoundingSphere bs = tile->getBound(); double max_range = 1e10; double radius = bs.radius(); double min_range = radius * _terrainOptions.minTileRangeFactor().get(); // Set the skirt height of the heightfield osgTerrain::HeightFieldLayer* hfLayer = static_cast<osgTerrain::HeightFieldLayer*>(tile->getElevationLayer()); if (!hfLayer) { OE_WARN << LC << "Warning: Couldn't get hfLayer for " << key.str() << std::endl; } hfLayer->getHeightField()->setSkirtHeight(radius * _terrainOptions.heightFieldSkirtRatio().get() ); // In a Plate Carre tesselation, scale the heightfield elevations from meters to degrees if ( mapInfo.isPlateCarre() && hfLayer->getHeightField() ) HeightFieldUtils::scaleHeightFieldToDegrees( hfLayer->getHeightField() ); bool markTileLoaded = false; if ( _terrainOptions.loadingPolicy()->mode().get() != LoadingPolicy::MODE_STANDARD ) { markTileLoaded = true; tile->setHasElevationHint( hasElevation ); } // install a tile switcher: tile->attachToTerrain( terrain ); //tile->setTerrain( terrain ); //terrain->registerTile( tile ); osg::Node* result = 0L; // create a PLOD so we can keep subdividing: osg::PagedLOD* plod = new osg::PagedLOD(); plod->setCenter( bs.center() ); plod->addChild( tile, min_range, max_range ); if ( key.getLevelOfDetail() < (unsigned int)getTerrainOptions().maxLOD().get() ) { plod->setFileName( 1, createURI( _engineId, key ) ); //map->getId(), key ) ); plod->setRange( 1, 0.0, min_range ); } else { plod->setRange( 0, 0, FLT_MAX ); } #if 0 //USE_FILELOCATIONCALLBACK osgDB::Options* options = new osgDB::Options; options->setFileLocationCallback( new FileLocationCallback); plod->setDatabaseOptions( options ); #endif result = plod; // Install a callback that will load the actual tile data via the pager. result->addCullCallback( new PopulateStreamingTileDataCallback( _cull_thread_mapf ) ); // Install a cluster culler (FIXME for cube mode) //bool isCube = map->getMapOptions().coordSysType() == MapOptions::CSTYPE_GEOCENTRIC_CUBE; if ( mapInfo.isGeocentric() && !mapInfo.isCube() ) { osg::ClusterCullingCallback* ccc = createClusterCullingCallback( tile, locator->getEllipsoidModel() ); result->addCullCallback( ccc ); } return result; }