void KML_Geometry::parseStyle( xml_node<>* node, KMLContext& cx, Style& style ) { _extrude = getValue(node, "extrude") == "1"; _tessellate = getValue(node, "tessellate") == "1"; std::string am = getValue(node, "altitudemode"); if ( am.empty() ) am = "clampToGround"; // default. bool isPoly = _geom.valid() && _geom->getComponentType() == Geometry::TYPE_POLYGON; bool isLine = _geom.valid() && _geom->getComponentType() == Geometry::TYPE_LINESTRING; // Resolve the correct altitude symbol. CLAMP_TO_TERRAIN is the default, but the // technique will depend on the geometry's type and setup. AltitudeSymbol* alt = style.getOrCreate<AltitudeSymbol>(); alt->clamping() = alt->CLAMP_TO_TERRAIN; // Compute some info about the geometry // Are all of the elevations zero? bool zeroElev = true; // Are all of the the elevations the same? bool sameElev = true; double maxElevation = -DBL_MAX; //if ( isPoly ) //compute maxElevation also for line strings for extrusion height { bool first = true; double e = 0.0; ConstGeometryIterator gi( _geom.get(), false ); while(gi.hasMore() ) { const Geometry* g = gi.next(); for( Geometry::const_iterator ji = g->begin(); ji != g->end(); ++ji ) { if ( !osg::equivalent(ji->z(), 0.0) ) zeroElev = false; if (first) { first = false; e = ji->z(); } else { if (!osg::equivalent(e, ji->z())) { sameElev = false; } } if (ji->z() > maxElevation) maxElevation = ji->z(); } } } // clamp to ground mode: if ( am == "clampToGround" ) { if ( _extrude ) { alt->technique() = alt->TECHNIQUE_MAP; } else if ( isPoly ) { alt->technique() = alt->TECHNIQUE_DRAPE; } else if ( isLine) { alt->technique() = alt->TECHNIQUE_DRAPE; // or could be GPU. } else // line or point { alt->technique() = alt->TECHNIQUE_SCENE; } // extrusion is not compatible with clampToGround. _extrude = false; } // "relativeToGround" means the coordinates' Z values are relative to the Z of the // terrain at that point. NOTE: GE flattens rooftops in this mode when extrude=1, // which seems wrong.. else if ( am == "relativeToGround" ) { alt->clamping() = alt->CLAMP_RELATIVE_TO_TERRAIN; if (isPoly) { // If all of the verts have the same elevation then assume that it should be clamped at the centroid and not per vertex. if (sameElev) { alt->binding() = AltitudeSymbol::BINDING_CENTROID; } if ( _extrude ) { alt->technique() = alt->TECHNIQUE_MAP; } else { alt->technique() = alt->TECHNIQUE_SCENE; if ( zeroElev ) { alt->clamping() = alt->CLAMP_TO_TERRAIN; alt->technique() = alt->TECHNIQUE_DRAPE; } } } } // "absolute" means to treat the Z values as-is else if ( am == "absolute" ) { alt->clamping() = AltitudeSymbol::CLAMP_NONE; } if ( _extrude ) { ExtrusionSymbol* es = style.getOrCreate<ExtrusionSymbol>(); es->flatten() = false; if (*alt->clamping() == AltitudeSymbol::CLAMP_NONE) { // Set the height to the max elevation + the approx depth of the mariana trench so that it will extend low enough to be always go to the surface of the earth. // This lets us avoid clamping absolute absolute extruded polygons completely. es->height() = -(maxElevation + 11100.0); } } else { // remove polystyle since it doesn't apply to non-extruded lines and points if ( !isPoly ) { style.remove<PolygonSymbol>(); } } }
osg::Geode* BuildGeometryFilter::processPoints(FeatureList& features, FilterContext& context) { osg::Geode* geode = new osg::Geode(); bool makeECEF = false; const SpatialReference* featureSRS = 0L; const SpatialReference* outputSRS = 0L; // set up referencing information: if ( context.isGeoreferenced() ) { //makeECEF = context.getSession()->getMapInfo().isGeocentric(); featureSRS = context.extent()->getSRS(); outputSRS = context.getOutputSRS(); makeECEF = outputSRS->isGeographic(); } for( FeatureList::iterator f = features.begin(); f != features.end(); ++f ) { Feature* input = f->get(); GeometryIterator parts( input->getGeometry(), true ); while( parts.hasMore() ) { Geometry* part = parts.next(); // extract the required point symbol; bail out if not found. const PointSymbol* point = input->style().isSet() && input->style()->has<PointSymbol>() ? input->style()->get<PointSymbol>() : _style.get<PointSymbol>(); if ( !point ) continue; // collect all the pre-transformation HAT (Z) values. osg::ref_ptr<osg::FloatArray> hats = new osg::FloatArray(); hats->reserve( part->size() ); for(Geometry::const_iterator i = part->begin(); i != part->end(); ++i ) hats->push_back( i->z() ); // resolve the color: osg::Vec4f primaryColor = point->fill()->color(); osg::ref_ptr<osg::Geometry> osgGeom = new osg::Geometry(); osgGeom->setUseVertexBufferObjects( true ); osgGeom->setUseDisplayList( false ); // embed the feature name if requested. Warning: blocks geometry merge optimization! if ( _featureNameExpr.isSet() ) { const std::string& name = input->eval( _featureNameExpr.mutable_value(), &context ); osgGeom->setName( name ); } // build the geometry: osg::Vec3Array* allPoints = new osg::Vec3Array(); transformAndLocalize( part->asVector(), featureSRS, allPoints, outputSRS, _world2local, makeECEF ); osgGeom->addPrimitiveSet( new osg::DrawArrays(GL_POINTS, 0, allPoints->getNumElements()) ); osgGeom->setVertexArray( allPoints ); if ( input->style().isSet() ) { //TODO: re-evaluate this. does it hinder geometry merging? applyPointSymbology( osgGeom->getOrCreateStateSet(), point ); } // assign the primary color (PER_VERTEX required for later optimization) osg::Vec4Array* colors = new osg::Vec4Array; colors->assign( osgGeom->getVertexArray()->getNumElements(), primaryColor ); osgGeom->setColorArray( colors ); osgGeom->setColorBinding( osg::Geometry::BIND_PER_VERTEX ); geode->addDrawable( osgGeom ); // record the geometry's primitive set(s) in the index: if ( context.featureIndex() ) context.featureIndex()->tagDrawable( osgGeom, input ); // install clamping attributes if necessary if (_style.has<AltitudeSymbol>() && _style.get<AltitudeSymbol>()->technique() == AltitudeSymbol::TECHNIQUE_GPU) { Clamping::applyDefaultClampingAttrs( osgGeom, input->getDouble("__oe_verticalOffset", 0.0) ); Clamping::setHeights( osgGeom, hats.get() ); } } } return geode; }
osg::Geode* BuildGeometryFilter::processLines(FeatureList& features, FilterContext& context) { osg::Geode* geode = new osg::Geode(); bool makeECEF = false; const SpatialReference* featureSRS = 0L; const SpatialReference* outputSRS = 0L; // set up referencing information: if ( context.isGeoreferenced() ) { //makeECEF = context.getSession()->getMapInfo().isGeocentric(); featureSRS = context.extent()->getSRS(); outputSRS = context.getOutputSRS(); makeECEF = outputSRS->isGeographic(); } for( FeatureList::iterator f = features.begin(); f != features.end(); ++f ) { Feature* input = f->get(); // extract the required line symbol; bail out if not found. const LineSymbol* line = input->style().isSet() && input->style()->has<LineSymbol>() ? input->style()->get<LineSymbol>() : _style.get<LineSymbol>(); if ( !line ) continue; // run a symbol script if present. if ( line->script().isSet() ) { StringExpression temp( line->script().get() ); input->eval( temp, &context ); } GeometryIterator parts( input->getGeometry(), true ); while( parts.hasMore() ) { Geometry* part = parts.next(); // skip invalid geometry for lines. if ( part->size() < 2 ) continue; // collect all the pre-transformation HAT (Z) values. osg::ref_ptr<osg::FloatArray> hats = new osg::FloatArray(); hats->reserve( part->size() ); for(Geometry::const_iterator i = part->begin(); i != part->end(); ++i ) hats->push_back( i->z() ); // if the underlying geometry is a ring (or a polygon), use a line loop; otherwise // use a line strip. GLenum primMode = dynamic_cast<Ring*>(part) ? GL_LINE_LOOP : GL_LINE_STRIP; // resolve the color: osg::Vec4f primaryColor = line->stroke()->color(); osg::ref_ptr<osg::Geometry> osgGeom = new osg::Geometry(); osgGeom->setUseVertexBufferObjects( true ); osgGeom->setUseDisplayList( false ); // embed the feature name if requested. Warning: blocks geometry merge optimization! if ( _featureNameExpr.isSet() ) { const std::string& name = input->eval( _featureNameExpr.mutable_value(), &context ); osgGeom->setName( name ); } // build the geometry: osg::Vec3Array* allPoints = new osg::Vec3Array(); transformAndLocalize( part->asVector(), featureSRS, allPoints, outputSRS, _world2local, makeECEF ); osgGeom->addPrimitiveSet( new osg::DrawArrays(primMode, 0, allPoints->getNumElements()) ); osgGeom->setVertexArray( allPoints ); if ( input->style().isSet() ) { //TODO: re-evaluate this. does it hinder geometry merging? applyLineSymbology( osgGeom->getOrCreateStateSet(), line ); } // subdivide the mesh if necessary to conform to an ECEF globe; // but if the tessellation is set to zero, or if the style specifies a // tessellation size, skip this step. if ( makeECEF && !line->tessellation().isSetTo(0) && !line->tessellationSize().isSet() ) { double threshold = osg::DegreesToRadians( *_maxAngle_deg ); OE_DEBUG << "Running mesh subdivider with threshold " << *_maxAngle_deg << std::endl; MeshSubdivider ms( _world2local, _local2world ); //ms.setMaxElementsPerEBO( INT_MAX ); if ( input->geoInterp().isSet() ) ms.run( *osgGeom, threshold, *input->geoInterp() ); else ms.run( *osgGeom, threshold, *_geoInterp ); } // assign the primary color (PER_VERTEX required for later optimization) osg::Vec4Array* colors = new osg::Vec4Array; colors->assign( osgGeom->getVertexArray()->getNumElements(), primaryColor ); osgGeom->setColorArray( colors ); osgGeom->setColorBinding( osg::Geometry::BIND_PER_VERTEX ); geode->addDrawable( osgGeom ); // record the geometry's primitive set(s) in the index: if ( context.featureIndex() ) context.featureIndex()->tagDrawable( osgGeom, input ); // install clamping attributes if necessary if (_style.has<AltitudeSymbol>() && _style.get<AltitudeSymbol>()->technique() == AltitudeSymbol::TECHNIQUE_GPU) { Clamping::applyDefaultClampingAttrs( osgGeom, input->getDouble("__oe_verticalOffset", 0.0) ); Clamping::setHeights( osgGeom, hats.get() ); } } } return geode; }
osg::Geode* BuildGeometryFilter::processPolygonizedLines(FeatureList& features, bool twosided, FilterContext& context) { osg::Geode* geode = new osg::Geode(); // establish some referencing bool makeECEF = false; const SpatialReference* featureSRS = 0L; const SpatialReference* outputSRS = 0L; if ( context.isGeoreferenced() ) { //makeECEF = context.getSession()->getMapInfo().isGeocentric(); featureSRS = context.extent()->getSRS(); outputSRS = context.getOutputSRS(); makeECEF = outputSRS->isGeographic(); //mapSRS = context.getSession()->getMapInfo().getProfile()->getSRS(); } // iterate over all features. for( FeatureList::iterator i = features.begin(); i != features.end(); ++i ) { Feature* input = i->get(); // extract the required line symbol; bail out if not found. const LineSymbol* line = input->style().isSet() && input->style()->has<LineSymbol>() ? input->style()->get<LineSymbol>() : _style.get<LineSymbol>(); if ( !line ) continue; // run a symbol script if present. if ( line->script().isSet() ) { StringExpression temp( line->script().get() ); input->eval( temp, &context ); } // The operator we'll use to make lines into polygons. PolygonizeLinesOperator polygonizer( *line->stroke() ); // iterate over all the feature's geometry parts. We will treat // them as lines strings. GeometryIterator parts( input->getGeometry(), true ); while( parts.hasMore() ) { Geometry* part = parts.next(); // if the underlying geometry is a ring (or a polygon), close it so the // polygonizer will generate a closed loop. Ring* ring = dynamic_cast<Ring*>(part); if ( ring ) ring->close(); // skip invalid geometry if ( part->size() < 2 ) continue; // collect all the pre-transformation HAT (Z) values. osg::ref_ptr<osg::FloatArray> hats = new osg::FloatArray(); hats->reserve( part->size() ); for(Geometry::const_iterator i = part->begin(); i != part->end(); ++i ) hats->push_back( i->z() ); // transform the geometry into the target SRS and localize it about // a local reference point. osg::ref_ptr<osg::Vec3Array> verts = new osg::Vec3Array(); osg::ref_ptr<osg::Vec3Array> normals = new osg::Vec3Array(); transformAndLocalize( part->asVector(), featureSRS, verts.get(), normals.get(), outputSRS, _world2local, makeECEF ); // turn the lines into polygons. osg::Geometry* geom = polygonizer( verts.get(), normals.get(), twosided ); if ( geom ) { geode->addDrawable( geom ); } // record the geometry's primitive set(s) in the index: if ( context.featureIndex() ) context.featureIndex()->tagDrawable( geom, input ); // install clamping attributes if necessary if (_style.has<AltitudeSymbol>() && _style.get<AltitudeSymbol>()->technique() == AltitudeSymbol::TECHNIQUE_GPU) { Clamping::applyDefaultClampingAttrs( geom, input->getDouble("__oe_verticalOffset", 0.0) ); Clamping::setHeights( geom, hats.get() ); } } polygonizer.installShaders( geode ); } return geode; }
osg::Geode* BuildGeometryFilter::processPolygons(FeatureList& features, FilterContext& context) { osg::Geode* geode = new osg::Geode(); bool makeECEF = false; const SpatialReference* featureSRS = 0L; //const SpatialReference* mapSRS = 0L; const SpatialReference* outputSRS = 0L; // set up the reference system info: if ( context.isGeoreferenced() ) { //makeECEF = context.getSession()->getMapInfo().isGeocentric(); featureSRS = context.extent()->getSRS(); outputSRS = context.getOutputSRS(); //getSession()->getMapInfo().getProfile()->getSRS(); makeECEF = context.getOutputSRS()->isGeographic(); } for( FeatureList::iterator f = features.begin(); f != features.end(); ++f ) { Feature* input = f->get(); // access the polygon symbol, and bail out if there isn't one const PolygonSymbol* poly = input->style().isSet() && input->style()->has<PolygonSymbol>() ? input->style()->get<PolygonSymbol>() : _style.get<PolygonSymbol>(); if ( !poly ) { OE_TEST << LC << "Discarding feature with no poly symbol\n"; continue; } // run a symbol script if present. if ( poly->script().isSet() ) { StringExpression temp( poly->script().get() ); input->eval( temp, &context ); } GeometryIterator parts( input->getGeometry(), false ); while( parts.hasMore() ) { Geometry* part = parts.next(); part->removeDuplicates(); // skip geometry that is invalid for a polygon if ( part->size() < 3 ) { OE_TEST << LC << "Discarding illegal part (less than 3 verts)\n"; continue; } // resolve the color: osg::Vec4f primaryColor = poly->fill()->color(); osg::ref_ptr<osg::Geometry> osgGeom = new osg::Geometry(); osgGeom->setUseVertexBufferObjects( true ); osgGeom->setUseDisplayList( false ); // are we embedding a feature name? if ( _featureNameExpr.isSet() ) { const std::string& name = input->eval( _featureNameExpr.mutable_value(), &context ); osgGeom->setName( name ); } // compute localizing matrices or use globals osg::Matrixd w2l, l2w; if (makeECEF) { osgEarth::GeoExtent featureExtent(featureSRS); featureExtent.expandToInclude(part->getBounds()); computeLocalizers(context, featureExtent, w2l, l2w); } else { w2l = _world2local; l2w = _local2world; } // collect all the pre-transformation HAT (Z) values. osg::ref_ptr<osg::FloatArray> hats = new osg::FloatArray(); hats->reserve( part->size() ); for(Geometry::const_iterator i = part->begin(); i != part->end(); ++i ) hats->push_back( i->z() ); // build the geometry: tileAndBuildPolygon(part, featureSRS, outputSRS, makeECEF, true, osgGeom, w2l); //buildPolygon(part, featureSRS, mapSRS, makeECEF, true, osgGeom, w2l); osg::Vec3Array* allPoints = static_cast<osg::Vec3Array*>(osgGeom->getVertexArray()); if (allPoints && allPoints->size() > 0) { // subdivide the mesh if necessary to conform to an ECEF globe: if ( makeECEF ) { //convert back to world coords for( osg::Vec3Array::iterator i = allPoints->begin(); i != allPoints->end(); ++i ) { osg::Vec3d v(*i); v = v * l2w; v = v * _world2local; (*i)._v[0] = v[0]; (*i)._v[1] = v[1]; (*i)._v[2] = v[2]; } double threshold = osg::DegreesToRadians( *_maxAngle_deg ); OE_TEST << "Running mesh subdivider with threshold " << *_maxAngle_deg << std::endl; MeshSubdivider ms( _world2local, _local2world ); if ( input->geoInterp().isSet() ) ms.run( *osgGeom, threshold, *input->geoInterp() ); else ms.run( *osgGeom, threshold, *_geoInterp ); } // assign the primary color array. PER_VERTEX required in order to support // vertex optimization later unsigned count = osgGeom->getVertexArray()->getNumElements(); osg::Vec4Array* colors = new osg::Vec4Array; colors->assign( count, primaryColor ); osgGeom->setColorArray( colors ); osgGeom->setColorBinding( osg::Geometry::BIND_PER_VERTEX ); geode->addDrawable( osgGeom ); // record the geometry's primitive set(s) in the index: if ( context.featureIndex() ) context.featureIndex()->tagDrawable( osgGeom, input ); // install clamping attributes if necessary if (_style.has<AltitudeSymbol>() && _style.get<AltitudeSymbol>()->technique() == AltitudeSymbol::TECHNIQUE_GPU) { Clamping::applyDefaultClampingAttrs( osgGeom, input->getDouble("__oe_verticalOffset", 0.0) ); } } else { OE_TEST << LC << "Oh no. buildAndTilePolygon returned nothing.\n"; } } } OE_TEST << LC << "Num drawables = " << geode->getNumDrawables() << "\n"; return geode; }
void KML_Placemark::build( const Config& conf, KMLContext& cx ) { Style masterStyle; if ( conf.hasValue("styleurl") ) { // process a "stylesheet" style const Style* ref_style = cx._sheet->getStyle( conf.value("styleurl"), false ); if ( ref_style ) masterStyle = *ref_style; } else if ( conf.hasChild("style") ) { // process an "inline" style KML_Style kmlStyle; kmlStyle.scan( conf.child("style"), cx ); masterStyle = cx._activeStyle; } // parse the geometry. the placemark must have geometry to be valid. The // geometry parse may optionally specify an altitude mode as well. KML_Geometry geometry; geometry.build(conf, cx, masterStyle); Geometry* allGeom = geometry._geom.get(); if ( allGeom ) { GeometryIterator giter( allGeom, false ); while( giter.hasMore() ) { Geometry* geom = giter.next(); Style style = masterStyle; // KML's default altitude mode is clampToGround. AltitudeMode altMode = ALTMODE_RELATIVE; AltitudeSymbol* altSym = style.get<AltitudeSymbol>(); if ( !altSym ) { altSym = style.getOrCreate<AltitudeSymbol>(); altSym->clamping() = AltitudeSymbol::CLAMP_RELATIVE_TO_TERRAIN; altSym->technique() = AltitudeSymbol::TECHNIQUE_SCENE; } else if ( !altSym->clamping().isSetTo(AltitudeSymbol::CLAMP_RELATIVE_TO_TERRAIN) ) { altMode = ALTMODE_ABSOLUTE; } if ( geom && geom->getTotalPointCount() > 0 ) { GeoPoint position(cx._srs.get(), geom->getBounds().center(), altMode); bool isPoly = geom->getComponentType() == Geometry::TYPE_POLYGON; bool isPoint = geom->getComponentType() == Geometry::TYPE_POINTSET; // check for symbols. ModelSymbol* model = style.get<ModelSymbol>(); IconSymbol* icon = style.get<IconSymbol>(); TextSymbol* text = style.get<TextSymbol>(); if ( !text && cx._options->defaultTextSymbol().valid() ) text = cx._options->defaultTextSymbol().get(); // the annotation name: std::string name = conf.hasValue("name") ? conf.value("name") : ""; if ( text && !name.empty() ) { text->content()->setLiteral( name ); } AnnotationNode* featureNode = 0L; AnnotationNode* iconNode = 0L; AnnotationNode* modelNode = 0L; // one coordinate? It's a place marker or a label. if ( model || icon || text || geom->getTotalPointCount() == 1 ) { // load up the default icon if there we don't have one. if ( !model && !icon ) { icon = cx._options->defaultIconSymbol().get(); if ( icon ) style.add( icon ); } // if there's a model, render that - models do NOT get labels. if ( model ) { ModelNode* node = new ModelNode( cx._mapNode, style, cx._dbOptions ); node->setPosition( position ); if ( cx._options->modelScale() != 1.0f ) { float s = *cx._options->modelScale(); node->setScale( osg::Vec3f(s,s,s) ); } if ( !cx._options->modelRotation()->zeroRotation() ) { node->setLocalRotation( *cx._options->modelRotation() ); } modelNode = node; } else if ( !text && !name.empty() ) { text = style.getOrCreate<TextSymbol>(); text->content()->setLiteral( name ); } if ( icon ) { iconNode = new PlaceNode( cx._mapNode, position, style, cx._dbOptions ); } else if ( !model && text && !name.empty() ) { // note: models do not get labels. iconNode = new LabelNode( cx._mapNode, position, style ); } } // multiple coords? feature: if ( geom->getTotalPointCount() > 1 ) { ExtrusionSymbol* extruded = style.get<ExtrusionSymbol>(); AltitudeSymbol* altitude = style.get<AltitudeSymbol>(); // Remove symbols that we have already processed so the geometry // compiler doesn't get confused. if ( model ) style.removeSymbol( model ); if ( icon ) style.removeSymbol( icon ); if ( text ) style.removeSymbol( text ); // analyze the data; if the Z coords are all 0.0, enable draping. if ( /*isPoly &&*/ !extruded && altitude && altitude->clamping() != AltitudeSymbol::CLAMP_TO_TERRAIN ) { bool zeroElev = true; ConstGeometryIterator gi( geom, false ); while( zeroElev == true && gi.hasMore() ) { const Geometry* g = gi.next(); for( Geometry::const_iterator ji = g->begin(); ji != g->end() && zeroElev == true; ++ji ) { if ( !osg::equivalent(ji->z(), 0.0) ) zeroElev = false; } } if ( zeroElev ) { altitude->clamping() = AltitudeSymbol::CLAMP_TO_TERRAIN; altitude->technique() = AltitudeSymbol::TECHNIQUE_GPU; } } // Make a feature node; drape if we're not extruding. bool draped = isPoly && !extruded && (!altitude || altitude->clamping() == AltitudeSymbol::CLAMP_TO_TERRAIN); if ( draped && style.get<LineSymbol>() && !style.get<PolygonSymbol>() ) { draped = false; } // turn off the clamping if we're draping. if ( draped && altitude ) { altitude->technique() = AltitudeSymbol::TECHNIQUE_DRAPE; } GeometryCompilerOptions compilerOptions; // Check for point-model substitution: if ( style.has<ModelSymbol>() ) { compilerOptions.instancing() = true; } Feature* feature = new Feature(geom, cx._srs.get(), style); featureNode = new FeatureNode( cx._mapNode, feature, draped, compilerOptions ); } // assemble the results: if ( (iconNode || modelNode) && featureNode ) { osg::Group* group = new osg::Group(); group->addChild( featureNode ); if ( iconNode ) group->addChild( iconNode ); if ( modelNode ) group->addChild( modelNode ); cx._groupStack.top()->addChild( group ); if ( iconNode && cx._options->declutter() == true ) Decluttering::setEnabled( iconNode->getOrCreateStateSet(), true ); if ( iconNode ) KML_Feature::build( conf, cx, iconNode ); if ( modelNode ) KML_Feature::build( conf, cx, modelNode ); if ( featureNode ) KML_Feature::build( conf, cx, featureNode ); } else { if ( iconNode ) { if ( cx._options->iconAndLabelGroup().valid() ) { cx._options->iconAndLabelGroup()->addChild( iconNode ); } else { cx._groupStack.top()->addChild( iconNode ); if ( cx._options->declutter() == true ) Decluttering::setEnabled( iconNode->getOrCreateStateSet(), true ); } KML_Feature::build( conf, cx, iconNode ); } if ( modelNode ) { cx._groupStack.top()->addChild( modelNode ); KML_Feature::build( conf, cx, modelNode ); } if ( featureNode ) { cx._groupStack.top()->addChild( featureNode ); KML_Feature::build( conf, cx, featureNode ); } } } } } }