diff --git a/src/applications/osgearth_atlas/osgearth_atlas.cpp b/src/applications/osgearth_atlas/osgearth_atlas.cpp index 47d1d312a8..5d688f07f8 100644 --- a/src/applications/osgearth_atlas/osgearth_atlas.cpp +++ b/src/applications/osgearth_atlas/osgearth_atlas.cpp @@ -263,7 +263,7 @@ show(osg::ArgumentParser& arguments) if (drawLabels) { osgText::Text* label = new osgText::Text(); - label->setText(skins[k]->name()); + label->setText(skins[k]->name().value()); label->setPosition(osg::Vec3(x+0.5*s, -0.005f, y+0.5*t)); label->setAlignment(label->CENTER_CENTER); label->setAutoRotateToScreen(true); diff --git a/src/osgEarth/BuildGeometryFilter b/src/osgEarth/BuildGeometryFilter index bfe708e1c6..a3ea54fdeb 100644 --- a/src/osgEarth/BuildGeometryFilter +++ b/src/osgEarth/BuildGeometryFilter @@ -16,9 +16,7 @@ * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see */ - -#ifndef OSGEARTHFEATURES_BUILD_GEOMETRY_FILTER_H -#define OSGEARTHFEATURES_BUILD_GEOMETRY_FILTER_H 1 +#pragma once #include #include @@ -119,14 +117,7 @@ namespace osgEarth { namespace Util bool makeECEF, bool tessellate, osg::Geometry* osgGeom, - const osg::Matrixd &world2local); - - void buildPolygon( - Geometry* input, - const SpatialReference* featureSRS, - const SpatialReference* mapSRS, - bool makeECEF, - osg::Geometry* osgGeom, + const SkinResource* skinResource, const osg::Matrixd &world2local); osg::Geode* processPolygons (FeatureList& input, FilterContext& cx); @@ -136,5 +127,3 @@ namespace osgEarth { namespace Util osg::Geode* processMeshes(FeatureList& input, FilterContext& cx); }; } } - -#endif // OSGEARTHFEATURES_BUILD_GEOMETRY_FILTER_H diff --git a/src/osgEarth/BuildGeometryFilter.cpp b/src/osgEarth/BuildGeometryFilter.cpp index 89a0be4b95..03e899ab16 100644 --- a/src/osgEarth/BuildGeometryFilter.cpp +++ b/src/osgEarth/BuildGeometryFilter.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -255,6 +256,43 @@ BuildGeometryFilter::processPolygons(FeatureList& features, FilterContext& conte makeECEF = context.getOutputSRS()->isGeographic(); } + // if there's a skin, set that up. + // (TODO? only works for stylesheet-level symbols, not per-feature symbols) + auto* skin_symbol = _style.get(); + osg::ref_ptr skin_res = nullptr; + osg::ref_ptr skin_stateset; + + if (skin_symbol && skin_symbol->library().isSet()) + { + auto sheet = context.getSession() ? context.getSession()->styles() : nullptr; + if (sheet) + { + osg::ref_ptr res_lib = sheet->getResourceLibrary(skin_symbol->library().value()); + if (res_lib.valid()) + { + // TODO: move this into the feature loop to support per-feature styling... + skin_res = res_lib->getSkin(skin_symbol->name()->eval(), context.getDBOptions()); + if (skin_res) + { + context.resourceCache()->getOrCreateStateSet(skin_res, skin_stateset, context.getDBOptions()); + } + else + { + OE_WARN << LC << "Unable to find skin '" << skin_symbol->name()->eval() << "'" + << "in library; geometry will have no textures." << std::endl; + skin_symbol = nullptr; + } + } + else + { + OE_WARN << LC << "Unable to load resource library '" << skin_symbol->library().value() << "'" + << "; geometry will have no textures." << std::endl; + skin_symbol = nullptr; + } + } + } + + // ready, time to iterate the features. for( FeatureList::iterator f = features.begin(); f != features.end(); ++f ) { Feature* input = f->get(); @@ -306,6 +344,11 @@ BuildGeometryFilter::processPolygons(FeatureList& features, FilterContext& conte osgGeom->setName( name ); } + // apply the skin if there is one. + if (skin_stateset.valid()) + { + osgGeom->setStateSet(skin_stateset); + } // compute localizing matrices or use globals osg::Matrixd w2l, l2w; @@ -327,7 +370,7 @@ BuildGeometryFilter::processPolygons(FeatureList& features, FilterContext& conte hats->push_back( i->z() ); // build the geometry: - tileAndBuildPolygon(part, featureSRS, outputSRS, makeECEF, true, osgGeom.get(), w2l); + tileAndBuildPolygon(part, featureSRS, outputSRS, makeECEF, true, osgGeom.get(), skin_res, w2l); osg::Vec3Array* allPoints = static_cast(osgGeom->getVertexArray()); if (allPoints && allPoints->size() > 0) @@ -378,7 +421,7 @@ BuildGeometryFilter::processPolygons(FeatureList& features, FilterContext& conte } else { - OE_TEST << LC << "Oh no. buildAndTilePolygon returned nothing.\n"; + OE_TEST << LC << "Oh no. tileAndBuildPolygon returned nothing.\n"; } } } @@ -1135,14 +1178,26 @@ BuildGeometryFilter::tileAndBuildPolygon( bool makeECEF, bool tessellate, osg::Geometry* osgGeom, + const SkinResource* skin_res, const osg::Matrixd& world2local) { OE_SOFT_ASSERT_AND_RETURN(input != nullptr, void()); OE_SOFT_ASSERT_AND_RETURN(input->getType() != Geometry::TYPE_MULTI, void()); + const double circum = 40041000.0; //m + double ref_x = 0.0, ref_y = 0.0; + osg::ref_ptr verts = new osg::Vec3Array(); verts->reserve(input->getTotalPointCount()); + osg::ref_ptr uvs; + if (skin_res) + { + uvs = new osg::Vec2Array(); + uvs->reserve(verts->size()); + osgGeom->setTexCoordArray(0, uvs.get()); + } + // hard copy so we can project the values osg::ref_ptr proj = input->clone(); @@ -1174,6 +1229,7 @@ BuildGeometryFilter::tileAndBuildPolygon( // of the centroid) and that is way too slow. auto local_geom = proj; // working copy Bounds local_ex; + Bounds geo_ex; double z = -DBL_MAX; GeometryIterator iter(local_geom.get()); while (iter.hasMore()) @@ -1182,12 +1238,23 @@ BuildGeometryFilter::tileAndBuildPolygon( inputSRS->transform(part->asVector(), outputSRS); // to geographic for (auto& p : *part) { + geo_ex.expandBy(p); geo_to_gnomonic(p, centroid, gnomonic_scale); local_ex.expandBy(p); z = std::max(z, p.z()); } } + // calculate a UV reference point close to the centroid of the geometry. + if (skin_res) + { + auto geo_ex_anchor = geo_ex.center(); + auto x = circum * (geo_ex_anchor.x() + 180.0) / 360.0; + auto y = (0.5 * circum) * (geo_ex_anchor.y() + 90.0) / 180.0; + ref_x = x - fmod(x, skin_res->imageWidth().value()); + ref_y = y - fmod(y, skin_res->imageHeight().value()); + } + // start with a weemesh covering the feature extent. weemesh::mesh_t m; const int marker = 0; @@ -1264,12 +1331,21 @@ BuildGeometryFilter::tileAndBuildPolygon( // Finally we convert from gnomonic back to localized tile coordinates. osg::ref_ptr new_verts = new osg::Vec3Array(); new_verts->reserve(m.verts.size()); - osg::Vec3d temp; - for (auto& v : m.verts) + osg::Vec3d ecef; + for (auto& vert : m.verts) { - gnomonic_to_geo(v, centroid, gnomonic_scale); // to geographic - outputSRS->transformToWorld(osg::Vec3d(v.x, v.y, v.z), temp); // to ECEF - new_verts->push_back(temp * world2local); // localized to tile + gnomonic_to_geo(vert, centroid, gnomonic_scale); // to geographic + outputSRS->transformToWorld(osg::Vec3d(vert.x, vert.y, vert.z), ecef); // to ECEF + new_verts->push_back(ecef* world2local); // localized to tile + + if (uvs) + { + auto x = circum * (vert.x + 180.0) / 360.0; + auto y = (0.5 * circum) * (vert.y + 90.0) / 180.0; + auto u = (x - ref_x) / skin_res->imageWidth().value(); + auto v = (y - ref_y) / (skin_res->imageHeight().value()); + uvs->push_back(osg::Vec2f(u, v)); + } } // Assemble the final geometry. @@ -1291,13 +1367,14 @@ BuildGeometryFilter::tileAndBuildPolygon( { // original tesselation approach Tessellator::Plane plane = Tessellator::PLANE_XY; + Bounds geo_ex; if (outputSRS) { // for geographic data we need to project into 2D before tessellating: if (outputSRS->isGeographic()) { - osg::Vec3d temp; + osg::Vec3d geo; osg::BoundingBoxd ecef_bb; bool allOnEquator = true; @@ -1308,16 +1385,27 @@ BuildGeometryFilter::tileAndBuildPolygon( part->open(); for (osg::Vec3d& p : *part) { - inputSRS->transform(p, outputSRS, temp); - if (temp.y() != 0.0) + inputSRS->transform(p, outputSRS, geo); + if (geo.y() != 0.0) { allOnEquator = false; } - outputSRS->transformToWorld(temp, p); + geo_ex.expandBy(geo); + outputSRS->transformToWorld(geo, p); ecef_bb.expandBy(p); } } + // calculate a UV reference point close to the centroid of the geometry. + if (skin_res) + { + auto geo_ex_anchor = geo_ex.center(); + auto x = circum * (geo_ex_anchor.x() + 180.0) / 360.0; + auto y = (0.5*circum) * (geo_ex_anchor.y() + 90.0) / 180.0; + ref_x = x - fmod(x, skin_res->imageWidth().value()); + ref_y = y - fmod(y, skin_res->imageHeight().value()); + } + const osg::Vec3d& center = ecef_bb.center(); GeometryIterator proj_iter(proj.get(), true); @@ -1371,16 +1459,26 @@ BuildGeometryFilter::tileAndBuildPolygon( if (outputSRS && outputSRS->isGeographic()) { + osg::Vec3d geo; ConstGeometryIterator verts_iter(input, true); while (verts_iter.hasMore()) { const Geometry* part = verts_iter.next(); for (const auto& p : *part) { - inputSRS->transform(p, outputSRS, temp); - outputSRS->transformToWorld(temp, vert); + inputSRS->transform(p, outputSRS, geo); + outputSRS->transformToWorld(geo, vert); vert = vert * world2local; verts->push_back(vert); + + if (uvs) + { + auto x = circum * (geo.x() + 180.0) / 360.0; + auto y = (0.5*circum) * (geo.y() + 90.0) / 180.0; + auto u = (x - ref_x) / skin_res->imageWidth().value(); + auto v = (y - ref_y) / (skin_res->imageHeight().value()); + uvs->push_back(osg::Vec2f(u, v)); + } } } } @@ -1393,6 +1491,9 @@ BuildGeometryFilter::tileAndBuildPolygon( for (const auto& p : *part) { verts->push_back(p * world2local); + + //TODO + uvs->push_back(osg::Vec2f(0, 0)); } } } @@ -1531,175 +1632,6 @@ BuildGeometryFilter::tileAndBuildPolygon(Geometry* ring, } #endif -// builds and tessellates a polygon (with or without holes) -void -BuildGeometryFilter::buildPolygon(Geometry* ring, - const SpatialReference* featureSRS, - const SpatialReference* outputSRS, - bool makeECEF, - osg::Geometry* osgGeom, - const osg::Matrixd &world2local) -{ - if ( !ring->isValid() ) - return; - - ring->rewind(osgEarth::Geometry::ORIENTATION_CCW); - - osg::ref_ptr allPoints = new osg::Vec3Array(); - transformAndLocalize( ring->asVector(), featureSRS, allPoints.get(), outputSRS, world2local, makeECEF ); - - Polygon* poly = dynamic_cast(ring); - if ( poly ) - { - RingCollection ordered(poly->getHoles().begin(), poly->getHoles().end()); - std::sort(ordered.begin(), ordered.end(), holeCompare); - - for( RingCollection::const_iterator h = ordered.begin(); h != ordered.end(); ++h ) - { - Geometry* hole = h->get(); - if ( hole->isValid() ) - { - hole->rewind(osgEarth::Geometry::ORIENTATION_CW); - - osg::ref_ptr holePoints = new osg::Vec3Array(); - transformAndLocalize( hole->asVector(), featureSRS, holePoints.get(), outputSRS, world2local, makeECEF ); - - // find the point with the highest x value - unsigned int hCursor = 0; - for (unsigned int i=1; i < holePoints->size(); i++) - { - if ((*holePoints)[i].x() > (*holePoints)[hCursor].x()) - hCursor = i; - } - - double x1 = (*holePoints)[hCursor].x(); - double y1 = (*holePoints)[hCursor].y(); - double y2 = (*holePoints)[hCursor].y(); - - unsigned int edgeCursor = UINT_MAX; - double edgeDistance = DBL_MAX; - unsigned int foundPointCursor = UINT_MAX; - for (unsigned int i=0; i < allPoints->size(); i++) - { - unsigned int next = i == allPoints->size() - 1 ? 0 : i + 1; - double xMax = osg::maximum((*allPoints)[i].x(), (*allPoints)[next].x()); - - if (xMax > (*holePoints)[hCursor].x()) - { - double x2 = xMax + 1.0; - double x3 = (*allPoints)[i].x(); - double y3 = (*allPoints)[i].y(); - double x4 = (*allPoints)[next].x(); - double y4 = (*allPoints)[next].y(); - - double xi=0.0, yi=0.0; - bool intersects = false; - unsigned int hitPointCursor = UINT_MAX; - if (y1 == y3 && x3 > x1) - { - xi = x3; - hitPointCursor = i; - intersects = true; - } - else if (y1 == y4 && x4 > x1) - { - xi = x4; - hitPointCursor = next; - intersects = true; - } - else if (segmentsIntersect(x1, y1, x2, y2, x3, y3, x4, y4, xi, yi)) - { - intersects = true; - } - - double dist = (osg::Vec2d(xi, yi) - osg::Vec2d(x1, y1)).length(); - if (intersects && dist < edgeDistance) - { - foundPointCursor = hitPointCursor; - edgeCursor = hitPointCursor != UINT_MAX ? hitPointCursor : (x3 >= x4 ? i : next); - edgeDistance = dist; - } - } - } - - if (foundPointCursor == UINT_MAX && edgeCursor != UINT_MAX) - { - // test for intersecting edges between x1 and x2 - // (skipping the two segments for which edgeCursor is a vert) - - double x2 = (*allPoints)[edgeCursor].x(); - y2 = (*allPoints)[edgeCursor].y(); - - bool foundIntersection = false; - for (unsigned int i=0; i < allPoints->size(); i++) - { - unsigned int next = i == allPoints->size() - 1 ? 0 : i + 1; - - if (i == edgeCursor || next == edgeCursor) - continue; - - double x3 = (*allPoints)[i].x(); - double y3 = (*allPoints)[i].y(); - double x4 = (*allPoints)[next].x(); - double y4 = (*allPoints)[next].y(); - - foundIntersection = foundIntersection || segmentsIntersect(x1, y1, x2, y2, x3, y3, x4, y4); - - if (foundIntersection) - { - unsigned int prev = i == 0 ? allPoints->size() - 1 : i - 1; - - if (!isCCW((*allPoints)[prev].x(), (*allPoints)[prev].y(), x3, y3, x4, y4)) - { - edgeCursor = i; - x2 = (*allPoints)[edgeCursor].x(); - y2 = (*allPoints)[edgeCursor].y(); - foundIntersection = false; - } - } - - } - } - - if (edgeCursor != UINT_MAX) - { - // build array of correctly ordered new points to add to the outer loop - osg::ref_ptr insertPoints = new osg::Vec3Array(); - insertPoints->reserve(holePoints->size() + 2); - - unsigned int p = hCursor; - do - { - insertPoints->push_back((*holePoints)[p]); - p = p == holePoints->size() - 1 ? 0 : p + 1; - } while(p != hCursor); - - insertPoints->push_back((*holePoints)[hCursor]); - insertPoints->push_back((*allPoints)[edgeCursor]); - - // insert new points into outer loop - osg::Vec3Array::iterator it = edgeCursor == allPoints->size() - 1 ? allPoints->end() : allPoints->begin() + (edgeCursor + 1); - allPoints->insert(it, insertPoints->begin(), insertPoints->end()); - } - } - } - } - - GLenum mode = GL_LINE_LOOP; - if ( osgGeom->getVertexArray() == 0L ) - { - osgGeom->addPrimitiveSet( new osg::DrawArrays( mode, 0, allPoints->size() ) ); - osgGeom->setVertexArray( allPoints.get() ); - } - else - { - osg::Vec3Array* v = static_cast(osgGeom->getVertexArray()); - osgGeom->addPrimitiveSet( new osg::DrawArrays( mode, v->size(), allPoints->size() ) ); - //v->reserve(v->size() + allPoints->size()); - std::copy(allPoints->begin(), allPoints->end(), std::back_inserter(*v)); - } -} - namespace { diff --git a/src/osgEarth/FeatureRasterizer.cpp b/src/osgEarth/FeatureRasterizer.cpp index c754467cea..cdb018e530 100644 --- a/src/osgEarth/FeatureRasterizer.cpp +++ b/src/osgEarth/FeatureRasterizer.cpp @@ -556,7 +556,7 @@ namespace osgEarth { osg::ref_ptr< osg::Image > image = skin->image().get(); if (!image.valid()) { - image = skin->createImage(nullptr); + image = skin->createColorImage(nullptr); } if (image.valid()) { diff --git a/src/osgEarth/PBRMaterial b/src/osgEarth/PBRMaterial index 0dbeae72c3..a55f39ed73 100644 --- a/src/osgEarth/PBRMaterial +++ b/src/osgEarth/PBRMaterial @@ -77,6 +77,18 @@ namespace osgEarth conf.set("opacity", opacity()); return conf; } + + //! Returns true if the material is "simple" (i.e., has only a color map) + bool isSimple() const + { + return color().isSet() && + !normal().isSet() && + !roughness().isSet() && + !ao().isSet() && + !metal().isSet() && + !displacement().isSet() && + !opacity().isSet(); + } }; /** diff --git a/src/osgEarth/PolygonSymbol b/src/osgEarth/PolygonSymbol index 65591fa875..96bf9448d6 100644 --- a/src/osgEarth/PolygonSymbol +++ b/src/osgEarth/PolygonSymbol @@ -16,12 +16,11 @@ * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see */ - -#ifndef OSGEARTHSYMBOLOGY_GEOMETRY_SYMBOL_H -#define OSGEARTHSYMBOLOGY_GEOMETRY_SYMBOL_H 1 +#pragma once #include #include +#include namespace osgEarth { @@ -33,31 +32,26 @@ namespace osgEarth public: META_Object(osgEarth, PolygonSymbol); - PolygonSymbol(const PolygonSymbol& rhs,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY); - PolygonSymbol( const Config& conf =Config() ); + PolygonSymbol(const PolygonSymbol& rhs, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY); + PolygonSymbol(const Config& conf = Config()); /** dtor */ virtual ~PolygonSymbol() { } - /** Polygon fill properties. */ - optional& fill() { return _fill; } - const optional& fill() const { return _fill; } + //! Polygon fill properties. + OE_OPTION(Fill, fill); + + //! Whether to use a LineStyle (if one exists in the Style) as an outline. + //! This defaults to true, but you set this to false to suppress outlining + //! even when a LineStyle exists. + OE_OPTION(bool, outline, true); - /** Whether to use a LineStyle (if one exists in the Style) as an outline. - * This defaults to true, but you set this to false to suppress outlining - * even when a LineStyle exists. */ - optional& outline() { return _outline; } - const optional& outline() const { return _outline; } + //! URI of material to use to texture polygon geometries + OE_OPTION(URI, material); public: virtual Config getConfig() const; virtual void mergeConfig(const Config& conf); static void parseSLD(const Config& c, class Style& style); - - protected: - optional _fill; - optional _outline; }; } // namespace osgEarth - -#endif // OSGEARTH_SYMBOLOGY_SYMBOL_H diff --git a/src/osgEarth/PolygonSymbol.cpp b/src/osgEarth/PolygonSymbol.cpp index fcba0eb792..9e9440fa43 100644 --- a/src/osgEarth/PolygonSymbol.cpp +++ b/src/osgEarth/PolygonSymbol.cpp @@ -23,49 +23,55 @@ using namespace osgEarth; OSGEARTH_REGISTER_SIMPLE_SYMBOL(polygon, PolygonSymbol); -PolygonSymbol::PolygonSymbol(const PolygonSymbol& rhs,const osg::CopyOp& copyop): -Symbol(rhs, copyop), -_fill(rhs._fill), -_outline(rhs._outline) +PolygonSymbol::PolygonSymbol(const PolygonSymbol& rhs, const osg::CopyOp& copyop) : + Symbol(rhs, copyop), + _fill(rhs._fill), + _outline(rhs._outline) { //nop } -PolygonSymbol::PolygonSymbol( const Config& conf ) : -Symbol( conf ), -_fill ( Fill() ), -_outline( true ) +PolygonSymbol::PolygonSymbol(const Config& conf) : + Symbol(conf) { mergeConfig(conf); } -Config +Config PolygonSymbol::getConfig() const { Config conf = Symbol::getConfig(); conf.key() = "polygon"; - conf.set( "fill", _fill ); - conf.set("outline", _outline); + conf.set("fill", fill()); + conf.set("outline", outline()); + conf.set("material", material()); return conf; } -void -PolygonSymbol::mergeConfig(const Config& conf ) +void +PolygonSymbol::mergeConfig(const Config& conf) { - conf.get( "fill", _fill ); - conf.get("outline", _outline); + conf.get("fill", fill()); + conf.get("outline", outline()); + conf.get("material", material()); } void PolygonSymbol::parseSLD(const Config& c, Style& style) { - if ( match(c.key(), "fill") ) { + if (match(c.key(), "fill")) { style.getOrCreate()->fill().mutable_value().color() = Color(c.value()); } - else if ( match(c.key(), "fill-opacity") ) { - style.getOrCreate()->fill().mutable_value().color().a() = as( c.value(), 1.0f ); + else if (match(c.key(), "fill-opacity")) { + style.getOrCreate()->fill().mutable_value().color().a() = as(c.value(), 1.0f); } - else if ( match(c.key(), "fill-script") ) { + else if (match(c.key(), "fill-script")) { style.getOrCreate()->script() = StringExpression(c.value()); } + else if (match(c.key(), "fill-outline")) { + style.getOrCreate()->outline() = as(c.value(), true); + } + else if (match(c.key(), "fill-material")) { + style.getOrCreate()->material() = URI(c.value(), c.referrer()); + } } diff --git a/src/osgEarth/ResourceLibrary.cpp b/src/osgEarth/ResourceLibrary.cpp index d58aab5187..d301c9b86c 100644 --- a/src/osgEarth/ResourceLibrary.cpp +++ b/src/osgEarth/ResourceLibrary.cpp @@ -259,7 +259,7 @@ ResourceLibrary::matches( const SkinSymbol* q, SkinResource* s ) const { if ( q->name().isSet() ) { - return osgEarth::ciEquals(q->name()->eval(), s->name()); + return osgEarth::ciEquals(q->name()->eval(), s->name().value()); } if (q->objectHeight().isSet()) diff --git a/src/osgEarth/Skins b/src/osgEarth/Skins index 75e4d86f8e..2cedd8e94d 100644 --- a/src/osgEarth/Skins +++ b/src/osgEarth/Skins @@ -16,151 +16,125 @@ * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see */ - -#ifndef OSGEARTHSYMBOLOGY_SKIN_RESOURCE_H -#define OSGEARTHSYMBOLOGY_SKIN_RESOURCE_H 1 +#pragma once #include #include #include #include +#include #include #include -namespace osgEarth { namespace Util +namespace osgEarth { - /** - * A resource that points to a "skin", which is a Texture image paired with - * a collection of metadata that describes its suitability for use in a scene. - */ - class OSGEARTH_EXPORT SkinResource : public Resource + namespace Util { - public: - /** Constructs a new skin resource. */ - SkinResource( const Config& conf =Config() ); - - /** dtor */ - virtual ~SkinResource() { } - /** - * Creates a new StateSet containing a Texture based on this Skin. + * A resource that points to a "skin", which is a Texture image paired with + * a collection of metadata that describes its suitability for use in a scene. */ - osg::StateSet* createStateSet( const osgDB::Options* dbOptions ) const; + class OSGEARTH_EXPORT SkinResource : public Resource + { + public: + /** Constructs a new skin resource. */ + SkinResource(const Config& conf = Config()); - /** - * Creates an image for this SkinResource. - */ - osg::ref_ptr createImage( const osgDB::Options* options ) const; - - osg::Texture* createTexture(const osgDB::Options* readOptions) const; + /** dtor */ + virtual ~SkinResource() { } - osg::Texture* createTexture(osg::Image* image) const; + //! Creates a new StateSet containing a Texture based on this Skin. + osg::StateSet* createStateSet(const osgDB::Options* dbOptions) const; - /** - * A key string that can uniquely identify this object for the purposes - * of creating its state set (e.g., for a cache) - */ - std::string getUniqueID() const; + //! Creates a state attribute for this resource. This will either be + //! an osg::Texture (for a simple image), a PBRTexture (for a PBR material), + //! or nullptr. + osg::StateAttribute* createStateAttribute(const osgDB::Options* readOptions) const; - public: - /** inline image (not serializable) */ - osg::ref_ptr& image() { return _image; } - const osg::ref_ptr& image() const { return _image; } + //! Creates a texture for the input image. Supports multilayer array images. + osg::Texture* createTexture(osg::Image* image) const; - /** Source location of the actual texture image. */ - optional& imageURI() { return _imageURI; } - const optional& imageURI() const { return _imageURI; } + //! Create an image from the color channel of the configured material. + //! TODO: expand to support other material channels. + osg::ref_ptr createColorImage(const osgDB::Options* options) const; - /** Real-world width of the image, in meters */ - optional& imageWidth() { return _imageWidth; } - const optional& imageWidth() const { return _imageWidth; } + //! A key string that can uniquely identify this object for the purposes + //! of creating its state set (e.g., for a cache) + std::string getUniqueID() const; - /** Real-world height of the image, in meters */ - optional& imageHeight() { return _imageHeight; } - const optional& imageHeight() const { return _imageHeight; } + public: + /** inline image (not serializable) */ + OE_OPTION_REFPTR(osg::Image, image); - /** Minimum acceptable real-world object height (meters) for which this image would make an appropriate texture */ - optional& minObjectHeight() { return _minObjHeight; } - const optional& minObjectHeight() const { return _minObjHeight; } + OE_OPTION(std::string, name); - /** Maximum acceptable real-world object height (meters) for which this image would make an appropriate texture */ - optional& maxObjectHeight() { return _maxObjHeight; } - const optional& maxObjectHeight() const { return _maxObjHeight; } + //! Source data for the textures. + //! Use this OR imageURI. + OE_OPTION(PBRMaterial, material); - /** Whether this image is suitable for use as a vertically repeating texture */ - optional& isTiled() { return _isTiled; } - const optional& isTiled() const { return _isTiled; } + //! Source location of a texture image. + //! This is an alternative to using material() that will load just a color image. + //! This supports texture arrays and atlases, whereas material does not (yet). + OE_OPTION(URI, imageURI); - /** Image offset within a source atlas (S dimension [0..1]) */ - optional& imageBiasS() { return _imageBiasS; } - const optional& imageBiasS() const { return _imageBiasS; } + /** Real-world width of the image, in meters */ + OE_OPTION(float, imageWidth, 10.0f); - /** Image offset (pixels) within a source atlas (T dimension [0..1]) */ - optional& imageBiasT() { return _imageBiasT; } - const optional& imageBiasT() const { return _imageBiasT; } + /** Real-world height of the image, in meters */ + OE_OPTION(float, imageHeight, 3.0f); - /** Image layer index within a source atlas (R dimension) */ - optional& imageLayer() { return _imageLayer; } - const optional& imageLayer() const { return _imageLayer; } + /** Minimum acceptable real-world object height (meters) for which this image would make an appropriate texture */ + OE_OPTION(float, minObjectHeight, 0.0f); - /** Image scalke factor within a source atlas (S dimension) */ - optional& imageScaleS() { return _imageScaleS; } - const optional& imageScaleS() const { return _imageScaleS; } + /** Maximum acceptable real-world object height (meters) for which this image would make an appropriate texture */ + OE_OPTION(float, maxObjectHeight, FLT_MAX); - /** Image scalke factor within a source atlas (T dimension) */ - optional& imageScaleT() { return _imageScaleT; } - const optional& imageScaleT() const { return _imageScaleT; } + /** Whether this image is suitable for use as a vertically repeating texture */ + OE_OPTION(bool, isTiled, false); - /** GL texture application mode */ - optional& texEnvMode() { return _texEnvMode; } - const optional& texEnvMode() const { return _texEnvMode; } + /** Image offset within a source atlas (S dimension [0..1]) */ + OE_OPTION(float, imageBiasS, 0.0f); - /** The maximum allowable size of a texture (in either dimension) that uses this image. */ - optional maxTextureSpan() { return _maxTexSpan; } - const optional maxTextureSpan() const { return _maxTexSpan; } + /** Image offset (pixels) within a source atlas (T dimension [0..1]) */ + OE_OPTION(float, imageBiasT, 0.0f); - /** Whether this image is suitable for texture atlasing */ - optional& atlasHint() { return _atlasHint; } - const optional& atlasHint() const { return _atlasHint; } + /** Image layer index within a source atlas (R dimension) */ + OE_OPTION(unsigned, imageLayer, 0); - /** Options string to pass in when reading the image */ - optional& readOptions() { return _readOptions; } - const optional& readOptions() const { return _readOptions; } + /** Image scalke factor within a source atlas (S dimension) */ + OE_OPTION(float, imageScaleS, 1.0f); - public: // serialization methods + /** Image scalke factor within a source atlas (T dimension) */ + OE_OPTION(float, imageScaleT, 1.0f); - virtual Config getConfig() const; - void mergeConfig( const Config& conf ); + /** GL texture application mode */ + OE_OPTION(osg::TexEnv::Mode, texEnvMode, osg::TexEnv::MODULATE); - protected: + /** The maximum allowable size of a texture (in either dimension) that uses this image. */ + OE_OPTION(unsigned, maxTexSpan, 1024); - osg::StateSet* createStateSet( osg::Image* image ) const; + /** Whether this image is suitable for texture atlasing */ + OE_OPTION(bool, atlasHint, true); - protected: + /** Options string to pass in when reading the image */ + OE_OPTION(std::string, readOptions); - optional _name; - optional _imageURI; - optional _imageWidth; - optional _imageHeight; - optional _minObjHeight; - optional _maxObjHeight; - optional _isTiled; - optional _texEnvMode; - optional _maxTexSpan; - optional _imageBiasS; - optional _imageBiasT; - optional _imageLayer; - optional _imageScaleS; - optional _imageScaleT; - optional _atlasHint; - optional _readOptions; - - osg::ref_ptr _image; - }; + public: // serialization methods + + virtual Config getConfig() const; + void mergeConfig(const Config& conf); + + protected: + + osg::StateSet* createStateSet(osg::Image* image) const; + }; + + using SkinResourceVector = std::vector< osg::ref_ptr >; + } /** - * Query object that you can use to search for applicable Skin resources from the + * Query object that you can use to search for applicable Skin resources from the * ResourceLibrary. */ class OSGEARTH_EXPORT SkinSymbol : public TaggableWithConfig @@ -168,8 +142,8 @@ namespace osgEarth { namespace Util public: META_Object(osgEarth, SkinSymbol); - SkinSymbol(const SkinSymbol& rhs,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY); - SkinSymbol( const Config& conf =Config() ); + SkinSymbol(const SkinSymbol& rhs, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY); + SkinSymbol(const Config& conf = Config()); /** dtor */ virtual ~SkinSymbol() { } @@ -218,8 +192,5 @@ namespace osgEarth { namespace Util optional _randomSeed; optional _name; }; +} - typedef std::vector< osg::ref_ptr > SkinResourceVector; -} } - -#endif // OSGEARTHSYMBOLOGY_SKIN_RESOURCE_H diff --git a/src/osgEarth/Skins.cpp b/src/osgEarth/Skins.cpp index b880641820..66fa728a15 100644 --- a/src/osgEarth/Skins.cpp +++ b/src/osgEarth/Skins.cpp @@ -32,50 +32,38 @@ using namespace osgEarth; //--------------------------------------------------------------------------- -SkinResource::SkinResource( const Config& conf ) : -Resource ( conf ), -_imageWidth ( 10.0f ), -_imageHeight ( 3.0f ), -_minObjHeight ( 0.0f ), -_maxObjHeight ( FLT_MAX ), -_isTiled ( false ), -_texEnvMode ( osg::TexEnv::MODULATE ), -_maxTexSpan ( 1024 ), -_imageBiasS ( 0.0f ), -_imageBiasT ( 0.0f ), -_imageLayer ( 0 ), -_imageScaleS ( 1.0f ), -_imageScaleT ( 1.0f ), -_atlasHint ( true ) +SkinResource::SkinResource(const Config& conf) : + Resource(conf) { - mergeConfig( conf ); + mergeConfig(conf); } void -SkinResource::mergeConfig( const Config& conf ) +SkinResource::mergeConfig(const Config& conf) { - conf.get( "url", _imageURI ); - conf.get( "image_width", _imageWidth ); - conf.get( "image_height", _imageHeight ); - conf.get( "min_object_height", _minObjHeight ); - conf.get( "max_object_height", _maxObjHeight ); - conf.get( "tiled", _isTiled ); - conf.get( "max_texture_span", _maxTexSpan ); - - conf.get( "texture_mode", "decal", _texEnvMode, osg::TexEnv::DECAL ); - conf.get( "texture_mode", "modulate", _texEnvMode, osg::TexEnv::MODULATE ); - conf.get( "texture_mode", "replace", _texEnvMode, osg::TexEnv::REPLACE ); - conf.get( "texture_mode", "blend", _texEnvMode, osg::TexEnv::BLEND ); + conf.get("material", material()); + conf.get("url", imageURI()); + conf.get("image_width", _imageWidth); + conf.get("image_height", _imageHeight); + conf.get("min_object_height", minObjectHeight()); + conf.get("max_object_height", maxObjectHeight()); + conf.get("tiled", _isTiled); + conf.get("max_texture_span", _maxTexSpan); + + conf.get("texture_mode", "decal", _texEnvMode, osg::TexEnv::DECAL); + conf.get("texture_mode", "modulate", _texEnvMode, osg::TexEnv::MODULATE); + conf.get("texture_mode", "replace", _texEnvMode, osg::TexEnv::REPLACE); + conf.get("texture_mode", "blend", _texEnvMode, osg::TexEnv::BLEND); // texture atlas support - conf.get( "image_bias_s", _imageBiasS ); - conf.get( "image_bias_t", _imageBiasT ); - conf.get( "image_layer", _imageLayer ); - conf.get( "image_scale_s", _imageScaleS ); - conf.get( "image_scale_t", _imageScaleT ); - - conf.get( "atlas", _atlasHint ); - conf.get( "read_options", _readOptions ); + conf.get("image_bias_s", _imageBiasS); + conf.get("image_bias_t", _imageBiasT); + conf.get("image_layer", _imageLayer); + conf.get("image_scale_s", _imageScaleS); + conf.get("image_scale_t", _imageScaleT); + + conf.get("atlas", _atlasHint); + conf.get("read_options", _readOptions); } Config @@ -84,28 +72,29 @@ SkinResource::getConfig() const Config conf = Resource::getConfig(); conf.key() = "skin"; - conf.set( "url", _imageURI ); - conf.set( "image_width", _imageWidth ); - conf.set( "image_height", _imageHeight ); - conf.set( "min_object_height", _minObjHeight ); - conf.set( "max_object_height", _maxObjHeight ); - conf.set( "tiled", _isTiled ); - conf.set( "max_texture_span", _maxTexSpan ); - - conf.set( "texture_mode", "decal", _texEnvMode, osg::TexEnv::DECAL ); - conf.set( "texture_mode", "modulate", _texEnvMode, osg::TexEnv::MODULATE ); - conf.set( "texture_mode", "replace", _texEnvMode, osg::TexEnv::REPLACE ); - conf.set( "texture_mode", "blend", _texEnvMode, osg::TexEnv::BLEND ); + conf.set("material", material()); + conf.set("url", imageURI()); + conf.set("image_width", _imageWidth); + conf.set("image_height", _imageHeight); + conf.set("min_object_height", minObjectHeight()); + conf.set("max_object_height", maxObjectHeight()); + conf.set("tiled", _isTiled); + conf.set("max_texture_span", _maxTexSpan); + + conf.set("texture_mode", "decal", _texEnvMode, osg::TexEnv::DECAL); + conf.set("texture_mode", "modulate", _texEnvMode, osg::TexEnv::MODULATE); + conf.set("texture_mode", "replace", _texEnvMode, osg::TexEnv::REPLACE); + conf.set("texture_mode", "blend", _texEnvMode, osg::TexEnv::BLEND); // texture atlas support - conf.set( "image_bias_s", _imageBiasS ); - conf.set( "image_bias_t", _imageBiasT ); - conf.set( "image_layer", _imageLayer ); - conf.set( "image_scale_s", _imageScaleS ); - conf.set( "image_scale_t", _imageScaleT ); - - conf.set( "atlas", _atlasHint ); - conf.set( "read_options", _readOptions ); + conf.set("image_bias_s", _imageBiasS); + conf.set("image_bias_t", _imageBiasT); + conf.set("image_layer", _imageLayer); + conf.set("image_scale_s", _imageScaleS); + conf.set("image_scale_t", _imageScaleT); + + conf.set("atlas", _atlasHint); + conf.set("read_options", _readOptions); return conf; } @@ -113,15 +102,45 @@ SkinResource::getConfig() const std::string SkinResource::getUniqueID() const { - return imageURI()->full(); + return + imageURI().isSet() ? imageURI()->full() : + material().isSet() ? material()->color()->full() : + std::string{}; } -osg::Texture* -SkinResource::createTexture(const osgDB::Options* readOptions) const +osg::StateAttribute* +SkinResource::createStateAttribute(const osgDB::Options* readOptions) const { - //OE_DEBUG << LC << "Creating skin texture for " << imageURI()->full() << std::endl; - osg::ref_ptr image = createImage(readOptions); - return createTexture(image.get()); + if (material().isSet()) + { + osg::ref_ptr pbr_texture = new PBRTexture(); + auto status = pbr_texture->load(material().value(), readOptions); + if (!status.isOK()) + { + OE_WARN << LC << "One or more errors loading material for skin " << name().value() << std::endl; + return nullptr; + } + + if (isTiled() == true) + { + for (auto& tex : { pbr_texture->albedo, pbr_texture->normal, pbr_texture->pbr }) + { + if (tex.valid()) + { + tex->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); + tex->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); + } + } + } + + return pbr_texture.release(); + } + + else + { + osg::ref_ptr image = createColorImage(readOptions); + return createTexture(image.get()); + } } osg::Texture* @@ -177,45 +196,48 @@ osg::StateSet* SkinResource::createStateSet(const osgDB::Options* readOptions) const { OE_DEBUG << LC << "Creating skin state set for " << imageURI()->full() << std::endl; - osg::ref_ptr image = createImage(readOptions); - return createStateSet(image.get()); -} -osg::StateSet* -SkinResource::createStateSet( osg::Image* image ) const -{ - osg::StateSet* stateSet = 0L; - if ( image ) + auto stateset = new osg::StateSet(); + osg::Texture* albedo_texture = nullptr; + + auto sa = createStateAttribute(readOptions); + + auto pbr_texture = dynamic_cast(sa); + if (pbr_texture) + albedo_texture = pbr_texture->albedo.get(); + else + albedo_texture = dynamic_cast(sa); + + if (sa) { - stateSet = new osg::StateSet(); - - osg::Texture* tex = createTexture(image); - if ( tex ) - { - stateSet->setTextureAttributeAndModes(0, tex, osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(0, sa, osg::StateAttribute::ON); + } - if ( _texEnvMode.isSet() ) - { - osg::TexEnv* texenv = new osg::TexEnv(); - texenv->setMode( *_texEnvMode ); - stateSet->setTextureAttributeAndModes( 0, texenv, osg::StateAttribute::ON ); - } + if (pbr_texture) + { + stateset->setTextureAttributeAndModes(0, pbr_texture, osg::StateAttribute::ON); + } - if ( ImageUtils::hasAlphaChannel( image ) ) - { - osg::BlendFunc* blendFunc = new osg::BlendFunc(); - blendFunc->setFunction( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); - stateSet->setAttributeAndModes( blendFunc, osg::StateAttribute::ON ); - stateSet->setRenderingHint( osg::StateSet::TRANSPARENT_BIN ); - } - } + if (_texEnvMode.isSet()) + { + osg::TexEnv* texenv = new osg::TexEnv(); + texenv->setMode(*_texEnvMode); + stateset->setTextureAttributeAndModes(0, texenv, osg::StateAttribute::ON); } - return stateSet; + if (albedo_texture && ImageUtils::hasAlphaChannel(albedo_texture->getImage(0))) + { + auto* blendFunc = new osg::BlendFunc(); + blendFunc->setFunction(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + stateset->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); + stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); + } + + return stateset; } osg::ref_ptr -SkinResource::createImage( const osgDB::Options* dbOptions ) const +SkinResource::createColorImage( const osgDB::Options* dbOptions ) const { if (getStatus().isError()) return 0L; @@ -225,11 +247,11 @@ SkinResource::createImage( const osgDB::Options* dbOptions ) const { osg::ref_ptr ro = Registry::cloneOrCreateOptions(dbOptions); ro->setOptionString(Stringify() << _readOptions.get() << " " << ro->getOptionString()); - result = _imageURI->readImage(ro.get()); + result = imageURI()->readImage(ro.get()); } else { - result = _imageURI->readImage(dbOptions); + result = imageURI()->readImage(dbOptions); } if (result.failed()) @@ -245,31 +267,31 @@ SkinResource::createImage( const osgDB::Options* dbOptions ) const OSGEARTH_REGISTER_SIMPLE_SYMBOL(skin, SkinSymbol); -SkinSymbol::SkinSymbol(const SkinSymbol& rhs,const osg::CopyOp& copyop): -TaggableWithConfig(rhs, copyop), -_library(rhs._library), -_objHeight(rhs._objHeight), -_minObjHeight(rhs._minObjHeight), -_maxObjHeight(rhs._maxObjHeight), -_isTiled(rhs._isTiled), -_randomSeed(rhs._randomSeed), -_name(rhs._name) +SkinSymbol::SkinSymbol(const SkinSymbol& rhs, const osg::CopyOp& copyop) : + TaggableWithConfig(rhs, copyop), + _library(rhs._library), + _objHeight(rhs._objHeight), + _minObjHeight(rhs._minObjHeight), + _maxObjHeight(rhs._maxObjHeight), + _isTiled(rhs._isTiled), + _randomSeed(rhs._randomSeed), + _name(rhs._name) { } -SkinSymbol::SkinSymbol( const Config& conf ) : -TaggableWithConfig(conf), -_objHeight ( 0.0f ), -_minObjHeight ( 0.0f ), -_maxObjHeight ( FLT_MAX ), -_isTiled ( false ), -_randomSeed ( 0 ) +SkinSymbol::SkinSymbol(const Config& conf) : + TaggableWithConfig(conf), + _objHeight(0.0f), + _minObjHeight(0.0f), + _maxObjHeight(FLT_MAX), + _isTiled(false), + _randomSeed(0) { - if ( !conf.empty() ) - mergeConfig( conf ); + if (!conf.empty()) + mergeConfig(conf); } -void +void SkinSymbol::mergeConfig( const Config& conf ) { conf.get( "library", _library ); @@ -330,7 +352,7 @@ SkinSymbol::parseSLD(const Config& c, Style& style) else if (match(c.key(), "skin-random-seed") ) { style.getOrCreate()->randomSeed() = as( c.value(), 0u ); } - else if (match(c.key(), "skin-name")) { + else if (match(c.key(), "skin") || match(c.key(), "skin-name")) { style.getOrCreate()->name() = StringExpression(c.value()); } } diff --git a/tests/feature_polygons_textured.earth b/tests/feature_polygons_textured.earth new file mode 100644 index 0000000000..55d5510ea7 --- /dev/null +++ b/tests/feature_polygons_textured.earth @@ -0,0 +1,43 @@ + + + + + + + + + + ../data/world.shp + + + + + + + + + + ../data/flag_us.png + true + 1000000 + 500000 + decal + + + + + + + +