FilterContext ClampFilter::push( FeatureList& features, const FilterContext& cx ) { const Session* session = cx.getSession(); if ( !session ) { OE_WARN << LC << "No session - session is required for elevation clamping" << std::endl; return cx; } // the map against which we'll be doing elevation clamping MapFrame mapf = session->createMapFrame( Map::ELEVATION_LAYERS ); const SpatialReference* mapSRS = mapf.getProfile()->getSRS(); const SpatialReference* featureSRS = cx.profile()->getSRS(); bool isGeocentric = session->getMapInfo().isGeocentric(); // establish an elevation query interface based on the features' SRS. ElevationQuery eq( mapf ); for( FeatureList::iterator i = features.begin(); i != features.end(); ++i ) { Feature* feature = i->get(); GeometryIterator gi( feature->getGeometry() ); while( gi.hasMore() ) { Geometry* geom = gi.next(); if ( isGeocentric ) { // convert to map coords: cx.toWorld( geom ); mapSRS->transformFromECEF( geom ); // populate the elevations: eq.getElevations( geom, mapSRS ); // convert back to geocentric: mapSRS->transformToECEF( geom ); cx.toLocal( geom ); } else { // clamps the entire array to the highest available resolution. eq.getElevations( geom, featureSRS ); } } } return cx; }
void FeaturesToNodeFilter::computeLocalizers( const FilterContext& context ) { if ( context.isGeoreferenced() ) { if ( context.getSession()->getMapInfo().isGeocentric() ) { const SpatialReference* geogSRS = context.profile()->getSRS()->getGeographicSRS(); GeoExtent geodExtent = context.extent()->transform( geogSRS ); if ( geodExtent.width() < 180.0 ) { osg::Vec3d centroid, centroidECEF; geodExtent.getCentroid( centroid.x(), centroid.y() ); geogSRS->transform( centroid, geogSRS->getECEF(), centroidECEF ); geogSRS->getECEF()->createLocalToWorld( centroidECEF, _local2world ); _world2local.invert( _local2world ); } } else // projected { if ( context.extent().isSet() ) { osg::Vec3d centroid; context.extent()->getCentroid(centroid.x(), centroid.y()); context.extent()->getSRS()->transform( centroid, context.getSession()->getMapInfo().getProfile()->getSRS(), centroid ); _world2local.makeTranslate( -centroid ); _local2world.invert( _world2local ); } } } }
void FeaturesToNodeFilter::computeLocalizers( const FilterContext& context, const osgEarth::GeoExtent &extent, osg::Matrixd &out_w2l, osg::Matrixd &out_l2w ) { if ( context.isGeoreferenced() ) { if ( context.getSession()->getMapInfo().isGeocentric() ) { const SpatialReference* geogSRS = context.profile()->getSRS()->getGeographicSRS(); GeoExtent geodExtent = extent.transform( geogSRS ); if ( geodExtent.width() < 180.0 ) { osg::Vec3d centroid, centroidECEF; geodExtent.getCentroid( centroid.x(), centroid.y() ); geogSRS->transform( centroid, geogSRS->getECEF(), centroidECEF ); geogSRS->getECEF()->createLocalToWorld( centroidECEF, out_l2w ); out_w2l.invert( out_l2w ); } } else // projected { if ( extent.isValid() ) { osg::Vec3d centroid; extent.getCentroid(centroid.x(), centroid.y()); extent.getSRS()->transform( centroid, context.getSession()->getMapInfo().getProfile()->getSRS(), centroid ); out_w2l.makeTranslate( -centroid ); out_l2w.invert( out_w2l ); } } } }
FilterContext AltitudeFilter::push( FeatureList& features, FilterContext& cx ) { bool clamp = _altitude.valid() && _altitude->clamping() != AltitudeSymbol::CLAMP_NONE && cx.getSession() != 0L && cx.profile() != 0L; if ( clamp ) pushAndClamp( features, cx ); else pushAndDontClamp( features, cx ); return cx; }
v8::Handle<v8::Value> JSFilterContext::PropertyCallback(v8::Local<v8::String> name, const v8::AccessorInfo& info) { FilterContext* context = V8Util::UnwrapObject<FilterContext>(info.Holder()); v8::String::Utf8Value utf8_value(name); std::string prop(*utf8_value); if (!context || prop.empty()) return v8::Handle<v8::Value>(); if (prop == "session") return JSSession::WrapSession(const_cast<Session*>(context->getSession())); if (prop == "profile") return JSFeatureProfile::WrapFeatureProfile(const_cast<FeatureProfile*>(context->profile().get())); if (prop == "extent" && context->extent().isSet()) return JSGeoExtent::WrapGeoExtent(const_cast<osgEarth::GeoExtent*>(&context->extent().get())); //if (prop == "geocentric") // return v8::Boolean::New(context->isGeocentric()); return v8::Handle<v8::Value>(); }
bool BuildGeometryFilter::process( FeatureList& features, const FilterContext& context ) { bool makeECEF = false; const SpatialReference* featureSRS = 0L; const SpatialReference* mapSRS = 0L; if ( context.isGeoreferenced() ) { makeECEF = context.getSession()->getMapInfo().isGeocentric(); featureSRS = context.extent()->getSRS(); mapSRS = context.getSession()->getMapInfo().getProfile()->getSRS(); } for( FeatureList::iterator f = features.begin(); f != features.end(); ++f ) { Feature* input = f->get(); GeometryIterator parts( input->getGeometry(), false ); while( parts.hasMore() ) { Geometry* part = parts.next(); // skip empty geometry if ( part->size() == 0 ) continue; const Style& myStyle = input->style().isSet() ? *input->style() : _style; bool setLinePropsHere = input->style().isSet(); // otherwise it will be set globally, we assume float width = 1.0f; bool hasPolyOutline = false; const PointSymbol* pointSymbol = myStyle.get<PointSymbol>(); const LineSymbol* lineSymbol = myStyle.get<LineSymbol>(); const PolygonSymbol* polySymbol = myStyle.get<PolygonSymbol>(); // resolve the geometry type from the component type and the symbology: Geometry::Type renderType = Geometry::TYPE_UNKNOWN; // First priority is a matching part type and symbol: if ( polySymbol != 0L && part->getType() == Geometry::TYPE_POLYGON ) { renderType = Geometry::TYPE_POLYGON; } else if ( lineSymbol != 0L && part->isLinear() ) { renderType = part->getType(); } else if ( pointSymbol != 0L && part->getType() == Geometry::TYPE_POINTSET ) { renderType = Geometry::TYPE_POINTSET; } // Second priority is the symbol: else if ( polySymbol != 0L ) { renderType = Geometry::TYPE_POLYGON; } else if ( lineSymbol != 0L ) { if ( part->getType() == Geometry::TYPE_POLYGON ) renderType = Geometry::TYPE_RING; else renderType = Geometry::TYPE_LINESTRING; } else if ( pointSymbol != 0L ) { renderType = Geometry::TYPE_POINTSET; } // No symbol? just use the geometry type. else { renderType = part->getType(); } // validate the geometry: if ( renderType == Geometry::TYPE_POLYGON && part->size() < 3 ) continue; else if ( (renderType == Geometry::TYPE_LINESTRING || renderType == Geometry::TYPE_RING) && part->size() < 2 ) continue; // resolve the color: osg::Vec4f primaryColor = polySymbol ? osg::Vec4f(polySymbol->fill()->color()) : lineSymbol ? osg::Vec4f(lineSymbol->stroke()->color()) : pointSymbol ? osg::Vec4f(pointSymbol->fill()->color()) : osg::Vec4f(1,1,1,1); osg::Geometry* osgGeom = new osg::Geometry(); osgGeom->setUseVertexBufferObjects( _useVertexBufferObjects.value() ); if ( _featureNameExpr.isSet() ) { const std::string& name = input->eval( _featureNameExpr.mutable_value(), &context ); osgGeom->setName( name ); } // build the geometry: osg::Vec3Array* allPoints = 0L; if ( renderType == Geometry::TYPE_POLYGON ) { buildPolygon(part, featureSRS, mapSRS, makeECEF, true, osgGeom); allPoints = static_cast<osg::Vec3Array*>( osgGeom->getVertexArray() ); } else { // line or point geometry GLenum primMode = renderType == Geometry::TYPE_LINESTRING ? GL_LINE_STRIP : renderType == Geometry::TYPE_RING ? GL_LINE_LOOP : GL_POINTS; allPoints = new osg::Vec3Array(); transformAndLocalize( part->asVector(), featureSRS, allPoints, mapSRS, _world2local, makeECEF ); osgGeom->addPrimitiveSet( new osg::DrawArrays( primMode, 0, part->size() ) ); osgGeom->setVertexArray( allPoints ); applyLineAndPointSymbology( osgGeom->getOrCreateStateSet(), lineSymbol, pointSymbol ); if ( primMode == GL_POINTS && allPoints->size() == 1 ) { const osg::Vec3d& center = (*allPoints)[0]; osgGeom->setInitialBound( osg::BoundingBox(center-osg::Vec3(.5,.5,.5), center+osg::Vec3(.5,.5,.5)) ); } } if (allPoints->getVertexBufferObject()) allPoints->getVertexBufferObject()->setUsage(GL_STATIC_DRAW_ARB); // subdivide the mesh if necessary to conform to an ECEF globe: if ( makeECEF && renderType != Geometry::TYPE_POINTSET ) { // check for explicit tessellation disable: const LineSymbol* line = _style.get<LineSymbol>(); bool disableTess = line && line->tessellation().isSetTo(0); if ( makeECEF && !disableTess ) { 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: #if USE_SINGLE_COLOR osg::Vec4Array* colors = new osg::Vec4Array( 1 ); (*colors)[0] = primaryColor; osgGeom->setColorBinding( osg::Geometry::BIND_OVERALL ); #else osg::Vec4Array* colors = new osg::Vec4Array( osgGeom->getVertexArray()->getNumElements() ); //allPoints->size() ); for(unsigned c=0; c<colors->size(); ++c) (*colors)[c] = primaryColor; osgGeom->setColorBinding( osg::Geometry::BIND_PER_VERTEX ); #endif osgGeom->setColorArray( colors ); _geode->addDrawable( osgGeom ); // record the geometry's primitive set(s) in the index: if ( context.featureIndex() ) context.featureIndex()->tagPrimitiveSets( osgGeom, input ); // build secondary geometry, if necessary (polygon outlines) if ( renderType == Geometry::TYPE_POLYGON && lineSymbol ) { // polygon offset on the poly so the outline doesn't z-fight osgGeom->getOrCreateStateSet()->setAttributeAndModes( new osg::PolygonOffset(1,1), 1 ); osg::Geometry* outline = new osg::Geometry(); outline->setUseVertexBufferObjects( _useVertexBufferObjects.value() ); buildPolygon(part, featureSRS, mapSRS, makeECEF, false, outline); if ( outline->getVertexArray()->getVertexBufferObject() ) outline->getVertexArray()->getVertexBufferObject()->setUsage(GL_STATIC_DRAW_ARB); osg::Vec4f outlineColor = lineSymbol->stroke()->color(); osg::Vec4Array* outlineColors = new osg::Vec4Array(); #if USE_SINGLE_COLOR outlineColors->reserve(1); outlineColors->push_back( outlineColor ); outline->setColorBinding( osg::Geometry::BIND_OVERALL ); #else unsigned pcount = part->getTotalPointCount(); outlineColors->reserve( pcount ); for( unsigned c=0; c < pcount; ++c ) outlineColors->push_back( outlineColor ); outline->setColorBinding( osg::Geometry::BIND_PER_VERTEX ); #endif outline->setColorArray(outlineColors); // check for explicit tessellation disable: bool disableTess = lineSymbol && lineSymbol->tessellation().isSetTo(0); // subdivide if necessary. if ( makeECEF && !disableTess ) { double threshold = osg::DegreesToRadians( *_maxAngle_deg ); OE_DEBUG << "Running mesh subdivider for outlines with threshold " << *_maxAngle_deg << std::endl; MeshSubdivider ms( _world2local, _local2world ); if ( input->geoInterp().isSet() ) ms.run( *outline, threshold, *input->geoInterp() ); else ms.run( *outline, threshold, *_geoInterp ); } applyLineAndPointSymbology( outline->getOrCreateStateSet(), lineSymbol, 0L ); // make normals before adding an outline osgUtil::SmoothingVisitor sv; _geode->accept( sv ); _geode->addDrawable( outline ); //_featureNode->addDrawable( outline, input->getFID() ); // Mark each primitive set with its feature ID. if ( context.featureIndex() ) context.featureIndex()->tagPrimitiveSets( outline, input ); } } } return true; }
void AltitudeFilter::pushAndClamp( FeatureList& features, FilterContext& cx ) { const Session* session = cx.getSession(); // the map against which we'll be doing elevation clamping //MapFrame mapf = session->createMapFrame( Map::ELEVATION_LAYERS ); MapFrame mapf = session->createMapFrame( (Map::ModelParts)(Map::TERRAIN_LAYERS | Map::MODEL_LAYERS) ); const SpatialReference* mapSRS = mapf.getProfile()->getSRS(); osg::ref_ptr<const SpatialReference> featureSRS = cx.profile()->getSRS(); // establish an elevation query interface based on the features' SRS. ElevationQuery eq( mapf ); // want a result even if it's low res eq.setFallBackOnNoData( true ); NumericExpression scaleExpr; if ( _altitude->verticalScale().isSet() ) scaleExpr = *_altitude->verticalScale(); NumericExpression offsetExpr; if ( _altitude->verticalOffset().isSet() ) offsetExpr = *_altitude->verticalOffset(); // whether to record the min/max height-above-terrain values. bool collectHATs = _altitude->clamping() == AltitudeSymbol::CLAMP_RELATIVE_TO_TERRAIN || _altitude->clamping() == AltitudeSymbol::CLAMP_ABSOLUTE; // whether to clamp every vertex (or just the centroid) bool perVertex = _altitude->binding() == AltitudeSymbol::BINDING_VERTEX; // whether the SRS's have a compatible vertical datum. bool vertEquiv = featureSRS->isVertEquivalentTo( mapSRS ); for( FeatureList::iterator i = features.begin(); i != features.end(); ++i ) { Feature* feature = i->get(); // run a symbol script if present. if ( _altitude.valid() && _altitude->script().isSet() ) { StringExpression temp( _altitude->script().get() ); feature->eval( temp, &cx ); } double maxTerrainZ = -DBL_MAX; double minTerrainZ = DBL_MAX; double minHAT = DBL_MAX; double maxHAT = -DBL_MAX; double scaleZ = 1.0; if ( _altitude.valid() && _altitude->verticalScale().isSet() ) scaleZ = feature->eval( scaleExpr, &cx ); double offsetZ = 0.0; if ( _altitude.valid() && _altitude->verticalOffset().isSet() ) offsetZ = feature->eval( offsetExpr, &cx ); GeometryIterator gi( feature->getGeometry() ); while( gi.hasMore() ) { Geometry* geom = gi.next(); // Absolute heights in Z. Only need to collect the HATs; the geometry // remains unchanged. if ( _altitude->clamping() == AltitudeSymbol::CLAMP_ABSOLUTE ) { if ( perVertex ) { std::vector<double> elevations; elevations.reserve( geom->size() ); if ( eq.getElevations( geom->asVector(), featureSRS, elevations, _maxRes ) ) { for( unsigned i=0; i<geom->size(); ++i ) { osg::Vec3d& p = (*geom)[i]; p.z() *= scaleZ; p.z() += offsetZ; double z = p.z(); if ( !vertEquiv ) { osg::Vec3d tempgeo; if ( !featureSRS->transform(p, mapSRS->getGeographicSRS(), tempgeo) ) z = tempgeo.z(); } double hat = z - elevations[i]; if ( hat > maxHAT ) maxHAT = hat; if ( hat < minHAT ) minHAT = hat; if ( elevations[i] > maxTerrainZ ) maxTerrainZ = elevations[i]; if ( elevations[i] < minTerrainZ ) minTerrainZ = elevations[i]; } } } else // per centroid { osgEarth::Bounds bounds = geom->getBounds(); const osg::Vec2d& center = bounds.center2d(); GeoPoint centroid(featureSRS, center.x(), center.y()); double centroidElevation; if ( eq.getElevation( centroid, centroidElevation, _maxRes ) ) { for( unsigned i=0; i<geom->size(); ++i ) { osg::Vec3d& p = (*geom)[i]; p.z() *= scaleZ; p.z() += offsetZ; double z = p.z(); if ( !vertEquiv ) { osg::Vec3d tempgeo; if ( !featureSRS->transform(p, mapSRS->getGeographicSRS(), tempgeo) ) z = tempgeo.z(); } double hat = z - centroidElevation; if ( hat > maxHAT ) maxHAT = hat; if ( hat < minHAT ) minHAT = hat; } if ( centroidElevation > maxTerrainZ ) maxTerrainZ = centroidElevation; if ( centroidElevation < minTerrainZ ) minTerrainZ = centroidElevation; } } } // Heights-above-ground in Z. Need to resolve this to an absolute number // and record HATs along the way. else if ( _altitude->clamping() == AltitudeSymbol::CLAMP_RELATIVE_TO_TERRAIN ) { osg::ref_ptr<const SpatialReference> featureSRSwithMapVertDatum = !vertEquiv ? SpatialReference::create(featureSRS->getHorizInitString(), mapSRS->getVertInitString()) : 0L; if ( perVertex ) { std::vector<double> elevations; elevations.reserve( geom->size() ); if ( eq.getElevations( geom->asVector(), featureSRS, elevations, _maxRes ) ) { for( unsigned i=0; i<geom->size(); ++i ) { osg::Vec3d& p = (*geom)[i]; p.z() *= scaleZ; p.z() += offsetZ; double hat = p.z(); p.z() = elevations[i] + p.z(); // if necessary, convert the Z value (which is now in the map's SRS) back to // the feature's SRS. if ( !vertEquiv ) { featureSRSwithMapVertDatum->transform(p, featureSRS, p); } if ( hat > maxHAT ) maxHAT = hat; if ( hat < minHAT ) minHAT = hat; if ( elevations[i] > maxTerrainZ ) maxTerrainZ = elevations[i]; if ( elevations[i] < minTerrainZ ) minTerrainZ = elevations[i]; } } } else // per-centroid { osgEarth::Bounds bounds = geom->getBounds(); const osg::Vec2d& center = bounds.center2d(); GeoPoint centroid(featureSRS, center.x(), center.y()); double centroidElevation; if ( eq.getElevation( centroid, centroidElevation, _maxRes ) ) { for( unsigned i=0; i<geom->size(); ++i ) { osg::Vec3d& p = (*geom)[i]; p.z() *= scaleZ; p.z() += offsetZ; double hat = p.z(); p.z() = centroidElevation + p.z(); // if necessary, convert the Z value (which is now in the map's SRS) back to // the feature's SRS. if ( !vertEquiv ) { featureSRSwithMapVertDatum->transform(p, featureSRS, p); } if ( hat > maxHAT ) maxHAT = hat; if ( hat < minHAT ) minHAT = hat; } if ( centroidElevation > maxTerrainZ ) maxTerrainZ = centroidElevation; if ( centroidElevation < minTerrainZ ) minTerrainZ = centroidElevation; } } } // Clamp - replace the geometry's Z with the terrain height. else // CLAMP_TO_TERRAIN { if ( perVertex ) { eq.getElevations( geom->asVector(), featureSRS, true, _maxRes ); // if necessary, transform the Z values (which are now in the map SRS) back // into the feature's SRS. if ( !vertEquiv ) { osg::ref_ptr<const SpatialReference> featureSRSwithMapVertDatum = SpatialReference::create(featureSRS->getHorizInitString(), mapSRS->getVertInitString()); osg::Vec3d tempgeo; for( unsigned i=0; i<geom->size(); ++i ) { osg::Vec3d& p = (*geom)[i]; featureSRSwithMapVertDatum->transform(p, featureSRS, p); } } } else // per-centroid { osgEarth::Bounds bounds = geom->getBounds(); const osg::Vec2d& center = bounds.center2d(); GeoPoint centroid(featureSRS, center.x(), center.y()); double centroidElevation; osg::ref_ptr<const SpatialReference> featureSRSWithMapVertDatum; if ( !vertEquiv ) featureSRSWithMapVertDatum = SpatialReference::create(featureSRS->getHorizInitString(), mapSRS->getVertInitString()); if ( eq.getElevation( centroid, centroidElevation, _maxRes ) ) { for( unsigned i=0; i<geom->size(); ++i ) { osg::Vec3d& p = (*geom)[i]; p.z() = centroidElevation; if ( !vertEquiv ) { featureSRSWithMapVertDatum->transform(p, featureSRS, p); } } } } } if ( !collectHATs ) { for( Geometry::iterator i = geom->begin(); i != geom->end(); ++i ) { i->z() *= scaleZ; i->z() += offsetZ; } } } if ( minHAT != DBL_MAX ) { feature->set( "__min_hat", minHAT ); feature->set( "__max_hat", maxHAT ); } if ( minTerrainZ != DBL_MAX ) { feature->set( "__min_terrain_z", minTerrainZ ); feature->set( "__max_terrain_z", maxTerrainZ ); } } }
bool ExtrudeGeometryFilter::extrudeGeometry(const Geometry* input, double height, double heightOffset, bool flatten, osg::Geometry* walls, osg::Geometry* roof, osg::Geometry* base, osg::Geometry* outline, const osg::Vec4& wallColor, const osg::Vec4& wallBaseColor, const osg::Vec4& roofColor, const osg::Vec4& outlineColor, const SkinResource* wallSkin, const SkinResource* roofSkin, FilterContext& cx ) { bool makeECEF = false; const SpatialReference* srs = 0L; const SpatialReference* mapSRS = 0L; if ( cx.isGeoreferenced() ) { srs = cx.extent()->getSRS(); makeECEF = cx.getSession()->getMapInfo().isGeocentric(); mapSRS = cx.getSession()->getMapInfo().getProfile()->getSRS(); } bool made_geom = false; double tex_width_m = wallSkin ? *wallSkin->imageWidth() : 1.0; double tex_height_m = wallSkin ? *wallSkin->imageHeight() : 1.0; bool tex_repeats_y = wallSkin ? *wallSkin->isTiled() : false; bool useColor = (!wallSkin || wallSkin->texEnvMode() != osg::TexEnv::DECAL) && !_makeStencilVolume; bool isPolygon = input->getComponentType() == Geometry::TYPE_POLYGON; unsigned pointCount = input->getTotalPointCount(); // If we are extruding a polygon, and applying a wall texture, we need an extra // point in the geometry in order to close the polygon and generate a unique // texture coordinate for that final point. bool isSkinnedPolygon = isPolygon && wallSkin != 0L; // Total number of verts. Add 2 to close a polygon (necessary so the first and last // points can have unique texture coordinates) unsigned numWallVerts = 2 * pointCount + (isSkinnedPolygon? (2 * input->getNumGeometries()) : 0); // create all the OSG geometry components osg::Vec3Array* verts = new osg::Vec3Array( numWallVerts ); walls->setVertexArray( verts ); osg::Vec2Array* wallTexcoords = 0L; if ( wallSkin ) { wallTexcoords = new osg::Vec2Array( numWallVerts ); walls->setTexCoordArray( 0, wallTexcoords ); } osg::Vec4Array* colors = 0L; if ( useColor ) { // per-vertex colors are necessary if we are going to use the MeshConsolidator -gw colors = new osg::Vec4Array(); colors->reserve( numWallVerts ); colors->assign( numWallVerts, wallColor ); walls->setColorArray( colors ); walls->setColorBinding( osg::Geometry::BIND_PER_VERTEX ); } // set up rooftop tessellation and texturing, if necessary: osg::Vec3Array* roofVerts = 0L; osg::Vec2Array* roofTexcoords = 0L; float roofRotation = 0.0f; Bounds roofBounds; float sinR = 0.0f, cosR = 0.0f; double roofTexSpanX = 0.0, roofTexSpanY = 0.0; osg::ref_ptr<const SpatialReference> roofProjSRS; if ( roof ) { roofVerts = new osg::Vec3Array( pointCount ); roof->setVertexArray( roofVerts ); // per-vertex colors are necessary if we are going to use the MeshConsolidator -gw if ( useColor ) { osg::Vec4Array* roofColors = new osg::Vec4Array(); roofColors->reserve( pointCount ); roofColors->assign( pointCount, roofColor ); roof->setColorArray( roofColors ); roof->setColorBinding( osg::Geometry::BIND_PER_VERTEX ); } if ( roofSkin ) { roofTexcoords = new osg::Vec2Array( pointCount ); roof->setTexCoordArray( 0, roofTexcoords ); // Get the orientation of the geometry. This is a hueristic that will help // us align the roof skin texture properly. TODO: make this optional? It makes // sense for buildings and such, but perhaps not for all extruded shapes. roofRotation = getApparentRotation( input ); roofBounds = input->getBounds(); // if our data is lat/long, we need to reproject the geometry and the bounds into a projected // coordinate system in order to properly generate tex coords. if ( srs && srs->isGeographic() ) { osg::Vec2d geogCenter = roofBounds.center2d(); roofProjSRS = srs->createUTMFromLonLat( Angular(geogCenter.x()), Angular(geogCenter.y()) ); roofBounds.transform( srs, roofProjSRS.get() ); osg::ref_ptr<Geometry> projectedInput = input->clone(); srs->transform( projectedInput->asVector(), roofProjSRS.get() ); roofRotation = getApparentRotation( projectedInput.get() ); } else { roofRotation = getApparentRotation( input ); } sinR = sin(roofRotation); cosR = cos(roofRotation); if ( !roofSkin->isTiled().value() ) { //note: doesn't really work roofTexSpanX = cosR*roofBounds.width() - sinR*roofBounds.height(); roofTexSpanY = sinR*roofBounds.width() + cosR*roofBounds.height(); } else { roofTexSpanX = roofSkin->imageWidth().isSet() ? *roofSkin->imageWidth() : roofSkin->imageHeight().isSet() ? *roofSkin->imageHeight() : 10.0; if ( roofTexSpanX <= 0.0 ) roofTexSpanX = 10.0; roofTexSpanY = roofSkin->imageHeight().isSet() ? *roofSkin->imageHeight() : roofSkin->imageWidth().isSet() ? *roofSkin->imageWidth() : 10.0; if ( roofTexSpanY <= 0.0 ) roofTexSpanY = 10.0; } } } osg::Vec3Array* baseVerts = NULL; if ( base ) { baseVerts = new osg::Vec3Array( pointCount ); base->setVertexArray( baseVerts ); } osg::Vec3Array* outlineVerts = 0L; osg::Vec3Array* outlineNormals = 0L; if ( outline ) { outlineVerts = new osg::Vec3Array( numWallVerts ); outline->setVertexArray( outlineVerts ); osg::Vec4Array* outlineColors = new osg::Vec4Array(); outlineColors->reserve( numWallVerts ); outlineColors->assign( numWallVerts, outlineColor ); outline->setColorArray( outlineColors ); outline->setColorBinding( osg::Geometry::BIND_PER_VERTEX ); // cop out, just point all the outline normals up. fix this later. outlineNormals = new osg::Vec3Array(); outlineNormals->reserve( numWallVerts ); outlineNormals->assign( numWallVerts, osg::Vec3(0,0,1) ); outline->setNormalArray( outlineNormals ); } unsigned wallVertPtr = 0; unsigned roofVertPtr = 0; unsigned baseVertPtr = 0; double targetLen = -DBL_MAX; osg::Vec3d minLoc(DBL_MAX, DBL_MAX, DBL_MAX); double minLoc_len = DBL_MAX; osg::Vec3d maxLoc(0,0,0); double maxLoc_len = 0; // Initial pass over the geometry does two things: // 1: Calculate the minimum Z across all parts. // 2: Establish a "target length" for extrusion double absHeight = fabs(height); ConstGeometryIterator zfinder( input ); while( zfinder.hasMore() ) { const Geometry* geom = zfinder.next(); for( Geometry::const_iterator m = geom->begin(); m != geom->end(); ++m ) { osg::Vec3d m_point = *m; if ( m_point.z() + absHeight > targetLen ) targetLen = m_point.z() + absHeight; if (m_point.z() < minLoc.z()) minLoc = m_point; if (m_point.z() > maxLoc.z()) maxLoc = m_point; } } // apply the height offsets height -= heightOffset; targetLen -= heightOffset; // now generate the extruded geometry. ConstGeometryIterator iter( input ); while( iter.hasMore() ) { const Geometry* part = iter.next(); double tex_height_m_adj = tex_height_m; unsigned wallPartPtr = wallVertPtr; unsigned roofPartPtr = roofVertPtr; unsigned basePartPtr = baseVertPtr; double partLen = 0.0; double maxHeight = 0.0; maxHeight = targetLen - minLoc.z(); // Adjust the texture height so it is a multiple of the maximum height double div = osg::round(maxHeight / tex_height_m); if (div == 0) div = 1; //Prevent divide by zero tex_height_m_adj = maxHeight / div; //osg::DrawElementsUShort* idx = new osg::DrawElementsUShort( GL_TRIANGLES ); osg::DrawElementsUInt* idx = new osg::DrawElementsUInt( GL_TRIANGLES ); for( Geometry::const_iterator m = part->begin(); m != part->end(); ++m ) { osg::Vec3d basePt = *m; osg::Vec3d roofPt; if ( height >= 0 ) { if ( flatten ) roofPt.set( basePt.x(), basePt.y(), targetLen ); else roofPt.set( basePt.x(), basePt.y(), basePt.z() + height ); } else // height < 0 { roofPt = *m; basePt.z() += height; } // add to the approprate vertex lists: int p = wallVertPtr; // figure out the rooftop texture coordinates before doing any // transformations: if ( roofSkin && roofProjSRS && srs ) { double xr, yr; if ( srs && srs->isGeographic() ) { osg::Vec3d projRoofPt; srs->transform( roofPt, roofProjSRS.get(), projRoofPt ); xr = (projRoofPt.x() - roofBounds.xMin()); yr = (projRoofPt.y() - roofBounds.yMin()); } else { xr = (roofPt.x() - roofBounds.xMin()); yr = (roofPt.y() - roofBounds.yMin()); } float u = (cosR*xr - sinR*yr) / roofTexSpanX; float v = (sinR*xr + cosR*yr) / roofTexSpanY; (*roofTexcoords)[roofVertPtr].set( u, v ); } transformAndLocalize( basePt, srs, basePt, mapSRS, _world2local, makeECEF ); transformAndLocalize( roofPt, srs, roofPt, mapSRS, _world2local, makeECEF ); if ( base ) { (*baseVerts)[baseVertPtr] = basePt; } if ( roof ) { (*roofVerts)[roofVertPtr] = roofPt; } baseVertPtr++; roofVertPtr++; (*verts)[p] = roofPt; (*verts)[p+1] = basePt; if ( useColor ) { (*colors)[p+1] = wallBaseColor; } if ( outline ) { (*outlineVerts)[p] = roofPt; (*outlineVerts)[p+1] = basePt; } partLen += wallVertPtr > wallPartPtr ? ((*verts)[p] - (*verts)[p-2]).length() : 0.0; double h = tex_repeats_y ? -((*verts)[p] - (*verts)[p+1]).length() : -tex_height_m_adj; if ( wallSkin ) { (*wallTexcoords)[p].set( partLen/tex_width_m, 0.0f ); (*wallTexcoords)[p+1].set( partLen/tex_width_m, h/tex_height_m_adj ); } // form the 2 triangles if ( (m+1) == part->end() ) { if ( isPolygon ) { // end of the wall; loop around to close it off. if ( isSkinnedPolygon ) { // if we requested an extra geometry point, that means we are generating // a polygon-closing line so we can have a unique texcoord for it. idx->push_back(wallVertPtr); idx->push_back(wallVertPtr+1); idx->push_back(wallVertPtr+2); idx->push_back(wallVertPtr+1); idx->push_back(wallVertPtr+3); idx->push_back(wallVertPtr+2); (*verts)[p+2] = (*verts)[wallPartPtr]; (*verts)[p+3] = (*verts)[wallPartPtr+1]; if ( wallSkin ) { partLen += ((*verts)[p+2] - (*verts)[p]).length(); double h = tex_repeats_y ? -((*verts)[p+2] - (*verts)[p+3]).length() : -tex_height_m_adj; (*wallTexcoords)[p+2].set( partLen/tex_width_m, 0.0f ); (*wallTexcoords)[p+3].set( partLen/tex_width_m, h/tex_height_m_adj ); } wallVertPtr += 2; } else { // either not a poly, or no wall skin, so we can share the polygon-closing // loop point. idx->push_back(wallVertPtr); idx->push_back(wallVertPtr+1); idx->push_back(wallPartPtr); idx->push_back(wallVertPtr+1); idx->push_back(wallPartPtr+1); idx->push_back(wallPartPtr); } } else { //nop - no elements required at the end of a line } } else { idx->push_back(wallVertPtr); idx->push_back(wallVertPtr+1); idx->push_back(wallVertPtr+2); idx->push_back(wallVertPtr+1); idx->push_back(wallVertPtr+3); idx->push_back(wallVertPtr+2); } wallVertPtr += 2; made_geom = true; } walls->addPrimitiveSet( idx ); if ( roof ) { roof->addPrimitiveSet( new osg::DrawArrays( osg::PrimitiveSet::LINE_LOOP, roofPartPtr, roofVertPtr - roofPartPtr ) ); } if ( base ) { // reverse the base verts: int len = baseVertPtr - basePartPtr; for( int i=basePartPtr; i<len/2; i++ ) std::swap( (*baseVerts)[i], (*baseVerts)[basePartPtr+(len-1)-i] ); base->addPrimitiveSet( new osg::DrawArrays( osg::PrimitiveSet::LINE_LOOP, basePartPtr, baseVertPtr - basePartPtr ) ); } if ( outline ) { unsigned len = baseVertPtr - basePartPtr; GLenum roofLineMode = isPolygon ? GL_LINE_LOOP : GL_LINE_STRIP; osg::DrawElementsUInt* roofLine = new osg::DrawElementsUInt( roofLineMode ); roofLine->reserveElements( len ); for( unsigned i=0; i<len; ++i ) roofLine->addElement( basePartPtr + i*2 ); outline->addPrimitiveSet( roofLine ); // if the outline is tessellated, we only want outlines on the original // points (not the inserted points) unsigned step = std::max( 1u, _outlineSymbol->tessellation().isSet() ? *_outlineSymbol->tessellation() : 1u ); osg::DrawElementsUInt* wallLines = new osg::DrawElementsUInt( GL_LINES ); wallLines->reserve( len*2 ); for( unsigned i=0; i<len; i+=step ) { wallLines->push_back( basePartPtr + i*2 ); wallLines->push_back( basePartPtr + i*2 + 1 ); } outline->addPrimitiveSet( wallLines ); applyLineSymbology( outline->getOrCreateStateSet(), _outlineSymbol.get() ); } } return made_geom; }
bool SubstituteModelFilter::process(const FeatureList& features, const MarkerSymbol* symbol, Session* session, osg::Group* attachPoint, FilterContext& context ) { bool makeECEF = context.getSession()->getMapInfo().isGeocentric(); // first, go through the features and build the model cache. Apply the model matrix' scale // factor to any AutoTransforms directly (cloning them as necessary) std::map< std::pair<URI, float>, osg::ref_ptr<osg::Node> > uniqueModels; //std::map< Feature*, osg::ref_ptr<osg::Node> > featureModels; StringExpression uriEx = *symbol->url(); NumericExpression scaleEx = *symbol->scale(); for( FeatureList::const_iterator f = features.begin(); f != features.end(); ++f ) { Feature* input = f->get(); // evaluate the marker URI expression: StringExpression uriEx = *symbol->url(); URI markerURI( input->eval(uriEx, &context), uriEx.uriContext() ); // find the corresponding marker in the cache MarkerResource* marker = 0L; MarkerCache::Record rec = _markerCache.get( markerURI ); if ( rec.valid() ) { marker = rec.value(); } else { marker = new MarkerResource(); marker->uri() = markerURI; _markerCache.insert( markerURI, marker ); } // evalute the scale expression (if there is one) float scale = 1.0f; osg::Matrixd scaleMatrix; if ( symbol->scale().isSet() ) { scale = input->eval( scaleEx, &context ); if ( scale == 0.0 ) scale = 1.0; scaleMatrix = osg::Matrix::scale( scale, scale, scale ); } // how that we have a marker source, create a node for it std::pair<URI,float> key( markerURI, scale ); osg::ref_ptr<osg::Node>& model = uniqueModels[key]; if ( !model.valid() ) { model = context.resourceCache()->getMarkerNode( marker ); if ( scale != 1.0f && dynamic_cast<osg::AutoTransform*>( model.get() ) ) { // clone the old AutoTransform, set the new scale, and copy over its children. osg::AutoTransform* oldAT = dynamic_cast<osg::AutoTransform*>(model.get()); osg::AutoTransform* newAT = osg::clone( oldAT ); // make a scaler and put it between the new AutoTransform and its kids osg::MatrixTransform* scaler = new osg::MatrixTransform(osg::Matrix::scale(scale,scale,scale)); for( unsigned i=0; i<newAT->getNumChildren(); ++i ) scaler->addChild( newAT->getChild(0) ); newAT->removeChildren(0, newAT->getNumChildren()); newAT->addChild( scaler ); model = newAT; } } if ( model.valid() ) { GeometryIterator gi( input->getGeometry(), false ); while( gi.hasMore() ) { Geometry* geom = gi.next(); for( unsigned i=0; i<geom->size(); ++i ) { osg::Matrixd mat; osg::Vec3d point = (*geom)[i]; if ( makeECEF ) { // the "rotation" element lets us re-orient the instance to ensure it's pointing up. We // could take a shortcut and just use the current extent's local2world matrix for this, // but if the tile is big enough the up vectors won't be quite right. osg::Matrixd rotation; ECEF::transformAndGetRotationMatrix( point, context.profile()->getSRS(), point, rotation ); mat = rotation * scaleMatrix * osg::Matrixd::translate( point ) * _world2local; } else { mat = scaleMatrix * osg::Matrixd::translate( point ) * _world2local; } osg::MatrixTransform* xform = new osg::MatrixTransform(); xform->setMatrix( mat ); xform->addChild( model.get() ); attachPoint->addChild( xform ); // name the feature if necessary if ( !_featureNameExpr.empty() ) { const std::string& name = input->eval( _featureNameExpr, &context); if ( !name.empty() ) xform->setName( name ); } } } } } return true; }
//clustering: // troll the external model for geodes. for each geode, create a geode in the target // model. then, for each geometry in that geode, replicate it once for each instance of // the model in the feature batch and transform the actual verts to a local offset // relative to the tile centroid. Finally, reassemble all the geodes and optimize. // hopefully stateset sharing etc will work out. we may need to strip out LODs too. bool SubstituteModelFilter::cluster(const FeatureList& features, const MarkerSymbol* symbol, Session* session, osg::Group* attachPoint, FilterContext& context ) { MarkerToFeatures markerToFeatures; // first, sort the features into buckets, each bucket corresponding to a // unique marker. for (FeatureList::const_iterator i = features.begin(); i != features.end(); ++i) { Feature* f = i->get(); // resolve the URI for the marker: StringExpression uriEx( *symbol->url() ); URI markerURI( f->eval( uriEx, &context ), uriEx.uriContext() ); // find and load the corresponding marker model. We're using the session-level // object store to cache models. This is thread-safe sine we are always going // to CLONE the model before using it. osg::ref_ptr<osg::Node> model = context.getSession()->getObject<osg::Node>( markerURI.full() ); if ( !model.valid() ) { osg::ref_ptr<MarkerResource> mres = new MarkerResource(); mres->uri() = markerURI; model = mres->createNode( context.getSession()->getDBOptions() ); if ( model.valid() ) { // store it, but only if there isn't already one in there. context.getSession()->putObject( markerURI.full(), model.get(), false ); } } if ( model.valid() ) { MarkerToFeatures::iterator itr = markerToFeatures.find( model.get() ); if (itr == markerToFeatures.end()) markerToFeatures[ model.get() ].push_back( f ); else itr->second.push_back( f ); } } //For each model, cluster the features that use that marker for (MarkerToFeatures::iterator i = markerToFeatures.begin(); i != markerToFeatures.end(); ++i) { osg::Node* prototype = i->first; // we're using the Session cache since we know we'll be cloning. if ( prototype ) { osg::Node* clone = osg::clone( prototype, osg::CopyOp::DEEP_COPY_ALL ); // ..and apply the clustering to the copy. ClusterVisitor cv( i->second, symbol, this, context ); clone->accept( cv ); attachPoint->addChild( clone ); } } return true; }
bool SubstituteModelFilter::process(const FeatureList& features, const InstanceSymbol* symbol, Session* session, osg::Group* attachPoint, FilterContext& context ) { // Establish SRS information: bool makeECEF = context.getSession()->getMapInfo().isGeocentric(); const SpatialReference* targetSRS = context.getSession()->getMapInfo().getSRS(); // first, go through the features and build the model cache. Apply the model matrix' scale // factor to any AutoTransforms directly (cloning them as necessary) std::map< std::pair<URI, float>, osg::ref_ptr<osg::Node> > uniqueModels; // keep track of failed URIs so we don't waste time or warning messages on them std::set< URI > missing; StringExpression uriEx = *symbol->url(); NumericExpression scaleEx = *symbol->scale(); const ModelSymbol* modelSymbol = dynamic_cast<const ModelSymbol*>(symbol); const IconSymbol* iconSymbol = dynamic_cast<const IconSymbol*> (symbol); NumericExpression headingEx; if ( modelSymbol ) headingEx = *modelSymbol->heading(); for( FeatureList::const_iterator f = features.begin(); f != features.end(); ++f ) { Feature* input = f->get(); // evaluate the instance URI expression: StringExpression uriEx = *symbol->url(); URI instanceURI( input->eval(uriEx, &context), uriEx.uriContext() ); // find the corresponding marker in the cache osg::ref_ptr<InstanceResource> instance; if ( !findResource(instanceURI, symbol, context, missing, instance) ) continue; // evalute the scale expression (if there is one) float scale = 1.0f; osg::Matrixd scaleMatrix; if ( symbol->scale().isSet() ) { scale = input->eval( scaleEx, &context ); if ( scale == 0.0 ) scale = 1.0; if ( scale != 1.0 ) _normalScalingRequired = true; scaleMatrix = osg::Matrix::scale( scale, scale, scale ); } osg::Matrixd rotationMatrix; if ( modelSymbol && modelSymbol->heading().isSet() ) { float heading = input->eval(headingEx, &context); rotationMatrix.makeRotate( osg::Quat(osg::DegreesToRadians(heading), osg::Vec3(0,0,1)) ); } // how that we have a marker source, create a node for it std::pair<URI,float> key( instanceURI, scale ); // cache nodes per instance. osg::ref_ptr<osg::Node>& model = uniqueModels[key]; if ( !model.valid() ) { context.resourceCache()->getInstanceNode( instance.get(), model ); // if icon decluttering is off, install an AutoTransform. if ( iconSymbol ) { if ( iconSymbol->declutter() == true ) { Decluttering::setEnabled( model->getOrCreateStateSet(), true ); } else if ( dynamic_cast<osg::AutoTransform*>(model.get()) == 0L ) { osg::AutoTransform* at = new osg::AutoTransform(); at->setAutoRotateMode( osg::AutoTransform::ROTATE_TO_SCREEN ); at->setAutoScaleToScreen( true ); at->addChild( model ); model = at; } } } if ( model.valid() ) { GeometryIterator gi( input->getGeometry(), false ); while( gi.hasMore() ) { Geometry* geom = gi.next(); // if necessary, transform the points to the target SRS: if ( !makeECEF && !targetSRS->isEquivalentTo(context.profile()->getSRS()) ) { context.profile()->getSRS()->transform( geom->asVector(), targetSRS ); } for( unsigned i=0; i<geom->size(); ++i ) { osg::Matrixd mat; osg::Vec3d point = (*geom)[i]; if ( makeECEF ) { // the "rotation" element lets us re-orient the instance to ensure it's pointing up. We // could take a shortcut and just use the current extent's local2world matrix for this, // but if the tile is big enough the up vectors won't be quite right. osg::Matrixd rotation; ECEF::transformAndGetRotationMatrix( point, context.profile()->getSRS(), point, targetSRS, rotation ); mat = rotationMatrix * rotation * scaleMatrix * osg::Matrixd::translate( point ) * _world2local; } else { mat = rotationMatrix * scaleMatrix * osg::Matrixd::translate( point ) * _world2local; } osg::MatrixTransform* xform = new osg::MatrixTransform(); xform->setMatrix( mat ); xform->setDataVariance( osg::Object::STATIC ); xform->addChild( model.get() ); attachPoint->addChild( xform ); if ( context.featureIndex() && !_useDrawInstanced ) { context.featureIndex()->tagNode( xform, input ); } // name the feature if necessary if ( !_featureNameExpr.empty() ) { const std::string& name = input->eval( _featureNameExpr, &context); if ( !name.empty() ) xform->setName( name ); } } } } } if ( iconSymbol ) { // activate decluttering for icons if requested if ( iconSymbol->declutter() == true ) { Decluttering::setEnabled( attachPoint->getOrCreateStateSet(), true ); } // activate horizon culling if we are in geocentric space if ( context.getSession() && context.getSession()->getMapInfo().isGeocentric() ) { HorizonCullingProgram::install( attachPoint->getOrCreateStateSet() ); } } // active DrawInstanced if required: if ( _useDrawInstanced && Registry::capabilities().supportsDrawInstanced() ) { DrawInstanced::convertGraphToUseDrawInstanced( attachPoint ); // install a shader program to render draw-instanced. DrawInstanced::install( attachPoint->getOrCreateStateSet() ); } return true; }
osg::Node* GeomCompiler::compile(FeatureCursor* cursor, const Style& style, const FilterContext& context) { if ( !context.profile() ) { OE_WARN << LC << "Valid feature profile required" << std::endl; return 0L; } if ( style.empty() ) { OE_WARN << LC << "Non-empty style required" << std::endl; return 0L; } osg::ref_ptr<osg::Group> resultGroup = new osg::Group(); // start by making a working copy of the feature set FeatureList workingSet; cursor->fill( workingSet ); // create a filter context that will track feature data through the process FilterContext cx = context; if ( !cx.extent().isSet() ) cx.extent() = cx.profile()->getExtent(); // only localize coordinates if the map if geocentric AND the extent is // less than 180 degrees. const MapInfo& mi = cx.getSession()->getMapInfo(); GeoExtent workingExtent = cx.extent()->transform( cx.profile()->getSRS()->getGeographicSRS() ); bool localize = mi.isGeocentric() && workingExtent.width() < 180.0; // go through the Style and figure out which filters to use. const MarkerSymbol* marker = style.get<MarkerSymbol>(); const PointSymbol* point = style.get<PointSymbol>(); const LineSymbol* line = style.get<LineSymbol>(); const PolygonSymbol* polygon = style.get<PolygonSymbol>(); const ExtrusionSymbol* extrusion = style.get<ExtrusionSymbol>(); const AltitudeSymbol* altitude = style.get<AltitudeSymbol>(); const TextSymbol* text = style.get<TextSymbol>(); // transform the features into the map profile TransformFilter xform( mi.getProfile()->getSRS(), mi.isGeocentric() ); xform.setLocalizeCoordinates( localize ); if ( altitude && altitude->verticalOffset().isSet() ) xform.setMatrix( osg::Matrixd::translate(0, 0, *altitude->verticalOffset()) ); cx = xform.push( workingSet, cx ); bool clampRequired = altitude && altitude->clamping() != AltitudeSymbol::CLAMP_NONE; // model substitution if ( marker ) { if ( marker->placement() == MarkerSymbol::PLACEMENT_RANDOM || marker->placement() == MarkerSymbol::PLACEMENT_INTERVAL ) { ScatterFilter scatter; scatter.setDensity( *marker->density() ); scatter.setRandom( marker->placement() == MarkerSymbol::PLACEMENT_RANDOM ); scatter.setRandomSeed( *marker->randomSeed() ); cx = scatter.push( workingSet, cx ); } if ( clampRequired ) { ClampFilter clamp; clamp.setIgnoreZ( altitude->clamping() == AltitudeSymbol::CLAMP_TO_TERRAIN ); cx = clamp.push( workingSet, cx ); clampRequired = false; } SubstituteModelFilter sub( style ); sub.setClustering( *_options.clustering() ); if ( marker->scale().isSet() ) sub.setModelMatrix( osg::Matrixd::scale( *marker->scale() ) ); cx = sub.push( workingSet, cx ); osg::Node* node = sub.getNode(); if ( node ) resultGroup->addChild( node ); } // extruded geometry if ( extrusion && ( line || polygon ) ) { if ( clampRequired ) { ClampFilter clamp; clamp.setIgnoreZ( altitude->clamping() == AltitudeSymbol::CLAMP_TO_TERRAIN ); if ( extrusion->heightReference() == ExtrusionSymbol::HEIGHT_REFERENCE_MSL ) clamp.setMaxZAttributeName( "__max_z"); cx = clamp.push( workingSet, cx ); clampRequired = false; } ExtrudeGeometryFilter extrude; if ( extrusion ) { if ( extrusion->height().isSet() ) extrude.setExtrusionHeight( *extrusion->height() ); if ( extrusion->heightExpression().isSet() ) extrude.setExtrusionExpr( *extrusion->heightExpression() ); //extrude.setHeightReferenceFrame( *extrusion->heightReference() ); if ( extrusion->heightReference() == ExtrusionSymbol::HEIGHT_REFERENCE_MSL ) extrude.setHeightOffsetExpression( NumericExpression("[__max_z]") ); extrude.setFlatten( *extrusion->flatten() ); } if ( polygon ) { extrude.setColor( polygon->fill()->color() ); } osg::Node* node = extrude.push( workingSet, cx ); if ( node ) resultGroup->addChild( node ); } // simple geometry else if ( point || line || polygon ) { if ( clampRequired ) { ClampFilter clamp; clamp.setIgnoreZ( altitude->clamping() == AltitudeSymbol::CLAMP_TO_TERRAIN ); cx = clamp.push( workingSet, cx ); clampRequired = false; } BuildGeometryFilter filter( style ); if ( _options.maxGranularity().isSet() ) filter.maxGranularity() = *_options.maxGranularity(); if ( _options.mergeGeometry().isSet() ) filter.mergeGeometry() = *_options.mergeGeometry(); if ( _options.featureName().isSet() ) filter.featureName() = *_options.featureName(); cx = filter.push( workingSet, cx ); osg::Node* node = filter.getNode(); if ( node ) resultGroup->addChild( node ); } if ( text ) { if ( clampRequired ) { ClampFilter clamp; clamp.setIgnoreZ( altitude->clamping() == AltitudeSymbol::CLAMP_TO_TERRAIN ); cx = clamp.push( workingSet, cx ); clampRequired = false; } BuildTextFilter filter( style ); cx = filter.push( workingSet, cx ); osg::Node* node = filter.takeNode(); if ( node ) resultGroup->addChild( node ); } //else // insufficient symbology //{ // OE_WARN << LC << "Insufficient symbology; no geometry created" << std::endl; //} // install the localization transform if necessary. if ( cx.hasReferenceFrame() ) { osg::MatrixTransform* delocalizer = new osg::MatrixTransform( cx.inverseReferenceFrame() ); delocalizer->addChild( resultGroup.get() ); resultGroup = delocalizer; } resultGroup->getOrCreateStateSet()->setMode( GL_BLEND, 1 ); //osgDB::writeNodeFile( *(resultGroup.get()), "out.osg" ); return resultGroup.release(); }
osg::Node* GeometryCompiler::compile(FeatureList& workingSet, const Style& style, const FilterContext& context) { #ifdef PROFILING osg::Timer_t p_start = osg::Timer::instance()->tick(); unsigned p_features = workingSet.size(); #endif osg::ref_ptr<osg::Group> resultGroup = new osg::Group(); // create a filter context that will track feature data through the process FilterContext sharedCX = context; if ( !sharedCX.extent().isSet() && sharedCX.profile() ) { sharedCX.extent() = sharedCX.profile()->getExtent(); } // ref_ptr's to hold defaults in case we need them. osg::ref_ptr<PointSymbol> defaultPoint; osg::ref_ptr<LineSymbol> defaultLine; osg::ref_ptr<PolygonSymbol> defaultPolygon; // go through the Style and figure out which filters to use. const PointSymbol* point = style.get<PointSymbol>(); const LineSymbol* line = style.get<LineSymbol>(); const PolygonSymbol* polygon = style.get<PolygonSymbol>(); const ExtrusionSymbol* extrusion = style.get<ExtrusionSymbol>(); const AltitudeSymbol* altitude = style.get<AltitudeSymbol>(); const TextSymbol* text = style.get<TextSymbol>(); const MarkerSymbol* marker = style.get<MarkerSymbol>(); // to be deprecated const IconSymbol* icon = style.get<IconSymbol>(); const ModelSymbol* model = style.get<ModelSymbol>(); // check whether we need tessellation: if ( line && line->tessellation().isSet() ) { TemplateFeatureFilter<TessellateOperator> filter; filter.setNumPartitions( *line->tessellation() ); sharedCX = filter.push( workingSet, sharedCX ); } // if the style was empty, use some defaults based on the geometry type of the // first feature. if ( !point && !line && !polygon && !marker && !extrusion && !text && !model && !icon && workingSet.size() > 0 ) { Feature* first = workingSet.begin()->get(); Geometry* geom = first->getGeometry(); if ( geom ) { switch( geom->getComponentType() ) { case Geometry::TYPE_LINESTRING: case Geometry::TYPE_RING: defaultLine = new LineSymbol(); line = defaultLine.get(); break; case Geometry::TYPE_POINTSET: defaultPoint = new PointSymbol(); point = defaultPoint.get(); break; case Geometry::TYPE_POLYGON: defaultPolygon = new PolygonSymbol(); polygon = defaultPolygon.get(); break; case Geometry::TYPE_MULTI: case Geometry::TYPE_UNKNOWN: break; } } } // resample the geometry if necessary: if (_options.resampleMode().isSet()) { ResampleFilter resample; resample.resampleMode() = *_options.resampleMode(); if (_options.resampleMaxLength().isSet()) { resample.maxLength() = *_options.resampleMaxLength(); } sharedCX = resample.push( workingSet, sharedCX ); } // check whether we need to do elevation clamping: bool altRequired = _options.ignoreAltitudeSymbol() != true && altitude && ( altitude->clamping() != AltitudeSymbol::CLAMP_NONE || altitude->verticalOffset().isSet() || altitude->verticalScale().isSet() || altitude->script().isSet() ); // marker substitution -- to be deprecated in favor of model/icon if ( marker ) { // use a separate filter context since we'll be munging the data FilterContext markerCX = sharedCX; if ( marker->placement() == MarkerSymbol::PLACEMENT_RANDOM || marker->placement() == MarkerSymbol::PLACEMENT_INTERVAL ) { ScatterFilter scatter; scatter.setDensity( *marker->density() ); scatter.setRandom( marker->placement() == MarkerSymbol::PLACEMENT_RANDOM ); scatter.setRandomSeed( *marker->randomSeed() ); markerCX = scatter.push( workingSet, markerCX ); } else if ( marker->placement() == MarkerSymbol::PLACEMENT_CENTROID ) { CentroidFilter centroid; centroid.push( workingSet, markerCX ); } if ( altRequired ) { AltitudeFilter clamp; clamp.setPropertiesFromStyle( style ); markerCX = clamp.push( workingSet, markerCX ); // don't set this; we changed the input data. //altRequired = false; } SubstituteModelFilter sub( style ); sub.setClustering( *_options.clustering() ); sub.setUseDrawInstanced( *_options.instancing() ); if ( _options.featureName().isSet() ) sub.setFeatureNameExpr( *_options.featureName() ); osg::Node* node = sub.push( workingSet, markerCX ); if ( node ) { resultGroup->addChild( node ); } } // instance substitution (replaces marker) else if ( model ) { const InstanceSymbol* instance = model ? (const InstanceSymbol*)model : (const InstanceSymbol*)icon; // use a separate filter context since we'll be munging the data FilterContext localCX = sharedCX; if ( instance->placement() == InstanceSymbol::PLACEMENT_RANDOM || instance->placement() == InstanceSymbol::PLACEMENT_INTERVAL ) { ScatterFilter scatter; scatter.setDensity( *instance->density() ); scatter.setRandom( instance->placement() == InstanceSymbol::PLACEMENT_RANDOM ); scatter.setRandomSeed( *instance->randomSeed() ); localCX = scatter.push( workingSet, localCX ); } else if ( instance->placement() == InstanceSymbol::PLACEMENT_CENTROID ) { CentroidFilter centroid; centroid.push( workingSet, localCX ); } if ( altRequired ) { AltitudeFilter clamp; clamp.setPropertiesFromStyle( style ); localCX = clamp.push( workingSet, localCX ); } SubstituteModelFilter sub( style ); // activate clustering sub.setClustering( *_options.clustering() ); // activate draw-instancing sub.setUseDrawInstanced( *_options.instancing() ); // activate feature naming if ( _options.featureName().isSet() ) sub.setFeatureNameExpr( *_options.featureName() ); osg::Node* node = sub.push( workingSet, localCX ); if ( node ) { resultGroup->addChild( node ); // enable auto scaling on the group? if ( model && model->autoScale() == true ) { resultGroup->getOrCreateStateSet()->setRenderBinDetails(0, osgEarth::AUTO_SCALE_BIN ); } } } // extruded geometry if ( extrusion ) { if ( altRequired ) { AltitudeFilter clamp; clamp.setPropertiesFromStyle( style ); sharedCX = clamp.push( workingSet, sharedCX ); altRequired = false; } ExtrudeGeometryFilter extrude; extrude.setStyle( style ); // Activate texture arrays if the GPU supports them and if the user // hasn't disabled them. extrude.useTextureArrays() = Registry::capabilities().supportsTextureArrays() && _options.useTextureArrays() == true; // apply per-feature naming if requested. if ( _options.featureName().isSet() ) extrude.setFeatureNameExpr( *_options.featureName() ); if ( _options.useVertexBufferObjects().isSet()) extrude.useVertexBufferObjects() = *_options.useVertexBufferObjects(); osg::Node* node = extrude.push( workingSet, sharedCX ); if ( node ) { resultGroup->addChild( node ); } } // simple geometry else if ( point || line || polygon ) { if ( altRequired ) { AltitudeFilter clamp; clamp.setPropertiesFromStyle( style ); sharedCX = clamp.push( workingSet, sharedCX ); altRequired = false; } BuildGeometryFilter filter( style ); if ( _options.maxGranularity().isSet() ) filter.maxGranularity() = *_options.maxGranularity(); if ( _options.geoInterp().isSet() ) filter.geoInterp() = *_options.geoInterp(); if ( _options.featureName().isSet() ) filter.featureName() = *_options.featureName(); osg::Node* node = filter.push( workingSet, sharedCX ); if ( node ) { resultGroup->addChild( node ); } } if ( text || icon ) { if ( altRequired ) { AltitudeFilter clamp; clamp.setPropertiesFromStyle( style ); sharedCX = clamp.push( workingSet, sharedCX ); altRequired = false; } BuildTextFilter filter( style ); osg::Node* node = filter.push( workingSet, sharedCX ); if ( node ) { resultGroup->addChild( node ); } } if (Registry::capabilities().supportsGLSL()) { if ( _options.shaderPolicy() == SHADERPOLICY_GENERATE ) { // no ss cache because we will optimize later. Registry::shaderGenerator().run( resultGroup.get(), "osgEarth.GeomCompiler" ); } else if ( _options.shaderPolicy() == SHADERPOLICY_DISABLE ) { resultGroup->getOrCreateStateSet()->setAttributeAndModes( new osg::Program(), osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE ); } } // Optimize stateset sharing. if ( _options.optimizeStateSharing() == true ) { // Common state set cache? osg::ref_ptr<StateSetCache> sscache; if ( sharedCX.getSession() ) { // with a shared cache, don't combine statesets. They may be // in the live graph sscache = sharedCX.getSession()->getStateSetCache(); sscache->consolidateStateAttributes( resultGroup.get() ); } else { // isolated: perform full optimization sscache = new StateSetCache(); sscache->optimize( resultGroup.get() ); } } //test: dump the tile to disk //osgDB::writeNodeFile( *(resultGroup.get()), "out.osg" ); #ifdef PROFILING osg::Timer_t p_end = osg::Timer::instance()->tick(); OE_INFO << LC << "features = " << p_features << ", time = " << osg::Timer::instance()->delta_s(p_start, p_end) << " s." << std::endl; #endif return resultGroup.release(); }
bool SubstituteModelFilter::process(const FeatureList& features, const MarkerSymbol* symbol, Session* session, osg::Group* attachPoint, FilterContext& context ) { bool makeECEF = context.getSession()->getMapInfo().isGeocentric(); for( FeatureList::const_iterator f = features.begin(); f != features.end(); ++f ) { Feature* input = f->get(); GeometryIterator gi( input->getGeometry(), false ); while( gi.hasMore() ) { Geometry* geom = gi.next(); for( unsigned i=0; i<geom->size(); ++i ) { osg::Matrixd mat; osg::Vec3d point = (*geom)[i]; if ( makeECEF ) { // the "rotation" element lets us re-orient the instance to ensure it's pointing up. We // could take a shortcut and just use the current extent's local2world matrix for this, // but it the tile is big enough the up vectors won't be quite right. osg::Matrixd rotation; ECEF::transformAndGetRotationMatrix( context.profile()->getSRS(), point, point, rotation ); mat = rotation * _modelMatrix * osg::Matrixd::translate( point ) * _world2local; } else { mat = _modelMatrix * osg::Matrixd::translate( point ) * _world2local; } osg::MatrixTransform* xform = new osg::MatrixTransform(); xform->setMatrix( mat ); xform->setDataVariance( osg::Object::STATIC ); MarkerFactory factory( session); osg::ref_ptr< osg::Node > model = factory.getOrCreateNode( input, symbol ); if (model.get()) { xform->addChild( model.get() ); } attachPoint->addChild( xform ); // name the feature if necessary if ( !_featureNameExpr.empty() ) { const std::string& name = input->eval( _featureNameExpr ); if ( !name.empty() ) xform->setName( name ); } } } } return true; }
bool ExtrudeGeometryFilter::buildStructure(const Geometry* input, double height, double heightOffset, bool flatten, const SkinResource* wallSkin, const SkinResource* roofSkin, Structure& structure, FilterContext& cx ) { bool makeECEF = false; const SpatialReference* srs = 0L; const SpatialReference* mapSRS = 0L; if ( cx.isGeoreferenced() ) { srs = cx.extent()->getSRS(); makeECEF = cx.getSession()->getMapInfo().isGeocentric(); mapSRS = cx.getSession()->getMapInfo().getProfile()->getSRS(); } // whether this is a closed polygon structure. structure.isPolygon = (input->getComponentType() == Geometry::TYPE_POLYGON); // extrusion working variables double targetLen = -DBL_MAX; osg::Vec3d minLoc(DBL_MAX, DBL_MAX, DBL_MAX); double minLoc_len = DBL_MAX; osg::Vec3d maxLoc(0,0,0); double maxLoc_len = 0; // Initial pass over the geometry does two things: // 1: Calculate the minimum Z across all parts. // 2: Establish a "target length" for extrusion double absHeight = fabs(height); ConstGeometryIterator zfinder( input ); while( zfinder.hasMore() ) { const Geometry* geom = zfinder.next(); for( Geometry::const_iterator m = geom->begin(); m != geom->end(); ++m ) { osg::Vec3d m_point = *m; if ( m_point.z() + absHeight > targetLen ) targetLen = m_point.z() + absHeight; if (m_point.z() < minLoc.z()) minLoc = m_point; if (m_point.z() > maxLoc.z()) maxLoc = m_point; } } // apply the height offsets height -= heightOffset; targetLen -= heightOffset; float roofRotation = 0.0f; Bounds roofBounds; float sinR = 0.0f, cosR = 0.0f; double roofTexSpanX = 0.0, roofTexSpanY = 0.0; osg::ref_ptr<const SpatialReference> roofProjSRS; if ( roofSkin ) { roofBounds = input->getBounds(); // if our data is lat/long, we need to reproject the geometry and the bounds into a projected // coordinate system in order to properly generate tex coords. if ( srs && srs->isGeographic() ) { osg::Vec2d geogCenter = roofBounds.center2d(); roofProjSRS = srs->createUTMFromLonLat( Angle(geogCenter.x()), Angle(geogCenter.y()) ); if ( roofProjSRS.valid() ) { roofBounds.transform( srs, roofProjSRS.get() ); osg::ref_ptr<Geometry> projectedInput = input->clone(); srs->transform( projectedInput->asVector(), roofProjSRS.get() ); roofRotation = getApparentRotation( projectedInput.get() ); } } else { roofRotation = getApparentRotation( input ); } sinR = sin(roofRotation); cosR = cos(roofRotation); if ( !roofSkin->isTiled().value() ) { //note: non-tiled roofs don't really work atm. roofTexSpanX = cosR*roofBounds.width() - sinR*roofBounds.height(); roofTexSpanY = sinR*roofBounds.width() + cosR*roofBounds.height(); } else { roofTexSpanX = roofSkin->imageWidth().isSet() ? *roofSkin->imageWidth() : roofSkin->imageHeight().isSet() ? *roofSkin->imageHeight() : 10.0; if ( roofTexSpanX <= 0.0 ) roofTexSpanX = 10.0; roofTexSpanY = roofSkin->imageHeight().isSet() ? *roofSkin->imageHeight() : roofSkin->imageWidth().isSet() ? *roofSkin->imageWidth() : 10.0; if ( roofTexSpanY <= 0.0 ) roofTexSpanY = 10.0; } } // prep for wall texture coordinate generation. double texWidthM = wallSkin ? *wallSkin->imageWidth() : 0.0; double texHeightM = wallSkin ? *wallSkin->imageHeight() : 1.0; ConstGeometryIterator iter( input ); while( iter.hasMore() ) { const Geometry* part = iter.next(); // skip a part that's too small if (part->size() < 2) continue; // add a new wall. structure.elevations.push_back(Elevation()); Elevation& elevation = structure.elevations.back(); double maxHeight = targetLen - minLoc.z(); // Adjust the texture height so it is a multiple of the maximum height double div = osg::round(maxHeight / texHeightM); elevation.texHeightAdjustedM = div > 0.0 ? maxHeight / div : maxHeight; // Step 1 - Create the real corners and transform them into our target SRS. Corners corners; for(Geometry::const_iterator m = part->begin(); m != part->end(); ++m) { Corners::iterator corner = corners.insert(corners.end(), Corner()); // mark as "from source", as opposed to being inserted by the algorithm. corner->isFromSource = true; corner->base = *m; // extrude: if ( height >= 0 ) // extrude up { if ( flatten ) corner->roof.set( corner->base.x(), corner->base.y(), targetLen ); else corner->roof.set( corner->base.x(), corner->base.y(), corner->base.z() + height ); } else // height < 0 .. extrude down { corner->roof = *m; corner->base.z() += height; } // figure out the rooftop texture coords before doing any transformation: if ( roofSkin && srs ) { double xr, yr; if ( srs && srs->isGeographic() && roofProjSRS ) { osg::Vec3d projRoofPt; srs->transform( corner->roof, roofProjSRS.get(), projRoofPt ); xr = (projRoofPt.x() - roofBounds.xMin()); yr = (projRoofPt.y() - roofBounds.yMin()); } else { xr = (corner->roof.x() - roofBounds.xMin()); yr = (corner->roof.y() - roofBounds.yMin()); } corner->roofTexU = (cosR*xr - sinR*yr) / roofTexSpanX; corner->roofTexV = (sinR*xr + cosR*yr) / roofTexSpanY; } // transform into target SRS. transformAndLocalize( corner->base, srs, corner->base, mapSRS, _world2local, makeECEF ); transformAndLocalize( corner->roof, srs, corner->roof, mapSRS, _world2local, makeECEF ); } // Step 2 - Insert intermediate Corners as needed to satify texturing // requirements (if necessary) and record each corner offset (horizontal distance // from the beginning of the part geometry to the corner.) double cornerOffset = 0.0; double nextTexBoundary = texWidthM; for(Corners::iterator c = corners.begin(); c != corners.end(); ++c) { Corners::iterator this_corner = c; Corners::iterator next_corner = c; if ( ++next_corner == corners.end() ) next_corner = corners.begin(); osg::Vec3d base_vec = next_corner->base - this_corner->base; double span = base_vec.length(); this_corner->offsetX = cornerOffset; if (wallSkin) { base_vec /= span; // normalize osg::Vec3d roof_vec = next_corner->roof - this_corner->roof; roof_vec.normalize(); while(nextTexBoundary < cornerOffset+span) { // insert a new fake corner. Corners::iterator new_corner = corners.insert(next_corner, Corner()); new_corner->isFromSource = false; double advance = nextTexBoundary-cornerOffset; new_corner->base = this_corner->base + base_vec*advance; new_corner->roof = this_corner->roof + roof_vec*advance; new_corner->offsetX = cornerOffset + advance; nextTexBoundary += texWidthM; // advance the main iterator c = new_corner; } } cornerOffset += span; } // Step 3 - Calculate the angle of each corner. osg::Vec3d prev_vec; for(Corners::iterator c = corners.begin(); c != corners.end(); ++c) { Corners::const_iterator this_corner = c; Corners::const_iterator next_corner = c; if ( ++next_corner == corners.end() ) next_corner = corners.begin(); if ( this_corner == corners.begin() ) { Corners::const_iterator prev_corner = corners.end(); --prev_corner; prev_vec = this_corner->roof - prev_corner->roof; prev_vec.normalize(); } osg::Vec3d this_vec = next_corner->roof - this_corner->roof; this_vec.normalize(); if ( c != corners.begin() ) { c->cosAngle = prev_vec * this_vec; } } // Step 4 - Create faces connecting each pair of Posts. Faces& faces = elevation.faces; for(Corners::const_iterator c = corners.begin(); c != corners.end(); ++c) { Corners::const_iterator this_corner = c; Corners::const_iterator next_corner = c; if ( ++next_corner == corners.end() ) next_corner = corners.begin(); // only close the shape for polygons. if (next_corner != corners.begin() || structure.isPolygon) { faces.push_back(Face()); Face& face = faces.back(); face.left = *this_corner; face.right = *next_corner; // recalculate the final offset on the last face if ( next_corner == corners.begin() ) { osg::Vec3d vec = next_corner->roof - this_corner->roof; face.right.offsetX = face.left.offsetX + vec.length(); } face.widthM = next_corner->offsetX - this_corner->offsetX; } } } return true; }
osg::Node* GeometryCompiler::compile(FeatureList& workingSet, const Style& style, const FilterContext& context) { #ifdef PROFILING osg::Timer_t p_start = osg::Timer::instance()->tick(); unsigned p_features = workingSet.size(); #endif // for debugging/validation. std::vector<std::string> history; bool trackHistory = (_options.validate() == true); osg::ref_ptr<osg::Group> resultGroup = new osg::Group(); // create a filter context that will track feature data through the process FilterContext sharedCX = context; if ( !sharedCX.extent().isSet() && sharedCX.profile() ) { sharedCX.extent() = sharedCX.profile()->getExtent(); } // ref_ptr's to hold defaults in case we need them. osg::ref_ptr<PointSymbol> defaultPoint; osg::ref_ptr<LineSymbol> defaultLine; osg::ref_ptr<PolygonSymbol> defaultPolygon; // go through the Style and figure out which filters to use. const PointSymbol* point = style.get<PointSymbol>(); const LineSymbol* line = style.get<LineSymbol>(); const PolygonSymbol* polygon = style.get<PolygonSymbol>(); const ExtrusionSymbol* extrusion = style.get<ExtrusionSymbol>(); const AltitudeSymbol* altitude = style.get<AltitudeSymbol>(); const TextSymbol* text = style.get<TextSymbol>(); const MarkerSymbol* marker = style.get<MarkerSymbol>(); // to be deprecated const IconSymbol* icon = style.get<IconSymbol>(); const ModelSymbol* model = style.get<ModelSymbol>(); // Perform tessellation first. if ( line ) { if ( line->tessellation().isSet() ) { TemplateFeatureFilter<TessellateOperator> filter; filter.setNumPartitions( *line->tessellation() ); filter.setDefaultGeoInterp( _options.geoInterp().get() ); sharedCX = filter.push( workingSet, sharedCX ); if ( trackHistory ) history.push_back( "tessellation" ); } else if ( line->tessellationSize().isSet() ) { TemplateFeatureFilter<TessellateOperator> filter; filter.setMaxPartitionSize( *line->tessellationSize() ); filter.setDefaultGeoInterp( _options.geoInterp().get() ); sharedCX = filter.push( workingSet, sharedCX ); if ( trackHistory ) history.push_back( "tessellationSize" ); } } // if the style was empty, use some defaults based on the geometry type of the // first feature. if ( !point && !line && !polygon && !marker && !extrusion && !text && !model && !icon && workingSet.size() > 0 ) { Feature* first = workingSet.begin()->get(); Geometry* geom = first->getGeometry(); if ( geom ) { switch( geom->getComponentType() ) { case Geometry::TYPE_LINESTRING: case Geometry::TYPE_RING: defaultLine = new LineSymbol(); line = defaultLine.get(); break; case Geometry::TYPE_POINTSET: defaultPoint = new PointSymbol(); point = defaultPoint.get(); break; case Geometry::TYPE_POLYGON: defaultPolygon = new PolygonSymbol(); polygon = defaultPolygon.get(); break; case Geometry::TYPE_MULTI: case Geometry::TYPE_UNKNOWN: break; } } } // resample the geometry if necessary: if (_options.resampleMode().isSet()) { ResampleFilter resample; resample.resampleMode() = *_options.resampleMode(); if (_options.resampleMaxLength().isSet()) { resample.maxLength() = *_options.resampleMaxLength(); } sharedCX = resample.push( workingSet, sharedCX ); if ( trackHistory ) history.push_back( "resample" ); } // check whether we need to do elevation clamping: bool altRequired = _options.ignoreAltitudeSymbol() != true && altitude && ( altitude->clamping() != AltitudeSymbol::CLAMP_NONE || altitude->verticalOffset().isSet() || altitude->verticalScale().isSet() || altitude->script().isSet() ); // marker substitution -- to be deprecated in favor of model/icon if ( marker ) { if ( trackHistory ) history.push_back( "marker" ); // use a separate filter context since we'll be munging the data FilterContext markerCX = sharedCX; if ( marker->placement() == MarkerSymbol::PLACEMENT_RANDOM || marker->placement() == MarkerSymbol::PLACEMENT_INTERVAL ) { ScatterFilter scatter; scatter.setDensity( *marker->density() ); scatter.setRandom( marker->placement() == MarkerSymbol::PLACEMENT_RANDOM ); scatter.setRandomSeed( *marker->randomSeed() ); markerCX = scatter.push( workingSet, markerCX ); if ( trackHistory ) history.push_back( "scatter" ); } else if ( marker->placement() == MarkerSymbol::PLACEMENT_CENTROID ) { CentroidFilter centroid; markerCX = centroid.push( workingSet, markerCX ); if ( trackHistory ) history.push_back( "centroid" ); } if ( altRequired ) { AltitudeFilter clamp; clamp.setPropertiesFromStyle( style ); markerCX = clamp.push( workingSet, markerCX ); if ( trackHistory ) history.push_back( "altitude" ); // don't set this; we changed the input data. //altRequired = false; } SubstituteModelFilter sub( style ); sub.setClustering( *_options.clustering() ); sub.setUseDrawInstanced( *_options.instancing() ); if ( _options.featureName().isSet() ) sub.setFeatureNameExpr( *_options.featureName() ); osg::Node* node = sub.push( workingSet, markerCX ); if ( node ) { if ( trackHistory ) history.push_back( "substitute" ); resultGroup->addChild( node ); } } // instance substitution (replaces marker) else if ( model ) { const InstanceSymbol* instance = model ? (const InstanceSymbol*)model : (const InstanceSymbol*)icon; // use a separate filter context since we'll be munging the data FilterContext localCX = sharedCX; if ( trackHistory ) history.push_back( "model"); if ( instance->placement() == InstanceSymbol::PLACEMENT_RANDOM || instance->placement() == InstanceSymbol::PLACEMENT_INTERVAL ) { ScatterFilter scatter; scatter.setDensity( *instance->density() ); scatter.setRandom( instance->placement() == InstanceSymbol::PLACEMENT_RANDOM ); scatter.setRandomSeed( *instance->randomSeed() ); localCX = scatter.push( workingSet, localCX ); if ( trackHistory ) history.push_back( "scatter" ); } else if ( instance->placement() == InstanceSymbol::PLACEMENT_CENTROID ) { CentroidFilter centroid; localCX = centroid.push( workingSet, localCX ); if ( trackHistory ) history.push_back( "centroid" ); } if ( altRequired ) { AltitudeFilter clamp; clamp.setPropertiesFromStyle( style ); localCX = clamp.push( workingSet, localCX ); if ( trackHistory ) history.push_back( "altitude" ); } SubstituteModelFilter sub( style ); // activate clustering sub.setClustering( *_options.clustering() ); // activate draw-instancing sub.setUseDrawInstanced( *_options.instancing() ); // activate feature naming if ( _options.featureName().isSet() ) sub.setFeatureNameExpr( *_options.featureName() ); osg::Node* node = sub.push( workingSet, localCX ); if ( node ) { if ( trackHistory ) history.push_back( "substitute" ); resultGroup->addChild( node ); // enable auto scaling on the group? if ( model && model->autoScale() == true ) { resultGroup->getOrCreateStateSet()->setRenderBinDetails(0, osgEarth::AUTO_SCALE_BIN ); } } } // extruded geometry if ( extrusion ) { if ( altRequired ) { AltitudeFilter clamp; clamp.setPropertiesFromStyle( style ); sharedCX = clamp.push( workingSet, sharedCX ); if ( trackHistory ) history.push_back( "altitude" ); altRequired = false; } ExtrudeGeometryFilter extrude; extrude.setStyle( style ); // apply per-feature naming if requested. if ( _options.featureName().isSet() ) extrude.setFeatureNameExpr( *_options.featureName() ); if ( _options.mergeGeometry().isSet() ) extrude.setMergeGeometry( *_options.mergeGeometry() ); osg::Node* node = extrude.push( workingSet, sharedCX ); if ( node ) { if ( trackHistory ) history.push_back( "extrude" ); resultGroup->addChild( node ); } } // simple geometry else if ( point || line || polygon ) { if ( altRequired ) { AltitudeFilter clamp; clamp.setPropertiesFromStyle( style ); sharedCX = clamp.push( workingSet, sharedCX ); if ( trackHistory ) history.push_back( "altitude" ); altRequired = false; } BuildGeometryFilter filter( style ); filter.maxGranularity() = *_options.maxGranularity(); filter.geoInterp() = *_options.geoInterp(); if ( _options.featureName().isSet() ) filter.featureName() = *_options.featureName(); osg::Node* node = filter.push( workingSet, sharedCX ); if ( node ) { if ( trackHistory ) history.push_back( "geometry" ); resultGroup->addChild( node ); } } if ( text || icon ) { if ( altRequired ) { AltitudeFilter clamp; clamp.setPropertiesFromStyle( style ); sharedCX = clamp.push( workingSet, sharedCX ); if ( trackHistory ) history.push_back( "altitude" ); altRequired = false; } BuildTextFilter filter( style ); osg::Node* node = filter.push( workingSet, sharedCX ); if ( node ) { if ( trackHistory ) history.push_back( "text" ); resultGroup->addChild( node ); } } if (Registry::capabilities().supportsGLSL()) { if ( _options.shaderPolicy() == SHADERPOLICY_GENERATE ) { // no ss cache because we will optimize later. Registry::shaderGenerator().run( resultGroup.get(), "osgEarth.GeomCompiler" ); } else if ( _options.shaderPolicy() == SHADERPOLICY_DISABLE ) { resultGroup->getOrCreateStateSet()->setAttributeAndModes( new osg::Program(), osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE ); if ( trackHistory ) history.push_back( "no shaders" ); } } // Optimize stateset sharing. if ( _options.optimizeStateSharing() == true ) { // Common state set cache? osg::ref_ptr<StateSetCache> sscache; if ( sharedCX.getSession() ) { // with a shared cache, don't combine statesets. They may be // in the live graph sscache = sharedCX.getSession()->getStateSetCache(); sscache->consolidateStateAttributes( resultGroup.get() ); } else { // isolated: perform full optimization sscache = new StateSetCache(); sscache->optimize( resultGroup.get() ); } if ( trackHistory ) history.push_back( "share state" ); } if ( _options.optimize() == true ) { OE_DEBUG << LC << "optimize begin" << std::endl; // Run the optimizer on the resulting graph int optimizations = osgUtil::Optimizer::FLATTEN_STATIC_TRANSFORMS | osgUtil::Optimizer::REMOVE_REDUNDANT_NODES | osgUtil::Optimizer::COMBINE_ADJACENT_LODS | osgUtil::Optimizer::SHARE_DUPLICATE_STATE | osgUtil::Optimizer::MERGE_GEOMETRY | osgUtil::Optimizer::CHECK_GEOMETRY | osgUtil::Optimizer::MERGE_GEODES | osgUtil::Optimizer::STATIC_OBJECT_DETECTION; osgUtil::Optimizer opt; opt.optimize(resultGroup.get(), optimizations); OE_DEBUG << LC << "optimize complete" << std::endl; if ( trackHistory ) history.push_back( "optimize" ); } //test: dump the tile to disk //osgDB::writeNodeFile( *(resultGroup.get()), "out.osg" ); #ifdef PROFILING static double totalTime = 0.0; static Threading::Mutex totalTimeMutex; osg::Timer_t p_end = osg::Timer::instance()->tick(); double t = osg::Timer::instance()->delta_s(p_start, p_end); totalTimeMutex.lock(); totalTime += t; totalTimeMutex.unlock(); OE_INFO << LC << "features = " << p_features << ", time = " << t << " s. cummulative = " << totalTime << " s." << std::endl; #endif if ( _options.validate() == true ) { OE_NOTICE << LC << "-- Start Debugging --\n"; std::stringstream buf; buf << "HISTORY "; for(std::vector<std::string>::iterator h = history.begin(); h != history.end(); ++h) buf << ".. " << *h; OE_NOTICE << LC << buf.str() << "\n"; osgEarth::GeometryValidator validator; resultGroup->accept(validator); OE_NOTICE << LC << "-- End Debugging --\n"; } return resultGroup.release(); }
void ExtrudeGeometryFilter::reset( const FilterContext& context ) { _cosWallAngleThresh = cos( _wallAngleThresh_deg ); _geodes.clear(); if ( _styleDirty ) { const StyleSheet* sheet = context.getSession() ? context.getSession()->styles() : 0L; _wallSkinSymbol = 0L; _wallPolygonSymbol = 0L; _roofSkinSymbol = 0L; _roofPolygonSymbol = 0L; _extrusionSymbol = 0L; _outlineSymbol = 0L; _extrusionSymbol = _style.get<ExtrusionSymbol>(); if ( _extrusionSymbol.valid() ) { // make a copy of the height expression so we can use it: if ( _extrusionSymbol->heightExpression().isSet() ) { _heightExpr = *_extrusionSymbol->heightExpression(); } // If there is no height expression, and we have either absolute or terrain-relative // clamping, THAT means that we want to extrude DOWN from the geometry to the ground // (instead of from the geometry.) AltitudeSymbol* alt = _style.get<AltitudeSymbol>(); if ( alt && !_extrusionSymbol->heightExpression().isSet() && !_extrusionSymbol->height().isSet() ) { if (alt->clamping() == AltitudeSymbol::CLAMP_ABSOLUTE || alt->clamping() == AltitudeSymbol::CLAMP_RELATIVE_TO_TERRAIN ) { _heightExpr = NumericExpression( "0-[__max_hat]" ); } } // attempt to extract the wall symbols: if ( _extrusionSymbol->wallStyleName().isSet() && sheet != 0L ) { const Style* wallStyle = sheet->getStyle( *_extrusionSymbol->wallStyleName(), false ); if ( wallStyle ) { _wallSkinSymbol = wallStyle->get<SkinSymbol>(); _wallPolygonSymbol = wallStyle->get<PolygonSymbol>(); } } // attempt to extract the rooftop symbols: if ( _extrusionSymbol->roofStyleName().isSet() && sheet != 0L ) { const Style* roofStyle = sheet->getStyle( *_extrusionSymbol->roofStyleName(), false ); if ( roofStyle ) { _roofSkinSymbol = roofStyle->get<SkinSymbol>(); _roofPolygonSymbol = roofStyle->get<PolygonSymbol>(); } } // if there's a line symbol, use it to outline the extruded data. _outlineSymbol = _style.get<LineSymbol>(); } // backup plan for skin symbols: const SkinSymbol* skin = _style.get<SkinSymbol>(); if ( skin ) { if ( !_wallSkinSymbol.valid() ) _wallSkinSymbol = skin; if ( !_roofSkinSymbol.valid() ) _roofSkinSymbol = skin; } // backup plan for poly symbols: const PolygonSymbol* poly = _style.get<PolygonSymbol>(); if ( poly ) { if ( !_wallPolygonSymbol.valid() ) _wallPolygonSymbol = poly; if ( !_roofPolygonSymbol.valid() ) _roofPolygonSymbol = poly; } _styleDirty = false; } }
bool SubstituteModelFilter::process(const FeatureList& features, const InstanceSymbol* symbol, Session* session, osg::Group* attachPoint, FilterContext& context ) { // Establish SRS information: bool makeECEF = context.getSession()->getMapInfo().isGeocentric(); const SpatialReference* targetSRS = context.getSession()->getMapInfo().getSRS(); // first, go through the features and build the model cache. Apply the model matrix' scale // factor to any AutoTransforms directly (cloning them as necessary) std::map< std::pair<URI, float>, osg::ref_ptr<osg::Node> > uniqueModels; // URI cache speeds up URI creation since it can be slow. osgEarth::fast_map<std::string, URI> uriCache; // keep track of failed URIs so we don't waste time or warning messages on them std::set< URI > missing; StringExpression uriEx = *symbol->url(); NumericExpression scaleEx = *symbol->scale(); const ModelSymbol* modelSymbol = dynamic_cast<const ModelSymbol*>(symbol); const IconSymbol* iconSymbol = dynamic_cast<const IconSymbol*> (symbol); NumericExpression headingEx; NumericExpression scaleXEx; NumericExpression scaleYEx; NumericExpression scaleZEx; if ( modelSymbol ) { headingEx = *modelSymbol->heading(); scaleXEx = *modelSymbol->scaleX(); scaleYEx = *modelSymbol->scaleY(); scaleZEx = *modelSymbol->scaleZ(); } for( FeatureList::const_iterator f = features.begin(); f != features.end(); ++f ) { Feature* input = f->get(); // Run a feature pre-processing script. if ( symbol->script().isSet() ) { StringExpression scriptExpr(symbol->script().get()); input->eval( scriptExpr, &context ); } // evaluate the instance URI expression: const std::string& st = input->eval(uriEx, &context); URI& instanceURI = uriCache[st]; if(instanceURI.empty()) // Create a map, to reuse URI's, since they take a long time to create { instanceURI = URI( st, uriEx.uriContext() ); } // find the corresponding marker in the cache osg::ref_ptr<InstanceResource> instance; if ( !findResource(instanceURI, symbol, context, missing, instance) ) continue; // evalute the scale expression (if there is one) float scale = 1.0f; osg::Vec3d scaleVec(1.0, 1.0, 1.0); osg::Matrixd scaleMatrix; if ( symbol->scale().isSet() ) { scale = input->eval( scaleEx, &context ); scaleVec.set(scale, scale, scale); } if ( modelSymbol ) { if ( modelSymbol->scaleX().isSet() ) { scaleVec.x() *= input->eval( scaleXEx, &context ); } if ( modelSymbol->scaleY().isSet() ) { scaleVec.y() *= input->eval( scaleYEx, &context ); } if ( modelSymbol->scaleZ().isSet() ) { scaleVec.z() *= input->eval( scaleZEx, &context ); } } if ( scaleVec.x() == 0.0 ) scaleVec.x() = 1.0; if ( scaleVec.y() == 0.0 ) scaleVec.y() = 1.0; if ( scaleVec.z() == 0.0 ) scaleVec.z() = 1.0; scaleMatrix = osg::Matrix::scale( scaleVec ); osg::Matrixd rotationMatrix; if ( modelSymbol && modelSymbol->heading().isSet() ) { float heading = input->eval(headingEx, &context); rotationMatrix.makeRotate( osg::Quat(osg::DegreesToRadians(heading), osg::Vec3(0,0,1)) ); } // how that we have a marker source, create a node for it std::pair<URI,float> key( instanceURI, iconSymbol? scale : 1.0f ); //use 1.0 for models, since we don't want unique models based on scaling // cache nodes per instance. osg::ref_ptr<osg::Node>& model = uniqueModels[key]; if ( !model.valid() ) { // Always clone the cached instance so we're not processing data that's // already in the scene graph. -gw context.resourceCache()->cloneOrCreateInstanceNode(instance.get(), model, context.getDBOptions()); // if icon decluttering is off, install an AutoTransform. if ( iconSymbol ) { if ( iconSymbol->declutter() == true ) { ScreenSpaceLayout::activate(model->getOrCreateStateSet()); } else if ( dynamic_cast<osg::AutoTransform*>(model.get()) == 0L ) { osg::AutoTransform* at = new osg::AutoTransform(); at->setAutoRotateMode( osg::AutoTransform::ROTATE_TO_SCREEN ); at->setAutoScaleToScreen( true ); at->addChild( model ); model = at; } } } if ( model.valid() ) { GeometryIterator gi( input->getGeometry(), false ); while( gi.hasMore() ) { Geometry* geom = gi.next(); // if necessary, transform the points to the target SRS: if ( !makeECEF && !targetSRS->isEquivalentTo(context.profile()->getSRS()) ) { context.profile()->getSRS()->transform( geom->asVector(), targetSRS ); } for( unsigned i=0; i<geom->size(); ++i ) { osg::Matrixd mat; // need to recalcluate expression-based data per-point, not just per-feature! float scale = 1.0f; osg::Vec3d scaleVec(1.0, 1.0, 1.0); osg::Matrixd scaleMatrix; if ( symbol->scale().isSet() ) { scale = input->eval( scaleEx, &context ); scaleVec.set(scale, scale, scale); } if ( modelSymbol ) { if ( modelSymbol->scaleX().isSet() ) { scaleVec.x() *= input->eval( scaleXEx, &context ); } if ( modelSymbol->scaleY().isSet() ) { scaleVec.y() *= input->eval( scaleYEx, &context ); } if ( modelSymbol->scaleZ().isSet() ) { scaleVec.z() *= input->eval( scaleZEx, &context ); } } if ( scaleVec.x() == 0.0 ) scaleVec.x() = 1.0; if ( scaleVec.y() == 0.0 ) scaleVec.y() = 1.0; if ( scaleVec.z() == 0.0 ) scaleVec.z() = 1.0; scaleMatrix = osg::Matrix::scale( scaleVec ); if ( modelSymbol->heading().isSet() ) { float heading = input->eval(headingEx, &context); rotationMatrix.makeRotate( osg::Quat(osg::DegreesToRadians(heading), osg::Vec3(0,0,1)) ); } osg::Vec3d point = (*geom)[i]; if ( makeECEF ) { // the "rotation" element lets us re-orient the instance to ensure it's pointing up. We // could take a shortcut and just use the current extent's local2world matrix for this, // but if the tile is big enough the up vectors won't be quite right. osg::Matrixd rotation; ECEF::transformAndGetRotationMatrix( point, context.profile()->getSRS(), point, targetSRS, rotation ); mat = scaleMatrix * rotationMatrix * rotation * osg::Matrixd::translate( point ) * _world2local; } else { mat = scaleMatrix * rotationMatrix * osg::Matrixd::translate( point ) * _world2local; } osg::MatrixTransform* xform = new osg::MatrixTransform(); xform->setMatrix( mat ); xform->setDataVariance( osg::Object::STATIC ); xform->addChild( model.get() ); attachPoint->addChild( xform ); // Only tag nodes if we aren't using clustering. if ( context.featureIndex() && !_cluster) { context.featureIndex()->tagNode( xform, input ); } // name the feature if necessary if ( !_featureNameExpr.empty() ) { const std::string& name = input->eval( _featureNameExpr, &context); if ( !name.empty() ) xform->setName( name ); } } } } } if ( iconSymbol ) { // activate decluttering for icons if requested if ( iconSymbol->declutter() == true ) { ScreenSpaceLayout::activate(attachPoint->getOrCreateStateSet()); } // activate horizon culling if we are in geocentric space if ( context.getSession() && context.getSession()->getMapInfo().isGeocentric() ) { // should this use clipping, or a horizon cull callback? //HorizonCullingProgram::install( attachPoint->getOrCreateStateSet() ); attachPoint->getOrCreateStateSet()->setMode(GL_CLIP_DISTANCE0, 1); } } // active DrawInstanced if required: if ( _useDrawInstanced ) { DrawInstanced::convertGraphToUseDrawInstanced( attachPoint ); // install a shader program to render draw-instanced. DrawInstanced::install( attachPoint->getOrCreateStateSet() ); } return true; }