diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/map/map.cpp | 8 | ||||
-rw-r--r-- | src/map/tile_parser.cpp | 119 | ||||
-rw-r--r-- | src/renderer/icon_bucket.cpp | 79 | ||||
-rw-r--r-- | src/renderer/line_bucket.cpp | 2 | ||||
-rw-r--r-- | src/renderer/painter_icon.cpp | 45 | ||||
-rw-r--r-- | src/renderer/painter_symbol.cpp | 202 | ||||
-rw-r--r-- | src/renderer/painter_text.cpp | 156 | ||||
-rw-r--r-- | src/renderer/symbol_bucket.cpp | 271 | ||||
-rw-r--r-- | src/renderer/text_bucket.cpp | 146 | ||||
-rw-r--r-- | src/style/property_fallback.cpp | 1 | ||||
-rw-r--r-- | src/style/style_bucket.cpp | 3 | ||||
-rw-r--r-- | src/style/style_layer.cpp | 42 | ||||
-rw-r--r-- | src/style/style_parser.cpp | 138 | ||||
-rw-r--r-- | src/style/style_properties.cpp | 2 | ||||
-rw-r--r-- | src/text/placement.cpp | 16 |
15 files changed, 603 insertions, 627 deletions
diff --git a/src/map/map.cpp b/src/map/map.cpp index 9252a57cce..ca43662e09 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -683,13 +683,9 @@ void Map::renderLayer(std::shared_ptr<StyleLayer> layer_desc, RenderPass pass) { if (pass == Opaque) return; if (!layer_desc->getProperties<LineProperties>().isVisible()) return; break; - case StyleLayerType::Icon: + case StyleLayerType::Symbol: if (pass == Opaque) return; - if (!layer_desc->getProperties<IconProperties>().isVisible()) return; - break; - case StyleLayerType::Text: - if (pass == Opaque) return; - if (!layer_desc->getProperties<TextProperties>().isVisible()) return; + if (!layer_desc->getProperties<SymbolProperties>().isVisible()) return; break; case StyleLayerType::Raster: if (pass == Translucent) return; diff --git a/src/map/tile_parser.cpp b/src/map/tile_parser.cpp index 807e827972..6be9bcb32c 100644 --- a/src/map/tile_parser.cpp +++ b/src/map/tile_parser.cpp @@ -6,8 +6,7 @@ #include <mbgl/style/style_layer_group.hpp> #include <mbgl/renderer/fill_bucket.hpp> #include <mbgl/renderer/line_bucket.hpp> -#include <mbgl/renderer/icon_bucket.hpp> -#include <mbgl/renderer/text_bucket.hpp> +#include <mbgl/renderer/symbol_bucket.hpp> #include <mbgl/renderer/raster_bucket.hpp> #include <mbgl/util/raster.hpp> #include <mbgl/util/constants.hpp> @@ -51,21 +50,6 @@ void TileParser::parse() { bool TileParser::obsolete() const { return tile.state == TileData::State::obsolete; } -void TileParser::addGlyph(uint64_t tileid, const std::string stackname, - const std::u32string &string, const FontStack &fontStack, - GlyphAtlas &glyphAtlas, GlyphPositions &face) { - const std::map<uint32_t, SDFGlyph> &sdfs = fontStack.getSDFs(); - // Loop through all characters and add glyph to atlas, positions. - for (uint32_t chr : string) { - auto sdf_it = sdfs.find(chr); - if (sdf_it != sdfs.end()) { - const SDFGlyph& sdf = sdf_it->second; - const Rect<uint16_t> rect = glyphAtlas.addGlyph(tileid, stackname, sdf); - face.emplace(chr, Glyph{rect, sdf.metrics}); - } - } -} - void TileParser::parseStyleLayers(std::shared_ptr<StyleLayerGroup> group) { if (!group) { return; @@ -119,10 +103,8 @@ std::unique_ptr<Bucket> TileParser::createBucket(std::shared_ptr<StyleBucket> bu return createFillBucket(layer, bucket_desc->filter, bucket_desc->render.get<StyleBucketFill>()); } else if (bucket_desc->render.is<StyleBucketLine>()) { return createLineBucket(layer, bucket_desc->filter, bucket_desc->render.get<StyleBucketLine>()); - } else if (bucket_desc->render.is<StyleBucketIcon>()) { - return createIconBucket(layer, bucket_desc->filter, bucket_desc->render.get<StyleBucketIcon>()); - } else if (bucket_desc->render.is<StyleBucketText>()) { - return createTextBucket(layer, bucket_desc->filter, bucket_desc->render.get<StyleBucketText>()); + } else if (bucket_desc->render.is<StyleBucketSymbol>()) { + return createSymbolBucket(layer, bucket_desc->filter, bucket_desc->render.get<StyleBucketSymbol>()); } else if (bucket_desc->render.is<StyleBucketRaster>()) { return nullptr; } else { @@ -140,7 +122,7 @@ std::unique_ptr<Bucket> TileParser::createBucket(std::shared_ptr<StyleBucket> bu } template <class Bucket> -void TileParser::addBucketFeatures(Bucket& bucket, const VectorTileLayer& layer, const FilterExpression &filter) { +void TileParser::addBucketGeometries(Bucket& bucket, const VectorTileLayer& layer, const FilterExpression &filter) { FilteredVectorTileLayer filtered_layer(layer, filter); for (pbf feature : filtered_layer) { if (obsolete()) @@ -157,103 +139,20 @@ void TileParser::addBucketFeatures(Bucket& bucket, const VectorTileLayer& layer, } } -template <class Bucket, typename... Args> -void TileParser::addBucketFeatures(Bucket& bucket, const VectorTileLayer& layer, const FilterExpression &filter, Args&& ...args) { - FilteredVectorTileLayer filtered_layer(layer, filter); - for (const pbf &feature_pbf : filtered_layer) { - if (obsolete()) { - return; - } - bucket->addFeature({feature_pbf, layer}, std::forward<Args>(args)...); - } -} - - std::unique_ptr<Bucket> TileParser::createFillBucket(const VectorTileLayer& layer, const FilterExpression &filter, const StyleBucketFill &fill) { std::unique_ptr<FillBucket> bucket = std::make_unique<FillBucket>(tile.fillVertexBuffer, tile.triangleElementsBuffer, tile.lineElementsBuffer, fill); - addBucketFeatures(bucket, layer, filter); + addBucketGeometries(bucket, layer, filter); return obsolete() ? nullptr : std::move(bucket); } std::unique_ptr<Bucket> TileParser::createLineBucket(const VectorTileLayer& layer, const FilterExpression &filter, const StyleBucketLine &line) { std::unique_ptr<LineBucket> bucket = std::make_unique<LineBucket>(tile.lineVertexBuffer, tile.triangleElementsBuffer, tile.pointElementsBuffer, line); - addBucketFeatures(bucket, layer, filter); + addBucketGeometries(bucket, layer, filter); return obsolete() ? nullptr : std::move(bucket); } -std::unique_ptr<Bucket> TileParser::createIconBucket(const VectorTileLayer& layer, const FilterExpression &filter, const StyleBucketIcon &icon) { - std::unique_ptr<IconBucket> bucket = std::make_unique<IconBucket>(tile.iconVertexBuffer, icon); - addBucketFeatures(bucket, layer, filter, *spriteAtlas); +std::unique_ptr<Bucket> TileParser::createSymbolBucket(const VectorTileLayer& layer, const FilterExpression &filter, const StyleBucketSymbol &symbol) { + std::unique_ptr<SymbolBucket> bucket = std::make_unique<SymbolBucket>(tile.textVertexBuffer, tile.iconVertexBuffer, tile.triangleElementsBuffer, symbol, placement); + bucket->addFeatures(layer, filter, tile.id, *spriteAtlas, *glyphAtlas, *glyphStore); return obsolete() ? nullptr : std::move(bucket); } - -std::unique_ptr<Bucket> TileParser::createTextBucket(const VectorTileLayer& layer, const FilterExpression &filter, const StyleBucketText &text) { - - // Make sure that we always have a valid glyph store. If this is not set, the stylesheet - // doesn't specify a glyph URL. - if (!glyphStore) { - return nullptr; - } - - const StyleBucketText &properties = text; - - std::unique_ptr<TextBucket> bucket = std::make_unique<TextBucket>( - tile.textVertexBuffer, tile.triangleElementsBuffer, properties, placement); - - util::utf8_to_utf32 ucs4conv; - - std::vector<std::pair<std::u32string, pbf>> labels; - - // Determine and load glyph ranges - { - std::set<GlyphRange> ranges; - - FilteredVectorTileLayer filtered_layer(layer, filter); - for (const pbf &feature_pbf : filtered_layer) { - if (obsolete()) - return nullptr; - VectorTileFeature feature{feature_pbf, layer}; - - std::string u8string = util::replaceTokens(properties.field, feature.properties); - - auto& convert = std::use_facet<std::ctype<char>>(std::locale()); - if (properties.transform == TextTransformType::Uppercase) { - convert.toupper(&u8string[0], &u8string[0] + u8string.size()); - } else if (properties.transform == TextTransformType::Lowercase) { - convert.tolower(&u8string[0], &u8string[0] + u8string.size()); - } - - std::u32string string = ucs4conv.convert(u8string); - - // Loop through all characters of this text and collect unique codepoints. - for (char32_t chr : string) { - ranges.insert(getGlyphRange(chr)); - } - - labels.emplace_back(string, feature.geometry); - } - - glyphStore->waitForGlyphRanges(properties.font, ranges); - } - - // Create a copy! - const FontStack &fontStack = glyphStore->getFontStack(properties.font); - GlyphPositions face; - - // Shape and place all labels. - for (const std::pair<std::u32string, pbf> &label : labels) { - - // Shape labels. - const Shaping shaping = fontStack.getShaping(label.first, properties.max_width, - properties.line_height, properties.alignment, - properties.vertical_alignment, properties.letter_spacing); - - // Place labels. - addGlyph(tile.id.to_uint64(), properties.font, label.first, fontStack, *glyphAtlas, - face); - - bucket->addFeature(label.second, face, shaping); - } - - return std::move(bucket); -} diff --git a/src/renderer/icon_bucket.cpp b/src/renderer/icon_bucket.cpp deleted file mode 100644 index af988fee80..0000000000 --- a/src/renderer/icon_bucket.cpp +++ /dev/null @@ -1,79 +0,0 @@ -#include <mbgl/renderer/icon_bucket.hpp> -#include <mbgl/geometry/icon_buffer.hpp> -#include <mbgl/geometry/elements_buffer.hpp> -#include <mbgl/geometry/geometry.hpp> -#include <mbgl/geometry/sprite_atlas.hpp> - -#include <mbgl/renderer/painter.hpp> -#include <mbgl/style/style.hpp> -#include <mbgl/map/sprite.hpp> -#include <mbgl/map/vector_tile.hpp> - -#include <mbgl/platform/gl.hpp> -#include <mbgl/util/constants.hpp> -#include <mbgl/util/token.hpp> - -#include <cassert> - -struct geometry_too_long_exception : std::exception {}; - -using namespace mbgl; - -IconBucket::IconBucket(IconVertexBuffer& vertexBuffer, - const StyleBucketIcon& properties) - : properties(properties), - vertexBuffer(vertexBuffer), - vertex_start(vertexBuffer.index()) { -} - -void IconBucket::addFeature(const VectorTileFeature &feature, SpriteAtlas &sprite_atlas) { - std::string field; - - if (properties.icon.size()) { - field = util::replaceTokens(properties.icon, feature.properties); - } - - if (!field.size()) { - field = "<circle>"; - } - - const Rect<uint16_t> rect = sprite_atlas.getIcon(properties.size, field); - const uint16_t tx = rect.x + rect.w / 2; - const uint16_t ty = rect.y + rect.h / 2; - - Geometry::command cmd; - pbf geom = feature.geometry; - Geometry geometry(geom); - int32_t x, y; - while ((cmd = geometry.next(x, y)) != Geometry::end) { - if (cmd == Geometry::move_to) { - vertexBuffer.add(x, y, tx, ty); - } else { - if (debug::tileParseWarnings) { - fprintf(stderr, "[WARNING] other command than move_to in icon geometry\n"); - } - } - } - - vertex_end = vertexBuffer.index(); -} - -void IconBucket::render(Painter& painter, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID& id) { - painter.renderIcon(*this, layer_desc, id); -} - -bool IconBucket::hasData() const { - return vertex_end > 0; -} - -void IconBucket::drawIcons(IconShader& shader) { - char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize); - array.bind(shader, vertexBuffer, vertex_index); - glDrawArrays(GL_POINTS, 0, (GLsizei)(vertex_end - vertex_start)); -} - -void IconBucket::drawIcons(DotShader& shader) { - char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize); - array.bind(shader, vertexBuffer, vertex_index); - glDrawArrays(GL_POINTS, 0, (GLsizei)(vertex_end - vertex_start)); -} diff --git a/src/renderer/line_bucket.cpp b/src/renderer/line_bucket.cpp index 74cfba3c6d..2e04820e42 100644 --- a/src/renderer/line_bucket.cpp +++ b/src/renderer/line_bucket.cpp @@ -80,7 +80,7 @@ void LineBucket::addGeometry(const std::vector<Coordinate>& vertices) { CapType beginCap = properties.cap; CapType endCap = closed ? CapType::Butt : properties.cap; - JoinType currentJoin = JoinType::Default; + JoinType currentJoin = JoinType::Miter; Coordinate currentVertex = Coordinate::null(), prevVertex = Coordinate::null(), diff --git a/src/renderer/painter_icon.cpp b/src/renderer/painter_icon.cpp deleted file mode 100644 index 158030df19..0000000000 --- a/src/renderer/painter_icon.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include <mbgl/renderer/painter.hpp> -#include <mbgl/renderer/icon_bucket.hpp> -#include <mbgl/map/map.hpp> -#include <mbgl/map/sprite.hpp> -#include <mbgl/style/style_layer.hpp> -#include <mbgl/geometry/sprite_atlas.hpp> -#include <mbgl/util/math.hpp> - -using namespace mbgl; - -void Painter::renderIcon(IconBucket& bucket, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID& id) { - // Abort early. - if (!bucket.hasData()) return; - if (pass == Opaque) return; - - const IconProperties &properties = layer_desc->getProperties<IconProperties>(); - -// TODO: when translating icon, are we doing this in the bucket already? -// const mat4 &vtxMatrix = translatedMatrix(properties.translate, id, properties.translateAnchor); - - SpriteAtlas &spriteAtlas = *map.getSpriteAtlas(); - - useProgram(iconShader->program); - iconShader->setMatrix(matrix); -// TODO: update - iconShader->setColor({{ 1, 1, 1, 1 }}); - iconShader->setImage(0); - iconShader->setRatio(map.getState().getPixelRatio()); - iconShader->setDimension({{ - spriteAtlas.getTextureWidth(), - spriteAtlas.getTextureHeight(), - }}); - - spriteAtlas.bind(map.getState().isChanging()); - - const float iconSize = bucket.properties.size * map.getState().getPixelRatio(); - iconShader->setSize(iconSize); -#ifndef GL_ES_VERSION_2_0 - glPointSize(iconSize); - glEnable(GL_POINT_SPRITE); -#endif - - glDepthRange(strata, 1.0f); - bucket.drawIcons(*iconShader); -} diff --git a/src/renderer/painter_symbol.cpp b/src/renderer/painter_symbol.cpp new file mode 100644 index 0000000000..8201e4b7f1 --- /dev/null +++ b/src/renderer/painter_symbol.cpp @@ -0,0 +1,202 @@ +#include <mbgl/renderer/painter.hpp> +#include <mbgl/renderer/symbol_bucket.hpp> +#include <mbgl/style/style_layer.hpp> +#include <mbgl/geometry/glyph_atlas.hpp> +#include <mbgl/geometry/sprite_atlas.hpp> +#include <mbgl/map/map.hpp> +#include <mbgl/util/math.hpp> +#include <cmath> + +namespace mbgl { + +void Painter::renderSymbol(SymbolBucket &bucket, std::shared_ptr<StyleLayer> layer_desc, + const Tile::ID &id) { + // Abort early. + if (pass == Opaque) { + return; + } + + if (bucket.hasTextData()) { + const SymbolProperties &properties = layer_desc->getProperties<SymbolProperties>(); + + mat4 exMatrix; + matrix::copy(exMatrix, projMatrix); + if (bucket.properties.placement == PlacementType::Line) { + matrix::rotate_z(exMatrix, exMatrix, map.getState().getAngle()); + } + + // If layerStyle.size > bucket.info.fontSize then labels may collide + float fontSize = std::fmin(properties.text.size, bucket.properties.text.max_size); + matrix::scale(exMatrix, exMatrix, fontSize / 24.0f, fontSize / 24.0f, 1.0f); + + // TODO: figure out whether we actually need to account for this while painting; we might + // already have + // done this during label placement. + // const mat4 &vtxMatrix = translatedMatrix(properties.translate, id, + // properties.translateAnchor); + + useProgram(textShader->program); + textShader->setMatrix(matrix); + textShader->setExtrudeMatrix(exMatrix); + + GlyphAtlas &glyphAtlas = *map.getGlyphAtlas(); + glyphAtlas.bind(); + textShader->setTextureSize( + {{static_cast<float>(glyphAtlas.width), static_cast<float>(glyphAtlas.height)}}); + + // Convert the -pi..pi to an int8 range. + float angle = std::round((map.getState().getAngle()) / M_PI * 128); + + // adjust min/max zooms for variable font sies + float zoomAdjust = log(fontSize / bucket.properties.text.max_size) / log(2); + + textShader->setAngle((int32_t)(angle + 256) % 256); + textShader->setFlip(bucket.properties.placement == PlacementType::Line ? 1 : 0); + textShader->setZoom((map.getState().getNormalizedZoom() - zoomAdjust) * + 10); // current zoom level + + // Label fading + const timestamp duration = 300_milliseconds; + const timestamp currentTime = util::now(); + + std::deque<FrameSnapshot> &history = frameHistory.history; + + // Remove frames until only one is outside the duration, or until there are only three + while (history.size() > 3 && history[1].t + duration < currentTime) { + history.pop_front(); + } + + if (history[1].t + duration < currentTime) { + history[0].z = history[1].z; + } + + assert("there should never be less than three frames in the history" && + (history.size() >= 3)); + + // Find the range of zoom levels we want to fade between + float startingZ = history.front().z; + const FrameSnapshot lastFrame = history.back(); + float endingZ = lastFrame.z; + float lowZ = std::fmin(startingZ, endingZ); + float highZ = std::fmax(startingZ, endingZ); + + // Calculate the speed of zooming, and how far it would zoom in terms of zoom levels in one + // duration + float zoomDiff = endingZ - history[1].z, timeDiff = lastFrame.t - history[1].t; + float fadedist = zoomDiff / (timeDiff / duration); + +#if defined(DEBUG) + if (std::isnan(fadedist)) + fprintf(stderr, "fadedist should never be NaN\n"); +#endif + + // At end of a zoom when the zoom stops changing continue pretending to zoom at that speed + // bump is how much farther it would have been if it had continued zooming at the same rate + float bump = (currentTime - lastFrame.t) / duration * fadedist; + + textShader->setFadeDist(fadedist * 10); + textShader->setMinFadeZoom(std::floor(lowZ * 10)); + textShader->setMaxFadeZoom(std::floor(highZ * 10)); + textShader->setFadeZoom((map.getState().getNormalizedZoom() + bump) * 10); + + // This defines the gamma around the SDF cutoff value. + const float sdfGamma = 0.75f / 10.0f; + + // Our signed distance fields are scaled so that 1 pixel is scaled to 32 pixels. + // Our cutoff between positive and negative values is hard coded to 64 (== 2 pixels). + // This means that our 6/8 of the value range lies outside the glyph outline. + const float sdfOffset = (256.0f - 64.0f) / 32.0f; + + // Currently, all of our fonts are rendered with a font size of 24px. + const float sdfFontSize = 24.0f; + + // The default gamma value has to be adjust for the current pixelratio so that we're not + // drawing + // blurry font on retina screens. + const float gamma = sdfGamma * sdfFontSize / fontSize / map.getState().getPixelRatio(); + + // We're drawing in the translucent pass which is bottom-to-top, so we need + // to draw the halo first. + if (properties.text.halo_color[3] > 0.0f) { + const float haloWidth = util::clamp( + (sdfOffset - properties.text.halo_width / (fontSize / sdfFontSize)) / 8.0f, 0.0f, + 1.0f); + + if (properties.text.halo_blur != 0.0f) { + // We are converting the halo_blur value to current screen pixels. + // Then we're dividing it by two because the gamma value is added/subtracted into + // both + // directions in the shader, but the halo_blur describes the entire width of the + // blur. + // Note that this does *not* have to be adjusted for retina screens, because we want + // the + // same blur width when we explicitly specify one. + textShader->setGamma((properties.text.halo_blur / (fontSize / sdfFontSize)) / 8.0f / + 2.0f); + } else { + textShader->setGamma(sdfGamma); + } + + if (properties.text.opacity < 1.0f) { + Color color = properties.text.halo_color; + color[0] *= properties.text.opacity; + color[1] *= properties.text.opacity; + color[2] *= properties.text.opacity; + color[3] *= properties.text.opacity; + textShader->setColor(color); + } else { + textShader->setColor(properties.text.halo_color); + } + textShader->setBuffer(haloWidth); + glDepthRange(strata, 1.0f); + bucket.drawGlyphs(*textShader); + } + + if (properties.text.color[3] > 0.0f) { + // Then, we draw the text over the halo + textShader->setGamma(gamma); + if (properties.text.opacity < 1.0f) { + Color color = properties.text.color; + color[0] *= properties.text.opacity; + color[1] *= properties.text.opacity; + color[2] *= properties.text.opacity; + color[3] *= properties.text.opacity; + textShader->setColor(color); + } else { + textShader->setColor(properties.text.color); + } + textShader->setBuffer((256.0f - 64.0f) / 256.0f); + glDepthRange(strata + strata_epsilon, 1.0f); + bucket.drawGlyphs(*textShader); + } + } + + if (bucket.hasIconData()) { + SpriteAtlas &spriteAtlas = *map.getSpriteAtlas(); + + useProgram(iconShader->program); + iconShader->setMatrix(matrix); + + // TODO: update + iconShader->setColor({{1, 1, 1, 1}}); + iconShader->setImage(0); + iconShader->setRatio(map.getState().getPixelRatio()); + iconShader->setDimension({{ + spriteAtlas.getTextureWidth(), spriteAtlas.getTextureHeight(), + }}); + + spriteAtlas.bind(map.getState().isChanging()); + + // TODO: remove hardcoded icon size. + const float iconSize = 12 * map.getState().getPixelRatio(); + iconShader->setSize(iconSize); +#ifndef GL_ES_VERSION_2_0 + glPointSize(iconSize); + glEnable(GL_POINT_SPRITE); +#endif + + glDepthRange(strata, 1.0f); + bucket.drawIcons(*iconShader); + } +} +} diff --git a/src/renderer/painter_text.cpp b/src/renderer/painter_text.cpp deleted file mode 100644 index d5ad3db51f..0000000000 --- a/src/renderer/painter_text.cpp +++ /dev/null @@ -1,156 +0,0 @@ -#include <mbgl/renderer/painter.hpp> -#include <mbgl/renderer/text_bucket.hpp> -#include <mbgl/style/style_layer.hpp> -#include <mbgl/geometry/glyph_atlas.hpp> -#include <mbgl/map/map.hpp> -#include <mbgl/util/math.hpp> -#include <cmath> - -using namespace mbgl; - -void Painter::renderText(TextBucket& bucket, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID& id) { - // Abort early. - if (pass == Opaque) return; - if (!bucket.hasData()) return; - - const TextProperties &properties = layer_desc->getProperties<TextProperties>(); - - mat4 exMatrix; - matrix::copy(exMatrix, projMatrix); - if (bucket.properties.path == TextPathType::Curve) { - matrix::rotate_z(exMatrix, exMatrix, map.getState().getAngle()); - } - - // If layerStyle.size > bucket.info.fontSize then labels may collide - float fontSize = std::fmin(properties.size, bucket.properties.max_size); - matrix::scale(exMatrix, exMatrix, fontSize / 24.0f, fontSize / 24.0f, 1.0f); - -// TODO: figure out whether we actually need to account for this while painting; we might already have -// done this during label placement. -// const mat4 &vtxMatrix = translatedMatrix(properties.translate, id, properties.translateAnchor); - - useProgram(textShader->program); - textShader->setMatrix(matrix); - textShader->setExtrudeMatrix(exMatrix); - - GlyphAtlas &glyphAtlas = *map.getGlyphAtlas(); - glyphAtlas.bind(); - textShader->setTextureSize({{static_cast<float>(glyphAtlas.width), - static_cast<float>(glyphAtlas.height)}}); - - // Convert the -pi..pi to an int8 range. - float angle = std::round((map.getState().getAngle()) / M_PI * 128); - - // adjust min/max zooms for variable font sies - float zoomAdjust = log(fontSize / bucket.properties.max_size) / log(2); - - textShader->setAngle((int32_t)(angle + 256) % 256); - textShader->setFlip(bucket.properties.path == TextPathType::Curve ? 1 : 0); - textShader->setZoom((map.getState().getNormalizedZoom() - zoomAdjust) * 10); // current zoom level - - // Label fading - const timestamp duration = 300_milliseconds; - const timestamp currentTime = util::now(); - - std::deque<FrameSnapshot> &history = frameHistory.history; - - // Remove frames until only one is outside the duration, or until there are only three - while (history.size() > 3 && history[1].t + duration < currentTime) { - history.pop_front(); - } - - if (history[1].t + duration < currentTime) { - history[0].z = history[1].z; - } - - assert("there should never be less than three frames in the history" && (history.size() >= 3)); - - // Find the range of zoom levels we want to fade between - float startingZ = history.front().z; - const FrameSnapshot lastFrame = history.back(); - float endingZ = lastFrame.z; - float lowZ = std::fmin(startingZ, endingZ); - float highZ = std::fmax(startingZ, endingZ); - - // Calculate the speed of zooming, and how far it would zoom in terms of zoom levels in one duration - float zoomDiff = endingZ - history[1].z, - timeDiff = lastFrame.t - history[1].t; - float fadedist = zoomDiff / (timeDiff / duration); - -#if defined(DEBUG) - if (std::isnan(fadedist)) fprintf(stderr, "fadedist should never be NaN\n"); -#endif - - // At end of a zoom when the zoom stops changing continue pretending to zoom at that speed - // bump is how much farther it would have been if it had continued zooming at the same rate - float bump = (currentTime - lastFrame.t) / duration * fadedist; - - textShader->setFadeDist(fadedist * 10); - textShader->setMinFadeZoom(std::floor(lowZ * 10)); - textShader->setMaxFadeZoom(std::floor(highZ * 10)); - textShader->setFadeZoom((map.getState().getNormalizedZoom() + bump) * 10); - - // This defines the gamma around the SDF cutoff value. - const float sdfGamma = 0.75f / 10.0f; - - // Our signed distance fields are scaled so that 1 pixel is scaled to 32 pixels. - // Our cutoff between positive and negative values is hard coded to 64 (== 2 pixels). - // This means that our 6/8 of the value range lies outside the glyph outline. - const float sdfOffset = (256.0f - 64.0f) / 32.0f; - - // Currently, all of our fonts are rendered with a font size of 24px. - const float sdfFontSize = 24.0f; - - // The default gamma value has to be adjust for the current pixelratio so that we're not drawing - // blurry font on retina screens. - const float gamma = sdfGamma * sdfFontSize / fontSize / map.getState().getPixelRatio(); - - // We're drawing in the translucent pass which is bottom-to-top, so we need - // to draw the halo first. - if (properties.halo_color[3] > 0.0f) { - const float haloWidth = util::clamp((sdfOffset - properties.halo_width / (fontSize / sdfFontSize)) / 8.0f, 0.0f, 1.0f); - - if (properties.halo_blur != 0.0f) { - // We are converting the halo_blur value to current screen pixels. - // Then we're dividing it by two because the gamma value is added/subtracted into both - // directions in the shader, but the halo_blur describes the entire width of the blur. - // Note that this does *not* have to be adjusted for retina screens, because we want the - // same blur width when we explicitly specify one. - textShader->setGamma((properties.halo_blur / (fontSize / sdfFontSize)) / 8.0f / 2.0f); - } else { - textShader->setGamma(sdfGamma); - } - - if (properties.opacity < 1.0f) { - Color color = properties.halo_color; - color[0] *= properties.opacity; - color[1] *= properties.opacity; - color[2] *= properties.opacity; - color[3] *= properties.opacity; - textShader->setColor(color); - } else { - textShader->setColor(properties.halo_color); - } - textShader->setBuffer(haloWidth); - glDepthRange(strata, 1.0f); - bucket.drawGlyphs(*textShader); - } - - if (properties.color[3] > 0.0f) { - // Then, we draw the text over the halo - textShader->setGamma(gamma); - if (properties.opacity < 1.0f) { - Color color = properties.color; - color[0] *= properties.opacity; - color[1] *= properties.opacity; - color[2] *= properties.opacity; - color[3] *= properties.opacity; - textShader->setColor(color); - } else { - textShader->setColor(properties.color); - } - textShader->setBuffer((256.0f - 64.0f) / 256.0f); - glDepthRange(strata + strata_epsilon, 1.0f); - bucket.drawGlyphs(*textShader); - } -} diff --git a/src/renderer/symbol_bucket.cpp b/src/renderer/symbol_bucket.cpp new file mode 100644 index 0000000000..60fd6e240a --- /dev/null +++ b/src/renderer/symbol_bucket.cpp @@ -0,0 +1,271 @@ +#include <mbgl/renderer/symbol_bucket.hpp> +#include <mbgl/geometry/text_buffer.hpp> +#include <mbgl/geometry/icon_buffer.hpp> +#include <mbgl/geometry/glyph_atlas.hpp> +#include <mbgl/geometry/sprite_atlas.hpp> +#include <mbgl/geometry/geometry.hpp> +#include <mbgl/renderer/painter.hpp> +#include <mbgl/text/glyph_store.hpp> +#include <mbgl/text/placement.hpp> +#include <mbgl/platform/log.hpp> + +#include <mbgl/util/utf.hpp> +#include <mbgl/util/token.hpp> +#include <mbgl/util/math.hpp> + + +namespace mbgl { + +SymbolBucket::SymbolBucket(TextVertexBuffer &textVertexBuffer, IconVertexBuffer &iconVertexBuffer, + TriangleElementsBuffer &triangleElementsBuffer, + const StyleBucketSymbol &properties, Placement &placement) + : properties(properties), + textVertexBuffer(textVertexBuffer), + iconVertexBuffer(iconVertexBuffer), + triangleElementsBuffer(triangleElementsBuffer), + placement(placement), + text_vertex_start(textVertexBuffer.index()), + icon_vertex_start(iconVertexBuffer.index()), + triangle_elements_start(triangleElementsBuffer.index()) {} + +void SymbolBucket::render(Painter &painter, std::shared_ptr<StyleLayer> layer_desc, + const Tile::ID &id) { + painter.renderSymbol(*this, layer_desc, id); +} + +bool SymbolBucket::hasData() const { + return hasTextData() || hasIconData(); +} + + +bool SymbolBucket::hasTextData() const { + return !triangleGroups.empty(); +} + +bool SymbolBucket::hasIconData() const { + return icon_vertex_end > 0; +} + +void SymbolBucket::addGlyph(uint64_t tileid, const std::string stackname, + const std::u32string &string, const FontStack &fontStack, + GlyphAtlas &glyphAtlas, GlyphPositions &face) { + const std::map<uint32_t, SDFGlyph> &sdfs = fontStack.getSDFs(); + // Loop through all characters and add glyph to atlas, positions. + for (uint32_t chr : string) { + auto sdf_it = sdfs.find(chr); + if (sdf_it != sdfs.end()) { + const SDFGlyph& sdf = sdf_it->second; + const Rect<uint16_t> rect = glyphAtlas.addGlyph(tileid, stackname, sdf); + face.emplace(chr, Glyph{rect, sdf.metrics}); + } + } +} + +void SymbolBucket::addFeatures(const VectorTileLayer &layer, const FilterExpression &filter, + const Tile::ID &id, SpriteAtlas &spriteAtlas, GlyphAtlas &glyphAtlas, + GlyphStore &glyphStore) { + const bool text = properties.text.field.size(); + const bool icon = properties.icon.image.size(); + + util::utf8_to_utf32 ucs4conv; + + std::vector<std::pair<std::u32string, pbf>> labels; + + // Determine and load glyph ranges + { + std::set<GlyphRange> ranges; + + FilteredVectorTileLayer filtered_layer(layer, filter); + for (const pbf &feature_pbf : filtered_layer) { + VectorTileFeature feature {feature_pbf, layer}; + + if (text) { + std::string u8string = util::replaceTokens(properties.text.field, feature.properties); + + auto& convert = std::use_facet<std::ctype<char>>(std::locale()); + if (properties.text.transform == TextTransformType::Uppercase) { + convert.toupper(&u8string[0], &u8string[0] + u8string.size()); + } else if (properties.text.transform == TextTransformType::Lowercase) { + convert.tolower(&u8string[0], &u8string[0] + u8string.size()); + } + + std::u32string string = ucs4conv.convert(u8string); + + if (string.size()) { + // Loop through all characters of this text and collect unique codepoints. + for (char32_t chr : string) { + ranges.insert(getGlyphRange(chr)); + } + + labels.emplace_back(string, feature.geometry); + } + } + + // TODO: refactor for simultaneous placement + // TODO: refactor to use quads instead of GL_POINTS + if (icon) { + std::string field = util::replaceTokens(properties.icon.image, feature.properties); + + // TODO: remove hardcoded size 12. + const Rect<uint16_t> rect = spriteAtlas.getIcon(12, field); + const uint16_t tx = rect.x + rect.w / 2; + const uint16_t ty = rect.y + rect.h / 2; + + Geometry::command cmd; + pbf geom = feature.geometry; + Geometry geometry(geom); + int32_t x, y; + while ((cmd = geometry.next(x, y)) != Geometry::end) { + if (cmd == Geometry::move_to) { + iconVertexBuffer.add(x, y, tx, ty); + } else { + Log::Warning(Event::ParseTile, "other command than move_to in icon geometry"); + } + } + + icon_vertex_end = iconVertexBuffer.index(); + } + } + + glyphStore.waitForGlyphRanges(properties.text.font, ranges); + } + + + float horizontalAlign = 0.5; + if (properties.text.horizontal_align == TextHorizontalAlignType::Right) horizontalAlign = 1; + else if (properties.text.horizontal_align == TextHorizontalAlignType::Left) horizontalAlign = 0; + + float verticalAlign = 0.5; + if (properties.text.vertical_align == TextVerticalAlignType::Bottom) verticalAlign = 1; + else if (properties.text.vertical_align == TextVerticalAlignType::Top) verticalAlign = 0; + + + + // Create a copy! + const FontStack &fontStack = glyphStore.getFontStack(properties.text.font); + GlyphPositions face; + + + // Shape and place all labels. + for (const std::pair<std::u32string, pbf> &label : labels) { + + // Shape labels. + const Shaping shaping = fontStack.getShaping(label.first, properties.text.max_width, + properties.text.line_height, horizontalAlign, + verticalAlign, properties.text.letter_spacing); + + addGlyph(id.to_uint64(), properties.text.font, label.first, fontStack, glyphAtlas, face); + + // Place labels. + addFeature(label.second, face, shaping); + } +} + +void SymbolBucket::addFeature(const pbf &geom_pbf, const GlyphPositions &face, + const Shaping &shaping) { + // Decode all lines. + std::vector<Coordinate> line; + Geometry::command cmd; + + Coordinate coord; + pbf geom(geom_pbf); + Geometry geometry(geom); + int32_t x, y; + while ((cmd = geometry.next(x, y)) != Geometry::end) { + if (cmd == Geometry::move_to) { + if (!line.empty()) { + placement.addFeature(*this, line, properties, face, shaping); + line.clear(); + } + } + line.emplace_back(x, y); + } + if (line.size()) { + placement.addFeature(*this, line, properties, face, shaping); + } +} + +void SymbolBucket::addGlyphs(const PlacedGlyphs &glyphs, float placementZoom, + PlacementRange placementRange, float zoom) { + placementZoom += zoom; + + for (const PlacedGlyph &glyph : glyphs) { + const auto &tl = glyph.tl; + const auto &tr = glyph.tr; + const auto &bl = glyph.bl; + const auto &br = glyph.br; + const auto &tex = glyph.tex; + const auto &angle = glyph.angle; + + float minZoom = util::max(static_cast<float>(zoom + log(glyph.glyphBox.minScale) / log(2)), + placementZoom); + float maxZoom = + util::min(static_cast<float>(zoom + log(glyph.glyphBox.maxScale) / log(2)), 25.0f); + const auto &glyphAnchor = glyph.glyphBox.anchor; + + if (maxZoom <= minZoom) + continue; + + // Lower min zoom so that while fading out the label + // it can be shown outside of collision-free zoom levels + if (minZoom == placementZoom) { + minZoom = 0; + } + + const int glyph_vertex_length = 4; + + if (!triangleGroups.size() || + (triangleGroups.back().vertex_length + glyph_vertex_length > 65535)) { + // Move to a new group because the old one can't hold the geometry. + triangleGroups.emplace_back(); + } + + // We're generating triangle fans, so we always start with the first + // coordinate in this polygon. + triangle_group_type &triangleGroup = triangleGroups.back(); + uint32_t triangleIndex = triangleGroup.vertex_length; + + // coordinates (2 triangles) + textVertexBuffer.add(glyphAnchor.x, glyphAnchor.y, tl.x, tl.y, tex.x, tex.y, angle, minZoom, + placementRange, maxZoom, placementZoom); + textVertexBuffer.add(glyphAnchor.x, glyphAnchor.y, tr.x, tr.y, tex.x + tex.w, tex.y, angle, + minZoom, placementRange, maxZoom, placementZoom); + textVertexBuffer.add(glyphAnchor.x, glyphAnchor.y, bl.x, bl.y, tex.x, tex.y + tex.h, angle, + minZoom, placementRange, maxZoom, placementZoom); + textVertexBuffer.add(glyphAnchor.x, glyphAnchor.y, br.x, br.y, tex.x + tex.w, tex.y + tex.h, + angle, minZoom, placementRange, maxZoom, placementZoom); + + // add the two triangles, referencing the four coordinates we just inserted. + triangleElementsBuffer.add(triangleIndex + 0, triangleIndex + 1, triangleIndex + 2); + triangleElementsBuffer.add(triangleIndex + 1, triangleIndex + 2, triangleIndex + 3); + + triangleGroup.vertex_length += glyph_vertex_length; + triangleGroup.elements_length += 2; + } +} + +void SymbolBucket::drawGlyphs(TextShader &shader) { + char *vertex_index = BUFFER_OFFSET(text_vertex_start * textVertexBuffer.itemSize); + char *elements_index = + BUFFER_OFFSET(triangle_elements_start * triangleElementsBuffer.itemSize); + for (triangle_group_type &group : triangleGroups) { + group.array.bind(shader, textVertexBuffer, triangleElementsBuffer, vertex_index); + glDrawElements(GL_TRIANGLES, group.elements_length * 3, GL_UNSIGNED_SHORT, elements_index); + vertex_index += group.vertex_length * textVertexBuffer.itemSize; + elements_index += group.elements_length * triangleElementsBuffer.itemSize; + } +} + +void SymbolBucket::drawIcons(IconShader& shader) { + char *vertex_index = BUFFER_OFFSET(icon_vertex_start * iconVertexBuffer.itemSize); + array.bind(shader, iconVertexBuffer, vertex_index); + glDrawArrays(GL_POINTS, 0, (GLsizei)(icon_vertex_end - icon_vertex_start)); +} + +void SymbolBucket::drawIcons(DotShader& shader) { + char *vertex_index = BUFFER_OFFSET(icon_vertex_start * iconVertexBuffer.itemSize); + array.bind(shader, iconVertexBuffer, vertex_index); + glDrawArrays(GL_POINTS, 0, (GLsizei)(icon_vertex_end - icon_vertex_start)); +} + +} diff --git a/src/renderer/text_bucket.cpp b/src/renderer/text_bucket.cpp deleted file mode 100644 index 45c7bf971f..0000000000 --- a/src/renderer/text_bucket.cpp +++ /dev/null @@ -1,146 +0,0 @@ -#include <mbgl/renderer/text_bucket.hpp> -#include <mbgl/geometry/text_buffer.hpp> -#include <mbgl/geometry/elements_buffer.hpp> -#include <mbgl/geometry/geometry.hpp> - -#include <mbgl/renderer/painter.hpp> -#include <mbgl/style/style.hpp> -#include <mbgl/map/vector_tile.hpp> -#include <mbgl/text/placement.hpp> -#include <mbgl/text/glyph_store.hpp> -#include <mbgl/util/constants.hpp> - -#include <mbgl/util/math.hpp> -#include <mbgl/platform/gl.hpp> - -#include <iostream> - -#include <cassert> - -using namespace mbgl; - -TextBucket::TextBucket( - TextVertexBuffer &vertexBuffer, - TriangleElementsBuffer &triangleElementsBuffer, - const StyleBucketText &properties, Placement &placement) - : properties(properties), - vertexBuffer(vertexBuffer), - triangleElementsBuffer(triangleElementsBuffer), - placement(placement), - vertex_start(vertexBuffer.index()), - triangle_elements_start(triangleElementsBuffer.index()) {} - -void TextBucket::addGlyphs(const PlacedGlyphs &glyphs, float placementZoom, - PlacementRange placementRange, float zoom) { - placementZoom += zoom; - - for (const PlacedGlyph &glyph : glyphs) { - const auto &tl = glyph.tl; - const auto &tr = glyph.tr; - const auto &bl = glyph.bl; - const auto &br = glyph.br; - const auto &tex = glyph.tex; - const auto &angle = glyph.angle; - - float minZoom = util::max( - static_cast<float>(zoom + log(glyph.glyphBox.minScale) / log(2)), - placementZoom); - float maxZoom = util::min( - static_cast<float>(zoom + log(glyph.glyphBox.maxScale) / log(2)), - 25.0f); - const auto &glyphAnchor = glyph.glyphBox.anchor; - - if (maxZoom <= minZoom) - continue; - - // Lower min zoom so that while fading out the label - // it can be shown outside of collision-free zoom levels - if (minZoom == placementZoom) { - minZoom = 0; - } - - const int glyph_vertex_length = 4; - - if (!triangleGroups.size() || - (triangleGroups.back().vertex_length + glyph_vertex_length > - 65535)) { - // Move to a new group because the old one can't hold the geometry. - triangleGroups.emplace_back(); - } - - // We're generating triangle fans, so we always start with the first - // coordinate in this polygon. - triangle_group_type &triangleGroup = triangleGroups.back(); - uint32_t triangleIndex = triangleGroup.vertex_length; - - // coordinates (2 triangles) - vertexBuffer.add(glyphAnchor.x, glyphAnchor.y, tl.x, tl.y, tex.x, - tex.y, angle, minZoom, placementRange, maxZoom, - placementZoom); - vertexBuffer.add(glyphAnchor.x, glyphAnchor.y, tr.x, tr.y, - tex.x + tex.w, tex.y, angle, minZoom, placementRange, - maxZoom, placementZoom); - vertexBuffer.add(glyphAnchor.x, glyphAnchor.y, bl.x, bl.y, tex.x, - tex.y + tex.h, angle, minZoom, placementRange, - maxZoom, placementZoom); - vertexBuffer.add(glyphAnchor.x, glyphAnchor.y, br.x, br.y, - tex.x + tex.w, tex.y + tex.h, angle, minZoom, - placementRange, maxZoom, placementZoom); - - // add the two triangles, referencing the four coordinates we just - // inserted. - triangleElementsBuffer.add(triangleIndex + 0, triangleIndex + 1, - triangleIndex + 2); - triangleElementsBuffer.add(triangleIndex + 1, triangleIndex + 2, - triangleIndex + 3); - - triangleGroup.vertex_length += glyph_vertex_length; - triangleGroup.elements_length += 2; - } -}; - -void TextBucket::addFeature(const pbf &geom_pbf, - const GlyphPositions &face, - const Shaping &shaping) { - // Decode all lines. - std::vector<Coordinate> line; - Geometry::command cmd; - - Coordinate coord; - pbf geom(geom_pbf); - Geometry geometry(geom); - int32_t x, y; - while ((cmd = geometry.next(x, y)) != Geometry::end) { - if (cmd == Geometry::move_to) { - if (!line.empty()) { - placement.addFeature(*this, line, properties, face, shaping); - line.clear(); - } - } - line.emplace_back(x, y); - } - if (line.size()) { - placement.addFeature(*this, line, properties, face, shaping); - } -} - -void TextBucket::render(Painter &painter, std::shared_ptr<StyleLayer> layer_desc, - const Tile::ID &id) { - painter.renderText(*this, layer_desc, id); -} - -bool TextBucket::hasData() const { - return !triangleGroups.empty(); -} - -void TextBucket::drawGlyphs(TextShader &shader) { - char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize); - char *elements_index = - BUFFER_OFFSET(triangle_elements_start * triangleElementsBuffer.itemSize); - for (triangle_group_type &group : triangleGroups) { - group.array.bind(shader, vertexBuffer, triangleElementsBuffer, vertex_index); - glDrawElements(GL_TRIANGLES, group.elements_length * 3, GL_UNSIGNED_SHORT, elements_index); - vertex_index += group.vertex_length * vertexBuffer.itemSize; - elements_index += group.elements_length * triangleElementsBuffer.itemSize; - } -} diff --git a/src/style/property_fallback.cpp b/src/style/property_fallback.cpp index fb291034e9..4401c8105a 100644 --- a/src/style/property_fallback.cpp +++ b/src/style/property_fallback.cpp @@ -33,6 +33,7 @@ const std::map<PropertyKey, PropertyValue> PropertyFallbackValue::properties = { { PropertyKey::IconTranslateX, defaultStyleProperties<SymbolProperties>().icon.translate[0] }, { PropertyKey::IconTranslateY, defaultStyleProperties<SymbolProperties>().icon.translate[1] }, { PropertyKey::IconTranslateAnchor, defaultStyleProperties<SymbolProperties>().icon.translate_anchor }, + { PropertyKey::TextOpacity, defaultStyleProperties<SymbolProperties>().text.opacity }, { PropertyKey::TextSize, defaultStyleProperties<SymbolProperties>().text.size }, { PropertyKey::TextColor, defaultStyleProperties<SymbolProperties>().text.color }, diff --git a/src/style/style_bucket.cpp b/src/style/style_bucket.cpp index afd4bc09f7..9a40c2386b 100644 --- a/src/style/style_bucket.cpp +++ b/src/style/style_bucket.cpp @@ -6,8 +6,7 @@ StyleBucket::StyleBucket(StyleLayerType type) { switch (type) { case StyleLayerType::Fill: render = StyleBucketFill{}; break; case StyleLayerType::Line: render = StyleBucketLine{}; break; - case StyleLayerType::Icon: render = StyleBucketIcon{}; break; - case StyleLayerType::Text: render = StyleBucketText{}; break; + case StyleLayerType::Symbol: render = StyleBucketSymbol{}; break; case StyleLayerType::Raster: render = StyleBucketRaster{}; break; default: break; } diff --git a/src/style/style_layer.cpp b/src/style/style_layer.cpp index df94ef9524..3d5ffd6a43 100644 --- a/src/style/style_layer.cpp +++ b/src/style/style_layer.cpp @@ -191,23 +191,30 @@ void StyleLayer::applyStyleProperties<LineProperties>(const float z, const times } template <> -void StyleLayer::applyStyleProperties<IconProperties>(const float z, const timestamp now) { - properties.set<IconProperties>(); - IconProperties &icon = properties.get<IconProperties>(); - applyStyleProperty(PropertyKey::IconOpacity, icon.opacity, z, now); - applyStyleProperty(PropertyKey::IconRotate, icon.rotate, z, now); -} +void StyleLayer::applyStyleProperties<SymbolProperties>(const float z, const timestamp now) { + properties.set<SymbolProperties>(); + SymbolProperties &symbol = properties.get<SymbolProperties>(); + applyStyleProperty(PropertyKey::IconOpacity, symbol.icon.opacity, z, now); + applyStyleProperty(PropertyKey::IconRotate, symbol.icon.rotate, z, now); + applyStyleProperty(PropertyKey::IconSize, symbol.icon.size, z, now); + applyStyleProperty(PropertyKey::IconColor, symbol.icon.color, z, now); + applyStyleProperty(PropertyKey::IconHaloColor, symbol.icon.halo_color, z, now); + applyStyleProperty(PropertyKey::IconHaloWidth, symbol.icon.halo_width, z, now); + applyStyleProperty(PropertyKey::IconHaloBlur, symbol.icon.halo_blur, z, now); + applyStyleProperty(PropertyKey::IconTranslateX, symbol.icon.translate[0], z, now); + applyStyleProperty(PropertyKey::IconTranslateY, symbol.icon.translate[1], z, now); + applyStyleProperty(PropertyKey::IconTranslateAnchor, symbol.icon.translate_anchor, z, now); + + applyStyleProperty(PropertyKey::TextOpacity, symbol.text.opacity, z, now); + applyStyleProperty(PropertyKey::TextSize, symbol.text.size, z, now); + applyStyleProperty(PropertyKey::TextColor, symbol.text.color, z, now); + applyStyleProperty(PropertyKey::TextHaloColor, symbol.text.halo_color, z, now); + applyStyleProperty(PropertyKey::TextHaloWidth, symbol.text.halo_width, z, now); + applyStyleProperty(PropertyKey::TextHaloBlur, symbol.text.halo_blur, z, now); + applyStyleProperty(PropertyKey::TextTranslateX, symbol.text.translate[0], z, now); + applyStyleProperty(PropertyKey::TextTranslateY, symbol.text.translate[1], z, now); + applyStyleProperty(PropertyKey::TextTranslateAnchor, symbol.text.translate_anchor, z, now); -template <> -void StyleLayer::applyStyleProperties<TextProperties>(const float z, const timestamp now) { - properties.set<TextProperties>(); - TextProperties &text = properties.get<TextProperties>(); - applyStyleProperty(PropertyKey::TextOpacity, text.opacity, z, now); - applyStyleProperty(PropertyKey::TextSize, text.size, z, now); - applyStyleProperty(PropertyKey::TextColor, text.color, z, now); - applyStyleProperty(PropertyKey::TextHaloColor, text.halo_color, z, now); - applyStyleProperty(PropertyKey::TextHaloWidth, text.halo_width, z, now); - applyStyleProperty(PropertyKey::TextHaloBlur, text.halo_blur, z, now); } template <> @@ -247,8 +254,7 @@ void StyleLayer::updateProperties(float z, const timestamp now) { switch (type) { case StyleLayerType::Fill: applyStyleProperties<FillProperties>(z, now); break; case StyleLayerType::Line: applyStyleProperties<LineProperties>(z, now); break; - case StyleLayerType::Icon: applyStyleProperties<IconProperties>(z, now); break; - case StyleLayerType::Text: applyStyleProperties<TextProperties>(z, now); break; + case StyleLayerType::Symbol: applyStyleProperties<SymbolProperties>(z, now); break; case StyleLayerType::Raster: applyStyleProperties<RasterProperties>(z, now); break; case StyleLayerType::Composite: applyStyleProperties<CompositeProperties>(z, now); break; case StyleLayerType::Background: applyStyleProperties<BackgroundProperties>(z, now); break; diff --git a/src/style/style_parser.cpp b/src/style/style_parser.cpp index 0bed3381cd..3377185d32 100644 --- a/src/style/style_parser.cpp +++ b/src/style/style_parser.cpp @@ -63,6 +63,20 @@ JSVal StyleParser::replaceConstant(JSVal value) { #pragma mark - Parse Render Properties +template<> bool StyleParser::parseRenderProperty(JSVal value, bool &target, const char *name) { + if (value.HasMember(name)) { + JSVal property = replaceConstant(value[name]); + if (property.IsBool()) { + target = property.GetBool(); + return true; + } else { + fprintf(stderr, "[WARNING] '%s' must be a boolean\n", name); + } + } + return false; +} + + template<> bool StyleParser::parseRenderProperty(JSVal value, std::string &target, const char *name) { if (value.HasMember(name)) { JSVal property = replaceConstant(value[name]); @@ -139,12 +153,12 @@ template<> bool StyleParser::parseRenderProperty(JSVal value, vec2<float> &targe return false; } -template<typename T, typename Parser> -bool StyleParser::parseRenderProperty(JSVal value, T &target, const char *name, Parser &parser) { +template<typename Parser, typename T> +bool StyleParser::parseRenderProperty(JSVal value, T &target, const char *name) { if (value.HasMember(name)) { JSVal property = replaceConstant(value[name]); if (property.IsString()) { - target = parser({ property.GetString(), property.GetStringLength() }); + target = Parser({ property.GetString(), property.GetStringLength() }); return true; } else { fprintf(stderr, "[WARNING] %s must have one of the enum values\n", name); @@ -167,7 +181,7 @@ void StyleParser::parseSources(JSVal value) { int32_t min_zoom = 0; int32_t max_zoom = 22; - parseRenderProperty(itr->value, type, "type", parseSourceType); + parseRenderProperty<SourceTypeClass>(itr->value, type, "type"); parseRenderProperty(itr->value, url, "url"); if (type == SourceType::Raster) { parseRenderProperty(itr->value, tile_size, "tileSize"); @@ -348,19 +362,19 @@ template<> std::tuple<bool, std::string> StyleParser::parseProperty(JSVal value, template<> std::tuple<bool, TranslateAnchorType> StyleParser::parseProperty(JSVal value, const char *property_name) { if (!value.IsString()) { fprintf(stderr, "[WARNING] value of '%s' must be a string\n", property_name); - return std::tuple<bool, TranslateAnchorType> { false, TranslateAnchorType::Default }; + return std::tuple<bool, TranslateAnchorType> { false, TranslateAnchorType::Map }; } - return std::tuple<bool, TranslateAnchorType> { true, parseTranslateAnchorType({ value.GetString(), value.GetStringLength() }) }; + return std::tuple<bool, TranslateAnchorType> { true, TranslateAnchorTypeClass({ value.GetString(), value.GetStringLength() }) }; } template<> std::tuple<bool, RotateAnchorType> StyleParser::parseProperty<RotateAnchorType>(JSVal value, const char *property_name) { if (!value.IsString()) { fprintf(stderr, "[WARNING] value of '%s' must be a string\n", property_name); - return std::tuple<bool, RotateAnchorType> { false, RotateAnchorType::Default }; + return std::tuple<bool, RotateAnchorType> { false, RotateAnchorType::Map }; } - return std::tuple<bool, RotateAnchorType> { true, parseRotateAnchorType({ value.GetString(), value.GetStringLength() }) }; + return std::tuple<bool, RotateAnchorType> { true, RotateAnchorTypeClass({ value.GetString(), value.GetStringLength() }) }; } template<> std::tuple<bool, PropertyTransition> StyleParser::parseProperty(JSVal value, const char *property_name) { @@ -592,6 +606,18 @@ void StyleParser::parseStyle(JSVal value, ClassProperties &klass) { parseOptionalProperty<Function<float>>("icon-opacity", Key::IconOpacity, klass, value); parseOptionalProperty<PropertyTransition>("transition-icon-opacity", Key::IconOpacity, klass, value); parseOptionalProperty<Function<float>>("icon-rotate", Key::IconRotate, klass, value); + parseOptionalProperty<Function<float>>("icon-size", Key::IconSize, klass, value); + parseOptionalProperty<PropertyTransition>("transition-icon-size", Key::IconSize, klass, value); + parseOptionalProperty<Function<Color>>("icon-color", Key::IconColor, klass, value); + parseOptionalProperty<PropertyTransition>("transition-icon-color", Key::IconColor, klass, value); + parseOptionalProperty<Function<Color>>("icon-halo-color", Key::IconHaloColor, klass, value); + parseOptionalProperty<PropertyTransition>("transition-icon-halo-color", Key::IconHaloColor, klass, value); + parseOptionalProperty<Function<float>>("icon-halo-width", Key::IconHaloWidth, klass, value); + parseOptionalProperty<PropertyTransition>("transition-icon-halo-width", Key::IconHaloWidth, klass, value); + parseOptionalProperty<Function<float>>("icon-halo-blur", Key::IconHaloBlur, klass, value); + parseOptionalProperty<PropertyTransition>("transition-icon-halo-blur", Key::IconHaloBlur, klass, value); + parseOptionalProperty<Function<float>>("icon-translate", { Key::IconTranslateX, Key::IconTranslateY }, klass, value); + parseOptionalProperty<PropertyTransition>("transition-icon-translate", Key::IconTranslate, klass, value); parseOptionalProperty<Function<float>>("text-opacity", Key::TextOpacity, klass, value); parseOptionalProperty<PropertyTransition>("transition-text-opacity", Key::TextOpacity, klass, value); @@ -605,6 +631,8 @@ void StyleParser::parseStyle(JSVal value, ClassProperties &klass) { parseOptionalProperty<PropertyTransition>("transition-text-halo-width", Key::TextHaloWidth, klass, value); parseOptionalProperty<Function<float>>("text-halo-blur", Key::TextHaloBlur, klass, value); parseOptionalProperty<PropertyTransition>("transition-text-halo-blur", Key::TextHaloBlur, klass, value); + parseOptionalProperty<Function<float>>("text-translate", { Key::TextTranslateX, Key::TextTranslateY }, klass, value); + parseOptionalProperty<PropertyTransition>("transition-text-translate", Key::TextTranslate, klass, value); parseOptionalProperty<Function<float>>("composite-opacity", Key::CompositeOpacity, klass, value); parseOptionalProperty<PropertyTransition>("transition-composite-opacity", Key::CompositeOpacity, klass, value); @@ -833,61 +861,63 @@ void StyleParser::parseRender(JSVal value, std::shared_ptr<StyleLayer> &layer) { case StyleLayerType::Fill: { StyleBucketFill &render = bucket.render.get<StyleBucketFill>(); - parseRenderProperty(value, render.winding, "fill-winding", parseWindingType); + parseRenderProperty<WindingTypeClass>(value, render.winding, "fill-winding"); } break; case StyleLayerType::Line: { StyleBucketLine &render = bucket.render.get<StyleBucketLine>(); - parseRenderProperty(value, render.cap, "line-cap", parseCapType); - parseRenderProperty(value, render.join, "line-join", parseJoinType); + parseRenderProperty<CapTypeClass>(value, render.cap, "line-cap"); + parseRenderProperty<JoinTypeClass>(value, render.join, "line-join"); parseRenderProperty(value, render.miter_limit, "line-miter-limit"); parseRenderProperty(value, render.round_limit, "line-round-limit"); } break; - case StyleLayerType::Icon: { - StyleBucketIcon &render = bucket.render.get<StyleBucketIcon>(); - - parseRenderProperty(value, render.size, "icon-size"); - parseRenderProperty(value, render.icon, "icon-image"); - parseRenderProperty(value, render.spacing, "icon-spacing"); - parseRenderProperty(value, render.padding, "icon-padding"); - if (parseRenderProperty(value, render.translate, "icon-translate")) { - render.translate.x *= 24; - render.translate.y *= -24; - } - parseRenderProperty(value, render.translate_anchor, "icon-translate-anchor", parseTranslateAnchorType); - } break; - - case StyleLayerType::Text: { - StyleBucketText &render = bucket.render.get<StyleBucketText>(); - - parseRenderProperty(value, render.field, "text-field"); - parseRenderProperty(value, render.path, "text-path", parseTextPathType); - parseRenderProperty(value, render.transform, "text-transform", parseTextTransformType); - parseRenderProperty(value, render.font, "text-font"); - parseRenderProperty(value, render.max_size, "text-max-size"); - if (parseRenderProperty(value, render.max_width, "text-max-width")) { - render.max_width *= 24; // em - } - if (parseRenderProperty(value, render.line_height, "text-line-height")) { - render.line_height *= 24; // em - } - if (parseRenderProperty(value, render.letter_spacing, "text-letter-spacing")) { - render.letter_spacing *= 24; // em - } - parseRenderProperty(value, render.alignment, "text-alignment", parseAlignmentType); - parseRenderProperty(value, render.vertical_alignment, "text-vertical-alignment", parseVerticalAlignmentType); - parseRenderProperty(value, render.max_angle_delta, "text-max-angle"); - parseRenderProperty(value, render.min_distance, "text-min-distance"); - parseRenderProperty(value, render.rotate, "text-rotate"); - parseRenderProperty(value, render.slant, "text-slant"); - parseRenderProperty(value, render.padding, "text-padding"); - if (parseRenderProperty(value, render.translate, "text-translate")) { - render.translate.x *= 24; - render.translate.y *= -24; - } - parseRenderProperty(value, render.translate_anchor, "text-translate-anchor", parseTranslateAnchorType); + case StyleLayerType::Symbol: { + StyleBucketSymbol &render = bucket.render.get<StyleBucketSymbol>(); + + parseRenderProperty<PlacementTypeClass>(value, render.placement, "symbol-placement"); + parseRenderProperty(value, render.min_distance, "symbol-min-distance"); + + parseRenderProperty(value, render.icon.allow_overlap, "icon-allow-overlap"); + parseRenderProperty(value, render.icon.ignore_placement, "icon-ignore-placement"); + parseRenderProperty(value, render.icon.optional, "icon-optional"); + parseRenderProperty<RotationAlignmentTypeClass>(value, render.icon.rotation_alignment, "icon-rotation-alignment"); + parseRenderProperty(value, render.icon.max_size, "icon-max-size"); + parseRenderProperty(value, render.icon.image, "icon-image"); + parseRenderProperty(value, render.icon.padding, "icon-padding"); + parseRenderProperty(value, render.icon.keep_upright, "icon-keep-upright"); + parseRenderProperty(value, render.icon.offset, "icon-offset"); + parseRenderProperty<TranslateAnchorTypeClass>(value, render.icon.translate_anchor, "icon-translate-anchor"); + + + parseRenderProperty<RotationAlignmentTypeClass>(value, render.text.rotation_alignment, "text-rotation-alignment"); + parseRenderProperty(value, render.text.field, "text-field"); + parseRenderProperty(value, render.text.font, "text-font"); + parseRenderProperty(value, render.text.max_size, "text-max-size"); + if (parseRenderProperty(value, render.text.max_width, "text-max-width")) { + render.text.max_width *= 24; // em + } + if (parseRenderProperty(value, render.text.line_height, "text-line-height")) { + render.text.line_height *= 24; // em + } + if (parseRenderProperty(value, render.text.letter_spacing, "text-letter-spacing")) { + render.text.letter_spacing *= 24; // em + } + parseRenderProperty<TextJustifyTypeClass>(value, render.text.justify, "text-justify"); + parseRenderProperty<TextHorizontalAlignTypeClass>(value, render.text.horizontal_align, "text-horizontal-align"); + parseRenderProperty<TextVerticalAlignTypeClass>(value, render.text.vertical_align, "text-vertical-align"); + parseRenderProperty(value, render.text.max_angle_delta, "text-max-angle"); + parseRenderProperty(value, render.text.rotate, "text-rotate"); + parseRenderProperty(value, render.text.slant, "text-slant"); + parseRenderProperty(value, render.text.padding, "text-padding"); + parseRenderProperty(value, render.text.keep_upright, "text-keep-upright"); + parseRenderProperty<TextTransformTypeClass>(value, render.text.transform, "text-transform"); + parseRenderProperty(value, render.text.offset, "text-offset"); + parseRenderProperty<TranslateAnchorTypeClass>(value, render.text.translate_anchor, "text-translate-anchor"); + parseRenderProperty(value, render.text.allow_overlap, "text-allow-overlap"); + parseRenderProperty(value, render.text.ignore_placement, "text-ignore-placement"); + parseRenderProperty(value, render.text.optional, "text-optional"); } break; default: // There are no render properties for these layer types. diff --git a/src/style/style_properties.cpp b/src/style/style_properties.cpp index 3908d35ffa..1b05528d2f 100644 --- a/src/style/style_properties.cpp +++ b/src/style/style_properties.cpp @@ -4,8 +4,6 @@ namespace mbgl { template<> const FillProperties &defaultStyleProperties() { static const FillProperties p; return p; } template<> const LineProperties &defaultStyleProperties() { static const LineProperties p; return p; } -template<> const IconProperties &defaultStyleProperties() { static const IconProperties p; return p; } -template<> const TextProperties &defaultStyleProperties() { static const TextProperties p; return p; } template<> const SymbolProperties &defaultStyleProperties() { static const SymbolProperties p; return p; } template<> const CompositeProperties &defaultStyleProperties() { static const CompositeProperties p; return p; } template<> const RasterProperties &defaultStyleProperties() { static const RasterProperties p; return p; } diff --git a/src/text/placement.cpp b/src/text/placement.cpp index b15266137b..82c45b4e0c 100644 --- a/src/text/placement.cpp +++ b/src/text/placement.cpp @@ -1,7 +1,7 @@ #include <mbgl/text/placement.hpp> #include <mbgl/map/vector_tile.hpp> #include <mbgl/geometry/interpolate.hpp> -#include <mbgl/renderer/text_bucket.hpp> +#include <mbgl/renderer/symbol_bucket.hpp> #include <mbgl/util/math.hpp> #include <mbgl/util/constants.hpp> @@ -245,11 +245,11 @@ void getGlyphs(PlacedGlyphs &glyphs, GlyphBoxes &boxes, } } -void Placement::addFeature(TextBucket &bucket, const std::vector<Coordinate> &line, - const StyleBucketText &info, const GlyphPositions &face, +void Placement::addFeature(SymbolBucket &bucket, const std::vector<Coordinate> &line, + const StyleBucketSymbol &info, const GlyphPositions &face, const Shaping &shaping) { - const bool horizontal = info.path == TextPathType::Horizontal; - const float fontScale = (tileExtent / util::tileSize) / (glyphSize / info.max_size); + const bool horizontal = info.text.rotation_alignment == RotationAlignmentType::Viewport; + const float fontScale = (tileExtent / util::tileSize) / (glyphSize / info.text.max_size); Anchors anchors; @@ -272,10 +272,10 @@ void Placement::addFeature(TextBucket &bucket, const std::vector<Coordinate> &li PlacedGlyphs glyphs; GlyphBoxes boxes; - getGlyphs(glyphs, boxes, anchor, info.translate, shaping, face, fontScale, horizontal, line, - info.max_angle_delta * (M_PI/180), info.rotate); + getGlyphs(glyphs, boxes, anchor, info.text.offset, shaping, face, fontScale, horizontal, line, + info.text.max_angle_delta * (M_PI/180), info.text.rotate); PlacementProperty place = collision.place(boxes, anchor, anchor.scale, maxPlacementScale, - info.padding, horizontal, info.always_visible); + info.text.padding, horizontal, info.text.ignore_placement); if (place) { bucket.addGlyphs(glyphs, place.zoom, place.rotationRange, zoom - zOffset); } |