osgEarth 2.1.1
|
00001 /* -*-c++-*- */ 00002 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph 00003 * Copyright 2008-2010 Pelican Mapping 00004 * http://osgearth.org 00005 * 00006 * osgEarth is free software; you can redistribute it and/or modify 00007 * it under the terms of the GNU Lesser General Public License as published by 00008 * the Free Software Foundation; either version 2 of the License, or 00009 * (at your option) any later version. 00010 * 00011 * This program is distributed in the hope that it will be useful, 00012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00014 * GNU Lesser General Public License for more details. 00015 * 00016 * You should have received a copy of the GNU Lesser General Public License 00017 * along with this program. If not, see <http://www.gnu.org/licenses/> 00018 */ 00019 #include <osgEarthUtil/Graticule> 00020 #include <osgEarthUtil/AutoClipPlaneHandler> 00021 #include <osgEarthFeatures/BuildGeometryFilter> 00022 #include <osgEarthFeatures/TransformFilter> 00023 #include <osgEarthFeatures/ResampleFilter> 00024 #include <osgEarthSymbology/Geometry> 00025 #include <osgEarth/Registry> 00026 #include <osgEarth/FindNode> 00027 #include <osgEarth/Utils> 00028 #include <OpenThreads/Mutex> 00029 #include <OpenThreads/ScopedLock> 00030 #include <osg/PagedLOD> 00031 #include <osg/ProxyNode> 00032 #include <osg/MatrixTransform> 00033 #include <osg/Depth> 00034 #include <osg/Program> 00035 #include <osg/LineStipple> 00036 #include <osg/ClusterCullingCallback> 00037 #include <osgDB/FileNameUtils> 00038 #include <osgUtil/Optimizer> 00039 #include <osgText/Text> 00040 #include <sstream> 00041 #include <iomanip> 00042 00043 using namespace osgEarth; 00044 using namespace osgEarth::Util; 00045 using namespace osgEarth::Features; 00046 using namespace osgEarth::Symbology; 00047 using namespace OpenThreads; 00048 00049 static Mutex s_graticuleMutex; 00050 typedef std::map<unsigned int, osg::ref_ptr<Graticule> > GraticuleRegistry; 00051 static GraticuleRegistry s_graticuleRegistry; 00052 00053 #define GRATICLE_EXTENSION "osgearthutil_graticule" 00054 #define TEXT_MARKER "t" 00055 #define GRID_MARKER "g" 00056 00057 //--------------------------------------------------------------------------- 00058 00059 namespace 00060 { 00061 char s_vertexShader[] = 00062 "varying vec3 Normal; \n" 00063 "void main(void) \n" 00064 "{ \n" 00065 " Normal = normalize( gl_NormalMatrix * gl_Normal ); \n" 00066 " gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; \n" 00067 " gl_FrontColor = gl_Color; \n" 00068 "} \n"; 00069 00070 char s_fragmentShader[] = 00071 "varying vec3 Normal; \n" 00072 "void main(void) \n" 00073 "{ \n" 00074 " gl_FragColor = gl_Color; \n" 00075 "} \n"; 00076 } 00077 00078 //--------------------------------------------------------------------------- 00079 00080 Graticule::Graticule( const Map* map ) : 00081 _autoLevels( true ), 00082 _map( map ), 00083 _textColor( 1,1,0,1 ) 00084 { 00085 // safely generate a unique ID for this graticule: 00086 _id = Registry::instance()->createUID(); 00087 { 00088 ScopedLock<Mutex> lock( s_graticuleMutex ); 00089 s_graticuleRegistry[_id] = this; 00090 } 00091 00092 setLineColor( osg::Vec4f(1,1,1,0.7) ); 00093 setTextColor( osg::Vec4f(1,1,0,1) ); 00094 00095 if ( _map->isGeocentric() ) 00096 { 00097 double r = map->getProfile()->getSRS()->getEllipsoid()->getRadiusEquator(); 00098 00099 int x=8, y=4; 00100 double d = 3.5*r; 00101 double lw=0.15; 00102 addLevel( FLT_MAX, x, y, lw ); 00103 for(int i=0; i<9; i++) 00104 { 00105 x *= 2, y *= 2; 00106 lw *= 0.5; 00107 d *= 0.5; 00108 addLevel( r+d, x, y, lw ); 00109 } 00110 } 00111 00112 // Prime the grid: 00113 { 00114 std::stringstream buf; 00115 buf << "0_" << _id << "." << GRID_MARKER << "." << GRATICLE_EXTENSION; 00116 std::string bufStr = buf.str(); 00117 osg::ProxyNode* proxy = new osg::ProxyNode(); 00118 proxy->setFileName( 0, bufStr ); 00119 proxy->setCenterMode( osg::ProxyNode::USER_DEFINED_CENTER ); 00120 proxy->setCenter( osg::Vec3(0,0,0) ); 00121 proxy->setRadius( 1e10 ); 00122 this->addChild( proxy ); 00123 } 00124 00125 // Prime the text: 00126 { 00127 std::stringstream buf; 00128 buf << "0_" << _id << "." << TEXT_MARKER << "." << GRATICLE_EXTENSION; 00129 std::string bufStr = buf.str(); 00130 00131 osg::ProxyNode* proxy = new osg::ProxyNode(); 00132 proxy->setFileName( 0, bufStr ); 00133 proxy->setCenterMode( osg::ProxyNode::USER_DEFINED_CENTER ); 00134 proxy->setCenter( osg::Vec3(0,0,0) ); 00135 proxy->setRadius( 1e10 ); 00136 00137 this->addChild( proxy ); 00138 } 00139 00140 osg::StateSet* set = this->getOrCreateStateSet(); 00141 set->setRenderBinDetails( 9999, "RenderBin" ); 00142 set->setAttributeAndModes( 00143 new osg::Depth( osg::Depth::ALWAYS ), 00144 osg::StateAttribute::ON | osg::StateAttribute::PROTECTED ); 00145 set->setMode( GL_LIGHTING, 0 ); 00146 00147 //osg::Program* program = new osg::Program(); 00148 //program->addShader( new osg::Shader( osg::Shader::VERTEX, s_vertexShader ) ); 00149 //program->addShader( new osg::Shader( osg::Shader::FRAGMENT, s_fragmentShader ) ); 00150 //set->setAttributeAndModes( program, osg::StateAttribute::ON ); 00151 00152 this->addEventCallback( new AutoClipPlaneCallback( _map.get() ) ); 00153 } 00154 00155 void 00156 Graticule::addLevel( float maxRange, unsigned int cellsX, unsigned int cellsY, double lineWidth ) 00157 { 00158 if ( _autoLevels ) 00159 { 00160 _autoLevels = false; 00161 _levels.clear(); 00162 } 00163 00164 Level level; 00165 level._maxRange = maxRange; 00166 level._cellsX = cellsX; 00167 level._cellsY = cellsY; 00168 level._lineWidth = lineWidth; 00169 00170 for( std::vector<Level>::iterator i = _levels.begin(); i != _levels.end(); ++i ) 00171 { 00172 if ( maxRange > i->_maxRange ) 00173 { 00174 _levels.insert( i, level ); 00175 return; 00176 } 00177 } 00178 _levels.push_back( level ); 00179 } 00180 00181 bool 00182 Graticule::getLevel( unsigned int level, Graticule::Level& out_level ) const 00183 { 00184 if ( level < _levels.size() ) 00185 { 00186 out_level = _levels[level]; 00187 return true; 00188 } 00189 else 00190 { 00191 return false; 00192 } 00193 } 00194 00195 namespace 00196 { 00197 Geometry* 00198 createCellGeometry( const GeoExtent& tex, double lw, const GeoExtent& profEx, bool isGeocentric ) 00199 { 00200 LineString* geom = 0L; 00201 00202 if ( tex.yMin() == profEx.yMin() ) 00203 { 00204 geom = new LineString(2); 00205 geom->push_back( osg::Vec3d( tex.xMin(), tex.yMax(), 0 ) ); 00206 geom->push_back( osg::Vec3d( tex.xMin(), tex.yMin(), 0 ) ); 00207 } 00208 else 00209 { 00210 geom = new LineString(3); 00211 geom->push_back( osg::Vec3d( tex.xMin(), tex.yMax(), 0 ) ); 00212 geom->push_back( osg::Vec3d( tex.xMin(), tex.yMin(), 0 ) ); 00213 geom->push_back( osg::Vec3d( tex.xMax(), tex.yMin(), 0 ) ); 00214 } 00215 00216 return geom; 00217 } 00218 00219 struct HardCodeCellBoundCB : public osg::Node::ComputeBoundingSphereCallback 00220 { 00221 HardCodeCellBoundCB( const osg::BoundingSphere& bs ) : _bs(bs) { } 00222 virtual osg::BoundingSphere computeBound(const osg::Node&) const { return _bs; } 00223 osg::BoundingSphere _bs; 00224 }; 00225 00226 osg::Node* 00227 createTextTransform(double x, double y, double value, 00228 const osg::EllipsoidModel* ell, 00229 float size, const osg::Vec4f& color, 00230 float rotation =0.0f ) 00231 { 00232 osg::Vec3d pos; 00233 if ( ell ) // is geocentric 00234 { 00235 ell->convertLatLongHeightToXYZ( 00236 osg::DegreesToRadians( y ), 00237 osg::DegreesToRadians( x ), 00238 0, 00239 pos.x(), pos.y(), pos.z() ); 00240 } 00241 else 00242 { 00243 pos.set( x, y, 0 ); 00244 } 00245 00246 osgText::Text* t = new osgText::Text(); 00247 t->setFont( "fonts/arial.ttf" ); 00248 t->setAlignment( osgText::Text::CENTER_BOTTOM ); 00249 t->setCharacterSizeMode( osgText::Text::SCREEN_COORDS ); 00250 t->setCharacterSize( size ); 00251 t->setBackdropType( osgText::Text::OUTLINE ); 00252 t->setBackdropColor( osg::Vec4f(0,0,0,1) ); 00253 t->setColor( color ); 00254 00255 std::stringstream buf; 00256 buf << std::fixed << std::setprecision(3) << value; 00257 std::string bufStr = buf.str(); 00258 t->setText( bufStr ); 00259 00260 if ( rotation != 0.0f ) 00261 { 00262 osg::Quat rot; 00263 rot.makeRotate( osg::DegreesToRadians(rotation), 0, 0, 1 ); 00264 t->setRotation( rot ); 00265 } 00266 00267 osg::Geode* geode = new osg::Geode(); 00268 geode->addDrawable( t ); 00269 00270 osg::Matrixd L2W; 00271 ell->computeLocalToWorldTransformFromXYZ( 00272 pos.x(), pos.y(), pos.z(), L2W ); 00273 00274 osg::MatrixTransform* xform = new osg::MatrixTransform(); 00275 xform->setMatrix( L2W ); 00276 xform->addChild( geode ); 00277 00278 // in geocentric mode, install a plane culler 00279 if ( ell ) 00280 xform->setCullCallback( new CullNodeByNormal(pos) ); 00281 00282 return xform; 00283 } 00284 } 00285 00286 void 00287 Graticule::setLineColor( const osg::Vec4f& color ) 00288 { 00289 _lineStyle.getOrCreateSymbol<LineSymbol>()->stroke()->color() = color; 00290 } 00291 00292 osg::Node* 00293 Graticule::createGridLevel( unsigned int levelNum ) const 00294 { 00295 if ( !_map->isGeocentric() ) 00296 { 00297 OE_WARN << "Graticule: only supports geocentric maps" << std::endl; 00298 return 0L; 00299 } 00300 00301 Graticule::Level level; 00302 if ( !getLevel( levelNum, level ) ) 00303 return 0L; 00304 00305 OE_DEBUG << "Graticule: creating grid level " << levelNum << std::endl; 00306 00307 osg::Group* group = new osg::Group(); 00308 00309 const Profile* mapProfile = _map->getProfile(); 00310 const GeoExtent& pex = mapProfile->getExtent(); 00311 00312 double tw = pex.width() / (double)level._cellsX; 00313 double th = pex.height() / (double)level._cellsY; 00314 00315 for( unsigned int x=0; x<level._cellsX; ++x ) 00316 { 00317 for( unsigned int y=0; y<level._cellsY; ++y ) 00318 { 00319 GeoExtent tex( 00320 mapProfile->getSRS(), 00321 pex.xMin() + tw * (double)x, 00322 pex.yMin() + th * (double)y, 00323 pex.xMin() + tw * (double)(x+1), 00324 pex.yMin() + th * (double)(y+1) ); 00325 00326 Geometry* geom = createCellGeometry( tex, level._lineWidth, pex, _map->isGeocentric() ); 00327 00328 Feature* feature = new Feature(); 00329 feature->setGeometry( geom ); 00330 FeatureList features; 00331 features.push_back( feature ); 00332 00333 FilterContext cx; 00334 cx.profile() = new FeatureProfile( tex ); 00335 cx.isGeocentric() = _map->isGeocentric(); 00336 00337 if ( _map->isGeocentric() ) 00338 { 00339 // We need to make sure that on a round globe, the points are sampled such that 00340 // long segments follow the curvature of the earth. 00341 ResampleFilter resample; 00342 resample.maxLength() = tex.width() / 10.0; 00343 cx = resample.push( features, cx ); 00344 } 00345 00346 TransformFilter xform( mapProfile->getSRS() ); 00347 xform.setMakeGeocentric( _map->isGeocentric() ); 00348 xform.setLocalizeCoordinates( true ); 00349 cx = xform.push( features, cx ); 00350 00351 osg::ref_ptr<osg::Node> output; 00352 BuildGeometryFilter bg; 00353 bg.setStyle( _lineStyle ); 00354 //cx = bg.push( features, cx ); 00355 output = bg.push( features, cx ); //.getNode(); 00356 00357 if ( cx.isGeocentric() ) 00358 { 00359 // get the geocentric control point: 00360 double cplon, cplat, cpx, cpy, cpz; 00361 tex.getCentroid( cplon, cplat ); 00362 tex.getSRS()->getEllipsoid()->convertLatLongHeightToXYZ( 00363 osg::DegreesToRadians( cplat ), osg::DegreesToRadians( cplon ), 0.0, cpx, cpy, cpz ); 00364 osg::Vec3 controlPoint(cpx, cpy, cpz); 00365 00366 // get the horizon point: 00367 tex.getSRS()->getEllipsoid()->convertLatLongHeightToXYZ( 00368 osg::DegreesToRadians( tex.yMin() ), osg::DegreesToRadians( tex.xMin() ), 0.0, 00369 cpx, cpy, cpz ); 00370 osg::Vec3 horizonPoint(cpx, cpy, cpz); 00371 00372 // the deviation is the dot product of the control vector and the vector from the 00373 // control point to the horizon point. 00374 osg::Vec3 controlPointNorm = controlPoint; controlPointNorm.normalize(); 00375 osg::Vec3 horizonVecNorm = horizonPoint - controlPoint; horizonVecNorm.normalize(); 00376 float deviation = controlPointNorm * horizonVecNorm; 00377 00378 // construct the culling callback using the deviation. 00379 osg::ClusterCullingCallback* ccc = new osg::ClusterCullingCallback(); 00380 ccc->set( controlPoint, controlPointNorm, deviation, (controlPoint-horizonPoint).length() ); 00381 00382 // need a new group, because never put a cluster culler on a matrixtransform (doesn't work) 00383 osg::Group* me = new osg::Group(); 00384 me->setCullCallback( ccc ); 00385 me->addChild( output.get() ); 00386 output = me; 00387 } 00388 00389 group->addChild( output.get() ); 00390 } 00391 } 00392 00393 // organize it for better culling 00394 osgUtil::Optimizer opt; 00395 opt.optimize( group, osgUtil::Optimizer::SPATIALIZE_GROUPS ); 00396 00397 osg::Node* result = group; 00398 00399 if ( levelNum < getNumLevels() ) 00400 { 00401 Graticule::Level nextLevel; 00402 if ( getLevel( levelNum+1, nextLevel ) ) 00403 { 00404 osg::PagedLOD* plod = new osg::PagedLOD(); 00405 plod->addChild( group, nextLevel._maxRange, level._maxRange ); 00406 std::stringstream buf; 00407 buf << levelNum+1 << "_" << getID() << "." << GRID_MARKER << "." << GRATICLE_EXTENSION; 00408 std::string bufStr = buf.str(); 00409 plod->setFileName( 1, bufStr ); 00410 plod->setRange( 1, 0, nextLevel._maxRange ); 00411 result = plod; 00412 } 00413 } 00414 00415 return result; 00416 } 00417 00418 osg::Node* 00419 Graticule::createTextLevel( unsigned int levelNum ) const 00420 { 00421 if ( !_map->isGeocentric() ) 00422 { 00423 OE_WARN << "Graticule: only supports geocentric maps" << std::endl; 00424 return 0L; 00425 } 00426 00427 Graticule::Level level; 00428 if ( !getLevel( levelNum, level ) ) 00429 return 0L; 00430 00431 OE_DEBUG << "Graticule: creating text level " << levelNum << std::endl; 00432 00433 osg::Group* group = new osg::Group(); 00434 00435 const Profile* mapProfile = _map->getProfile(); 00436 const GeoExtent& pex = mapProfile->getExtent(); 00437 00438 double tw = pex.width() / (double)level._cellsX; 00439 double th = pex.height() / (double)level._cellsY; 00440 00441 const osg::EllipsoidModel* ell = _map->getProfile()->getSRS()->getEllipsoid(); 00442 00443 for( unsigned int x=0; x<level._cellsX; ++x ) 00444 { 00445 for( unsigned int y=0; y<level._cellsY; ++y ) 00446 { 00447 GeoExtent tex( 00448 mapProfile->getSRS(), 00449 pex.xMin() + tw * (double)x, 00450 pex.yMin() + th * (double)y, 00451 pex.xMin() + tw * (double)(x+1), 00452 pex.yMin() + th * (double)(y+1) ); 00453 00454 double offset = 2.0 * level._lineWidth; 00455 00456 double cx, cy; 00457 tex.getCentroid( cx, cy ); 00458 00459 // y value on the x-axis: 00460 group->addChild( createTextTransform( 00461 cx, 00462 tex.yMin() + offset, 00463 tex.yMin(), 00464 ell, 00465 20.0f, 00466 _textColor ) ); 00467 00468 // x value on the y-axis: 00469 group->addChild( createTextTransform( 00470 tex.xMin() + offset, 00471 cy, 00472 tex.xMin(), 00473 ell, 00474 20.0f, 00475 _textColor, 00476 -90.0f ) ); 00477 } 00478 } 00479 00480 // organize it for better culling 00481 osgUtil::Optimizer opt; 00482 opt.optimize( group, osgUtil::Optimizer::SPATIALIZE_GROUPS ); 00483 00484 osg::Node* result = group; 00485 00486 if ( levelNum+1 < getNumLevels() ) 00487 { 00488 Graticule::Level nextLevel; 00489 if ( getLevel( levelNum+1, nextLevel ) ) 00490 { 00491 osg::PagedLOD* plod = new osg::PagedLOD(); 00492 plod->addChild( group, nextLevel._maxRange, level._maxRange ); 00493 std::stringstream buf; 00494 buf << levelNum+1 << "_" << getID() << "." << TEXT_MARKER << "." << GRATICLE_EXTENSION; 00495 std::string bufStr = buf.str(); 00496 plod->setFileName( 1, bufStr ); 00497 plod->setRange( 1, 0, nextLevel._maxRange ); 00498 result = plod; 00499 } 00500 } 00501 return result; 00502 } 00503 00504 /**************************************************************************/ 00505 00506 namespace osgEarth { namespace Util 00507 { 00508 // OSG Plugin for loading subsequent graticule levels 00509 class GraticuleFactory : public osgDB::ReaderWriter 00510 { 00511 public: 00512 virtual const char* className() 00513 { 00514 supportsExtension( GRATICLE_EXTENSION, "osgEarth graticule" ); 00515 return "osgEarth graticule LOD loader"; 00516 } 00517 00518 virtual bool acceptsExtension(const std::string& extension) const 00519 { 00520 return osgDB::equalCaseInsensitive(extension, GRATICLE_EXTENSION); 00521 } 00522 00523 virtual ReadResult readNode(const std::string& uri, const Options* options) const 00524 { 00525 std::string ext = osgDB::getFileExtension( uri ); 00526 if ( !acceptsExtension( ext ) ) 00527 return ReadResult::FILE_NOT_HANDLED; 00528 00529 // the graticule definition is formatted: LEVEL_ID.MARKER.EXTENSION 00530 std::string def = osgDB::getNameLessExtension( uri ); 00531 00532 std::string marker = osgDB::getFileExtension( def ); 00533 def = osgDB::getNameLessExtension( def ); 00534 00535 int levelNum, id; 00536 sscanf( def.c_str(), "%d_%d", &levelNum, &id ); 00537 00538 // look up the graticule referenced in the location name: 00539 Graticule* graticule = 0L; 00540 { 00541 ScopedLock<Mutex> lock( s_graticuleMutex ); 00542 GraticuleRegistry::iterator i = s_graticuleRegistry.find(id); 00543 if ( i != s_graticuleRegistry.end() ) 00544 graticule = i->second.get(); 00545 } 00546 00547 if ( marker == GRID_MARKER ) 00548 { 00549 osg::Node* result = graticule->createGridLevel( levelNum ); 00550 return result ? ReadResult( result ) : ReadResult::ERROR_IN_READING_FILE; 00551 } 00552 else if ( marker == TEXT_MARKER ) 00553 { 00554 osg::Node* result = graticule->createTextLevel( levelNum ); 00555 return result ? ReadResult( result ) : ReadResult::ERROR_IN_READING_FILE; 00556 } 00557 else 00558 { 00559 OE_NOTICE << "oh no! no markers" << std::endl; 00560 return ReadResult::FILE_NOT_HANDLED; 00561 } 00562 } 00563 }; 00564 REGISTER_OSGPLUGIN(osgearthutil_graticule, GraticuleFactory) 00565 00566 } } // namespace osgEarth::Util 00567