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 00020 #include <osgEarth/Registry> 00021 #include <osgEarth/Map> 00022 #include <osgEarthFeatures/FeatureModelSource> 00023 #include <osgEarthFeatures/FeatureSource> 00024 #include <osgEarthFeatures/BufferFilter> 00025 #include <osgEarthFeatures/TransformFilter> 00026 #include <osgEarthFeatures/ResampleFilter> 00027 #include <osgEarthFeatures/ConvertTypeFilter> 00028 #include <osgEarthFeatures/FeatureGridder> 00029 #include <osgEarthSymbology/StencilVolumeNode> 00030 #include <osgEarthSymbology/Style> 00031 #include <osgEarthSymbology/StencilVolumeNode> 00032 #include <osg/Notify> 00033 #include <osg/MatrixTransform> 00034 #include <osg/ClusterCullingCallback> 00035 #include <osg/Geode> 00036 #include <osg/Projection> 00037 #include <osgUtil/Tessellator> 00038 #include <osg/MatrixTransform> 00039 #include <osgDB/FileNameUtils> 00040 #include <OpenThreads/Mutex> 00041 #include <OpenThreads/ScopedLock> 00042 00043 #include "FeatureStencilModelOptions" 00044 00045 using namespace osgEarth; 00046 using namespace osgEarth::Features; 00047 using namespace osgEarth::Symbology; 00048 using namespace osgEarth::Drivers; 00049 using namespace OpenThreads; 00050 00051 #define LC "[FeatureStencilModelSource] " 00052 00053 #define RENDER_BIN_START 100 00054 #define MAX_NUM_STYLES 100 00055 #define OFF_PROTECTED osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED 00056 00057 namespace 00058 { 00060 osg::Node* createColorNode( const osg::Vec4f& color ) 00061 { 00062 // make a full screen quad: 00063 osg::Geometry* quad = new osg::Geometry(); 00064 osg::Vec3Array* verts = new osg::Vec3Array(4); 00065 (*verts)[0].set( 0, 1, 0 ); 00066 (*verts)[1].set( 0, 0, 0 ); 00067 (*verts)[2].set( 1, 0, 0 ); 00068 (*verts)[3].set( 1, 1, 0 ); 00069 quad->setVertexArray( verts ); 00070 quad->addPrimitiveSet( new osg::DrawArrays( osg::PrimitiveSet::QUADS, 0, 4 ) ); 00071 osg::Vec4Array* colors = new osg::Vec4Array(1); 00072 (*colors)[0] = color; 00073 quad->setColorArray( colors ); 00074 quad->setColorBinding( osg::Geometry::BIND_OVERALL ); 00075 osg::Geode* quad_geode = new osg::Geode(); 00076 quad_geode->addDrawable( quad ); 00077 00078 osg::StateSet* quad_ss = quad->getOrCreateStateSet(); 00079 quad_ss->setMode( GL_CULL_FACE, OFF_PROTECTED ); 00080 quad_ss->setMode( GL_DEPTH_TEST, OFF_PROTECTED ); 00081 quad_ss->setMode( GL_LIGHTING, OFF_PROTECTED ); 00082 osg::MatrixTransform* abs = new osg::MatrixTransform(); 00083 abs->setReferenceFrame( osg::Transform::ABSOLUTE_RF ); 00084 abs->setMatrix( osg::Matrix::identity() ); 00085 abs->addChild( quad_geode ); 00086 00087 osg::Projection* proj = new osg::Projection(); 00088 proj->setMatrix( osg::Matrix::ortho(0, 1, 0, 1, 0, -1) ); 00089 proj->addChild( abs ); 00090 00091 proj->getOrCreateStateSet()->setMode( GL_BLEND, 1 ); 00092 00093 return proj; 00094 } 00095 00096 void tessellate( osg::Geometry* geom ) 00097 { 00098 osgUtil::Tessellator tess; 00099 tess.setTessellationType( osgUtil::Tessellator::TESS_TYPE_GEOMETRY ); 00100 tess.setWindingType( osgUtil::Tessellator::TESS_WINDING_ODD ); 00101 // tess.setWindingType( osgUtil::Tessellator::TESS_WINDING_POSITIVE ); 00102 tess.retessellatePolygons( *geom ); 00103 } 00104 00105 osg::Geode* 00106 createVolume(osgEarth::Symbology::Geometry* geom, 00107 double offset, 00108 double height, 00109 const FilterContext& context ) 00110 { 00111 if ( !geom ) return 0L; 00112 00113 int numRings = 0; 00114 00115 // start by offsetting the input data and counting the number of rings 00116 { 00117 osgEarth::Symbology::GeometryIterator i( geom ); 00118 while( i.hasMore() ) 00119 { 00120 osgEarth::Symbology::Geometry* part = i.next(); 00121 00122 if (offset != 0.0) 00123 { 00124 for( osg::Vec3dArray::iterator j = part->begin(); j != part->end(); j++ ) 00125 { 00126 if ( context.isGeocentric() ) 00127 { 00128 osg::Vec3d world = context.toWorld( *j ); 00129 // TODO: get the proper up vector; this is spherical.. or does it really matter for 00130 // stencil volumes? 00131 osg::Vec3d offset_vec = world; 00132 offset_vec.normalize(); 00133 *j = context.toLocal( world + offset_vec * offset ); //(*j) += offset_vec * offset; 00134 } 00135 else 00136 { 00137 (*j).z() += offset; 00138 } 00139 } 00140 } 00141 00142 // in the meantime, count the # of closed geoms. We will need to know this in 00143 // order to pre-allocate the proper # of verts. 00144 if ( part->getType() == osgEarth::Symbology::Geometry::TYPE_POLYGON || part->getType() == osgEarth::Symbology::Geometry::TYPE_RING ) 00145 { 00146 numRings++; 00147 } 00148 } 00149 } 00150 00151 // now, go thru and remove any coplanar segments from the geometry. The tesselator will 00152 // not work include a vert connecting two colinear segments in the tesselation, and this 00153 // will break the stenciling logic. 00154 #define PARALLEL_EPSILON 0.01 00155 osgEarth::Symbology::GeometryIterator i( geom ); 00156 while( i.hasMore() ) 00157 { 00158 osgEarth::Symbology::Geometry* part = i.next(); 00159 if ( part->size() >= 3 ) 00160 { 00161 osg::Vec3d prevVec = part->front() - part->back(); 00162 prevVec.normalize(); 00163 00164 for( osg::Vec3dArray::iterator j = part->begin(); part->size() >= 3 && j != part->end(); ) 00165 { 00166 osg::Vec3d& p0 = *j; 00167 osg::Vec3d& p1 = j+1 != part->end() ? *(j+1) : part->front(); 00168 osg::Vec3d vec = p1-p0; vec.normalize(); 00169 00170 // if the vectors are essentially parallel, remove the extraneous vertex. 00171 if ( (prevVec ^ vec).length() < PARALLEL_EPSILON ) 00172 { 00173 j = part->erase( j ); 00174 //OE_NOTICE << "removed colinear segment" << std::endl; 00175 } 00176 else 00177 { 00178 ++j; 00179 prevVec = vec; 00180 } 00181 } 00182 } 00183 } 00184 00185 00186 bool made_geom = true; 00187 const SpatialReference* srs = context.profile()->getSRS(); 00188 00189 // total up all the points so we can pre-allocate the vertex arrays. 00190 int num_cap_verts = geom->getTotalPointCount(); 00191 int num_wall_verts = 2 * (num_cap_verts + numRings); // add in numRings b/c we need to close each wall 00192 00193 osg::Geometry* walls = new osg::Geometry(); 00194 osg::Vec3Array* verts = new osg::Vec3Array( num_wall_verts ); 00195 walls->setVertexArray( verts ); 00196 00197 osg::Geometry* top_cap = new osg::Geometry(); 00198 osg::Vec3Array* top_verts = new osg::Vec3Array( num_cap_verts ); 00199 top_cap->setVertexArray( top_verts ); 00200 00201 osg::Geometry* bottom_cap = new osg::Geometry(); 00202 osg::Vec3Array* bottom_verts = new osg::Vec3Array( num_cap_verts ); 00203 bottom_cap->setVertexArray( bottom_verts ); 00204 00205 int wall_vert_ptr = 0; 00206 int top_vert_ptr = 0; 00207 int bottom_vert_ptr = 0; 00208 00209 //double target_len = height; 00210 00211 // now generate the extruded geometry. 00212 osgEarth::Symbology::GeometryIterator k( geom ); 00213 while( k.hasMore() ) 00214 { 00215 osgEarth::Symbology::Geometry* part = k.next(); 00216 00217 unsigned int wall_part_ptr = wall_vert_ptr; 00218 unsigned int top_part_ptr = top_vert_ptr; 00219 unsigned int bottom_part_ptr = bottom_vert_ptr; 00220 double part_len = 0.0; 00221 00222 GLenum prim_type = part->getType() == osgEarth::Symbology::Geometry::TYPE_POINTSET ? GL_LINES : GL_TRIANGLE_STRIP; 00223 00224 for( osg::Vec3dArray::const_iterator m = part->begin(); m != part->end(); ++m ) 00225 { 00226 osg::Vec3d extrude_vec; 00227 00228 if ( srs ) 00229 { 00230 osg::Vec3d m_world = context.toWorld( *m ); //*m * context.inverseReferenceFrame(); 00231 if ( context.isGeocentric() ) 00232 { 00233 osg::Vec3d p_vec = m_world; // todo: not exactly right; spherical 00234 00235 osg::Vec3d unit_vec = p_vec; 00236 unit_vec.normalize(); 00237 p_vec = p_vec + unit_vec*height; 00238 00239 extrude_vec = context.toLocal( p_vec ); //p_vec * context.referenceFrame(); 00240 } 00241 else 00242 { 00243 extrude_vec.set( m_world.x(), m_world.y(), height ); 00244 extrude_vec = context.toLocal( extrude_vec ); //extrude_vec * context.referenceFrame(); 00245 } 00246 } 00247 else 00248 { 00249 extrude_vec.set( m->x(), m->y(), height ); 00250 } 00251 00252 (*top_verts)[top_vert_ptr++] = extrude_vec; 00253 (*bottom_verts)[bottom_vert_ptr++] = *m; 00254 00255 part_len += wall_vert_ptr > (int)wall_part_ptr? 00256 (extrude_vec - (*verts)[wall_vert_ptr-2]).length() : 00257 0.0; 00258 00259 int p; 00260 00261 p = wall_vert_ptr++; 00262 (*verts)[p] = extrude_vec; 00263 00264 p = wall_vert_ptr++; 00265 (*verts)[p] = *m; 00266 } 00267 00268 // close the wall if it's a ring/poly: 00269 if ( part->getType() == osgEarth::Symbology::Geometry::TYPE_RING || part->getType() == osgEarth::Symbology::Geometry::TYPE_POLYGON ) 00270 { 00271 part_len += wall_vert_ptr > (int)wall_part_ptr? 00272 ((*verts)[wall_part_ptr] - (*verts)[wall_vert_ptr-2]).length() : 00273 0.0; 00274 00275 int p; 00276 00277 p = wall_vert_ptr++; 00278 (*verts)[p] = (*verts)[wall_part_ptr]; 00279 00280 p = wall_vert_ptr++; 00281 (*verts)[p] = (*verts)[wall_part_ptr+1]; 00282 } 00283 00284 walls->addPrimitiveSet( new osg::DrawArrays( 00285 prim_type, 00286 wall_part_ptr, wall_vert_ptr - wall_part_ptr ) ); 00287 00288 top_cap->addPrimitiveSet( new osg::DrawArrays( 00289 osg::PrimitiveSet::LINE_LOOP, 00290 top_part_ptr, top_vert_ptr - top_part_ptr ) ); 00291 00292 // reverse the bottom verts so the front face is down: 00293 std::reverse( bottom_verts->begin()+bottom_part_ptr, bottom_verts->begin()+bottom_vert_ptr ); 00294 00295 bottom_cap->addPrimitiveSet( new osg::DrawArrays( 00296 osg::PrimitiveSet::LINE_LOOP, 00297 bottom_part_ptr, bottom_vert_ptr - bottom_part_ptr ) ); 00298 } 00299 00300 // build solid surfaces for the caps: 00301 tessellate( top_cap ); 00302 tessellate( bottom_cap ); 00303 00304 osg::Geode* geode = new osg::Geode(); 00305 geode->addDrawable( walls ); 00306 geode->addDrawable( top_cap ); 00307 geode->addDrawable( bottom_cap ); 00308 00309 return geode; 00310 } 00311 00312 struct BuildData // : public osg::Referenced 00313 { 00314 //BuildData() { } 00315 BuildData( int renderBinStart ) : _renderBin( renderBinStart ) { } 00316 00317 typedef std::pair<std::string, osg::ref_ptr<StencilVolumeNode> > StyleGroup; 00318 int _renderBin; 00319 Threading::ReadWriteMutex _mutex; 00320 std::vector<StyleGroup> _styleGroups; // NOTE: DO NOT ACCESS without a mutex! 00321 00322 00323 bool getStyleNode( const std::string& styleName, StencilVolumeNode*& out_svn, bool useLock ) 00324 { 00325 if ( useLock ) 00326 { 00327 Threading::ScopedReadLock lock( _mutex ); 00328 return getStyleNodeWithoutLocking( styleName, out_svn ); 00329 } 00330 else 00331 { 00332 return getStyleNodeWithoutLocking( styleName, out_svn ); 00333 } 00334 } 00335 00336 private: 00337 bool getStyleNodeWithoutLocking( const std::string& styleName, StencilVolumeNode*& out_svn ) 00338 { 00339 for(std::vector<StyleGroup>::iterator i = _styleGroups.begin(); i != _styleGroups.end(); ++i ) 00340 { 00341 if( i->first == styleName ) 00342 { 00343 out_svn = i->second.get(); 00344 return true; 00345 } 00346 } 00347 return false; 00348 } 00349 }; 00350 00351 class StencilVolumeNodeFactory : public FeatureNodeFactory 00352 { 00353 protected: 00354 const FeatureStencilModelOptions _options; 00355 int _renderBinStart; 00356 BuildData _buildData; 00357 00358 public: 00359 StencilVolumeNodeFactory( const FeatureStencilModelOptions& options, int renderBinStart ) 00360 : _options(options), 00361 _buildData( renderBinStart ) 00362 { } 00363 00364 //override 00365 bool createOrUpdateNode( 00366 FeatureCursor* cursor, 00367 const Style& style, 00368 const FilterContext& context, 00369 osg::ref_ptr<osg::Node>& node ) 00370 { 00371 const MapInfo& mi = context.getSession()->getMapInfo(); 00372 00373 // A processing context to use locally 00374 FilterContext cx = context; 00375 00376 // make a working copy of the feature data. 00377 FeatureList featureList; 00378 cursor->fill( featureList ); 00379 00380 //for (FeatureList::const_iterator it = features.begin(); it != features.end(); ++it) 00381 // featureList.push_back(osg::clone((*it).get(),osg::CopyOp::DEEP_COPY_ALL)); 00382 00383 // establish the extrusion distance for the stencil volumes 00384 double extrusionDistance = 1; 00385 double densificationThreshold = 1.0; 00386 if ( _options.extrusionDistance().isSet() ) 00387 { 00388 extrusionDistance = *_options.extrusionDistance(); 00389 } 00390 else 00391 { 00392 if ( mi.isGeocentric() ) 00393 extrusionDistance = 300000.0; // meters geocentric 00394 else if ( mi.getProfile()->getSRS()->isGeographic() ) 00395 extrusionDistance = 5.0; // degrees-as-meters 00396 else 00397 extrusionDistance = 12000.0; // meters 00398 } 00399 00400 densificationThreshold = *_options.densificationThreshold(); 00401 00402 // Scan the geometry to see if it includes line data, since that will require buffering: 00403 bool hasLines = false; 00404 for( FeatureList::const_iterator i = featureList.begin(); i != featureList.end(); ++i ) 00405 { 00406 Feature* feature = (*i).get(); 00407 Geometry* geom = feature->getGeometry(); 00408 if ( geom && 00409 ( geom->getComponentType() == Geometry::TYPE_LINESTRING || 00410 geom->getComponentType() == Geometry::TYPE_RING ) ) 00411 { 00412 hasLines = true; 00413 break; 00414 } 00415 } 00416 00417 // If the geometry is lines, we need to buffer them before they will work with stenciling 00418 if ( hasLines ) 00419 { 00420 const LineSymbol* line = style.getSymbol<LineSymbol>(); 00421 if (line) 00422 { 00423 BufferFilter buffer; 00424 buffer.distance() = 0.5 * line->stroke()->width().value(); 00425 buffer.capStyle() = line->stroke()->lineCap().value(); 00426 cx = buffer.push( featureList, cx ); 00427 } 00428 } 00429 00430 // Transform them into the map's SRS, localizing the verts along the way: 00431 TransformFilter xform( mi.getProfile()->getSRS() ); 00432 xform.setMakeGeocentric( mi.isGeocentric() ); 00433 xform.setLocalizeCoordinates( !mi.isGeocentric() ); 00434 cx = xform.push( featureList, cx ); 00435 00436 if ( mi.isGeocentric() ) 00437 { 00438 // We need to make sure that on a round globe, the points are sampled such that 00439 // long segments follow the curvature of the earth. By the way, if a Buffer was 00440 // applied, that will also remove colinear segment points. Resample the points to 00441 // achieve a usable tesselation. 00442 ResampleFilter resample; 00443 resample.maxLength() = densificationThreshold; 00444 resample.minLength() = 0.0; 00445 resample.perturbationThreshold() = 0.1; 00446 cx = resample.push( featureList, cx ); 00447 } 00448 00449 // Extrude and cap the geometry in both directions to build a stencil volume: 00450 osg::Group* volumes = 0L; 00451 00452 for( FeatureList::iterator i = featureList.begin(); i != featureList.end(); ++i ) 00453 { 00454 Feature* feature = (*i).get(); 00455 Geometry* geom = feature->getGeometry(); 00456 osg::Node* volume = createVolume( geom, -extrusionDistance, extrusionDistance * 2.0, cx ); 00457 00458 if ( volume ) 00459 { 00460 if ( !volumes ) 00461 volumes = new osg::Group(); 00462 volumes->addChild( volume ); 00463 } 00464 } 00465 00466 if ( volumes ) 00467 { 00468 // Resolve the localizing reference frame if necessary: 00469 if ( cx.hasReferenceFrame() ) 00470 { 00471 osg::MatrixTransform* xform = new osg::MatrixTransform( cx.inverseReferenceFrame() ); 00472 xform->addChild( volumes ); 00473 volumes = xform; 00474 } 00475 00476 // Apply an LOD if required: 00477 if ( _options.minRange().isSet() || _options.maxRange().isSet() ) 00478 { 00479 osg::LOD* lod = new osg::LOD(); 00480 lod->addChild( volumes, _options.minRange().value(), _options.maxRange().value() ); 00481 volumes = lod; 00482 } 00483 00484 // Add the volumes to the appropriate style group. 00485 StencilVolumeNode* styleNode = dynamic_cast<StencilVolumeNode*>( getOrCreateStyleGroup( style, cx.getSession() ) ); 00486 styleNode->addVolumes( volumes ); 00487 } 00488 00489 node = 0L; // always return null, since we added our geom to the style group. 00490 return volumes != 0L; 00491 } 00492 00493 //override 00494 osg::Group* getOrCreateStyleGroup( const Style& style, Session* session ) 00495 { 00496 if ( _options.showVolumes() == true ) 00497 { 00498 return new osg::Group(); 00499 } 00500 else 00501 { 00502 StencilVolumeNode* styleNode = 0L; 00503 if ( !_buildData.getStyleNode(style.getName(), styleNode, true) ) 00504 { 00505 // did not find; write-lock it and try again (double-check pattern) 00506 Threading::ScopedWriteLock exclusiveLock( _buildData._mutex ); 00507 00508 if ( !_buildData.getStyleNode(style.getName(), styleNode, false) ) 00509 { 00510 OE_INFO << LC << "Create style group \"" << style.getName() << "\"" << std::endl; 00511 00512 styleNode = new StencilVolumeNode( *_options.mask(), *_options.inverted() ); 00513 00514 if ( _options.mask() == false ) 00515 { 00516 osg::Vec4f maskColor = osg::Vec4(1,1,0,1); 00517 00518 if (/*hasLines &&*/ style.getSymbol<LineSymbol>()) 00519 { 00520 const LineSymbol* line = style.getSymbol<LineSymbol>(); 00521 maskColor = line->stroke()->color(); 00522 } 00523 else 00524 { 00525 const PolygonSymbol* poly = style.getSymbol<PolygonSymbol>(); 00526 if (poly) 00527 maskColor = poly->fill()->color(); 00528 } 00529 styleNode->addChild( createColorNode(maskColor) ); 00530 00531 osg::StateSet* ss = styleNode->getOrCreateStateSet(); 00532 00533 ss->setMode( GL_LIGHTING, _options.enableLighting() == true? 00534 osg::StateAttribute::ON | osg::StateAttribute::PROTECTED : 00535 osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED ); 00536 } 00537 00538 _buildData._renderBin = styleNode->setBaseRenderBin( _buildData._renderBin ); 00539 _buildData._styleGroups.push_back( BuildData::StyleGroup(style.getName(), styleNode) ); 00540 } 00541 } 00542 00543 return styleNode; 00544 } 00545 } 00546 }; 00547 00548 00549 class FeatureStencilModelSource : public FeatureModelSource 00550 { 00551 public: 00552 FeatureStencilModelSource( const ModelSourceOptions& options, int renderBinStart ) : 00553 FeatureModelSource( options ), 00554 _options( options ), 00555 _renderBinStart( renderBinStart ) 00556 { 00557 // make sure we have stencil bits. Note, this only works before 00558 // a viewer gets created. You may need to allocate stencil bits 00559 // yourself if you make this object after realizing a viewer. 00560 if ( osg::DisplaySettings::instance()->getMinimumNumStencilBits() < 8 ) 00561 { 00562 osg::DisplaySettings::instance()->setMinimumNumStencilBits( 8 ); 00563 } 00564 } 00565 00566 //override 00567 virtual const FeatureModelSourceOptions& getFeatureModelOptions() const 00568 { 00569 return _options; 00570 } 00571 00572 //override 00573 void initialize( const std::string& referenceURI, const Map* map ) 00574 { 00575 FeatureModelSource::initialize( referenceURI, map ); 00576 } 00577 00578 //override 00579 FeatureNodeFactory* createFeatureNodeFactory() 00580 { 00581 return new StencilVolumeNodeFactory( _options, _renderBinStart ); 00582 } 00583 00584 protected: 00585 int _renderBinStart; 00586 const FeatureStencilModelOptions _options; 00587 }; 00588 } 00589 00590 00591 class FeatureStencilModelSourceDriver : public ModelSourceDriver 00592 { 00593 public: 00594 FeatureStencilModelSourceDriver() : 00595 _renderBinStart( RENDER_BIN_START ) 00596 { 00597 supportsExtension( "osgearth_model_feature_stencil", "osgEarth feature stencil plugin" ); 00598 } 00599 00600 virtual const char* className() 00601 { 00602 return "osgEarth Feature Stencil Model Plugin"; 00603 } 00604 00605 FeatureStencilModelSource* create( const Options* options ) 00606 { 00607 ScopedLock<Mutex> lock( _createMutex ); 00608 00609 FeatureStencilModelSource* obj = new FeatureStencilModelSource( 00610 getModelSourceOptions(options), 00611 _renderBinStart ); 00612 00613 _renderBinStart += MAX_NUM_STYLES*4; 00614 00615 return obj; 00616 } 00617 00618 virtual ReadResult readObject(const std::string& file_name, const Options* options) const 00619 { 00620 if ( !acceptsExtension(osgDB::getLowerCaseFileExtension( file_name ))) 00621 return ReadResult::FILE_NOT_HANDLED; 00622 00623 FeatureStencilModelSourceDriver* nonConstThis = const_cast<FeatureStencilModelSourceDriver*>(this); 00624 return nonConstThis->create( options ); 00625 } 00626 00627 protected: 00628 Mutex _createMutex; 00629 int _renderBinStart; 00630 }; 00631 00632 REGISTER_OSGPLUGIN(osgearth_model_feature_stencil, FeatureStencilModelSourceDriver)