diff options
Diffstat (limited to 'src')
202 files changed, 10343 insertions, 1622 deletions
diff --git a/src/mbgl/annotation/render_annotation_source.cpp b/src/mbgl/annotation/render_annotation_source.cpp index 0aff64583d..38ca5ccd0b 100644 --- a/src/mbgl/annotation/render_annotation_source.cpp +++ b/src/mbgl/annotation/render_annotation_source.cpp @@ -64,8 +64,9 @@ std::unordered_map<std::string, std::vector<Feature>> RenderAnnotationSource::queryRenderedFeatures(const ScreenLineString& geometry, const TransformState& transformState, const std::vector<const RenderLayer*>& layers, - const RenderedQueryOptions& options) const { - return tilePyramid.queryRenderedFeatures(geometry, transformState, layers, options); + const RenderedQueryOptions& options, + const CollisionIndex& collisionIndex) const { + return tilePyramid.queryRenderedFeatures(geometry, transformState, layers, options, collisionIndex); } std::vector<Feature> RenderAnnotationSource::querySourceFeatures(const SourceQueryOptions&) const { diff --git a/src/mbgl/annotation/render_annotation_source.hpp b/src/mbgl/annotation/render_annotation_source.hpp index 9536b2e101..aa2578d4e3 100644 --- a/src/mbgl/annotation/render_annotation_source.hpp +++ b/src/mbgl/annotation/render_annotation_source.hpp @@ -27,7 +27,8 @@ public: queryRenderedFeatures(const ScreenLineString& geometry, const TransformState& transformState, const std::vector<const RenderLayer*>& layers, - const RenderedQueryOptions& options) const final; + const RenderedQueryOptions& options, + const CollisionIndex& collisionIndex) const final; std::vector<Feature> querySourceFeatures(const SourceQueryOptions&) const final; @@ -43,7 +44,7 @@ private: template <> inline bool RenderSource::is<RenderAnnotationSource>() const { - return baseImpl->type == SourceType::Annotations; + return baseImpl->type == style::SourceType::Annotations; } } // namespace mbgl diff --git a/src/mbgl/geometry/dem_data.cpp b/src/mbgl/geometry/dem_data.cpp new file mode 100644 index 0000000000..78134dadc1 --- /dev/null +++ b/src/mbgl/geometry/dem_data.cpp @@ -0,0 +1,94 @@ +#include <mbgl/geometry/dem_data.hpp> +#include <mbgl/math/clamp.hpp> + +namespace mbgl { + +DEMData::DEMData(const PremultipliedImage& _image): + dim(_image.size.height), + border(std::max<int32_t>(std::ceil(_image.size.height / 2), 1)), + stride(dim + 2 * border), + image({ static_cast<uint32_t>(stride), static_cast<uint32_t>(stride) }) { + + if (_image.size.height != _image.size.width){ + throw std::runtime_error("raster-dem tiles must be square."); + } + + std::memset(image.data.get(), 0, image.bytes()); + + for (int32_t y = 0; y < dim; y++) { + for (int32_t x = 0; x < dim; x++) { + const int32_t i = y * dim + x; + const int32_t j = i * 4; + set(x, y, (_image.data[j] * 256 * 256 + _image.data[j+1] * 256 + _image.data[j+2])/10 - 10000); + } + } + + // in order to avoid flashing seams between tiles, here we are initially populating a 1px border of + // pixels around the image with the data of the nearest pixel from the image. this data is eventually + // replaced when the tile's neighboring tiles are loaded and the accurate data can be backfilled using + // DEMData#backfillBorder + + for (int32_t x = 0; x < dim; x++) { + // left vertical border + set(-1, x, get(0, x)); + + // right vertical border + set(dim, x, get(dim - 1, x)); + + //left horizontal border + set(x, -1, get(x, 0)); + + // right horizontal border + set(x, dim, get(x, dim - 1)); + } + + // corners + set(-1, -1, get(0, 0)); + set(dim, -1, get(dim - 1, 0)); + set( -1, dim, get(0, dim - 1)); + set(dim, dim, get(dim - 1, dim - 1)); +} + +// This function takes the DEMData from a neighboring tile and backfills the edge/corner +// data in order to create a one pixel "buffer" of image data around the tile. This is +// necessary because the hillshade formula calculates the dx/dz, dy/dz derivatives at each +// pixel of the tile by querying the 8 surrounding pixels, and if we don't have the pixel +// buffer we get seams at tile boundaries. +void DEMData::backfillBorder(const DEMData& borderTileData, int8_t dx, int8_t dy) { + auto& o = borderTileData; + + // Tiles from the same source should always be of the same dimensions. + assert(dim == o.dim); + + // We determine the pixel range to backfill based which corner/edge `borderTileData` + // represents. For example, dx = -1, dy = -1 represents the upper left corner of the + // base tile, so we only need to backfill one pixel at coordinates (-1, -1) of the tile + // image. + int32_t _xMin = dx * dim; + int32_t _xMax = dx * dim + dim; + int32_t _yMin = dy * dim; + int32_t _yMax = dy * dim + dim; + + if (dx == -1) _xMin = _xMax - 1; + else if (dx == 1) _xMax = _xMin + 1; + + if (dy == -1) _yMin = _yMax - 1; + else if (dy == 1) _yMax = _yMin + 1; + + int32_t xMin = util::clamp(_xMin, -border, dim + border); + int32_t xMax = util::clamp(_xMax, -border, dim + border); + + int32_t yMin = util::clamp(_yMin, -border, dim + border); + int32_t yMax = util::clamp(_yMax, -border, dim + border); + + int32_t ox = -dx * dim; + int32_t oy = -dy * dim; + + for (int32_t y = yMin; y < yMax; y++) { + for (int32_t x = xMin; x < xMax; x++) { + set(x, y, o.get(x + ox, y + oy)); + } + } +} + +} // namespace mbgl diff --git a/src/mbgl/geometry/dem_data.hpp b/src/mbgl/geometry/dem_data.hpp new file mode 100644 index 0000000000..507a51661d --- /dev/null +++ b/src/mbgl/geometry/dem_data.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include <mbgl/math/clamp.hpp> +#include <mbgl/util/image.hpp> + +#include <memory> +#include <array> +#include <cassert> +#include <vector> + +namespace mbgl { + +class DEMData { +public: + DEMData(const PremultipliedImage& image); + void backfillBorder(const DEMData& borderTileData, int8_t dx, int8_t dy); + + void set(const int32_t x, const int32_t y, const int32_t value) { + reinterpret_cast<int32_t*>(image.data.get())[idx(x, y)] = value + 65536; + } + + int32_t get(const int32_t x, const int32_t y) const { + return reinterpret_cast<const int32_t*>(image.data.get())[idx(x, y)] - 65536; + } + + const PremultipliedImage* getImage() const { + return ℑ + } + + const int32_t dim; + const int32_t border; + const int32_t stride; + + + private: + PremultipliedImage image; + + size_t idx(const int32_t x, const int32_t y) const { + assert(x >= -border); + assert(x < dim + border); + assert(y >= -border); + assert(y < dim + border); + return (y + border) * stride + (x + border); + } + +}; + +} // namespace mbgl diff --git a/src/mbgl/geometry/feature_index.cpp b/src/mbgl/geometry/feature_index.cpp index 1adb933e44..3b5e12b54a 100644 --- a/src/mbgl/geometry/feature_index.cpp +++ b/src/mbgl/geometry/feature_index.cpp @@ -2,7 +2,7 @@ #include <mbgl/renderer/render_layer.hpp> #include <mbgl/renderer/query.hpp> #include <mbgl/renderer/layers/render_symbol_layer.hpp> -#include <mbgl/text/collision_tile.hpp> +#include <mbgl/text/collision_index.hpp> #include <mbgl/util/constants.hpp> #include <mbgl/util/math.hpp> #include <mbgl/math/minmax.hpp> @@ -18,7 +18,7 @@ namespace mbgl { FeatureIndex::FeatureIndex() - : grid(util::EXTENT, 16, 0) { + : grid(util::EXTENT, util::EXTENT, util::EXTENT / 16) { // 16x16 grid -> 32px cell } void FeatureIndex::insert(const GeometryCollection& geometries, @@ -26,8 +26,9 @@ void FeatureIndex::insert(const GeometryCollection& geometries, const std::string& sourceLayerName, const std::string& bucketName) { for (const auto& ring : geometries) { - grid.insert(IndexedSubfeature { index, sourceLayerName, bucketName, sortIndex++ }, - mapbox::geometry::envelope(ring)); + auto envelope = mapbox::geometry::envelope(ring); + grid.insert(IndexedSubfeature(index, sourceLayerName, bucketName, sortIndex++), + {convertPoint<float>(envelope.min), convertPoint<float>(envelope.max)}); } } @@ -47,9 +48,10 @@ void FeatureIndex::query( const double scale, const RenderedQueryOptions& queryOptions, const GeometryTileData& geometryTileData, - const CanonicalTileID& tileID, + const UnwrappedTileID& tileID, + const std::string& sourceID, const std::vector<const RenderLayer*>& layers, - const CollisionTile* collisionTile, + const CollisionIndex& collisionIndex, const float additionalQueryRadius) const { // Determine query radius @@ -58,7 +60,8 @@ void FeatureIndex::query( // Query the grid index mapbox::geometry::box<int16_t> box = mapbox::geometry::envelope(queryGeometry); - std::vector<IndexedSubfeature> features = grid.query({ box.min - additionalRadius, box.max + additionalRadius }); + std::vector<IndexedSubfeature> features = grid.query({ convertPoint<float>(box.min - additionalRadius), + convertPoint<float>(box.max + additionalRadius) }); std::sort(features.begin(), features.end(), topDown); @@ -69,18 +72,13 @@ void FeatureIndex::query( if (indexedFeature.sortIndex == previousSortIndex) continue; previousSortIndex = indexedFeature.sortIndex; - addFeature(result, indexedFeature, queryGeometry, queryOptions, geometryTileData, tileID, layers, bearing, pixelsToTileUnits); + addFeature(result, indexedFeature, queryGeometry, queryOptions, geometryTileData, tileID.canonical, layers, bearing, pixelsToTileUnits); } - // Query symbol features, if they've been placed. - if (!collisionTile) { - return; - } - - std::vector<IndexedSubfeature> symbolFeatures = collisionTile->queryRenderedSymbols(queryGeometry, scale); + std::vector<IndexedSubfeature> symbolFeatures = collisionIndex.queryRenderedSymbols(queryGeometry, tileID, sourceID); std::sort(symbolFeatures.begin(), symbolFeatures.end(), topDownSymbols); for (const auto& symbolFeature : symbolFeatures) { - addFeature(result, symbolFeature, queryGeometry, queryOptions, geometryTileData, tileID, layers, bearing, pixelsToTileUnits); + addFeature(result, symbolFeature, queryGeometry, queryOptions, geometryTileData, tileID.canonical, layers, bearing, pixelsToTileUnits); } } diff --git a/src/mbgl/geometry/feature_index.hpp b/src/mbgl/geometry/feature_index.hpp index 2ae7da33df..e95bb94da6 100644 --- a/src/mbgl/geometry/feature_index.hpp +++ b/src/mbgl/geometry/feature_index.hpp @@ -2,6 +2,7 @@ #include <mbgl/style/types.hpp> #include <mbgl/tile/geometry_tile_data.hpp> +#include <mbgl/tile/tile_id.hpp> #include <mbgl/util/grid_index.hpp> #include <mbgl/util/feature.hpp> @@ -14,16 +15,37 @@ namespace mbgl { class RenderedQueryOptions; class RenderLayer; -class CollisionTile; -class CanonicalTileID; +class CollisionIndex; class IndexedSubfeature { public: IndexedSubfeature() = delete; - std::size_t index; + IndexedSubfeature(std::size_t index_, std::string sourceLayerName_, std::string bucketName_, size_t sortIndex_) + : index(index_) + , sourceLayerName(std::move(sourceLayerName_)) + , bucketName(std::move(bucketName_)) + , sortIndex(sortIndex_) + , tileID(0, 0, 0) + {} + + IndexedSubfeature(std::size_t index_, std::string sourceLayerName_, std::string bucketName_, size_t sortIndex_, + std::string sourceID_, CanonicalTileID tileID_) + : index(index_) + , sourceLayerName(std::move(sourceLayerName_)) + , bucketName(std::move(bucketName_)) + , sortIndex(std::move(sortIndex_)) + , sourceID(std::move(sourceID_)) + , tileID(std::move(tileID_)) + {} + + size_t index; std::string sourceLayerName; std::string bucketName; size_t sortIndex; + + // Only used for symbol features + std::string sourceID; + CanonicalTileID tileID; }; class FeatureIndex { @@ -40,9 +62,10 @@ public: const double scale, const RenderedQueryOptions& options, const GeometryTileData&, - const CanonicalTileID&, + const UnwrappedTileID&, + const std::string&, const std::vector<const RenderLayer*>&, - const CollisionTile*, + const CollisionIndex&, const float additionalQueryRadius) const; static optional<GeometryCoordinates> translateQueryGeometry( diff --git a/src/mbgl/gl/color_mode.hpp b/src/mbgl/gl/color_mode.hpp index e73c8737eb..c6594a3a77 100644 --- a/src/mbgl/gl/color_mode.hpp +++ b/src/mbgl/gl/color_mode.hpp @@ -49,7 +49,7 @@ public: struct Replace { static constexpr BlendEquation equation = BlendEquation::Add; static constexpr BlendFactor srcFactor = One; - static constexpr BlendFactor dstFactor = One; + static constexpr BlendFactor dstFactor = Zero; }; using Add = LinearBlend<BlendEquation::Add>; diff --git a/src/mbgl/gl/context.cpp b/src/mbgl/gl/context.cpp index d1a37a861a..b6244d58c7 100644 --- a/src/mbgl/gl/context.cpp +++ b/src/mbgl/gl/context.cpp @@ -226,16 +226,25 @@ void Context::updateVertexBuffer(UniqueBuffer& buffer, const void* data, std::si MBGL_CHECK_ERROR(glBufferSubData(GL_ARRAY_BUFFER, 0, size, data)); } -UniqueBuffer Context::createIndexBuffer(const void* data, std::size_t size) { +UniqueBuffer Context::createIndexBuffer(const void* data, std::size_t size, const BufferUsage usage) { BufferID id = 0; MBGL_CHECK_ERROR(glGenBuffers(1, &id)); UniqueBuffer result { std::move(id), { this } }; bindVertexArray = 0; globalVertexArrayState.indexBuffer = result; - MBGL_CHECK_ERROR(glBufferData(GL_ELEMENT_ARRAY_BUFFER, size, data, GL_STATIC_DRAW)); + MBGL_CHECK_ERROR(glBufferData(GL_ELEMENT_ARRAY_BUFFER, size, data, static_cast<GLenum>(usage))); return result; } +void Context::updateIndexBuffer(UniqueBuffer& buffer, const void* data, std::size_t size) { + // Be sure to unbind any existing vertex array object before binding the index buffer + // so that we don't mess up another VAO + bindVertexArray = 0; + globalVertexArrayState.indexBuffer = buffer; + MBGL_CHECK_ERROR(glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, size, data)); +} + + UniqueTexture Context::createTexture() { if (pooledTextures.empty()) { pooledTextures.resize(TextureMax); @@ -600,19 +609,19 @@ void Context::clear(optional<mbgl::Color> color, if (color) { mask |= GL_COLOR_BUFFER_BIT; clearColor = *color; - colorMask = { true, true, true, true }; + colorMask = value::ColorMask::Default; } if (depth) { mask |= GL_DEPTH_BUFFER_BIT; clearDepth = *depth; - depthMask = true; + depthMask = value::DepthMask::Default; } if (stencil) { mask |= GL_STENCIL_BUFFER_BIT; clearStencil = *stencil; - stencilMask = 0xFF; + stencilMask = value::StencilMask::Default; } MBGL_CHECK_ERROR(glClear(mask)); diff --git a/src/mbgl/gl/context.hpp b/src/mbgl/gl/context.hpp index 528113cbba..14f078367f 100644 --- a/src/mbgl/gl/context.hpp +++ b/src/mbgl/gl/context.hpp @@ -61,7 +61,7 @@ public: optional<std::pair<BinaryProgramFormat, std::string>> getBinaryProgram(ProgramID) const; template <class Vertex, class DrawMode> - VertexBuffer<Vertex, DrawMode> createVertexBuffer(VertexVector<Vertex, DrawMode>&& v, const BufferUsage usage=BufferUsage::StaticDraw) { + VertexBuffer<Vertex, DrawMode> createVertexBuffer(VertexVector<Vertex, DrawMode>&& v, const BufferUsage usage = BufferUsage::StaticDraw) { return VertexBuffer<Vertex, DrawMode> { v.vertexSize(), createVertexBuffer(v.data(), v.byteSize(), usage) @@ -75,11 +75,18 @@ public: } template <class DrawMode> - IndexBuffer<DrawMode> createIndexBuffer(IndexVector<DrawMode>&& v) { + IndexBuffer<DrawMode> createIndexBuffer(IndexVector<DrawMode>&& v, const BufferUsage usage = BufferUsage::StaticDraw) { return IndexBuffer<DrawMode> { - createIndexBuffer(v.data(), v.byteSize()) + v.indexSize(), + createIndexBuffer(v.data(), v.byteSize(), usage) }; } + + template <class DrawMode> + void updateIndexBuffer(IndexBuffer<DrawMode>& buffer, IndexVector<DrawMode>&& v) { + assert(v.indexSize() == buffer.indexCount); + updateIndexBuffer(buffer.buffer, v.data(), v.byteSize()); + } template <RenderbufferType type> Renderbuffer<type> createRenderbuffer(const Size size) { @@ -250,7 +257,8 @@ private: UniqueBuffer createVertexBuffer(const void* data, std::size_t size, const BufferUsage usage); void updateVertexBuffer(UniqueBuffer& buffer, const void* data, std::size_t size); - UniqueBuffer createIndexBuffer(const void* data, std::size_t size); + UniqueBuffer createIndexBuffer(const void* data, std::size_t size, const BufferUsage usage); + void updateIndexBuffer(UniqueBuffer& buffer, const void* data, std::size_t size); UniqueTexture createTexture(Size size, const void* data, TextureFormat, TextureUnit); void updateTexture(TextureID, Size size, const void* data, TextureFormat, TextureUnit); UniqueFramebuffer createFramebuffer(); @@ -281,8 +289,13 @@ private: std::vector<RenderbufferID> abandonedRenderbuffers; public: - // For testing + // For testing and Windows because Qt + ANGLE + // crashes with VAO enabled. +#if defined(_WINDOWS) + bool disableVAOExtension = true; +#else bool disableVAOExtension = false; +#endif }; } // namespace gl diff --git a/src/mbgl/gl/gl.hpp b/src/mbgl/gl/gl.hpp index 3e21731330..976b7d2f74 100644 --- a/src/mbgl/gl/gl.hpp +++ b/src/mbgl/gl/gl.hpp @@ -1,36 +1,10 @@ #pragma once +#include <mbgl/gl/gl_impl.hpp> + #include <stdexcept> #include <limits> -#if __APPLE__ - #include "TargetConditionals.h" - #if TARGET_OS_IPHONE - #include <OpenGLES/ES2/gl.h> - #include <OpenGLES/ES2/glext.h> - #elif TARGET_IPHONE_SIMULATOR - #include <OpenGLES/ES2/gl.h> - #include <OpenGLES/ES2/glext.h> - #elif TARGET_OS_MAC - #include <OpenGL/OpenGL.h> - #include <OpenGL/gl.h> - #include <OpenGL/glext.h> - #else - #error Unsupported Apple platform - #endif -#elif __ANDROID__ || MBGL_USE_GLES2 - #define GL_GLEXT_PROTOTYPES - #include <GLES2/gl2.h> - #include <GLES2/gl2ext.h> -#elif __QT__ && QT_VERSION >= 0x050000 - #define GL_GLEXT_PROTOTYPES - #include <QtGui/qopengl.h> -#else - #define GL_GLEXT_PROTOTYPES - #include <GL/gl.h> - #include <GL/glext.h> -#endif - namespace mbgl { namespace gl { diff --git a/src/mbgl/gl/index_buffer.hpp b/src/mbgl/gl/index_buffer.hpp index 01b6396e00..87bfb6068f 100644 --- a/src/mbgl/gl/index_buffer.hpp +++ b/src/mbgl/gl/index_buffer.hpp @@ -35,6 +35,7 @@ private: template <class DrawMode> class IndexBuffer { public: + std::size_t indexCount; UniqueBuffer buffer; }; diff --git a/src/mbgl/layout/symbol_instance.cpp b/src/mbgl/layout/symbol_instance.cpp index 02fb800df6..6e152349ca 100644 --- a/src/mbgl/layout/symbol_instance.cpp +++ b/src/mbgl/layout/symbol_instance.cpp @@ -11,7 +11,6 @@ SymbolInstance::SymbolInstance(Anchor& anchor_, optional<PositionedIcon> shapedIcon, const SymbolLayoutProperties::Evaluated& layout, const float layoutTextSize, - const bool addToBuffers, const uint32_t index_, const float textBoxScale, const float textPadding, @@ -19,11 +18,12 @@ SymbolInstance::SymbolInstance(Anchor& anchor_, const std::array<float, 2> textOffset_, const float iconBoxScale, const float iconPadding, - const SymbolPlacementType iconPlacement, const std::array<float, 2> iconOffset_, const GlyphPositionMap& positions, const IndexedSubfeature& indexedFeature, - const std::size_t featureIndex_) : + const std::size_t featureIndex_, + const std::u16string& key_, + const float overscaling) : anchor(anchor_), line(line_), index(index_), @@ -31,25 +31,22 @@ SymbolInstance::SymbolInstance(Anchor& anchor_, hasIcon(shapedIcon), // Create the collision features that will be used to check whether this symbol instance can be placed - textCollisionFeature(line_, anchor, shapedTextOrientations.second ?: shapedTextOrientations.first, textBoxScale, textPadding, textPlacement, indexedFeature), - iconCollisionFeature(line_, anchor, shapedIcon, iconBoxScale, iconPadding, iconPlacement, indexedFeature), + textCollisionFeature(line_, anchor, shapedTextOrientations.first, textBoxScale, textPadding, textPlacement, indexedFeature, overscaling), + iconCollisionFeature(line_, anchor, shapedIcon, iconBoxScale, iconPadding, indexedFeature), featureIndex(featureIndex_), textOffset(textOffset_), - iconOffset(iconOffset_) { + iconOffset(iconOffset_), + key(key_) { // Create the quads used for rendering the icon and glyphs. - if (addToBuffers) { - if (shapedIcon) { - iconQuad = getIconQuad(*shapedIcon, layout, layoutTextSize, shapedTextOrientations.first); - } - if (shapedTextOrientations.first) { - auto quads = getGlyphQuads(shapedTextOrientations.first, layout, textPlacement, positions); - glyphQuads.insert(glyphQuads.end(), quads.begin(), quads.end()); - } - if (shapedTextOrientations.second) { - auto quads = getGlyphQuads(shapedTextOrientations.second, layout, textPlacement, positions); - glyphQuads.insert(glyphQuads.end(), quads.begin(), quads.end()); - } + if (shapedIcon) { + iconQuad = getIconQuad(*shapedIcon, layout, layoutTextSize, shapedTextOrientations.first); + } + if (shapedTextOrientations.first) { + horizontalGlyphQuads = getGlyphQuads(shapedTextOrientations.first, layout, textPlacement, positions); + } + if (shapedTextOrientations.second) { + verticalGlyphQuads = getGlyphQuads(shapedTextOrientations.second, layout, textPlacement, positions); } if (shapedTextOrientations.first && shapedTextOrientations.second) { diff --git a/src/mbgl/layout/symbol_instance.hpp b/src/mbgl/layout/symbol_instance.hpp index f1df416cd1..827a5dbbdb 100644 --- a/src/mbgl/layout/symbol_instance.hpp +++ b/src/mbgl/layout/symbol_instance.hpp @@ -5,6 +5,7 @@ #include <mbgl/text/collision_feature.hpp> #include <mbgl/style/layers/symbol_layer_properties.hpp> + namespace mbgl { class Anchor; @@ -18,7 +19,6 @@ public: optional<PositionedIcon> shapedIcon, const style::SymbolLayoutProperties::Evaluated&, const float layoutTextSize, - const bool inside, const uint32_t index, const float textBoxScale, const float textPadding, @@ -26,18 +26,20 @@ public: const std::array<float, 2> textOffset, const float iconBoxScale, const float iconPadding, - style::SymbolPlacementType iconPlacement, const std::array<float, 2> iconOffset, const GlyphPositionMap&, const IndexedSubfeature&, - const std::size_t featureIndex); + const std::size_t featureIndex, + const std::u16string& key, + const float overscaling); Anchor anchor; GeometryCoordinates line; uint32_t index; bool hasText; bool hasIcon; - SymbolQuads glyphQuads; + SymbolQuads horizontalGlyphQuads; + SymbolQuads verticalGlyphQuads; optional<SymbolQuad> iconQuad; CollisionFeature textCollisionFeature; CollisionFeature iconCollisionFeature; @@ -45,6 +47,12 @@ public: std::size_t featureIndex; std::array<float, 2> textOffset; std::array<float, 2> iconOffset; + std::u16string key; + bool isDuplicate; + optional<size_t> placedTextIndex; + optional<size_t> placedVerticalTextIndex; + optional<size_t> placedIconIndex; + uint32_t crossTileID = 0; }; } // namespace mbgl diff --git a/src/mbgl/layout/symbol_layout.cpp b/src/mbgl/layout/symbol_layout.cpp index 2c90b69b08..a41a98fcaf 100644 --- a/src/mbgl/layout/symbol_layout.cpp +++ b/src/mbgl/layout/symbol_layout.cpp @@ -8,7 +8,6 @@ #include <mbgl/renderer/image_atlas.hpp> #include <mbgl/style/layers/symbol_layer_impl.hpp> #include <mbgl/text/get_anchors.hpp> -#include <mbgl/text/collision_tile.hpp> #include <mbgl/text/shaping.hpp> #include <mbgl/util/constants.hpp> #include <mbgl/util/utf.hpp> @@ -33,7 +32,7 @@ using namespace style; template <class Property> static bool has(const style::SymbolLayoutProperties::PossiblyEvaluated& layout) { return layout.get<Property>().match( - [&] (const std::string& s) { return !s.empty(); }, + [&] (const typename Property::Type& t) { return !t.empty(); }, [&] (const auto&) { return true; } ); } @@ -43,8 +42,8 @@ SymbolLayout::SymbolLayout(const BucketParameters& parameters, std::unique_ptr<GeometryTileLayer> sourceLayer_, ImageDependencies& imageDependencies, GlyphDependencies& glyphDependencies) - : sourceLayer(std::move(sourceLayer_)), - bucketName(layers.at(0)->getID()), + : bucketName(layers.at(0)->getID()), + sourceLayer(std::move(sourceLayer_)), overscaling(parameters.tileID.overscaleFactor()), zoom(parameters.tileID.overscaledZ), mode(parameters.mode), @@ -83,7 +82,7 @@ SymbolLayout::SymbolLayout(const BucketParameters& parameters, layout.get<IconPitchAlignment>() = layout.get<IconRotationAlignment>(); } - const bool hasText = has<TextField>(layout) && !layout.get<TextFont>().empty(); + const bool hasText = has<TextField>(layout) && has<TextFont>(layout); const bool hasIcon = has<IconImage>(layout); if (!hasText && !hasIcon) { @@ -144,12 +143,15 @@ SymbolLayout::SymbolLayout(const BucketParameters& parameters, && layout.get<SymbolPlacement>() == SymbolPlacementType::Line && util::i18n::allowsVerticalWritingMode(*ft.text); + FontStack fontStack = layout.evaluate<TextFont>(zoom, ft); + GlyphIDs& dependencies = glyphDependencies[fontStack]; + // Loop through all characters of this text and collect unique codepoints. for (char16_t chr : *ft.text) { - glyphDependencies[layout.get<TextFont>()].insert(chr); + dependencies.insert(chr); if (canVerticalizeText) { if (char16_t verticalChr = util::i18n::verticalizePunctuation(chr)) { - glyphDependencies[layout.get<TextFont>()].insert(verticalChr); + dependencies.insert(verticalChr); } } } @@ -179,22 +181,25 @@ bool SymbolLayout::hasSymbolInstances() const { } void SymbolLayout::prepare(const GlyphMap& glyphMap, const GlyphPositions& glyphPositions, - const ImageMap& imageMap, const ImagePositions& imagePositions) { + const ImageMap& imageMap, const ImagePositions& imagePositions, + const OverscaledTileID& tileID, const std::string& sourceID) { const bool textAlongLine = layout.get<TextRotationAlignment>() == AlignmentType::Map && layout.get<SymbolPlacement>() == SymbolPlacementType::Line; - auto glyphMapIt = glyphMap.find(layout.get<TextFont>()); - const Glyphs& glyphs = glyphMapIt != glyphMap.end() - ? glyphMapIt->second : Glyphs(); - - auto glyphPositionsIt = glyphPositions.find(layout.get<TextFont>()); - const GlyphPositionMap& glyphPositionMap = glyphPositionsIt != glyphPositions.end() - ? glyphPositionsIt->second : GlyphPositionMap(); - for (auto it = features.begin(); it != features.end(); ++it) { auto& feature = *it; if (feature.geometry.empty()) continue; + FontStack fontStack = layout.evaluate<TextFont>(zoom, feature); + + auto glyphMapIt = glyphMap.find(fontStack); + const Glyphs& glyphs = glyphMapIt != glyphMap.end() + ? glyphMapIt->second : Glyphs(); + + auto glyphPositionsIt = glyphPositions.find(fontStack); + const GlyphPositionMap& glyphPositionMap = glyphPositionsIt != glyphPositions.end() + ? glyphPositionsIt->second : GlyphPositionMap(); + std::pair<Shaping, Shaping> shapedTextOrientations; optional<PositionedIcon> shapedIcon; @@ -248,7 +253,7 @@ void SymbolLayout::prepare(const GlyphMap& glyphMap, const GlyphPositions& glyph // if either shapedText or icon position is present, add the feature if (shapedTextOrientations.first || shapedIcon) { - addFeature(std::distance(features.begin(), it), feature, shapedTextOrientations, shapedIcon, glyphPositionMap); + addFeature(std::distance(features.begin(), it), feature, shapedTextOrientations, shapedIcon, glyphPositionMap, tileID, sourceID); } feature.geometry.clear(); @@ -261,7 +266,9 @@ void SymbolLayout::addFeature(const std::size_t index, const SymbolFeature& feature, const std::pair<Shaping, Shaping>& shapedTextOrientations, optional<PositionedIcon> shapedIcon, - const GlyphPositionMap& glyphPositionMap) { + const GlyphPositionMap& glyphPositionMap, + const OverscaledTileID& tileID, + const std::string& sourceID) { const float minScale = 0.5f; const float glyphSize = 24.0f; @@ -288,12 +295,10 @@ void SymbolLayout::addFeature(const std::size_t index, const SymbolPlacementType textPlacement = layout.get<TextRotationAlignment>() != AlignmentType::Map ? SymbolPlacementType::Point : layout.get<SymbolPlacement>(); - const SymbolPlacementType iconPlacement = layout.get<IconRotationAlignment>() != AlignmentType::Map - ? SymbolPlacementType::Point - : layout.get<SymbolPlacement>(); + const float textRepeatDistance = symbolSpacing / 2; - IndexedSubfeature indexedFeature = { feature.index, sourceLayer->getName(), bucketName, - symbolInstances.size() }; + IndexedSubfeature indexedFeature(feature.index, sourceLayer->getName(), bucketName, symbolInstances.size(), + sourceID, tileID.canonical); auto addSymbolInstance = [&] (const GeometryCoordinates& line, Anchor& anchor) { // https://github.com/mapbox/vector-tile-spec/tree/master/2.1#41-layers @@ -314,14 +319,14 @@ void SymbolLayout::addFeature(const std::size_t index, if (avoidEdges && !inside) return; - const bool addToBuffers = mode == MapMode::Still || withinPlus0; - - symbolInstances.emplace_back(anchor, line, shapedTextOrientations, shapedIcon, - layout.evaluate(zoom, feature), layoutTextSize, - addToBuffers, symbolInstances.size(), - textBoxScale, textPadding, textPlacement, textOffset, - iconBoxScale, iconPadding, iconPlacement, iconOffset, - glyphPositionMap, indexedFeature, index); + if (mode == MapMode::Tile || withinPlus0) { + symbolInstances.emplace_back(anchor, line, shapedTextOrientations, shapedIcon, + layout.evaluate(zoom, feature), layoutTextSize, + symbolInstances.size(), + textBoxScale, textPadding, textPlacement, textOffset, + iconBoxScale, iconPadding, iconOffset, + glyphPositionMap, indexedFeature, index, feature.text.value_or(std::u16string()), overscaling); + } }; const auto& type = feature.getType(); @@ -392,108 +397,93 @@ bool SymbolLayout::anchorIsTooClose(const std::u16string& text, const float repe return false; } -std::unique_ptr<SymbolBucket> SymbolLayout::place(CollisionTile& collisionTile) { - auto bucket = std::make_unique<SymbolBucket>(layout, layerPaintProperties, textSize, iconSize, zoom, sdfIcons, iconsNeedLinear); - - // Calculate which labels can be shown and when they can be shown and - // create the bufers used for rendering. - - const SymbolPlacementType textPlacement = layout.get<TextRotationAlignment>() != AlignmentType::Map - ? SymbolPlacementType::Point - : layout.get<SymbolPlacement>(); - const SymbolPlacementType iconPlacement = layout.get<IconRotationAlignment>() != AlignmentType::Map - ? SymbolPlacementType::Point - : layout.get<SymbolPlacement>(); +// Analog of `addToLineVertexArray` in JS. This version doesn't need to build up a line array like the +// JS version does, but it uses the same logic to calculate tile distances. +std::vector<float> CalculateTileDistances(const GeometryCoordinates& line, const Anchor& anchor) { + std::vector<float> tileDistances(line.size()); + if (anchor.segment != -1) { + auto sumForwardLength = util::dist<float>(anchor.point, line[anchor.segment + 1]); + auto sumBackwardLength = util::dist<float>(anchor.point, line[anchor.segment]); + for (size_t i = anchor.segment + 1; i < line.size(); i++) { + tileDistances[i] = sumForwardLength; + if (i < line.size() - 1) { + sumForwardLength += util::dist<float>(line[i + 1], line[i]); + } + } + for (auto i = anchor.segment; i >= 0; i--) { + tileDistances[i] = sumBackwardLength; + if (i > 0) { + sumBackwardLength += util::dist<float>(line[i - 1], line[i]); + } + } + } + return tileDistances; +} +std::unique_ptr<SymbolBucket> SymbolLayout::place(const bool showCollisionBoxes) { const bool mayOverlap = layout.get<TextAllowOverlap>() || layout.get<IconAllowOverlap>() || layout.get<TextIgnorePlacement>() || layout.get<IconIgnorePlacement>(); + + auto bucket = std::make_unique<SymbolBucket>(layout, layerPaintProperties, textSize, iconSize, zoom, sdfIcons, iconsNeedLinear, mayOverlap, std::move(symbolInstances)); - const bool keepUpright = layout.get<TextKeepUpright>(); - - // Sort symbols by their y position on the canvas so that they lower symbols - // are drawn on top of higher symbols. - // Don't sort symbols that won't overlap because it isn't necessary and - // because it causes more labels to pop in and out when rotating. - if (mayOverlap) { - const float sin = std::sin(collisionTile.config.angle); - const float cos = std::cos(collisionTile.config.angle); - - std::sort(symbolInstances.begin(), symbolInstances.end(), [sin, cos](SymbolInstance &a, SymbolInstance &b) { - const int32_t aRotated = sin * a.anchor.point.x + cos * a.anchor.point.y; - const int32_t bRotated = sin * b.anchor.point.x + cos * b.anchor.point.y; - return aRotated != bRotated ? - aRotated < bRotated : - a.index > b.index; - }); - } - - for (SymbolInstance &symbolInstance : symbolInstances) { + for (SymbolInstance &symbolInstance : bucket->symbolInstances) { const bool hasText = symbolInstance.hasText; const bool hasIcon = symbolInstance.hasIcon; - const bool iconWithoutText = layout.get<TextOptional>() || !hasText; - const bool textWithoutIcon = layout.get<IconOptional>() || !hasIcon; - - // Calculate the scales at which the text and icon can be placed without collision. - - float glyphScale = hasText ? - collisionTile.placeFeature(symbolInstance.textCollisionFeature, - layout.get<TextAllowOverlap>(), layout.get<SymbolAvoidEdges>()) : - collisionTile.minScale; - float iconScale = hasIcon ? - collisionTile.placeFeature(symbolInstance.iconCollisionFeature, - layout.get<IconAllowOverlap>(), layout.get<SymbolAvoidEdges>()) : - collisionTile.minScale; - - - // Combine the scales for icons and text. - - if (!iconWithoutText && !textWithoutIcon) { - iconScale = glyphScale = util::max(iconScale, glyphScale); - } else if (!textWithoutIcon && glyphScale) { - glyphScale = util::max(iconScale, glyphScale); - } else if (!iconWithoutText && iconScale) { - iconScale = util::max(iconScale, glyphScale); - } - const auto& feature = features.at(symbolInstance.featureIndex); // Insert final placement into collision tree and add glyphs/icons to buffers if (hasText) { - const float placementZoom = util::max(util::log2(glyphScale) + zoom, 0.0f); - collisionTile.insertFeature(symbolInstance.textCollisionFeature, glyphScale, layout.get<TextIgnorePlacement>()); - if (glyphScale < collisionTile.maxScale) { - - const float labelAngle = std::fmod((symbolInstance.anchor.angle + collisionTile.config.angle) + 2 * M_PI, 2 * M_PI); - const bool inVerticalRange = ( - (labelAngle > M_PI * 1.0 / 4.0 && labelAngle <= M_PI * 3.0 / 4) || - (labelAngle > M_PI * 5.0 / 4.0 && labelAngle <= M_PI * 7.0 / 4)); - const bool useVerticalMode = symbolInstance.writingModes & WritingModeType::Vertical && inVerticalRange; - - const Range<float> sizeData = bucket->textSizeBinder->getVertexSizeData(feature); + const Range<float> sizeData = bucket->textSizeBinder->getVertexSizeData(feature); + bucket->text.placedSymbols.emplace_back(symbolInstance.anchor.point, symbolInstance.anchor.segment, sizeData.min, sizeData.max, + symbolInstance.textOffset, symbolInstance.writingModes, symbolInstance.line, CalculateTileDistances(symbolInstance.line, symbolInstance.anchor)); + symbolInstance.placedTextIndex = bucket->text.placedSymbols.size() - 1; + PlacedSymbol& horizontalSymbol = bucket->text.placedSymbols.back(); + + bool firstHorizontal = true; + for (const auto& symbol : symbolInstance.horizontalGlyphQuads) { + size_t index = addSymbol( + bucket->text, sizeData, symbol, + symbolInstance.anchor, horizontalSymbol); + if (firstHorizontal) { + horizontalSymbol.vertexStartIndex = index; + firstHorizontal = false; + } + } + + if (symbolInstance.writingModes & WritingModeType::Vertical) { bucket->text.placedSymbols.emplace_back(symbolInstance.anchor.point, symbolInstance.anchor.segment, sizeData.min, sizeData.max, - symbolInstance.textOffset, placementZoom, useVerticalMode, symbolInstance.line); - - for (const auto& symbol : symbolInstance.glyphQuads) { - addSymbol( - bucket->text, sizeData, symbol, placementZoom, - keepUpright, textPlacement, symbolInstance.anchor, bucket->text.placedSymbols.back()); + symbolInstance.textOffset, WritingModeType::Vertical, symbolInstance.line, CalculateTileDistances(symbolInstance.line, symbolInstance.anchor)); + symbolInstance.placedVerticalTextIndex = bucket->text.placedSymbols.size() - 1; + + PlacedSymbol& verticalSymbol = bucket->text.placedSymbols.back(); + bool firstVertical = true; + + for (const auto& symbol : symbolInstance.verticalGlyphQuads) { + size_t index = addSymbol( + bucket->text, sizeData, symbol, + symbolInstance.anchor, verticalSymbol); + + if (firstVertical) { + verticalSymbol.vertexStartIndex = index; + firstVertical = false; + } } } } if (hasIcon) { - const float placementZoom = util::max(util::log2(iconScale) + zoom, 0.0f); - collisionTile.insertFeature(symbolInstance.iconCollisionFeature, iconScale, layout.get<IconIgnorePlacement>()); - if (iconScale < collisionTile.maxScale && symbolInstance.iconQuad) { + if (symbolInstance.iconQuad) { const Range<float> sizeData = bucket->iconSizeBinder->getVertexSizeData(feature); bucket->icon.placedSymbols.emplace_back(symbolInstance.anchor.point, symbolInstance.anchor.segment, sizeData.min, sizeData.max, - symbolInstance.iconOffset, placementZoom, false, symbolInstance.line); - addSymbol( - bucket->icon, sizeData, *symbolInstance.iconQuad, placementZoom, - keepUpright, iconPlacement, symbolInstance.anchor, bucket->icon.placedSymbols.back()); + symbolInstance.iconOffset, WritingModeType::None, symbolInstance.line, std::vector<float>()); + symbolInstance.placedIconIndex = bucket->icon.placedSymbols.size() - 1; + PlacedSymbol& iconSymbol = bucket->icon.placedSymbols.back(); + iconSymbol.vertexStartIndex = addSymbol( + bucket->icon, sizeData, *symbolInstance.iconQuad, + symbolInstance.anchor, iconSymbol); } } @@ -503,20 +493,17 @@ std::unique_ptr<SymbolBucket> SymbolLayout::place(CollisionTile& collisionTile) } } - if (collisionTile.config.debug) { - addToDebugBuffers(collisionTile, *bucket); + if (showCollisionBoxes) { + addToDebugBuffers(*bucket); } return bucket; } template <typename Buffer> -void SymbolLayout::addSymbol(Buffer& buffer, +size_t SymbolLayout::addSymbol(Buffer& buffer, const Range<float> sizeData, const SymbolQuad& symbol, - const float placementZoom, - const bool keepUpright, - const style::SymbolPlacementType placement, const Anchor& labelAnchor, PlacedSymbol& placedSymbol) { constexpr const uint16_t vertexLength = 4; @@ -527,11 +514,6 @@ void SymbolLayout::addSymbol(Buffer& buffer, const auto &br = symbol.br; const auto &tex = symbol.tex; - if (placement == style::SymbolPlacementType::Line && keepUpright) { - // drop incorrectly oriented glyphs - if ((symbol.writingMode == WritingModeType::Vertical) != placedSymbol.useVerticalMode) return; - } - if (buffer.segments.empty() || buffer.segments.back().vertexLength + vertexLength > std::numeric_limits<uint16_t>::max()) { buffer.segments.emplace_back(buffer.vertices.vertexSize(), buffer.triangles.indexSize()); } @@ -548,11 +530,19 @@ void SymbolLayout::addSymbol(Buffer& buffer, buffer.vertices.emplace_back(SymbolLayoutAttributes::vertex(labelAnchor.point, bl, symbol.glyphOffset.y, tex.x, tex.y + tex.h, sizeData)); buffer.vertices.emplace_back(SymbolLayoutAttributes::vertex(labelAnchor.point, br, symbol.glyphOffset.y, tex.x + tex.w, tex.y + tex.h, sizeData)); - auto dynamicVertex = SymbolDynamicLayoutAttributes::vertex(labelAnchor.point, 0, placementZoom); + // Dynamic/Opacity vertices are initialized so that the vertex count always agrees with + // the layout vertex buffer, but they will always be updated before rendering happens + auto dynamicVertex = SymbolDynamicLayoutAttributes::vertex(labelAnchor.point, 0); buffer.dynamicVertices.emplace_back(dynamicVertex); buffer.dynamicVertices.emplace_back(dynamicVertex); buffer.dynamicVertices.emplace_back(dynamicVertex); buffer.dynamicVertices.emplace_back(dynamicVertex); + + auto opacityVertex = SymbolOpacityAttributes::vertex(1.0, 1.0); + buffer.opacityVertices.emplace_back(opacityVertex); + buffer.opacityVertices.emplace_back(opacityVertex); + buffer.opacityVertices.emplace_back(opacityVertex); + buffer.opacityVertices.emplace_back(opacityVertex); // add the two triangles, referencing the four coordinates we just inserted. buffer.triangles.emplace_back(index + 0, index + 1, index + 2); @@ -562,54 +552,62 @@ void SymbolLayout::addSymbol(Buffer& buffer, segment.indexLength += 6; placedSymbol.glyphOffsets.push_back(symbol.glyphOffset.x); + + return index; } -void SymbolLayout::addToDebugBuffers(CollisionTile& collisionTile, SymbolBucket& bucket) { +void SymbolLayout::addToDebugBuffers(SymbolBucket& bucket) { if (!hasSymbolInstances()) { return; } - const float yStretch = collisionTile.yStretch; - - auto& collisionBox = bucket.collisionBox; - for (const SymbolInstance &symbolInstance : symbolInstances) { auto populateCollisionBox = [&](const auto& feature) { + SymbolBucket::CollisionBuffer& collisionBuffer = feature.alongLine ? + static_cast<SymbolBucket::CollisionBuffer&>(bucket.collisionCircle) : + static_cast<SymbolBucket::CollisionBuffer&>(bucket.collisionBox); for (const CollisionBox &box : feature.boxes) { auto& anchor = box.anchor; - Point<float> tl{box.x1, box.y1 * yStretch}; - Point<float> tr{box.x2, box.y1 * yStretch}; - Point<float> bl{box.x1, box.y2 * yStretch}; - Point<float> br{box.x2, box.y2 * yStretch}; - tl = util::matrixMultiply(collisionTile.reverseRotationMatrix, tl); - tr = util::matrixMultiply(collisionTile.reverseRotationMatrix, tr); - bl = util::matrixMultiply(collisionTile.reverseRotationMatrix, bl); - br = util::matrixMultiply(collisionTile.reverseRotationMatrix, br); - - const float maxZoom = util::clamp(zoom + util::log2(box.maxScale), util::MIN_ZOOM_F, util::MAX_ZOOM_F); - const float placementZoom = util::clamp(zoom + util::log2(box.placementScale), util::MIN_ZOOM_F, util::MAX_ZOOM_F); + Point<float> tl{box.x1, box.y1}; + Point<float> tr{box.x2, box.y1}; + Point<float> bl{box.x1, box.y2}; + Point<float> br{box.x2, box.y2}; static constexpr std::size_t vertexLength = 4; - static constexpr std::size_t indexLength = 8; + const std::size_t indexLength = feature.alongLine ? 6 : 8; - if (collisionBox.segments.empty() || collisionBox.segments.back().vertexLength + vertexLength > std::numeric_limits<uint16_t>::max()) { - collisionBox.segments.emplace_back(collisionBox.vertices.vertexSize(), collisionBox.lines.indexSize()); + if (collisionBuffer.segments.empty() || collisionBuffer.segments.back().vertexLength + vertexLength > std::numeric_limits<uint16_t>::max()) { + collisionBuffer.segments.emplace_back(collisionBuffer.vertices.vertexSize(), + feature.alongLine? bucket.collisionCircle.triangles.indexSize() : bucket.collisionBox.lines.indexSize()); } - auto& segment = collisionBox.segments.back(); + auto& segment = collisionBuffer.segments.back(); uint16_t index = segment.vertexLength; - collisionBox.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.anchor.point, tl, maxZoom, placementZoom)); - collisionBox.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.anchor.point, tr, maxZoom, placementZoom)); - collisionBox.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.anchor.point, br, maxZoom, placementZoom)); - collisionBox.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.anchor.point, bl, maxZoom, placementZoom)); - - collisionBox.lines.emplace_back(index + 0, index + 1); - collisionBox.lines.emplace_back(index + 1, index + 2); - collisionBox.lines.emplace_back(index + 2, index + 3); - collisionBox.lines.emplace_back(index + 3, index + 0); + collisionBuffer.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.anchor.point, tl)); + collisionBuffer.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.anchor.point, tr)); + collisionBuffer.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.anchor.point, br)); + collisionBuffer.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.anchor.point, bl)); + + // Dynamic vertices are initialized so that the vertex count always agrees with + // the layout vertex buffer, but they will always be updated before rendering happens + auto dynamicVertex = CollisionBoxDynamicAttributes::vertex(false, false); + collisionBuffer.dynamicVertices.emplace_back(dynamicVertex); + collisionBuffer.dynamicVertices.emplace_back(dynamicVertex); + collisionBuffer.dynamicVertices.emplace_back(dynamicVertex); + collisionBuffer.dynamicVertices.emplace_back(dynamicVertex); + + if (feature.alongLine) { + bucket.collisionCircle.triangles.emplace_back(index, index + 1, index + 2); + bucket.collisionCircle.triangles.emplace_back(index, index + 2, index + 3); + } else { + bucket.collisionBox.lines.emplace_back(index + 0, index + 1); + bucket.collisionBox.lines.emplace_back(index + 1, index + 2); + bucket.collisionBox.lines.emplace_back(index + 2, index + 3); + bucket.collisionBox.lines.emplace_back(index + 3, index + 0); + } segment.vertexLength += vertexLength; segment.indexLength += indexLength; diff --git a/src/mbgl/layout/symbol_layout.hpp b/src/mbgl/layout/symbol_layout.hpp index 90f5b3c91d..6951c29ada 100644 --- a/src/mbgl/layout/symbol_layout.hpp +++ b/src/mbgl/layout/symbol_layout.hpp @@ -16,7 +16,6 @@ namespace mbgl { class BucketParameters; -class CollisionTile; class SymbolBucket; class Anchor; class RenderLayer; @@ -35,42 +34,44 @@ public: GlyphDependencies&); void prepare(const GlyphMap&, const GlyphPositions&, - const ImageMap&, const ImagePositions&); + const ImageMap&, const ImagePositions&, + const OverscaledTileID&, const std::string&); - std::unique_ptr<SymbolBucket> place(CollisionTile&); + std::unique_ptr<SymbolBucket> place(const bool showCollisionBoxes); bool hasSymbolInstances() const; std::map<std::string, std::pair<style::IconPaintProperties::PossiblyEvaluated, style::TextPaintProperties::PossiblyEvaluated>> layerPaintProperties; + const std::string bucketName; + std::vector<SymbolInstance> symbolInstances; + private: void addFeature(const size_t, const SymbolFeature&, const std::pair<Shaping, Shaping>& shapedTextOrientations, optional<PositionedIcon> shapedIcon, - const GlyphPositionMap&); + const GlyphPositionMap&, + const OverscaledTileID&, + const std::string&); bool anchorIsTooClose(const std::u16string& text, const float repeatDistance, const Anchor&); std::map<std::u16string, std::vector<Anchor>> compareText; - void addToDebugBuffers(CollisionTile&, SymbolBucket&); + void addToDebugBuffers(SymbolBucket&); // Adds placed items to the buffer. template <typename Buffer> - void addSymbol(Buffer&, + size_t addSymbol(Buffer&, const Range<float> sizeData, const SymbolQuad&, - float scale, - const bool keepUpright, - const style::SymbolPlacementType, const Anchor& labelAnchor, PlacedSymbol& placedSymbol); // Stores the layer so that we can hold on to GeometryTileFeature instances in SymbolFeature, // which may reference data from this object. const std::unique_ptr<GeometryTileLayer> sourceLayer; - const std::string bucketName; const float overscaling; const float zoom; const MapMode mode; @@ -87,7 +88,6 @@ private: style::TextSize::UnevaluatedType textSize; style::IconSize::UnevaluatedType iconSize; - std::vector<SymbolInstance> symbolInstances; std::vector<SymbolFeature> features; BiDi bidi; // Consider moving this up to geometry tile worker to reduce reinstantiation costs; use of BiDi/ubiditransform object must be constrained to one thread diff --git a/src/mbgl/layout/symbol_projection.cpp b/src/mbgl/layout/symbol_projection.cpp index d97bfb1ac0..9e077e2532 100644 --- a/src/mbgl/layout/symbol_projection.cpp +++ b/src/mbgl/layout/symbol_projection.cpp @@ -3,7 +3,6 @@ #include <mbgl/renderer/render_tile.hpp> #include <mbgl/renderer/buckets/symbol_bucket.hpp> #include <mbgl/renderer/layers/render_symbol_layer.hpp> -#include <mbgl/renderer/frame_history.hpp> #include <mbgl/util/optional.hpp> #include <mbgl/util/math.hpp> @@ -93,9 +92,6 @@ namespace mbgl { return m; } - - typedef std::pair<Point<float>,float> PointAndCameraDistance; - PointAndCameraDistance project(const Point<float>& point, const mat4& matrix) { vec4 pos = {{ point.x, point.y, 0, 1 }}; matrix::transformMat4(pos, pos, matrix); @@ -114,7 +110,7 @@ namespace mbgl { } } - bool isVisible(const vec4& anchorPos, const float placementZoom, const std::array<double, 2>& clippingBuffer, const FrameHistory& frameHistory) { + bool isVisible(const vec4& anchorPos, const std::array<double, 2>& clippingBuffer) { const float x = anchorPos[0] / anchorPos[3]; const float y = anchorPos[1] / anchorPos[3]; const bool inPaddedViewport = ( @@ -122,12 +118,12 @@ namespace mbgl { x <= clippingBuffer[0] && y >= -clippingBuffer[1] && y <= clippingBuffer[1]); - return inPaddedViewport && frameHistory.isVisible(placementZoom); + return inPaddedViewport; } - void addDynamicAttributes(const Point<float>& anchorPoint, const float angle, const float placementZoom, + void addDynamicAttributes(const Point<float>& anchorPoint, const float angle, gl::VertexVector<SymbolDynamicLayoutAttributes::Vertex>& dynamicVertexArray) { - auto dynamicVertex = SymbolDynamicLayoutAttributes::vertex(anchorPoint, angle, placementZoom); + auto dynamicVertex = SymbolDynamicLayoutAttributes::vertex(anchorPoint, angle); dynamicVertexArray.emplace_back(dynamicVertex); dynamicVertexArray.emplace_back(dynamicVertex); dynamicVertexArray.emplace_back(dynamicVertex); @@ -137,20 +133,15 @@ namespace mbgl { void hideGlyphs(size_t numGlyphs, gl::VertexVector<SymbolDynamicLayoutAttributes::Vertex>& dynamicVertexArray) { const Point<float> offscreenPoint = { -INFINITY, -INFINITY }; for (size_t i = 0; i < numGlyphs; i++) { - addDynamicAttributes(offscreenPoint, 0, 25, dynamicVertexArray); + addDynamicAttributes(offscreenPoint, 0, dynamicVertexArray); } } - struct PlacedGlyph { - PlacedGlyph(Point<float> point_, float angle_) : point(point_), angle(angle_) {} - Point<float> point; - float angle; - }; - enum PlacementResult { OK, NotEnoughRoom, - NeedsFlipping + NeedsFlipping, + UseVertical }; Point<float> projectTruncatedLineSegment(const Point<float>& previousTilePoint, const Point<float>& currentTilePoint, const Point<float>& previousProjectedPoint, const float minimumLength, const mat4& projectionMatrix) { @@ -165,7 +156,7 @@ namespace mbgl { } optional<PlacedGlyph> placeGlyphAlongLine(const float offsetX, const float lineOffsetX, const float lineOffsetY, const bool flip, - const Point<float>& projectedAnchorPoint, const Point<float>& tileAnchorPoint, const uint16_t anchorSegment, const GeometryCoordinates& line, const mat4& labelPlaneMatrix) { + const Point<float>& projectedAnchorPoint, const Point<float>& tileAnchorPoint, const uint16_t anchorSegment, const GeometryCoordinates& line, const std::vector<float>& tileDistances, const mat4& labelPlaneMatrix, const bool returnTileDistance) { const float combinedOffsetX = flip ? offsetX - lineOffsetX : @@ -185,6 +176,7 @@ namespace mbgl { int32_t currentIndex = dir > 0 ? anchorSegment : anchorSegment + 1; + const int32_t initialIndex = currentIndex; Point<float> current = projectedAnchorPoint; Point<float> prev = projectedAnchorPoint; float distanceToPrev = 0.0; @@ -195,7 +187,9 @@ namespace mbgl { currentIndex += dir; // offset does not fit on the projected line - if (currentIndex < 0 || currentIndex >= static_cast<int32_t>(line.size())) return {}; + if (currentIndex < 0 || currentIndex >= static_cast<int32_t>(line.size())) { + return {}; + } prev = current; PointAndCameraDistance projection = project(convertPoint<float>(line.at(currentIndex)), labelPlaneMatrix); @@ -225,7 +219,62 @@ namespace mbgl { const float segmentAngle = angle + std::atan2(current.y - prev.y, current.x - prev.x); - return {{ p, segmentAngle }}; + return {{ + p, + segmentAngle, + returnTileDistance ? + TileDistance( + (currentIndex - dir) == initialIndex ? 0 : tileDistances[currentIndex - dir], + absOffsetX - distanceToPrev + ) : + optional<TileDistance>() + }}; + } + + optional<std::pair<PlacedGlyph, PlacedGlyph>> placeFirstAndLastGlyph(const float fontScale, + const float lineOffsetX, + const float lineOffsetY, + const bool flip, + const Point<float>& anchorPoint, + const Point<float>& tileAnchorPoint, + const PlacedSymbol& symbol, + const mat4& labelPlaneMatrix, + const bool returnTileDistance) { + + const float firstGlyphOffset = symbol.glyphOffsets.front(); + const float lastGlyphOffset = symbol.glyphOffsets.back();; + + optional<PlacedGlyph> firstPlacedGlyph = placeGlyphAlongLine(fontScale * firstGlyphOffset, lineOffsetX, lineOffsetY, flip, anchorPoint, tileAnchorPoint, symbol.segment, symbol.line, symbol.tileDistances, labelPlaneMatrix, returnTileDistance); + if (!firstPlacedGlyph) + return optional<std::pair<PlacedGlyph, PlacedGlyph>>(); + + optional<PlacedGlyph> lastPlacedGlyph = placeGlyphAlongLine(fontScale * lastGlyphOffset, lineOffsetX, lineOffsetY, flip, anchorPoint, tileAnchorPoint, symbol.segment, symbol.line, symbol.tileDistances, labelPlaneMatrix, returnTileDistance); + if (!lastPlacedGlyph) + return optional<std::pair<PlacedGlyph, PlacedGlyph>>(); + + return std::make_pair(*firstPlacedGlyph, *lastPlacedGlyph); + } + + optional<PlacementResult> requiresOrientationChange(const WritingModeType writingModes, const Point<float>& firstPoint, const Point<float>& lastPoint, const float aspectRatio) { + if (writingModes == (WritingModeType::Horizontal | WritingModeType::Vertical)) { + // On top of choosing whether to flip, choose whether to render this version of the glyphs or the alternate + // vertical glyphs. We can't just filter out vertical glyphs in the horizontal range because the horizontal + // and vertical versions can have slightly different projections which could lead to angles where both or + // neither showed. + auto rise = std::abs(lastPoint.y - firstPoint.y); + auto run = std::abs(lastPoint.x - firstPoint.x) * aspectRatio; + if (rise > run) { + return PlacementResult::UseVertical; + } + } + + if ((writingModes == WritingModeType::Vertical) ? + (firstPoint.y < lastPoint.y) : + (firstPoint.x > lastPoint.x)) { + // Includes "horizontalOnly" case for labels without vertical glyphs + return PlacementResult::NeedsFlipping; + } + return {}; } PlacementResult placeGlyphsAlongLine(const PlacedSymbol& symbol, @@ -236,7 +285,8 @@ namespace mbgl { const mat4& labelPlaneMatrix, const mat4& glCoordMatrix, gl::VertexVector<SymbolDynamicLayoutAttributes::Vertex>& dynamicVertexArray, - const Point<float>& projectedAnchorPoint) { + const Point<float>& projectedAnchorPoint, + const float aspectRatio) { const float fontScale = fontSize / 24.0; const float lineOffsetX = symbol.lineOffset[0] * fontSize; const float lineOffsetY = symbol.lineOffset[1] * fontSize; @@ -244,33 +294,30 @@ namespace mbgl { std::vector<PlacedGlyph> placedGlyphs; if (symbol.glyphOffsets.size() > 1) { - const float firstGlyphOffset = symbol.glyphOffsets.front(); - const float lastGlyphOffset = symbol.glyphOffsets.back(); - - optional<PlacedGlyph> firstPlacedGlyph = placeGlyphAlongLine(fontScale * firstGlyphOffset, lineOffsetX, lineOffsetY, flip, projectedAnchorPoint, symbol.anchorPoint, symbol.segment, symbol.line, labelPlaneMatrix); - if (!firstPlacedGlyph) - return PlacementResult::NotEnoughRoom; - - optional<PlacedGlyph> lastPlacedGlyph = placeGlyphAlongLine(fontScale * lastGlyphOffset, lineOffsetX, lineOffsetY, flip, projectedAnchorPoint, symbol.anchorPoint, symbol.segment, symbol.line, labelPlaneMatrix); - if (!lastPlacedGlyph) + const optional<std::pair<PlacedGlyph, PlacedGlyph>> firstAndLastGlyph = + placeFirstAndLastGlyph(fontScale, lineOffsetX, lineOffsetY, flip, projectedAnchorPoint, symbol.anchorPoint, symbol, labelPlaneMatrix, false); + if (!firstAndLastGlyph) { return PlacementResult::NotEnoughRoom; + } - const Point<float> firstPoint = project(firstPlacedGlyph->point, glCoordMatrix).first; - const Point<float> lastPoint = project(lastPlacedGlyph->point, glCoordMatrix).first; + const Point<float> firstPoint = project(firstAndLastGlyph->first.point, glCoordMatrix).first; + const Point<float> lastPoint = project(firstAndLastGlyph->second.point, glCoordMatrix).first; - if (keepUpright && !flip && - (symbol.useVerticalMode ? firstPoint.y < lastPoint.y : firstPoint.x > lastPoint.x)) { - return PlacementResult::NeedsFlipping; + if (keepUpright && !flip) { + auto orientationChange = requiresOrientationChange(symbol.writingModes, firstPoint, lastPoint, aspectRatio); + if (orientationChange) { + return *orientationChange; + } } - placedGlyphs.push_back(*firstPlacedGlyph); + placedGlyphs.push_back(firstAndLastGlyph->first); for (size_t glyphIndex = 1; glyphIndex < symbol.glyphOffsets.size() - 1; glyphIndex++) { const float glyphOffsetX = symbol.glyphOffsets[glyphIndex]; // Since first and last glyph fit on the line, we're sure that the rest of the glyphs can be placed - auto placedGlyph = placeGlyphAlongLine(glyphOffsetX * fontScale, lineOffsetX, lineOffsetY, flip, projectedAnchorPoint, symbol.anchorPoint, symbol.segment, symbol.line, labelPlaneMatrix); + auto placedGlyph = placeGlyphAlongLine(glyphOffsetX * fontScale, lineOffsetX, lineOffsetY, flip, projectedAnchorPoint, symbol.anchorPoint, symbol.segment, symbol.line, symbol.tileDistances, labelPlaneMatrix, false); placedGlyphs.push_back(*placedGlyph); } - placedGlyphs.push_back(*lastPlacedGlyph); + placedGlyphs.push_back(firstAndLastGlyph->second); } else if (symbol.glyphOffsets.size() == 1) { // Only a single glyph to place // So, determine whether to flip based on projected angle of the line segment it's on @@ -285,13 +332,14 @@ namespace mbgl { projectedVertex.first : projectTruncatedLineSegment(symbol.anchorPoint,tileSegmentEnd, a, 1, posMatrix); - if (symbol.useVerticalMode ? b.y > a.y : b.x < a.x) { - return PlacementResult::NeedsFlipping; + auto orientationChange = requiresOrientationChange(symbol.writingModes, a, b, aspectRatio); + if (orientationChange) { + return *orientationChange; } } const float glyphOffsetX = symbol.glyphOffsets.front(); optional<PlacedGlyph> singleGlyph = placeGlyphAlongLine(fontScale * glyphOffsetX, lineOffsetX, lineOffsetY, flip, projectedAnchorPoint, symbol.anchorPoint, symbol.segment, - symbol.line, labelPlaneMatrix); + symbol.line, symbol.tileDistances, labelPlaneMatrix, false); if (!singleGlyph) return PlacementResult::NotEnoughRoom; @@ -301,7 +349,7 @@ namespace mbgl { // The number of placedGlyphs must equal the number of glyphOffsets, which must correspond to the number of glyph vertices // There may be 0 glyphs here, if a label consists entirely of glyphs that have 0x0 dimensions for (auto& placedGlyph : placedGlyphs) { - addDynamicAttributes(placedGlyph.point, placedGlyph.angle, symbol.placementZoom, dynamicVertexArray); + addDynamicAttributes(placedGlyph.point, placedGlyph.angle, dynamicVertexArray); } return PlacementResult::OK; @@ -310,7 +358,7 @@ namespace mbgl { void reprojectLineLabels(gl::VertexVector<SymbolDynamicLayoutAttributes::Vertex>& dynamicVertexArray, const std::vector<PlacedSymbol>& placedSymbols, const mat4& posMatrix, const style::SymbolPropertyValues& values, - const RenderTile& tile, const SymbolSizeBinder& sizeBinder, const TransformState& state, const FrameHistory& frameHistory) { + const RenderTile& tile, const SymbolSizeBinder& sizeBinder, const TransformState& state) { const ZoomEvaluatedSize partiallyEvaluatedSize = sizeBinder.evaluateForZoom(state.getZoom()); @@ -326,19 +374,31 @@ namespace mbgl { const mat4 glCoordMatrix = getGlCoordMatrix(posMatrix, pitchWithMap, rotateWithMap, state, pixelsToTileUnits); dynamicVertexArray.clear(); + + bool useVertical = false; for (auto& placedSymbol : placedSymbols) { + // Don't do calculations for vertical glyphs unless the previous symbol was horizontal + // and we determined that vertical glyphs were necessary. + // Also don't do calculations for symbols that are collided and fully faded out + if (placedSymbol.hidden || (placedSymbol.writingModes == WritingModeType::Vertical && !useVertical)) { + hideGlyphs(placedSymbol.glyphOffsets.size(), dynamicVertexArray); + continue; + } + // Awkward... but we're counting on the paired "vertical" symbol coming immediately after its horizontal counterpart + useVertical = false; + vec4 anchorPos = {{ placedSymbol.anchorPoint.x, placedSymbol.anchorPoint.y, 0, 1 }}; matrix::transformMat4(anchorPos, anchorPos, posMatrix); // Don't bother calculating the correct point for invisible labels. - if (!isVisible(anchorPos, placedSymbol.placementZoom, clippingBuffer, frameHistory)) { + if (!isVisible(anchorPos, clippingBuffer)) { hideGlyphs(placedSymbol.glyphOffsets.size(), dynamicVertexArray); continue; } const float cameraToAnchorDistance = anchorPos[3]; - const float perspectiveRatio = 1 + 0.5 * ((cameraToAnchorDistance / state.getCameraToCenterDistance()) - 1.0); + const float perspectiveRatio = 0.5 + 0.5 * (cameraToAnchorDistance / state.getCameraToCenterDistance()); const float fontSize = evaluateSizeForFeature(partiallyEvaluatedSize, placedSymbol); const float pitchScaledFontSize = values.pitchAlignment == style::AlignmentType::Map ? @@ -347,11 +407,13 @@ namespace mbgl { const Point<float> anchorPoint = project(placedSymbol.anchorPoint, labelPlaneMatrix).first; - PlacementResult placeUnflipped = placeGlyphsAlongLine(placedSymbol, pitchScaledFontSize, false /*unflipped*/, values.keepUpright, posMatrix, labelPlaneMatrix, glCoordMatrix, dynamicVertexArray, anchorPoint); + PlacementResult placeUnflipped = placeGlyphsAlongLine(placedSymbol, pitchScaledFontSize, false /*unflipped*/, values.keepUpright, posMatrix, labelPlaneMatrix, glCoordMatrix, dynamicVertexArray, anchorPoint, state.getSize().aspectRatio()); + + useVertical = placeUnflipped == PlacementResult::UseVertical; - if (placeUnflipped == PlacementResult::NotEnoughRoom || + if (placeUnflipped == PlacementResult::NotEnoughRoom || useVertical || (placeUnflipped == PlacementResult::NeedsFlipping && - placeGlyphsAlongLine(placedSymbol, pitchScaledFontSize, true /*flipped*/, values.keepUpright, posMatrix, labelPlaneMatrix, glCoordMatrix, dynamicVertexArray, anchorPoint) == PlacementResult::NotEnoughRoom)) { + placeGlyphsAlongLine(placedSymbol, pitchScaledFontSize, true /*flipped*/, values.keepUpright, posMatrix, labelPlaneMatrix, glCoordMatrix, dynamicVertexArray, anchorPoint, state.getSize().aspectRatio()) == PlacementResult::NotEnoughRoom)) { hideGlyphs(placedSymbol.glyphOffsets.size(), dynamicVertexArray); } } diff --git a/src/mbgl/layout/symbol_projection.hpp b/src/mbgl/layout/symbol_projection.hpp index 2652fe7ace..3e57d162fd 100644 --- a/src/mbgl/layout/symbol_projection.hpp +++ b/src/mbgl/layout/symbol_projection.hpp @@ -8,18 +8,56 @@ namespace mbgl { class TransformState; class RenderTile; - class FrameHistory; class SymbolSizeBinder; class PlacedSymbol; namespace style { class SymbolPropertyValues; } // end namespace style + + struct TileDistance { + TileDistance(float prevTileDistance_, float lastSegmentViewportDistance_) + : prevTileDistance(prevTileDistance_), lastSegmentViewportDistance(lastSegmentViewportDistance_) + {} + float prevTileDistance; + float lastSegmentViewportDistance; + }; + + struct PlacedGlyph { + PlacedGlyph() = default; + PlacedGlyph(Point<float> point_, float angle_, optional<TileDistance> tileDistance_) + : point(point_), angle(angle_), tileDistance(std::move(tileDistance_)) + {} + PlacedGlyph(PlacedGlyph&& other) noexcept + : point(std::move(other.point)), angle(other.angle), tileDistance(std::move(other.tileDistance)) + {} + PlacedGlyph(const PlacedGlyph& other) + : point(std::move(other.point)), angle(other.angle), tileDistance(std::move(other.tileDistance)) + {} + Point<float> point; + float angle; + optional<TileDistance> tileDistance; + }; + + float evaluateSizeForFeature(const ZoomEvaluatedSize& zoomEvaluatedSize, const PlacedSymbol& placedSymbol); mat4 getLabelPlaneMatrix(const mat4& posMatrix, const bool pitchWithMap, const bool rotateWithMap, const TransformState& state, const float pixelsToTileUnits); mat4 getGlCoordMatrix(const mat4& posMatrix, const bool pitchWithMap, const bool rotateWithMap, const TransformState& state, const float pixelsToTileUnits); + + using PointAndCameraDistance = std::pair<Point<float>,float>; + PointAndCameraDistance project(const Point<float>& point, const mat4& matrix); void reprojectLineLabels(gl::VertexVector<SymbolDynamicLayoutAttributes::Vertex>&, const std::vector<PlacedSymbol>&, const mat4& posMatrix, const style::SymbolPropertyValues&, - const RenderTile&, const SymbolSizeBinder& sizeBinder, const TransformState&, const FrameHistory& frameHistory); + const RenderTile&, const SymbolSizeBinder& sizeBinder, const TransformState&); + + optional<std::pair<PlacedGlyph, PlacedGlyph>> placeFirstAndLastGlyph(const float fontScale, + const float lineOffsetX, + const float lineOffsetY, + const bool flip, + const Point<float>& anchorPoint, + const Point<float>& tileAnchorPoint, + const PlacedSymbol& symbol, + const mat4& labelPlaneMatrix, + const bool returnTileDistance); } // end namespace mbgl diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp index 5e5063905e..947973415a 100644 --- a/src/mbgl/map/map.cpp +++ b/src/mbgl/map/map.cpp @@ -149,8 +149,8 @@ void Map::renderStill(StillImageCallback callback) { return; } - if (impl->mode != MapMode::Still) { - callback(std::make_exception_ptr(util::MisuseException("Map is not in still image render mode"))); + if (impl->mode != MapMode::Static && impl->mode != MapMode::Tile) { + callback(std::make_exception_ptr(util::MisuseException("Map is not in static or tile image render modes"))); return; } @@ -617,6 +617,35 @@ ViewportMode Map::getViewportMode() const { return impl->transform.getViewportMode(); } +#pragma mark - Projection mode + +void Map::setAxonometric(bool axonometric) { + impl->transform.setAxonometric(axonometric); + impl->onUpdate(); +} + +bool Map::getAxonometric() const { + return impl->transform.getAxonometric(); +} + +void Map::setXSkew(double xSkew) { + impl->transform.setXSkew(xSkew); + impl->onUpdate(); +} + +double Map::getXSkew() const { + return impl->transform.getXSkew(); +} + +void Map::setYSkew(double ySkew) { + impl->transform.setYSkew(ySkew); + impl->onUpdate(); +} + +double Map::getYSkew() const { + return impl->transform.getYSkew(); +} + #pragma mark - Projection ScreenCoordinate Map::pixelForLatLng(const LatLng& latLng) const { @@ -765,7 +794,7 @@ void Map::Impl::onStyleError(std::exception_ptr error) { } void Map::Impl::onResourceError(std::exception_ptr error) { - if (mode == MapMode::Still && stillImageRequest) { + if (mode != MapMode::Continuous && stillImageRequest) { auto request = std::move(stillImageRequest); request->callback(error); } diff --git a/src/mbgl/map/transform.cpp b/src/mbgl/map/transform.cpp index 2bb25af28f..105adf0400 100644 --- a/src/mbgl/map/transform.cpp +++ b/src/mbgl/map/transform.cpp @@ -527,6 +527,32 @@ ViewportMode Transform::getViewportMode() const { return state.getViewportMode(); } +#pragma mark - Projection mode + +void Transform::setAxonometric(bool axonometric) { + state.axonometric = axonometric; +} + +bool Transform::getAxonometric() const { + return state.axonometric; +} + +void Transform::setXSkew(double xSkew) { + state.xSkew = xSkew; +} + +double Transform::getXSkew() const { + return state.xSkew; +} + +void Transform::setYSkew(double ySkew) { + state.ySkew = ySkew; +} + +double Transform::getYSkew() const { + return state.ySkew; +} + #pragma mark - Transition void Transform::startTransition(const CameraOptions& camera, diff --git a/src/mbgl/map/transform.hpp b/src/mbgl/map/transform.hpp index 749228bdf5..d429c57661 100644 --- a/src/mbgl/map/transform.hpp +++ b/src/mbgl/map/transform.hpp @@ -125,6 +125,14 @@ public: void setViewportMode(ViewportMode); ViewportMode getViewportMode() const; + // Projection mode + void setAxonometric(bool); + bool getAxonometric() const; + void setXSkew(double xSkew); + double getXSkew() const; + void setYSkew(double ySkew); + double getYSkew() const; + // Transitions bool inTransition() const; void updateTransitions(const TimePoint& now); diff --git a/src/mbgl/map/transform_state.cpp b/src/mbgl/map/transform_state.cpp index d1a320beae..18d2c24aee 100644 --- a/src/mbgl/map/transform_state.cpp +++ b/src/mbgl/map/transform_state.cpp @@ -27,7 +27,7 @@ void TransformState::matrixFor(mat4& matrix, const UnwrappedTileID& tileID) cons matrix::scale(matrix, matrix, s / util::EXTENT, s / util::EXTENT, 1); } -void TransformState::getProjMatrix(mat4& projMatrix, uint16_t nearZ) const { +void TransformState::getProjMatrix(mat4& projMatrix, uint16_t nearZ, bool aligned) const { if (size.isEmpty()) { return; } @@ -63,11 +63,35 @@ void TransformState::getProjMatrix(mat4& projMatrix, uint16_t nearZ) const { matrix::rotate_z(projMatrix, projMatrix, getAngle() + getNorthOrientationAngle()); - matrix::translate(projMatrix, projMatrix, pixel_x() - size.width / 2.0f, - pixel_y() - size.height / 2.0f, 0); + const double dx = pixel_x() - size.width / 2.0f, dy = pixel_y() - size.height / 2.0f; + matrix::translate(projMatrix, projMatrix, dx, dy, 0); + + if (axonometric) { + // mat[11] controls perspective + projMatrix[11] = 0; + + // mat[8], mat[9] control x-skew, y-skew + projMatrix[8] = xSkew; + projMatrix[9] = ySkew; + } matrix::scale(projMatrix, projMatrix, 1, 1, 1.0 / Projection::getMetersPerPixelAtLatitude(getLatLng(LatLng::Unwrapped).latitude(), getZoom())); + + // Make a second projection matrix that is aligned to a pixel grid for rendering raster tiles. + // We're rounding the (floating point) x/y values to achieve to avoid rendering raster images to fractional + // coordinates. Additionally, we adjust by half a pixel in either direction in case that viewport dimension + // is an odd integer to preserve rendering to the pixel grid. We're rotating this shift based on the angle + // of the transformation so that 0°, 90°, 180°, and 270° rasters are crisp, and adjust the shift so that + // it is always <= 0.5 pixels. + if (aligned) { + const float xShift = float(size.width % 2) / 2, yShift = float(size.height % 2) / 2; + const double angleCos = std::cos(angle), angleSin = std::sin(angle); + double devNull; + const float dxa = -std::modf(dx, &devNull) + angleCos * xShift + angleSin * yShift; + const float dya = -std::modf(dy, &devNull) + angleCos * yShift + angleSin * xShift; + matrix::translate(projMatrix, projMatrix, dxa > 0.5 ? dxa - 1 : dxa, dya > 0.5 ? dya - 1 : dya, 0); + } } #pragma mark - Dimensions diff --git a/src/mbgl/map/transform_state.hpp b/src/mbgl/map/transform_state.hpp index 59522d89fd..451802034d 100644 --- a/src/mbgl/map/transform_state.hpp +++ b/src/mbgl/map/transform_state.hpp @@ -25,7 +25,7 @@ public: // Matrix void matrixFor(mat4&, const UnwrappedTileID&) const; - void getProjMatrix(mat4& matrix, uint16_t nearZ = 1) const; + void getProjMatrix(mat4& matrix, uint16_t nearZ = 1, bool aligned = false) const; // Dimensions Size getSize() const; @@ -134,6 +134,9 @@ private: // `fov = 2 * arctan((height / 2) / (height * 1.5))` double fov = 0.6435011087932844; double pitch = 0.0; + double xSkew = 0.0; + double ySkew = 1.0; + bool axonometric = false; // cache values for spherical mercator math double Bc = Projection::worldSize(scale) / util::DEGREES_MAX; diff --git a/src/mbgl/programs/attributes.hpp b/src/mbgl/programs/attributes.hpp index d023ec7d83..5d7a6474cf 100644 --- a/src/mbgl/programs/attributes.hpp +++ b/src/mbgl/programs/attributes.hpp @@ -28,8 +28,9 @@ MBGL_DEFINE_ATTRIBUTE(float, 3, a_projected_pos); MBGL_DEFINE_ATTRIBUTE(int16_t, 2, a_label_pos); MBGL_DEFINE_ATTRIBUTE(int16_t, 2, a_anchor_pos); MBGL_DEFINE_ATTRIBUTE(uint16_t, 2, a_texture_pos); -MBGL_DEFINE_ATTRIBUTE(int16_t, 3, a_normal); -MBGL_DEFINE_ATTRIBUTE(uint16_t, 1, a_edgedistance); +MBGL_DEFINE_ATTRIBUTE(int16_t, 4, a_normal_ed); +MBGL_DEFINE_ATTRIBUTE(uint8_t, 1, a_fade_opacity); +MBGL_DEFINE_ATTRIBUTE(uint8_t, 2, a_placed); template <typename T, std::size_t N> struct a_data { @@ -142,4 +143,9 @@ struct a_halo_blur { }; } // namespace attributes + +struct PositionOnlyLayoutAttributes : gl::Attributes< + attributes::a_pos> +{}; + } // namespace mbgl diff --git a/src/mbgl/programs/background_program.cpp b/src/mbgl/programs/background_program.cpp new file mode 100644 index 0000000000..52a9638d6b --- /dev/null +++ b/src/mbgl/programs/background_program.cpp @@ -0,0 +1,47 @@ +#include <mbgl/programs/background_program.hpp> +#include <mbgl/renderer/image_atlas.hpp> +#include <mbgl/renderer/cross_faded_property_evaluator.hpp> +#include <mbgl/tile/tile_id.hpp> +#include <mbgl/map/transform_state.hpp> + +namespace mbgl { + +using namespace style; + +static_assert(sizeof(BackgroundLayoutVertex) == 4, "expected BackgroundLayoutVertex size"); + +BackgroundPatternUniforms::Values +BackgroundPatternUniforms::values(mat4 matrix, + float opacity, + Size atlasSize, + const ImagePosition& a, + const ImagePosition& b, + const Faded<std::string>& fading, + const UnwrappedTileID& tileID, + const TransformState& state) +{ + int32_t tileSizeAtNearestZoom = util::tileSize * state.zoomScale(state.getIntegerZoom() - tileID.canonical.z); + int32_t pixelX = tileSizeAtNearestZoom * (tileID.canonical.x + tileID.wrap * state.zoomScale(tileID.canonical.z)); + int32_t pixelY = tileSizeAtNearestZoom * tileID.canonical.y; + + return BackgroundPatternUniforms::Values { + uniforms::u_matrix::Value{ matrix }, + uniforms::u_opacity::Value{ opacity }, + uniforms::u_texsize::Value{ atlasSize }, + uniforms::u_pattern_tl_a::Value{ a.tl() }, + uniforms::u_pattern_br_a::Value{ a.br() }, + uniforms::u_pattern_tl_b::Value{ b.tl() }, + uniforms::u_pattern_br_b::Value{ b.br() }, + uniforms::u_pattern_size_a::Value{ a.displaySize() }, + uniforms::u_pattern_size_b::Value{ b.displaySize() }, + uniforms::u_scale_a::Value{ fading.fromScale }, + uniforms::u_scale_b::Value{ fading.toScale }, + uniforms::u_mix::Value{ fading.t }, + uniforms::u_image::Value{ 0 }, + uniforms::u_pixel_coord_upper::Value{ std::array<float, 2> {{ float(pixelX >> 16), float(pixelY >> 16) }} }, + uniforms::u_pixel_coord_lower::Value{ std::array<float, 2> {{ float(pixelX & 0xFFFF), float(pixelY & 0xFFFF) }} }, + uniforms::u_tile_units_to_pixels::Value{ 1.0f / tileID.pixelsToTileUnits(1.0f, state.getIntegerZoom()) }, + }; +} + +} // namespace mbgl diff --git a/src/mbgl/programs/background_program.hpp b/src/mbgl/programs/background_program.hpp new file mode 100644 index 0000000000..b76318938c --- /dev/null +++ b/src/mbgl/programs/background_program.hpp @@ -0,0 +1,83 @@ +#pragma once + +#include <mbgl/programs/program.hpp> +#include <mbgl/programs/attributes.hpp> +#include <mbgl/programs/uniforms.hpp> +#include <mbgl/shaders/background.hpp> +#include <mbgl/shaders/background_pattern.hpp> +#include <mbgl/util/geometry.hpp> +#include <mbgl/util/mat4.hpp> +#include <mbgl/util/size.hpp> +#include <mbgl/style/layers/background_layer_properties.hpp> + +#include <string> + +namespace mbgl { + +class ImagePosition; +class UnwrappedTileID; +class TransformState; +template <class> class Faded; + +using BackgroundLayoutAttributes = PositionOnlyLayoutAttributes; + +struct BackgroundUniforms : gl::Uniforms< + uniforms::u_matrix, + uniforms::u_color, + uniforms::u_opacity> +{}; + +struct BackgroundPatternUniforms : gl::Uniforms< + uniforms::u_matrix, + uniforms::u_opacity, + uniforms::u_texsize, + uniforms::u_pattern_tl_a, + uniforms::u_pattern_br_a, + uniforms::u_pattern_tl_b, + uniforms::u_pattern_br_b, + uniforms::u_pattern_size_a, + uniforms::u_pattern_size_b, + uniforms::u_scale_a, + uniforms::u_scale_b, + uniforms::u_mix, + uniforms::u_image, + uniforms::u_pixel_coord_upper, + uniforms::u_pixel_coord_lower, + uniforms::u_tile_units_to_pixels> +{ + static Values values(mat4 matrix, + float opacity, + Size atlasSize, + const ImagePosition&, + const ImagePosition&, + const Faded<std::string>&, + const UnwrappedTileID&, + const TransformState&); +}; + +class BackgroundProgram : public Program< + shaders::background, + gl::Triangle, + BackgroundLayoutAttributes, + BackgroundUniforms, + style::Properties<>> +{ +public: + using Program::Program; +}; + +class BackgroundPatternProgram : public Program< + shaders::background_pattern, + gl::Triangle, + BackgroundLayoutAttributes, + BackgroundPatternUniforms, + style::Properties<>> +{ +public: + using Program::Program; +}; + +using BackgroundLayoutVertex = BackgroundProgram::LayoutVertex; +using BackgroundAttributes = BackgroundProgram::Attributes; + +} // namespace mbgl diff --git a/src/mbgl/programs/clipping_mask_program.hpp b/src/mbgl/programs/clipping_mask_program.hpp new file mode 100644 index 0000000000..5dff4849fe --- /dev/null +++ b/src/mbgl/programs/clipping_mask_program.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include <mbgl/programs/program.hpp> +#include <mbgl/programs/attributes.hpp> +#include <mbgl/programs/uniforms.hpp> +#include <mbgl/shaders/clipping_mask.hpp> +#include <mbgl/style/properties.hpp> + +namespace mbgl { + +class ClippingMaskProgram : public Program< + shaders::clipping_mask, + gl::Triangle, + PositionOnlyLayoutAttributes, + gl::Uniforms< + uniforms::u_matrix>, + style::Properties<>> +{ +public: + using Program::Program; +}; + +using ClippingMaskLayoutVertex = ClippingMaskProgram::LayoutVertex; +using ClippingMaskAttributes = ClippingMaskProgram::Attributes; + +} // namespace mbgl diff --git a/src/mbgl/programs/collision_box_program.hpp b/src/mbgl/programs/collision_box_program.hpp index ba99e0c087..6e75adf36e 100644 --- a/src/mbgl/programs/collision_box_program.hpp +++ b/src/mbgl/programs/collision_box_program.hpp @@ -4,6 +4,7 @@ #include <mbgl/programs/attributes.hpp> #include <mbgl/programs/uniforms.hpp> #include <mbgl/shaders/collision_box.hpp> +#include <mbgl/shaders/collision_circle.hpp> #include <mbgl/style/properties.hpp> #include <mbgl/util/geometry.hpp> @@ -11,37 +12,34 @@ namespace mbgl { -namespace uniforms { -MBGL_DEFINE_UNIFORM_SCALAR(float, u_scale); -MBGL_DEFINE_UNIFORM_SCALAR(float, u_maxzoom); -} // namespace uniforms - -using CollisionBoxAttributes = gl::Attributes< +using CollisionBoxLayoutAttributes = gl::Attributes< attributes::a_pos, attributes::a_anchor_pos, - attributes::a_extrude, - attributes::a_data<uint8_t, 2>>; + attributes::a_extrude>; + +struct CollisionBoxDynamicAttributes : gl::Attributes<attributes::a_placed> { + static Vertex vertex(bool placed, bool notUsed) { + return Vertex { + {{ static_cast<uint8_t>(placed), static_cast<uint8_t>(notUsed) }} + }; + } +}; class CollisionBoxProgram : public Program< shaders::collision_box, gl::Line, - CollisionBoxAttributes, + gl::ConcatenateAttributes<CollisionBoxLayoutAttributes, CollisionBoxDynamicAttributes>, gl::Uniforms< uniforms::u_matrix, - uniforms::u_scale, - uniforms::u_zoom, - uniforms::u_maxzoom, - uniforms::u_collision_y_stretch, - uniforms::u_camera_to_center_distance, - uniforms::u_pitch, - uniforms::u_fadetexture>, + uniforms::u_extrude_scale, + uniforms::u_camera_to_center_distance>, style::Properties<>> { public: using Program::Program; - static LayoutVertex vertex(Point<float> a, Point<float> anchor, Point<float> o, float maxzoom, float placementZoom) { - return LayoutVertex { + static CollisionBoxLayoutAttributes::Vertex vertex(Point<float> a, Point<float> anchor, Point<float> o) { + return CollisionBoxLayoutAttributes::Vertex { {{ static_cast<int16_t>(a.x), static_cast<int16_t>(a.y) @@ -53,13 +51,132 @@ public: {{ static_cast<int16_t>(::round(o.x)), static_cast<int16_t>(::round(o.y)) + }} + }; + } + + template <class DrawMode> + void draw(gl::Context& context, + DrawMode drawMode, + gl::DepthMode depthMode, + gl::StencilMode stencilMode, + gl::ColorMode colorMode, + const UniformValues& uniformValues, + const gl::VertexBuffer<CollisionBoxLayoutAttributes::Vertex>& layoutVertexBuffer, + const gl::VertexBuffer<CollisionBoxDynamicAttributes::Vertex>& dynamicVertexBuffer, + const gl::IndexBuffer<DrawMode>& indexBuffer, + const SegmentVector<Attributes>& segments, + const PaintPropertyBinders& paintPropertyBinders, + const typename PaintProperties::PossiblyEvaluated& currentProperties, + float currentZoom, + const std::string& layerID) { + typename AllUniforms::Values allUniformValues = uniformValues + .concat(paintPropertyBinders.uniformValues(currentZoom, currentProperties)); + + typename Attributes::Bindings allAttributeBindings = CollisionBoxLayoutAttributes::bindings(layoutVertexBuffer) + .concat(CollisionBoxDynamicAttributes::bindings(dynamicVertexBuffer)) + .concat(paintPropertyBinders.attributeBindings(currentProperties)); + + assert(layoutVertexBuffer.vertexCount == dynamicVertexBuffer.vertexCount); + + for (auto& segment : segments) { + auto vertexArrayIt = segment.vertexArrays.find(layerID); + + if (vertexArrayIt == segment.vertexArrays.end()) { + vertexArrayIt = segment.vertexArrays.emplace(layerID, context.createVertexArray()).first; + } + + program.draw( + context, + std::move(drawMode), + std::move(depthMode), + std::move(stencilMode), + std::move(colorMode), + allUniformValues, + vertexArrayIt->second, + Attributes::offsetBindings(allAttributeBindings, segment.vertexOffset), + indexBuffer, + segment.indexOffset, + segment.indexLength); + } + } +}; + + +class CollisionCircleProgram : public Program< + shaders::collision_circle, + gl::Triangle, + gl::ConcatenateAttributes<CollisionBoxLayoutAttributes, CollisionBoxDynamicAttributes>, + gl::Uniforms< + uniforms::u_matrix, + uniforms::u_extrude_scale, + uniforms::u_overscale_factor, + uniforms::u_camera_to_center_distance>, + style::Properties<>> +{ +public: + using Program::Program; + + static CollisionBoxLayoutAttributes::Vertex vertex(Point<float> a, Point<float> anchor, Point<float> o) { + return CollisionBoxLayoutAttributes::Vertex { + {{ + static_cast<int16_t>(a.x), + static_cast<int16_t>(a.y) + }}, + {{ + static_cast<int16_t>(anchor.x), + static_cast<int16_t>(anchor.y) }}, {{ - static_cast<uint8_t>(maxzoom * 10), - static_cast<uint8_t>(placementZoom * 10) + static_cast<int16_t>(::round(o.x)), + static_cast<int16_t>(::round(o.y)) }} }; } + + template <class DrawMode> + void draw(gl::Context& context, + DrawMode drawMode, + gl::DepthMode depthMode, + gl::StencilMode stencilMode, + gl::ColorMode colorMode, + const UniformValues& uniformValues, + const gl::VertexBuffer<CollisionBoxLayoutAttributes::Vertex>& layoutVertexBuffer, + const gl::VertexBuffer<CollisionBoxDynamicAttributes::Vertex>& dynamicVertexBuffer, + const gl::IndexBuffer<DrawMode>& indexBuffer, + const SegmentVector<Attributes>& segments, + const PaintPropertyBinders& paintPropertyBinders, + const typename PaintProperties::PossiblyEvaluated& currentProperties, + float currentZoom, + const std::string& layerID) { + typename AllUniforms::Values allUniformValues = uniformValues + .concat(paintPropertyBinders.uniformValues(currentZoom, currentProperties)); + + typename Attributes::Bindings allAttributeBindings = CollisionBoxLayoutAttributes::bindings(layoutVertexBuffer) + .concat(CollisionBoxDynamicAttributes::bindings(dynamicVertexBuffer)) + .concat(paintPropertyBinders.attributeBindings(currentProperties)); + + for (auto& segment : segments) { + auto vertexArrayIt = segment.vertexArrays.find(layerID); + + if (vertexArrayIt == segment.vertexArrays.end()) { + vertexArrayIt = segment.vertexArrays.emplace(layerID, context.createVertexArray()).first; + } + + program.draw( + context, + std::move(drawMode), + std::move(depthMode), + std::move(stencilMode), + std::move(colorMode), + allUniformValues, + vertexArrayIt->second, + Attributes::offsetBindings(allAttributeBindings, segment.vertexOffset), + indexBuffer, + segment.indexOffset, + segment.indexLength); + } + } }; using CollisionBoxVertex = CollisionBoxProgram::LayoutVertex; diff --git a/src/mbgl/programs/fill_extrusion_program.hpp b/src/mbgl/programs/fill_extrusion_program.hpp index 820670068e..c499e9ef2d 100644 --- a/src/mbgl/programs/fill_extrusion_program.hpp +++ b/src/mbgl/programs/fill_extrusion_program.hpp @@ -30,8 +30,7 @@ MBGL_DEFINE_UNIFORM_SCALAR(float, u_height_factor); struct FillExtrusionLayoutAttributes : gl::Attributes< attributes::a_pos, - attributes::a_normal, - attributes::a_edgedistance> + attributes::a_normal_ed> {}; struct FillExtrusionUniforms : gl::Uniforms< @@ -100,12 +99,9 @@ public: // We pack a bool (`t`) into the x component indicating whether it is an upper or lower vertex static_cast<int16_t>(floor(nx * factor) * 2 + t), static_cast<int16_t>(ny * factor * 2), - static_cast<int16_t>(nz * factor * 2) - - }}, - {{ + static_cast<int16_t>(nz * factor * 2), // The edgedistance attribute is used for wrapping fill_extrusion patterns - e + static_cast<int16_t>(e) }} }; } diff --git a/src/mbgl/programs/fill_program.hpp b/src/mbgl/programs/fill_program.hpp index 2dfeea3279..ac478250fc 100644 --- a/src/mbgl/programs/fill_program.hpp +++ b/src/mbgl/programs/fill_program.hpp @@ -21,9 +21,7 @@ class UnwrappedTileID; class TransformState; template <class> class Faded; -struct FillLayoutAttributes : gl::Attributes< - attributes::a_pos> -{}; +using FillLayoutAttributes = PositionOnlyLayoutAttributes; struct FillUniforms : gl::Uniforms< uniforms::u_matrix, diff --git a/src/mbgl/programs/hillshade_prepare_program.cpp b/src/mbgl/programs/hillshade_prepare_program.cpp new file mode 100644 index 0000000000..0c0446d3f5 --- /dev/null +++ b/src/mbgl/programs/hillshade_prepare_program.cpp @@ -0,0 +1,7 @@ +#include <mbgl/programs/hillshade_prepare_program.hpp> + +namespace mbgl { + +static_assert(sizeof(HillshadePrepareLayoutVertex) == 8, "expected HillshadeLayoutVertex size"); + +} // namespace mbgl diff --git a/src/mbgl/programs/hillshade_prepare_program.hpp b/src/mbgl/programs/hillshade_prepare_program.hpp new file mode 100644 index 0000000000..0f31a22df5 --- /dev/null +++ b/src/mbgl/programs/hillshade_prepare_program.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include <mbgl/programs/program.hpp> +#include <mbgl/programs/attributes.hpp> +#include <mbgl/programs/uniforms.hpp> +#include <mbgl/shaders/hillshade_prepare.hpp> +#include <mbgl/util/geometry.hpp> + +namespace mbgl { + +namespace uniforms { +MBGL_DEFINE_UNIFORM_VECTOR(uint16_t, 2, u_dimension); +} // namespace uniforms + +class HillshadePrepareProgram : public Program< + shaders::hillshade_prepare, + gl::Triangle, + gl::Attributes< + attributes::a_pos, + attributes::a_texture_pos>, + gl::Uniforms< + uniforms::u_matrix, + uniforms::u_dimension, + uniforms::u_zoom, + uniforms::u_image>, + style::Properties<>> { +public: + using Program::Program; + + static LayoutVertex layoutVertex(Point<int16_t> p, Point<uint16_t> t) { + return LayoutVertex { + {{ + p.x, + p.y + }}, + {{ + t.x, + t.y + }} + }; + } +}; + +using HillshadePrepareLayoutVertex = HillshadePrepareProgram::LayoutVertex; +using HillshadePrepareAttributes = HillshadePrepareProgram::Attributes; + +} // namespace mbgl diff --git a/src/mbgl/programs/hillshade_program.cpp b/src/mbgl/programs/hillshade_program.cpp new file mode 100644 index 0000000000..f054ad4b74 --- /dev/null +++ b/src/mbgl/programs/hillshade_program.cpp @@ -0,0 +1,7 @@ +#include <mbgl/programs/hillshade_program.hpp> + +namespace mbgl { + +static_assert(sizeof(HillshadeLayoutVertex) == 8, "expected HillshadeLayoutVertex size"); + +} // namespace mbgl diff --git a/src/mbgl/programs/hillshade_program.hpp b/src/mbgl/programs/hillshade_program.hpp new file mode 100644 index 0000000000..5f9b4d1c2f --- /dev/null +++ b/src/mbgl/programs/hillshade_program.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include <mbgl/programs/program.hpp> +#include <mbgl/programs/attributes.hpp> +#include <mbgl/programs/uniforms.hpp> +#include <mbgl/shaders/hillshade.hpp> +#include <mbgl/util/geometry.hpp> +#include <mbgl/style/layers/hillshade_layer_properties.hpp> + +namespace mbgl { + +namespace uniforms { +MBGL_DEFINE_UNIFORM_SCALAR(Color, u_shadow); +MBGL_DEFINE_UNIFORM_SCALAR(Color, u_highlight); +MBGL_DEFINE_UNIFORM_SCALAR(Color, u_accent); +MBGL_DEFINE_UNIFORM_VECTOR(float, 2, u_light); +MBGL_DEFINE_UNIFORM_VECTOR(float, 2, u_latrange); +} // namespace uniforms + +class HillshadeProgram : public Program< + shaders::hillshade, + gl::Triangle, + gl::Attributes< + attributes::a_pos, + attributes::a_texture_pos>, + gl::Uniforms< + uniforms::u_matrix, + uniforms::u_image, + uniforms::u_highlight, + uniforms::u_shadow, + uniforms::u_accent, + uniforms::u_light, + uniforms::u_latrange>, + style::HillshadePaintProperties>{ +public: + using Program::Program; + + static LayoutVertex layoutVertex(Point<int16_t> p, Point<uint16_t> t) { + return LayoutVertex { + {{ + p.x, + p.y + }}, + {{ + t.x, + t.y + }} + }; + } +}; + +using HillshadeLayoutVertex = HillshadeProgram::LayoutVertex; +using HillshadeAttributes = HillshadeProgram::Attributes; + +} // namespace mbgl diff --git a/src/mbgl/programs/programs.hpp b/src/mbgl/programs/programs.hpp index 37ced32745..f533a6f633 100644 --- a/src/mbgl/programs/programs.hpp +++ b/src/mbgl/programs/programs.hpp @@ -1,9 +1,13 @@ #pragma once +#include <mbgl/programs/background_program.hpp> #include <mbgl/programs/circle_program.hpp> +#include <mbgl/programs/clipping_mask_program.hpp> #include <mbgl/programs/extrusion_texture_program.hpp> #include <mbgl/programs/fill_program.hpp> #include <mbgl/programs/fill_extrusion_program.hpp> +#include <mbgl/programs/hillshade_program.hpp> +#include <mbgl/programs/hillshade_prepare_program.hpp> #include <mbgl/programs/line_program.hpp> #include <mbgl/programs/raster_program.hpp> #include <mbgl/programs/symbol_program.hpp> @@ -16,7 +20,9 @@ namespace mbgl { class Programs { public: Programs(gl::Context& context, const ProgramParameters& programParameters) - : circle(context, programParameters), + : background(context, programParameters), + backgroundPattern(context, programParameters), + circle(context, programParameters), extrusionTexture(context, programParameters), fill(context, programParameters), fillExtrusion(context, programParameters), @@ -24,6 +30,8 @@ public: fillPattern(context, programParameters), fillOutline(context, programParameters), fillOutlinePattern(context, programParameters), + hillshade(context, programParameters), + hillshadePrepare(context, programParameters), line(context, programParameters), lineSDF(context, programParameters), linePattern(context, programParameters), @@ -32,9 +40,13 @@ public: symbolIconSDF(context, programParameters), symbolGlyph(context, programParameters), debug(context, programParameters), - collisionBox(context, programParameters) { + collisionBox(context, programParameters), + collisionCircle(context, programParameters), + clippingMask(context, programParameters) { } + BackgroundProgram background; + BackgroundPatternProgram backgroundPattern; ProgramMap<CircleProgram> circle; ExtrusionTextureProgram extrusionTexture; ProgramMap<FillProgram> fill; @@ -43,6 +55,8 @@ public: ProgramMap<FillPatternProgram> fillPattern; ProgramMap<FillOutlineProgram> fillOutline; ProgramMap<FillOutlinePatternProgram> fillOutlinePattern; + HillshadeProgram hillshade; + HillshadePrepareProgram hillshadePrepare; ProgramMap<LineProgram> line; ProgramMap<LineSDFProgram> lineSDF; ProgramMap<LinePatternProgram> linePattern; @@ -53,6 +67,8 @@ public: DebugProgram debug; CollisionBoxProgram collisionBox; + CollisionCircleProgram collisionCircle; + ClippingMaskProgram clippingMask; }; } // namespace mbgl diff --git a/src/mbgl/programs/symbol_program.cpp b/src/mbgl/programs/symbol_program.cpp index 58174ff8a7..84a7a53f1d 100644 --- a/src/mbgl/programs/symbol_program.cpp +++ b/src/mbgl/programs/symbol_program.cpp @@ -37,6 +37,7 @@ Values makeValues(const bool isText, const bool alongLine, const RenderTile& tile, const TransformState& state, + const float symbolFadeChange, Args&&... args) { std::array<float, 2> extrudeScale; @@ -82,9 +83,8 @@ Values makeValues(const bool isText, uniforms::u_extrude_scale::Value{ extrudeScale }, uniforms::u_texsize::Value{ texsize }, uniforms::u_texture::Value{ 0 }, - uniforms::u_fadetexture::Value{ 1 }, + uniforms::u_fade_change::Value{ symbolFadeChange }, uniforms::u_is_text::Value{ isText }, - uniforms::u_collision_y_stretch::Value{ tile.tile.yStretch() }, uniforms::u_camera_to_center_distance::Value{ state.getCameraToCenterDistance() }, uniforms::u_pitch::Value{ state.getPitch() }, uniforms::u_pitch_with_map::Value{ pitchWithMap }, @@ -102,7 +102,8 @@ SymbolIconProgram::uniformValues(const bool isText, const std::array<float, 2>& pixelsToGLUnits, const bool alongLine, const RenderTile& tile, - const TransformState& state) + const TransformState& state, + const float symbolFadeChange) { return makeValues<SymbolIconProgram::UniformValues>( isText, @@ -111,7 +112,8 @@ SymbolIconProgram::uniformValues(const bool isText, pixelsToGLUnits, alongLine, tile, - state + state, + symbolFadeChange ); } @@ -124,6 +126,7 @@ typename SymbolSDFProgram<PaintProperties>::UniformValues SymbolSDFProgram<Paint const bool alongLine, const RenderTile& tile, const TransformState& state, + const float symbolFadeChange, const SymbolSDFPart part) { const float gammaScale = (values.pitchAlignment == AlignmentType::Map @@ -138,6 +141,7 @@ typename SymbolSDFProgram<PaintProperties>::UniformValues SymbolSDFProgram<Paint alongLine, tile, state, + symbolFadeChange, uniforms::u_gamma_scale::Value{ gammaScale }, uniforms::u_is_halo::Value{ part == SymbolSDFPart::Halo } ); diff --git a/src/mbgl/programs/symbol_program.hpp b/src/mbgl/programs/symbol_program.hpp index a7abf94f56..a14afac702 100644 --- a/src/mbgl/programs/symbol_program.hpp +++ b/src/mbgl/programs/symbol_program.hpp @@ -75,18 +75,24 @@ struct SymbolLayoutAttributes : gl::Attributes< }; struct SymbolDynamicLayoutAttributes : gl::Attributes<attributes::a_projected_pos> { - static Vertex vertex(Point<float> anchorPoint, float labelAngle, float labelminzoom) { + static Vertex vertex(Point<float> anchorPoint, float labelAngle) { return Vertex { {{ anchorPoint.x, anchorPoint.y, - static_cast<float>(mbgl::attributes::packUint8Pair( - static_cast<uint8_t>(std::fmod(labelAngle + 2 * M_PI, 2 * M_PI) / (2 * M_PI) * 255), - static_cast<uint8_t>(labelminzoom * 10))) + labelAngle }} }; } }; + +struct SymbolOpacityAttributes : gl::Attributes<attributes::a_fade_opacity> { + static Vertex vertex(bool placed, float opacity) { + return Vertex { + {{ static_cast<uint8_t>((static_cast<uint8_t>(opacity * 127) << 1) | static_cast<uint8_t>(placed)) }} + }; + } +}; struct ZoomEvaluatedSize { bool isZoomConstant; @@ -128,23 +134,6 @@ public: } }; -// Return the smallest range of stops that covers the interval [lowerZoom, upperZoom] -template <class Stops> -Range<float> getCoveringStops(Stops s, float lowerZoom, float upperZoom) { - assert(!s.stops.empty()); - auto minIt = s.stops.lower_bound(lowerZoom); - auto maxIt = s.stops.lower_bound(upperZoom); - - // lower_bound yields first element >= lowerZoom, but we want the *last* - // element <= lowerZoom, so if we found a stop > lowerZoom, back up by one. - if (minIt != s.stops.begin() && minIt != s.stops.end() && minIt->first > lowerZoom) { - minIt--; - } - return Range<float> { - minIt == s.stops.end() ? s.stops.rbegin()->first : minIt->first, - maxIt == s.stops.end() ? s.stops.rbegin()->first : maxIt->first - }; -} class ConstantSymbolSizeBinder final : public SymbolSizeBinder { public: @@ -155,19 +144,12 @@ public: : layoutSize(defaultValue) {} ConstantSymbolSizeBinder(const float tileZoom, const style::CameraFunction<float>& function_, const float /*defaultValue*/) - : layoutSize(function_.evaluate(tileZoom + 1)) { - function_.stops.match( - [&] (const style::ExponentialStops<float>& stops) { - const auto& zoomLevels = getCoveringStops(stops, tileZoom, tileZoom + 1); - coveringRanges = std::make_tuple( - zoomLevels, - Range<float> { function_.evaluate(zoomLevels.min), function_.evaluate(zoomLevels.max) } - ); - functionInterpolationBase = stops.base; - }, - [&] (const style::IntervalStops<float>&) { - function = function_; - } + : layoutSize(function_.evaluate(tileZoom + 1)), + function(function_) { + const Range<float> zoomLevels = function_.getCoveringStops(tileZoom, tileZoom + 1); + coveringRanges = std::make_tuple( + zoomLevels, + Range<float> { function_.evaluate(zoomLevels.min), function_.evaluate(zoomLevels.max) } ); } @@ -185,7 +167,7 @@ public: const Range<float>& zoomLevels = std::get<0>(*coveringRanges); const Range<float>& sizeLevels = std::get<1>(*coveringRanges); float t = util::clamp( - util::interpolationFactor(*functionInterpolationBase, zoomLevels, currentZoom), + function->interpolationFactor(zoomLevels, currentZoom), 0.0f, 1.0f ); size = sizeLevels.min + t * (sizeLevels.max - sizeLevels.min); @@ -198,10 +180,7 @@ public: } float layoutSize; - // used for exponential functions optional<std::tuple<Range<float>, Range<float>>> coveringRanges; - optional<float> functionInterpolationBase; - // used for interval functions optional<style::CameraFunction<float>> function; }; @@ -226,7 +205,7 @@ public: return { true, false, unused, unused, unused }; } - const style::SourceFunction<float>& function; + style::SourceFunction<float> function; const float defaultValue; }; @@ -237,9 +216,7 @@ public: : function(function_), defaultValue(defaultValue_), layoutZoom(tileZoom + 1), - coveringZoomStops(function.stops.match( - [&] (const auto& stops) { - return getCoveringStops(stops, tileZoom, tileZoom + 1); })) + coveringZoomStops(function.getCoveringStops(tileZoom, tileZoom + 1)) {} Range<float> getVertexSizeData(const GeometryTileFeature& feature) override { @@ -251,7 +228,7 @@ public: ZoomEvaluatedSize evaluateForZoom(float currentZoom) const override { float sizeInterpolationT = util::clamp( - util::interpolationFactor(1.0f, coveringZoomStops, currentZoom), + function.interpolationFactor(coveringZoomStops, currentZoom), 0.0f, 1.0f ); @@ -259,7 +236,7 @@ public: return { false, false, sizeInterpolationT, unused, unused }; } - const style::CompositeFunction<float>& function; + style::CompositeFunction<float> function; const float defaultValue; float layoutZoom; Range<float> coveringZoomStops; @@ -276,7 +253,7 @@ public: using LayoutAttributes = LayoutAttrs; using LayoutVertex = typename LayoutAttributes::Vertex; - using LayoutAndSizeAttributes = gl::ConcatenateAttributes<LayoutAttributes, SymbolDynamicLayoutAttributes>; + using LayoutAndSizeAttributes = gl::ConcatenateAttributes<LayoutAttributes, gl::ConcatenateAttributes<SymbolDynamicLayoutAttributes, SymbolOpacityAttributes>>; using PaintProperties = PaintProps; using PaintPropertyBinders = typename PaintProperties::Binders; @@ -310,6 +287,7 @@ public: const UniformValues& uniformValues, const gl::VertexBuffer<LayoutVertex>& layoutVertexBuffer, const gl::VertexBuffer<SymbolDynamicLayoutAttributes::Vertex>& dynamicLayoutVertexBuffer, + const gl::VertexBuffer<SymbolOpacityAttributes::Vertex>& opacityVertexBuffer, const SymbolSizeBinder& symbolSizeBinder, const gl::IndexBuffer<DrawMode>& indexBuffer, const SegmentVector<Attributes>& segments, @@ -323,8 +301,12 @@ public: typename Attributes::Bindings allAttributeBindings = LayoutAttributes::bindings(layoutVertexBuffer) .concat(SymbolDynamicLayoutAttributes::bindings(dynamicLayoutVertexBuffer)) + .concat(SymbolOpacityAttributes::bindings(opacityVertexBuffer)) .concat(paintPropertyBinders.attributeBindings(currentProperties)); + assert(layoutVertexBuffer.vertexCount == dynamicLayoutVertexBuffer.vertexCount && + layoutVertexBuffer.vertexCount == opacityVertexBuffer.vertexCount); + for (auto& segment : segments) { auto vertexArrayIt = segment.vertexArrays.find(layerID); @@ -359,9 +341,8 @@ class SymbolIconProgram : public SymbolProgram< uniforms::u_extrude_scale, uniforms::u_texsize, uniforms::u_texture, - uniforms::u_fadetexture, + uniforms::u_fade_change, uniforms::u_is_text, - uniforms::u_collision_y_stretch, uniforms::u_camera_to_center_distance, uniforms::u_pitch, uniforms::u_pitch_with_map, @@ -379,7 +360,8 @@ public: const std::array<float, 2>& pixelsToGLUnits, const bool alongLine, const RenderTile&, - const TransformState&); + const TransformState&, + const float symbolFadeChange); }; enum class SymbolSDFPart { @@ -399,9 +381,8 @@ class SymbolSDFProgram : public SymbolProgram< uniforms::u_extrude_scale, uniforms::u_texsize, uniforms::u_texture, - uniforms::u_fadetexture, + uniforms::u_fade_change, uniforms::u_is_text, - uniforms::u_collision_y_stretch, uniforms::u_camera_to_center_distance, uniforms::u_pitch, uniforms::u_pitch_with_map, @@ -423,9 +404,8 @@ public: uniforms::u_extrude_scale, uniforms::u_texsize, uniforms::u_texture, - uniforms::u_fadetexture, + uniforms::u_fade_change, uniforms::u_is_text, - uniforms::u_collision_y_stretch, uniforms::u_camera_to_center_distance, uniforms::u_pitch, uniforms::u_pitch_with_map, @@ -449,6 +429,7 @@ public: const bool alongLine, const RenderTile&, const TransformState&, + const float SymbolFadeChange, const SymbolSDFPart); }; diff --git a/src/mbgl/programs/uniforms.hpp b/src/mbgl/programs/uniforms.hpp index 285d243251..107c084918 100644 --- a/src/mbgl/programs/uniforms.hpp +++ b/src/mbgl/programs/uniforms.hpp @@ -36,6 +36,7 @@ MBGL_DEFINE_UNIFORM_SCALAR(Size, u_world); MBGL_DEFINE_UNIFORM_SCALAR(Size, u_texsize); MBGL_DEFINE_UNIFORM_SCALAR(bool, u_pitch_with_map); MBGL_DEFINE_UNIFORM_SCALAR(float, u_camera_to_center_distance); +MBGL_DEFINE_UNIFORM_SCALAR(float, u_fade_change); MBGL_DEFINE_UNIFORM_VECTOR(float, 2, u_extrude_scale); @@ -54,6 +55,7 @@ MBGL_DEFINE_UNIFORM_SCALAR(gl::TextureUnit, u_fadetexture); MBGL_DEFINE_UNIFORM_SCALAR(float, u_scale_a); MBGL_DEFINE_UNIFORM_SCALAR(float, u_scale_b); MBGL_DEFINE_UNIFORM_SCALAR(float, u_tile_units_to_pixels); +MBGL_DEFINE_UNIFORM_SCALAR(float, u_overscale_factor); } // namespace uniforms } // namespace mbgl diff --git a/src/mbgl/renderer/buckets/circle_bucket.cpp b/src/mbgl/renderer/buckets/circle_bucket.cpp index 04126990b3..c442b661de 100644 --- a/src/mbgl/renderer/buckets/circle_bucket.cpp +++ b/src/mbgl/renderer/buckets/circle_bucket.cpp @@ -49,7 +49,7 @@ void CircleBucket::addFeature(const GeometryTileFeature& feature, // Do not include points that are outside the tile boundaries. // Include all points in Still mode. You need to include points from // neighbouring tiles so that they are not clipped at tile boundaries. - if ((mode != MapMode::Still) && + if ((mode == MapMode::Continuous) && (x < 0 || x >= util::EXTENT || y < 0 || y >= util::EXTENT)) continue; if (segments.empty() || segments.back().vertexLength + vertexLength > std::numeric_limits<uint16_t>::max()) { @@ -108,8 +108,9 @@ float CircleBucket::getQueryRadius(const RenderLayer& layer) const { auto circleLayer = layer.as<RenderCircleLayer>(); float radius = get<CircleRadius>(*circleLayer, paintPropertyBinders); + float stroke = get<CircleStrokeWidth>(*circleLayer, paintPropertyBinders); auto translate = circleLayer->evaluated.get<CircleTranslate>(); - return radius + util::length(translate[0], translate[1]); + return radius + stroke + util::length(translate[0], translate[1]); } } // namespace mbgl diff --git a/src/mbgl/renderer/buckets/fill_extrusion_bucket.cpp b/src/mbgl/renderer/buckets/fill_extrusion_bucket.cpp index 7f53326fe1..5e2c937091 100644 --- a/src/mbgl/renderer/buckets/fill_extrusion_bucket.cpp +++ b/src/mbgl/renderer/buckets/fill_extrusion_bucket.cpp @@ -101,13 +101,17 @@ void FillExtrusionBucket::addFeature(const GeometryTileFeature& feature, const auto d2 = convertPoint<double>(p2); const Point<double> perp = util::unit(util::perp(d1 - d2)); + const auto dist = util::dist<int16_t>(d1, d2); + if (dist > std::numeric_limits<int16_t>::max()) { + edgeDistance = 0; + } vertices.emplace_back( FillExtrusionProgram::layoutVertex(p1, perp.x, perp.y, 0, 0, edgeDistance)); vertices.emplace_back( FillExtrusionProgram::layoutVertex(p1, perp.x, perp.y, 0, 1, edgeDistance)); - edgeDistance += util::dist<int16_t>(d1, d2); + edgeDistance += dist; vertices.emplace_back( FillExtrusionProgram::layoutVertex(p2, perp.x, perp.y, 0, 0, edgeDistance)); diff --git a/src/mbgl/renderer/buckets/hillshade_bucket.cpp b/src/mbgl/renderer/buckets/hillshade_bucket.cpp new file mode 100644 index 0000000000..8011681ee0 --- /dev/null +++ b/src/mbgl/renderer/buckets/hillshade_bucket.cpp @@ -0,0 +1,113 @@ +#include <mbgl/renderer/buckets/hillshade_bucket.hpp> +#include <mbgl/renderer/layers/render_hillshade_layer.hpp> +#include <mbgl/programs/hillshade_program.hpp> +#include <mbgl/programs/hillshade_prepare_program.hpp> +#include <mbgl/gl/context.hpp> + +namespace mbgl { + +using namespace style; + +HillshadeBucket::HillshadeBucket(PremultipliedImage&& image_): demdata(image_) { +} + +HillshadeBucket::HillshadeBucket(DEMData&& demdata_) : demdata(std::move(demdata_)) { +} + +const DEMData& HillshadeBucket::getDEMData() const { + return demdata; +} + +DEMData& HillshadeBucket::getDEMData() { + return demdata; +} + +void HillshadeBucket::upload(gl::Context& context) { + if (!hasData()) { + return; + } + + + const PremultipliedImage* image = demdata.getImage(); + dem = context.createTexture(*image); + + if (!segments.empty()) { + vertexBuffer = context.createVertexBuffer(std::move(vertices)); + indexBuffer = context.createIndexBuffer(std::move(indices)); + } + uploaded = true; +} + +void HillshadeBucket::clear() { + vertexBuffer = {}; + indexBuffer = {}; + segments.clear(); + vertices.clear(); + indices.clear(); + + uploaded = false; +} + +void HillshadeBucket::setMask(TileMask&& mask_) { + if (mask == mask_) { + return; + } + + mask = std::move(mask_); + clear(); + + if (mask == TileMask{ { 0, 0, 0 } }) { + // We want to render the full tile, and keeping the segments/vertices/indices empty means + // using the global shared buffers for covering the entire tile. + return; + } + + // Create a new segment so that we will upload (empty) buffers even when there is nothing to + // draw for this tile. + segments.emplace_back(0, 0); + + constexpr const uint16_t vertexLength = 4; + + // Create the vertex buffer for the specified tile mask. + for (const auto& id : mask) { + // Create a quad for every masked tile. + const int32_t vertexExtent = util::EXTENT >> id.z; + + const Point<int16_t> tlVertex = { static_cast<int16_t>(id.x * vertexExtent), + static_cast<int16_t>(id.y * vertexExtent) }; + const Point<int16_t> brVertex = { static_cast<int16_t>(tlVertex.x + vertexExtent), + static_cast<int16_t>(tlVertex.y + vertexExtent) }; + + if (segments.back().vertexLength + vertexLength > std::numeric_limits<uint16_t>::max()) { + // Move to a new segments because the old one can't hold the geometry. + segments.emplace_back(vertices.vertexSize(), indices.indexSize()); + } + + vertices.emplace_back( + HillshadeProgram::layoutVertex({ tlVertex.x, tlVertex.y }, { static_cast<uint16_t>(tlVertex.x), static_cast<uint16_t>(tlVertex.y) })); + vertices.emplace_back( + HillshadeProgram::layoutVertex({ brVertex.x, tlVertex.y }, { static_cast<uint16_t>(brVertex.x), static_cast<uint16_t>(tlVertex.y) })); + vertices.emplace_back( + HillshadeProgram::layoutVertex({ tlVertex.x, brVertex.y }, { static_cast<uint16_t>(tlVertex.x), static_cast<uint16_t>(brVertex.y) })); + vertices.emplace_back( + HillshadeProgram::layoutVertex({ brVertex.x, brVertex.y }, { static_cast<uint16_t>(brVertex.x), static_cast<uint16_t>(brVertex.y) })); + + auto& segment = segments.back(); + assert(segment.vertexLength <= std::numeric_limits<uint16_t>::max()); + const uint16_t offset = segment.vertexLength; + + // 0, 1, 2 + // 1, 2, 3 + indices.emplace_back(offset, offset + 1, offset + 2); + indices.emplace_back(offset + 1, offset + 2, offset + 3); + + segment.vertexLength += vertexLength; + segment.indexLength += 6; + } +} + +bool HillshadeBucket::hasData() const { + return demdata.getImage()->valid(); +} + +} // namespace mbgl diff --git a/src/mbgl/renderer/buckets/hillshade_bucket.hpp b/src/mbgl/renderer/buckets/hillshade_bucket.hpp new file mode 100644 index 0000000000..3d9f6c61af --- /dev/null +++ b/src/mbgl/renderer/buckets/hillshade_bucket.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include <mbgl/gl/index_buffer.hpp> +#include <mbgl/gl/texture.hpp> +#include <mbgl/gl/vertex_buffer.hpp> +#include <mbgl/programs/hillshade_program.hpp> +#include <mbgl/programs/hillshade_prepare_program.hpp> +#include <mbgl/renderer/bucket.hpp> +#include <mbgl/renderer/tile_mask.hpp> +#include <mbgl/geometry/dem_data.hpp> +#include <mbgl/util/image.hpp> +#include <mbgl/util/mat4.hpp> +#include <mbgl/util/optional.hpp> + +namespace mbgl { + +class HillshadeBucket : public Bucket { +public: + HillshadeBucket(PremultipliedImage&&); + HillshadeBucket(std::shared_ptr<PremultipliedImage>); + HillshadeBucket(DEMData&&); + + + void upload(gl::Context&) override; + bool hasData() const override; + + void clear(); + void setMask(TileMask&&); + + optional<gl::Texture> dem; + optional<gl::Texture> texture; + + TileMask mask{ { 0, 0, 0 } }; + + const DEMData& getDEMData() const; + DEMData& getDEMData(); + + bool isPrepared() const { + return prepared; + } + + void setPrepared (bool preparedState) { + prepared = preparedState; + } + + // Raster-DEM Tile Sources use the default buffers from Painter + gl::VertexVector<HillshadeLayoutVertex> vertices; + gl::IndexVector<gl::Triangles> indices; + SegmentVector<HillshadeAttributes> segments; + + optional<gl::VertexBuffer<HillshadeLayoutVertex>> vertexBuffer; + optional<gl::IndexBuffer<gl::Triangles>> indexBuffer; +private: + DEMData demdata; + bool prepared = false; +}; + +} // namespace mbgl diff --git a/src/mbgl/renderer/buckets/symbol_bucket.cpp b/src/mbgl/renderer/buckets/symbol_bucket.cpp index a3f71f1f6e..60e8a0b504 100644 --- a/src/mbgl/renderer/buckets/symbol_bucket.cpp +++ b/src/mbgl/renderer/buckets/symbol_bucket.cpp @@ -16,10 +16,14 @@ SymbolBucket::SymbolBucket(style::SymbolLayoutProperties::PossiblyEvaluated layo const style::DataDrivenPropertyValue<float>& iconSize, float zoom, bool sdfIcons_, - bool iconsNeedLinear_) + bool iconsNeedLinear_, + bool sortFeaturesByY_, + const std::vector<SymbolInstance>&& symbolInstances_) : layout(std::move(layout_)), sdfIcons(sdfIcons_), iconsNeedLinear(iconsNeedLinear_ || iconSize.isDataDriven() || !iconSize.isZoomConstant()), + sortFeaturesByY(sortFeaturesByY_), + symbolInstances(std::move(symbolInstances_)), textSizeBinder(SymbolSizeBinder::create(zoom, textSize, TextSize::defaultValue())), iconSizeBinder(SymbolSizeBinder::create(zoom, iconSize, IconSize::defaultValue())) { @@ -36,28 +40,84 @@ SymbolBucket::SymbolBucket(style::SymbolLayoutProperties::PossiblyEvaluated layo void SymbolBucket::upload(gl::Context& context) { if (hasTextData()) { - text.vertexBuffer = context.createVertexBuffer(std::move(text.vertices)); - text.dynamicVertexBuffer = context.createVertexBuffer(std::move(text.dynamicVertices), gl::BufferUsage::StreamDraw); - text.indexBuffer = context.createIndexBuffer(std::move(text.triangles)); + if (!staticUploaded) { + text.indexBuffer = context.createIndexBuffer(std::move(text.triangles), sortFeaturesByY ? gl::BufferUsage::StreamDraw : gl::BufferUsage::StaticDraw); + text.vertexBuffer = context.createVertexBuffer(std::move(text.vertices)); + } else if (!sortUploaded) { + context.updateIndexBuffer(*text.indexBuffer, std::move(text.triangles)); + } + + if (!dynamicUploaded) { + text.dynamicVertexBuffer = context.createVertexBuffer(std::move(text.dynamicVertices), gl::BufferUsage::StreamDraw); + } + if (!placementChangesUploaded) { + if (!text.opacityVertexBuffer) { + text.opacityVertexBuffer = context.createVertexBuffer(std::move(text.opacityVertices), gl::BufferUsage::StreamDraw); + } else { + context.updateVertexBuffer(*text.opacityVertexBuffer, std::move(text.opacityVertices)); + } + } } if (hasIconData()) { - icon.vertexBuffer = context.createVertexBuffer(std::move(icon.vertices)); - icon.dynamicVertexBuffer = context.createVertexBuffer(std::move(icon.dynamicVertices), gl::BufferUsage::StreamDraw); - icon.indexBuffer = context.createIndexBuffer(std::move(icon.triangles)); + if (!staticUploaded) { + icon.indexBuffer = context.createIndexBuffer(std::move(icon.triangles), sortFeaturesByY ? gl::BufferUsage::StreamDraw : gl::BufferUsage::StaticDraw); + icon.vertexBuffer = context.createVertexBuffer(std::move(icon.vertices)); + } else if (!sortUploaded) { + context.updateIndexBuffer(*icon.indexBuffer, std::move(icon.triangles)); + } + if (!dynamicUploaded) { + icon.dynamicVertexBuffer = context.createVertexBuffer(std::move(icon.dynamicVertices), gl::BufferUsage::StreamDraw); + } + if (!placementChangesUploaded) { + if (!icon.opacityVertexBuffer) { + icon.opacityVertexBuffer = context.createVertexBuffer(std::move(icon.opacityVertices), gl::BufferUsage::StreamDraw); + } else { + context.updateVertexBuffer(*icon.opacityVertexBuffer, std::move(icon.opacityVertices)); + } + } } - if (!collisionBox.vertices.empty()) { - collisionBox.vertexBuffer = context.createVertexBuffer(std::move(collisionBox.vertices)); - collisionBox.indexBuffer = context.createIndexBuffer(std::move(collisionBox.lines)); + if (hasCollisionBoxData()) { + if (!staticUploaded) { + collisionBox.indexBuffer = context.createIndexBuffer(std::move(collisionBox.lines)); + collisionBox.vertexBuffer = context.createVertexBuffer(std::move(collisionBox.vertices)); + } + if (!placementChangesUploaded) { + if (!collisionBox.dynamicVertexBuffer) { + collisionBox.dynamicVertexBuffer = context.createVertexBuffer(std::move(collisionBox.dynamicVertices), gl::BufferUsage::StreamDraw); + } else { + context.updateVertexBuffer(*collisionBox.dynamicVertexBuffer, std::move(collisionBox.dynamicVertices)); + } + } } - - for (auto& pair : paintPropertyBinders) { - pair.second.first.upload(context); - pair.second.second.upload(context); + + if (hasCollisionCircleData()) { + if (!staticUploaded) { + collisionCircle.indexBuffer = context.createIndexBuffer(std::move(collisionCircle.triangles)); + collisionCircle.vertexBuffer = context.createVertexBuffer(std::move(collisionCircle.vertices)); + } + if (!placementChangesUploaded) { + if (!collisionCircle.dynamicVertexBuffer) { + collisionCircle.dynamicVertexBuffer = context.createVertexBuffer(std::move(collisionCircle.dynamicVertices), gl::BufferUsage::StreamDraw); + } else { + context.updateVertexBuffer(*collisionCircle.dynamicVertexBuffer, std::move(collisionCircle.dynamicVertices)); + } + } } + if (!staticUploaded) { + for (auto& pair : paintPropertyBinders) { + pair.second.first.upload(context); + pair.second.second.upload(context); + } + } + uploaded = true; + staticUploaded = true; + placementChangesUploaded = true; + dynamicUploaded = true; + sortUploaded = true; } bool SymbolBucket::hasData() const { @@ -76,4 +136,83 @@ bool SymbolBucket::hasCollisionBoxData() const { return !collisionBox.segments.empty(); } +bool SymbolBucket::hasCollisionCircleData() const { + return !collisionCircle.segments.empty(); +} + +void SymbolBucket::updateOpacity() { + placementChangesUploaded = false; + uploaded = false; +} + +void addPlacedSymbol(gl::IndexVector<gl::Triangles>& triangles, const PlacedSymbol& placedSymbol) { + auto endIndex = placedSymbol.vertexStartIndex + placedSymbol.glyphOffsets.size() * 4; + for (auto vertexIndex = placedSymbol.vertexStartIndex; vertexIndex < endIndex; vertexIndex += 4) { + triangles.emplace_back(vertexIndex + 0, vertexIndex + 1, vertexIndex + 2); + triangles.emplace_back(vertexIndex + 1, vertexIndex + 2, vertexIndex + 3); + } +} + +void SymbolBucket::sortFeatures(const float angle) { + if (!sortFeaturesByY) { + return; + } + + if (sortedAngle && *sortedAngle == angle) { + return; + } + + sortedAngle = angle; + + // The current approach to sorting doesn't sort across segments so don't try. + // Sorting within segments separately seemed not to be worth the complexity. + if (text.segments.size() > 1 || icon.segments.size() > 1) { + return; + } + + sortUploaded = false; + uploaded = false; + + // If the symbols are allowed to overlap sort them by their vertical screen position. + // The index array buffer is rewritten to reference the (unchanged) vertices in the + // sorted order. + + // To avoid sorting the actual symbolInstance array we sort an array of indexes. + std::vector<size_t> symbolInstanceIndexes; + symbolInstanceIndexes.reserve(symbolInstances.size()); + for (size_t i = 0; i < symbolInstances.size(); i++) { + symbolInstanceIndexes.push_back(i); + } + + const float sin = std::sin(angle); + const float cos = std::cos(angle); + + std::sort(symbolInstanceIndexes.begin(), symbolInstanceIndexes.end(), [sin, cos, this](size_t &aIndex, size_t &bIndex) { + const SymbolInstance& a = symbolInstances[aIndex]; + const SymbolInstance& b = symbolInstances[bIndex]; + const int32_t aRotated = sin * a.anchor.point.x + cos * a.anchor.point.y; + const int32_t bRotated = sin * b.anchor.point.x + cos * b.anchor.point.y; + return aRotated != bRotated ? + aRotated < bRotated : + a.index > b.index; + }); + + text.triangles.clear(); + icon.triangles.clear(); + + for (auto i : symbolInstanceIndexes) { + const SymbolInstance& symbolInstance = symbolInstances[i]; + + if (symbolInstance.placedTextIndex) { + addPlacedSymbol(text.triangles, text.placedSymbols[*symbolInstance.placedTextIndex]); + } + if (symbolInstance.placedVerticalTextIndex) { + addPlacedSymbol(text.triangles, text.placedSymbols[*symbolInstance.placedVerticalTextIndex]); + } + if (symbolInstance.placedIconIndex) { + addPlacedSymbol(icon.triangles, icon.placedSymbols[*symbolInstance.placedIconIndex]); + } + } +} + } // namespace mbgl diff --git a/src/mbgl/renderer/buckets/symbol_bucket.hpp b/src/mbgl/renderer/buckets/symbol_bucket.hpp index 32f976bcb2..ed8afb052c 100644 --- a/src/mbgl/renderer/buckets/symbol_bucket.hpp +++ b/src/mbgl/renderer/buckets/symbol_bucket.hpp @@ -10,6 +10,7 @@ #include <mbgl/text/glyph_range.hpp> #include <mbgl/style/layers/symbol_layer_properties.hpp> #include <mbgl/layout/symbol_feature.hpp> +#include <mbgl/layout/symbol_instance.hpp> #include <vector> @@ -18,18 +19,22 @@ namespace mbgl { class PlacedSymbol { public: PlacedSymbol(Point<float> anchorPoint_, uint16_t segment_, float lowerSize_, float upperSize_, - std::array<float, 2> lineOffset_, float placementZoom_, bool useVerticalMode_, GeometryCoordinates line_) : + std::array<float, 2> lineOffset_, WritingModeType writingModes_, GeometryCoordinates line_, std::vector<float> tileDistances_) : anchorPoint(anchorPoint_), segment(segment_), lowerSize(lowerSize_), upperSize(upperSize_), - lineOffset(lineOffset_), placementZoom(placementZoom_), useVerticalMode(useVerticalMode_), line(std::move(line_)) {} + lineOffset(lineOffset_), writingModes(writingModes_), line(std::move(line_)), tileDistances(std::move(tileDistances_)), hidden(false), vertexStartIndex(0) + { + } Point<float> anchorPoint; uint16_t segment; float lowerSize; float upperSize; std::array<float, 2> lineOffset; - float placementZoom; - bool useVerticalMode; + WritingModeType writingModes; GeometryCoordinates line; + std::vector<float> tileDistances; std::vector<float> glyphOffsets; + bool hidden; + size_t vertexStartIndex; }; class SymbolBucket : public Bucket { @@ -40,17 +45,33 @@ public: const style::DataDrivenPropertyValue<float>& iconSize, float zoom, bool sdfIcons, - bool iconsNeedLinear); + bool iconsNeedLinear, + bool sortFeaturesByY, + const std::vector<SymbolInstance>&&); void upload(gl::Context&) override; bool hasData() const override; bool hasTextData() const; bool hasIconData() const; bool hasCollisionBoxData() const; + bool hasCollisionCircleData() const; + + void updateOpacity(); + void sortFeatures(const float angle); const style::SymbolLayoutProperties::PossiblyEvaluated layout; const bool sdfIcons; const bool iconsNeedLinear; + const bool sortFeaturesByY; + + optional<float> sortedAngle; + + bool staticUploaded = false; + bool placementChangesUploaded = false; + bool dynamicUploaded = false; + bool sortUploaded = false; + + std::vector<SymbolInstance> symbolInstances; std::map<std::string, std::pair< SymbolIconProgram::PaintPropertyBinders, @@ -61,12 +82,14 @@ public: struct TextBuffer { gl::VertexVector<SymbolLayoutVertex> vertices; gl::VertexVector<SymbolDynamicLayoutAttributes::Vertex> dynamicVertices; + gl::VertexVector<SymbolOpacityAttributes::Vertex> opacityVertices; gl::IndexVector<gl::Triangles> triangles; SegmentVector<SymbolTextAttributes> segments; std::vector<PlacedSymbol> placedSymbols; optional<gl::VertexBuffer<SymbolLayoutVertex>> vertexBuffer; optional<gl::VertexBuffer<SymbolDynamicLayoutAttributes::Vertex>> dynamicVertexBuffer; + optional<gl::VertexBuffer<SymbolOpacityAttributes::Vertex>> opacityVertexBuffer; optional<gl::IndexBuffer<gl::Triangles>> indexBuffer; } text; @@ -75,6 +98,7 @@ public: struct IconBuffer { gl::VertexVector<SymbolLayoutVertex> vertices; gl::VertexVector<SymbolDynamicLayoutAttributes::Vertex> dynamicVertices; + gl::VertexVector<SymbolOpacityAttributes::Vertex> opacityVertices; gl::IndexVector<gl::Triangles> triangles; SegmentVector<SymbolIconAttributes> segments; std::vector<PlacedSymbol> placedSymbols; @@ -82,18 +106,31 @@ public: optional<gl::VertexBuffer<SymbolLayoutVertex>> vertexBuffer; optional<gl::VertexBuffer<SymbolDynamicLayoutAttributes::Vertex>> dynamicVertexBuffer; + optional<gl::VertexBuffer<SymbolOpacityAttributes::Vertex>> opacityVertexBuffer; optional<gl::IndexBuffer<gl::Triangles>> indexBuffer; } icon; - struct CollisionBoxBuffer { - gl::VertexVector<CollisionBoxVertex> vertices; - gl::IndexVector<gl::Lines> lines; - SegmentVector<CollisionBoxAttributes> segments; + struct CollisionBuffer { + gl::VertexVector<CollisionBoxLayoutAttributes::Vertex> vertices; + gl::VertexVector<CollisionBoxDynamicAttributes::Vertex> dynamicVertices; + SegmentVector<CollisionBoxProgram::Attributes> segments; - optional<gl::VertexBuffer<CollisionBoxVertex>> vertexBuffer; - optional<gl::VertexBuffer<SymbolDynamicLayoutAttributes::Vertex>> dynamicVertexBuffer; + optional<gl::VertexBuffer<CollisionBoxLayoutAttributes::Vertex>> vertexBuffer; + optional<gl::VertexBuffer<CollisionBoxDynamicAttributes::Vertex>> dynamicVertexBuffer; + }; + + struct CollisionBoxBuffer : public CollisionBuffer { + gl::IndexVector<gl::Lines> lines; optional<gl::IndexBuffer<gl::Lines>> indexBuffer; } collisionBox; + + struct CollisionCircleBuffer : public CollisionBuffer { + gl::IndexVector<gl::Triangles> triangles; + optional<gl::IndexBuffer<gl::Triangles>> indexBuffer; + } collisionCircle; + + uint32_t bucketInstanceId = 0; + bool justReloaded = false; }; } // namespace mbgl diff --git a/src/mbgl/renderer/frame_history.cpp b/src/mbgl/renderer/frame_history.cpp deleted file mode 100644 index de153b6963..0000000000 --- a/src/mbgl/renderer/frame_history.cpp +++ /dev/null @@ -1,81 +0,0 @@ -#include <mbgl/renderer/frame_history.hpp> -#include <mbgl/math/minmax.hpp> -#include <mbgl/gl/context.hpp> - -#include <cassert> - -namespace mbgl { - -FrameHistory::FrameHistory() { - changeOpacities.fill(0); - opacities.fill(0); -} - -void FrameHistory::record(const TimePoint& now, float zoom, const Duration& duration) { - - int16_t zoomIndex = std::floor(zoom * 10.0); - - if (firstFrame) { - changeTimes.fill(now); - - for (int16_t z = 0; z <= zoomIndex; z++) { - opacities.data[z] = 255u; - } - firstFrame = false; - } - - if (zoomIndex < previousZoomIndex) { - for (int16_t z = zoomIndex + 1; z <= previousZoomIndex; z++) { - changeTimes[z] = now; - changeOpacities[z] = opacities.data[z]; - } - } else { - for (int16_t z = zoomIndex; z > previousZoomIndex; z--) { - changeTimes[z] = now; - changeOpacities[z] = opacities.data[z]; - } - } - - for (int16_t z = 0; z <= 255; z++) { - const std::chrono::duration<float> timeDiff = now - changeTimes[z]; - const int32_t opacityChange = (duration == Milliseconds(0) ? 1 : (timeDiff / duration)) * 255; - const uint8_t opacity = z <= zoomIndex - ? util::min(255, changeOpacities[z] + opacityChange) - : util::max(0, changeOpacities[z] - opacityChange); - if (opacities.data[z] != opacity) { - opacities.data[z] = opacity; - dirty = true; - } - } - - if (zoomIndex != previousZoomIndex) { - previousZoomIndex = zoomIndex; - previousTime = now; - } - - time = now; -} - -bool FrameHistory::needsAnimation(const Duration& duration) const { - return (time - previousTime) < duration; -} - -void FrameHistory::upload(gl::Context& context, uint32_t unit) { - if (!texture) { - texture = context.createTexture(opacities, unit); - } else if (dirty) { - context.updateTexture(*texture, opacities, unit); - } - dirty = false; -} - -void FrameHistory::bind(gl::Context& context, uint32_t unit) { - upload(context, unit); - context.bindTexture(*texture, unit); -} - -bool FrameHistory::isVisible(const float zoom) const { - return opacities.data[std::floor(zoom * 10)] != 0; -} - -} // namespace mbgl diff --git a/src/mbgl/renderer/frame_history.hpp b/src/mbgl/renderer/frame_history.hpp deleted file mode 100644 index 75a8b60a71..0000000000 --- a/src/mbgl/renderer/frame_history.hpp +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include <array> - -#include <mbgl/util/platform.hpp> -#include <mbgl/gl/texture.hpp> -#include <mbgl/util/chrono.hpp> -#include <mbgl/util/image.hpp> -#include <mbgl/util/optional.hpp> - -namespace mbgl { - -namespace gl { -class Context; -} // namespace gl - -class FrameHistory { -public: - FrameHistory(); - void record(const TimePoint&, float zoom, const Duration&); - - bool needsAnimation(const Duration&) const; - void bind(gl::Context&, uint32_t); - void upload(gl::Context&, uint32_t); - bool isVisible(const float zoom) const; - -private: - std::array<TimePoint, 256> changeTimes; - std::array<uint8_t, 256> changeOpacities; - AlphaImage opacities{ { 256, 1 } }; - - int16_t previousZoomIndex = 0; - TimePoint previousTime; - TimePoint time; - bool firstFrame = true; - bool dirty = true; - - mbgl::optional<gl::Texture> texture; -}; - -} // namespace mbgl diff --git a/src/mbgl/renderer/layers/render_background_layer.cpp b/src/mbgl/renderer/layers/render_background_layer.cpp index 9fddba3f74..aebc4cc9aa 100644 --- a/src/mbgl/renderer/layers/render_background_layer.cpp +++ b/src/mbgl/renderer/layers/render_background_layer.cpp @@ -5,7 +5,7 @@ #include <mbgl/renderer/image_manager.hpp> #include <mbgl/renderer/render_static_data.hpp> #include <mbgl/programs/programs.hpp> -#include <mbgl/programs/fill_program.hpp> +#include <mbgl/programs/background_program.hpp> #include <mbgl/util/tile_cover.hpp> namespace mbgl { @@ -46,12 +46,8 @@ void RenderBackgroundLayer::render(PaintParameters& parameters, RenderSource*) { // Note that for bottommost layers without a pattern, the background color is drawn with // glClear rather than this method. - style::FillPaintProperties::PossiblyEvaluated properties; - properties.get<FillPattern>() = evaluated.get<BackgroundPattern>(); - properties.get<FillOpacity>() = { evaluated.get<BackgroundOpacity>() }; - properties.get<FillColor>() = { evaluated.get<BackgroundColor>() }; - - const FillProgram::PaintPropertyBinders paintAttibuteData(properties, 0); + const Properties<>::PossiblyEvaluated properties; + const BackgroundProgram::PaintPropertyBinders paintAttributeData(properties, 0); if (!evaluated.get<BackgroundPattern>().to.empty()) { optional<ImagePosition> imagePosA = parameters.imageManager.getPattern(evaluated.get<BackgroundPattern>().from); @@ -63,15 +59,15 @@ void RenderBackgroundLayer::render(PaintParameters& parameters, RenderSource*) { parameters.imageManager.bind(parameters.context, 0); for (const auto& tileID : util::tileCover(parameters.state, parameters.state.getIntegerZoom())) { - parameters.programs.fillPattern.get(properties).draw( + parameters.programs.backgroundPattern.draw( parameters.context, gl::Triangles(), parameters.depthModeForSublayer(0, gl::DepthMode::ReadOnly), gl::StencilMode::disabled(), parameters.colorModeForRenderPass(), - FillPatternUniforms::values( + BackgroundPatternUniforms::values( parameters.matrixForTile(tileID), - parameters.context.viewport.getCurrentValue().size, + evaluated.get<BackgroundOpacity>(), parameters.imageManager.getPixelSize(), *imagePosA, *imagePosB, @@ -82,7 +78,7 @@ void RenderBackgroundLayer::render(PaintParameters& parameters, RenderSource*) { parameters.staticData.tileVertexBuffer, parameters.staticData.quadTriangleIndexBuffer, parameters.staticData.tileTriangleSegments, - paintAttibuteData, + paintAttributeData, properties, parameters.state.getZoom(), getID() @@ -90,20 +86,21 @@ void RenderBackgroundLayer::render(PaintParameters& parameters, RenderSource*) { } } else { for (const auto& tileID : util::tileCover(parameters.state, parameters.state.getIntegerZoom())) { - parameters.programs.fill.get(properties).draw( + parameters.programs.background.draw( parameters.context, gl::Triangles(), parameters.depthModeForSublayer(0, gl::DepthMode::ReadOnly), gl::StencilMode::disabled(), parameters.colorModeForRenderPass(), - FillProgram::UniformValues { + BackgroundProgram::UniformValues { uniforms::u_matrix::Value{ parameters.matrixForTile(tileID) }, - uniforms::u_world::Value{ parameters.context.viewport.getCurrentValue().size }, + uniforms::u_color::Value{ evaluated.get<BackgroundColor>() }, + uniforms::u_opacity::Value{ evaluated.get<BackgroundOpacity>() }, }, parameters.staticData.tileVertexBuffer, parameters.staticData.quadTriangleIndexBuffer, parameters.staticData.tileTriangleSegments, - paintAttibuteData, + paintAttributeData, properties, parameters.state.getZoom(), getID() diff --git a/src/mbgl/renderer/layers/render_circle_layer.cpp b/src/mbgl/renderer/layers/render_circle_layer.cpp index e7b022f3ee..6092ff5452 100644 --- a/src/mbgl/renderer/layers/render_circle_layer.cpp +++ b/src/mbgl/renderer/layers/render_circle_layer.cpp @@ -63,7 +63,7 @@ void RenderCircleLayer::render(PaintParameters& parameters, RenderSource*) { parameters.context, gl::Triangles(), parameters.depthModeForSublayer(0, gl::DepthMode::ReadOnly), - parameters.mapMode == MapMode::Still + parameters.mapMode != MapMode::Continuous ? parameters.stencilModeForClipping(tile.clip) : gl::StencilMode::disabled(), parameters.colorModeForRenderPass(), @@ -108,13 +108,12 @@ bool RenderCircleLayer::queryIntersectsFeature( bearing, pixelsToTileUnits); - // Evaluate function - auto circleRadius = evaluated.get<style::CircleRadius>() - .evaluate(feature, zoom, style::CircleRadius::defaultValue()) - * pixelsToTileUnits; + // Evaluate functions + auto radius = evaluated.evaluate<style::CircleRadius>(zoom, feature) * pixelsToTileUnits; + auto stroke = evaluated.evaluate<style::CircleStrokeWidth>(zoom, feature) * pixelsToTileUnits; // Test intersection - return util::polygonIntersectsBufferedMultiPoint(translatedQueryGeometry.value_or(queryGeometry), feature.getGeometries(), circleRadius); + return util::polygonIntersectsBufferedMultiPoint(translatedQueryGeometry.value_or(queryGeometry), feature.getGeometries(), radius + stroke); } } // namespace mbgl diff --git a/src/mbgl/renderer/layers/render_hillshade_layer.cpp b/src/mbgl/renderer/layers/render_hillshade_layer.cpp new file mode 100644 index 0000000000..55702849df --- /dev/null +++ b/src/mbgl/renderer/layers/render_hillshade_layer.cpp @@ -0,0 +1,158 @@ +#include <mbgl/renderer/layers/render_hillshade_layer.hpp> +#include <mbgl/renderer/buckets/hillshade_bucket.hpp> +#include <mbgl/renderer/render_tile.hpp> +#include <mbgl/renderer/paint_parameters.hpp> +#include <mbgl/renderer/render_static_data.hpp> +#include <mbgl/programs/programs.hpp> +#include <mbgl/programs/hillshade_program.hpp> +#include <mbgl/programs/hillshade_prepare_program.hpp> +#include <mbgl/tile/tile.hpp> +#include <mbgl/style/layers/hillshade_layer_impl.hpp> +#include <mbgl/util/geo.hpp> +#include <mbgl/util/offscreen_texture.hpp> + +namespace mbgl { + +using namespace style; +RenderHillshadeLayer::RenderHillshadeLayer(Immutable<style::HillshadeLayer::Impl> _impl) + : RenderLayer(style::LayerType::Hillshade, _impl), + unevaluated(impl().paint.untransitioned()) { +} + +const style::HillshadeLayer::Impl& RenderHillshadeLayer::impl() const { + return static_cast<const style::HillshadeLayer::Impl&>(*baseImpl); +} + +std::unique_ptr<Bucket> RenderHillshadeLayer::createBucket(const BucketParameters&, const std::vector<const RenderLayer*>&) const { + assert(false); + return nullptr; +} + +const std::array<float, 2> RenderHillshadeLayer::getLatRange(const UnwrappedTileID& id) { + const LatLng latlng0 = LatLng(id); + const LatLng latlng1 = LatLng(UnwrappedTileID(id.canonical.z, id.canonical.x, id.canonical.y + 1)); + return {{ (float)latlng0.latitude(), (float)latlng1.latitude() }}; +} + +const std::array<float, 2> RenderHillshadeLayer::getLight(const PaintParameters& parameters){ + float azimuthal = evaluated.get<HillshadeIlluminationDirection>() * util::DEG2RAD; + if (evaluated.get<HillshadeIlluminationAnchor>() == HillshadeIlluminationAnchorType::Viewport) azimuthal = azimuthal - parameters.state.getAngle(); + return {{evaluated.get<HillshadeExaggeration>(), azimuthal}}; +} + +void RenderHillshadeLayer::transition(const TransitionParameters& parameters) { + unevaluated = impl().paint.transitioned(parameters, std::move(unevaluated)); +} + +void RenderHillshadeLayer::evaluate(const PropertyEvaluationParameters& parameters) { + evaluated = unevaluated.evaluate(parameters); + passes = (evaluated.get<style::HillshadeExaggeration >() > 0) + ? (RenderPass::Translucent | RenderPass::Pass3D) + : RenderPass::None; +} + +bool RenderHillshadeLayer::hasTransition() const { + return unevaluated.hasTransition(); +} + +void RenderHillshadeLayer::render(PaintParameters& parameters, RenderSource*) { + if (parameters.pass != RenderPass::Translucent && parameters.pass != RenderPass::Pass3D) + return; + + auto draw = [&] (const mat4& matrix, + const auto& vertexBuffer, + const auto& indexBuffer, + const auto& segments, + const UnwrappedTileID& id) { + parameters.programs.hillshade.draw( + parameters.context, + gl::Triangles(), + parameters.depthModeForSublayer(0, gl::DepthMode::ReadOnly), + gl::StencilMode::disabled(), + parameters.colorModeForRenderPass(), + HillshadeProgram::UniformValues { + uniforms::u_matrix::Value{ matrix }, + uniforms::u_image::Value{ 0 }, + uniforms::u_highlight::Value{ evaluated.get<HillshadeHighlightColor>() }, + uniforms::u_shadow::Value{ evaluated.get<HillshadeShadowColor>() }, + uniforms::u_accent::Value{ evaluated.get<HillshadeAccentColor>() }, + uniforms::u_light::Value{ getLight(parameters) }, + uniforms::u_latrange::Value{ getLatRange(id) }, + }, + vertexBuffer, + indexBuffer, + segments, + HillshadeProgram::PaintPropertyBinders { evaluated, 0 }, + evaluated, + parameters.state.getZoom(), + getID() + ); + }; + + mat4 mat; + matrix::ortho(mat, 0, util::EXTENT, -util::EXTENT, 0, 0, 1); + matrix::translate(mat, mat, 0, -util::EXTENT, 0); + + for (const RenderTile& tile : renderTiles) { + assert(dynamic_cast<HillshadeBucket*>(tile.tile.getBucket(*baseImpl))); + HillshadeBucket& bucket = *reinterpret_cast<HillshadeBucket*>(tile.tile.getBucket(*baseImpl)); + if (!bucket.hasData()){ + continue; + } + + if (!bucket.isPrepared() && parameters.pass == RenderPass::Pass3D) { + const uint16_t tilesize = bucket.getDEMData().dim; + OffscreenTexture view(parameters.context, { tilesize, tilesize }); + view.bind(); + + parameters.context.bindTexture(*bucket.dem, 0, gl::TextureFilter::Nearest, gl::TextureMipMap::No, gl::TextureWrap::Clamp, gl::TextureWrap::Clamp); + const Properties<>::PossiblyEvaluated properties; + + parameters.programs.hillshadePrepare.draw( + parameters.context, + gl::Triangles(), + parameters.depthModeForSublayer(0, gl::DepthMode::ReadOnly), + gl::StencilMode::disabled(), + parameters.colorModeForRenderPass(), + HillshadePrepareProgram::UniformValues { + uniforms::u_matrix::Value { mat }, + uniforms::u_dimension::Value { {{uint16_t(tilesize * 2), uint16_t(tilesize * 2) }} }, + uniforms::u_zoom::Value{ float(tile.id.canonical.z) }, + uniforms::u_image::Value{ 0 } + }, + parameters.staticData.rasterVertexBuffer, + parameters.staticData.quadTriangleIndexBuffer, + parameters.staticData.rasterSegments, + HillshadePrepareProgram::PaintPropertyBinders { properties, 0 }, + properties, + parameters.state.getZoom(), + getID() + ); + bucket.texture = std::move(view.getTexture()); + bucket.setPrepared(true); + } else if (parameters.pass == RenderPass::Translucent) { + assert(bucket.texture); + parameters.context.bindTexture(*bucket.texture, 0, gl::TextureFilter::Linear, gl::TextureMipMap::No, gl::TextureWrap::Clamp, gl::TextureWrap::Clamp); + + if (bucket.vertexBuffer && bucket.indexBuffer && !bucket.segments.empty()) { + // Draw only the parts of the tile that aren't drawn by another tile in the layer. + draw(parameters.matrixForTile(tile.id, true), + *bucket.vertexBuffer, + *bucket.indexBuffer, + bucket.segments, + tile.id); + } else { + // Draw the full tile. + draw(parameters.matrixForTile(tile.id, true), + parameters.staticData.rasterVertexBuffer, + parameters.staticData.quadTriangleIndexBuffer, + parameters.staticData.rasterSegments, + tile.id); + } + } + + + } +} + +} // namespace mbgl diff --git a/src/mbgl/renderer/layers/render_hillshade_layer.hpp b/src/mbgl/renderer/layers/render_hillshade_layer.hpp new file mode 100644 index 0000000000..e9b9db1ec3 --- /dev/null +++ b/src/mbgl/renderer/layers/render_hillshade_layer.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include <mbgl/renderer/render_layer.hpp> +#include <mbgl/style/layers/hillshade_layer_impl.hpp> +#include <mbgl/style/layers/hillshade_layer_properties.hpp> +#include <mbgl/tile/tile_id.hpp> + +namespace mbgl { + +class RenderHillshadeLayer: public RenderLayer { +public: + RenderHillshadeLayer(Immutable<style::HillshadeLayer::Impl>); + ~RenderHillshadeLayer() final = default; + + void transition(const TransitionParameters&) override; + void evaluate(const PropertyEvaluationParameters&) override; + bool hasTransition() const override; + + void render(PaintParameters&, RenderSource*) override; + + std::unique_ptr<Bucket> createBucket(const BucketParameters&, const std::vector<const RenderLayer*>&) const override; + + // Paint properties + style::HillshadePaintProperties::Unevaluated unevaluated; + style::HillshadePaintProperties::PossiblyEvaluated evaluated; + + const style::HillshadeLayer::Impl& impl() const; +private: + const std::array<float, 2> getLatRange(const UnwrappedTileID& id); + const std::array<float, 2> getLight(const PaintParameters& parameters); +}; + +template <> +inline bool RenderLayer::is<RenderHillshadeLayer>() const { + return type == style::LayerType::Hillshade; +} + +} // namespace mbgl diff --git a/src/mbgl/renderer/layers/render_raster_layer.cpp b/src/mbgl/renderer/layers/render_raster_layer.cpp index 06616d90e5..b41b2ac560 100644 --- a/src/mbgl/renderer/layers/render_raster_layer.cpp +++ b/src/mbgl/renderer/layers/render_raster_layer.cpp @@ -137,13 +137,13 @@ void RenderRasterLayer::render(PaintParameters& parameters, RenderSource* source if (bucket.vertexBuffer && bucket.indexBuffer && !bucket.segments.empty()) { // Draw only the parts of the tile that aren't drawn by another tile in the layer. - draw(tile.matrix, + draw(parameters.matrixForTile(tile.id, true), *bucket.vertexBuffer, *bucket.indexBuffer, bucket.segments); } else { // Draw the full tile. - draw(tile.matrix, + draw(parameters.matrixForTile(tile.id, true), parameters.staticData.rasterVertexBuffer, parameters.staticData.quadTriangleIndexBuffer, parameters.staticData.rasterSegments); diff --git a/src/mbgl/renderer/layers/render_symbol_layer.cpp b/src/mbgl/renderer/layers/render_symbol_layer.cpp index 1376e8a3d8..9e493003c0 100644 --- a/src/mbgl/renderer/layers/render_symbol_layer.cpp +++ b/src/mbgl/renderer/layers/render_symbol_layer.cpp @@ -4,7 +4,6 @@ #include <mbgl/renderer/property_evaluation_parameters.hpp> #include <mbgl/renderer/render_tile.hpp> #include <mbgl/renderer/paint_parameters.hpp> -#include <mbgl/renderer/frame_history.hpp> #include <mbgl/text/glyph_atlas.hpp> #include <mbgl/programs/programs.hpp> #include <mbgl/programs/symbol_program.hpp> @@ -81,8 +80,6 @@ void RenderSymbolLayer::render(PaintParameters& parameters, RenderSource*) { const auto& layout = bucket.layout; - parameters.frameHistory.bind(parameters.context, 1); - auto draw = [&] (auto& program, auto&& uniformValues, const auto& buffers, @@ -91,22 +88,18 @@ void RenderSymbolLayer::render(PaintParameters& parameters, RenderSource*) { const auto& binders, const auto& paintProperties) { - // We clip symbols to their tile extent in still mode. - const bool needsClipping = parameters.mapMode == MapMode::Still; - program.get(paintProperties).draw( parameters.context, gl::Triangles(), values_.pitchAlignment == AlignmentType::Map ? parameters.depthModeForSublayer(0, gl::DepthMode::ReadOnly) : gl::DepthMode::disabled(), - needsClipping - ? parameters.stencilModeForClipping(tile.clip) - : gl::StencilMode::disabled(), + gl::StencilMode::disabled(), parameters.colorModeForRenderPass(), std::move(uniformValues), *buffers.vertexBuffer, *buffers.dynamicVertexBuffer, + *buffers.opacityVertexBuffer, *symbolSizeBinder, *buffers.indexBuffer, buffers.segments, @@ -134,8 +127,7 @@ void RenderSymbolLayer::render(PaintParameters& parameters, RenderSource*) { values, tile, *bucket.iconSizeBinder, - parameters.state, - parameters.frameHistory); + parameters.state); parameters.context.updateVertexBuffer(*bucket.icon.dynamicVertexBuffer, std::move(bucket.icon.dynamicVertices)); } @@ -152,7 +144,7 @@ void RenderSymbolLayer::render(PaintParameters& parameters, RenderSource*) { if (bucket.sdfIcons) { if (values.hasHalo) { draw(parameters.programs.symbolIconSDF, - SymbolSDFIconProgram::uniformValues(false, values, texsize, parameters.pixelsToGLUnits, alongLine, tile, parameters.state, SymbolSDFPart::Halo), + SymbolSDFIconProgram::uniformValues(false, values, texsize, parameters.pixelsToGLUnits, alongLine, tile, parameters.state, parameters.symbolFadeChange, SymbolSDFPart::Halo), bucket.icon, bucket.iconSizeBinder, values, @@ -162,7 +154,7 @@ void RenderSymbolLayer::render(PaintParameters& parameters, RenderSource*) { if (values.hasFill) { draw(parameters.programs.symbolIconSDF, - SymbolSDFIconProgram::uniformValues(false, values, texsize, parameters.pixelsToGLUnits, alongLine, tile, parameters.state, SymbolSDFPart::Fill), + SymbolSDFIconProgram::uniformValues(false, values, texsize, parameters.pixelsToGLUnits, alongLine, tile, parameters.state, parameters.symbolFadeChange, SymbolSDFPart::Fill), bucket.icon, bucket.iconSizeBinder, values, @@ -171,7 +163,7 @@ void RenderSymbolLayer::render(PaintParameters& parameters, RenderSource*) { } } else { draw(parameters.programs.symbolIcon, - SymbolIconProgram::uniformValues(false, values, texsize, parameters.pixelsToGLUnits, alongLine, tile, parameters.state), + SymbolIconProgram::uniformValues(false, values, texsize, parameters.pixelsToGLUnits, alongLine, tile, parameters.state, parameters.symbolFadeChange), bucket.icon, bucket.iconSizeBinder, values, @@ -196,8 +188,7 @@ void RenderSymbolLayer::render(PaintParameters& parameters, RenderSource*) { values, tile, *bucket.textSizeBinder, - parameters.state, - parameters.frameHistory); + parameters.state); parameters.context.updateVertexBuffer(*bucket.text.dynamicVertexBuffer, std::move(bucket.text.dynamicVertices)); } @@ -206,7 +197,7 @@ void RenderSymbolLayer::render(PaintParameters& parameters, RenderSource*) { if (values.hasHalo) { draw(parameters.programs.symbolGlyph, - SymbolSDFTextProgram::uniformValues(true, values, texsize, parameters.pixelsToGLUnits, alongLine, tile, parameters.state, SymbolSDFPart::Halo), + SymbolSDFTextProgram::uniformValues(true, values, texsize, parameters.pixelsToGLUnits, alongLine, tile, parameters.state, parameters.symbolFadeChange, SymbolSDFPart::Halo), bucket.text, bucket.textSizeBinder, values, @@ -216,7 +207,7 @@ void RenderSymbolLayer::render(PaintParameters& parameters, RenderSource*) { if (values.hasFill) { draw(parameters.programs.symbolGlyph, - SymbolSDFTextProgram::uniformValues(true, values, texsize, parameters.pixelsToGLUnits, alongLine, tile, parameters.state, SymbolSDFPart::Fill), + SymbolSDFTextProgram::uniformValues(true, values, texsize, parameters.pixelsToGLUnits, alongLine, tile, parameters.state, parameters.symbolFadeChange, SymbolSDFPart::Fill), bucket.text, bucket.textSizeBinder, values, @@ -229,23 +220,27 @@ void RenderSymbolLayer::render(PaintParameters& parameters, RenderSource*) { static const style::Properties<>::PossiblyEvaluated properties {}; static const CollisionBoxProgram::PaintPropertyBinders paintAttributeData(properties, 0); + auto pixelRatio = tile.id.pixelsToTileUnits(1, parameters.state.getZoom()); + auto scale = std::pow(2.0f, float(parameters.state.getZoom() - tile.tile.id.overscaledZ)); + std::array<float,2> extrudeScale = + {{ + parameters.pixelsToGLUnits[0] / (pixelRatio * scale), + parameters.pixelsToGLUnits[1] / (pixelRatio * scale) + + }}; parameters.programs.collisionBox.draw( parameters.context, gl::Lines { 1.0f }, gl::DepthMode::disabled(), - parameters.stencilModeForClipping(tile.clip), + gl::StencilMode::disabled(), parameters.colorModeForRenderPass(), CollisionBoxProgram::UniformValues { uniforms::u_matrix::Value{ tile.matrix }, - uniforms::u_scale::Value{ std::pow(2.0f, float(parameters.state.getZoom() - tile.tile.id.overscaledZ)) }, - uniforms::u_zoom::Value{ float(parameters.state.getZoom() * 10) }, - uniforms::u_maxzoom::Value{ float((tile.id.canonical.z + 1) * 10) }, - uniforms::u_collision_y_stretch::Value{ tile.tile.yStretch() }, - uniforms::u_camera_to_center_distance::Value{ parameters.state.getCameraToCenterDistance() }, - uniforms::u_pitch::Value{ parameters.state.getPitch() }, - uniforms::u_fadetexture::Value{ 1 } + uniforms::u_extrude_scale::Value{ extrudeScale }, + uniforms::u_camera_to_center_distance::Value{ parameters.state.getCameraToCenterDistance() } }, *bucket.collisionBox.vertexBuffer, + *bucket.collisionBox.dynamicVertexBuffer, *bucket.collisionBox.indexBuffer, bucket.collisionBox.segments, paintAttributeData, @@ -254,6 +249,42 @@ void RenderSymbolLayer::render(PaintParameters& parameters, RenderSource*) { getID() ); } + if (bucket.hasCollisionCircleData()) { + static const style::Properties<>::PossiblyEvaluated properties {}; + static const CollisionBoxProgram::PaintPropertyBinders paintAttributeData(properties, 0); + + auto pixelRatio = tile.id.pixelsToTileUnits(1, parameters.state.getZoom()); + auto scale = std::pow(2.0f, float(parameters.state.getZoom() - tile.tile.id.overscaledZ)); + std::array<float,2> extrudeScale = + {{ + parameters.pixelsToGLUnits[0] / (pixelRatio * scale), + parameters.pixelsToGLUnits[1] / (pixelRatio * scale) + + }}; + + parameters.programs.collisionCircle.draw( + parameters.context, + gl::Triangles(), + gl::DepthMode::disabled(), + gl::StencilMode::disabled(), + parameters.colorModeForRenderPass(), + CollisionCircleProgram::UniformValues { + uniforms::u_matrix::Value{ tile.matrix }, + uniforms::u_extrude_scale::Value{ extrudeScale }, + uniforms::u_overscale_factor::Value{ float(tile.tile.id.overscaleFactor()) }, + uniforms::u_camera_to_center_distance::Value{ parameters.state.getCameraToCenterDistance() } + }, + *bucket.collisionCircle.vertexBuffer, + *bucket.collisionCircle.dynamicVertexBuffer, + *bucket.collisionCircle.indexBuffer, + bucket.collisionCircle.segments, + paintAttributeData, + properties, + parameters.state.getZoom(), + getID() + ); + + } } } diff --git a/src/mbgl/renderer/paint_parameters.cpp b/src/mbgl/renderer/paint_parameters.cpp index 299db844bc..a7f621eb61 100644 --- a/src/mbgl/renderer/paint_parameters.cpp +++ b/src/mbgl/renderer/paint_parameters.cpp @@ -12,7 +12,6 @@ PaintParameters::PaintParameters(gl::Context& context_, const UpdateParameters& updateParameters, const EvaluatedLight& evaluatedLight_, RenderStaticData& staticData_, - FrameHistory& frameHistory_, ImageManager& imageManager_, LineAtlas& lineAtlas_) : context(context_), @@ -20,7 +19,6 @@ PaintParameters::PaintParameters(gl::Context& context_, state(updateParameters.transformState), evaluatedLight(evaluatedLight_), staticData(staticData_), - frameHistory(frameHistory_), imageManager(imageManager_), lineAtlas(lineAtlas_), mapMode(updateParameters.mode), @@ -37,6 +35,10 @@ PaintParameters::PaintParameters(gl::Context& context_, // Update the default matrices to the current viewport dimensions. state.getProjMatrix(projMatrix); + // Also compute a projection matrix that aligns with the current pixel grid, taking into account + // odd viewport sizes. + state.getProjMatrix(alignedProjMatrix, 1, true); + // Calculate a second projection matrix with the near plane clipped to 100 so as // not to waste lots of depth buffer precision on very close empty space, for layer // types (fill-extrusion) that use the depth buffer to emulate real-world space. @@ -49,10 +51,10 @@ PaintParameters::PaintParameters(gl::Context& context_, } } -mat4 PaintParameters::matrixForTile(const UnwrappedTileID& tileID) { +mat4 PaintParameters::matrixForTile(const UnwrappedTileID& tileID, bool aligned) const { mat4 matrix; state.matrixFor(matrix, tileID); - matrix::multiply(matrix, projMatrix, matrix); + matrix::multiply(matrix, aligned ? alignedProjMatrix : projMatrix, matrix); return matrix; } diff --git a/src/mbgl/renderer/paint_parameters.hpp b/src/mbgl/renderer/paint_parameters.hpp index 4a2c2c6f12..41f46ae34e 100644 --- a/src/mbgl/renderer/paint_parameters.hpp +++ b/src/mbgl/renderer/paint_parameters.hpp @@ -2,6 +2,7 @@ #include <mbgl/renderer/render_pass.hpp> #include <mbgl/renderer/render_light.hpp> +#include <mbgl/renderer/mode.hpp> #include <mbgl/map/mode.hpp> #include <mbgl/gl/depth_mode.hpp> #include <mbgl/gl/stencil_mode.hpp> @@ -16,7 +17,6 @@ namespace mbgl { class RendererBackend; class UpdateParameters; class RenderStaticData; -class FrameHistory; class Programs; class TransformState; class ImageManager; @@ -32,7 +32,6 @@ public: const UpdateParameters&, const EvaluatedLight&, RenderStaticData&, - FrameHistory&, ImageManager&, LineAtlas&); @@ -43,7 +42,6 @@ public: const EvaluatedLight& evaluatedLight; RenderStaticData& staticData; - FrameHistory& frameHistory; ImageManager& imageManager; LineAtlas& lineAtlas; @@ -64,15 +62,18 @@ public: gl::StencilMode stencilModeForClipping(const ClipID&) const; gl::ColorMode colorModeForRenderPass() const; - mat4 matrixForTile(const UnwrappedTileID&); + mat4 matrixForTile(const UnwrappedTileID&, bool aligned = false) const; mat4 projMatrix; + mat4 alignedProjMatrix; mat4 nearClippedProjMatrix; int numSublayers = 3; uint32_t currentLayer; float depthRangeSize; const float depthEpsilon = 1.0f / (1 << 16); + + float symbolFadeChange; }; } // namespace mbgl diff --git a/src/mbgl/renderer/paint_property_binder.hpp b/src/mbgl/renderer/paint_property_binder.hpp index 652948c8df..3a49882f12 100644 --- a/src/mbgl/renderer/paint_property_binder.hpp +++ b/src/mbgl/renderer/paint_property_binder.hpp @@ -190,11 +190,11 @@ public: CompositeFunctionPaintPropertyBinder(style::CompositeFunction<T> function_, float zoom, T defaultValue_) : function(std::move(function_)), defaultValue(std::move(defaultValue_)), - rangeOfCoveringRanges(function.rangeOfCoveringRanges({zoom, zoom + 1})) { + zoomRange({zoom, zoom + 1}) { } void populateVertexVector(const GeometryTileFeature& feature, std::size_t length) override { - Range<T> range = function.evaluate(rangeOfCoveringRanges, feature, defaultValue); + Range<T> range = function.evaluate(zoomRange, feature, defaultValue); this->statistics.add(range.min); this->statistics.add(range.max); AttributeValue value = zoomInterpolatedAttributeValue( @@ -219,9 +219,9 @@ public: float interpolationFactor(float currentZoom) const override { if (function.useIntegerZoom) { - return util::interpolationFactor(1.0f, { rangeOfCoveringRanges.min.zoom, rangeOfCoveringRanges.max.zoom }, std::floor(currentZoom)); + return function.interpolationFactor(zoomRange, std::floor(currentZoom)); } else { - return util::interpolationFactor(1.0f, { rangeOfCoveringRanges.min.zoom, rangeOfCoveringRanges.max.zoom }, currentZoom); + return function.interpolationFactor(zoomRange, currentZoom); } } @@ -237,8 +237,7 @@ public: private: style::CompositeFunction<T> function; T defaultValue; - using CoveringRanges = typename style::CompositeFunction<T>::CoveringRanges; - Range<CoveringRanges> rangeOfCoveringRanges; + Range<float> zoomRange; gl::VertexVector<Vertex> vertexVector; optional<gl::VertexBuffer<Vertex>> vertexBuffer; }; diff --git a/src/mbgl/renderer/render_layer.cpp b/src/mbgl/renderer/render_layer.cpp index eb2b74ffe0..248905f834 100644 --- a/src/mbgl/renderer/render_layer.cpp +++ b/src/mbgl/renderer/render_layer.cpp @@ -4,6 +4,7 @@ #include <mbgl/renderer/layers/render_custom_layer.hpp> #include <mbgl/renderer/layers/render_fill_extrusion_layer.hpp> #include <mbgl/renderer/layers/render_fill_layer.hpp> +#include <mbgl/renderer/layers/render_hillshade_layer.hpp> #include <mbgl/renderer/layers/render_line_layer.hpp> #include <mbgl/renderer/layers/render_raster_layer.hpp> #include <mbgl/renderer/layers/render_symbol_layer.hpp> @@ -26,6 +27,8 @@ std::unique_ptr<RenderLayer> RenderLayer::create(Immutable<Layer::Impl> impl) { return std::make_unique<RenderSymbolLayer>(staticImmutableCast<SymbolLayer::Impl>(impl)); case LayerType::Raster: return std::make_unique<RenderRasterLayer>(staticImmutableCast<RasterLayer::Impl>(impl)); + case LayerType::Hillshade: + return std::make_unique<RenderHillshadeLayer>(staticImmutableCast<HillshadeLayer::Impl>(impl)); case LayerType::Background: return std::make_unique<RenderBackgroundLayer>(staticImmutableCast<BackgroundLayer::Impl>(impl)); case LayerType::Custom: diff --git a/src/mbgl/renderer/render_layer.hpp b/src/mbgl/renderer/render_layer.hpp index dfc6bcf2fd..55831cb72c 100644 --- a/src/mbgl/renderer/render_layer.hpp +++ b/src/mbgl/renderer/render_layer.hpp @@ -82,13 +82,16 @@ public: friend std::string layoutKey(const RenderLayer&); protected: + // renderTiles are exposed directly to CrossTileSymbolIndex and Placement so they + // can update opacities in the symbol buckets immediately before rendering + friend class CrossTileSymbolIndex; + friend class Placement; + // Stores current set of tiles to be rendered for this layer. + std::vector<std::reference_wrapper<RenderTile>> renderTiles; + // Stores what render passes this layer is currently enabled for. This depends on the // evaluated StyleProperties object and is updated accordingly. RenderPass passes = RenderPass::None; - - //Stores current set of tiles to be rendered for this layer. - std::vector<std::reference_wrapper<RenderTile>> renderTiles; - }; } // namespace mbgl diff --git a/src/mbgl/renderer/render_source.cpp b/src/mbgl/renderer/render_source.cpp index 7723a1c7ca..d160eb16e3 100644 --- a/src/mbgl/renderer/render_source.cpp +++ b/src/mbgl/renderer/render_source.cpp @@ -2,10 +2,12 @@ #include <mbgl/renderer/render_source_observer.hpp> #include <mbgl/renderer/sources/render_geojson_source.hpp> #include <mbgl/renderer/sources/render_raster_source.hpp> +#include <mbgl/renderer/sources/render_raster_dem_source.hpp> #include <mbgl/renderer/sources/render_vector_source.hpp> #include <mbgl/renderer/tile_parameters.hpp> #include <mbgl/annotation/render_annotation_source.hpp> #include <mbgl/renderer/sources/render_image_source.hpp> +#include <mbgl/renderer/sources/render_custom_geometry_source.hpp> #include <mbgl/tile/tile.hpp> namespace mbgl { @@ -18,6 +20,8 @@ std::unique_ptr<RenderSource> RenderSource::create(Immutable<Source::Impl> impl) return std::make_unique<RenderVectorSource>(staticImmutableCast<VectorSource::Impl>(impl)); case SourceType::Raster: return std::make_unique<RenderRasterSource>(staticImmutableCast<RasterSource::Impl>(impl)); + case SourceType::RasterDEM: + return std::make_unique<RenderRasterDEMSource>(staticImmutableCast<RasterSource::Impl>(impl)); case SourceType::GeoJSON: return std::make_unique<RenderGeoJSONSource>(staticImmutableCast<GeoJSONSource::Impl>(impl)); case SourceType::Video: @@ -27,6 +31,8 @@ std::unique_ptr<RenderSource> RenderSource::create(Immutable<Source::Impl> impl) return std::make_unique<RenderAnnotationSource>(staticImmutableCast<AnnotationSource::Impl>(impl)); case SourceType::Image: return std::make_unique<RenderImageSource>(staticImmutableCast<ImageSource::Impl>(impl)); + case SourceType::CustomVector: + return std::make_unique<RenderCustomGeometrySource>(staticImmutableCast<CustomGeometrySource::Impl>(impl)); } // Not reachable, but placate GCC. diff --git a/src/mbgl/renderer/render_source.hpp b/src/mbgl/renderer/render_source.hpp index 8293923ff6..db88230e53 100644 --- a/src/mbgl/renderer/render_source.hpp +++ b/src/mbgl/renderer/render_source.hpp @@ -24,6 +24,7 @@ class SourceQueryOptions; class Tile; class RenderSourceObserver; class TileParameters; +class CollisionIndex; class RenderSource : protected TileObserver { public: @@ -63,7 +64,8 @@ public: queryRenderedFeatures(const ScreenLineString& geometry, const TransformState& transformState, const std::vector<const RenderLayer*>& layers, - const RenderedQueryOptions& options) const = 0; + const RenderedQueryOptions& options, + const CollisionIndex& collisionIndex) const = 0; virtual std::vector<Feature> querySourceFeatures(const SourceQueryOptions&) const = 0; @@ -82,7 +84,7 @@ protected: bool enabled = false; - void onTileChanged(Tile&) final; + void onTileChanged(Tile&) override; void onTileError(Tile&, std::exception_ptr) final; }; diff --git a/src/mbgl/renderer/render_static_data.cpp b/src/mbgl/renderer/render_static_data.cpp index ccf239e643..0b3937ded0 100644 --- a/src/mbgl/renderer/render_static_data.cpp +++ b/src/mbgl/renderer/render_static_data.cpp @@ -3,12 +3,12 @@ namespace mbgl { -static gl::VertexVector<FillLayoutVertex> tileVertices() { - gl::VertexVector<FillLayoutVertex> result; - result.emplace_back(FillProgram::layoutVertex({ 0, 0 })); - result.emplace_back(FillProgram::layoutVertex({ util::EXTENT, 0 })); - result.emplace_back(FillProgram::layoutVertex({ 0, util::EXTENT })); - result.emplace_back(FillProgram::layoutVertex({ util::EXTENT, util::EXTENT })); +static gl::VertexVector<PositionOnlyLayoutAttributes::Vertex> tileVertices() { + gl::VertexVector<PositionOnlyLayoutAttributes::Vertex> result; + result.emplace_back(PositionOnlyLayoutAttributes::Vertex({{{ 0, 0 }}})); + result.emplace_back(PositionOnlyLayoutAttributes::Vertex({{{ util::EXTENT, 0 }}})); + result.emplace_back(PositionOnlyLayoutAttributes::Vertex({{{ 0, util::EXTENT }}})); + result.emplace_back(PositionOnlyLayoutAttributes::Vertex({{{ util::EXTENT, util::EXTENT }}})); return result; } diff --git a/src/mbgl/renderer/render_static_data.hpp b/src/mbgl/renderer/render_static_data.hpp index cf58c31f4d..c2b54f3815 100644 --- a/src/mbgl/renderer/render_static_data.hpp +++ b/src/mbgl/renderer/render_static_data.hpp @@ -13,14 +13,14 @@ class RenderStaticData { public: RenderStaticData(gl::Context&, float pixelRatio, const optional<std::string>& programCacheDir); - gl::VertexBuffer<FillLayoutVertex> tileVertexBuffer; + gl::VertexBuffer<PositionOnlyLayoutAttributes::Vertex> tileVertexBuffer; gl::VertexBuffer<RasterLayoutVertex> rasterVertexBuffer; gl::VertexBuffer<ExtrusionTextureLayoutVertex> extrusionTextureVertexBuffer; gl::IndexBuffer<gl::Triangles> quadTriangleIndexBuffer; gl::IndexBuffer<gl::LineStrip> tileBorderIndexBuffer; - SegmentVector<FillAttributes> tileTriangleSegments; + SegmentVector<BackgroundAttributes> tileTriangleSegments; SegmentVector<DebugAttributes> tileBorderSegments; SegmentVector<RasterAttributes> rasterSegments; SegmentVector<ExtrusionTextureAttributes> extrusionTextureSegments; diff --git a/src/mbgl/renderer/render_tile.cpp b/src/mbgl/renderer/render_tile.cpp index 8df31f8d7c..35b34833e4 100644 --- a/src/mbgl/renderer/render_tile.cpp +++ b/src/mbgl/renderer/render_tile.cpp @@ -72,7 +72,7 @@ void RenderTile::finishRender(PaintParameters& parameters) { return; static const style::Properties<>::PossiblyEvaluated properties {}; - static const DebugProgram::PaintPropertyBinders paintAttibuteData(properties, 0); + static const DebugProgram::PaintPropertyBinders paintAttributeData(properties, 0); if (parameters.debugOptions & (MapDebugOptions::Timestamps | MapDebugOptions::ParseStatus)) { if (!tile.debugBucket || tile.debugBucket->renderable != tile.isRenderable() || @@ -98,7 +98,7 @@ void RenderTile::finishRender(PaintParameters& parameters) { *tile.debugBucket->vertexBuffer, *tile.debugBucket->indexBuffer, tile.debugBucket->segments, - paintAttibuteData, + paintAttributeData, properties, parameters.state.getZoom(), "debug" @@ -117,7 +117,7 @@ void RenderTile::finishRender(PaintParameters& parameters) { *tile.debugBucket->vertexBuffer, *tile.debugBucket->indexBuffer, tile.debugBucket->segments, - paintAttibuteData, + paintAttributeData, properties, parameters.state.getZoom(), "debug" @@ -138,7 +138,7 @@ void RenderTile::finishRender(PaintParameters& parameters) { parameters.staticData.tileVertexBuffer, parameters.staticData.tileBorderIndexBuffer, parameters.staticData.tileBorderSegments, - paintAttibuteData, + paintAttributeData, properties, parameters.state.getZoom(), "debug" diff --git a/src/mbgl/renderer/renderer_backend.cpp b/src/mbgl/renderer/renderer_backend.cpp index 159ef432b3..22d263313c 100644 --- a/src/mbgl/renderer/renderer_backend.cpp +++ b/src/mbgl/renderer/renderer_backend.cpp @@ -16,7 +16,7 @@ gl::Context& RendererBackend::getContext() { context = std::make_unique<gl::Context>(); context->enableDebugging(); context->initializeExtensions( - std::bind(&RendererBackend::initializeExtension, this, std::placeholders::_1)); + std::bind(&RendererBackend::getExtensionFunctionPointer, this, std::placeholders::_1)); }); return *context; } diff --git a/src/mbgl/renderer/renderer_impl.cpp b/src/mbgl/renderer/renderer_impl.cpp index 3a7afdb03d..61e7d17242 100644 --- a/src/mbgl/renderer/renderer_impl.cpp +++ b/src/mbgl/renderer/renderer_impl.cpp @@ -14,6 +14,7 @@ #include <mbgl/renderer/layers/render_background_layer.hpp> #include <mbgl/renderer/layers/render_custom_layer.hpp> #include <mbgl/renderer/layers/render_fill_extrusion_layer.hpp> +#include <mbgl/renderer/layers/render_hillshade_layer.hpp> #include <mbgl/renderer/style_diff.hpp> #include <mbgl/renderer/query.hpp> #include <mbgl/renderer/backend_scope.hpp> @@ -57,7 +58,8 @@ Renderer::Impl::Impl(RendererBackend& backend_, , imageImpls(makeMutable<std::vector<Immutable<style::Image::Impl>>>()) , sourceImpls(makeMutable<std::vector<Immutable<style::Source::Impl>>>()) , layerImpls(makeMutable<std::vector<Immutable<style::Layer::Impl>>>()) - , renderLight(makeMutable<Light::Impl>()) { + , renderLight(makeMutable<Light::Impl>()) + , placement(std::make_unique<Placement>(TransformState{}, MapMode::Static)) { glyphManager->setObserver(this); } @@ -81,7 +83,7 @@ void Renderer::Impl::setObserver(RendererObserver* observer_) { } void Renderer::Impl::render(const UpdateParameters& updateParameters) { - if (updateParameters.mode == MapMode::Still) { + if (updateParameters.mode != MapMode::Continuous) { // Don't load/render anyting in still mode until explicitly requested. if (!updateParameters.stillImageRequest) { return; @@ -253,13 +255,12 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) { updateParameters, renderLight.getEvaluated(), *staticData, - frameHistory, *imageManager, *lineAtlas }; bool loaded = updateParameters.styleLoaded && isLoaded(); - if (updateParameters.mode == MapMode::Still && !loaded) { + if (updateParameters.mode != MapMode::Continuous && !loaded) { return; } @@ -289,7 +290,7 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) { RenderLayer* layer = getRenderLayer(layerImpl->id); assert(layer); - if (!parameters.staticData.has3D && layer->is<RenderFillExtrusionLayer>()) { + if (!parameters.staticData.has3D && (layer->is<RenderFillExtrusionLayer>() || layer->is<RenderHillshadeLayer>())) { parameters.staticData.has3D = true; } @@ -299,7 +300,9 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) { if (const RenderBackgroundLayer* background = layer->as<RenderBackgroundLayer>()) { const BackgroundPaintProperties::PossiblyEvaluated& paint = background->evaluated; - if (layerImpl.get() == layerImpls->at(0).get() && paint.get<BackgroundPattern>().from.empty()) { + if (parameters.contextMode == GLContextMode::Unique + && layerImpl.get() == layerImpls->at(0).get() + && paint.get<BackgroundPattern>().from.empty()) { // This is a solid background. We can use glClear(). backgroundColor = paint.get<BackgroundColor>() * paint.get<BackgroundOpacity>(); } else { @@ -333,11 +336,15 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) { auto par = util::rotate(pa, parameters.state.getAngle()); auto pbr = util::rotate(pb, parameters.state.getAngle()); - return std::tie(par.y, par.x) < std::tie(pbr.y, pbr.x); + return std::tie(b.id.canonical.z, par.y, par.x) < std::tie(a.id.canonical.z, pbr.y, pbr.x); }); } else { std::sort(sortedTiles.begin(), sortedTiles.end(), [](const auto& a, const auto& b) { return a.get().id < b.get().id; }); + // Don't render non-symbol layers for tiles that we're only holding on to for symbol fading + sortedTiles.erase(std::remove_if(sortedTiles.begin(), sortedTiles.end(), + [](const auto& tile) { return tile.get().tile.holdForFade(); }), + sortedTiles.end()); } std::vector<std::reference_wrapper<RenderTile>> sortedTilesForInsertion; @@ -347,35 +354,13 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) { continue; } - // We're not clipping symbol layers, so when we have both parents and children of symbol - // layers, we drop all children in favor of their parent to avoid duplicate labels. - // See https://github.com/mapbox/mapbox-gl-native/issues/2482 - if (symbolLayer) { - bool skip = false; - // Look back through the buckets we decided to render to find out whether there is - // already a bucket from this layer that is a parent of this tile. Tiles are ordered - // by zoom level when we obtain them from getTiles(). - for (auto it = sortedTilesForInsertion.rbegin(); - it != sortedTilesForInsertion.rend(); ++it) { - if (tile.tile.id.isChildOf(it->get().tile.id)) { - skip = true; - break; - } - } - if (skip) { - continue; - } - } - auto bucket = tile.tile.getBucket(*layer->baseImpl); if (bucket) { sortedTilesForInsertion.emplace_back(tile); tile.used = true; - // We only need clipping when we're _not_ drawing a symbol layer. The only exception - // for symbol layers is when we're rendering still images. See render_symbol_layer.cpp - // for the exception we make there. - if (!symbolLayer || parameters.mapMode == MapMode::Still) { + // We only need clipping when we're _not_ drawing a symbol layer. + if (!symbolLayer) { tile.needsClipping = true; } } @@ -384,9 +369,54 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) { order.emplace_back(RenderItem { *layer, source }); } - frameHistory.record(parameters.timePoint, - parameters.state.getZoom(), - parameters.mapMode == MapMode::Continuous ? util::DEFAULT_TRANSITION_DURATION : Milliseconds(0)); + bool symbolBucketsChanged = false; + if (parameters.mapMode != MapMode::Continuous) { + // TODO: Think about right way for symbol index to handle still rendering + crossTileSymbolIndex.reset(); + } + for (auto it = order.rbegin(); it != order.rend(); ++it) { + if (it->layer.is<RenderSymbolLayer>()) { + if (crossTileSymbolIndex.addLayer(*it->layer.as<RenderSymbolLayer>())) symbolBucketsChanged = true; + } + } + + bool placementChanged = false; + if (!placement->stillRecent(parameters.timePoint)) { + auto newPlacement = std::make_unique<Placement>(parameters.state, parameters.mapMode); + std::set<std::string> usedSymbolLayers; + for (auto it = order.rbegin(); it != order.rend(); ++it) { + if (it->layer.is<RenderSymbolLayer>()) { + usedSymbolLayers.insert(it->layer.getID()); + newPlacement->placeLayer(*it->layer.as<RenderSymbolLayer>(), parameters.projMatrix, parameters.debugOptions & MapDebugOptions::Collision); + } + } + + placementChanged = newPlacement->commit(*placement, parameters.timePoint); + // commitFeatureIndexes depends on the assumption that no new FeatureIndex has been loaded since placement + // started. If we violate this assumption, then we need to either make CollisionIndex completely independendent of + // FeatureIndex, or find a way for its entries to point to multiple FeatureIndexes. + commitFeatureIndexes(); + crossTileSymbolIndex.pruneUnusedLayers(usedSymbolLayers); + if (placementChanged || symbolBucketsChanged) { + placement = std::move(newPlacement); + } + + placement->setRecent(parameters.timePoint); + + updateFadingTiles(); + } else { + placement->setStale(); + } + + parameters.symbolFadeChange = placement->symbolFadeChange(parameters.timePoint); + + if (placementChanged || symbolBucketsChanged) { + for (auto it = order.rbegin(); it != order.rend(); ++it) { + if (it->layer.is<RenderSymbolLayer>()) { + placement->updateLayerOpacities(*it->layer.as<RenderSymbolLayer>()); + } + } + } // - UPLOAD PASS ------------------------------------------------------------------------------- // Uploads all required buffers and images before we do any actual rendering. @@ -395,8 +425,7 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) { parameters.imageManager.upload(parameters.context, 0); parameters.lineAtlas.upload(parameters.context, 0); - parameters.frameHistory.upload(parameters.context, 0); - + // Update all clipping IDs + upload buckets. for (const auto& entry : renderSources) { if (entry.second->isEnabled()) { @@ -435,13 +464,17 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) { // Renders the backdrop of the OpenGL view. This also paints in areas where we don't have any // tiles whatsoever. { + using namespace gl::value; + MBGL_DEBUG_GROUP(parameters.context, "clear"); parameters.backend.bind(); - parameters.context.clear((parameters.debugOptions & MapDebugOptions::Overdraw) - ? Color::black() - : backgroundColor, - 1.0f, - 0); + if (parameters.debugOptions & MapDebugOptions::Overdraw) { + parameters.context.clear(Color::black(), ClearDepth::Default, ClearStencil::Default); + } else if (parameters.contextMode == GLContextMode::Shared) { + parameters.context.clear({}, ClearDepth::Default, ClearStencil::Default); + } else { + parameters.context.clear(backgroundColor, ClearDepth::Default, ClearStencil::Default); + } } // - CLIPPING MASKS ---------------------------------------------------------------------------- @@ -449,11 +482,11 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) { { MBGL_DEBUG_GROUP(parameters.context, "clipping masks"); - static const style::FillPaintProperties::PossiblyEvaluated properties {}; - static const FillProgram::PaintPropertyBinders paintAttibuteData(properties, 0); + static const Properties<>::PossiblyEvaluated properties {}; + static const ClippingMaskProgram::PaintPropertyBinders paintAttributeData(properties, 0); for (const auto& clipID : parameters.clipIDGenerator.getClipIDs()) { - parameters.staticData.programs.fill.get(properties).draw( + parameters.staticData.programs.clippingMask.draw( parameters.context, gl::Triangles(), gl::DepthMode::disabled(), @@ -466,14 +499,13 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) { gl::StencilMode::Replace }, gl::ColorMode::disabled(), - FillProgram::UniformValues { + ClippingMaskProgram::UniformValues { uniforms::u_matrix::Value{ parameters.matrixForTile(clipID.first) }, - uniforms::u_world::Value{ parameters.context.viewport.getCurrentValue().size }, }, parameters.staticData.tileVertexBuffer, parameters.staticData.quadTriangleIndexBuffer, parameters.staticData.tileTriangleSegments, - paintAttibuteData, + paintAttributeData, properties, parameters.state.getZoom(), "clipping" @@ -602,7 +634,7 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) { observer->onDidFinishRenderingFrame( loaded ? RendererObserver::RenderMode::Full : RendererObserver::RenderMode::Partial, - updateParameters.mode == MapMode::Continuous && (hasTransitions() || frameHistory.needsAnimation(util::DEFAULT_TRANSITION_DURATION)) + updateParameters.mode == MapMode::Continuous && hasTransitions(parameters.timePoint) ); if (!loaded) { @@ -642,7 +674,7 @@ std::vector<Feature> Renderer::Impl::queryRenderedFeatures(const ScreenLineStrin std::unordered_map<std::string, std::vector<Feature>> resultsByLayer; for (const auto& sourceID : sourceIDs) { if (RenderSource* renderSource = getRenderSource(sourceID)) { - auto sourceResults = renderSource->queryRenderedFeatures(geometry, transformState, layers, options); + auto sourceResults = renderSource->queryRenderedFeatures(geometry, transformState, layers, options, placement->getCollisionIndex()); std::move(sourceResults.begin(), sourceResults.end(), std::inserter(resultsByLayer, resultsByLayer.begin())); } } @@ -722,7 +754,7 @@ RenderSource* Renderer::Impl::getRenderSource(const std::string& id) const { return it != renderSources.end() ? it->second.get() : nullptr; } -bool Renderer::Impl::hasTransitions() const { +bool Renderer::Impl::hasTransitions(TimePoint timePoint) const { if (renderLight.hasTransition()) { return true; } @@ -733,9 +765,39 @@ bool Renderer::Impl::hasTransitions() const { } } + if (placement->hasTransitions(timePoint)) { + return true; + } + + if (fadingTiles) { + return true; + } + return false; } +void Renderer::Impl::commitFeatureIndexes() { + for (auto& source : renderSources) { + for (auto& renderTile : source.second->getRenderTiles()) { + Tile& tile = renderTile.get().tile; + tile.commitFeatureIndex(); + } + } +} + +void Renderer::Impl::updateFadingTiles() { + fadingTiles = false; + for (auto& source : renderSources) { + for (auto& renderTile : source.second->getRenderTiles()) { + Tile& tile = renderTile.get().tile; + if (tile.holdForFade()) { + fadingTiles = true; + tile.performedFadePlacement(); + } + } + } +} + bool Renderer::Impl::isLoaded() const { for (const auto& entry: renderSources) { if (!entry.second->isLoaded()) { diff --git a/src/mbgl/renderer/renderer_impl.hpp b/src/mbgl/renderer/renderer_impl.hpp index a199cec4d0..4f45d514a5 100644 --- a/src/mbgl/renderer/renderer_impl.hpp +++ b/src/mbgl/renderer/renderer_impl.hpp @@ -1,16 +1,17 @@ #pragma once +#include <mbgl/renderer/mode.hpp> #include <mbgl/renderer/renderer.hpp> #include <mbgl/renderer/render_source_observer.hpp> #include <mbgl/renderer/render_light.hpp> -#include <mbgl/renderer/frame_history.hpp> #include <mbgl/style/image.hpp> #include <mbgl/style/source.hpp> #include <mbgl/style/layer.hpp> #include <mbgl/map/transform_state.hpp> #include <mbgl/map/zoom_history.hpp> -#include <mbgl/map/mode.hpp> +#include <mbgl/text/cross_tile_symbol_index.hpp> #include <mbgl/text/glyph_manager_observer.hpp> +#include <mbgl/text/placement.hpp> #include <memory> #include <string> @@ -31,6 +32,7 @@ class Scheduler; class GlyphManager; class ImageManager; class LineAtlas; +class CrossTileSymbolIndex; class Renderer::Impl : public GlyphManagerObserver, public RenderSourceObserver{ @@ -56,7 +58,7 @@ public: private: bool isLoaded() const; - bool hasTransitions() const; + bool hasTransitions(TimePoint) const; RenderSource* getRenderSource(const std::string& id) const; @@ -72,6 +74,9 @@ private: void onTileChanged(RenderSource&, const OverscaledTileID&) override; void onTileError(RenderSource&, const OverscaledTileID&, std::exception_ptr) override; + void commitFeatureIndexes(); + void updateFadingTiles(); + friend class Renderer; RendererBackend& backend; @@ -91,7 +96,6 @@ private: }; RenderState renderState = RenderState::Never; - FrameHistory frameHistory; ZoomHistory zoomHistory; TransformState transformState; @@ -108,7 +112,11 @@ private: std::unordered_map<std::string, std::unique_ptr<RenderLayer>> renderLayers; RenderLight renderLight; + CrossTileSymbolIndex crossTileSymbolIndex; + std::unique_ptr<Placement> placement; + bool contextLost = false; + bool fadingTiles = false; }; } // namespace mbgl diff --git a/src/mbgl/renderer/renderer_observer.hpp b/src/mbgl/renderer/renderer_observer.hpp deleted file mode 100644 index 551b5c803e..0000000000 --- a/src/mbgl/renderer/renderer_observer.hpp +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#include <exception> - -namespace mbgl { - -class RendererObserver { -public: - virtual ~RendererObserver() = default; - - enum class RenderMode : uint32_t { - Partial, - Full - }; - - // Signals that a repaint is required - virtual void onInvalidate() {} - - // Resource failed to download / parse - virtual void onResourceError(std::exception_ptr) {} - - // First frame - virtual void onWillStartRenderingMap() {} - - // Start of frame, initial is the first frame for this map - virtual void onWillStartRenderingFrame() {} - - // End of frame, boolean flags that a repaint is required - virtual void onDidFinishRenderingFrame(RenderMode, bool) {} - - // Final frame - virtual void onDidFinishRenderingMap() {} -}; - -} // namespace mbgl diff --git a/src/mbgl/renderer/sources/render_custom_geometry_source.cpp b/src/mbgl/renderer/sources/render_custom_geometry_source.cpp new file mode 100644 index 0000000000..df615a7e20 --- /dev/null +++ b/src/mbgl/renderer/sources/render_custom_geometry_source.cpp @@ -0,0 +1,87 @@ +#include <mbgl/renderer/sources/render_custom_geometry_source.hpp> +#include <mbgl/renderer/render_tile.hpp> +#include <mbgl/renderer/paint_parameters.hpp> +#include <mbgl/tile/custom_geometry_tile.hpp> + +#include <mbgl/algorithm/generate_clip_ids.hpp> +#include <mbgl/algorithm/generate_clip_ids_impl.hpp> + +namespace mbgl { + +using namespace style; + +RenderCustomGeometrySource::RenderCustomGeometrySource(Immutable<style::CustomGeometrySource::Impl> impl_) + : RenderSource(impl_) { + tilePyramid.setObserver(this); +} + +const style::CustomGeometrySource::Impl& RenderCustomGeometrySource::impl() const { + return static_cast<const style::CustomGeometrySource::Impl&>(*baseImpl); +} + +bool RenderCustomGeometrySource::isLoaded() const { + return tilePyramid.isLoaded(); +} + +void RenderCustomGeometrySource::update(Immutable<style::Source::Impl> baseImpl_, + const std::vector<Immutable<Layer::Impl>>& layers, + const bool needsRendering, + const bool needsRelayout, + const TileParameters& parameters) { + std::swap(baseImpl, baseImpl_); + + enabled = needsRendering; + + auto tileLoader = impl().getTileLoader(); + if (!tileLoader) { + return; + } + + tilePyramid.update(layers, + needsRendering, + needsRelayout, + parameters, + SourceType::CustomVector, + util::tileSize, + impl().getZoomRange(), + {}, + [&] (const OverscaledTileID& tileID) { + return std::make_unique<CustomGeometryTile>(tileID, impl().id, parameters, impl().getTileOptions(), *tileLoader); + }); +} + +void RenderCustomGeometrySource::startRender(PaintParameters& parameters) { + parameters.clipIDGenerator.update(tilePyramid.getRenderTiles()); + tilePyramid.startRender(parameters); +} + +void RenderCustomGeometrySource::finishRender(PaintParameters& parameters) { + tilePyramid.finishRender(parameters); +} + +std::vector<std::reference_wrapper<RenderTile>> RenderCustomGeometrySource::getRenderTiles() { + return tilePyramid.getRenderTiles(); +} + +std::unordered_map<std::string, std::vector<Feature>> +RenderCustomGeometrySource::queryRenderedFeatures(const ScreenLineString& geometry, + const TransformState& transformState, + const std::vector<const RenderLayer*>& layers, + const RenderedQueryOptions& options, + const CollisionIndex& collisionIndex) const { + return tilePyramid.queryRenderedFeatures(geometry, transformState, layers, options, collisionIndex); +} + +std::vector<Feature> RenderCustomGeometrySource::querySourceFeatures(const SourceQueryOptions& options) const { + return tilePyramid.querySourceFeatures(options); +} + +void RenderCustomGeometrySource::onLowMemory() { + tilePyramid.onLowMemory(); +} + +void RenderCustomGeometrySource::dumpDebugLogs() const { + tilePyramid.dumpDebugLogs(); +} + +} // namespace mbgl diff --git a/src/mbgl/renderer/sources/render_custom_geometry_source.hpp b/src/mbgl/renderer/sources/render_custom_geometry_source.hpp new file mode 100644 index 0000000000..82e691d5c9 --- /dev/null +++ b/src/mbgl/renderer/sources/render_custom_geometry_source.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include <mbgl/renderer/render_source.hpp> +#include <mbgl/renderer/tile_pyramid.hpp> +#include <mbgl/style/sources/custom_geometry_source_impl.hpp> + +namespace mbgl { + +class RenderCustomGeometrySource : public RenderSource { +public: + RenderCustomGeometrySource(Immutable<style::CustomGeometrySource::Impl>); + + bool isLoaded() const final; + + void update(Immutable<style::Source::Impl>, + const std::vector<Immutable<style::Layer::Impl>>&, + bool needsRendering, + bool needsRelayout, + const TileParameters&) final; + + void startRender(PaintParameters&) final; + void finishRender(PaintParameters&) final; + + std::vector<std::reference_wrapper<RenderTile>> getRenderTiles() final; + + std::unordered_map<std::string, std::vector<Feature>> + queryRenderedFeatures(const ScreenLineString& geometry, + const TransformState& transformState, + const std::vector<const RenderLayer*>& layers, + const RenderedQueryOptions& options, + const CollisionIndex& collisionIndex) const final; + + std::vector<Feature> + querySourceFeatures(const SourceQueryOptions&) const final; + + void onLowMemory() final; + void dumpDebugLogs() const final; + +private: + const style::CustomGeometrySource::Impl& impl() const; + + TilePyramid tilePyramid; +}; + +template <> +inline bool RenderSource::is<RenderCustomGeometrySource>() const { + return baseImpl->type == style::SourceType::CustomVector; +} + +} // namespace mbgl diff --git a/src/mbgl/renderer/sources/render_geojson_source.cpp b/src/mbgl/renderer/sources/render_geojson_source.cpp index c13cd49f46..8ea80cd813 100644 --- a/src/mbgl/renderer/sources/render_geojson_source.cpp +++ b/src/mbgl/renderer/sources/render_geojson_source.cpp @@ -85,8 +85,9 @@ std::unordered_map<std::string, std::vector<Feature>> RenderGeoJSONSource::queryRenderedFeatures(const ScreenLineString& geometry, const TransformState& transformState, const std::vector<const RenderLayer*>& layers, - const RenderedQueryOptions& options) const { - return tilePyramid.queryRenderedFeatures(geometry, transformState, layers, options); + const RenderedQueryOptions& options, + const CollisionIndex& collisionIndex) const { + return tilePyramid.queryRenderedFeatures(geometry, transformState, layers, options, collisionIndex); } std::vector<Feature> RenderGeoJSONSource::querySourceFeatures(const SourceQueryOptions& options) const { diff --git a/src/mbgl/renderer/sources/render_geojson_source.hpp b/src/mbgl/renderer/sources/render_geojson_source.hpp index 72ab4879ef..55166ea901 100644 --- a/src/mbgl/renderer/sources/render_geojson_source.hpp +++ b/src/mbgl/renderer/sources/render_geojson_source.hpp @@ -31,7 +31,8 @@ public: queryRenderedFeatures(const ScreenLineString& geometry, const TransformState& transformState, const std::vector<const RenderLayer*>& layers, - const RenderedQueryOptions& options) const final; + const RenderedQueryOptions& options, + const CollisionIndex&) const final; std::vector<Feature> querySourceFeatures(const SourceQueryOptions&) const final; @@ -48,7 +49,7 @@ private: template <> inline bool RenderSource::is<RenderGeoJSONSource>() const { - return baseImpl->type == SourceType::GeoJSON; + return baseImpl->type == style::SourceType::GeoJSON; } } // namespace mbgl diff --git a/src/mbgl/renderer/sources/render_image_source.cpp b/src/mbgl/renderer/sources/render_image_source.cpp index 9140e01711..31a5916a34 100644 --- a/src/mbgl/renderer/sources/render_image_source.cpp +++ b/src/mbgl/renderer/sources/render_image_source.cpp @@ -41,7 +41,7 @@ void RenderImageSource::startRender(PaintParameters& parameters) { mat4 matrix; matrix::identity(matrix); parameters.state.matrixFor(matrix, tileIds[i]); - matrix::multiply(matrix, parameters.projMatrix, matrix); + matrix::multiply(matrix, parameters.alignedProjMatrix, matrix); matrices.push_back(matrix); } @@ -56,7 +56,7 @@ void RenderImageSource::finishRender(PaintParameters& parameters) { } static const style::Properties<>::PossiblyEvaluated properties {}; - static const DebugProgram::PaintPropertyBinders paintAttibuteData(properties, 0); + static const DebugProgram::PaintPropertyBinders paintAttributeData(properties, 0); for (auto matrix : matrices) { parameters.programs.debug.draw( @@ -72,7 +72,7 @@ void RenderImageSource::finishRender(PaintParameters& parameters) { parameters.staticData.tileVertexBuffer, parameters.staticData.tileBorderIndexBuffer, parameters.staticData.tileBorderSegments, - paintAttibuteData, + paintAttributeData, properties, parameters.state.getZoom(), "debug" @@ -84,7 +84,8 @@ std::unordered_map<std::string, std::vector<Feature>> RenderImageSource::queryRenderedFeatures(const ScreenLineString&, const TransformState&, const std::vector<const RenderLayer*>&, - const RenderedQueryOptions&) const { + const RenderedQueryOptions&, + const CollisionIndex&) const { return std::unordered_map<std::string, std::vector<Feature>> {}; } @@ -113,44 +114,43 @@ void RenderImageSource::update(Immutable<style::Source::Impl> baseImpl_, return; } - auto size = transformState.getSize(); - const double viewportHeight = size.height; - - // Compute the screen coordinates at wrap=0 for the given LatLng - ScreenCoordinate nePixel = { -INFINITY, -INFINITY }; - ScreenCoordinate swPixel = { INFINITY, INFINITY }; - + // Compute the z0 tile coordinates for the given LatLngs + TileCoordinatePoint nePoint = { -INFINITY, -INFINITY }; + TileCoordinatePoint swPoint = { INFINITY, INFINITY }; + std::vector<TileCoordinatePoint> tileCoordinates; for (LatLng latLng : coords) { - ScreenCoordinate pixel = transformState.latLngToScreenCoordinate(latLng); - swPixel.x = std::min(swPixel.x, pixel.x); - nePixel.x = std::max(nePixel.x, pixel.x); - swPixel.y = std::min(swPixel.y, viewportHeight - pixel.y); - nePixel.y = std::max(nePixel.y, viewportHeight - pixel.y); - } - const double width = nePixel.x - swPixel.x; - const double height = nePixel.y - swPixel.y; + auto point = TileCoordinate::fromLatLng(0, latLng).p; + tileCoordinates.push_back(point); + swPoint.x = std::min(swPoint.x, point.x); + nePoint.x = std::max(nePoint.x, point.x); + swPoint.y = std::min(swPoint.y, point.y); + nePoint.y = std::max(nePoint.y, point.y); + } - // Don't bother drawing the ImageSource unless it occupies >4 screen pixels - enabled = (width * height > 4); + // Calculate the optimum zoom level to determine the tile ids to use for transforms + auto dx = nePoint.x - swPoint.x; + auto dy = nePoint.y - swPoint.y; + auto dMax = std::max(dx, dy); + double zoom = std::max(0.0, std::floor(-util::log2(dMax))); + + // Only enable if the long side of the image is > 2 pixels. Resulting in a + // display of at least 2 x 1 px image + // A tile coordinate unit represents the length of one tile (tileSize) at a given zoom. + // To convert a tile coordinate to pixels, multiply by tileSize. + // Here dMax is in z0 tile units, so we also scale by 2^z to match current zoom. + enabled = dMax * std::pow(2.0, transformState.getZoom()) * util::tileSize > 2.0; if (!enabled) { return; } - // Calculate the optimum zoom level to determine the tile ids to use for transforms - double minScale = INFINITY; - double scaleX = double(size.width) / width; - double scaleY = double(size.height) / height; - minScale = util::min(scaleX, scaleY); - double zoom = transformState.getZoom() + util::log2(minScale); - zoom = std::floor(util::clamp(zoom, transformState.getMinZoom(), transformState.getMaxZoom())); auto imageBounds = LatLngBounds::hull(coords[0], coords[1]); imageBounds.extend(coords[2]); imageBounds.extend(coords[3]); auto tileCover = util::tileCover(imageBounds, zoom); tileIds.clear(); tileIds.push_back(tileCover[0]); - bool hasVisibleTile = false; + bool hasVisibleTile = false; // Add additional wrapped tile ids if neccessary auto idealTiles = util::tileCover(transformState, transformState.getZoom()); for (auto tile : idealTiles) { @@ -176,9 +176,8 @@ void RenderImageSource::update(Immutable<style::Source::Impl> baseImpl_, // Calculate Geometry Coordinates based on tile cover at ideal zoom GeometryCoordinates geomCoords; - for (auto latLng : coords) { - auto tc = TileCoordinate::fromLatLng(0, latLng); - auto gc = TileCoordinate::toGeometryCoordinate(tileIds[0], tc.p); + for (auto tileCoords : tileCoordinates) { + auto gc = TileCoordinate::toGeometryCoordinate(tileIds[0], tileCoords); geomCoords.push_back(gc); } if (!bucket) { diff --git a/src/mbgl/renderer/sources/render_image_source.hpp b/src/mbgl/renderer/sources/render_image_source.hpp index 7b69d09fa7..72cf4cea61 100644 --- a/src/mbgl/renderer/sources/render_image_source.hpp +++ b/src/mbgl/renderer/sources/render_image_source.hpp @@ -32,7 +32,8 @@ public: queryRenderedFeatures(const ScreenLineString& geometry, const TransformState& transformState, const std::vector<const RenderLayer*>& layers, - const RenderedQueryOptions& options) const final; + const RenderedQueryOptions& options, + const CollisionIndex& collisionIndex) const final; std::vector<Feature> querySourceFeatures(const SourceQueryOptions&) const final; @@ -52,7 +53,7 @@ private: template <> inline bool RenderSource::is<RenderImageSource>() const { - return baseImpl->type == SourceType::Image; + return baseImpl->type == style::SourceType::Image; } } // namespace mbgl diff --git a/src/mbgl/renderer/sources/render_raster_dem_source.cpp b/src/mbgl/renderer/sources/render_raster_dem_source.cpp new file mode 100644 index 0000000000..4de949c9f2 --- /dev/null +++ b/src/mbgl/renderer/sources/render_raster_dem_source.cpp @@ -0,0 +1,166 @@ +#include <mbgl/renderer/sources/render_raster_dem_source.hpp> +#include <mbgl/renderer/render_tile.hpp> +#include <mbgl/tile/raster_dem_tile.hpp> +#include <mbgl/algorithm/update_tile_masks.hpp> +#include <mbgl/geometry/dem_data.hpp> +#include <mbgl/renderer/buckets/hillshade_bucket.hpp> +#include <iostream> + +namespace mbgl { + +using namespace style; + +RenderRasterDEMSource::RenderRasterDEMSource(Immutable<style::RasterSource::Impl> impl_) + : RenderSource(impl_) { + tilePyramid.setObserver(this); +} + +const style::RasterSource::Impl& RenderRasterDEMSource::impl() const { + return static_cast<const style::RasterSource::Impl&>(*baseImpl); +} + +bool RenderRasterDEMSource::isLoaded() const { + return tilePyramid.isLoaded(); +} + +void RenderRasterDEMSource::update(Immutable<style::Source::Impl> baseImpl_, + const std::vector<Immutable<Layer::Impl>>& layers, + const bool needsRendering, + const bool needsRelayout, + const TileParameters& parameters) { + std::swap(baseImpl, baseImpl_); + + enabled = needsRendering; + + optional<Tileset> _tileset = impl().getTileset(); + + if (tileset != _tileset) { + tileset = _tileset; + + // TODO: this removes existing buckets, and will cause flickering. + // Should instead refresh tile data in place. + tilePyramid.tiles.clear(); + tilePyramid.renderTiles.clear(); + tilePyramid.cache.clear(); + } + // Allow clearing the tile pyramid first, before the early return in case + // the new tileset is not yet available or has an error in loading + if (!_tileset) { + return; + } + + tilePyramid.update(layers, + needsRendering, + needsRelayout, + parameters, + SourceType::RasterDEM, + impl().getTileSize(), + tileset->zoomRange, + tileset->bounds, + [&] (const OverscaledTileID& tileID) { + return std::make_unique<RasterDEMTile>(tileID, parameters, *tileset); + }); +} + +void RenderRasterDEMSource::onTileChanged(Tile& tile){ + RasterDEMTile& demtile = static_cast<RasterDEMTile&>(tile); + + std::map<DEMTileNeighbors, DEMTileNeighbors> opposites = { + { DEMTileNeighbors::Left, DEMTileNeighbors::Right }, + { DEMTileNeighbors::Right, DEMTileNeighbors::Left }, + { DEMTileNeighbors::TopLeft, DEMTileNeighbors::BottomRight }, + { DEMTileNeighbors::TopCenter, DEMTileNeighbors::BottomCenter }, + { DEMTileNeighbors::TopRight, DEMTileNeighbors::BottomLeft }, + { DEMTileNeighbors::BottomRight, DEMTileNeighbors::TopLeft }, + { DEMTileNeighbors::BottomCenter, DEMTileNeighbors:: TopCenter }, + { DEMTileNeighbors::BottomLeft, DEMTileNeighbors::TopRight } + }; + + if (tile.isRenderable() && demtile.neighboringTiles != DEMTileNeighbors::Complete) { + const CanonicalTileID canonical = tile.id.canonical; + const uint32_t dim = std::pow(2, canonical.z); + const uint32_t px = (canonical.x - 1 + dim) % dim; + const int pxw = canonical.x == 0 ? tile.id.wrap - 1 : tile.id.wrap; + const uint32_t nx = (canonical.x + 1 + dim) % dim; + const int nxw = (canonical.x + 1 == dim) ? tile.id.wrap + 1 : tile.id.wrap; + + auto getNeighbor = [&] (DEMTileNeighbors mask){ + if (mask == DEMTileNeighbors::Left){ + return OverscaledTileID(tile.id.overscaledZ, pxw, canonical.z, px, canonical.y); + } else if (mask == DEMTileNeighbors::Right){ + return OverscaledTileID(tile.id.overscaledZ, nxw, canonical.z, nx, canonical.y); + } else if (mask == DEMTileNeighbors::TopLeft){ + return OverscaledTileID(tile.id.overscaledZ, pxw, canonical.z, px, canonical.y - 1); + } else if (mask == DEMTileNeighbors::TopCenter){ + return OverscaledTileID(tile.id.overscaledZ, tile.id.wrap, canonical.z, canonical.x, canonical.y - 1); + } else if (mask == DEMTileNeighbors::TopRight){ + return OverscaledTileID(tile.id.overscaledZ, nxw, canonical.z, nx, canonical.y - 1); + } else if (mask == DEMTileNeighbors::BottomLeft){ + return OverscaledTileID(tile.id.overscaledZ, pxw, canonical.z, px, canonical.y + 1); + } else if (mask == DEMTileNeighbors::BottomCenter){ + return OverscaledTileID(tile.id.overscaledZ, tile.id.wrap, canonical.z, canonical.x, canonical.y + 1); + } else if (mask == DEMTileNeighbors::BottomRight){ + return OverscaledTileID(tile.id.overscaledZ, nxw, canonical.z, nx, canonical.y + 1); + } else{ + throw std::runtime_error("mask is not a valid tile neighbor"); + } + }; + + for (uint8_t i = 0; i < 8; i++) { + DEMTileNeighbors mask = DEMTileNeighbors(std::pow(2,i)); + // only backfill if this neighbor has not been previously backfilled + if ((demtile.neighboringTiles & mask) != mask) { + OverscaledTileID neighborid = getNeighbor(mask); + Tile* renderableNeighbor = tilePyramid.getTile(neighborid); + if (renderableNeighbor != nullptr && renderableNeighbor->isRenderable()) { + RasterDEMTile& borderTile = static_cast<RasterDEMTile&>(*renderableNeighbor); + demtile.backfillBorder(borderTile, mask); + + // if the border tile has not been backfilled by a previous instance of the main + // tile, backfill its corresponding neighbor as well. + const DEMTileNeighbors& borderMask = opposites[mask]; + if ((borderTile.neighboringTiles & borderMask) != borderMask){ + borderTile.backfillBorder(demtile, borderMask); + } + } + } + } + } + RenderSource::onTileChanged(tile); +} + +void RenderRasterDEMSource::startRender(PaintParameters& parameters) { + algorithm::updateTileMasks(tilePyramid.getRenderTiles()); + tilePyramid.startRender(parameters); +} + +void RenderRasterDEMSource::finishRender(PaintParameters& parameters) { + tilePyramid.finishRender(parameters); +} + +std::vector<std::reference_wrapper<RenderTile>> RenderRasterDEMSource::getRenderTiles() { + return tilePyramid.getRenderTiles(); +} + +std::unordered_map<std::string, std::vector<Feature>> +RenderRasterDEMSource::queryRenderedFeatures(const ScreenLineString&, + const TransformState&, + const std::vector<const RenderLayer*>&, + const RenderedQueryOptions&, + const CollisionIndex& ) const { + return std::unordered_map<std::string, std::vector<Feature>> {}; +} + +std::vector<Feature> RenderRasterDEMSource::querySourceFeatures(const SourceQueryOptions&) const { + return {}; +} + +void RenderRasterDEMSource::onLowMemory() { + tilePyramid.onLowMemory(); +} + +void RenderRasterDEMSource::dumpDebugLogs() const { + tilePyramid.dumpDebugLogs(); +} + +} // namespace mbgl diff --git a/src/mbgl/renderer/sources/render_raster_dem_source.hpp b/src/mbgl/renderer/sources/render_raster_dem_source.hpp new file mode 100644 index 0000000000..87f9bcb563 --- /dev/null +++ b/src/mbgl/renderer/sources/render_raster_dem_source.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include <mbgl/renderer/render_source.hpp> +#include <mbgl/renderer/tile_pyramid.hpp> +#include <mbgl/style/sources/raster_source_impl.hpp> + +namespace mbgl { + +class RenderRasterDEMSource : public RenderSource { +public: + RenderRasterDEMSource(Immutable<style::RasterSource::Impl>); + + bool isLoaded() const final; + + void update(Immutable<style::Source::Impl>, + const std::vector<Immutable<style::Layer::Impl>>&, + bool needsRendering, + bool needsRelayout, + const TileParameters&) final; + + void startRender(PaintParameters&) final; + void finishRender(PaintParameters&) final; + + std::vector<std::reference_wrapper<RenderTile>> getRenderTiles() final; + + std::unordered_map<std::string, std::vector<Feature>> + queryRenderedFeatures(const ScreenLineString& geometry, + const TransformState& transformState, + const std::vector<const RenderLayer*>& layers, + const RenderedQueryOptions& options, + const CollisionIndex& collisionIndex) const final; + + std::vector<Feature> + querySourceFeatures(const SourceQueryOptions&) const final; + + void onLowMemory() final; + void dumpDebugLogs() const final; + +private: + const style::RasterSource::Impl& impl() const; + + TilePyramid tilePyramid; + optional<Tileset> tileset; + +protected: + void onTileChanged(Tile&) final; +}; + +template <> +inline bool RenderSource::is<RenderRasterDEMSource>() const { + return baseImpl->type == style::SourceType::RasterDEM; +} + +} // namespace mbgl diff --git a/src/mbgl/renderer/sources/render_raster_source.cpp b/src/mbgl/renderer/sources/render_raster_source.cpp index 46e3c2eff6..5d32818663 100644 --- a/src/mbgl/renderer/sources/render_raster_source.cpp +++ b/src/mbgl/renderer/sources/render_raster_source.cpp @@ -76,7 +76,8 @@ std::unordered_map<std::string, std::vector<Feature>> RenderRasterSource::queryRenderedFeatures(const ScreenLineString&, const TransformState&, const std::vector<const RenderLayer*>&, - const RenderedQueryOptions&) const { + const RenderedQueryOptions&, + const CollisionIndex& ) const { return std::unordered_map<std::string, std::vector<Feature>> {}; } diff --git a/src/mbgl/renderer/sources/render_raster_source.hpp b/src/mbgl/renderer/sources/render_raster_source.hpp index aebc49bf8a..bc6ac1bd9f 100644 --- a/src/mbgl/renderer/sources/render_raster_source.hpp +++ b/src/mbgl/renderer/sources/render_raster_source.hpp @@ -27,7 +27,8 @@ public: queryRenderedFeatures(const ScreenLineString& geometry, const TransformState& transformState, const std::vector<const RenderLayer*>& layers, - const RenderedQueryOptions& options) const final; + const RenderedQueryOptions& options, + const CollisionIndex& collisionIndex) const final; std::vector<Feature> querySourceFeatures(const SourceQueryOptions&) const final; @@ -44,7 +45,7 @@ private: template <> inline bool RenderSource::is<RenderRasterSource>() const { - return baseImpl->type == SourceType::Raster; + return baseImpl->type == style::SourceType::Raster; } } // namespace mbgl diff --git a/src/mbgl/renderer/sources/render_vector_source.cpp b/src/mbgl/renderer/sources/render_vector_source.cpp index 310dfe68b8..c01257aea9 100644 --- a/src/mbgl/renderer/sources/render_vector_source.cpp +++ b/src/mbgl/renderer/sources/render_vector_source.cpp @@ -79,8 +79,9 @@ std::unordered_map<std::string, std::vector<Feature>> RenderVectorSource::queryRenderedFeatures(const ScreenLineString& geometry, const TransformState& transformState, const std::vector<const RenderLayer*>& layers, - const RenderedQueryOptions& options) const { - return tilePyramid.queryRenderedFeatures(geometry, transformState, layers, options); + const RenderedQueryOptions& options, + const CollisionIndex& collisionIndex) const { + return tilePyramid.queryRenderedFeatures(geometry, transformState, layers, options, collisionIndex); } std::vector<Feature> RenderVectorSource::querySourceFeatures(const SourceQueryOptions& options) const { diff --git a/src/mbgl/renderer/sources/render_vector_source.hpp b/src/mbgl/renderer/sources/render_vector_source.hpp index f237b4b825..57fe6dbb6c 100644 --- a/src/mbgl/renderer/sources/render_vector_source.hpp +++ b/src/mbgl/renderer/sources/render_vector_source.hpp @@ -27,7 +27,8 @@ public: queryRenderedFeatures(const ScreenLineString& geometry, const TransformState& transformState, const std::vector<const RenderLayer*>& layers, - const RenderedQueryOptions& options) const final; + const RenderedQueryOptions& options, + const CollisionIndex& collisionIndex) const final; std::vector<Feature> querySourceFeatures(const SourceQueryOptions&) const final; @@ -44,7 +45,7 @@ private: template <> inline bool RenderSource::is<RenderVectorSource>() const { - return baseImpl->type == SourceType::Vector; + return baseImpl->type == style::SourceType::Vector; } } // namespace mbgl diff --git a/src/mbgl/renderer/tile_pyramid.cpp b/src/mbgl/renderer/tile_pyramid.cpp index c9e3b0630a..07239b7a1c 100644 --- a/src/mbgl/renderer/tile_pyramid.cpp +++ b/src/mbgl/renderer/tile_pyramid.cpp @@ -5,7 +5,6 @@ #include <mbgl/renderer/tile_parameters.hpp> #include <mbgl/renderer/query.hpp> #include <mbgl/map/transform.hpp> -#include <mbgl/text/placement_config.hpp> #include <mbgl/math/clamp.hpp> #include <mbgl/util/tile_cover.hpp> #include <mbgl/util/tile_range.hpp> @@ -57,6 +56,11 @@ std::vector<std::reference_wrapper<RenderTile>> TilePyramid::getRenderTiles() { return { renderTiles.begin(), renderTiles.end() }; } +Tile* TilePyramid::getTile(const OverscaledTileID& tileID){ + auto it = tiles.find(tileID); + return it == tiles.end() ? cache.get(tileID) : it->second.get(); +} + void TilePyramid::update(const std::vector<Immutable<style::Layer::Impl>>& layers, const bool needsRendering, const bool needsRelayout, @@ -97,20 +101,21 @@ void TilePyramid::update(const std::vector<Immutable<style::Layer::Impl>>& layer if (overscaledZoom >= zoomRange.min) { int32_t idealZoom = std::min<int32_t>(zoomRange.max, overscaledZoom); + // Make sure we're not reparsing overzoomed raster tiles. if (type == SourceType::Raster) { tileZoom = idealZoom; + } - // FIXME: Prefetching is only enabled for raster - // tiles until we fix #7026. - - // Request lower zoom level tiles (if configure to do so) in an attempt + // Only attempt prefetching in continuous mode. + if (parameters.mode == MapMode::Continuous) { + // Request lower zoom level tiles (if configured to do so) in an attempt // to show something on the screen faster at the cost of a little of bandwidth. if (parameters.prefetchZoomDelta) { panZoom = std::max<int32_t>(tileZoom - parameters.prefetchZoomDelta, zoomRange.min); } - if (panZoom < tileZoom) { + if (panZoom < idealZoom) { panTiles = util::tileCover(parameters.transformState, panZoom); } } @@ -123,6 +128,7 @@ void TilePyramid::update(const std::vector<Immutable<style::Layer::Impl>>& layer // use because they're still loading. In addition to that, we also need to retain all tiles that // we're actively using, e.g. as a replacement for tile that aren't loaded yet. std::set<OverscaledTileID> retain; + std::set<UnwrappedTileID> rendered; auto retainTileFn = [&](Tile& tile, TileNecessity necessity) -> void { if (retain.emplace(tile.id).second) { @@ -146,7 +152,7 @@ void TilePyramid::update(const std::vector<Immutable<style::Layer::Impl>>& layer if (tileRange && !tileRange->contains(tileID.canonical)) { return nullptr; } - std::unique_ptr<Tile> tile = cache.get(tileID); + std::unique_ptr<Tile> tile = cache.pop(tileID); if (!tile) { tile = createTile(tileID); if (tile) { @@ -159,8 +165,17 @@ void TilePyramid::update(const std::vector<Immutable<style::Layer::Impl>>& layer } return tiles.emplace(tileID, std::move(tile)).first->second.get(); }; + + std::map<UnwrappedTileID, Tile*> previouslyRenderedTiles; + for (auto& renderTile : renderTiles) { + previouslyRenderedTiles[renderTile.id] = &renderTile.tile; + } + auto renderTileFn = [&](const UnwrappedTileID& tileID, Tile& tile) { renderTiles.emplace_back(tileID, tile); + rendered.emplace(tileID); + previouslyRenderedTiles.erase(tileID); // Still rendering this tile, no need for special fading logic. + tile.markRenderedIdeal(); }; renderTiles.clear(); @@ -172,6 +187,18 @@ void TilePyramid::update(const std::vector<Immutable<style::Layer::Impl>>& layer algorithm::updateRenderables(getTileFn, createTileFn, retainTileFn, renderTileFn, idealTiles, zoomRange, tileZoom); + + for (auto previouslyRenderedTile : previouslyRenderedTiles) { + Tile& tile = *previouslyRenderedTile.second; + tile.markRenderedPreviously(); + if (tile.holdForFade()) { + // Since it was rendered in the last frame, we know we have it + // Don't mark the tile "Required" to avoid triggering a new network request + retainTileFn(tile, TileNecessity::Optional); + renderTiles.emplace_back(previouslyRenderedTile.first, tile); + rendered.emplace(previouslyRenderedTile.first); + } + } if (type != SourceType::Annotations) { size_t conservativeCacheSize = @@ -204,20 +231,15 @@ void TilePyramid::update(const std::vector<Immutable<style::Layer::Impl>>& layer } for (auto& pair : tiles) { - const PlacementConfig config { parameters.transformState.getAngle(), - parameters.transformState.getPitch(), - parameters.transformState.getCameraToCenterDistance(), - parameters.transformState.getCameraToTileDistance(pair.first.toUnwrapped()), - parameters.debugOptions & MapDebugOptions::Collision }; - - pair.second->setPlacementConfig(config); + pair.second->setShowCollisionBoxes(parameters.debugOptions & MapDebugOptions::Collision); } } std::unordered_map<std::string, std::vector<Feature>> TilePyramid::queryRenderedFeatures(const ScreenLineString& geometry, const TransformState& transformState, const std::vector<const RenderLayer*>& layers, - const RenderedQueryOptions& options) const { + const RenderedQueryOptions& options, + const CollisionIndex& collisionIndex) const { std::unordered_map<std::string, std::vector<Feature>> result; if (renderTiles.empty() || geometry.empty()) { return result; @@ -260,7 +282,8 @@ std::unordered_map<std::string, std::vector<Feature>> TilePyramid::queryRendered tileSpaceQueryGeometry, transformState, layers, - options); + options, + collisionIndex); } return result; diff --git a/src/mbgl/renderer/tile_pyramid.hpp b/src/mbgl/renderer/tile_pyramid.hpp index e34b050273..ad3f91bf88 100644 --- a/src/mbgl/renderer/tile_pyramid.hpp +++ b/src/mbgl/renderer/tile_pyramid.hpp @@ -37,7 +37,7 @@ public: bool needsRendering, bool needsRelayout, const TileParameters&, - SourceType type, + style::SourceType type, uint16_t tileSize, Range<uint8_t> zoomRange, optional<LatLngBounds> bounds, @@ -47,12 +47,14 @@ public: void finishRender(PaintParameters&); std::vector<std::reference_wrapper<RenderTile>> getRenderTiles(); + Tile* getTile(const OverscaledTileID&); std::unordered_map<std::string, std::vector<Feature>> queryRenderedFeatures(const ScreenLineString& geometry, const TransformState& transformState, const std::vector<const RenderLayer*>&, - const RenderedQueryOptions& options) const; + const RenderedQueryOptions& options, + const CollisionIndex& collisionIndex) const; std::vector<Feature> querySourceFeatures(const SourceQueryOptions&) const; diff --git a/src/mbgl/shaders/background.cpp b/src/mbgl/shaders/background.cpp new file mode 100644 index 0000000000..3eafa47b49 --- /dev/null +++ b/src/mbgl/shaders/background.cpp @@ -0,0 +1,34 @@ +// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED. + +#include <mbgl/shaders/background.hpp> + +namespace mbgl { +namespace shaders { + +const char* background::name = "background"; +const char* background::vertexSource = R"MBGL_SHADER( +attribute vec2 a_pos; + +uniform mat4 u_matrix; + +void main() { + gl_Position = u_matrix * vec4(a_pos, 0, 1); +} + +)MBGL_SHADER"; +const char* background::fragmentSource = R"MBGL_SHADER( +uniform vec4 u_color; +uniform float u_opacity; + +void main() { + gl_FragColor = u_color * u_opacity; + +#ifdef OVERDRAW_INSPECTOR + gl_FragColor = vec4(1.0); +#endif +} + +)MBGL_SHADER"; + +} // namespace shaders +} // namespace mbgl diff --git a/src/mbgl/shaders/background.hpp b/src/mbgl/shaders/background.hpp new file mode 100644 index 0000000000..2fa6f56e29 --- /dev/null +++ b/src/mbgl/shaders/background.hpp @@ -0,0 +1,16 @@ +// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED. + +#pragma once + +namespace mbgl { +namespace shaders { + +class background { +public: + static const char* name; + static const char* vertexSource; + static const char* fragmentSource; +}; + +} // namespace shaders +} // namespace mbgl diff --git a/src/mbgl/shaders/background_pattern.cpp b/src/mbgl/shaders/background_pattern.cpp new file mode 100644 index 0000000000..6fd0a53d19 --- /dev/null +++ b/src/mbgl/shaders/background_pattern.cpp @@ -0,0 +1,65 @@ +// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED. + +#include <mbgl/shaders/background_pattern.hpp> + +namespace mbgl { +namespace shaders { + +const char* background_pattern::name = "background_pattern"; +const char* background_pattern::vertexSource = R"MBGL_SHADER( +uniform mat4 u_matrix; +uniform vec2 u_pattern_size_a; +uniform vec2 u_pattern_size_b; +uniform vec2 u_pixel_coord_upper; +uniform vec2 u_pixel_coord_lower; +uniform float u_scale_a; +uniform float u_scale_b; +uniform float u_tile_units_to_pixels; + +attribute vec2 a_pos; + +varying vec2 v_pos_a; +varying vec2 v_pos_b; + +void main() { + gl_Position = u_matrix * vec4(a_pos, 0, 1); + + v_pos_a = get_pattern_pos(u_pixel_coord_upper, u_pixel_coord_lower, u_scale_a * u_pattern_size_a, u_tile_units_to_pixels, a_pos); + v_pos_b = get_pattern_pos(u_pixel_coord_upper, u_pixel_coord_lower, u_scale_b * u_pattern_size_b, u_tile_units_to_pixels, a_pos); +} + +)MBGL_SHADER"; +const char* background_pattern::fragmentSource = R"MBGL_SHADER( +uniform vec2 u_pattern_tl_a; +uniform vec2 u_pattern_br_a; +uniform vec2 u_pattern_tl_b; +uniform vec2 u_pattern_br_b; +uniform vec2 u_texsize; +uniform float u_mix; +uniform float u_opacity; + +uniform sampler2D u_image; + +varying vec2 v_pos_a; +varying vec2 v_pos_b; + +void main() { + vec2 imagecoord = mod(v_pos_a, 1.0); + vec2 pos = mix(u_pattern_tl_a / u_texsize, u_pattern_br_a / u_texsize, imagecoord); + vec4 color1 = texture2D(u_image, pos); + + vec2 imagecoord_b = mod(v_pos_b, 1.0); + vec2 pos2 = mix(u_pattern_tl_b / u_texsize, u_pattern_br_b / u_texsize, imagecoord_b); + vec4 color2 = texture2D(u_image, pos2); + + gl_FragColor = mix(color1, color2, u_mix) * u_opacity; + +#ifdef OVERDRAW_INSPECTOR + gl_FragColor = vec4(1.0); +#endif +} + +)MBGL_SHADER"; + +} // namespace shaders +} // namespace mbgl diff --git a/src/mbgl/shaders/background_pattern.hpp b/src/mbgl/shaders/background_pattern.hpp new file mode 100644 index 0000000000..e970ffd670 --- /dev/null +++ b/src/mbgl/shaders/background_pattern.hpp @@ -0,0 +1,16 @@ +// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED. + +#pragma once + +namespace mbgl { +namespace shaders { + +class background_pattern { +public: + static const char* name; + static const char* vertexSource; + static const char* fragmentSource; +}; + +} // namespace shaders +} // namespace mbgl diff --git a/src/mbgl/shaders/clipping_mask.cpp b/src/mbgl/shaders/clipping_mask.cpp new file mode 100644 index 0000000000..fb08d7cb00 --- /dev/null +++ b/src/mbgl/shaders/clipping_mask.cpp @@ -0,0 +1,27 @@ +// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED. + +#include <mbgl/shaders/clipping_mask.hpp> + +namespace mbgl { +namespace shaders { + +const char* clipping_mask::name = "clipping_mask"; +const char* clipping_mask::vertexSource = R"MBGL_SHADER( +attribute vec2 a_pos; + +uniform mat4 u_matrix; + +void main() { + gl_Position = u_matrix * vec4(a_pos, 0, 1); +} + +)MBGL_SHADER"; +const char* clipping_mask::fragmentSource = R"MBGL_SHADER( +void main() { + gl_FragColor = vec4(1.0); +} + +)MBGL_SHADER"; + +} // namespace shaders +} // namespace mbgl diff --git a/src/mbgl/shaders/clipping_mask.hpp b/src/mbgl/shaders/clipping_mask.hpp new file mode 100644 index 0000000000..bd01ab62fa --- /dev/null +++ b/src/mbgl/shaders/clipping_mask.hpp @@ -0,0 +1,16 @@ +// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED. + +#pragma once + +namespace mbgl { +namespace shaders { + +class clipping_mask { +public: + static const char* name; + static const char* vertexSource; + static const char* fragmentSource; +}; + +} // namespace shaders +} // namespace mbgl diff --git a/src/mbgl/shaders/collision_box.cpp b/src/mbgl/shaders/collision_box.cpp index 07fa94e338..9d11640bf4 100644 --- a/src/mbgl/shaders/collision_box.cpp +++ b/src/mbgl/shaders/collision_box.cpp @@ -10,77 +10,50 @@ const char* collision_box::vertexSource = R"MBGL_SHADER( attribute vec2 a_pos; attribute vec2 a_anchor_pos; attribute vec2 a_extrude; -attribute vec2 a_data; +attribute vec2 a_placed; uniform mat4 u_matrix; -uniform float u_scale; -uniform float u_pitch; -uniform float u_collision_y_stretch; +uniform vec2 u_extrude_scale; uniform float u_camera_to_center_distance; -varying float v_max_zoom; -varying float v_placement_zoom; -varying float v_perspective_zoom_adjust; -varying vec2 v_fade_tex; +varying float v_placed; +varying float v_notUsed; void main() { vec4 projectedPoint = u_matrix * vec4(a_anchor_pos, 0, 1); highp float camera_to_anchor_distance = projectedPoint.w; - highp float collision_perspective_ratio = 1.0 + 0.5 * ((camera_to_anchor_distance / u_camera_to_center_distance) - 1.0); + highp float collision_perspective_ratio = 0.5 + 0.5 * (u_camera_to_center_distance / camera_to_anchor_distance); - highp float incidence_stretch = camera_to_anchor_distance / (u_camera_to_center_distance * cos(u_pitch)); - highp float collision_adjustment = max(1.0, incidence_stretch / u_collision_y_stretch); + gl_Position = u_matrix * vec4(a_pos, 0.0, 1.0); + gl_Position.xy += a_extrude * u_extrude_scale * gl_Position.w * collision_perspective_ratio; - gl_Position = u_matrix * vec4(a_pos + a_extrude * collision_perspective_ratio * collision_adjustment / u_scale, 0.0, 1.0); - - v_max_zoom = a_data.x; - v_placement_zoom = a_data.y; - - v_perspective_zoom_adjust = floor(log2(collision_perspective_ratio * collision_adjustment) * 10.0); - v_fade_tex = vec2((v_placement_zoom + v_perspective_zoom_adjust) / 255.0, 0.0); + v_placed = a_placed.x; + v_notUsed = a_placed.y; } )MBGL_SHADER"; const char* collision_box::fragmentSource = R"MBGL_SHADER( -uniform float u_zoom; -// u_maxzoom is derived from the maximum scale considered by the CollisionTile -// Labels with placement zoom greater than this value will not be placed, -// regardless of perspective effects. -uniform float u_maxzoom; -uniform sampler2D u_fadetexture; - -// v_max_zoom is a collision-box-specific value that controls when line-following -// collision boxes are used. -varying float v_max_zoom; -varying float v_placement_zoom; -varying float v_perspective_zoom_adjust; -varying vec2 v_fade_tex; + +varying float v_placed; +varying float v_notUsed; void main() { float alpha = 0.5; - // Green = no collisions, label is showing - gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0) * alpha; + // Red = collision, hide label + gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0) * alpha; - // Red = collision, label hidden - if (texture2D(u_fadetexture, v_fade_tex).a < 1.0) { - gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0) * alpha; + // Blue = no collision, label is showing + if (v_placed > 0.5) { + gl_FragColor = vec4(0.0, 0.0, 1.0, 0.5) * alpha; } - // Faded black = this collision box is not used at this zoom (for curved labels) - if (u_zoom >= v_max_zoom + v_perspective_zoom_adjust) { - gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0) * alpha * 0.25; - } - - // Faded blue = the placement scale for this label is beyond the CollisionTile - // max scale, so it's impossible for this label to show without collision detection - // being run again (the label's glyphs haven't even been added to the symbol bucket) - if (v_placement_zoom >= u_maxzoom) { - gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0) * alpha * 0.2; + if (v_notUsed > 0.5) { + // This box not used, fade it out + gl_FragColor *= .1; } } - )MBGL_SHADER"; } // namespace shaders diff --git a/src/mbgl/shaders/collision_circle.cpp b/src/mbgl/shaders/collision_circle.cpp new file mode 100644 index 0000000000..82ebbf05a0 --- /dev/null +++ b/src/mbgl/shaders/collision_circle.cpp @@ -0,0 +1,87 @@ +// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED. + +#include <mbgl/shaders/collision_circle.hpp> + +namespace mbgl { +namespace shaders { + +const char* collision_circle::name = "collision_circle"; +const char* collision_circle::vertexSource = R"MBGL_SHADER( +attribute vec2 a_pos; +attribute vec2 a_anchor_pos; +attribute vec2 a_extrude; +attribute vec2 a_placed; + +uniform mat4 u_matrix; +uniform vec2 u_extrude_scale; +uniform float u_camera_to_center_distance; + +varying float v_placed; +varying float v_notUsed; +varying float v_radius; + +varying vec2 v_extrude; +varying vec2 v_extrude_scale; + +void main() { + vec4 projectedPoint = u_matrix * vec4(a_anchor_pos, 0, 1); + highp float camera_to_anchor_distance = projectedPoint.w; + highp float collision_perspective_ratio = clamp( + 0.5 + 0.5 * (u_camera_to_center_distance / camera_to_anchor_distance), + 0.0, // Prevents oversized near-field circles in pitched/overzoomed tiles + 4.0); + + gl_Position = u_matrix * vec4(a_pos, 0.0, 1.0); + + highp float padding_factor = 1.2; // Pad the vertices slightly to make room for anti-alias blur + gl_Position.xy += a_extrude * u_extrude_scale * padding_factor * gl_Position.w * collision_perspective_ratio; + + v_placed = a_placed.x; + v_notUsed = a_placed.y; + v_radius = abs(a_extrude.y); // We don't pitch the circles, so both units of the extrusion vector are equal in magnitude to the radius + + v_extrude = a_extrude * padding_factor; + v_extrude_scale = u_extrude_scale * u_camera_to_center_distance * collision_perspective_ratio; +} + +)MBGL_SHADER"; +const char* collision_circle::fragmentSource = R"MBGL_SHADER( +uniform float u_overscale_factor; + +varying float v_placed; +varying float v_notUsed; +varying float v_radius; +varying vec2 v_extrude; +varying vec2 v_extrude_scale; + +void main() { + float alpha = 0.5; + + // Red = collision, hide label + vec4 color = vec4(1.0, 0.0, 0.0, 1.0) * alpha; + + // Blue = no collision, label is showing + if (v_placed > 0.5) { + color = vec4(0.0, 0.0, 1.0, 0.5) * alpha; + } + + if (v_notUsed > 0.5) { + // This box not used, fade it out + color *= .2; + } + + float extrude_scale_length = length(v_extrude_scale); + float extrude_length = length(v_extrude) * extrude_scale_length; + float stroke_width = 15.0 * extrude_scale_length / u_overscale_factor; + float radius = v_radius * extrude_scale_length; + + float distance_to_edge = abs(extrude_length - radius); + float opacity_t = smoothstep(-stroke_width, 0.0, -distance_to_edge); + + gl_FragColor = opacity_t * color; +} + +)MBGL_SHADER"; + +} // namespace shaders +} // namespace mbgl diff --git a/src/mbgl/shaders/collision_circle.hpp b/src/mbgl/shaders/collision_circle.hpp new file mode 100644 index 0000000000..12b1bcd445 --- /dev/null +++ b/src/mbgl/shaders/collision_circle.hpp @@ -0,0 +1,16 @@ +// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED. + +#pragma once + +namespace mbgl { +namespace shaders { + +class collision_circle { +public: + static const char* name; + static const char* vertexSource; + static const char* fragmentSource; +}; + +} // namespace shaders +} // namespace mbgl diff --git a/src/mbgl/shaders/debug.cpp b/src/mbgl/shaders/debug.cpp index d18f3be5d1..9012cfa755 100644 --- a/src/mbgl/shaders/debug.cpp +++ b/src/mbgl/shaders/debug.cpp @@ -12,12 +12,7 @@ attribute vec2 a_pos; uniform mat4 u_matrix; void main() { - // We are using Int16 for texture position coordinates to give us enough precision for - // fractional coordinates. We use 8192 to scale the texture coordinates in the buffer - // as an arbitrarily high number to preserve adequate precision when rendering. - // This is also the same value as the EXTENT we are using for our tile buffer pos coordinates, - // so math for modifying either is consistent. - gl_Position = u_matrix * vec4(a_pos, step(8192.0, a_pos.x), 1); + gl_Position = u_matrix * vec4(a_pos, 0, 1); } )MBGL_SHADER"; diff --git a/src/mbgl/shaders/fill_extrusion.cpp b/src/mbgl/shaders/fill_extrusion.cpp index 817f73391c..5bb2b9cd07 100644 --- a/src/mbgl/shaders/fill_extrusion.cpp +++ b/src/mbgl/shaders/fill_extrusion.cpp @@ -13,8 +13,7 @@ uniform lowp vec3 u_lightpos; uniform lowp float u_lightintensity; attribute vec2 a_pos; -attribute vec3 a_normal; -attribute float a_edgedistance; +attribute vec4 a_normal_ed; varying vec4 v_color; @@ -70,11 +69,12 @@ void main() { #endif + vec3 normal = a_normal_ed.xyz; + base = max(0.0, base); height = max(0.0, height); - float ed = a_edgedistance; // use each attrib in order to not trip a VAO assert - float t = mod(a_normal.x, 2.0); + float t = mod(normal.x, 2.0); gl_Position = u_matrix * vec4(a_pos, t > 0.0 ? height : base, 1); @@ -88,7 +88,7 @@ void main() { color += ambientlight; // Calculate cos(theta), where theta is the angle between surface normal and diffuse light ray - float directional = clamp(dot(a_normal / 16384.0, u_lightpos), 0.0, 1.0); + float directional = clamp(dot(normal / 16384.0, u_lightpos), 0.0, 1.0); // Adjust directional so that // the range of values for highlight/shading is narrower @@ -97,7 +97,7 @@ void main() { directional = mix((1.0 - u_lightintensity), max((1.0 - colorvalue + u_lightintensity), 1.0), directional); // Add gradient along z axis of side surfaces - if (a_normal.y != 0.0) { + if (normal.y != 0.0) { directional *= clamp((t + base) * pow(height / 150.0, 0.5), mix(0.7, 0.98, 1.0 - u_lightintensity), 1.0); } diff --git a/src/mbgl/shaders/fill_extrusion_pattern.cpp b/src/mbgl/shaders/fill_extrusion_pattern.cpp index d3e5eef1bf..466d0e04fe 100644 --- a/src/mbgl/shaders/fill_extrusion_pattern.cpp +++ b/src/mbgl/shaders/fill_extrusion_pattern.cpp @@ -22,8 +22,7 @@ uniform lowp vec3 u_lightpos; uniform lowp float u_lightintensity; attribute vec2 a_pos; -attribute vec3 a_normal; -attribute float a_edgedistance; +attribute vec4 a_normal_ed; varying vec2 v_pos_a; varying vec2 v_pos_b; @@ -65,26 +64,29 @@ void main() { #endif + vec3 normal = a_normal_ed.xyz; + float edgedistance = a_normal_ed.w; + base = max(0.0, base); height = max(0.0, height); - float t = mod(a_normal.x, 2.0); + float t = mod(normal.x, 2.0); float z = t > 0.0 ? height : base; gl_Position = u_matrix * vec4(a_pos, z, 1); - vec2 pos = a_normal.x == 1.0 && a_normal.y == 0.0 && a_normal.z == 16384.0 + vec2 pos = normal.x == 1.0 && normal.y == 0.0 && normal.z == 16384.0 ? a_pos // extrusion top - : vec2(a_edgedistance, z * u_height_factor); // extrusion side + : vec2(edgedistance, z * u_height_factor); // extrusion side v_pos_a = get_pattern_pos(u_pixel_coord_upper, u_pixel_coord_lower, u_scale_a * u_pattern_size_a, u_tile_units_to_pixels, pos); v_pos_b = get_pattern_pos(u_pixel_coord_upper, u_pixel_coord_lower, u_scale_b * u_pattern_size_b, u_tile_units_to_pixels, pos); v_lighting = vec4(0.0, 0.0, 0.0, 1.0); - float directional = clamp(dot(a_normal / 16383.0, u_lightpos), 0.0, 1.0); + float directional = clamp(dot(normal / 16383.0, u_lightpos), 0.0, 1.0); directional = mix((1.0 - u_lightintensity), max((0.5 + u_lightintensity), 1.0), directional); - if (a_normal.y != 0.0) { + if (normal.y != 0.0) { directional *= clamp((t + base) * pow(height / 150.0, 0.5), mix(0.7, 0.98, 1.0 - u_lightintensity), 1.0); } diff --git a/src/mbgl/shaders/hillshade.cpp b/src/mbgl/shaders/hillshade.cpp new file mode 100644 index 0000000000..4083faa4b4 --- /dev/null +++ b/src/mbgl/shaders/hillshade.cpp @@ -0,0 +1,80 @@ +// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED. + +#include <mbgl/shaders/hillshade.hpp> + +namespace mbgl { +namespace shaders { + +const char* hillshade::name = "hillshade"; +const char* hillshade::vertexSource = R"MBGL_SHADER( +uniform mat4 u_matrix; + +attribute vec2 a_pos; +attribute vec2 a_texture_pos; + +varying vec2 v_pos; + +void main() { + gl_Position = u_matrix * vec4(a_pos, 0, 1); + v_pos = a_texture_pos / 8192.0; +} + +)MBGL_SHADER"; +const char* hillshade::fragmentSource = R"MBGL_SHADER( +uniform sampler2D u_image; +varying vec2 v_pos; + +uniform vec2 u_latrange; +uniform vec2 u_light; +uniform vec4 u_shadow; +uniform vec4 u_highlight; +uniform vec4 u_accent; + +#define PI 3.141592653589793 + +void main() { + vec4 pixel = texture2D(u_image, v_pos); + + vec2 deriv = ((pixel.rg * 2.0) - 1.0); + + // We divide the slope by a scale factor based on the cosin of the pixel's approximate latitude + // to account for mercator projection distortion. see #4807 for details + float scaleFactor = cos(radians((u_latrange[0] - u_latrange[1]) * (1.0 - v_pos.y) + u_latrange[1])); + // We also multiply the slope by an arbitrary z-factor of 1.25 + float slope = atan(1.25 * length(deriv) / scaleFactor); + float aspect = deriv.x != 0.0 ? atan(deriv.y, -deriv.x) : PI / 2.0 * (deriv.y > 0.0 ? 1.0 : -1.0); + + float intensity = u_light.x; + // We add PI to make this property match the global light object, which adds PI/2 to the light's azimuthal + // position property to account for 0deg corresponding to north/the top of the viewport in the style spec + // and the original shader was written to accept (-illuminationDirection - 90) as the azimuthal. + float azimuth = u_light.y + PI; + + // We scale the slope exponentially based on intensity, using a calculation similar to + // the exponential interpolation function in the style spec: + // https://github.com/mapbox/mapbox-gl-js/blob/master/src/style-spec/expression/definitions/interpolate.js#L217-L228 + // so that higher intensity values create more opaque hillshading. + float base = 1.875 - intensity * 1.75; + float maxValue = 0.5 * PI; + float scaledSlope = intensity != 0.5 ? ((pow(base, slope) - 1.0) / (pow(base, maxValue) - 1.0)) * maxValue : slope; + + // The accent color is calculated with the cosine of the slope while the shade color is calculated with the sine + // so that the accent color's rate of change eases in while the shade color's eases out. + float accent = cos(scaledSlope); + // We multiply both the accent and shade color by a clamped intensity value + // so that intensities >= 0.5 do not additionally affect the color values + // while intensity values < 0.5 make the overall color more transparent. + vec4 accent_color = (1.0 - accent) * u_accent * clamp(intensity * 2.0, 0.0, 1.0); + float shade = abs(mod((aspect + azimuth) / PI + 0.5, 2.0) - 1.0); + vec4 shade_color = mix(u_shadow, u_highlight, shade) * sin(scaledSlope) * clamp(intensity * 2.0, 0.0, 1.0); + gl_FragColor = accent_color * (1.0 - shade_color.a) + shade_color; + +#ifdef OVERDRAW_INSPECTOR + gl_FragColor = vec4(1.0); +#endif +} + +)MBGL_SHADER"; + +} // namespace shaders +} // namespace mbgl diff --git a/src/mbgl/shaders/hillshade.hpp b/src/mbgl/shaders/hillshade.hpp new file mode 100644 index 0000000000..a4a27cb595 --- /dev/null +++ b/src/mbgl/shaders/hillshade.hpp @@ -0,0 +1,16 @@ +// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED. + +#pragma once + +namespace mbgl { +namespace shaders { + +class hillshade { +public: + static const char* name; + static const char* vertexSource; + static const char* fragmentSource; +}; + +} // namespace shaders +} // namespace mbgl diff --git a/src/mbgl/shaders/hillshade_prepare.cpp b/src/mbgl/shaders/hillshade_prepare.cpp new file mode 100644 index 0000000000..733658435e --- /dev/null +++ b/src/mbgl/shaders/hillshade_prepare.cpp @@ -0,0 +1,99 @@ +// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED. + +#include <mbgl/shaders/hillshade_prepare.hpp> + +namespace mbgl { +namespace shaders { + +const char* hillshade_prepare::name = "hillshade_prepare"; +const char* hillshade_prepare::vertexSource = R"MBGL_SHADER( +uniform mat4 u_matrix; + +attribute vec2 a_pos; +attribute vec2 a_texture_pos; + +varying vec2 v_pos; + +void main() { + gl_Position = u_matrix * vec4(a_pos, 0, 1); + v_pos = (a_texture_pos / 8192.0) / 2.0 + 0.25; +} + +)MBGL_SHADER"; +const char* hillshade_prepare::fragmentSource = R"MBGL_SHADER( +#ifdef GL_ES +precision highp float; +#endif + +uniform sampler2D u_image; +varying vec2 v_pos; +uniform vec2 u_dimension; +uniform float u_zoom; + +float getElevation(vec2 coord, float bias) { + // Convert encoded elevation value to meters + vec4 data = texture2D(u_image, coord) * 255.0; + return (data.r + data.g * 256.0 + data.b * 256.0 * 256.0) / 4.0; +} + +void main() { + vec2 epsilon = 1.0 / u_dimension; + + // queried pixels: + // +-----------+ + // | | | | + // | a | b | c | + // | | | | + // +-----------+ + // | | | | + // | d | e | f | + // | | | | + // +-----------+ + // | | | | + // | g | h | i | + // | | | | + // +-----------+ + + float a = getElevation(v_pos + vec2(-epsilon.x, -epsilon.y), 0.0); + float b = getElevation(v_pos + vec2(0, -epsilon.y), 0.0); + float c = getElevation(v_pos + vec2(epsilon.x, -epsilon.y), 0.0); + float d = getElevation(v_pos + vec2(-epsilon.x, 0), 0.0); + float e = getElevation(v_pos, 0.0); + float f = getElevation(v_pos + vec2(epsilon.x, 0), 0.0); + float g = getElevation(v_pos + vec2(-epsilon.x, epsilon.y), 0.0); + float h = getElevation(v_pos + vec2(0, epsilon.y), 0.0); + float i = getElevation(v_pos + vec2(epsilon.x, epsilon.y), 0.0); + + // here we divide the x and y slopes by 8 * pixel size + // where pixel size (aka meters/pixel) is: + // circumference of the world / (pixels per tile * number of tiles) + // which is equivalent to: 8 * 40075016.6855785 / (512 * pow(2, u_zoom)) + // which can be reduced to: pow(2, 19.25619978527 - u_zoom) + // we want to vertically exaggerate the hillshading though, because otherwise + // it is barely noticeable at low zooms. to do this, we multiply this by some + // scale factor pow(2, (u_zoom - 14) * a) where a is an arbitrary value and 14 is the + // maxzoom of the tile source. here we use a=0.3 which works out to the + // expression below. see nickidlugash's awesome breakdown for more info + // https://github.com/mapbox/mapbox-gl-js/pull/5286#discussion_r148419556 + float exaggeration = u_zoom < 2.0 ? 0.4 : u_zoom < 4.5 ? 0.35 : 0.3; + + vec2 deriv = vec2( + (c + f + f + i) - (a + d + d + g), + (g + h + h + i) - (a + b + b + c) + ) / pow(2.0, (u_zoom - 14.0) * exaggeration + 19.2562 - u_zoom); + + gl_FragColor = clamp(vec4( + deriv.x / 2.0 + 0.5, + deriv.y / 2.0 + 0.5, + 1.0, + 1.0), 0.0, 1.0); + +#ifdef OVERDRAW_INSPECTOR + gl_FragColor = vec4(1.0); +#endif +} + +)MBGL_SHADER"; + +} // namespace shaders +} // namespace mbgl diff --git a/src/mbgl/shaders/hillshade_prepare.hpp b/src/mbgl/shaders/hillshade_prepare.hpp new file mode 100644 index 0000000000..c38b4a0d19 --- /dev/null +++ b/src/mbgl/shaders/hillshade_prepare.hpp @@ -0,0 +1,16 @@ +// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED. + +#pragma once + +namespace mbgl { +namespace shaders { + +class hillshade_prepare { +public: + static const char* name; + static const char* vertexSource; + static const char* fragmentSource; +}; + +} // namespace shaders +} // namespace mbgl diff --git a/src/mbgl/shaders/preludes.cpp b/src/mbgl/shaders/preludes.cpp index feb185a684..6baa488a10 100644 --- a/src/mbgl/shaders/preludes.cpp +++ b/src/mbgl/shaders/preludes.cpp @@ -34,6 +34,10 @@ vec2 unpack_float(const float packedValue) { return vec2(v0, packedIntValue - v0 * 256); } +vec2 unpack_opacity(const float packedOpacity) { + int intOpacity = int(packedOpacity) / 2; + return vec2(float(intOpacity) / 127.0, mod(packedOpacity, 2.0)); +} // To minimize the number of attributes needed, we encode a 4-component // color into a pair of floats (i.e. a vec2) as follows: diff --git a/src/mbgl/shaders/symbol_icon.cpp b/src/mbgl/shaders/symbol_icon.cpp index 1e96194738..f5c2bbe22d 100644 --- a/src/mbgl/shaders/symbol_icon.cpp +++ b/src/mbgl/shaders/symbol_icon.cpp @@ -12,6 +12,7 @@ const float PI = 3.141592653589793; attribute vec4 a_pos_offset; attribute vec4 a_data; attribute vec3 a_projected_pos; +attribute float a_fade_opacity; uniform bool u_is_size_zoom_constant; uniform bool u_is_size_feature_constant; @@ -21,7 +22,7 @@ uniform highp float u_camera_to_center_distance; uniform highp float u_pitch; uniform bool u_rotate_symbol; uniform highp float u_aspect_ratio; -uniform highp float u_collision_y_stretch; +uniform float u_fade_change; #ifndef HAS_UNIFORM_u_opacity @@ -43,7 +44,7 @@ uniform bool u_pitch_with_map; uniform vec2 u_texsize; varying vec2 v_tex; -varying vec2 v_fade_tex; +varying float v_fade_opacity; void main() { @@ -60,9 +61,7 @@ void main() { vec2 a_tex = a_data.xy; vec2 a_size = a_data.zw; - highp vec2 angle_labelminzoom = unpack_float(a_projected_pos[2]); - highp float segment_angle = -angle_labelminzoom[0] / 255.0 * 2.0 * PI; - mediump float a_labelminzoom = angle_labelminzoom[1]; + highp float segment_angle = -a_projected_pos[2]; float size; if (!u_is_size_zoom_constant && !u_is_size_feature_constant) { @@ -106,19 +105,14 @@ void main() { gl_Position = u_gl_coord_matrix * vec4(projected_pos.xy / projected_pos.w + rotation_matrix * (a_offset / 64.0 * fontScale), 0.0, 1.0); v_tex = a_tex / u_texsize; - // See comments in symbol_sdf.vertex - highp float incidence_stretch = camera_to_anchor_distance / (u_camera_to_center_distance * cos(u_pitch)); - highp float collision_adjustment = max(1.0, incidence_stretch / u_collision_y_stretch); - - highp float collision_perspective_ratio = 1.0 + 0.5*((camera_to_anchor_distance / u_camera_to_center_distance) - 1.0); - highp float perspective_zoom_adjust = floor(log2(collision_perspective_ratio * collision_adjustment) * 10.0); - v_fade_tex = vec2((a_labelminzoom + perspective_zoom_adjust) / 255.0, 0.0); + vec2 fade_opacity = unpack_opacity(a_fade_opacity); + float fade_change = fade_opacity[1] > 0.5 ? u_fade_change : -u_fade_change; + v_fade_opacity = max(0.0, min(1.0, fade_opacity[0] + fade_change)); } )MBGL_SHADER"; const char* symbol_icon::fragmentSource = R"MBGL_SHADER( uniform sampler2D u_texture; -uniform sampler2D u_fadetexture; #ifndef HAS_UNIFORM_u_opacity @@ -129,7 +123,7 @@ uniform lowp float u_opacity; varying vec2 v_tex; -varying vec2 v_fade_tex; +varying float v_fade_opacity; void main() { @@ -138,7 +132,7 @@ void main() { #endif - lowp float alpha = texture2D(u_fadetexture, v_fade_tex).a * opacity; + lowp float alpha = opacity * v_fade_opacity; gl_FragColor = texture2D(u_texture, v_tex) * alpha; #ifdef OVERDRAW_INSPECTOR diff --git a/src/mbgl/shaders/symbol_sdf.cpp b/src/mbgl/shaders/symbol_sdf.cpp index a4427f31ab..441eaf7aac 100644 --- a/src/mbgl/shaders/symbol_sdf.cpp +++ b/src/mbgl/shaders/symbol_sdf.cpp @@ -12,6 +12,7 @@ const float PI = 3.141592653589793; attribute vec4 a_pos_offset; attribute vec4 a_data; attribute vec3 a_projected_pos; +attribute float a_fade_opacity; // contents of a_size vary based on the type of property value // used for {text,icon}-size. @@ -81,12 +82,12 @@ uniform highp float u_pitch; uniform bool u_rotate_symbol; uniform highp float u_aspect_ratio; uniform highp float u_camera_to_center_distance; -uniform highp float u_collision_y_stretch; +uniform float u_fade_change; uniform vec2 u_texsize; -varying vec4 v_data0; -varying vec2 v_data1; +varying vec2 v_data0; +varying vec3 v_data1; void main() { @@ -131,9 +132,7 @@ void main() { vec2 a_tex = a_data.xy; vec2 a_size = a_data.zw; - highp vec2 angle_labelminzoom = unpack_float(a_projected_pos[2]); - highp float segment_angle = -angle_labelminzoom[0] / 255.0 * 2.0 * PI; - mediump float a_labelminzoom = angle_labelminzoom[1]; + highp float segment_angle = -a_projected_pos[2]; float size; if (!u_is_size_zoom_constant && !u_is_size_feature_constant) { @@ -185,31 +184,12 @@ void main() { float gamma_scale = gl_Position.w; vec2 tex = a_tex / u_texsize; - // incidence_stretch is the ratio of how much y space a label takes up on a tile while drawn perpendicular to the viewport vs - // how much space it would take up if it were drawn flat on the tile - // Using law of sines, camera_to_anchor/sin(ground_angle) = camera_to_center/sin(incidence_angle) - // sin(incidence_angle) = 1/incidence_stretch - // Incidence angle 90 -> head on, sin(incidence_angle) = 1, no incidence stretch - // Incidence angle 1 -> very oblique, sin(incidence_angle) =~ 0, lots of incidence stretch - // ground_angle = u_pitch + PI/2 -> sin(ground_angle) = cos(u_pitch) - // This 2D calculation is only exactly correct when gl_Position.x is in the center of the viewport, - // but it's a close enough approximation for our purposes - highp float incidence_stretch = camera_to_anchor_distance / (u_camera_to_center_distance * cos(u_pitch)); - // incidence_stretch only applies to the y-axis, but without re-calculating the collision tile, we can't - // adjust the size of only one axis. So, we do a crude approximation at placement time to get the aspect ratio - // about right, and then do the rest of the adjustment here: there will be some extra padding on the x-axis, - // but hopefully not too much. - // Never make the adjustment less than 1.0: instead of allowing collisions on the x-axis, be conservative on - // the y-axis. - highp float collision_adjustment = max(1.0, incidence_stretch / u_collision_y_stretch); - - // Floor to 1/10th zoom to dodge precision issues that can cause partially hidden labels - highp float collision_perspective_ratio = 1.0 + 0.5*((camera_to_anchor_distance / u_camera_to_center_distance) - 1.0); - highp float perspective_zoom_adjust = floor(log2(collision_perspective_ratio * collision_adjustment) * 10.0); - vec2 fade_tex = vec2((a_labelminzoom + perspective_zoom_adjust) / 255.0, 0.0); - - v_data0 = vec4(tex.x, tex.y, fade_tex.x, fade_tex.y); - v_data1 = vec2(gamma_scale, size); + vec2 fade_opacity = unpack_opacity(a_fade_opacity); + float fade_change = fade_opacity[1] > 0.5 ? u_fade_change : -u_fade_change; + float interpolated_fade_opacity = max(0.0, min(1.0, fade_opacity[0] + fade_change)); + + v_data0 = vec2(tex.x, tex.y); + v_data1 = vec3(gamma_scale, size, interpolated_fade_opacity); } )MBGL_SHADER"; @@ -255,12 +235,11 @@ uniform lowp float u_halo_blur; uniform sampler2D u_texture; -uniform sampler2D u_fadetexture; uniform highp float u_gamma_scale; uniform bool u_is_text; -varying vec4 v_data0; -varying vec2 v_data1; +varying vec2 v_data0; +varying vec3 v_data1; void main() { @@ -290,9 +269,9 @@ void main() { vec2 tex = v_data0.xy; - vec2 fade_tex = v_data0.zw; float gamma_scale = v_data1.x; float size = v_data1.y; + float fade_opacity = v_data1[2]; float fontScale = u_is_text ? size / 24.0 : size; @@ -306,11 +285,10 @@ void main() { } lowp float dist = texture2D(u_texture, tex).a; - lowp float fade_alpha = texture2D(u_fadetexture, fade_tex).a; highp float gamma_scaled = gamma * gamma_scale; - highp float alpha = smoothstep(buff - gamma_scaled, buff + gamma_scaled, dist) * fade_alpha; + highp float alpha = smoothstep(buff - gamma_scaled, buff + gamma_scaled, dist); - gl_FragColor = color * (alpha * opacity); + gl_FragColor = color * (alpha * opacity * fade_opacity); #ifdef OVERDRAW_INSPECTOR gl_FragColor = vec4(1.0); diff --git a/src/mbgl/storage/asset_file_source.hpp b/src/mbgl/storage/asset_file_source.hpp index 6ed7af8aaf..5d98b4e69e 100644 --- a/src/mbgl/storage/asset_file_source.hpp +++ b/src/mbgl/storage/asset_file_source.hpp @@ -15,6 +15,8 @@ public: std::unique_ptr<AsyncRequest> request(const Resource&, Callback) override; + static bool acceptsURL(const std::string& url); + private: class Impl; diff --git a/src/mbgl/style/conversion/constant.cpp b/src/mbgl/style/conversion/constant.cpp new file mode 100644 index 0000000000..e837c4e70b --- /dev/null +++ b/src/mbgl/style/conversion/constant.cpp @@ -0,0 +1,94 @@ +#include <mbgl/style/conversion/constant.hpp> + +namespace mbgl { +namespace style { +namespace conversion { + +optional<bool> Converter<bool>::operator()(const Convertible& value, Error& error) const { + optional<bool> converted = toBool(value); + if (!converted) { + error = { "value must be a boolean" }; + return {}; + } + return *converted; +} + +optional<float> Converter<float>::operator()(const Convertible& value, Error& error) const { + optional<float> converted = toNumber(value); + if (!converted) { + error = { "value must be a number" }; + return {}; + } + return *converted; +} + +optional<std::string> Converter<std::string>::operator()(const Convertible& value, Error& error) const { + optional<std::string> converted = toString(value); + if (!converted) { + error = { "value must be a string" }; + return {}; + } + return *converted; +} + +optional<Color> Converter<Color>::operator()(const Convertible& value, Error& error) const { + optional<std::string> string = toString(value); + if (!string) { + error = { "value must be a string" }; + return {}; + } + + optional<Color> color = Color::parse(*string); + if (!color) { + error = { "value must be a valid color" }; + return {}; + } + + return *color; +} + +optional<std::vector<float>> Converter<std::vector<float>>::operator()(const Convertible& value, Error& error) const { + if (!isArray(value)) { + error = { "value must be an array" }; + return {}; + } + + std::vector<float> result; + result.reserve(arrayLength(value)); + + for (std::size_t i = 0; i < arrayLength(value); ++i) { + optional<float> number = toNumber(arrayMember(value, i)); + if (!number) { + error = { "value must be an array of numbers" }; + return {}; + } + result.push_back(*number); + } + + return result; +} + +optional<std::vector<std::string>> Converter<std::vector<std::string>>::operator()(const Convertible& value, Error& error) const { + if (!isArray(value)) { + error = { "value must be an array" }; + return {}; + } + + std::vector<std::string> result; + result.reserve(arrayLength(value)); + + for (std::size_t i = 0; i < arrayLength(value); ++i) { + optional<std::string> string = toString(arrayMember(value, i)); + if (!string) { + error = { "value must be an array of strings" }; + return {}; + } + result.push_back(*string); + } + + return result; +} + +} // namespace conversion +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/conversion/coordinate.cpp b/src/mbgl/style/conversion/coordinate.cpp new file mode 100644 index 0000000000..9b2be3381e --- /dev/null +++ b/src/mbgl/style/conversion/coordinate.cpp @@ -0,0 +1,29 @@ +#include <mbgl/style/conversion/coordinate.hpp> + +namespace mbgl { +namespace style { +namespace conversion { + +optional<LatLng> Converter<LatLng>::operator() (const Convertible& value, Error& error) const { + if (!isArray(value) || arrayLength(value) < 2 ) { + error = { "coordinate array must contain numeric longitude and latitude values" }; + return {}; + } + //Style spec uses GeoJSON convention for specifying coordinates + optional<double> latitude = toDouble(arrayMember(value, 1)); + optional<double> longitude = toDouble(arrayMember(value, 0)); + + if (!latitude || !longitude) { + error = { "coordinate array must contain numeric longitude and latitude values" }; + return {}; + } + if (*latitude < -90 || *latitude > 90 ){ + error = { "coordinate latitude must be between -90 and 90" }; + return {}; + } + return LatLng(*latitude, *longitude); +} + +} // namespace conversion +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/conversion/filter.cpp b/src/mbgl/style/conversion/filter.cpp new file mode 100644 index 0000000000..bb7bb6ea98 --- /dev/null +++ b/src/mbgl/style/conversion/filter.cpp @@ -0,0 +1,248 @@ +#include <mbgl/style/conversion/filter.hpp> +#include <mbgl/util/geometry.hpp> + +namespace mbgl { +namespace style { +namespace conversion { + +static optional<Value> normalizeValue(const optional<Value>& value, Error& error) { + if (!value) { + error = { "filter expression value must be a boolean, number, or string" }; + return {}; + } else { + return *value; + } +} + +static optional<FeatureType> toFeatureType(const Convertible& value, Error& error) { + optional<std::string> type = toString(value); + if (!type) { + error = { "value for $type filter must be a string" }; + return {}; + } else if (*type == "Point") { + return FeatureType::Point; + } else if (*type == "LineString") { + return FeatureType::LineString; + } else if (*type == "Polygon") { + return FeatureType::Polygon; + } else { + error = { "value for $type filter must be Point, LineString, or Polygon" }; + return {}; + } +} + +static optional<FeatureIdentifier> toFeatureIdentifier(const Convertible& value, Error& error) { + optional<Value> identifier = toValue(value); + if (!identifier) { + error = { "filter expression value must be a boolean, number, or string" }; + return {}; + } else { + return (*identifier).match( + [] (uint64_t t) -> optional<FeatureIdentifier> { return { t }; }, + [] ( int64_t t) -> optional<FeatureIdentifier> { return { t }; }, + [] ( double t) -> optional<FeatureIdentifier> { return { t }; }, + [] (const std::string& t) -> optional<FeatureIdentifier> { return { t }; }, + [&] (const auto&) -> optional<FeatureIdentifier> { + error = { "filter expression value must be a boolean, number, or string" }; + return {}; + }); + } +} + +template <class FilterType, class IdentifierFilterType> +optional<Filter> convertUnaryFilter(const Convertible& value, Error& error) { + if (arrayLength(value) < 2) { + error = { "filter expression must have 2 elements" }; + return {}; + } + + optional<std::string> key = toString(arrayMember(value, 1)); + if (!key) { + error = { "filter expression key must be a string" }; + return {}; + } + + if (*key == "$id") { + return { IdentifierFilterType {} }; + } else { + return { FilterType { *key } }; + } +} + +template <class FilterType, class TypeFilterType, class IdentifierFilterType> +optional<Filter> convertEqualityFilter(const Convertible& value, Error& error) { + if (arrayLength(value) < 3) { + error = { "filter expression must have 3 elements" }; + return {}; + } + + optional<std::string> key = toString(arrayMember(value, 1)); + if (!key) { + error = { "filter expression key must be a string" }; + return {}; + } + + if (*key == "$type") { + optional<FeatureType> filterValue = toFeatureType(arrayMember(value, 2), error); + if (!filterValue) { + return {}; + } + + return { TypeFilterType { *filterValue } }; + + } else if (*key == "$id") { + optional<FeatureIdentifier> filterValue = toFeatureIdentifier(arrayMember(value, 2), error); + if (!filterValue) { + return {}; + } + + return { IdentifierFilterType { *filterValue } }; + + } else { + optional<Value> filterValue = normalizeValue(toValue(arrayMember(value, 2)), error); + if (!filterValue) { + return {}; + } + + return { FilterType { *key, *filterValue } }; + } +} + +template <class FilterType> +optional<Filter> convertBinaryFilter(const Convertible& value, Error& error) { + if (arrayLength(value) < 3) { + error = { "filter expression must have 3 elements" }; + return {}; + } + + optional<std::string> key = toString(arrayMember(value, 1)); + if (!key) { + error = { "filter expression key must be a string" }; + return {}; + } + + optional<Value> filterValue = normalizeValue(toValue(arrayMember(value, 2)), error); + if (!filterValue) { + return {}; + } + + return { FilterType { *key, *filterValue } }; +} + +template <class FilterType, class TypeFilterType, class IdentifierFilterType> +optional<Filter> convertSetFilter(const Convertible& value, Error& error) { + if (arrayLength(value) < 2) { + error = { "filter expression must at least 2 elements" }; + return {}; + } + + optional<std::string> key = toString(arrayMember(value, 1)); + if (!key) { + error = { "filter expression key must be a string" }; + return {}; + } + + if (*key == "$type") { + std::vector<FeatureType> values; + for (std::size_t i = 2; i < arrayLength(value); ++i) { + optional<FeatureType> filterValue = toFeatureType(arrayMember(value, i), error); + if (!filterValue) { + return {}; + } + values.push_back(*filterValue); + } + + return { TypeFilterType { std::move(values) } }; + + } else if (*key == "$id") { + std::vector<FeatureIdentifier> values; + for (std::size_t i = 2; i < arrayLength(value); ++i) { + optional<FeatureIdentifier> filterValue = toFeatureIdentifier(arrayMember(value, i), error); + if (!filterValue) { + return {}; + } + values.push_back(*filterValue); + } + + return { IdentifierFilterType { std::move(values) } }; + + } else { + std::vector<Value> values; + for (std::size_t i = 2; i < arrayLength(value); ++i) { + optional<Value> filterValue = normalizeValue(toValue(arrayMember(value, i)), error); + if (!filterValue) { + return {}; + } + values.push_back(*filterValue); + } + + return { FilterType { *key, std::move(values) } }; + } +} + +template <class FilterType> +optional<Filter> convertCompoundFilter(const Convertible& value, Error& error) { + std::vector<Filter> filters; + for (std::size_t i = 1; i < arrayLength(value); ++i) { + optional<Filter> element = convert<Filter>(arrayMember(value, i), error); + if (!element) { + return {}; + } + filters.push_back(*element); + } + + return { FilterType { std::move(filters) } }; +} + +optional<Filter> Converter<Filter>::operator()(const Convertible& value, Error& error) const { + if (!isArray(value)) { + error = { "filter expression must be an array" }; + return {}; + } + + if (arrayLength(value) < 1) { + error = { "filter expression must have at least 1 element" }; + return {}; + } + + optional<std::string> op = toString(arrayMember(value, 0)); + if (!op) { + error = { "filter operator must be a string" }; + return {}; + } + + if (*op == "==") { + return convertEqualityFilter<EqualsFilter, TypeEqualsFilter, IdentifierEqualsFilter>(value, error); + } else if (*op == "!=") { + return convertEqualityFilter<NotEqualsFilter, TypeNotEqualsFilter, IdentifierNotEqualsFilter>(value, error); + } else if (*op == ">") { + return convertBinaryFilter<GreaterThanFilter>(value, error); + } else if (*op == ">=") { + return convertBinaryFilter<GreaterThanEqualsFilter>(value, error); + } else if (*op == "<") { + return convertBinaryFilter<LessThanFilter>(value, error); + } else if (*op == "<=") { + return convertBinaryFilter<LessThanEqualsFilter>(value, error); + } else if (*op == "in") { + return convertSetFilter<InFilter, TypeInFilter, IdentifierInFilter>(value, error); + } else if (*op == "!in") { + return convertSetFilter<NotInFilter, TypeNotInFilter, IdentifierNotInFilter>(value, error); + } else if (*op == "all") { + return convertCompoundFilter<AllFilter>(value, error); + } else if (*op == "any") { + return convertCompoundFilter<AnyFilter>(value, error); + } else if (*op == "none") { + return convertCompoundFilter<NoneFilter>(value, error); + } else if (*op == "has") { + return convertUnaryFilter<HasFilter, HasIdentifierFilter>(value, error); + } else if (*op == "!has") { + return convertUnaryFilter<NotHasFilter, NotHasIdentifierFilter>(value, error); + } + + error = { R"(filter operator must be one of "==", "!=", ">", ">=", "<", "<=", "in", "!in", "all", "any", "none", "has", or "!has")" }; + return {}; +} + +} // namespace conversion +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/conversion/geojson.cpp b/src/mbgl/style/conversion/geojson.cpp index 8103e9014a..e39a1a80eb 100644 --- a/src/mbgl/style/conversion/geojson.cpp +++ b/src/mbgl/style/conversion/geojson.cpp @@ -1,26 +1,16 @@ #include <mbgl/style/conversion/geojson.hpp> #include <mbgl/style/conversion/json.hpp> -#include <mbgl/util/rapidjson.hpp> - -#include <mapbox/geojson.hpp> -#include <mapbox/geojson/rapidjson.hpp> namespace mbgl { namespace style { namespace conversion { -optional<GeoJSON> Converter<GeoJSON>::operator()(const std::string& value, Error& error) const { - return convertJSON<GeoJSON>(value, error); +optional<GeoJSON> Converter<GeoJSON>::operator()(const Convertible& value, Error& error) const { + return toGeoJSON(value, error); } -template <> -optional<GeoJSON> Converter<GeoJSON>::operator()(const JSValue& value, Error& error) const { - try { - return mapbox::geojson::convert(value); - } catch (const std::exception& ex) { - error = { ex.what() }; - return {}; - } +optional<GeoJSON> parseGeoJSON(const std::string& value, Error& error) { + return convertJSON<GeoJSON>(value, error); } } // namespace conversion diff --git a/src/mbgl/style/conversion/geojson_options.cpp b/src/mbgl/style/conversion/geojson_options.cpp new file mode 100644 index 0000000000..a2c5ed8816 --- /dev/null +++ b/src/mbgl/style/conversion/geojson_options.cpp @@ -0,0 +1,85 @@ +#include <mbgl/style/conversion/geojson_options.hpp> + +namespace mbgl { +namespace style { +namespace conversion { + +optional<GeoJSONOptions> Converter<GeoJSONOptions>::operator()(const Convertible& value, Error& error) const { + GeoJSONOptions options; + + const auto minzoomValue = objectMember(value, "minzoom"); + if (minzoomValue) { + if (toNumber(*minzoomValue)) { + options.minzoom = static_cast<uint8_t>(*toNumber(*minzoomValue)); + } else { + error = { "GeoJSON source minzoom value must be a number" }; + return {}; + } + } + + const auto maxzoomValue = objectMember(value, "maxzoom"); + if (maxzoomValue) { + if (toNumber(*maxzoomValue)) { + options.maxzoom = static_cast<uint8_t>(*toNumber(*maxzoomValue)); + } else { + error = { "GeoJSON source maxzoom value must be a number" }; + return {}; + } + } + + const auto bufferValue = objectMember(value, "buffer"); + if (bufferValue) { + if (toNumber(*bufferValue)) { + options.buffer = static_cast<uint16_t>(*toNumber(*bufferValue)); + } else { + error = { "GeoJSON source buffer value must be a number" }; + return {}; + } + } + + const auto toleranceValue = objectMember(value, "tolerance"); + if (toleranceValue) { + if (toNumber(*toleranceValue)) { + options.tolerance = static_cast<double>(*toNumber(*toleranceValue)); + } else { + error = { "GeoJSON source tolerance value must be a number" }; + return {}; + } + } + + const auto clusterValue = objectMember(value, "cluster"); + if (clusterValue) { + if (toBool(*clusterValue)) { + options.cluster = *toBool(*clusterValue); + } else { + error = { "GeoJSON source cluster value must be a boolean" }; + return {}; + } + } + + const auto clusterMaxZoomValue = objectMember(value, "clusterMaxZoom"); + if (clusterMaxZoomValue) { + if (toNumber(*clusterMaxZoomValue)) { + options.clusterMaxZoom = static_cast<uint8_t>(*toNumber(*clusterMaxZoomValue)); + } else { + error = { "GeoJSON source clusterMaxZoom value must be a number" }; + return {}; + } + } + + const auto clusterRadiusValue = objectMember(value, "clusterRadius"); + if (clusterRadiusValue) { + if (toNumber(*clusterRadiusValue)) { + options.clusterRadius = static_cast<double>(*toNumber(*clusterRadiusValue)); + } else { + error = { "GeoJSON source clusterRadius value must be a number" }; + return {}; + } + } + + return { options }; +} + +} // namespace conversion +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/conversion/get_json_type.cpp b/src/mbgl/style/conversion/get_json_type.cpp new file mode 100644 index 0000000000..cd3b4608b1 --- /dev/null +++ b/src/mbgl/style/conversion/get_json_type.cpp @@ -0,0 +1,34 @@ +#include <mbgl/style/conversion/get_json_type.hpp> +#include <mbgl/util/feature.hpp> + +namespace mbgl { +namespace style { +namespace conversion { + +std::string getJSONType(const Convertible& value) { + if (isUndefined(value)) { + return "null"; + } + if (isArray(value)) { + return "array"; + } + if (isObject(value)) { + return "object"; + } + optional<mbgl::Value> v = toValue(value); + + // Since we've already checked the non-atomic types above, value must then + // be a string, number, or boolean -- thus, assume that the toValue() + // conversion succeeds. + assert(v); + + return v->match( + [&] (const std::string&) { return "string"; }, + [&] (bool) { return "boolean"; }, + [&] (auto) { return "number"; } + ); +} + +} // namespace conversion +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/conversion/json.hpp b/src/mbgl/style/conversion/json.hpp index 0817ac09df..7dd2378f6b 100644 --- a/src/mbgl/style/conversion/json.hpp +++ b/src/mbgl/style/conversion/json.hpp @@ -20,7 +20,7 @@ optional<T> convertJSON(const std::string& json, Error& error, Args&&...args) { return {}; } - return convert<T, JSValue>(document, error, std::forward<Args>(args)...); + return convert<T>(document, error, std::forward<Args>(args)...); } } // namespace conversion diff --git a/src/mbgl/style/conversion/layer.cpp b/src/mbgl/style/conversion/layer.cpp new file mode 100644 index 0000000000..ad6998341d --- /dev/null +++ b/src/mbgl/style/conversion/layer.cpp @@ -0,0 +1,229 @@ +#include <mbgl/style/conversion/layer.hpp> +#include <mbgl/style/conversion/constant.hpp> +#include <mbgl/style/conversion/filter.hpp> +#include <mbgl/style/conversion/make_property_setters.hpp> +#include <mbgl/style/layers/background_layer.hpp> +#include <mbgl/style/layers/circle_layer.hpp> +#include <mbgl/style/layers/fill_layer.hpp> +#include <mbgl/style/layers/fill_extrusion_layer.hpp> +#include <mbgl/style/layers/hillshade_layer.hpp> +#include <mbgl/style/layers/line_layer.hpp> +#include <mbgl/style/layers/raster_layer.hpp> +#include <mbgl/style/layers/symbol_layer.hpp> + +namespace mbgl { +namespace style { +namespace conversion { + +optional<Error> setLayoutProperty(Layer& layer, const std::string& name, const Convertible& value) { + static const auto setters = makeLayoutPropertySetters(); + auto it = setters.find(name); + if (it == setters.end()) { + return Error { "property not found" }; + } + return it->second(layer, value); +} + +optional<Error> setPaintProperty(Layer& layer, const std::string& name, const Convertible& value) { + static const auto setters = makePaintPropertySetters(); + auto it = setters.find(name); + if (it == setters.end()) { + return Error { "property not found" }; + } + return it->second(layer, value); +} + +optional<Error> setPaintProperties(Layer& layer, const Convertible& value) { + auto paintValue = objectMember(value, "paint"); + if (!paintValue) { + return {}; + } + if (!isObject(*paintValue)) { + return { { "paint must be an object" } }; + } + return eachMember(*paintValue, [&] (const std::string& k, const Convertible& v) { + return setPaintProperty(layer, k, v); + }); +} + +template <class LayerType> +optional<std::unique_ptr<Layer>> convertVectorLayer(const std::string& id, const Convertible& value, Error& error) { + auto sourceValue = objectMember(value, "source"); + if (!sourceValue) { + error = { "layer must have a source" }; + return {}; + } + + optional<std::string> source = toString(*sourceValue); + if (!source) { + error = { "layer source must be a string" }; + return {}; + } + + std::unique_ptr<LayerType> layer = std::make_unique<LayerType>(id, *source); + + auto sourceLayerValue = objectMember(value, "source-layer"); + if (sourceLayerValue) { + optional<std::string> sourceLayer = toString(*sourceLayerValue); + if (!sourceLayer) { + error = { "layer source-layer must be a string" }; + return {}; + } + layer->setSourceLayer(*sourceLayer); + } + + auto filterValue = objectMember(value, "filter"); + if (filterValue) { + optional<Filter> filter = convert<Filter>(*filterValue, error); + if (!filter) { + return {}; + } + layer->setFilter(*filter); + } + + return { std::move(layer) }; +} + +static optional<std::unique_ptr<Layer>> convertRasterLayer(const std::string& id, const Convertible& value, Error& error) { + auto sourceValue = objectMember(value, "source"); + if (!sourceValue) { + error = { "layer must have a source" }; + return {}; + } + + optional<std::string> source = toString(*sourceValue); + if (!source) { + error = { "layer source must be a string" }; + return {}; + } + + return { std::make_unique<RasterLayer>(id, *source) }; +} + +static optional<std::unique_ptr<Layer>> convertHillshadeLayer(const std::string& id, const Convertible& value, Error& error) { + auto sourceValue = objectMember(value, "source"); + if (!sourceValue) { + error = { "layer must have a source" }; + return {}; + } + + optional<std::string> source = toString(*sourceValue); + if (!source) { + error = { "layer source must be a string" }; + return {}; + } + + return { std::make_unique<HillshadeLayer>(id, *source) }; +} + + +static optional<std::unique_ptr<Layer>> convertBackgroundLayer(const std::string& id, const Convertible&, Error&) { + return { std::make_unique<BackgroundLayer>(id) }; +} + +optional<std::unique_ptr<Layer>> Converter<std::unique_ptr<Layer>>::operator()(const Convertible& value, Error& error) const { + if (!isObject(value)) { + error = { "layer must be an object" }; + return {}; + } + + auto idValue = objectMember(value, "id"); + if (!idValue) { + error = { "layer must have an id" }; + return {}; + } + + optional<std::string> id = toString(*idValue); + if (!id) { + error = { "layer id must be a string" }; + return {}; + } + + auto typeValue = objectMember(value, "type"); + if (!typeValue) { + error = { "layer must have a type" }; + return {}; + } + + optional<std::string> type = toString(*typeValue); + if (!type) { + error = { "layer type must be a string" }; + return {}; + } + + optional<std::unique_ptr<Layer>> converted; + + if (*type == "fill") { + converted = convertVectorLayer<FillLayer>(*id, value, error); + } else if (*type == "fill-extrusion") { + converted = convertVectorLayer<FillExtrusionLayer>(*id, value, error); + } else if (*type == "line") { + converted = convertVectorLayer<LineLayer>(*id, value, error); + } else if (*type == "circle") { + converted = convertVectorLayer<CircleLayer>(*id, value, error); + } else if (*type == "symbol") { + converted = convertVectorLayer<SymbolLayer>(*id, value, error); + } else if (*type == "raster") { + converted = convertRasterLayer(*id, value, error); + } else if (*type == "hillshade") { + converted = convertHillshadeLayer(*id, value, error); + } else if (*type == "background") { + converted = convertBackgroundLayer(*id, value, error); + } else { + error = { "invalid layer type" }; + return {}; + } + + if (!converted) { + return converted; + } + + std::unique_ptr<Layer> layer = std::move(*converted); + + auto minzoomValue = objectMember(value, "minzoom"); + if (minzoomValue) { + optional<float> minzoom = toNumber(*minzoomValue); + if (!minzoom) { + error = { "minzoom must be numeric" }; + return {}; + } + layer->setMinZoom(*minzoom); + } + + auto maxzoomValue = objectMember(value, "maxzoom"); + if (maxzoomValue) { + optional<float> maxzoom = toNumber(*maxzoomValue); + if (!maxzoom) { + error = { "maxzoom must be numeric" }; + return {}; + } + layer->setMaxZoom(*maxzoom); + } + + auto layoutValue = objectMember(value, "layout"); + if (layoutValue) { + if (!isObject(*layoutValue)) { + error = { "layout must be an object" }; + return {}; + } + optional<Error> error_ = eachMember(*layoutValue, [&] (const std::string& k, const Convertible& v) { + return setLayoutProperty(*layer, k, v); + }); + if (error_) { + error = *error_; + return {}; + } + } + + optional<Error> error_ = setPaintProperties(*layer, value); + if (error_) { + error = *error_; + return {}; + } + + return std::move(layer); +} + +} // namespace conversion +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/conversion/light.cpp b/src/mbgl/style/conversion/light.cpp new file mode 100644 index 0000000000..f521f74386 --- /dev/null +++ b/src/mbgl/style/conversion/light.cpp @@ -0,0 +1,115 @@ +#include <mbgl/style/conversion/light.hpp> +#include <mbgl/style/conversion/position.hpp> +#include <mbgl/style/conversion/property_value.hpp> +#include <mbgl/style/conversion/transition_options.hpp> + +namespace mbgl { +namespace style { +namespace conversion { + +optional<Light> Converter<Light>::operator()(const Convertible& value, Error& error) const { + if (!isObject(value)) { + error = { "light must be an object" }; + return {}; + } + + Light light; + + const auto anchor = objectMember(value, "anchor"); + if (anchor) { + optional<PropertyValue<LightAnchorType>> convertedAnchor = + convert<PropertyValue<LightAnchorType>>(*anchor, error); + + if (convertedAnchor) { + light.setAnchor(*convertedAnchor); + } else { + return {}; + } + } + + const auto anchorTransition = objectMember(value, "anchor-transition"); + if (anchorTransition) { + optional<TransitionOptions> transition = + convert<TransitionOptions>(*anchorTransition, error); + if (transition) { + light.setAnchorTransition(*transition); + } else { + return {}; + } + } + + const auto color = objectMember(value, "color"); + if (color) { + optional<PropertyValue<Color>> convertedColor = + convert<PropertyValue<Color>>(*color, error); + + if (convertedColor) { + light.setColor(*convertedColor); + } else { + return {}; + } + } + + const auto colorTransition = objectMember(value, "color-transition"); + if (colorTransition) { + optional<TransitionOptions> transition = + convert<TransitionOptions>(*colorTransition, error); + if (transition) { + light.setColorTransition(*transition); + } else { + return {}; + } + } + + const auto position = objectMember(value, "position"); + if (position) { + optional<PropertyValue<Position>> convertedPosition = + convert<PropertyValue<Position>>(*position, error); + + if (convertedPosition) { + light.setPosition(*convertedPosition); + } else { + return {}; + } + } + + const auto positionTransition = objectMember(value, "position-transition"); + if (positionTransition) { + optional<TransitionOptions> transition = + convert<TransitionOptions>(*positionTransition, error); + if (transition) { + light.setPositionTransition(*transition); + } else { + return {}; + } + } + + const auto intensity = objectMember(value, "intensity"); + if (intensity) { + optional<PropertyValue<float>> convertedIntensity = + convert<PropertyValue<float>>(*intensity, error); + + if (convertedIntensity) { + light.setIntensity(*convertedIntensity); + } else { + return {}; + } + } + + const auto intensityTransition = objectMember(value, "intensity-transition"); + if (intensityTransition) { + optional<TransitionOptions> transition = + convert<TransitionOptions>(*intensityTransition, error); + if (transition) { + light.setIntensityTransition(*transition); + } else { + return {}; + } + } + + return { std::move(light) }; +} + +} // namespace conversion +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/conversion/make_property_setters.hpp b/src/mbgl/style/conversion/make_property_setters.hpp new file mode 100644 index 0000000000..adfcc4dd61 --- /dev/null +++ b/src/mbgl/style/conversion/make_property_setters.hpp @@ -0,0 +1,224 @@ +#pragma once + +// This file is generated. Edit make_property_setters.hpp.ejs, then run `make style-code`. + +#include <mbgl/style/conversion/property_setter.hpp> + +#include <mbgl/style/layers/fill_layer.hpp> +#include <mbgl/style/layers/line_layer.hpp> +#include <mbgl/style/layers/symbol_layer.hpp> +#include <mbgl/style/layers/circle_layer.hpp> +#include <mbgl/style/layers/fill_extrusion_layer.hpp> +#include <mbgl/style/layers/raster_layer.hpp> +#include <mbgl/style/layers/hillshade_layer.hpp> +#include <mbgl/style/layers/background_layer.hpp> + +#include <unordered_map> + +namespace mbgl { +namespace style { +namespace conversion { + +inline auto makeLayoutPropertySetters() { + std::unordered_map<std::string, PropertySetter> result; + + result["visibility"] = &setVisibility; + + + result["line-cap"] = &setProperty<LineLayer, PropertyValue<LineCapType>, &LineLayer::setLineCap>; + result["line-join"] = &setProperty<LineLayer, DataDrivenPropertyValue<LineJoinType>, &LineLayer::setLineJoin>; + result["line-miter-limit"] = &setProperty<LineLayer, PropertyValue<float>, &LineLayer::setLineMiterLimit>; + result["line-round-limit"] = &setProperty<LineLayer, PropertyValue<float>, &LineLayer::setLineRoundLimit>; + + result["symbol-placement"] = &setProperty<SymbolLayer, PropertyValue<SymbolPlacementType>, &SymbolLayer::setSymbolPlacement>; + result["symbol-spacing"] = &setProperty<SymbolLayer, PropertyValue<float>, &SymbolLayer::setSymbolSpacing>; + result["symbol-avoid-edges"] = &setProperty<SymbolLayer, PropertyValue<bool>, &SymbolLayer::setSymbolAvoidEdges>; + result["icon-allow-overlap"] = &setProperty<SymbolLayer, PropertyValue<bool>, &SymbolLayer::setIconAllowOverlap>; + result["icon-ignore-placement"] = &setProperty<SymbolLayer, PropertyValue<bool>, &SymbolLayer::setIconIgnorePlacement>; + result["icon-optional"] = &setProperty<SymbolLayer, PropertyValue<bool>, &SymbolLayer::setIconOptional>; + result["icon-rotation-alignment"] = &setProperty<SymbolLayer, PropertyValue<AlignmentType>, &SymbolLayer::setIconRotationAlignment>; + result["icon-size"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setIconSize>; + result["icon-text-fit"] = &setProperty<SymbolLayer, PropertyValue<IconTextFitType>, &SymbolLayer::setIconTextFit>; + result["icon-text-fit-padding"] = &setProperty<SymbolLayer, PropertyValue<std::array<float, 4>>, &SymbolLayer::setIconTextFitPadding>; + result["icon-image"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<std::string>, &SymbolLayer::setIconImage>; + result["icon-rotate"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setIconRotate>; + result["icon-padding"] = &setProperty<SymbolLayer, PropertyValue<float>, &SymbolLayer::setIconPadding>; + result["icon-keep-upright"] = &setProperty<SymbolLayer, PropertyValue<bool>, &SymbolLayer::setIconKeepUpright>; + result["icon-offset"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<std::array<float, 2>>, &SymbolLayer::setIconOffset>; + result["icon-anchor"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<SymbolAnchorType>, &SymbolLayer::setIconAnchor>; + result["icon-pitch-alignment"] = &setProperty<SymbolLayer, PropertyValue<AlignmentType>, &SymbolLayer::setIconPitchAlignment>; + result["text-pitch-alignment"] = &setProperty<SymbolLayer, PropertyValue<AlignmentType>, &SymbolLayer::setTextPitchAlignment>; + result["text-rotation-alignment"] = &setProperty<SymbolLayer, PropertyValue<AlignmentType>, &SymbolLayer::setTextRotationAlignment>; + result["text-field"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<std::string>, &SymbolLayer::setTextField>; + result["text-font"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<std::vector<std::string>>, &SymbolLayer::setTextFont>; + result["text-size"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setTextSize>; + result["text-max-width"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setTextMaxWidth>; + result["text-line-height"] = &setProperty<SymbolLayer, PropertyValue<float>, &SymbolLayer::setTextLineHeight>; + result["text-letter-spacing"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setTextLetterSpacing>; + result["text-justify"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<TextJustifyType>, &SymbolLayer::setTextJustify>; + result["text-anchor"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<SymbolAnchorType>, &SymbolLayer::setTextAnchor>; + result["text-max-angle"] = &setProperty<SymbolLayer, PropertyValue<float>, &SymbolLayer::setTextMaxAngle>; + result["text-rotate"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setTextRotate>; + result["text-padding"] = &setProperty<SymbolLayer, PropertyValue<float>, &SymbolLayer::setTextPadding>; + result["text-keep-upright"] = &setProperty<SymbolLayer, PropertyValue<bool>, &SymbolLayer::setTextKeepUpright>; + result["text-transform"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<TextTransformType>, &SymbolLayer::setTextTransform>; + result["text-offset"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<std::array<float, 2>>, &SymbolLayer::setTextOffset>; + result["text-allow-overlap"] = &setProperty<SymbolLayer, PropertyValue<bool>, &SymbolLayer::setTextAllowOverlap>; + result["text-ignore-placement"] = &setProperty<SymbolLayer, PropertyValue<bool>, &SymbolLayer::setTextIgnorePlacement>; + result["text-optional"] = &setProperty<SymbolLayer, PropertyValue<bool>, &SymbolLayer::setTextOptional>; + + + + + + + return result; +} + +inline auto makePaintPropertySetters() { + std::unordered_map<std::string, PropertySetter> result; + + result["fill-antialias"] = &setProperty<FillLayer, PropertyValue<bool>, &FillLayer::setFillAntialias>; + result["fill-antialias-transition"] = &setTransition<FillLayer, &FillLayer::setFillAntialiasTransition>; + result["fill-opacity"] = &setProperty<FillLayer, DataDrivenPropertyValue<float>, &FillLayer::setFillOpacity>; + result["fill-opacity-transition"] = &setTransition<FillLayer, &FillLayer::setFillOpacityTransition>; + result["fill-color"] = &setProperty<FillLayer, DataDrivenPropertyValue<Color>, &FillLayer::setFillColor>; + result["fill-color-transition"] = &setTransition<FillLayer, &FillLayer::setFillColorTransition>; + result["fill-outline-color"] = &setProperty<FillLayer, DataDrivenPropertyValue<Color>, &FillLayer::setFillOutlineColor>; + result["fill-outline-color-transition"] = &setTransition<FillLayer, &FillLayer::setFillOutlineColorTransition>; + result["fill-translate"] = &setProperty<FillLayer, PropertyValue<std::array<float, 2>>, &FillLayer::setFillTranslate>; + result["fill-translate-transition"] = &setTransition<FillLayer, &FillLayer::setFillTranslateTransition>; + result["fill-translate-anchor"] = &setProperty<FillLayer, PropertyValue<TranslateAnchorType>, &FillLayer::setFillTranslateAnchor>; + result["fill-translate-anchor-transition"] = &setTransition<FillLayer, &FillLayer::setFillTranslateAnchorTransition>; + result["fill-pattern"] = &setProperty<FillLayer, PropertyValue<std::string>, &FillLayer::setFillPattern>; + result["fill-pattern-transition"] = &setTransition<FillLayer, &FillLayer::setFillPatternTransition>; + + result["line-opacity"] = &setProperty<LineLayer, DataDrivenPropertyValue<float>, &LineLayer::setLineOpacity>; + result["line-opacity-transition"] = &setTransition<LineLayer, &LineLayer::setLineOpacityTransition>; + result["line-color"] = &setProperty<LineLayer, DataDrivenPropertyValue<Color>, &LineLayer::setLineColor>; + result["line-color-transition"] = &setTransition<LineLayer, &LineLayer::setLineColorTransition>; + result["line-translate"] = &setProperty<LineLayer, PropertyValue<std::array<float, 2>>, &LineLayer::setLineTranslate>; + result["line-translate-transition"] = &setTransition<LineLayer, &LineLayer::setLineTranslateTransition>; + result["line-translate-anchor"] = &setProperty<LineLayer, PropertyValue<TranslateAnchorType>, &LineLayer::setLineTranslateAnchor>; + result["line-translate-anchor-transition"] = &setTransition<LineLayer, &LineLayer::setLineTranslateAnchorTransition>; + result["line-width"] = &setProperty<LineLayer, DataDrivenPropertyValue<float>, &LineLayer::setLineWidth>; + result["line-width-transition"] = &setTransition<LineLayer, &LineLayer::setLineWidthTransition>; + result["line-gap-width"] = &setProperty<LineLayer, DataDrivenPropertyValue<float>, &LineLayer::setLineGapWidth>; + result["line-gap-width-transition"] = &setTransition<LineLayer, &LineLayer::setLineGapWidthTransition>; + result["line-offset"] = &setProperty<LineLayer, DataDrivenPropertyValue<float>, &LineLayer::setLineOffset>; + result["line-offset-transition"] = &setTransition<LineLayer, &LineLayer::setLineOffsetTransition>; + result["line-blur"] = &setProperty<LineLayer, DataDrivenPropertyValue<float>, &LineLayer::setLineBlur>; + result["line-blur-transition"] = &setTransition<LineLayer, &LineLayer::setLineBlurTransition>; + result["line-dasharray"] = &setProperty<LineLayer, PropertyValue<std::vector<float>>, &LineLayer::setLineDasharray>; + result["line-dasharray-transition"] = &setTransition<LineLayer, &LineLayer::setLineDasharrayTransition>; + result["line-pattern"] = &setProperty<LineLayer, PropertyValue<std::string>, &LineLayer::setLinePattern>; + result["line-pattern-transition"] = &setTransition<LineLayer, &LineLayer::setLinePatternTransition>; + + result["icon-opacity"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setIconOpacity>; + result["icon-opacity-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setIconOpacityTransition>; + result["icon-color"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<Color>, &SymbolLayer::setIconColor>; + result["icon-color-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setIconColorTransition>; + result["icon-halo-color"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<Color>, &SymbolLayer::setIconHaloColor>; + result["icon-halo-color-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setIconHaloColorTransition>; + result["icon-halo-width"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setIconHaloWidth>; + result["icon-halo-width-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setIconHaloWidthTransition>; + result["icon-halo-blur"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setIconHaloBlur>; + result["icon-halo-blur-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setIconHaloBlurTransition>; + result["icon-translate"] = &setProperty<SymbolLayer, PropertyValue<std::array<float, 2>>, &SymbolLayer::setIconTranslate>; + result["icon-translate-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setIconTranslateTransition>; + result["icon-translate-anchor"] = &setProperty<SymbolLayer, PropertyValue<TranslateAnchorType>, &SymbolLayer::setIconTranslateAnchor>; + result["icon-translate-anchor-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setIconTranslateAnchorTransition>; + result["text-opacity"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setTextOpacity>; + result["text-opacity-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setTextOpacityTransition>; + result["text-color"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<Color>, &SymbolLayer::setTextColor>; + result["text-color-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setTextColorTransition>; + result["text-halo-color"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<Color>, &SymbolLayer::setTextHaloColor>; + result["text-halo-color-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setTextHaloColorTransition>; + result["text-halo-width"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setTextHaloWidth>; + result["text-halo-width-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setTextHaloWidthTransition>; + result["text-halo-blur"] = &setProperty<SymbolLayer, DataDrivenPropertyValue<float>, &SymbolLayer::setTextHaloBlur>; + result["text-halo-blur-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setTextHaloBlurTransition>; + result["text-translate"] = &setProperty<SymbolLayer, PropertyValue<std::array<float, 2>>, &SymbolLayer::setTextTranslate>; + result["text-translate-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setTextTranslateTransition>; + result["text-translate-anchor"] = &setProperty<SymbolLayer, PropertyValue<TranslateAnchorType>, &SymbolLayer::setTextTranslateAnchor>; + result["text-translate-anchor-transition"] = &setTransition<SymbolLayer, &SymbolLayer::setTextTranslateAnchorTransition>; + + result["circle-radius"] = &setProperty<CircleLayer, DataDrivenPropertyValue<float>, &CircleLayer::setCircleRadius>; + result["circle-radius-transition"] = &setTransition<CircleLayer, &CircleLayer::setCircleRadiusTransition>; + result["circle-color"] = &setProperty<CircleLayer, DataDrivenPropertyValue<Color>, &CircleLayer::setCircleColor>; + result["circle-color-transition"] = &setTransition<CircleLayer, &CircleLayer::setCircleColorTransition>; + result["circle-blur"] = &setProperty<CircleLayer, DataDrivenPropertyValue<float>, &CircleLayer::setCircleBlur>; + result["circle-blur-transition"] = &setTransition<CircleLayer, &CircleLayer::setCircleBlurTransition>; + result["circle-opacity"] = &setProperty<CircleLayer, DataDrivenPropertyValue<float>, &CircleLayer::setCircleOpacity>; + result["circle-opacity-transition"] = &setTransition<CircleLayer, &CircleLayer::setCircleOpacityTransition>; + result["circle-translate"] = &setProperty<CircleLayer, PropertyValue<std::array<float, 2>>, &CircleLayer::setCircleTranslate>; + result["circle-translate-transition"] = &setTransition<CircleLayer, &CircleLayer::setCircleTranslateTransition>; + result["circle-translate-anchor"] = &setProperty<CircleLayer, PropertyValue<TranslateAnchorType>, &CircleLayer::setCircleTranslateAnchor>; + result["circle-translate-anchor-transition"] = &setTransition<CircleLayer, &CircleLayer::setCircleTranslateAnchorTransition>; + result["circle-pitch-scale"] = &setProperty<CircleLayer, PropertyValue<CirclePitchScaleType>, &CircleLayer::setCirclePitchScale>; + result["circle-pitch-scale-transition"] = &setTransition<CircleLayer, &CircleLayer::setCirclePitchScaleTransition>; + result["circle-pitch-alignment"] = &setProperty<CircleLayer, PropertyValue<AlignmentType>, &CircleLayer::setCirclePitchAlignment>; + result["circle-pitch-alignment-transition"] = &setTransition<CircleLayer, &CircleLayer::setCirclePitchAlignmentTransition>; + result["circle-stroke-width"] = &setProperty<CircleLayer, DataDrivenPropertyValue<float>, &CircleLayer::setCircleStrokeWidth>; + result["circle-stroke-width-transition"] = &setTransition<CircleLayer, &CircleLayer::setCircleStrokeWidthTransition>; + result["circle-stroke-color"] = &setProperty<CircleLayer, DataDrivenPropertyValue<Color>, &CircleLayer::setCircleStrokeColor>; + result["circle-stroke-color-transition"] = &setTransition<CircleLayer, &CircleLayer::setCircleStrokeColorTransition>; + result["circle-stroke-opacity"] = &setProperty<CircleLayer, DataDrivenPropertyValue<float>, &CircleLayer::setCircleStrokeOpacity>; + result["circle-stroke-opacity-transition"] = &setTransition<CircleLayer, &CircleLayer::setCircleStrokeOpacityTransition>; + + result["fill-extrusion-opacity"] = &setProperty<FillExtrusionLayer, PropertyValue<float>, &FillExtrusionLayer::setFillExtrusionOpacity>; + result["fill-extrusion-opacity-transition"] = &setTransition<FillExtrusionLayer, &FillExtrusionLayer::setFillExtrusionOpacityTransition>; + result["fill-extrusion-color"] = &setProperty<FillExtrusionLayer, DataDrivenPropertyValue<Color>, &FillExtrusionLayer::setFillExtrusionColor>; + result["fill-extrusion-color-transition"] = &setTransition<FillExtrusionLayer, &FillExtrusionLayer::setFillExtrusionColorTransition>; + result["fill-extrusion-translate"] = &setProperty<FillExtrusionLayer, PropertyValue<std::array<float, 2>>, &FillExtrusionLayer::setFillExtrusionTranslate>; + result["fill-extrusion-translate-transition"] = &setTransition<FillExtrusionLayer, &FillExtrusionLayer::setFillExtrusionTranslateTransition>; + result["fill-extrusion-translate-anchor"] = &setProperty<FillExtrusionLayer, PropertyValue<TranslateAnchorType>, &FillExtrusionLayer::setFillExtrusionTranslateAnchor>; + result["fill-extrusion-translate-anchor-transition"] = &setTransition<FillExtrusionLayer, &FillExtrusionLayer::setFillExtrusionTranslateAnchorTransition>; + result["fill-extrusion-pattern"] = &setProperty<FillExtrusionLayer, PropertyValue<std::string>, &FillExtrusionLayer::setFillExtrusionPattern>; + result["fill-extrusion-pattern-transition"] = &setTransition<FillExtrusionLayer, &FillExtrusionLayer::setFillExtrusionPatternTransition>; + result["fill-extrusion-height"] = &setProperty<FillExtrusionLayer, DataDrivenPropertyValue<float>, &FillExtrusionLayer::setFillExtrusionHeight>; + result["fill-extrusion-height-transition"] = &setTransition<FillExtrusionLayer, &FillExtrusionLayer::setFillExtrusionHeightTransition>; + result["fill-extrusion-base"] = &setProperty<FillExtrusionLayer, DataDrivenPropertyValue<float>, &FillExtrusionLayer::setFillExtrusionBase>; + result["fill-extrusion-base-transition"] = &setTransition<FillExtrusionLayer, &FillExtrusionLayer::setFillExtrusionBaseTransition>; + + result["raster-opacity"] = &setProperty<RasterLayer, PropertyValue<float>, &RasterLayer::setRasterOpacity>; + result["raster-opacity-transition"] = &setTransition<RasterLayer, &RasterLayer::setRasterOpacityTransition>; + result["raster-hue-rotate"] = &setProperty<RasterLayer, PropertyValue<float>, &RasterLayer::setRasterHueRotate>; + result["raster-hue-rotate-transition"] = &setTransition<RasterLayer, &RasterLayer::setRasterHueRotateTransition>; + result["raster-brightness-min"] = &setProperty<RasterLayer, PropertyValue<float>, &RasterLayer::setRasterBrightnessMin>; + result["raster-brightness-min-transition"] = &setTransition<RasterLayer, &RasterLayer::setRasterBrightnessMinTransition>; + result["raster-brightness-max"] = &setProperty<RasterLayer, PropertyValue<float>, &RasterLayer::setRasterBrightnessMax>; + result["raster-brightness-max-transition"] = &setTransition<RasterLayer, &RasterLayer::setRasterBrightnessMaxTransition>; + result["raster-saturation"] = &setProperty<RasterLayer, PropertyValue<float>, &RasterLayer::setRasterSaturation>; + result["raster-saturation-transition"] = &setTransition<RasterLayer, &RasterLayer::setRasterSaturationTransition>; + result["raster-contrast"] = &setProperty<RasterLayer, PropertyValue<float>, &RasterLayer::setRasterContrast>; + result["raster-contrast-transition"] = &setTransition<RasterLayer, &RasterLayer::setRasterContrastTransition>; + result["raster-fade-duration"] = &setProperty<RasterLayer, PropertyValue<float>, &RasterLayer::setRasterFadeDuration>; + result["raster-fade-duration-transition"] = &setTransition<RasterLayer, &RasterLayer::setRasterFadeDurationTransition>; + + result["hillshade-illumination-direction"] = &setProperty<HillshadeLayer, PropertyValue<float>, &HillshadeLayer::setHillshadeIlluminationDirection>; + result["hillshade-illumination-direction-transition"] = &setTransition<HillshadeLayer, &HillshadeLayer::setHillshadeIlluminationDirectionTransition>; + result["hillshade-illumination-anchor"] = &setProperty<HillshadeLayer, PropertyValue<HillshadeIlluminationAnchorType>, &HillshadeLayer::setHillshadeIlluminationAnchor>; + result["hillshade-illumination-anchor-transition"] = &setTransition<HillshadeLayer, &HillshadeLayer::setHillshadeIlluminationAnchorTransition>; + result["hillshade-exaggeration"] = &setProperty<HillshadeLayer, PropertyValue<float>, &HillshadeLayer::setHillshadeExaggeration>; + result["hillshade-exaggeration-transition"] = &setTransition<HillshadeLayer, &HillshadeLayer::setHillshadeExaggerationTransition>; + result["hillshade-shadow-color"] = &setProperty<HillshadeLayer, PropertyValue<Color>, &HillshadeLayer::setHillshadeShadowColor>; + result["hillshade-shadow-color-transition"] = &setTransition<HillshadeLayer, &HillshadeLayer::setHillshadeShadowColorTransition>; + result["hillshade-highlight-color"] = &setProperty<HillshadeLayer, PropertyValue<Color>, &HillshadeLayer::setHillshadeHighlightColor>; + result["hillshade-highlight-color-transition"] = &setTransition<HillshadeLayer, &HillshadeLayer::setHillshadeHighlightColorTransition>; + result["hillshade-accent-color"] = &setProperty<HillshadeLayer, PropertyValue<Color>, &HillshadeLayer::setHillshadeAccentColor>; + result["hillshade-accent-color-transition"] = &setTransition<HillshadeLayer, &HillshadeLayer::setHillshadeAccentColorTransition>; + + result["background-color"] = &setProperty<BackgroundLayer, PropertyValue<Color>, &BackgroundLayer::setBackgroundColor>; + result["background-color-transition"] = &setTransition<BackgroundLayer, &BackgroundLayer::setBackgroundColorTransition>; + result["background-pattern"] = &setProperty<BackgroundLayer, PropertyValue<std::string>, &BackgroundLayer::setBackgroundPattern>; + result["background-pattern-transition"] = &setTransition<BackgroundLayer, &BackgroundLayer::setBackgroundPatternTransition>; + result["background-opacity"] = &setProperty<BackgroundLayer, PropertyValue<float>, &BackgroundLayer::setBackgroundOpacity>; + result["background-opacity-transition"] = &setTransition<BackgroundLayer, &BackgroundLayer::setBackgroundOpacityTransition>; + + return result; +} + +} // namespace conversion +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/conversion/make_property_setters.hpp.ejs b/src/mbgl/style/conversion/make_property_setters.hpp.ejs new file mode 100644 index 0000000000..2975cb19f2 --- /dev/null +++ b/src/mbgl/style/conversion/make_property_setters.hpp.ejs @@ -0,0 +1,46 @@ +#pragma once + +// This file is generated. Edit make_property_setters.hpp.ejs, then run `make style-code`. + +#include <mbgl/style/conversion/property_setter.hpp> + +<% for (const layer of locals.layers) { -%> +#include <mbgl/style/layers/<%- layer.type.replace('-', '_') %>_layer.hpp> +<% } -%> + +#include <unordered_map> + +namespace mbgl { +namespace style { +namespace conversion { + +inline auto makeLayoutPropertySetters() { + std::unordered_map<std::string, PropertySetter> result; + + result["visibility"] = &setVisibility; + +<% for (const layer of locals.layers) { -%> +<% for (const property of layer.layoutProperties) { -%> + result["<%- property.name %>"] = &setProperty<<%- camelize(layer.type) %>Layer, <%- propertyValueType(property) %>, &<%- camelize(layer.type) %>Layer::set<%- camelize(property.name) %>>; +<% } -%> + +<% } -%> + return result; +} + +inline auto makePaintPropertySetters() { + std::unordered_map<std::string, PropertySetter> result; + +<% for (const layer of locals.layers) { -%> +<% for (const property of layer.paintProperties) { -%> + result["<%- property.name %>"] = &setProperty<<%- camelize(layer.type) %>Layer, <%- propertyValueType(property) %>, &<%- camelize(layer.type) %>Layer::set<%- camelize(property.name) %>>; + result["<%- property.name %>-transition"] = &setTransition<<%- camelize(layer.type) %>Layer, &<%- camelize(layer.type) %>Layer::set<%- camelize(property.name) %>Transition>; +<% } -%> + +<% } -%> + return result; +} + +} // namespace conversion +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/conversion/position.cpp b/src/mbgl/style/conversion/position.cpp new file mode 100644 index 0000000000..702d250dbf --- /dev/null +++ b/src/mbgl/style/conversion/position.cpp @@ -0,0 +1,22 @@ +#include <mbgl/style/conversion/position.hpp> +#include <mbgl/style/conversion/constant.hpp> + +#include <array> + +namespace mbgl { +namespace style { +namespace conversion { + +optional<Position> Converter<Position>::operator()(const Convertible& value, Error& error) const { + optional<std::array<float, 3>> spherical = convert<std::array<float, 3>>(value, error); + + if (!spherical) { + return {}; + } + + return Position(*spherical); +} + +} // namespace conversion +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/conversion/property_setter.hpp b/src/mbgl/style/conversion/property_setter.hpp new file mode 100644 index 0000000000..9e382b9c38 --- /dev/null +++ b/src/mbgl/style/conversion/property_setter.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include <mbgl/style/layer.hpp> +#include <mbgl/style/conversion.hpp> +#include <mbgl/style/conversion/constant.hpp> +#include <mbgl/style/conversion/property_value.hpp> +#include <mbgl/style/conversion/data_driven_property_value.hpp> +#include <mbgl/style/conversion/transition_options.hpp> + +#include <string> + +namespace mbgl { +namespace style { +namespace conversion { + +using PropertySetter = optional<Error> (*) (Layer&, const Convertible&); + +template <class L, class PropertyValue, void (L::*setter)(PropertyValue)> +optional<Error> setProperty(Layer& layer, const Convertible& value) { + auto* typedLayer = layer.as<L>(); + if (!typedLayer) { + return Error { "layer doesn't support this property" }; + } + + Error error; + optional<PropertyValue> typedValue = convert<PropertyValue>(value, error); + if (!typedValue) { + return error; + } + + (typedLayer->*setter)(*typedValue); + return {}; +} + +template <class L, void (L::*setter)(const TransitionOptions&)> +optional<Error> setTransition(Layer& layer, const Convertible& value) { + auto* typedLayer = layer.as<L>(); + if (!typedLayer) { + return Error { "layer doesn't support this property" }; + } + + Error error; + optional<TransitionOptions> transition = convert<TransitionOptions>(value, error); + if (!transition) { + return error; + } + + (typedLayer->*setter)(*transition); + return {}; +} + +inline optional<Error> setVisibility(Layer& layer, const Convertible& value) { + if (isUndefined(value)) { + layer.setVisibility(VisibilityType::Visible); + return {}; + } + + Error error; + optional<VisibilityType> visibility = convert<VisibilityType>(value, error); + if (!visibility) { + return error; + } + + layer.setVisibility(*visibility); + return {}; +} + +} // namespace conversion +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/conversion/source.cpp b/src/mbgl/style/conversion/source.cpp new file mode 100644 index 0000000000..670f50c041 --- /dev/null +++ b/src/mbgl/style/conversion/source.cpp @@ -0,0 +1,200 @@ +#include <mbgl/style/conversion/source.hpp> +#include <mbgl/style/conversion/coordinate.hpp> +#include <mbgl/style/conversion/geojson.hpp> +#include <mbgl/style/conversion/geojson_options.hpp> +#include <mbgl/style/conversion/tileset.hpp> +#include <mbgl/style/sources/geojson_source.hpp> +#include <mbgl/style/sources/raster_source.hpp> +#include <mbgl/style/sources/raster_dem_source.hpp> +#include <mbgl/style/sources/vector_source.hpp> +#include <mbgl/style/sources/image_source.hpp> +#include <mbgl/util/geo.hpp> + +namespace mbgl { +namespace style { +namespace conversion { + +// A tile source can either specify a URL to TileJSON, or inline TileJSON. +static optional<variant<std::string, Tileset>> convertURLOrTileset(const Convertible& value, Error& error) { + auto urlVal = objectMember(value, "url"); + if (!urlVal) { + optional<Tileset> tileset = convert<Tileset>(value, error); + if (!tileset) { + return {}; + } + return { *tileset }; + } + + optional<std::string> url = toString(*urlVal); + if (!url) { + error = { "source url must be a string" }; + return {}; + } + + return { *url }; +} + +static optional<std::unique_ptr<Source>> convertRasterSource(const std::string& id, + const Convertible& value, + Error& error) { + optional<variant<std::string, Tileset>> urlOrTileset = convertURLOrTileset(value, error); + if (!urlOrTileset) { + return {}; + } + + uint16_t tileSize = util::tileSize; + auto tileSizeValue = objectMember(value, "tileSize"); + if (tileSizeValue) { + optional<float> size = toNumber(*tileSizeValue); + if (!size || *size < 0 || *size > std::numeric_limits<uint16_t>::max()) { + error = { "invalid tileSize" }; + return {}; + } + tileSize = *size; + } + + return { std::make_unique<RasterSource>(id, std::move(*urlOrTileset), tileSize) }; +} + +static optional<std::unique_ptr<Source>> convertRasterDEMSource(const std::string& id, + const Convertible& value, + Error& error) { + optional<variant<std::string, Tileset>> urlOrTileset = convertURLOrTileset(value, error); + if (!urlOrTileset) { + return {}; + } + + uint16_t tileSize = util::tileSize; + auto tileSizeValue = objectMember(value, "tileSize"); + if (tileSizeValue) { + optional<float> size = toNumber(*tileSizeValue); + if (!size || *size < 0 || *size > std::numeric_limits<uint16_t>::max()) { + error = { "invalid tileSize" }; + return {}; + } + tileSize = *size; + } + + return { std::make_unique<RasterDEMSource>(id, std::move(*urlOrTileset), tileSize) }; +} + +static optional<std::unique_ptr<Source>> convertVectorSource(const std::string& id, + const Convertible& value, + Error& error) { + optional<variant<std::string, Tileset>> urlOrTileset = convertURLOrTileset(value, error); + if (!urlOrTileset) { + return {}; + } + + return { std::make_unique<VectorSource>(id, std::move(*urlOrTileset)) }; +} + +static optional<std::unique_ptr<Source>> convertGeoJSONSource(const std::string& id, + const Convertible& value, + Error& error) { + auto dataValue = objectMember(value, "data"); + if (!dataValue) { + error = { "GeoJSON source must have a data value" }; + return {}; + } + + optional<GeoJSONOptions> options = convert<GeoJSONOptions>(value, error); + if (!options) { + return {}; + } + + auto result = std::make_unique<GeoJSONSource>(id, *options); + + if (isObject(*dataValue)) { + optional<GeoJSON> geoJSON = convert<GeoJSON>(*dataValue, error); + if (!geoJSON) { + return {}; + } + result->setGeoJSON(std::move(*geoJSON)); + } else if (toString(*dataValue)) { + result->setURL(*toString(*dataValue)); + } else { + error = { "GeoJSON data must be a URL or an object" }; + return {}; + } + + return { std::move(result) }; +} + +static optional<std::unique_ptr<Source>> convertImageSource(const std::string& id, + const Convertible& value, + Error& error) { + auto urlValue = objectMember(value, "url"); + if (!urlValue) { + error = { "Image source must have a url value" }; + return {}; + } + + auto urlString = toString(*urlValue); + if (!urlString) { + error = { "Image url must be a URL string" }; + return {}; + } + + auto coordinatesValue = objectMember(value, "coordinates"); + if (!coordinatesValue) { + error = { "Image source must have a coordinates values" }; + return {}; + } + + if (!isArray(*coordinatesValue) || arrayLength(*coordinatesValue) != 4) { + error = { "Image coordinates must be an array of four longitude latitude pairs" }; + return {}; + } + + std::array<LatLng, 4> coordinates; + for (std::size_t i=0; i < 4; i++) { + auto latLng = conversion::convert<LatLng>(arrayMember(*coordinatesValue,i), error); + if (!latLng) { + return {}; + } + coordinates[i] = *latLng; + } + auto result = std::make_unique<ImageSource>(id, coordinates); + result->setURL(*urlString); + + return { std::move(result) }; +} + +optional<std::unique_ptr<Source>> Converter<std::unique_ptr<Source>>::operator()(const Convertible& value, Error& error, const std::string& id) const { + if (!isObject(value)) { + error = { "source must be an object" }; + return {}; + } + + auto typeValue = objectMember(value, "type"); + if (!typeValue) { + error = { "source must have a type" }; + return {}; + } + + optional<std::string> type = toString(*typeValue); + if (!type) { + error = { "source type must be a string" }; + return {}; + } + const std::string tname = *type; + if (*type == "raster") { + return convertRasterSource(id, value, error); + } else if (*type == "raster-dem") { + return convertRasterDEMSource(id, value, error); + } else if (*type == "vector") { + return convertVectorSource(id, value, error); + } else if (*type == "geojson") { + return convertGeoJSONSource(id, value, error); + } else if (*type == "image") { + return convertImageSource(id, value, error); + } else { + error = { "invalid source type" }; + return {}; + } +} + +} // namespace conversion +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/conversion/tileset.cpp b/src/mbgl/style/conversion/tileset.cpp index 6e559c0cac..88e78b1a83 100644 --- a/src/mbgl/style/conversion/tileset.cpp +++ b/src/mbgl/style/conversion/tileset.cpp @@ -6,7 +6,7 @@ namespace style { namespace conversion { bool validateLatitude(const double lat) { - return lat < 90 && lat > -90; + return lat <= 90 && lat >= -90; } optional<Tileset> Converter<Tileset>::operator()(const Convertible& value, Error& error) const { diff --git a/src/mbgl/style/conversion/transition_options.cpp b/src/mbgl/style/conversion/transition_options.cpp new file mode 100644 index 0000000000..8a60c5bfd8 --- /dev/null +++ b/src/mbgl/style/conversion/transition_options.cpp @@ -0,0 +1,40 @@ +#include <mbgl/style/conversion/transition_options.hpp> + +namespace mbgl { +namespace style { +namespace conversion { + +optional<TransitionOptions> Converter<TransitionOptions>::operator()(const Convertible& value, Error& error) const { + if (!isObject(value)) { + error = { "transition must be an object" }; + return {}; + } + + TransitionOptions result; + + auto duration = objectMember(value, "duration"); + if (duration) { + auto number = toNumber(*duration); + if (!number) { + error = { "duration must be a number" }; + return {}; + } + result.duration = { std::chrono::milliseconds(int64_t(*number)) }; + } + + auto delay = objectMember(value, "delay"); + if (delay) { + auto number = toNumber(*delay); + if (!number) { + error = { "delay must be a number" }; + return {}; + } + result.delay = { std::chrono::milliseconds(int64_t(*number)) }; + } + + return result; +} + +} // namespace conversion +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/custom_tile_loader.cpp b/src/mbgl/style/custom_tile_loader.cpp new file mode 100644 index 0000000000..1c587302b8 --- /dev/null +++ b/src/mbgl/style/custom_tile_loader.cpp @@ -0,0 +1,116 @@ +#include <mbgl/style/custom_tile_loader.hpp> +#include <mbgl/tile/custom_geometry_tile.hpp> +#include <mbgl/util/tile_range.hpp> + +namespace mbgl { +namespace style { + +CustomTileLoader::CustomTileLoader(const TileFunction& fetchTileFn, const TileFunction& cancelTileFn) { + fetchTileFunction = fetchTileFn; + cancelTileFunction = cancelTileFn; +} + +void CustomTileLoader::fetchTile(const OverscaledTileID& tileID, ActorRef<CustomGeometryTile> tileRef) { + auto cachedTileData = dataCache.find(tileID.canonical); + if (cachedTileData != dataCache.end()) { + tileRef.invoke(&CustomGeometryTile::setTileData, *(cachedTileData->second)); + } + auto tileCallbacks = tileCallbackMap.find(tileID.canonical); + if (tileCallbacks == tileCallbackMap.end()) { + auto tuple = std::make_tuple(tileID.overscaledZ, tileID.wrap, tileRef); + tileCallbackMap.insert({ tileID.canonical, std::vector<OverscaledIDFunctionTuple>(1, tuple) }); + } else { + for (auto iter = tileCallbacks->second.begin(); iter != tileCallbacks->second.end(); iter++) { + if (std::get<0>(*iter) == tileID.overscaledZ && std::get<1>(*iter) == tileID.wrap ) { + std::get<2>(*iter) = tileRef; + return; + } + } + tileCallbacks->second.emplace_back(std::make_tuple(tileID.overscaledZ, tileID.wrap, tileRef)); + } + if (cachedTileData == dataCache.end()) { + invokeTileFetch(tileID.canonical); + } +} + +void CustomTileLoader::cancelTile(const OverscaledTileID& tileID) { + if (tileCallbackMap.find(tileID.canonical) != tileCallbackMap.end()) { + invokeTileCancel(tileID.canonical); + } +} + +void CustomTileLoader::removeTile(const OverscaledTileID& tileID) { + auto tileCallbacks = tileCallbackMap.find(tileID.canonical); + if (tileCallbacks == tileCallbackMap.end()) return; + for (auto iter = tileCallbacks->second.begin(); iter != tileCallbacks->second.end(); iter++) { + if (std::get<0>(*iter) == tileID.overscaledZ && std::get<1>(*iter) == tileID.wrap ) { + tileCallbacks->second.erase(iter); + invokeTileCancel(tileID.canonical); + break; + } + } + if (tileCallbacks->second.size() == 0) { + tileCallbackMap.erase(tileCallbacks); + dataCache.erase(tileID.canonical); + } +} + +void CustomTileLoader::setTileData(const CanonicalTileID& tileID, const GeoJSON& data) { + + auto iter = tileCallbackMap.find(tileID); + if (iter == tileCallbackMap.end()) return; + auto dataPtr = std::make_unique<mapbox::geojson::geojson>(std::move(data)); + for (auto tuple : iter->second) { + auto actor = std::get<2>(tuple); + actor.invoke(&CustomGeometryTile::setTileData, *dataPtr); + } + dataCache[tileID] = std::move(dataPtr); +} + +void CustomTileLoader::invalidateTile(const CanonicalTileID& tileID) { + auto tileCallbacks = tileCallbackMap.find(tileID); + if (tileCallbacks == tileCallbackMap.end()) { return; } + for (auto iter = tileCallbacks->second.begin(); iter != tileCallbacks->second.end(); iter++) { + auto actor = std::get<2>(*iter); + actor.invoke(&CustomGeometryTile::invalidateTileData); + invokeTileCancel(tileID); + } + tileCallbackMap.erase(tileCallbacks); + dataCache.erase(tileID); +} + +void CustomTileLoader::invalidateRegion(const LatLngBounds& bounds, Range<uint8_t> ) { + std::map<uint8_t, util::TileRange> tileRanges; + + for (auto idtuple= tileCallbackMap.begin(); idtuple != tileCallbackMap.end(); idtuple++) { + auto zoom = idtuple->first.z; + auto tileRange = tileRanges.find(zoom); + if(tileRange == tileRanges.end()) { + tileRange = tileRanges.emplace(std::make_pair(zoom, util::TileRange::fromLatLngBounds(bounds, zoom))).first; + } + if (tileRange->second.contains(idtuple->first)) { + for (auto iter = idtuple->second.begin(); iter != idtuple->second.end(); iter++) { + auto actor = std::get<2>(*iter); + actor.invoke(&CustomGeometryTile::invalidateTileData); + invokeTileCancel(idtuple->first); + dataCache.erase(idtuple->first); + } + idtuple->second.clear(); + } + } +} + +void CustomTileLoader::invokeTileFetch(const CanonicalTileID& tileID) { + if (fetchTileFunction != nullptr) { + fetchTileFunction(tileID); + } +} + +void CustomTileLoader::invokeTileCancel(const CanonicalTileID& tileID) { + if (cancelTileFunction != nullptr) { + cancelTileFunction(tileID); + } +} + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/custom_tile_loader.hpp b/src/mbgl/style/custom_tile_loader.hpp new file mode 100644 index 0000000000..335d8c6143 --- /dev/null +++ b/src/mbgl/style/custom_tile_loader.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include <mbgl/style/sources/custom_geometry_source.hpp> +#include <mbgl/tile/tile_id.hpp> +#include <mbgl/util/geojson.hpp> +#include <mbgl/actor/actor_ref.hpp> + +#include <map> + +namespace mbgl { + +class CustomGeometryTile; + +namespace style { + +class CustomTileLoader : private util::noncopyable { +public: + + using OverscaledIDFunctionTuple = std::tuple<uint8_t, int16_t, ActorRef<CustomGeometryTile>>; + + CustomTileLoader(const TileFunction& fetchTileFn, const TileFunction& cancelTileFn); + + void fetchTile(const OverscaledTileID& tileID, ActorRef<CustomGeometryTile> tileRef); + void cancelTile(const OverscaledTileID& tileID); + + void removeTile(const OverscaledTileID& tileID); + void setTileData(const CanonicalTileID& tileID, const GeoJSON& data); + + void invalidateTile(const CanonicalTileID&); + void invalidateRegion(const LatLngBounds&, Range<uint8_t>); + +private: + void invokeTileFetch(const CanonicalTileID& tileID); + void invokeTileCancel(const CanonicalTileID& tileID); + + TileFunction fetchTileFunction; + TileFunction cancelTileFunction; + std::unordered_map<CanonicalTileID, std::vector<OverscaledIDFunctionTuple>> tileCallbackMap; + // Keep around a cache of tile data to serve back for wrapped and over-zooomed tiles + std::map<CanonicalTileID, std::unique_ptr<GeoJSON>> dataCache; + +}; + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/expression/array_assertion.cpp b/src/mbgl/style/expression/array_assertion.cpp new file mode 100644 index 0000000000..29f6a47b10 --- /dev/null +++ b/src/mbgl/style/expression/array_assertion.cpp @@ -0,0 +1,86 @@ +#include <mbgl/style/expression/array_assertion.hpp> +#include <mbgl/style/expression/check_subtype.hpp> +#include <mbgl/util/string.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +EvaluationResult ArrayAssertion::evaluate(const EvaluationContext& params) const { + auto result = input->evaluate(params); + if (!result) { + return result.error(); + } + type::Type expected = getType(); + type::Type actual = typeOf(*result); + if (checkSubtype(expected, actual)) { + return EvaluationError { + "Expected value to be of type " + toString(expected) + + ", but found " + toString(actual) + " instead." + }; + } + return *result; +} + +void ArrayAssertion::eachChild(const std::function<void(const Expression&)>& visit) const { + visit(*input); +} + +using namespace mbgl::style::conversion; +ParseResult ArrayAssertion::parse(const Convertible& value, ParsingContext& ctx) { + + static std::unordered_map<std::string, type::Type> itemTypes { + {"string", type::String}, + {"number", type::Number}, + {"boolean", type::Boolean} + }; + + auto length = arrayLength(value); + if (length < 2 || length > 4) { + ctx.error("Expected 1, 2, or 3 arguments, but found " + util::toString(length - 1) + " instead."); + return ParseResult(); + } + + optional<type::Type> itemType; + optional<std::size_t> N; + if (length > 2) { + optional<std::string> itemTypeName = toString(arrayMember(value, 1)); + auto it = itemTypeName ? itemTypes.find(*itemTypeName) : itemTypes.end(); + if (it == itemTypes.end()) { + ctx.error( + R"(The item type argument of "array" must be one of string, number, boolean)", + 1 + ); + return ParseResult(); + } + itemType = it->second; + } else { + itemType = {type::Value}; + } + + if (length > 3) { + auto n = toNumber(arrayMember(value, 2)); + if (!n || *n != std::floor(*n)) { + ctx.error( + R"(The length argument to "array" must be a positive integer literal.)", + 2 + ); + return ParseResult(); + } + N = optional<std::size_t>(*n); + } + + auto input = ctx.parse(arrayMember(value, length - 1), length - 1, {type::Value}); + if (!input) { + return input; + } + + return ParseResult(std::make_unique<ArrayAssertion>( + type::Array(*itemType, N), + std::move(*input) + )); +} + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/expression/assertion.cpp b/src/mbgl/style/expression/assertion.cpp new file mode 100644 index 0000000000..0187921af9 --- /dev/null +++ b/src/mbgl/style/expression/assertion.cpp @@ -0,0 +1,83 @@ +#include <mbgl/style/expression/assertion.hpp> +#include <mbgl/style/expression/check_subtype.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +using namespace mbgl::style::conversion; +ParseResult Assertion::parse(const Convertible& value, ParsingContext& ctx) { + static std::unordered_map<std::string, type::Type> types { + {"string", type::String}, + {"number", type::Number}, + {"boolean", type::Boolean}, + {"object", type::Object} + }; + + std::size_t length = arrayLength(value); + + if (length < 2) { + ctx.error("Expected at least one argument."); + return ParseResult(); + } + + auto it = types.find(*toString(arrayMember(value, 0))); + assert(it != types.end()); + + std::vector<std::unique_ptr<Expression>> parsed; + parsed.reserve(length - 1); + for (std::size_t i = 1; i < length; i++) { + ParseResult input = ctx.parse(arrayMember(value, i), i, {type::Value}); + if (!input) return ParseResult(); + parsed.push_back(std::move(*input)); + } + + return ParseResult(std::make_unique<Assertion>(it->second, std::move(parsed))); +} + +EvaluationResult Assertion::evaluate(const EvaluationContext& params) const { + for (std::size_t i = 0; i < inputs.size(); i++) { + EvaluationResult value = inputs[i]->evaluate(params); + if (!value) return value; + if (!type::checkSubtype(getType(), typeOf(*value))) { + return value; + } else if (i == inputs.size() - 1) { + return EvaluationError { + "Expected value to be of type " + toString(getType()) + + ", but found " + toString(typeOf(*value)) + " instead." + }; + } + } + + assert(false); + return EvaluationError { "Unreachable" }; +}; + +void Assertion::eachChild(const std::function<void(const Expression&)>& visit) const { + for(const std::unique_ptr<Expression>& input : inputs) { + visit(*input); + } +}; + +bool Assertion::operator==(const Expression& e) const { + if (auto rhs = dynamic_cast<const Assertion*>(&e)) { + return getType() == rhs->getType() && Expression::childrenEqual(inputs, rhs->inputs); + } + return false; +} + +std::vector<optional<Value>> Assertion::possibleOutputs() const { + std::vector<optional<Value>> result; + for (const auto& input : inputs) { + for (auto& output : input->possibleOutputs()) { + result.push_back(std::move(output)); + } + } + return result; +} + +} // namespace expression +} // namespace style +} // namespace mbgl + + diff --git a/src/mbgl/style/expression/at.cpp b/src/mbgl/style/expression/at.cpp new file mode 100644 index 0000000000..e447d33bc7 --- /dev/null +++ b/src/mbgl/style/expression/at.cpp @@ -0,0 +1,64 @@ +#include <mbgl/style/expression/at.hpp> +#include <mbgl/util/string.hpp> + + +namespace mbgl { +namespace style { +namespace expression { + +EvaluationResult At::evaluate(const EvaluationContext& params) const { + const EvaluationResult evaluatedIndex = index->evaluate(params); + const EvaluationResult evaluatedInput = input->evaluate(params); + if (!evaluatedIndex) { + return evaluatedIndex.error(); + } + if (!evaluatedInput) { + return evaluatedInput.error(); + } + + const auto i = evaluatedIndex->get<double>(); + const auto inputArray = evaluatedInput->get<std::vector<Value>>(); + + if (i < 0 || i >= inputArray.size()) { + return EvaluationError { + "Array index out of bounds: " + stringify(i) + + " > " + util::toString(inputArray.size()) + "." + }; + } + if (i != std::floor(i)) { + return EvaluationError { + "Array index must be an integer, but found " + stringify(i) + " instead." + }; + } + return inputArray[static_cast<std::size_t>(i)]; +} + +void At::eachChild(const std::function<void(const Expression&)>& visit) const { + visit(*index); + visit(*input); +} + +using namespace mbgl::style::conversion; +ParseResult At::parse(const Convertible& value, ParsingContext& ctx) { + assert(isArray(value)); + + std::size_t length = arrayLength(value); + if (length != 3) { + ctx.error("Expected 2 arguments, but found " + util::toString(length - 1) + " instead."); + return ParseResult(); + } + + ParseResult index = ctx.parse(arrayMember(value, 1), 1, {type::Number}); + + type::Type inputType = type::Array(ctx.getExpected() ? *ctx.getExpected() : type::Value); + ParseResult input = ctx.parse(arrayMember(value, 2), 2, {inputType}); + + if (!index || !input) return ParseResult(); + + return ParseResult(std::make_unique<At>(std::move(*index), std::move(*input))); + +} + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/expression/boolean_operator.cpp b/src/mbgl/style/expression/boolean_operator.cpp new file mode 100644 index 0000000000..8d277450ba --- /dev/null +++ b/src/mbgl/style/expression/boolean_operator.cpp @@ -0,0 +1,95 @@ +#include <mbgl/style/expression/boolean_operator.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +EvaluationResult Any::evaluate(const EvaluationContext& params) const { + for (auto it = inputs.begin(); it != inputs.end(); it++) { + const EvaluationResult result = (*it)->evaluate(params); + if (!result) return result; + if (result->get<bool>()) return EvaluationResult(true); + } + return EvaluationResult(false); +} + +void Any::eachChild(const std::function<void(const Expression&)>& visit) const { + for (const std::unique_ptr<Expression>& input : inputs) { + visit(*input); + } +} + +bool Any::operator==(const Expression& e) const { + if (auto rhs = dynamic_cast<const Any*>(&e)) { + return Expression::childrenEqual(inputs, rhs->inputs); + } + return false; +} + +std::vector<optional<Value>> Any::possibleOutputs() const { + return {{ true }, { false }}; +} + + +EvaluationResult All::evaluate(const EvaluationContext& params) const { + for (auto it = inputs.begin(); it != inputs.end(); it++) { + const EvaluationResult result = (*it)->evaluate(params); + if (!result) return result; + if (!result->get<bool>()) return EvaluationResult(false); + } + return EvaluationResult(true); +} + +void All::eachChild(const std::function<void(const Expression&)>& visit) const { + for (const std::unique_ptr<Expression>& input : inputs) { + visit(*input); + } +} + +bool All::operator==(const Expression& e) const { + if (auto rhs = dynamic_cast<const All*>(&e)) { + return Expression::childrenEqual(inputs, rhs->inputs); + } + return false; +} + +std::vector<optional<Value>> All::possibleOutputs() const { + return {{ true }, { false }}; +} + +using namespace mbgl::style::conversion; + +template <class T> +ParseResult parseBooleanOp(const Convertible& value, ParsingContext& ctx) { + + assert(isArray(value)); + auto length = arrayLength(value); + + std::vector<std::unique_ptr<Expression>> parsedInputs; + + parsedInputs.reserve(length - 1); + for (std::size_t i = 1; i < length; i++) { + auto parsed = ctx.parse(arrayMember(value, i), i, {type::Boolean}); + if (!parsed) { + return parsed; + } + + parsedInputs.push_back(std::move(*parsed)); + } + + return ParseResult(std::make_unique<T>(std::move(parsedInputs))); +} + +ParseResult Any::parse(const Convertible& value, ParsingContext& ctx) { + return parseBooleanOp<Any>(value, ctx); +} + +ParseResult All::parse(const Convertible& value, ParsingContext& ctx) { + return parseBooleanOp<All>(value, ctx); +} + + +} // namespace expression +} // namespace style +} // namespace mbgl + diff --git a/src/mbgl/style/expression/case.cpp b/src/mbgl/style/expression/case.cpp new file mode 100644 index 0000000000..295e694189 --- /dev/null +++ b/src/mbgl/style/expression/case.cpp @@ -0,0 +1,104 @@ +#include <mbgl/style/expression/case.hpp> +#include <mbgl/util/string.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +EvaluationResult Case::evaluate(const EvaluationContext& params) const { + for (const auto& branch : branches) { + const EvaluationResult evaluatedTest = branch.first->evaluate(params); + if (!evaluatedTest) { + return evaluatedTest.error(); + } + if (evaluatedTest->get<bool>()) { + return branch.second->evaluate(params); + } + } + + return otherwise->evaluate(params); +} + +void Case::eachChild(const std::function<void(const Expression&)>& visit) const { + for (const Branch& branch : branches) { + visit(*branch.first); + visit(*branch.second); + } + visit(*otherwise); +} + +bool Case::operator==(const Expression& e) const { + if (auto rhs = dynamic_cast<const Case*>(&e)) { + return *otherwise == *(rhs->otherwise) && Expression::childrenEqual(branches, rhs->branches); + } + return false; +} + +std::vector<optional<Value>> Case::possibleOutputs() const { + std::vector<optional<Value>> result; + for (const auto& branch : branches) { + for (auto& output : branch.second->possibleOutputs()) { + result.push_back(std::move(output)); + } + } + for (auto& output : otherwise->possibleOutputs()) { + result.push_back(std::move(output)); + } + return result; +} + +using namespace mbgl::style::conversion; +ParseResult Case::parse(const Convertible& value, ParsingContext& ctx) { + assert(isArray(value)); + auto length = arrayLength(value); + if (length < 4) { + ctx.error("Expected at least 3 arguments, but found only " + util::toString(length - 1) + "."); + return ParseResult(); + } + + // Expect even-length array: ["case", 2 * (n pairs)..., otherwise] + if (length % 2 != 0) { + ctx.error("Expected an odd number of arguments"); + return ParseResult(); + } + + optional<type::Type> outputType; + if (ctx.getExpected() && *ctx.getExpected() != type::Value) { + outputType = ctx.getExpected(); + } + + std::vector<Case::Branch> branches; + branches.reserve((length - 2) / 2); + for (size_t i = 1; i + 1 < length; i += 2) { + auto test = ctx.parse(arrayMember(value, i), i, {type::Boolean}); + if (!test) { + return test; + } + + auto output = ctx.parse(arrayMember(value, i + 1), i + 1, outputType); + if (!output) { + return output; + } + + if (!outputType) { + outputType = (*output)->getType(); + } + + branches.push_back(std::make_pair(std::move(*test), std::move(*output))); + } + + assert(outputType); + + auto otherwise = ctx.parse(arrayMember(value, length - 1), length - 1, outputType); + if (!otherwise) { + return otherwise; + } + + return ParseResult(std::make_unique<Case>(*outputType, + std::move(branches), + std::move(*otherwise))); +} + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/expression/check_subtype.cpp b/src/mbgl/style/expression/check_subtype.cpp new file mode 100644 index 0000000000..04a1643f0c --- /dev/null +++ b/src/mbgl/style/expression/check_subtype.cpp @@ -0,0 +1,60 @@ +#include <string> +#include <mbgl/style/expression/check_subtype.hpp> + +namespace mbgl { +namespace style { +namespace expression { +namespace type { + +std::string errorMessage(const Type& expected, const Type& t) { + return {"Expected " + toString(expected) + " but found " + toString(t) + " instead."}; +} + +optional<std::string> checkSubtype(const Type& expected, const Type& t) { + if (t.is<ErrorType>()) return {}; + + optional<std::string> result = expected.match( + [&] (const Array& expectedArray) -> optional<std::string> { + if (!t.is<Array>()) { return {errorMessage(expected, t)}; } + const auto& actualArray = t.get<Array>(); + const auto err = checkSubtype(expectedArray.itemType, actualArray.itemType); + if (err) return { errorMessage(expected, t) }; + if (expectedArray.N && expectedArray.N != actualArray.N) return { errorMessage(expected, t) }; + return {}; + }, + [&] (const ValueType&) -> optional<std::string> { + if (t.is<ValueType>()) return {}; + + const Type members[] = { + Null, + Boolean, + Number, + String, + Object, + Color, + Array(Value) + }; + + for (const auto& member : members) { + const auto err = checkSubtype(member, t); + if (!err) { + return {}; + } + } + return { errorMessage(expected, t) }; + }, + [&] (const auto&) -> optional<std::string> { + if (expected != t) { + return { errorMessage(expected, t) }; + } + return {}; + } + ); + + return result; +} + +} // namespace type +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/expression/coalesce.cpp b/src/mbgl/style/expression/coalesce.cpp new file mode 100644 index 0000000000..872a9abbef --- /dev/null +++ b/src/mbgl/style/expression/coalesce.cpp @@ -0,0 +1,84 @@ +#include <mbgl/style/expression/coalesce.hpp> +#include <mbgl/style/expression/check_subtype.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +EvaluationResult Coalesce::evaluate(const EvaluationContext& params) const { + EvaluationResult result = Null; + for (const auto& arg : args) { + result = arg->evaluate(params); + if (!result || *result != Null) break; + } + return result; +} + +void Coalesce::eachChild(const std::function<void(const Expression&)>& visit) const { + for (const std::unique_ptr<Expression>& arg : args) { + visit(*arg); + } +} + +bool Coalesce::operator==(const Expression& e) const { + if (auto rhs = dynamic_cast<const Coalesce*>(&e)) { + return Expression::childrenEqual(args, rhs->args); + } + return false; +} + +std::vector<optional<Value>> Coalesce::possibleOutputs() const { + std::vector<optional<Value>> result; + for (const auto& arg : args) { + for (auto& output : arg->possibleOutputs()) { + result.push_back(std::move(output)); + } + } + return result; +} + +using namespace mbgl::style::conversion; +ParseResult Coalesce::parse(const Convertible& value, ParsingContext& ctx) { + assert(isArray(value)); + auto length = arrayLength(value); + if (length < 2) { + ctx.error("Expected at least one argument."); + return ParseResult(); + } + + optional<type::Type> outputType; + optional<type::Type> expectedType = ctx.getExpected(); + if (expectedType && *expectedType != type::Value) { + outputType = expectedType; + } + + Coalesce::Args args; + args.reserve(length - 1); + for (std::size_t i = 1; i < length; i++) { + auto parsed = ctx.parse(arrayMember(value, i), i, outputType, ParsingContext::omitTypeAnnotations); + if (!parsed) { + return parsed; + } + if (!outputType) { + outputType = (*parsed)->getType(); + } + args.push_back(std::move(*parsed)); + } + assert(outputType); + + // Above, we parse arguments without inferred type annotation so that + // they don't produce a runtime error for `null` input, which would + // preempt the desired null-coalescing behavior. + // Thus, if any of our arguments would have needed an annotation, we + // need to wrap the enclosing coalesce expression with it instead. + bool needsAnnotation = expectedType && + std::any_of(args.begin(), args.end(), [&] (const auto& arg) { + return type::checkSubtype(*expectedType, arg->getType()); + }); + + return ParseResult(std::make_unique<Coalesce>(needsAnnotation ? type::Value : *outputType, std::move(args))); +} + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/expression/coercion.cpp b/src/mbgl/style/expression/coercion.cpp new file mode 100644 index 0000000000..56ab33fcfd --- /dev/null +++ b/src/mbgl/style/expression/coercion.cpp @@ -0,0 +1,154 @@ +#include <mbgl/style/expression/coercion.hpp> +#include <mbgl/style/expression/check_subtype.hpp> +#include <mbgl/style/expression/util.hpp> +#include <mbgl/util/string.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +EvaluationResult toNumber(const Value& v) { + optional<double> result = v.match( + [](const double f) -> optional<double> { return f; }, + [](const std::string& s) -> optional<double> { + try { + return util::stof(s); + } catch(std::exception) { + return optional<double>(); + } + }, + [](const auto&) { return optional<double>(); } + ); + if (!result) { + return EvaluationError { + "Could not convert " + stringify(v) + " to number." + }; + } + return *result; +} + +EvaluationResult toColor(const Value& colorValue) { + return colorValue.match( + [&](const std::string& colorString) -> EvaluationResult { + const optional<Color> result = Color::parse(colorString); + if (result) { + return *result; + } else { + return EvaluationError{ + "Could not parse color from value '" + colorString + "'" + }; + } + }, + [&](const std::vector<Value>& components) -> EvaluationResult { + std::size_t len = components.size(); + bool isNumeric = std::all_of(components.begin(), components.end(), [](const Value& item) { + return item.template is<double>(); + }); + if ((len == 3 || len == 4) && isNumeric) { + Result<Color> c = {rgba( + components[0].template get<double>(), + components[1].template get<double>(), + components[2].template get<double>(), + len == 4 ? components[3].template get<double>() : 1.0 + )}; + if (!c) return c.error(); + return *c; + } else { + return EvaluationError{ + "Invalid rbga value " + stringify(colorValue) + ": expected an array containing either three or four numeric values." + }; + } + }, + [&](const auto&) -> EvaluationResult { + return EvaluationError{ + "Could not parse color from value '" + stringify(colorValue) + "'" + }; + } + ); +} + +Coercion::Coercion(type::Type type_, std::vector<std::unique_ptr<Expression>> inputs_) : + Expression(std::move(type_)), + inputs(std::move(inputs_)) +{ + type::Type t = getType(); + if (t.is<type::NumberType>()) { + coerceSingleValue = toNumber; + } else if (t.is<type::ColorType>()) { + coerceSingleValue = toColor; + } else { + assert(false); + } +} + +using namespace mbgl::style::conversion; +ParseResult Coercion::parse(const Convertible& value, ParsingContext& ctx) { + static std::unordered_map<std::string, type::Type> types { + {"to-number", type::Number}, + {"to-color", type::Color} + }; + + std::size_t length = arrayLength(value); + + if (length < 2) { + ctx.error("Expected at least one argument."); + return ParseResult(); + } + + auto it = types.find(*toString(arrayMember(value, 0))); + assert(it != types.end()); + + std::vector<std::unique_ptr<Expression>> parsed; + parsed.reserve(length - 1); + for (std::size_t i = 1; i < length; i++) { + ParseResult input = ctx.parse(arrayMember(value, i), i, {type::Value}); + if (!input) return ParseResult(); + parsed.push_back(std::move(*input)); + } + + return ParseResult(std::make_unique<Coercion>(it->second, std::move(parsed))); +} + +EvaluationResult Coercion::evaluate(const EvaluationContext& params) const { + for (std::size_t i = 0; i < inputs.size(); i++) { + EvaluationResult value = inputs[i]->evaluate(params); + if (!value) return value; + EvaluationResult coerced = coerceSingleValue(*value); + if (coerced || i == inputs.size() - 1) { + return coerced; + } + } + + assert(false); + return EvaluationError { "Unreachable" }; +}; + +void Coercion::eachChild(const std::function<void(const Expression&)>& visit) const { + for(const std::unique_ptr<Expression>& input : inputs) { + visit(*input); + } +}; + +bool Coercion::operator==(const Expression& e) const { + if (auto rhs = dynamic_cast<const Coercion*>(&e)) { + return getType() == rhs->getType() && Expression::childrenEqual(inputs, rhs->inputs); + } + return false; +} + +std::vector<optional<Value>> Coercion::possibleOutputs() const { + std::vector<optional<Value>> result; + for (const auto& input : inputs) { + for (auto& output : input->possibleOutputs()) { + result.push_back(std::move(output)); + } + } + return result; +} + +} // namespace expression +} // namespace style +} // namespace mbgl + + + diff --git a/src/mbgl/style/expression/compound_expression.cpp b/src/mbgl/style/expression/compound_expression.cpp new file mode 100644 index 0000000000..42cb655024 --- /dev/null +++ b/src/mbgl/style/expression/compound_expression.cpp @@ -0,0 +1,557 @@ +#include <mbgl/style/expression/compound_expression.hpp> +#include <mbgl/style/expression/check_subtype.hpp> +#include <mbgl/style/expression/util.hpp> +#include <mbgl/tile/geometry_tile_data.hpp> +#include <mbgl/math/log2.hpp> +#include <mbgl/util/ignore.hpp> +#include <mbgl/util/string.hpp> +#include <mbgl/util/platform.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +namespace detail { + +/* + The Signature<Fn> structs are wrappers around an "evaluate()" function whose + purpose is to extract the necessary Type data from the evaluate function's + type. There are three key (partial) specializations: + + Signature<R (Params...)>: + Wraps a simple evaluate function (const T0&, const T1&, ...) -> Result<U> + + Signature<R (const Varargs<T>&)>: + Wraps an evaluate function that takes an arbitrary number of arguments (via + a Varargs<T>, which is just an alias for std::vector). + + Signature<R (const EvaluationContext&, Params...)>: + Wraps an evaluate function that needs to access the expression evaluation + parameters in addition to its subexpressions, i.e., + (const EvaluationParams& const T0&, const T1&, ...) -> Result<U>. Needed + for expressions like ["zoom"], ["get", key], etc. + + In each of the above evaluate signatures, T0, T1, etc. are the types of + the successfully evaluated subexpressions. +*/ +template <class, class Enable = void> +struct Signature; + +// Simple evaluate function (const T0&, const T1&, ...) -> Result<U> +template <class R, class... Params> +struct Signature<R (Params...)> : SignatureBase { + using Args = std::array<std::unique_ptr<Expression>, sizeof...(Params)>; + + Signature(R (*evaluate_)(Params...)) : + SignatureBase( + valueTypeToExpressionType<std::decay_t<typename R::Value>>(), + std::vector<type::Type> {valueTypeToExpressionType<std::decay_t<Params>>()...} + ), + evaluate(evaluate_) + {} + + EvaluationResult apply(const EvaluationContext& evaluationParameters, const Args& args) const { + return applyImpl(evaluationParameters, args, std::index_sequence_for<Params...>{}); + } + + std::unique_ptr<Expression> makeExpression(const std::string& name, + std::vector<std::unique_ptr<Expression>> args) const override { + typename Signature::Args argsArray; + std::copy_n(std::make_move_iterator(args.begin()), sizeof...(Params), argsArray.begin()); + return std::make_unique<CompoundExpression<Signature>>(name, *this, std::move(argsArray)); + } + + R (*evaluate)(Params...); +private: + template <std::size_t ...I> + EvaluationResult applyImpl(const EvaluationContext& evaluationParameters, const Args& args, std::index_sequence<I...>) const { + const std::array<EvaluationResult, sizeof...(I)> evaluated = {{std::get<I>(args)->evaluate(evaluationParameters)...}}; + for (const auto& arg : evaluated) { + if(!arg) return arg.error(); + } + const R value = evaluate(*fromExpressionValue<std::decay_t<Params>>(*(evaluated[I]))...); + if (!value) return value.error(); + return *value; + } +}; + +// Varargs evaluate function (const Varargs<T>&) -> Result<U> +template <class R, typename T> +struct Signature<R (const Varargs<T>&)> : SignatureBase { + using Args = std::vector<std::unique_ptr<Expression>>; + + Signature(R (*evaluate_)(const Varargs<T>&)) : + SignatureBase( + valueTypeToExpressionType<std::decay_t<typename R::Value>>(), + VarargsType { valueTypeToExpressionType<T>() } + ), + evaluate(evaluate_) + {} + + std::unique_ptr<Expression> makeExpression(const std::string& name, + std::vector<std::unique_ptr<Expression>> args) const override { + return std::make_unique<CompoundExpression<Signature>>(name, *this, std::move(args)); + }; + + EvaluationResult apply(const EvaluationContext& evaluationParameters, const Args& args) const { + Varargs<T> evaluated; + evaluated.reserve(args.size()); + for (const auto& arg : args) { + const EvaluationResult evaluatedArg = arg->evaluate(evaluationParameters); + if(!evaluatedArg) return evaluatedArg.error(); + evaluated.push_back(*fromExpressionValue<std::decay_t<T>>(*evaluatedArg)); + } + const R value = evaluate(evaluated); + if (!value) return value.error(); + return *value; + } + + R (*evaluate)(const Varargs<T>&); +}; + +// Evaluate function needing parameter access, +// (const EvaluationParams&, const T0&, const T1&, ...) -> Result<U> +template <class R, class... Params> +struct Signature<R (const EvaluationContext&, Params...)> : SignatureBase { + using Args = std::array<std::unique_ptr<Expression>, sizeof...(Params)>; + + Signature(R (*evaluate_)(const EvaluationContext&, Params...)) : + SignatureBase( + valueTypeToExpressionType<std::decay_t<typename R::Value>>(), + std::vector<type::Type> {valueTypeToExpressionType<std::decay_t<Params>>()...} + ), + evaluate(evaluate_) + {} + + std::unique_ptr<Expression> makeExpression(const std::string& name, + std::vector<std::unique_ptr<Expression>> args) const override { + typename Signature::Args argsArray; + std::copy_n(std::make_move_iterator(args.begin()), sizeof...(Params), argsArray.begin()); + return std::make_unique<CompoundExpression<Signature>>(name, *this, std::move(argsArray)); + } + + EvaluationResult apply(const EvaluationContext& evaluationParameters, const Args& args) const { + return applyImpl(evaluationParameters, args, std::index_sequence_for<Params...>{}); + } + +private: + template <std::size_t ...I> + EvaluationResult applyImpl(const EvaluationContext& evaluationParameters, const Args& args, std::index_sequence<I...>) const { + const std::array<EvaluationResult, sizeof...(I)> evaluated = {{std::get<I>(args)->evaluate(evaluationParameters)...}}; + for (const auto& arg : evaluated) { + if(!arg) return arg.error(); + } + // TODO: assert correct runtime type of each arg value + const R value = evaluate(evaluationParameters, *fromExpressionValue<std::decay_t<Params>>(*(evaluated[I]))...); + if (!value) return value.error(); + return *value; + } + + R (*evaluate)(const EvaluationContext&, Params...); +}; + +// Machinery to pull out function types from class methods, lambdas, etc. +template <class R, class... Params> +struct Signature<R (*)(Params...)> + : Signature<R (Params...)> +{ using Signature<R (Params...)>::Signature; }; + +template <class T, class R, class... Params> +struct Signature<R (T::*)(Params...) const> + : Signature<R (Params...)> +{ using Signature<R (Params...)>::Signature; }; + +template <class T, class R, class... Params> +struct Signature<R (T::*)(Params...)> + : Signature<R (Params...)> +{ using Signature<R (Params...)>::Signature; }; + +template <class Lambda> +struct Signature<Lambda, std::enable_if_t<std::is_class<Lambda>::value>> + : Signature<decltype(&Lambda::operator())> +{ using Signature<decltype(&Lambda::operator())>::Signature; }; + +} // namespace detail + +using Definition = CompoundExpressionRegistry::Definition; + +template <typename Fn> +static std::unique_ptr<detail::SignatureBase> makeSignature(Fn evaluateFunction) { + return std::make_unique<detail::Signature<Fn>>(evaluateFunction); +} + +std::unordered_map<std::string, CompoundExpressionRegistry::Definition> initializeDefinitions() { + std::unordered_map<std::string, CompoundExpressionRegistry::Definition> definitions; + auto define = [&](std::string name, auto fn) { + definitions[name].push_back(makeSignature(fn)); + }; + + define("e", []() -> Result<double> { return 2.718281828459045; }); + define("pi", []() -> Result<double> { return 3.141592653589793; }); + define("ln2", []() -> Result<double> { return 0.6931471805599453; }); + + define("typeof", [](const Value& v) -> Result<std::string> { return toString(typeOf(v)); }); + + define("to-string", [](const Value& value) -> Result<std::string> { + return value.match( + [](const Color& c) -> Result<std::string> { return c.stringify(); }, // avoid quoting + [](const std::string& s) -> Result<std::string> { return s; }, // avoid quoting + [](const auto& v) -> Result<std::string> { return stringify(v); } + ); + }); + + define("to-boolean", [](const Value& v) -> Result<bool> { + return v.match( + [&] (double f) { return (bool)f; }, + [&] (const std::string& s) { return s.length() > 0; }, + [&] (bool b) { return b; }, + [&] (const NullValue&) { return false; }, + [&] (const auto&) { return true; } + ); + }); + define("to-rgba", [](const Color& color) -> Result<std::array<double, 4>> { + return std::array<double, 4> {{ + 255 * color.r / color.a, + 255 * color.g / color.a, + 255 * color.b / color.a, + color.a + }}; + }); + + define("rgba", rgba); + define("rgb", [](double r, double g, double b) { return rgba(r, g, b, 1.0f); }); + + define("zoom", [](const EvaluationContext& params) -> Result<double> { + if (!params.zoom) { + return EvaluationError { + "The 'zoom' expression is unavailable in the current evaluation context." + }; + } + return *(params.zoom); + }); + + define("heatmap-density", [](const EvaluationContext& params) -> Result<double> { + if (!params.heatmapDensity) { + return EvaluationError { + "The 'heatmap-density' expression is unavailable in the current evaluation context." + }; + } + return *(params.heatmapDensity); + }); + + define("has", [](const EvaluationContext& params, const std::string& key) -> Result<bool> { + if (!params.feature) { + return EvaluationError { + "Feature data is unavailable in the current evaluation context." + }; + } + + return params.feature->getValue(key) ? true : false; + }); + define("has", [](const std::string& key, const std::unordered_map<std::string, Value>& object) -> Result<bool> { + return object.find(key) != object.end(); + }); + + define("get", [](const EvaluationContext& params, const std::string& key) -> Result<Value> { + if (!params.feature) { + return EvaluationError { + "Feature data is unavailable in the current evaluation context." + }; + } + + auto propertyValue = params.feature->getValue(key); + if (!propertyValue) { + return Null; + } + return Value(toExpressionValue(*propertyValue)); + }); + define("get", [](const std::string& key, const std::unordered_map<std::string, Value>& object) -> Result<Value> { + if (object.find(key) == object.end()) { + return Null; + } + return object.at(key); + }); + + define("length", [](const std::vector<Value>& arr) -> Result<double> { + return arr.size(); + }); + define("length", [] (const std::string s) -> Result<double> { + return s.size(); + }); + + define("properties", [](const EvaluationContext& params) -> Result<std::unordered_map<std::string, Value>> { + if (!params.feature) { + return EvaluationError { + "Feature data is unavailable in the current evaluation context." + }; + } + std::unordered_map<std::string, Value> result; + const PropertyMap properties = params.feature->getProperties(); + for (const auto& entry : properties) { + result[entry.first] = toExpressionValue(entry.second); + } + return result; + }); + + define("geometry-type", [](const EvaluationContext& params) -> Result<std::string> { + if (!params.feature) { + return EvaluationError { + "Feature data is unavailable in the current evaluation context." + }; + } + + auto type = params.feature->getType(); + if (type == FeatureType::Point) { + return "Point"; + } else if (type == FeatureType::LineString) { + return "LineString"; + } else if (type == FeatureType::Polygon) { + return "Polygon"; + } else { + return "Unknown"; + } + }); + + define("id", [](const EvaluationContext& params) -> Result<Value> { + if (!params.feature) { + return EvaluationError { + "Feature data is unavailable in the current evaluation context." + }; + } + + auto id = params.feature->getID(); + if (!id) { + return Null; + } + return id->match( + [](const auto& idValue) { + return toExpressionValue(mbgl::Value(idValue)); + } + ); + }); + + define("+", [](const Varargs<double>& args) -> Result<double> { + double sum = 0.0f; + for (auto arg : args) { + sum += arg; + } + return sum; + }); + define("-", [](double a, double b) -> Result<double> { return a - b; }); + define("-", [](double a) -> Result<double> { return -a; }); + define("*", [](const Varargs<double>& args) -> Result<double> { + double prod = 1.0f; + for (auto arg : args) { + prod *= arg; + } + return prod; + }); + define("/", [](double a, double b) -> Result<double> { return a / b; }); + define("%", [](double a, double b) -> Result<double> { return fmod(a, b); }); + define("^", [](double a, double b) -> Result<double> { return pow(a, b); }); + define("sqrt", [](double x) -> Result<double> { return sqrt(x); }); + define("log10", [](double x) -> Result<double> { return log10(x); }); + define("ln", [](double x) -> Result<double> { return log(x); }); + define("log2", [](double x) -> Result<double> { return util::log2(x); }); + define("sin", [](double x) -> Result<double> { return sin(x); }); + define("cos", [](double x) -> Result<double> { return cos(x); }); + define("tan", [](double x) -> Result<double> { return tan(x); }); + define("asin", [](double x) -> Result<double> { return asin(x); }); + define("acos", [](double x) -> Result<double> { return acos(x); }); + define("atan", [](double x) -> Result<double> { return atan(x); }); + + define("min", [](const Varargs<double>& args) -> Result<double> { + double result = std::numeric_limits<double>::infinity(); + for (double arg : args) { + result = fmin(arg, result); + } + return result; + }); + define("max", [](const Varargs<double>& args) -> Result<double> { + double result = -std::numeric_limits<double>::infinity(); + for (double arg : args) { + result = fmax(arg, result); + } + return result; + }); + + define(">", [](double lhs, double rhs) -> Result<bool> { return lhs > rhs; }); + define(">", [](const std::string& lhs, const std::string& rhs) -> Result<bool> { return lhs > rhs; }); + define(">=", [](double lhs, double rhs) -> Result<bool> { return lhs >= rhs; }); + define(">=",[](const std::string& lhs, const std::string& rhs) -> Result<bool> { return lhs >= rhs; }); + define("<", [](double lhs, double rhs) -> Result<bool> { return lhs < rhs; }); + define("<", [](const std::string& lhs, const std::string& rhs) -> Result<bool> { return lhs < rhs; }); + define("<=", [](double lhs, double rhs) -> Result<bool> { return lhs <= rhs; }); + define("<=", [](const std::string& lhs, const std::string& rhs) -> Result<bool> { return lhs <= rhs; }); + + define("!", [](bool e) -> Result<bool> { return !e; }); + + define("upcase", [](const std::string& input) -> Result<std::string> { + return platform::uppercase(input); + }); + define("downcase", [](const std::string& input) -> Result<std::string> { + return platform::lowercase(input); + }); + define("concat", [](const Varargs<std::string>& args) -> Result<std::string> { + std::string s; + for (const std::string& arg : args) { + s += arg; + } + return s; + }); + define("error", [](const std::string& input) -> Result<type::ErrorType> { + return EvaluationError { input }; + }); + + return definitions; +} + +std::unordered_map<std::string, Definition> CompoundExpressionRegistry::definitions = initializeDefinitions(); + +using namespace mbgl::style::conversion; +ParseResult parseCompoundExpression(const std::string name, const Convertible& value, ParsingContext& ctx) { + assert(isArray(value) && arrayLength(value) > 0); + + auto it = CompoundExpressionRegistry::definitions.find(name); + if (it == CompoundExpressionRegistry::definitions.end()) { + ctx.error( + R"(Unknown expression ")" + name + R"(". If you wanted a literal array, use ["literal", [...]].)", + 0 + ); + return ParseResult(); + } + const CompoundExpressionRegistry::Definition& definition = it->second; + + auto length = arrayLength(value); + + // Check if we have a single signature with the correct number of + // parameters. If so, then use that signature's parameter types for parsing + // (and inferring the types of) the arguments. + optional<std::size_t> singleMatchingSignature; + for (std::size_t j = 0; j < definition.size(); j++) { + const std::unique_ptr<detail::SignatureBase>& signature = definition[j]; + if ( + signature->params.is<VarargsType>() || + signature->params.get<std::vector<type::Type>>().size() == length - 1 + ) { + if (singleMatchingSignature) { + singleMatchingSignature = {}; + } else { + singleMatchingSignature = j; + } + } + } + + // parse subexpressions first + std::vector<std::unique_ptr<Expression>> args; + args.reserve(length - 1); + for (std::size_t i = 1; i < length; i++) { + optional<type::Type> expected; + + if (singleMatchingSignature) { + expected = definition[*singleMatchingSignature]->params.match( + [](const VarargsType& varargs) { return varargs.type; }, + [&](const std::vector<type::Type>& params_) { return params_[i - 1]; } + ); + } + + auto parsed = ctx.parse(arrayMember(value, i), i, expected); + if (!parsed) { + return parsed; + } + args.push_back(std::move(*parsed)); + } + return createCompoundExpression(name, definition, std::move(args), ctx); +} + + +ParseResult createCompoundExpression(const std::string& name, + std::vector<std::unique_ptr<Expression>> args, + ParsingContext& ctx) +{ + return createCompoundExpression(name, CompoundExpressionRegistry::definitions.at(name), std::move(args), ctx); +} + + +ParseResult createCompoundExpression(const std::string& name, + const Definition& definition, + std::vector<std::unique_ptr<Expression>> args, + ParsingContext& ctx) +{ + ParsingContext signatureContext(ctx.getKey()); + + for (const std::unique_ptr<detail::SignatureBase>& signature : definition) { + signatureContext.clearErrors(); + + if (signature->params.is<std::vector<type::Type>>()) { + const std::vector<type::Type>& params = signature->params.get<std::vector<type::Type>>(); + if (params.size() != args.size()) { + signatureContext.error( + "Expected " + util::toString(params.size()) + + " arguments, but found " + util::toString(args.size()) + " instead." + ); + continue; + } + + for (std::size_t i = 0; i < args.size(); i++) { + const std::unique_ptr<Expression>& arg = args[i]; + optional<std::string> err = type::checkSubtype(params.at(i), arg->getType()); + if (err) { + signatureContext.error(*err, i + 1); + } + } + } else if (signature->params.is<VarargsType>()) { + const type::Type& paramType = signature->params.get<VarargsType>().type; + for (std::size_t i = 0; i < args.size(); i++) { + const std::unique_ptr<Expression>& arg = args[i]; + optional<std::string> err = type::checkSubtype(paramType, arg->getType()); + if (err) { + signatureContext.error(*err, i + 1); + } + } + } + + if (signatureContext.getErrors().size() == 0) { + return ParseResult(signature->makeExpression(name, std::move(args))); + } + } + + if (definition.size() == 1) { + ctx.appendErrors(std::move(signatureContext)); + } else { + std::string signatures; + for (const auto& signature : definition) { + signatures += (signatures.size() > 0 ? " | " : ""); + signature->params.match( + [&](const VarargsType& varargs) { + signatures += "(" + toString(varargs.type) + ")"; + }, + [&](const std::vector<type::Type>& params) { + signatures += "("; + bool first = true; + for (const type::Type& param : params) { + if (!first) signatures += ", "; + signatures += toString(param); + first = false; + } + signatures += ")"; + } + ); + + } + std::string actualTypes; + for (const auto& arg : args) { + if (actualTypes.size() > 0) { + actualTypes += ", "; + } + actualTypes += toString(arg->getType()); + } + ctx.error("Expected arguments of type " + signatures + ", but found (" + actualTypes + ") instead."); + } + + return ParseResult(); +} + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/expression/equals.cpp b/src/mbgl/style/expression/equals.cpp new file mode 100644 index 0000000000..6d963cc1d8 --- /dev/null +++ b/src/mbgl/style/expression/equals.cpp @@ -0,0 +1,87 @@ +#include <mbgl/style/expression/equals.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +Equals::Equals(std::unique_ptr<Expression> lhs_, std::unique_ptr<Expression> rhs_, bool negate_) + : Expression(type::Boolean), + lhs(std::move(lhs_)), + rhs(std::move(rhs_)), + negate(negate_) { +} + +EvaluationResult Equals::evaluate(const EvaluationContext& params) const { + EvaluationResult lhsResult = lhs->evaluate(params); + if (!lhsResult) return lhsResult; + + EvaluationResult rhsResult = rhs->evaluate(params); + if (!rhsResult) return lhsResult; + + bool result = *lhsResult == *rhsResult; + if (negate) { + result = !result; + } + return result; +} + +void Equals::eachChild(const std::function<void(const Expression&)>& visit) const { + visit(*lhs); + visit(*rhs); +} + +bool Equals::operator==(const Expression& e) const { + if (auto eq = dynamic_cast<const Equals*>(&e)) { + return eq->negate == negate && *eq->lhs == *lhs && *eq->rhs == *rhs; + } + return false; +} + +std::vector<optional<Value>> Equals::possibleOutputs() const { + return {{ true }, { false }}; +} + +static bool isComparableType(const type::Type& type) { + return type == type::String || + type == type::Number || + type == type::Boolean || + type == type::Null; +} + +using namespace mbgl::style::conversion; +ParseResult Equals::parse(const Convertible& value, ParsingContext& ctx) { + std::size_t length = arrayLength(value); + + if (length != 3) { + ctx.error("Expected two arguments."); + return ParseResult(); + } + + bool negate = toString(arrayMember(value, 0)) == std::string("!="); + + ParseResult lhs = ctx.parse(arrayMember(value, 1), 1, {type::Value}); + if (!lhs) return ParseResult(); + + ParseResult rhs = ctx.parse(arrayMember(value, 2), 2, {type::Value}); + if (!rhs) return ParseResult(); + + type::Type lhsType = (*lhs)->getType(); + type::Type rhsType = (*rhs)->getType(); + + if (!isComparableType(lhsType) && !isComparableType(rhsType)) { + ctx.error("Expected at least one argument to be a string, number, boolean, or null, but found (" + + toString(lhsType) + ", " + toString(rhsType) + ") instead."); + return ParseResult(); + } + + if (lhsType != rhsType && lhsType != type::Value && rhsType != type::Value) { + ctx.error("Cannot compare " + toString(lhsType) + " and " + toString(rhsType) + "."); + return ParseResult(); + } + + return ParseResult(std::make_unique<Equals>(std::move(*lhs), std::move(*rhs), negate)); +} + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/expression/find_zoom_curve.cpp b/src/mbgl/style/expression/find_zoom_curve.cpp new file mode 100644 index 0000000000..5d39e0791e --- /dev/null +++ b/src/mbgl/style/expression/find_zoom_curve.cpp @@ -0,0 +1,76 @@ +#include <mbgl/style/expression/find_zoom_curve.hpp> +#include <mbgl/style/expression/compound_expression.hpp> +#include <mbgl/style/expression/let.hpp> +#include <mbgl/style/expression/coalesce.hpp> + +#include <mbgl/util/variant.hpp> +#include <mbgl/util/optional.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +optional<variant<const InterpolateBase*, const Step*, ParsingError>> findZoomCurve(const expression::Expression* e) { + optional<variant<const InterpolateBase*, const Step*, ParsingError>> result; + + if (auto let = dynamic_cast<const Let*>(e)) { + result = findZoomCurve(let->getResult()); + } else if (auto coalesce = dynamic_cast<const Coalesce*>(e)) { + std::size_t length = coalesce->getLength(); + for (std::size_t i = 0; i < length; i++) { + result = findZoomCurve(coalesce->getChild(i)); + if (result) { + break; + } + } + } else if (auto curve = dynamic_cast<const InterpolateBase*>(e)) { + auto z = dynamic_cast<CompoundExpressionBase*>(curve->getInput().get()); + if (z && z->getName() == "zoom") { + result = {curve}; + } + } else if (auto step = dynamic_cast<const Step*>(e)) { + auto z = dynamic_cast<CompoundExpressionBase*>(step->getInput().get()); + if (z && z->getName() == "zoom") { + result = {step}; + } + } + + if (result && result->is<ParsingError>()) { + return result; + } + + e->eachChild([&](const Expression& child) { + optional<variant<const InterpolateBase*, const Step*, ParsingError>> childResult(findZoomCurve(&child)); + if (childResult) { + if (childResult->is<ParsingError>()) { + result = childResult; + } else if (!result && childResult) { + result = {ParsingError { + R"("zoom" expression may only be used as input to a top-level "step" or "interpolate" expression.)", "" + }}; + } else if (result && childResult && result != childResult) { + result = {ParsingError { + R"(Only one zoom-based "step" or "interpolate" subexpression may be used in an expression.)", "" + }}; + } + } + }); + + return result; +} + +variant<const InterpolateBase*, const Step*> findZoomCurveChecked(const expression::Expression* e) { + return findZoomCurve(e)->match( + [](const ParsingError&) -> variant<const InterpolateBase*, const Step*> { + assert(false); + return {}; + }, + [](auto zoomCurve) -> variant<const InterpolateBase*, const Step*> { + return {std::move(zoomCurve)}; + } + ); +} + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/expression/get_covering_stops.cpp b/src/mbgl/style/expression/get_covering_stops.cpp new file mode 100644 index 0000000000..c9f87d93ac --- /dev/null +++ b/src/mbgl/style/expression/get_covering_stops.cpp @@ -0,0 +1,26 @@ +#include <mbgl/style/expression/get_covering_stops.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +Range<float> getCoveringStops(const std::map<double, std::unique_ptr<Expression>>& stops, + const double lower, const double upper) { + assert(!stops.empty()); + auto minIt = stops.lower_bound(lower); + auto maxIt = stops.lower_bound(upper); + + // lower_bound yields first element >= lowerZoom, but we want the *last* + // element <= lowerZoom, so if we found a stop > lowerZoom, back up by one. + if (minIt != stops.begin() && minIt != stops.end() && minIt->first > lower) { + minIt--; + } + return Range<float> { + static_cast<float>(minIt == stops.end() ? stops.rbegin()->first : minIt->first), + static_cast<float>(maxIt == stops.end() ? stops.rbegin()->first : maxIt->first) + }; +} + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/expression/interpolate.cpp b/src/mbgl/style/expression/interpolate.cpp new file mode 100644 index 0000000000..4cb22a3e4f --- /dev/null +++ b/src/mbgl/style/expression/interpolate.cpp @@ -0,0 +1,221 @@ +#include <mbgl/style/expression/interpolate.hpp> +#include <mbgl/util/string.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +using Interpolator = variant<ExponentialInterpolator, + CubicBezierInterpolator>; + +using namespace mbgl::style::conversion; + +ParseResult parseInterpolate(const Convertible& value, ParsingContext& ctx) { + assert(isArray(value)); + + auto length = arrayLength(value); + + if (length < 2) { + ctx.error("Expected an interpolation type expression."); + return ParseResult(); + } + + const Convertible& interp = arrayMember(value, 1); + if (!isArray(interp) || arrayLength(interp) == 0) { + ctx.error("Expected an interpolation type expression."); + return ParseResult(); + } + + optional<Interpolator> interpolator; + + const optional<std::string> interpName = toString(arrayMember(interp, 0)); + if (interpName && *interpName == "linear") { + interpolator = {ExponentialInterpolator(1.0)}; + } else if (interpName && *interpName == "exponential") { + optional<double> base; + if (arrayLength(interp) == 2) { + base = toDouble(arrayMember(interp, 1)); + } + if (!base) { + ctx.error("Exponential interpolation requires a numeric base.", 1, 1); + return ParseResult(); + } + interpolator = {ExponentialInterpolator(*base)}; + } else if (interpName && *interpName == "cubic-bezier") { + optional<double> x1; + optional<double> y1; + optional<double> x2; + optional<double> y2; + if (arrayLength(interp) == 5) { + x1 = toDouble(arrayMember(interp, 1)); + y1 = toDouble(arrayMember(interp, 2)); + x2 = toDouble(arrayMember(interp, 3)); + y2 = toDouble(arrayMember(interp, 4)); + } + if ( + !x1 || !y1 || !x2 || !y2 || + *x1 < 0 || *x1 > 1 || + *y1 < 0 || *y1 > 1 || + *x2 < 0 || *x2 > 1 || + *y2 < 0 || *y2 > 1 + ) { + ctx.error("Cubic bezier interpolation requires four numeric arguments with values between 0 and 1.", 1); + return ParseResult(); + + } + interpolator = {CubicBezierInterpolator(*x1, *y1, *x2, *y2)}; + } + + if (!interpolator) { + ctx.error("Unknown interpolation type " + (interpName ? *interpName : ""), 1, 0); + return ParseResult(); + } + + std::size_t minArgs = 4; + if (length - 1 < minArgs) { + ctx.error("Expected at least 4 arguments, but found only " + util::toString(length - 1) + "."); + return ParseResult(); + } + + // [interpolation, interp_type, input, 2 * (n pairs)...] + if ((length - 1) % 2 != 0) { + ctx.error("Expected an even number of arguments."); + return ParseResult(); + } + + ParseResult input = ctx.parse(arrayMember(value, 2), 2, {type::Number}); + if (!input) { + return input; + } + + std::map<double, std::unique_ptr<Expression>> stops; + optional<type::Type> outputType; + if (ctx.getExpected() && *ctx.getExpected() != type::Value) { + outputType = ctx.getExpected(); + } + + double previous = - std::numeric_limits<double>::infinity(); + + for (std::size_t i = 3; i + 1 < length; i += 2) { + const optional<mbgl::Value> labelValue = toValue(arrayMember(value, i)); + optional<double> label; + optional<std::string> labelError; + if (labelValue) { + labelValue->match( + [&](uint64_t n) { + if (n > std::numeric_limits<double>::max()) { + label = {std::numeric_limits<double>::infinity()}; + } else { + label = {static_cast<double>(n)}; + } + }, + [&](int64_t n) { + if (n > std::numeric_limits<double>::max()) { + label = {std::numeric_limits<double>::infinity()}; + } else { + label = {static_cast<double>(n)}; + } + }, + [&](double n) { + if (n > std::numeric_limits<double>::max()) { + label = {std::numeric_limits<double>::infinity()}; + } else { + label = {static_cast<double>(n)}; + } + }, + [&](const auto&) {} + ); + } + if (!label) { + ctx.error(labelError ? *labelError : + R"(Input/output pairs for "interpolate" expressions must be defined using literal numeric values (not computed expressions) for the input values.)", + i); + return ParseResult(); + } + + if (*label <= previous) { + ctx.error( + R"(Input/output pairs for "interpolate" expressions must be arranged with input values in strictly ascending order.)", + i + ); + return ParseResult(); + } + previous = *label; + + auto output = ctx.parse(arrayMember(value, i + 1), i + 1, outputType); + if (!output) { + return ParseResult(); + } + if (!outputType) { + outputType = (*output)->getType(); + } + + stops.emplace(*label, std::move(*output)); + } + + assert(outputType); + + if ( + *outputType != type::Number && + *outputType != type::Color && + !( + outputType->is<type::Array>() && + outputType->get<type::Array>().itemType == type::Number && + outputType->get<type::Array>().N + ) + ) + { + ctx.error("Type " + toString(*outputType) + " is not interpolatable."); + return ParseResult(); + } + + return outputType->match( + [&](const type::NumberType&) -> ParseResult { + return interpolator->match([&](const auto& interpolator_) { + return ParseResult(std::make_unique<Interpolate<double>>( + *outputType, interpolator_, std::move(*input), std::move(stops) + )); + }); + }, + [&](const type::ColorType&) -> ParseResult { + return interpolator->match([&](const auto& interpolator_) { + return ParseResult(std::make_unique<Interpolate<Color>>( + *outputType, interpolator_, std::move(*input), std::move(stops) + )); + }); + }, + [&](const type::Array& arrayType) -> ParseResult { + return interpolator->match( + [&](const auto& continuousInterpolator) { + if (arrayType.itemType != type::Number || !arrayType.N) { + assert(false); // interpolability already checked above. + return ParseResult(); + } + return ParseResult(std::make_unique<Interpolate<std::vector<Value>>>( + *outputType, continuousInterpolator, std::move(*input), std::move(stops) + )); + } + ); + }, + [&](const auto&) { + // unreachable: Null, Boolean, String, Object, Value output types + // are not interpolatable, and interpolability was already checked above + assert(false); + return ParseResult(); + } + ); +} + +std::vector<optional<Value>> InterpolateBase::possibleOutputs() const { + std::vector<optional<Value>> result; + for (const auto& stop : stops) { + for (auto& output : stop.second->possibleOutputs()) { + result.push_back(std::move(output)); + } + } + return result; +} + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/expression/is_constant.cpp b/src/mbgl/style/expression/is_constant.cpp new file mode 100644 index 0000000000..0ebb37faa9 --- /dev/null +++ b/src/mbgl/style/expression/is_constant.cpp @@ -0,0 +1,40 @@ +#include <mbgl/style/expression/is_constant.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +bool isFeatureConstant(const Expression& expression) { + if (auto e = dynamic_cast<const CompoundExpressionBase*>(&expression)) { + const std::string name = e->getName(); + optional<std::size_t> parameterCount = e->getParameterCount(); + if (name == "get" && parameterCount && *parameterCount == 1) { + return false; + } else if (name == "has" && parameterCount && *parameterCount == 1) { + return false; + } else if ( + name == "properties" || + name == "geometry-type" || + name == "id" + ) { + return false; + } + } + + bool featureConstant = true; + expression.eachChild([&](const Expression& e) { + if (featureConstant && !isFeatureConstant(e)) { + featureConstant = false; + } + }); + return featureConstant; +} + +bool isZoomConstant(const Expression& e) { + return isGlobalPropertyConstant(e, std::array<std::string, 1>{{"zoom"}}); +} + + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/expression/is_expression.cpp b/src/mbgl/style/expression/is_expression.cpp new file mode 100644 index 0000000000..77212f6a1e --- /dev/null +++ b/src/mbgl/style/expression/is_expression.cpp @@ -0,0 +1,29 @@ +#include <mbgl/style/expression/is_expression.hpp> +#include <mbgl/style/expression/compound_expression.hpp> +#include <mbgl/style/expression/parsing_context.hpp> + +#include <mbgl/style/conversion.hpp> + +#include <unordered_set> + +namespace mbgl { +namespace style { +namespace expression { + +using namespace mbgl::style::conversion; + +bool isExpression(const Convertible& value) { + const ExpressionRegistry& registry = getExpressionRegistry(); + + if (!isArray(value) || arrayLength(value) == 0) return false; + optional<std::string> name = toString(arrayMember(value, 0)); + if (!name) return false; + + return (registry.find(*name) != registry.end()) || + (CompoundExpressionRegistry::definitions.find(*name) != CompoundExpressionRegistry::definitions.end()); +} + + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/expression/let.cpp b/src/mbgl/style/expression/let.cpp new file mode 100644 index 0000000000..fe48138ac3 --- /dev/null +++ b/src/mbgl/style/expression/let.cpp @@ -0,0 +1,100 @@ +#include <mbgl/style/expression/let.hpp> +#include <mbgl/style/conversion/get_json_type.hpp> +#include <mbgl/util/string.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +EvaluationResult Let::evaluate(const EvaluationContext& params) const { + return result->evaluate(params); +} + +void Let::eachChild(const std::function<void(const Expression&)>& visit) const { + for (auto it = bindings.begin(); it != bindings.end(); it++) { + visit(*it->second); + } + visit(*result); +} + +std::vector<optional<Value>> Let::possibleOutputs() const { + return result->possibleOutputs(); +} + +using namespace mbgl::style::conversion; + +ParseResult Let::parse(const Convertible& value, ParsingContext& ctx) { + assert(isArray(value)); + + std::size_t length = arrayLength(value); + + if (length < 4) { + ctx.error("Expected at least 3 arguments, but found " + util::toString(length - 1) + " instead."); + return ParseResult(); + } + + std::map<std::string, std::shared_ptr<Expression>> bindings_; + for(std::size_t i = 1; i < length - 1; i += 2) { + optional<std::string> name = toString(arrayMember(value, i)); + if (!name) { + ctx.error("Expected string, but found " + getJSONType(arrayMember(value, i)) + " instead.", i); + return ParseResult(); + } + + bool isValidName = std::all_of(name->begin(), name->end(), [](unsigned char c) { + return ::isalnum(c) || c == '_'; + }); + if (!isValidName) { + ctx.error("Variable names must contain only alphanumeric characters or '_'.", 1); + return ParseResult(); + } + + ParseResult bindingValue = ctx.parse(arrayMember(value, i + 1), i + 1); + if (!bindingValue) { + return ParseResult(); + } + + bindings_.emplace(*name, std::move(*bindingValue)); + } + + ParseResult result_ = ctx.parse(arrayMember(value, length - 1), length - 1, ctx.getExpected(), bindings_); + if (!result_) { + return ParseResult(); + } + + return ParseResult(std::make_unique<Let>(std::move(bindings_), std::move(*result_))); +} + +EvaluationResult Var::evaluate(const EvaluationContext& params) const { + return value->evaluate(params); +} + +void Var::eachChild(const std::function<void(const Expression&)>&) const {} + +std::vector<optional<Value>> Var::possibleOutputs() const { + return { nullopt }; +} + +ParseResult Var::parse(const Convertible& value_, ParsingContext& ctx) { + assert(isArray(value_)); + + if (arrayLength(value_) != 2 || !toString(arrayMember(value_, 1))) { + ctx.error("'var' expression requires exactly one string literal argument."); + return ParseResult(); + } + + std::string name_ = *toString(arrayMember(value_, 1)); + + optional<std::shared_ptr<Expression>> bindingValue = ctx.getBinding(name_); + if (!bindingValue) { + ctx.error(R"(Unknown variable ")" + name_ + R"(". Make sure ")" + + name_ + R"(" has been bound in an enclosing "let" expression before using it.)", 1); + return ParseResult(); + } + + return ParseResult(std::make_unique<Var>(name_, std::move(*bindingValue))); +} + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/expression/literal.cpp b/src/mbgl/style/expression/literal.cpp new file mode 100644 index 0000000000..7e79fcbfe6 --- /dev/null +++ b/src/mbgl/style/expression/literal.cpp @@ -0,0 +1,108 @@ +#include <mbgl/style/expression/literal.hpp> +#include <mbgl/util/string.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +template <typename T> +optional<Value> checkNumber(T n) { + if (n > std::numeric_limits<double>::max()) { + return {std::numeric_limits<double>::infinity()}; + } else { + return {static_cast<double>(n)}; + } +} + +using namespace mbgl::style::conversion; +optional<Value> parseValue(const Convertible& value, ParsingContext& ctx) { + if (isUndefined(value)) return {Null}; + if (isObject(value)) { + std::unordered_map<std::string, Value> result; + bool error = false; + eachMember(value, [&] (const std::string& k, const mbgl::style::conversion::Convertible& v) -> optional<conversion::Error> { + if (!error) { + optional<Value> memberValue = parseValue(v, ctx); + if (memberValue) { + result.emplace(k, *memberValue); + } else { + error = true; + } + } + return {}; + }); + return error ? optional<Value>() : optional<Value>(result); + } + + if (isArray(value)) { + std::vector<Value> result; + const auto length = arrayLength(value); + for(std::size_t i = 0; i < length; i++) { + optional<Value> item = parseValue(arrayMember(value, i), ctx); + if (item) { + result.emplace_back(*item); + } else { + return optional<Value>(); + } + } + return optional<Value>(result); + } + + optional<mbgl::Value> v = toValue(value); + // since value represents a JSON value, if it's not undefined, object, or + // array, it must be convertible to mbgl::Value + assert(v); + + return v->match( + [&](uint64_t n) { return checkNumber(n); }, + [&](int64_t n) { return checkNumber(n); }, + [&](double n) { return checkNumber(n); }, + [&](const auto&) { + return optional<Value>(toExpressionValue(*v)); + } + ); +} + +ParseResult Literal::parse(const Convertible& value, ParsingContext& ctx) { + if (isObject(value)) { + ctx.error(R"(Bare objects invalid. Use ["literal", {...}] instead.)"); + return ParseResult(); + } else if (isArray(value)) { + // object or array value, quoted with ["literal", value] + if (arrayLength(value) != 2) { + ctx.error("'literal' expression requires exactly one argument, but found " + util::toString(arrayLength(value) - 1) + " instead."); + return ParseResult(); + } + const optional<Value> parsedValue = parseValue(arrayMember(value, 1), ctx); + if (!parsedValue) { + return ParseResult(); + } + + // special case: infer the item type if possible for zero-length arrays + if ( + ctx.getExpected() && + ctx.getExpected()->template is<type::Array>() && + parsedValue->template is<std::vector<Value>>() + ) { + auto type = typeOf(*parsedValue).template get<type::Array>(); + auto expected = ctx.getExpected()->template get<type::Array>(); + if ( + type.N && (*type.N == 0) && + (!expected.N || (*expected.N == 0)) + ) { + return ParseResult(std::make_unique<Literal>(expected, parsedValue->template get<std::vector<Value>>())); + } + } + + return ParseResult(std::make_unique<Literal>(*parsedValue)); + } else { + // bare primitive value (string, number, boolean, null) + const optional<Value> parsedValue = parseValue(value, ctx); + return ParseResult(std::make_unique<Literal>(*parsedValue)); + } +} + +} // namespace expression +} // namespace style +} // namespace mbgl + diff --git a/src/mbgl/style/expression/match.cpp b/src/mbgl/style/expression/match.cpp new file mode 100644 index 0000000000..0b2790b688 --- /dev/null +++ b/src/mbgl/style/expression/match.cpp @@ -0,0 +1,277 @@ +#include <mbgl/style/expression/match.hpp> +#include <mbgl/style/expression/check_subtype.hpp> +#include <mbgl/style/expression/parsing_context.hpp> +#include <mbgl/util/string.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +template <typename T> +void Match<T>::eachChild(const std::function<void(const Expression&)>& visit) const { + visit(*input); + for (const std::pair<T, std::shared_ptr<Expression>>& branch : branches) { + visit(*branch.second); + } + visit(*otherwise); +} + +template <typename T> +bool Match<T>::operator==(const Expression& e) const { + if (auto rhs = dynamic_cast<const Match*>(&e)) { + return (*input == *(rhs->input) && + *otherwise == *(rhs->otherwise) && + Expression::childrenEqual(branches, rhs->branches)); + } + return false; +} + +template <typename T> +std::vector<optional<Value>> Match<T>::possibleOutputs() const { + std::vector<optional<Value>> result; + for (const auto& branch : branches) { + for (auto& output : branch.second->possibleOutputs()) { + result.push_back(std::move(output)); + } + } + for (auto& output : otherwise->possibleOutputs()) { + result.push_back(std::move(output)); + } + return result; +} + + +template<> EvaluationResult Match<std::string>::evaluate(const EvaluationContext& params) const { + const EvaluationResult inputValue = input->evaluate(params); + if (!inputValue) { + return inputValue.error(); + } + + auto it = branches.find(inputValue->get<std::string>()); + if (it != branches.end()) { + return (*it).second->evaluate(params); + } + + return otherwise->evaluate(params); +} + +template<> EvaluationResult Match<int64_t>::evaluate(const EvaluationContext& params) const { + const EvaluationResult inputValue = input->evaluate(params); + if (!inputValue) { + return inputValue.error(); + } + + const auto numeric = inputValue->get<double>(); + int64_t rounded = std::floor(numeric); + if (numeric == rounded) { + auto it = branches.find(rounded); + if (it != branches.end()) { + return (*it).second->evaluate(params); + } + } + + return otherwise->evaluate(params); +} + +template class Match<int64_t>; +template class Match<std::string>; + +using InputType = variant<int64_t, std::string>; + +using namespace mbgl::style::conversion; +optional<InputType> parseInputValue(const Convertible& input, ParsingContext& parentContext, std::size_t index, optional<type::Type>& inputType) { + using namespace mbgl::style::conversion; + optional<InputType> result; + optional<type::Type> type; + + auto value = toValue(input); + + if (value) { + value->match( + [&] (uint64_t n) { + if (!Value::isSafeInteger(n)) { + parentContext.error("Branch labels must be integers no larger than " + util::toString(Value::maxSafeInteger()) + ".", index); + } else { + type = {type::Number}; + result = {static_cast<int64_t>(n)}; + } + }, + [&] (int64_t n) { + if (!Value::isSafeInteger(n)) { + parentContext.error("Branch labels must be integers no larger than " + util::toString(Value::maxSafeInteger()) + ".", index); + } else { + type = {type::Number}; + result = {n}; + } + }, + [&] (double n) { + if (!Value::isSafeInteger(n)) { + parentContext.error("Branch labels must be integers no larger than " + util::toString(Value::maxSafeInteger()) + ".", index); + } else if (n != std::floor(n)) { + parentContext.error("Numeric branch labels must be integer values.", index); + } else { + type = {type::Number}; + result = {static_cast<int64_t>(n)}; + } + }, + [&] (const std::string& s) { + type = {type::String}; + result = {s}; + }, + [&] (const auto&) { + parentContext.error("Branch labels must be numbers or strings.", index); + } + ); + } else { + parentContext.error("Branch labels must be numbers or strings.", index); + } + + if (!type) { + return result; + } + + if (!inputType) { + inputType = type; + } else { + optional<std::string> err = type::checkSubtype(*inputType, *type); + if (err) { + parentContext.error(*err, index); + return optional<InputType>(); + } + } + + return result; +} + +template <typename T> +static ParseResult create(type::Type outputType, + std::unique_ptr<Expression>input, + std::vector<std::pair<std::vector<InputType>, + std::unique_ptr<Expression>>> branches, + std::unique_ptr<Expression> otherwise, + ParsingContext& ctx) { + typename Match<T>::Branches typedBranches; + + std::size_t index = 2; + + typedBranches.reserve(branches.size()); + for (std::pair<std::vector<InputType>, + std::unique_ptr<Expression>>& pair : branches) { + std::shared_ptr<Expression> result = std::move(pair.second); + for (const InputType& label : pair.first) { + const auto& typedLabel = label.template get<T>(); + if (typedBranches.find(typedLabel) != typedBranches.end()) { + ctx.error("Branch labels must be unique.", index); + return ParseResult(); + } + typedBranches.emplace(typedLabel, result); + } + + index += 2; + } + return ParseResult(std::make_unique<Match<T>>( + outputType, + std::move(input), + std::move(typedBranches), + std::move(otherwise) + )); +} + +ParseResult parseMatch(const Convertible& value, ParsingContext& ctx) { + assert(isArray(value)); + auto length = arrayLength(value); + if (length < 5) { + ctx.error( + "Expected at least 4 arguments, but found only " + util::toString(length - 1) + "." + ); + return ParseResult(); + } + + // Expect odd-length array: ["match", input, 2 * (n pairs)..., otherwise] + if (length % 2 != 1) { + ctx.error("Expected an even number of arguments."); + return ParseResult(); + } + + optional<type::Type> inputType; + optional<type::Type> outputType; + if (ctx.getExpected() && *ctx.getExpected() != type::Value) { + outputType = ctx.getExpected(); + } + + std::vector<std::pair<std::vector<InputType>, + std::unique_ptr<Expression>>> branches; + + branches.reserve((length - 3) / 2); + for (size_t i = 2; i + 1 < length; i += 2) { + const auto& label = arrayMember(value, i); + + std::vector<InputType> labels; + // Match pair inputs are provided as either a literal value or a + // raw JSON array of string / number / boolean values. + if (isArray(label)) { + auto groupLength = arrayLength(label); + if (groupLength == 0) { + ctx.error("Expected at least one branch label.", i); + return ParseResult(); + } + + labels.reserve(groupLength); + for (size_t j = 0; j < groupLength; j++) { + const optional<InputType> inputValue = parseInputValue(arrayMember(label, j), ctx, i, inputType); + if (!inputValue) { + return ParseResult(); + } + labels.push_back(*inputValue); + } + } else { + const optional<InputType> inputValue = parseInputValue(label, ctx, i, inputType); + if (!inputValue) { + return ParseResult(); + } + labels.push_back(*inputValue); + } + + ParseResult output = ctx.parse(arrayMember(value, i + 1), i + 1, outputType); + if (!output) { + return ParseResult(); + } + + if (!outputType) { + outputType = (*output)->getType(); + } + + branches.push_back(std::make_pair(std::move(labels), std::move(*output))); + } + + auto input = ctx.parse(arrayMember(value, 1), 1, inputType); + if (!input) { + return ParseResult(); + } + + auto otherwise = ctx.parse(arrayMember(value, length - 1), length - 1, outputType); + if (!otherwise) { + return ParseResult(); + } + + assert(inputType && outputType); + + return inputType->match( + [&](const type::NumberType&) { + return create<int64_t>(*outputType, std::move(*input), std::move(branches), std::move(*otherwise), ctx); + }, + [&](const type::StringType&) { + return create<std::string>(*outputType, std::move(*input), std::move(branches), std::move(*otherwise), ctx); + }, + [&](const auto&) { + // unreachable: inputType is set by parseInputValue(), which only + // accepts string and (integer) numeric values. + assert(false); + return ParseResult(); + } + ); +} + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/expression/parsing_context.cpp b/src/mbgl/style/expression/parsing_context.cpp new file mode 100644 index 0000000000..0215982209 --- /dev/null +++ b/src/mbgl/style/expression/parsing_context.cpp @@ -0,0 +1,220 @@ + +#include <mbgl/style/expression/parsing_context.hpp> +#include <mbgl/style/expression/check_subtype.hpp> +#include <mbgl/style/expression/is_constant.hpp> +#include <mbgl/style/expression/type.hpp> + +#include <mbgl/style/expression/expression.hpp> +#include <mbgl/style/expression/at.hpp> +#include <mbgl/style/expression/array_assertion.hpp> +#include <mbgl/style/expression/assertion.hpp> +#include <mbgl/style/expression/boolean_operator.hpp> +#include <mbgl/style/expression/case.hpp> +#include <mbgl/style/expression/coalesce.hpp> +#include <mbgl/style/expression/coercion.hpp> +#include <mbgl/style/expression/compound_expression.hpp> +#include <mbgl/style/expression/equals.hpp> +#include <mbgl/style/expression/interpolate.hpp> +#include <mbgl/style/expression/let.hpp> +#include <mbgl/style/expression/literal.hpp> +#include <mbgl/style/expression/match.hpp> +#include <mbgl/style/expression/step.hpp> + +#include <mbgl/style/expression/find_zoom_curve.hpp> + +#include <mbgl/style/conversion/get_json_type.hpp> + +#include <mbgl/util/string.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +bool isConstant(const Expression& expression) { + if (dynamic_cast<const Var*>(&expression)) { + return false; + } + + if (auto compound = dynamic_cast<const CompoundExpressionBase*>(&expression)) { + if (compound->getName() == "error") { + return false; + } + } + + bool literalArgs = true; + expression.eachChild([&](const Expression& child) { + if (!dynamic_cast<const Literal*>(&child)) { + literalArgs = false; + } + }); + if (!literalArgs) { + return false; + } + + return isFeatureConstant(expression) && + isGlobalPropertyConstant(expression, std::array<std::string, 2>{{"zoom", "heatmap-density"}}); +} + +using namespace mbgl::style::conversion; + +ParseResult ParsingContext::parse(const Convertible& value, + std::size_t index_, + optional<type::Type> expected_, + TypeAnnotationOption typeAnnotationOption) { + ParsingContext child(key + "[" + util::toString(index_) + "]", + errors, + std::move(expected_), + scope); + return child.parse(value, typeAnnotationOption); +} + +ParseResult ParsingContext::parse(const Convertible& value, std::size_t index_, optional<type::Type> expected_, + const std::map<std::string, std::shared_ptr<Expression>>& bindings) { + ParsingContext child(key + "[" + util::toString(index_) + "]", + errors, + std::move(expected_), + std::make_shared<detail::Scope>(bindings, scope)); + return child.parse(value); +} + +const ExpressionRegistry& getExpressionRegistry() { + static ExpressionRegistry registry {{ + {"==", Equals::parse}, + {"!=", Equals::parse}, + {"all", All::parse}, + {"any", Any::parse}, + {"array", ArrayAssertion::parse}, + {"at", At::parse}, + {"boolean", Assertion::parse}, + {"case", Case::parse}, + {"coalesce", Coalesce::parse}, + {"interpolate", parseInterpolate}, + {"let", Let::parse}, + {"literal", Literal::parse}, + {"match", parseMatch}, + {"number", Assertion::parse}, + {"object", Assertion::parse}, + {"step", Step::parse}, + {"string", Assertion::parse}, + {"to-color", Coercion::parse}, + {"to-number", Coercion::parse}, + {"var", Var::parse} + }}; + return registry; +} + +ParseResult ParsingContext::parse(const Convertible& value, TypeAnnotationOption typeAnnotationOption) { + ParseResult parsed; + + if (isArray(value)) { + const std::size_t length = arrayLength(value); + if (length == 0) { + error(R"(Expected an array with at least one element. If you wanted a literal array, use ["literal", []].)"); + return ParseResult(); + } + + const optional<std::string> op = toString(arrayMember(value, 0)); + if (!op) { + error( + "Expression name must be a string, but found " + getJSONType(arrayMember(value, 0)) + + R"( instead. If you wanted a literal array, use ["literal", [...]].)", + 0 + ); + return ParseResult(); + } + + const ExpressionRegistry& registry = getExpressionRegistry(); + auto parseFunction = registry.find(*op); + if (parseFunction != registry.end()) { + parsed = parseFunction->second(value, *this); + } else { + parsed = parseCompoundExpression(*op, value, *this); + } + } else { + parsed = Literal::parse(value, *this); + } + + if (!parsed) { + assert(errors->size() > 0); + return parsed; + } + + if (expected) { + auto array = [&](std::unique_ptr<Expression> expression) { + std::vector<std::unique_ptr<Expression>> args; + args.push_back(std::move(expression)); + return args; + }; + + const type::Type actual = (*parsed)->getType(); + if ((*expected == type::String || *expected == type::Number || *expected == type::Boolean) && actual == type::Value) { + if (typeAnnotationOption == includeTypeAnnotations) { + parsed = { std::make_unique<Assertion>(*expected, array(std::move(*parsed))) }; + } + } else if (expected->is<type::Array>() && actual == type::Value) { + if (typeAnnotationOption == includeTypeAnnotations) { + parsed = { std::make_unique<ArrayAssertion>(expected->get<type::Array>(), std::move(*parsed)) }; + } + } else if (*expected == type::Color && (actual == type::Value || actual == type::String)) { + if (typeAnnotationOption == includeTypeAnnotations) { + parsed = { std::make_unique<Coercion>(*expected, array(std::move(*parsed))) }; + } + } else { + checkType((*parsed)->getType()); + if (errors->size() > 0) { + return ParseResult(); + } + } + } + + // If an expression's arguments are all literals, we can evaluate + // it immediately and replace it with a literal value in the + // parsed result. + if (!dynamic_cast<Literal *>(parsed->get()) && isConstant(**parsed)) { + EvaluationContext params(nullptr); + EvaluationResult evaluated((*parsed)->evaluate(params)); + if (!evaluated) { + error(evaluated.error().message); + return ParseResult(); + } + + const type::Type type = (*parsed)->getType(); + if (type.is<type::Array>()) { + // keep the original expression's array type, even if the evaluated + // type is more specific. + return ParseResult(std::make_unique<Literal>( + type.get<type::Array>(), + evaluated->get<std::vector<Value>>()) + ); + } else { + return ParseResult(std::make_unique<Literal>(*evaluated)); + } + } + + // if this is the root expression, enforce constraints on the use ["zoom"]. + if (key.size() == 0 && !isZoomConstant(**parsed)) { + optional<variant<const InterpolateBase*, const Step*, ParsingError>> zoomCurve = findZoomCurve(parsed->get()); + if (!zoomCurve) { + error(R"("zoom" expression may only be used as input to a top-level "step" or "interpolate" expression.)"); + return ParseResult(); + } else if (zoomCurve->is<ParsingError>()) { + error(zoomCurve->get<ParsingError>().message); + return ParseResult(); + } + } + + return parsed; +} + +optional<std::string> ParsingContext::checkType(const type::Type& t) { + assert(expected); + optional<std::string> err = type::checkSubtype(*expected, t); + if (err) { + error(*err); + } + return err; +} + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/expression/step.cpp b/src/mbgl/style/expression/step.cpp new file mode 100644 index 0000000000..34537d48ae --- /dev/null +++ b/src/mbgl/style/expression/step.cpp @@ -0,0 +1,175 @@ +#include <mbgl/style/expression/step.hpp> +#include <mbgl/style/expression/get_covering_stops.hpp> +#include <mbgl/util/string.hpp> + +#include <cmath> + +namespace mbgl { +namespace style { +namespace expression { + +EvaluationResult Step::evaluate(const EvaluationContext& params) const { + const EvaluationResult evaluatedInput = input->evaluate(params); + if (!evaluatedInput) { + return evaluatedInput.error(); + } + + float x = *fromExpressionValue<float>(*evaluatedInput); + if (std::isnan(x)) { + return EvaluationError { "Input is not a number." }; + } + + if (stops.empty()) { + return EvaluationError { "No stops in step curve." }; + } + + auto it = stops.upper_bound(x); + if (it == stops.end()) { + return stops.rbegin()->second->evaluate(params); + } else if (it == stops.begin()) { + return stops.begin()->second->evaluate(params); + } else { + return std::prev(it)->second->evaluate(params); + } +} + +void Step::eachChild(const std::function<void(const Expression&)>& visit) const { + visit(*input); + for (auto it = stops.begin(); it != stops.end(); it++) { + visit(*it->second); + } +} + +void Step::eachStop(const std::function<void(double, const Expression&)>& visit) const { + for (const auto &stop : stops) { + visit(stop.first, *stop.second); + } +} + +bool Step::operator==(const Expression& e) const { + if (auto rhs = dynamic_cast<const Step*>(&e)) { + return *input == *(rhs->input) && Expression::childrenEqual(stops, rhs->stops); + } + return false; +} + +std::vector<optional<Value>> Step::possibleOutputs() const { + std::vector<optional<Value>> result; + for (const auto& stop : stops) { + for (auto& output : stop.second->possibleOutputs()) { + result.push_back(std::move(output)); + } + } + return result; +} + +Range<float> Step::getCoveringStops(const double lower, const double upper) const { + return ::mbgl::style::expression::getCoveringStops(stops, lower, upper); +} + + +ParseResult Step::parse(const mbgl::style::conversion::Convertible& value, ParsingContext& ctx) { + assert(isArray(value)); + + auto length = arrayLength(value); + + if (length - 1 < 4) { + ctx.error("Expected at least 4 arguments, but found only " + util::toString(length - 1) + "."); + return ParseResult(); + } + + // [step, input, firstOutput_value, 2 * (n pairs)...] + if ((length - 1) % 2 != 0) { + ctx.error("Expected an even number of arguments."); + return ParseResult(); + } + + ParseResult input = ctx.parse(arrayMember(value, 1), 1, {type::Number}); + if (!input) { + return input; + } + + std::map<double, std::unique_ptr<Expression>> stops; + optional<type::Type> outputType; + if (ctx.getExpected() && *ctx.getExpected() != type::Value) { + outputType = ctx.getExpected(); + } + + double previous = - std::numeric_limits<double>::infinity(); + + // consume the first output value, which doesn't have a corresponding input value, + // before proceeding into the "stops" loop below. + auto firstOutput = ctx.parse(arrayMember(value, 2), 2, outputType); + if (!firstOutput) { + return ParseResult(); + } + if (!outputType) { + outputType = (*firstOutput)->getType(); + } + stops.emplace(-std::numeric_limits<double>::infinity(), std::move(*firstOutput)); + + + for (std::size_t i = 3; i + 1 < length; i += 2) { + const optional<mbgl::Value> labelValue = toValue(arrayMember(value, i)); + optional<double> label; + if (labelValue) { + labelValue->match( + [&](uint64_t n) { + if (n > std::numeric_limits<double>::max()) { + label = {std::numeric_limits<double>::infinity()}; + } else { + label = {static_cast<double>(n)}; + } + }, + [&](int64_t n) { + if (n > std::numeric_limits<double>::max()) { + label = {std::numeric_limits<double>::infinity()}; + } else { + label = {static_cast<double>(n)}; + } + }, + [&](double n) { + if (n > std::numeric_limits<double>::max()) { + label = {std::numeric_limits<double>::infinity()}; + } else { + label = {static_cast<double>(n)}; + } + }, + [&](const auto&) {} + ); + } + if (!label) { + ctx.error(R"(Input/output pairs for "step" expressions must be defined using literal numeric values (not computed expressions) for the input values.)", i); + return ParseResult(); + } + + if (*label <= previous) { + ctx.error( + R"(Input/output pairs for "step" expressions must be arranged with input values in strictly ascending order.)", + i + ); + return ParseResult(); + } + previous = *label; + + auto output = ctx.parse(arrayMember(value, i + 1), i + 1, outputType); + if (!output) { + return ParseResult(); + } + if (!outputType) { + outputType = (*output)->getType(); + } + + stops.emplace(*label, std::move(*output)); + } + + assert(outputType); + + return ParseResult(std::make_unique<Step>(*outputType, std::move(*input), std::move(stops))); +} + + +} // namespace expression +} // namespace style +} // namespace mbgl + diff --git a/src/mbgl/style/expression/util.cpp b/src/mbgl/style/expression/util.cpp new file mode 100644 index 0000000000..ee680dab08 --- /dev/null +++ b/src/mbgl/style/expression/util.cpp @@ -0,0 +1,37 @@ +#include <mbgl/style/expression/util.hpp> +#include <mbgl/style/expression/value.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +std::string stringifyColor(double r, double g, double b, double a) { + return stringify(r) + ", " + + stringify(g) + ", " + + stringify(b) + ", " + + stringify(a); +} + +Result<Color> rgba(double r, double g, double b, double a) { + if ( + r < 0 || r > 255 || + g < 0 || g > 255 || + b < 0 || b > 255 + ) { + return EvaluationError { + "Invalid rgba value [" + stringifyColor(r, g, b, a) + + "]: 'r', 'g', and 'b' must be between 0 and 255." + }; + } + if (a < 0 || a > 1) { + return EvaluationError { + "Invalid rgba value [" + stringifyColor(r, g, b, a) + + "]: 'a' must be between 0 and 1." + }; + } + return Color(r / 255 * a, g / 255 * a, b / 255 * a, a); +} + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/expression/util.hpp b/src/mbgl/style/expression/util.hpp new file mode 100644 index 0000000000..b6fc408ed9 --- /dev/null +++ b/src/mbgl/style/expression/util.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include <mbgl/style/expression/expression.hpp> +#include <mbgl/util/color.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +Result<Color> rgba(double r, double g, double b, double a); + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/expression/value.cpp b/src/mbgl/style/expression/value.cpp new file mode 100644 index 0000000000..faa44e78aa --- /dev/null +++ b/src/mbgl/style/expression/value.cpp @@ -0,0 +1,322 @@ +#include <rapidjson/writer.h> +#include <rapidjson/stringbuffer.h> +#include <mbgl/style/expression/value.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +type::Type typeOf(const Value& value) { + return value.match( + [&](bool) -> type::Type { return type::Boolean; }, + [&](double) -> type::Type { return type::Number; }, + [&](const std::string&) -> type::Type { return type::String; }, + [&](const Color&) -> type::Type { return type::Color; }, + [&](const NullValue&) -> type::Type { return type::Null; }, + [&](const std::unordered_map<std::string, Value>&) -> type::Type { return type::Object; }, + [&](const std::vector<Value>& arr) -> type::Type { + optional<type::Type> itemType; + for (const auto& item : arr) { + const type::Type t = typeOf(item); + if (!itemType) { + itemType = {t}; + } else if (*itemType == t) { + continue; + } else { + itemType = {type::Value}; + break; + } + } + + return type::Array(itemType.value_or(type::Value), arr.size()); + } + ); +} + +void writeJSON(rapidjson::Writer<rapidjson::StringBuffer>& writer, const Value& value) { + value.match( + [&] (const NullValue&) { writer.Null(); }, + [&] (bool b) { writer.Bool(b); }, + [&] (double f) { + // make sure integer values are stringified without trailing ".0". + f == std::floor(f) ? writer.Int(f) : writer.Double(f); + }, + [&] (const std::string& s) { writer.String(s); }, + [&] (const Color& c) { writer.String(c.stringify()); }, + [&] (const std::vector<Value>& arr) { + writer.StartArray(); + for(const auto& item : arr) { + writeJSON(writer, item); + } + writer.EndArray(); + }, + [&] (const std::unordered_map<std::string, Value>& obj) { + writer.StartObject(); + for(const auto& entry : obj) { + writer.Key(entry.first.c_str()); + writeJSON(writer, entry.second); + } + writer.EndObject(); + } + ); +} + +std::string stringify(const Value& value) { + rapidjson::StringBuffer buffer; + rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); + writeJSON(writer, value); + return buffer.GetString(); +} + +struct FromMBGLValue { + Value operator()(const std::vector<mbgl::Value>& v) { + std::vector<Value> result; + result.reserve(v.size()); + for(const auto& item : v) { + result.emplace_back(toExpressionValue(item)); + } + return result; + } + + Value operator()(const std::unordered_map<std::string, mbgl::Value>& v) { + std::unordered_map<std::string, Value> result; + result.reserve(v.size()); + for(const auto& entry : v) { + result.emplace(entry.first, toExpressionValue(entry.second)); + } + return result; + } + + Value operator()(const std::string& s) { return s; } + Value operator()(const bool b) { return b; } + Value operator()(const NullValue) { return Null; } + Value operator()(const double v) { return v; } + Value operator()(const uint64_t& v) { + return static_cast<double>(v); + } + Value operator()(const int64_t& v) { + return static_cast<double>(v); + } +}; + +Value ValueConverter<mbgl::Value>::toExpressionValue(const mbgl::Value& value) { + return mbgl::Value::visit(value, FromMBGLValue()); +} + +Value ValueConverter<float>::toExpressionValue(const float value) { + return static_cast<double>(value); +} + +optional<float> ValueConverter<float>::fromExpressionValue(const Value& value) { + return value.template is<double>() + ? static_cast<float>(value.template get<double>()) + : optional<float>(); +} + + +template <typename T, typename Container> +std::vector<Value> toArrayValue(const Container& value) { + std::vector<Value> result; + result.reserve(value.size()); + for (const T& item : value) { + result.push_back(ValueConverter<T>::toExpressionValue(item)); + } + return result; +} + +template <typename T, std::size_t N> +Value ValueConverter<std::array<T, N>>::toExpressionValue(const std::array<T, N>& value) { + return toArrayValue<T>(value); +} + +template <typename T, std::size_t N> +optional<std::array<T, N>> ValueConverter<std::array<T, N>>::fromExpressionValue(const Value& value) { + return value.match( + [&] (const std::vector<Value>& v) -> optional<std::array<T, N>> { + if (v.size() != N) return optional<std::array<T, N>>(); + std::array<T, N> result; + auto it = result.begin(); + for(const Value& item : v) { + optional<T> convertedItem = ValueConverter<T>::fromExpressionValue(item); + if (!convertedItem) { + return optional<std::array<T, N>>(); + } + *it = *convertedItem; + it = std::next(it); + } + return result; + }, + [&] (const auto&) { return optional<std::array<T, N>>(); } + ); +} + + +template <typename T> +Value ValueConverter<std::vector<T>>::toExpressionValue(const std::vector<T>& value) { + return toArrayValue<T>(value); +} + +template <typename T> +optional<std::vector<T>> ValueConverter<std::vector<T>>::fromExpressionValue(const Value& value) { + return value.match( + [&] (const std::vector<Value>& v) -> optional<std::vector<T>> { + std::vector<T> result; + result.reserve(v.size()); + for(const Value& item : v) { + optional<T> convertedItem = ValueConverter<T>::fromExpressionValue(item); + if (!convertedItem) { + return optional<std::vector<T>>(); + } + result.push_back(*convertedItem); + } + return result; + }, + [&] (const auto&) { return optional<std::vector<T>>(); } + ); +} + +Value ValueConverter<Position>::toExpressionValue(const mbgl::style::Position& value) { + return ValueConverter<std::array<float, 3>>::toExpressionValue(value.getSpherical()); +} + +optional<Position> ValueConverter<Position>::fromExpressionValue(const Value& v) { + auto pos = ValueConverter<std::array<float, 3>>::fromExpressionValue(v); + return pos ? optional<Position>(Position(*pos)) : optional<Position>(); +} + +template <typename T> +Value ValueConverter<T, std::enable_if_t< std::is_enum<T>::value >>::toExpressionValue(const T& value) { + return std::string(Enum<T>::toString(value)); +} + +template <typename T> +optional<T> ValueConverter<T, std::enable_if_t< std::is_enum<T>::value >>::fromExpressionValue(const Value& value) { + return value.match( + [&] (const std::string& v) { return Enum<T>::toEnum(v); }, + [&] (const auto&) { return optional<T>(); } + ); +} + + +Value toExpressionValue(const Value& v) { + return v; +} + +template <typename T, typename Enable> +Value toExpressionValue(const T& value) { + return ValueConverter<T>::toExpressionValue(value); +} + +optional<Value> fromExpressionValue(const Value& v) { + return optional<Value>(v); +} + +template <typename T> +std::enable_if_t< !std::is_convertible<T, Value>::value, +optional<T>> fromExpressionValue(const Value& v) +{ + return ValueConverter<T>::fromExpressionValue(v); +} + +template <typename T> +type::Type valueTypeToExpressionType() { + return ValueConverter<T>::expressionType(); +} + +template <> type::Type valueTypeToExpressionType<Value>() { return type::Value; } +template <> type::Type valueTypeToExpressionType<NullValue>() { return type::Null; } +template <> type::Type valueTypeToExpressionType<bool>() { return type::Boolean; } +template <> type::Type valueTypeToExpressionType<double>() { return type::Number; } +template <> type::Type valueTypeToExpressionType<std::string>() { return type::String; } +template <> type::Type valueTypeToExpressionType<Color>() { return type::Color; } +template <> type::Type valueTypeToExpressionType<std::unordered_map<std::string, Value>>() { return type::Object; } +template <> type::Type valueTypeToExpressionType<std::vector<Value>>() { return type::Array(type::Value); } + +// used only for the special (and private) "error" expression +template <> type::Type valueTypeToExpressionType<type::ErrorType>() { return type::Error; } + + +template Value toExpressionValue(const mbgl::Value&); + + +// for to_rgba expression +template type::Type valueTypeToExpressionType<std::array<double, 4>>(); +template optional<std::array<double, 4>> fromExpressionValue<std::array<double, 4>>(const Value&); +template Value toExpressionValue(const std::array<double, 4>&); + +// layout/paint property types +template type::Type valueTypeToExpressionType<float>(); +template optional<float> fromExpressionValue<float>(const Value&); +template Value toExpressionValue(const float&); + +template type::Type valueTypeToExpressionType<std::array<float, 2>>(); +template optional<std::array<float, 2>> fromExpressionValue<std::array<float, 2>>(const Value&); +template Value toExpressionValue(const std::array<float, 2>&); + +template type::Type valueTypeToExpressionType<std::array<float, 4>>(); +template optional<std::array<float, 4>> fromExpressionValue<std::array<float, 4>>(const Value&); +template Value toExpressionValue(const std::array<float, 4>&); + +template type::Type valueTypeToExpressionType<std::vector<float>>(); +template optional<std::vector<float>> fromExpressionValue<std::vector<float>>(const Value&); +template Value toExpressionValue(const std::vector<float>&); + +template type::Type valueTypeToExpressionType<std::vector<std::string>>(); +template optional<std::vector<std::string>> fromExpressionValue<std::vector<std::string>>(const Value&); +template Value toExpressionValue(const std::vector<std::string>&); + +template type::Type valueTypeToExpressionType<AlignmentType>(); +template optional<AlignmentType> fromExpressionValue<AlignmentType>(const Value&); +template Value toExpressionValue(const AlignmentType&); + +template type::Type valueTypeToExpressionType<CirclePitchScaleType>(); +template optional<CirclePitchScaleType> fromExpressionValue<CirclePitchScaleType>(const Value&); +template Value toExpressionValue(const CirclePitchScaleType&); + +template type::Type valueTypeToExpressionType<IconTextFitType>(); +template optional<IconTextFitType> fromExpressionValue<IconTextFitType>(const Value&); +template Value toExpressionValue(const IconTextFitType&); + +template type::Type valueTypeToExpressionType<LineCapType>(); +template optional<LineCapType> fromExpressionValue<LineCapType>(const Value&); +template Value toExpressionValue(const LineCapType&); + +template type::Type valueTypeToExpressionType<LineJoinType>(); +template optional<LineJoinType> fromExpressionValue<LineJoinType>(const Value&); +template Value toExpressionValue(const LineJoinType&); + +template type::Type valueTypeToExpressionType<SymbolPlacementType>(); +template optional<SymbolPlacementType> fromExpressionValue<SymbolPlacementType>(const Value&); +template Value toExpressionValue(const SymbolPlacementType&); + +template type::Type valueTypeToExpressionType<SymbolAnchorType>(); +template optional<SymbolAnchorType> fromExpressionValue<SymbolAnchorType>(const Value&); +template Value toExpressionValue(const SymbolAnchorType&); + +template type::Type valueTypeToExpressionType<TextJustifyType>(); +template optional<TextJustifyType> fromExpressionValue<TextJustifyType>(const Value&); +template Value toExpressionValue(const TextJustifyType&); + +template type::Type valueTypeToExpressionType<TextTransformType>(); +template optional<TextTransformType> fromExpressionValue<TextTransformType>(const Value&); +template Value toExpressionValue(const TextTransformType&); + +template type::Type valueTypeToExpressionType<TranslateAnchorType>(); +template optional<TranslateAnchorType> fromExpressionValue<TranslateAnchorType>(const Value&); +template Value toExpressionValue(const TranslateAnchorType&); + +template type::Type valueTypeToExpressionType<HillshadeIlluminationAnchorType>(); +template optional<HillshadeIlluminationAnchorType> fromExpressionValue<HillshadeIlluminationAnchorType>(const Value&); +template Value toExpressionValue(const HillshadeIlluminationAnchorType&); + +template type::Type valueTypeToExpressionType<LightAnchorType>(); +template optional<LightAnchorType> fromExpressionValue<LightAnchorType>(const Value&); +template Value toExpressionValue(const LightAnchorType&); + +template type::Type valueTypeToExpressionType<Position>(); +template optional<Position> fromExpressionValue<Position>(const Value&); +template Value toExpressionValue(const Position&); + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/function/expression.cpp b/src/mbgl/style/function/expression.cpp new file mode 100644 index 0000000000..d9dbbfa1d3 --- /dev/null +++ b/src/mbgl/style/function/expression.cpp @@ -0,0 +1,38 @@ +#include <mbgl/style/expression/expression.hpp> +#include <mbgl/style/expression/compound_expression.hpp> +#include <mbgl/tile/geometry_tile_data.hpp> + +namespace mbgl { +namespace style { +namespace expression { + +class GeoJSONFeature : public GeometryTileFeature { +public: + const Feature& feature; + + GeoJSONFeature(const Feature& feature_) : feature(feature_) {} + + FeatureType getType() const override { + return apply_visitor(ToFeatureType(), feature.geometry); + } + PropertyMap getProperties() const override { return feature.properties; } + optional<FeatureIdentifier> getID() const override { return feature.id; } + GeometryCollection getGeometries() const override { return {}; } + optional<mbgl::Value> getValue(const std::string& key) const override { + auto it = feature.properties.find(key); + if (it != feature.properties.end()) { + return optional<mbgl::Value>(it->second); + } + return optional<mbgl::Value>(); + } +}; + + +EvaluationResult Expression::evaluate(optional<float> zoom, const Feature& feature, optional<double> heatmapDensity) const { + GeoJSONFeature f(feature); + return this->evaluate(EvaluationContext(zoom, &f, heatmapDensity)); +} + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/layers/hillshade_layer.cpp b/src/mbgl/style/layers/hillshade_layer.cpp new file mode 100644 index 0000000000..ea736af1ad --- /dev/null +++ b/src/mbgl/style/layers/hillshade_layer.cpp @@ -0,0 +1,238 @@ +// This file is generated. Edit scripts/generate-style-code.js, then run `make style-code`. + +#include <mbgl/style/layers/hillshade_layer.hpp> +#include <mbgl/style/layers/hillshade_layer_impl.hpp> +#include <mbgl/style/layer_observer.hpp> + +namespace mbgl { +namespace style { + +HillshadeLayer::HillshadeLayer(const std::string& layerID, const std::string& sourceID) + : Layer(makeMutable<Impl>(LayerType::Hillshade, layerID, sourceID)) { +} + +HillshadeLayer::HillshadeLayer(Immutable<Impl> impl_) + : Layer(std::move(impl_)) { +} + +HillshadeLayer::~HillshadeLayer() = default; + +const HillshadeLayer::Impl& HillshadeLayer::impl() const { + return static_cast<const Impl&>(*baseImpl); +} + +Mutable<HillshadeLayer::Impl> HillshadeLayer::mutableImpl() const { + return makeMutable<Impl>(impl()); +} + +std::unique_ptr<Layer> HillshadeLayer::cloneRef(const std::string& id_) const { + auto impl_ = mutableImpl(); + impl_->id = id_; + impl_->paint = HillshadePaintProperties::Transitionable(); + return std::make_unique<HillshadeLayer>(std::move(impl_)); +} + +void HillshadeLayer::Impl::stringifyLayout(rapidjson::Writer<rapidjson::StringBuffer>&) const { +} + +// Source + +const std::string& HillshadeLayer::getSourceID() const { + return impl().source; +} + + +// Visibility + +void HillshadeLayer::setVisibility(VisibilityType value) { + if (value == getVisibility()) + return; + auto impl_ = mutableImpl(); + impl_->visibility = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +// Zoom range + +void HillshadeLayer::setMinZoom(float minZoom) { + auto impl_ = mutableImpl(); + impl_->minZoom = minZoom; + baseImpl = std::move(impl_); +} + +void HillshadeLayer::setMaxZoom(float maxZoom) { + auto impl_ = mutableImpl(); + impl_->maxZoom = maxZoom; + baseImpl = std::move(impl_); +} + +// Layout properties + + +// Paint properties + +PropertyValue<float> HillshadeLayer::getDefaultHillshadeIlluminationDirection() { + return { 335 }; +} + +PropertyValue<float> HillshadeLayer::getHillshadeIlluminationDirection() const { + return impl().paint.template get<HillshadeIlluminationDirection>().value; +} + +void HillshadeLayer::setHillshadeIlluminationDirection(PropertyValue<float> value) { + if (value == getHillshadeIlluminationDirection()) + return; + auto impl_ = mutableImpl(); + impl_->paint.template get<HillshadeIlluminationDirection>().value = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +void HillshadeLayer::setHillshadeIlluminationDirectionTransition(const TransitionOptions& options) { + auto impl_ = mutableImpl(); + impl_->paint.template get<HillshadeIlluminationDirection>().options = options; + baseImpl = std::move(impl_); +} + +TransitionOptions HillshadeLayer::getHillshadeIlluminationDirectionTransition() const { + return impl().paint.template get<HillshadeIlluminationDirection>().options; +} + +PropertyValue<HillshadeIlluminationAnchorType> HillshadeLayer::getDefaultHillshadeIlluminationAnchor() { + return { HillshadeIlluminationAnchorType::Viewport }; +} + +PropertyValue<HillshadeIlluminationAnchorType> HillshadeLayer::getHillshadeIlluminationAnchor() const { + return impl().paint.template get<HillshadeIlluminationAnchor>().value; +} + +void HillshadeLayer::setHillshadeIlluminationAnchor(PropertyValue<HillshadeIlluminationAnchorType> value) { + if (value == getHillshadeIlluminationAnchor()) + return; + auto impl_ = mutableImpl(); + impl_->paint.template get<HillshadeIlluminationAnchor>().value = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +void HillshadeLayer::setHillshadeIlluminationAnchorTransition(const TransitionOptions& options) { + auto impl_ = mutableImpl(); + impl_->paint.template get<HillshadeIlluminationAnchor>().options = options; + baseImpl = std::move(impl_); +} + +TransitionOptions HillshadeLayer::getHillshadeIlluminationAnchorTransition() const { + return impl().paint.template get<HillshadeIlluminationAnchor>().options; +} + +PropertyValue<float> HillshadeLayer::getDefaultHillshadeExaggeration() { + return { 0.5 }; +} + +PropertyValue<float> HillshadeLayer::getHillshadeExaggeration() const { + return impl().paint.template get<HillshadeExaggeration>().value; +} + +void HillshadeLayer::setHillshadeExaggeration(PropertyValue<float> value) { + if (value == getHillshadeExaggeration()) + return; + auto impl_ = mutableImpl(); + impl_->paint.template get<HillshadeExaggeration>().value = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +void HillshadeLayer::setHillshadeExaggerationTransition(const TransitionOptions& options) { + auto impl_ = mutableImpl(); + impl_->paint.template get<HillshadeExaggeration>().options = options; + baseImpl = std::move(impl_); +} + +TransitionOptions HillshadeLayer::getHillshadeExaggerationTransition() const { + return impl().paint.template get<HillshadeExaggeration>().options; +} + +PropertyValue<Color> HillshadeLayer::getDefaultHillshadeShadowColor() { + return { Color::black() }; +} + +PropertyValue<Color> HillshadeLayer::getHillshadeShadowColor() const { + return impl().paint.template get<HillshadeShadowColor>().value; +} + +void HillshadeLayer::setHillshadeShadowColor(PropertyValue<Color> value) { + if (value == getHillshadeShadowColor()) + return; + auto impl_ = mutableImpl(); + impl_->paint.template get<HillshadeShadowColor>().value = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +void HillshadeLayer::setHillshadeShadowColorTransition(const TransitionOptions& options) { + auto impl_ = mutableImpl(); + impl_->paint.template get<HillshadeShadowColor>().options = options; + baseImpl = std::move(impl_); +} + +TransitionOptions HillshadeLayer::getHillshadeShadowColorTransition() const { + return impl().paint.template get<HillshadeShadowColor>().options; +} + +PropertyValue<Color> HillshadeLayer::getDefaultHillshadeHighlightColor() { + return { Color::white() }; +} + +PropertyValue<Color> HillshadeLayer::getHillshadeHighlightColor() const { + return impl().paint.template get<HillshadeHighlightColor>().value; +} + +void HillshadeLayer::setHillshadeHighlightColor(PropertyValue<Color> value) { + if (value == getHillshadeHighlightColor()) + return; + auto impl_ = mutableImpl(); + impl_->paint.template get<HillshadeHighlightColor>().value = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +void HillshadeLayer::setHillshadeHighlightColorTransition(const TransitionOptions& options) { + auto impl_ = mutableImpl(); + impl_->paint.template get<HillshadeHighlightColor>().options = options; + baseImpl = std::move(impl_); +} + +TransitionOptions HillshadeLayer::getHillshadeHighlightColorTransition() const { + return impl().paint.template get<HillshadeHighlightColor>().options; +} + +PropertyValue<Color> HillshadeLayer::getDefaultHillshadeAccentColor() { + return { Color::black() }; +} + +PropertyValue<Color> HillshadeLayer::getHillshadeAccentColor() const { + return impl().paint.template get<HillshadeAccentColor>().value; +} + +void HillshadeLayer::setHillshadeAccentColor(PropertyValue<Color> value) { + if (value == getHillshadeAccentColor()) + return; + auto impl_ = mutableImpl(); + impl_->paint.template get<HillshadeAccentColor>().value = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +void HillshadeLayer::setHillshadeAccentColorTransition(const TransitionOptions& options) { + auto impl_ = mutableImpl(); + impl_->paint.template get<HillshadeAccentColor>().options = options; + baseImpl = std::move(impl_); +} + +TransitionOptions HillshadeLayer::getHillshadeAccentColorTransition() const { + return impl().paint.template get<HillshadeAccentColor>().options; +} + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/layers/hillshade_layer_impl.cpp b/src/mbgl/style/layers/hillshade_layer_impl.cpp new file mode 100644 index 0000000000..ed5aa922bf --- /dev/null +++ b/src/mbgl/style/layers/hillshade_layer_impl.cpp @@ -0,0 +1,11 @@ +#include <mbgl/style/layers/hillshade_layer_impl.hpp> + +namespace mbgl { +namespace style { + +bool HillshadeLayer::Impl::hasLayoutDifference(const Layer::Impl&) const { + return false; +} + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/layers/hillshade_layer_impl.hpp b/src/mbgl/style/layers/hillshade_layer_impl.hpp new file mode 100644 index 0000000000..5618b7dfe2 --- /dev/null +++ b/src/mbgl/style/layers/hillshade_layer_impl.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include <mbgl/style/layer_impl.hpp> +#include <mbgl/style/layers/hillshade_layer.hpp> +#include <mbgl/style/layers/hillshade_layer_properties.hpp> + +namespace mbgl { +namespace style { + +class HillshadeLayer::Impl : public Layer::Impl { +public: + using Layer::Impl::Impl; + + bool hasLayoutDifference(const Layer::Impl&) const override; + void stringifyLayout(rapidjson::Writer<rapidjson::StringBuffer>&) const override; + + HillshadePaintProperties::Transitionable paint; +}; + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/layers/hillshade_layer_properties.cpp b/src/mbgl/style/layers/hillshade_layer_properties.cpp new file mode 100644 index 0000000000..f296ab4520 --- /dev/null +++ b/src/mbgl/style/layers/hillshade_layer_properties.cpp @@ -0,0 +1,9 @@ +// This file is generated. Edit scripts/generate-style-code.js, then run `make style-code`. + +#include <mbgl/style/layers/hillshade_layer_properties.hpp> + +namespace mbgl { +namespace style { + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/layers/hillshade_layer_properties.hpp b/src/mbgl/style/layers/hillshade_layer_properties.hpp new file mode 100644 index 0000000000..260d7ea808 --- /dev/null +++ b/src/mbgl/style/layers/hillshade_layer_properties.hpp @@ -0,0 +1,49 @@ +// This file is generated. Edit scripts/generate-style-code.js, then run `make style-code`. + +#pragma once + +#include <mbgl/style/types.hpp> +#include <mbgl/style/layout_property.hpp> +#include <mbgl/style/paint_property.hpp> +#include <mbgl/style/properties.hpp> +#include <mbgl/programs/attributes.hpp> +#include <mbgl/programs/uniforms.hpp> + +namespace mbgl { +namespace style { + +struct HillshadeIlluminationDirection : PaintProperty<float> { + static float defaultValue() { return 335; } +}; + +struct HillshadeIlluminationAnchor : PaintProperty<HillshadeIlluminationAnchorType> { + static HillshadeIlluminationAnchorType defaultValue() { return HillshadeIlluminationAnchorType::Viewport; } +}; + +struct HillshadeExaggeration : PaintProperty<float> { + static float defaultValue() { return 0.5; } +}; + +struct HillshadeShadowColor : PaintProperty<Color> { + static Color defaultValue() { return Color::black(); } +}; + +struct HillshadeHighlightColor : PaintProperty<Color> { + static Color defaultValue() { return Color::white(); } +}; + +struct HillshadeAccentColor : PaintProperty<Color> { + static Color defaultValue() { return Color::black(); } +}; + +class HillshadePaintProperties : public Properties< + HillshadeIlluminationDirection, + HillshadeIlluminationAnchor, + HillshadeExaggeration, + HillshadeShadowColor, + HillshadeHighlightColor, + HillshadeAccentColor +> {}; + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/layers/layer.cpp.ejs b/src/mbgl/style/layers/layer.cpp.ejs index 573aabda8b..be44bb353d 100644 --- a/src/mbgl/style/layers/layer.cpp.ejs +++ b/src/mbgl/style/layers/layer.cpp.ejs @@ -59,7 +59,7 @@ const std::string& <%- camelize(type) %>Layer::getSourceID() const { return impl().source; } -<% if (type !== 'raster') { -%> +<% if (type !== 'raster' && type !== 'hillshade') { -%> void <%- camelize(type) %>Layer::setSourceLayer(const std::string& sourceLayer) { auto impl_ = mutableImpl(); impl_->sourceLayer = sourceLayer; diff --git a/src/mbgl/style/layers/symbol_layer.cpp b/src/mbgl/style/layers/symbol_layer.cpp index 9a944657ca..d1a1ba246e 100644 --- a/src/mbgl/style/layers/symbol_layer.cpp +++ b/src/mbgl/style/layers/symbol_layer.cpp @@ -412,15 +412,15 @@ void SymbolLayer::setTextField(DataDrivenPropertyValue<std::string> value) { baseImpl = std::move(impl_); observer->onLayerChanged(*this); } -PropertyValue<std::vector<std::string>> SymbolLayer::getDefaultTextFont() { +DataDrivenPropertyValue<std::vector<std::string>> SymbolLayer::getDefaultTextFont() { return TextFont::defaultValue(); } -PropertyValue<std::vector<std::string>> SymbolLayer::getTextFont() const { +DataDrivenPropertyValue<std::vector<std::string>> SymbolLayer::getTextFont() const { return impl().layout.get<TextFont>(); } -void SymbolLayer::setTextFont(PropertyValue<std::vector<std::string>> value) { +void SymbolLayer::setTextFont(DataDrivenPropertyValue<std::vector<std::string>> value) { if (value == getTextFont()) return; auto impl_ = mutableImpl(); diff --git a/src/mbgl/style/layers/symbol_layer_properties.hpp b/src/mbgl/style/layers/symbol_layer_properties.hpp index 436b5cbd4f..e70ac28d59 100644 --- a/src/mbgl/style/layers/symbol_layer_properties.hpp +++ b/src/mbgl/style/layers/symbol_layer_properties.hpp @@ -112,7 +112,7 @@ struct TextField : DataDrivenLayoutProperty<std::string> { static std::string defaultValue() { return ""; } }; -struct TextFont : LayoutProperty<std::vector<std::string>> { +struct TextFont : DataDrivenLayoutProperty<std::vector<std::string>> { static constexpr const char * key = "text-font"; static std::vector<std::string> defaultValue() { return { "Open Sans Regular", "Arial Unicode MS Regular" }; } }; diff --git a/src/mbgl/style/parser.cpp b/src/mbgl/style/parser.cpp index a83897dbf5..8d14d7972c 100644 --- a/src/mbgl/style/parser.cpp +++ b/src/mbgl/style/parser.cpp @@ -1,11 +1,13 @@ #include <mbgl/style/parser.hpp> #include <mbgl/style/layer_impl.hpp> +#include <mbgl/style/layers/symbol_layer.hpp> #include <mbgl/style/rapidjson_conversion.hpp> #include <mbgl/style/conversion.hpp> #include <mbgl/style/conversion/coordinate.hpp> #include <mbgl/style/conversion/source.hpp> #include <mbgl/style/conversion/layer.hpp> #include <mbgl/style/conversion/light.hpp> +#include <mbgl/style/conversion/transition_options.hpp> #include <mbgl/util/logging.hpp> #include <mbgl/util/string.hpp> @@ -117,6 +119,9 @@ StyleParseResult Parser::parse(const std::string& json) { } } + // Call for side effect of logging warnings for invalid values. + fontStacks(); + return nullptr; } @@ -149,7 +154,7 @@ void Parser::parseSources(const JSValue& value) { } for (const auto& property : value.GetObject()) { - std::string id = *conversion::toString(property.name); + std::string id { property.name.GetString(), property.name.GetStringLength() }; conversion::Error error; optional<std::unique_ptr<Source>> source = @@ -256,7 +261,7 @@ void Parser::parseLayer(const std::string& id, const JSValue& value, std::unique } layer = reference->cloneRef(id); - conversion::setPaintProperties(*layer, value); + conversion::setPaintProperties(*layer, conversion::Convertible(&value)); } else { conversion::Error error; optional<std::unique_ptr<Layer>> converted = conversion::convert<std::unique_ptr<Layer>>(value, error); @@ -269,28 +274,32 @@ void Parser::parseLayer(const std::string& id, const JSValue& value, std::unique } std::vector<FontStack> Parser::fontStacks() const { - std::set<FontStack> optional; + std::set<FontStack> result; for (const auto& layer : layers) { - if (layer->is<SymbolLayer>()) { - PropertyValue<FontStack> textFont = layer->as<SymbolLayer>()->getTextFont(); - if (textFont.isUndefined()) { - optional.insert({"Open Sans Regular", "Arial Unicode MS Regular"}); - } else if (textFont.isConstant()) { - optional.insert(textFont.asConstant()); - } else if (textFont.isCameraFunction()) { - textFont.asCameraFunction().stops.match( - [&] (const auto& stops) { - for (const auto& stop : stops.stops) { - optional.insert(stop.second); + if (layer->is<SymbolLayer>() && !layer->as<SymbolLayer>()->getTextField().isUndefined()) { + layer->as<SymbolLayer>()->getTextFont().match( + [&] (Undefined) { + result.insert({"Open Sans Regular", "Arial Unicode MS Regular"}); + }, + [&] (const FontStack& constant) { + result.insert(constant); + }, + [&] (const auto& function) { + for (const auto& value : function.possibleOutputs()) { + if (value) { + result.insert(*value); + } else { + Log::Warning(Event::ParseStyle, "Layer '%s' has an invalid value for text-font and will not work offline. Output values must be contained as literals within the expression.", layer->getID().c_str()); + break; } } - ); - } + } + ); } } - return std::vector<FontStack>(optional.begin(), optional.end()); + return std::vector<FontStack>(result.begin(), result.end()); } } // namespace style diff --git a/src/mbgl/style/rapidjson_conversion.hpp b/src/mbgl/style/rapidjson_conversion.hpp index 48a764ccb4..79bd9c928b 100644 --- a/src/mbgl/style/rapidjson_conversion.hpp +++ b/src/mbgl/style/rapidjson_conversion.hpp @@ -1,103 +1,125 @@ #pragma once #include <mbgl/util/rapidjson.hpp> -#include <mbgl/util/feature.hpp> #include <mbgl/style/conversion.hpp> +#include <mapbox/geojson.hpp> +#include <mapbox/geojson/rapidjson.hpp> + namespace mbgl { namespace style { namespace conversion { -inline bool isUndefined(const JSValue& value) { - return value.IsNull(); -} - -inline bool isArray(const JSValue& value) { - return value.IsArray(); -} +template <> +class ConversionTraits<const JSValue*> { +public: + static bool isUndefined(const JSValue* value) { + return value->IsNull(); + } -inline std::size_t arrayLength(const JSValue& value) { - return value.Size(); -} + static bool isArray(const JSValue* value) { + return value->IsArray(); + } -inline const JSValue& arrayMember(const JSValue& value, std::size_t i) { - return value[rapidjson::SizeType(i)]; -} + static std::size_t arrayLength(const JSValue* value) { + return value->Size(); + } -inline bool isObject(const JSValue& value) { - return value.IsObject(); -} + static const JSValue* arrayMember(const JSValue* value, std::size_t i) { + return &(*value)[rapidjson::SizeType(i)]; + } -inline const JSValue* objectMember(const JSValue& value, const char * name) { - if (!value.HasMember(name)) { - return nullptr; + static bool isObject(const JSValue* value) { + return value->IsObject(); } - return &value[name]; -} -template <class Fn> -optional<Error> eachMember(const JSValue& value, Fn&& fn) { - assert(value.IsObject()); - for (const auto& property : value.GetObject()) { - optional<Error> result = - fn({ property.name.GetString(), property.name.GetStringLength() }, property.value); - if (result) { - return result; + static optional<const JSValue*> objectMember(const JSValue* value, const char * name) { + if (!value->HasMember(name)) { + return optional<const JSValue*>(); } + const JSValue* const& member = &(*value)[name]; + return {member}; } - return {}; -} -inline optional<bool> toBool(const JSValue& value) { - if (!value.IsBool()) { + template <class Fn> + static optional<Error> eachMember(const JSValue* value, Fn&& fn) { + assert(value->IsObject()); + for (const auto& property : value->GetObject()) { + optional<Error> result = + fn({ property.name.GetString(), property.name.GetStringLength() }, &property.value); + if (result) { + return result; + } + } return {}; } - return value.GetBool(); -} -inline optional<float> toNumber(const JSValue& value) { - if (!value.IsNumber()) { - return {}; + static optional<bool> toBool(const JSValue* value) { + if (!value->IsBool()) { + return {}; + } + return value->GetBool(); } - return value.GetDouble(); -} -inline optional<double> toDouble(const JSValue& value) { - if (!value.IsNumber()) { - return {}; + static optional<float> toNumber(const JSValue* value) { + if (!value->IsNumber()) { + return {}; + } + return value->GetDouble(); } - return value.GetDouble(); -} -inline optional<std::string> toString(const JSValue& value) { - if (!value.IsString()) { - return {}; + static optional<double> toDouble(const JSValue* value) { + if (!value->IsNumber()) { + return {}; + } + return value->GetDouble(); + } + + static optional<std::string> toString(const JSValue* value) { + if (!value->IsString()) { + return {}; + } + return {{ value->GetString(), value->GetStringLength() }}; } - return {{ value.GetString(), value.GetStringLength() }}; -} -inline optional<Value> toValue(const JSValue& value) { - switch (value.GetType()) { - case rapidjson::kNullType: - case rapidjson::kFalseType: - return { false }; + static optional<Value> toValue(const JSValue* value) { + switch (value->GetType()) { + case rapidjson::kNullType: + case rapidjson::kFalseType: + return { false }; - case rapidjson::kTrueType: - return { true }; + case rapidjson::kTrueType: + return { true }; - case rapidjson::kStringType: - return { std::string { value.GetString(), value.GetStringLength() } }; + case rapidjson::kStringType: + return { std::string { value->GetString(), value->GetStringLength() } }; - case rapidjson::kNumberType: - if (value.IsUint64()) return { value.GetUint64() }; - if (value.IsInt64()) return { value.GetInt64() }; - return { value.GetDouble() }; + case rapidjson::kNumberType: + if (value->IsUint64()) return { value->GetUint64() }; + if (value->IsInt64()) return { value->GetInt64() }; + return { value->GetDouble() }; - default: + default: + return {}; + } + } + + static optional<GeoJSON> toGeoJSON(const JSValue* value, Error& error) { + try { + return mapbox::geojson::convert(*value); + } catch (const std::exception& ex) { + error = { ex.what() }; return {}; + } } +}; + +template <class T, class...Args> +optional<T> convert(const JSValue& value, Error& error, Args&&...args) { + return convert<T>(Convertible(&value), error, std::forward<Args>(args)...); } } // namespace conversion } // namespace style } // namespace mbgl + diff --git a/src/mbgl/style/sources/custom_geometry_source.cpp b/src/mbgl/style/sources/custom_geometry_source.cpp new file mode 100644 index 0000000000..b37490a5ce --- /dev/null +++ b/src/mbgl/style/sources/custom_geometry_source.cpp @@ -0,0 +1,45 @@ +#include <mbgl/style/sources/custom_geometry_source.hpp> +#include <mbgl/style/custom_tile_loader.hpp> +#include <mbgl/style/sources/custom_geometry_source_impl.hpp> +#include <mbgl/actor/actor.hpp> +#include <mbgl/actor/scheduler.hpp> +#include <mbgl/tile/tile_id.hpp> +#include <mbgl/util/shared_thread_pool.hpp> +#include <tuple> +#include <map> + +namespace mbgl { +namespace style { + +CustomGeometrySource::CustomGeometrySource(std::string id, + const CustomGeometrySource::Options options) + : Source(makeMutable<CustomGeometrySource::Impl>(std::move(id), options)), + loader(std::make_unique<Actor<CustomTileLoader>>(*sharedThreadPool(), options.fetchTileFunction, options.cancelTileFunction)) { +} + +CustomGeometrySource::~CustomGeometrySource() = default; + +const CustomGeometrySource::Impl& CustomGeometrySource::impl() const { + return static_cast<const CustomGeometrySource::Impl&>(*baseImpl); +} + +void CustomGeometrySource::loadDescription(FileSource&) { + baseImpl = makeMutable<CustomGeometrySource::Impl>(impl(), loader->self()); + loaded = true; +} + +void CustomGeometrySource::setTileData(const CanonicalTileID& tileID, + const GeoJSON& data) { + loader->invoke(&CustomTileLoader::setTileData, tileID, data); +} + +void CustomGeometrySource::invalidateTile(const CanonicalTileID& tileID) { + loader->invoke(&CustomTileLoader::invalidateTile, tileID); +} + +void CustomGeometrySource::invalidateRegion(const LatLngBounds& bounds) { + loader->invoke(&CustomTileLoader::invalidateRegion, bounds, impl().getZoomRange()); +} + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/sources/custom_geometry_source_impl.cpp b/src/mbgl/style/sources/custom_geometry_source_impl.cpp new file mode 100644 index 0000000000..67d52bdc24 --- /dev/null +++ b/src/mbgl/style/sources/custom_geometry_source_impl.cpp @@ -0,0 +1,40 @@ +#include <mbgl/style/sources/custom_geometry_source_impl.hpp> +#include <mbgl/style/source_observer.hpp> + +namespace mbgl { +namespace style { + +CustomGeometrySource::Impl::Impl(std::string id_, + const CustomGeometrySource::Options options) + : Source::Impl(SourceType::CustomVector, std::move(id_)), + tileOptions(options.tileOptions), + zoomRange(options.zoomRange), + loaderRef({}) { +} + +CustomGeometrySource::Impl::Impl(const Impl& impl, ActorRef<CustomTileLoader> loaderRef_) + : Source::Impl(impl), + tileOptions(impl.tileOptions), + zoomRange(impl.zoomRange), + loaderRef(loaderRef_){ + +} + +optional<std::string> CustomGeometrySource::Impl::getAttribution() const { + return {}; +} + +CustomGeometrySource::TileOptions CustomGeometrySource::Impl::getTileOptions() const { + return tileOptions; +} + +Range<uint8_t> CustomGeometrySource::Impl::getZoomRange() const { + return zoomRange; +} + +optional<ActorRef<CustomTileLoader>> CustomGeometrySource::Impl::getTileLoader() const { + return loaderRef; +} + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/sources/custom_geometry_source_impl.hpp b/src/mbgl/style/sources/custom_geometry_source_impl.hpp new file mode 100644 index 0000000000..ce7187202d --- /dev/null +++ b/src/mbgl/style/sources/custom_geometry_source_impl.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include <mbgl/style/source_impl.hpp> +#include <mbgl/style/sources/custom_geometry_source.hpp> +#include <mbgl/style/custom_tile_loader.hpp> +#include <mbgl/actor/actor_ref.hpp> + +namespace mbgl { +namespace style { + +class CustomGeometrySource::Impl : public Source::Impl { +public: + Impl(std::string id, CustomGeometrySource::Options options); + Impl(const Impl&, ActorRef<CustomTileLoader>); + + optional<std::string> getAttribution() const final; + + CustomGeometrySource::TileOptions getTileOptions() const; + Range<uint8_t> getZoomRange() const; + optional<ActorRef<CustomTileLoader>> getTileLoader() const; + +private: + CustomGeometrySource::TileOptions tileOptions; + Range<uint8_t> zoomRange; + optional<ActorRef<CustomTileLoader>> loaderRef; +}; + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/sources/raster_dem_source.cpp b/src/mbgl/style/sources/raster_dem_source.cpp new file mode 100644 index 0000000000..dc9feb8eeb --- /dev/null +++ b/src/mbgl/style/sources/raster_dem_source.cpp @@ -0,0 +1,19 @@ +#include <mbgl/style/sources/raster_dem_source.hpp> +#include <mbgl/style/sources/raster_source_impl.hpp> +#include <mbgl/style/source_observer.hpp> +#include <mbgl/style/conversion/json.hpp> +#include <mbgl/style/conversion/tileset.hpp> +#include <mbgl/storage/file_source.hpp> +#include <mbgl/util/mapbox.hpp> + +namespace mbgl { +namespace style { + +RasterDEMSource::RasterDEMSource(std::string id, variant<std::string, Tileset> urlOrTileset_, uint16_t tileSize) + : RasterSource(std::move(id), urlOrTileset_, tileSize, SourceType::RasterDEM){ +} + + + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/sources/raster_source.cpp b/src/mbgl/style/sources/raster_source.cpp index 0a0412a4ed..53f29d660b 100644 --- a/src/mbgl/style/sources/raster_source.cpp +++ b/src/mbgl/style/sources/raster_source.cpp @@ -9,8 +9,8 @@ namespace mbgl { namespace style { -RasterSource::RasterSource(std::string id, variant<std::string, Tileset> urlOrTileset_, uint16_t tileSize) - : Source(makeMutable<Impl>(std::move(id), tileSize)), +RasterSource::RasterSource(std::string id, variant<std::string, Tileset> urlOrTileset_, uint16_t tileSize, SourceType sourceType) + : Source(makeMutable<Impl>(sourceType, std::move(id), tileSize)), urlOrTileset(std::move(urlOrTileset_)) { } diff --git a/src/mbgl/style/sources/raster_source_impl.cpp b/src/mbgl/style/sources/raster_source_impl.cpp index 50dae1f07e..4db25aafd1 100644 --- a/src/mbgl/style/sources/raster_source_impl.cpp +++ b/src/mbgl/style/sources/raster_source_impl.cpp @@ -3,8 +3,8 @@ namespace mbgl { namespace style { -RasterSource::Impl::Impl(std::string id_, uint16_t tileSize_) - : Source::Impl(SourceType::Raster, std::move(id_)), +RasterSource::Impl::Impl(SourceType sourceType, std::string id_, uint16_t tileSize_) + : Source::Impl(sourceType, std::move(id_)), tileSize(tileSize_) { } diff --git a/src/mbgl/style/sources/raster_source_impl.hpp b/src/mbgl/style/sources/raster_source_impl.hpp index c41d5485b2..96f59a2159 100644 --- a/src/mbgl/style/sources/raster_source_impl.hpp +++ b/src/mbgl/style/sources/raster_source_impl.hpp @@ -8,7 +8,7 @@ namespace style { class RasterSource::Impl : public Source::Impl { public: - Impl(std::string id, uint16_t tileSize); + Impl(SourceType sourceType, std::string id, uint16_t tileSize); Impl(const Impl&, Tileset); optional<Tileset> getTileset() const; diff --git a/src/mbgl/style/style_impl.cpp b/src/mbgl/style/style_impl.cpp index 3214c6316e..f2a9c0f222 100644 --- a/src/mbgl/style/style_impl.cpp +++ b/src/mbgl/style/style_impl.cpp @@ -9,6 +9,7 @@ #include <mbgl/style/layers/line_layer.hpp> #include <mbgl/style/layers/circle_layer.hpp> #include <mbgl/style/layers/raster_layer.hpp> +#include <mbgl/style/layers/hillshade_layer.hpp> #include <mbgl/style/layer_impl.hpp> #include <mbgl/style/parser.hpp> #include <mbgl/style/transition_options.hpp> diff --git a/src/mbgl/style/types.cpp b/src/mbgl/style/types.cpp index 0a1781e01b..bdfa20a047 100644 --- a/src/mbgl/style/types.cpp +++ b/src/mbgl/style/types.cpp @@ -12,6 +12,7 @@ MBGL_DEFINE_ENUM(SourceType, { { SourceType::Video, "video" }, { SourceType::Annotations, "annotations" }, { SourceType::Image, "image" }, + { SourceType::CustomVector, "customvector" } }); MBGL_DEFINE_ENUM(VisibilityType, { @@ -24,6 +25,11 @@ MBGL_DEFINE_ENUM(TranslateAnchorType, { { TranslateAnchorType::Viewport, "viewport" }, }); +MBGL_DEFINE_ENUM(HillshadeIlluminationAnchorType, { + { HillshadeIlluminationAnchorType::Map, "map" }, + { HillshadeIlluminationAnchorType::Viewport, "viewport" }, +}); + MBGL_DEFINE_ENUM(RotateAnchorType, { { RotateAnchorType::Map, "map" }, { RotateAnchorType::Viewport, "viewport" }, diff --git a/src/mbgl/text/collision_feature.cpp b/src/mbgl/text/collision_feature.cpp index 3eb08da8d1..6d6f2aabc7 100644 --- a/src/mbgl/text/collision_feature.cpp +++ b/src/mbgl/text/collision_feature.cpp @@ -13,8 +13,9 @@ CollisionFeature::CollisionFeature(const GeometryCoordinates& line, const float padding, const style::SymbolPlacementType placement, IndexedSubfeature indexedFeature_, - const AlignmentType alignment) - : indexedFeature(std::move(indexedFeature_)) { + const float overscaling) + : indexedFeature(std::move(indexedFeature_)) + , alongLine(placement == style::SymbolPlacementType::Line) { if (top == 0 && bottom == 0 && left == 0 && right == 0) return; const float y1 = top * boxScale - padding; @@ -22,7 +23,7 @@ CollisionFeature::CollisionFeature(const GeometryCoordinates& line, const float x1 = left * boxScale - padding; const float x2 = right * boxScale + padding; - if (placement == style::SymbolPlacementType::Line) { + if (alongLine) { float height = y2 - y1; const double length = x2 - x1; @@ -31,29 +32,26 @@ CollisionFeature::CollisionFeature(const GeometryCoordinates& line, height = std::max(10.0f * boxScale, height); GeometryCoordinate anchorPoint = convertPoint<int16_t>(anchor.point); - - if (alignment == AlignmentType::Straight) { - // used for icon labels that are aligned with the line, but don't curve along it - const GeometryCoordinate vector = convertPoint<int16_t>(util::unit(convertPoint<double>(line[anchor.segment + 1] - line[anchor.segment])) * length); - const GeometryCoordinates newLine({ anchorPoint - vector, anchorPoint + vector }); - bboxifyLabel(newLine, anchorPoint, 0, length, height); - } else { - // used for text labels that curve along a line - bboxifyLabel(line, anchorPoint, anchor.segment, length, height); - } + bboxifyLabel(line, anchorPoint, anchor.segment, length, height, overscaling); } else { - boxes.emplace_back(anchor.point, Point<float>{ 0, 0 }, x1, y1, x2, y2, std::numeric_limits<float>::infinity()); + boxes.emplace_back(anchor.point, Point<float>{ 0, 0 }, x1, y1, x2, y2); } } void CollisionFeature::bboxifyLabel(const GeometryCoordinates& line, GeometryCoordinate& anchorPoint, - const int segment, const float labelLength, const float boxSize) { + const int segment, const float labelLength, const float boxSize, const float overscaling) { const float step = boxSize / 2; const int nBoxes = std::floor(labelLength / step); - // We calculate line collision boxes out to 300% of what would normally be our + // We calculate line collision circles out to 300% of what would normally be our // max size, to allow collision detection to work on labels that expand as // they move into the distance - const int nPitchPaddingBoxes = std::floor(nBoxes / 2); + // Vertically oriented labels in the distant field can extend past this padding + // This is a noticeable problem in overscaled tiles where the pitch 0-based + // symbol spacing will put labels very close together in a pitched map. + // To reduce the cost of adding extra collision circles, we slowly increase + // them for overscaled tiles. + const float overscalingPaddingFactor = 1 + .4 * std::log(overscaling) / std::log(2); + const int nPitchPaddingBoxes = std::floor(nBoxes * overscalingPaddingFactor / 2); // offset the center of the first box by half a box so that the edge of the // box is at the edge of the label. @@ -124,47 +122,18 @@ void CollisionFeature::bboxifyLabel(const GeometryCoordinates& line, GeometryCoo p0.x + segmentBoxDistance / segmentLength * (p1.x - p0.x), p0.y + segmentBoxDistance / segmentLength * (p1.y - p0.y) }; - - // Distance from label anchor point to inner (towards center) edge of this box - // The tricky thing here is that box positioning doesn't change with scale, - // but box size does change with scale. - // Technically, distanceToInnerEdge should be: - // Math.max(Math.abs(boxDistanceToAnchor - firstBoxOffset) - (step / scale), 0); - // But using that formula would make solving for maxScale more difficult, so we - // approximate with scale=2. - // This makes our calculation spot-on at scale=2, and on the conservative side for - // lower scales - const float distanceToInnerEdge = std::max(std::fabs(boxDistanceToAnchor - firstBoxOffset) - step / 2, 0.0f); - float maxScale = util::division(labelLength / 2, distanceToInnerEdge, std::numeric_limits<float>::infinity()); - - // The box maxScale calculations are designed to be conservative on collisions in the scale range - // [1,2]. At scale=1, each box has 50% overlap, and at scale=2, the boxes are lined up edge - // to edge (beyond scale 2, gaps start to appear, which could potentially allow missed collisions). - // We add "pitch padding" boxes to the left and right to handle effective underzooming - // (scale < 1) when labels are in the distance. The overlap approximation could cause us to use - // these boxes when the scale is greater than 1, but we prevent that because we know - // they're only necessary for scales less than one. - // This preserves the pre-pitch-padding behavior for unpitched maps. - if (i < 0 || i >= nBoxes) { - maxScale = std::min(maxScale, 0.99f); - } - - boxes.emplace_back(boxAnchor, boxAnchor - convertPoint<float>(anchorPoint), -boxSize / 2, -boxSize / 2, boxSize / 2, boxSize / 2, maxScale); + + // If the box is within boxSize of the anchor, force the box to be used + // (so even 0-width labels use at least one box) + // Otherwise, the .8 multiplication gives us a little bit of conservative + // padding in choosing which boxes to use (see CollisionIndex#placedCollisionCircles) + const float paddedAnchorDistance = std::abs(boxDistanceToAnchor - firstBoxOffset) < step ? + 0 : + (boxDistanceToAnchor - firstBoxOffset) * 0.8; + + boxes.emplace_back(boxAnchor, boxAnchor - convertPoint<float>(anchorPoint), -boxSize / 2, -boxSize / 2, boxSize / 2, boxSize / 2, paddedAnchorDistance, boxSize / 2); } } -float CollisionBox::adjustedMaxScale(const std::array<float, 4>& rotationMatrix, const float yStretch) const { - // When the map is pitched the distance covered by a line changes. - // Adjust the max scale by (approximatePitchedLength / approximateRegularLength) - // to compensate for this. - const Point<float> rotatedOffset = util::matrixMultiply(rotationMatrix, offset); - const float xSqr = rotatedOffset.x * rotatedOffset.x; - const float ySqr = rotatedOffset.y * rotatedOffset.y; - const float yStretchSqr = ySqr * yStretch * yStretch; - const float adjustmentFactor = xSqr + ySqr != 0 ? - std::sqrt((xSqr + yStretchSqr) / (xSqr + ySqr)) : - 1.0f; - return maxScale * adjustmentFactor; -} } // namespace mbgl diff --git a/src/mbgl/text/collision_feature.hpp b/src/mbgl/text/collision_feature.hpp index 3b6e461a26..df1b12819c 100644 --- a/src/mbgl/text/collision_feature.hpp +++ b/src/mbgl/text/collision_feature.hpp @@ -11,10 +11,8 @@ namespace mbgl { class CollisionBox { public: - CollisionBox(Point<float> _anchor, Point<float> _offset, float _x1, float _y1, float _x2, float _y2, float _maxScale) : - anchor(std::move(_anchor)), offset(_offset), x1(_x1), y1(_y1), x2(_x2), y2(_y2), maxScale(_maxScale) {} - - float adjustedMaxScale(const std::array<float, 4>& rotationMatrix, const float yStretch) const; + CollisionBox(Point<float> _anchor, Point<float> _offset, float _x1, float _y1, float _x2, float _y2, float _signedDistanceFromAnchor = 0, float _radius = 0) : + anchor(std::move(_anchor)), offset(_offset), x1(_x1), y1(_y1), x2(_x2), y2(_y2), used(true), signedDistanceFromAnchor(_signedDistanceFromAnchor), radius(_radius) {} // the box is centered around the anchor point Point<float> anchor; @@ -28,20 +26,23 @@ public: float x2; float y2; - // the box is only valid for scales < maxScale. - // The box does not block other boxes at scales >= maxScale; - float maxScale; + // Projected box geometry: generated/updated at placement time + float px1; + float py1; + float px2; + float py2; + + // Projected circle geometry: generated/updated at placement time + float px; + float py; + bool used; - // the scale at which the label can first be shown - float placementScale = 0.0f; + float signedDistanceFromAnchor; + float radius; }; class CollisionFeature { public: - enum class AlignmentType : bool { - Straight = false, - Curved - }; // for text CollisionFeature(const GeometryCoordinates& line, @@ -50,23 +51,31 @@ public: const float boxScale, const float padding, const style::SymbolPlacementType placement, - const IndexedSubfeature& indexedFeature_) - : CollisionFeature(line, anchor, shapedText.top, shapedText.bottom, shapedText.left, shapedText.right, boxScale, padding, placement, indexedFeature_, AlignmentType::Curved) {} + const IndexedSubfeature& indexedFeature_, + const float overscaling) + : CollisionFeature(line, anchor, shapedText.top, shapedText.bottom, shapedText.left, shapedText.right, boxScale, padding, placement, indexedFeature_, overscaling) {} // for icons + // Icons collision features are always SymbolPlacementType::Point, which means the collision feature + // will be viewport-rotation-aligned even if the icon is map-rotation-aligned (e.g. `icon-rotation-alignment: map` + // _or_ `symbol-placement: line`). We're relying on most icons being "close enough" to square that having + // incorrect rotation alignment doesn't throw off collision detection too much. + // See: https://github.com/mapbox/mapbox-gl-js/issues/4861 CollisionFeature(const GeometryCoordinates& line, const Anchor& anchor, optional<PositionedIcon> shapedIcon, const float boxScale, const float padding, - const style::SymbolPlacementType placement, const IndexedSubfeature& indexedFeature_) : CollisionFeature(line, anchor, (shapedIcon ? shapedIcon->top() : 0), (shapedIcon ? shapedIcon->bottom() : 0), (shapedIcon ? shapedIcon->left() : 0), (shapedIcon ? shapedIcon->right() : 0), - boxScale, padding, placement, indexedFeature_, AlignmentType::Straight) {} + boxScale, + padding, + style::SymbolPlacementType::Point, + indexedFeature_, 1) {} CollisionFeature(const GeometryCoordinates& line, const Anchor&, @@ -78,14 +87,15 @@ public: const float padding, const style::SymbolPlacementType, IndexedSubfeature, - const AlignmentType); + const float overscaling); std::vector<CollisionBox> boxes; IndexedSubfeature indexedFeature; + bool alongLine; private: void bboxifyLabel(const GeometryCoordinates& line, GeometryCoordinate& anchorPoint, - const int segment, const float length, const float height); + const int segment, const float length, const float height, const float overscaling); }; } // namespace mbgl diff --git a/src/mbgl/text/collision_index.cpp b/src/mbgl/text/collision_index.cpp new file mode 100644 index 0000000000..833658c33e --- /dev/null +++ b/src/mbgl/text/collision_index.cpp @@ -0,0 +1,364 @@ +#include <mbgl/text/collision_index.hpp> +#include <mbgl/layout/symbol_instance.hpp> +#include <mbgl/geometry/feature_index.hpp> +#include <mbgl/math/log2.hpp> +#include <mbgl/util/constants.hpp> +#include <mbgl/util/math.hpp> +#include <mbgl/math/minmax.hpp> +#include <mbgl/util/intersection_tests.hpp> +#include <mbgl/layout/symbol_projection.hpp> + +#include <mapbox/geometry/envelope.hpp> + +#include <mbgl/renderer/buckets/symbol_bucket.hpp> // For PlacedSymbol: pull out to another location + +#include <cmath> + +namespace mbgl { + +// When a symbol crosses the edge that causes it to be included in +// collision detection, it will cause changes in the symbols around +// it. This constant specifies how many pixels to pad the edge of +// the viewport for collision detection so that the bulk of the changes +// occur offscreen. Making this constant greater increases label +// stability, but it's expensive. +static const float viewportPadding = 100; + +CollisionIndex::CollisionIndex(const TransformState& transformState_) + : transformState(transformState_) + , collisionGrid(transformState.getSize().width + 2 * viewportPadding, transformState.getSize().height + 2 * viewportPadding, 25) + , ignoredGrid(transformState.getSize().width + 2 * viewportPadding, transformState.getSize().height + 2 * viewportPadding, 25) + , screenRightBoundary(transformState.getSize().width + viewportPadding) + , screenBottomBoundary(transformState.getSize().height + viewportPadding) + , gridRightBoundary(transformState.getSize().width + 2 * viewportPadding) + , gridBottomBoundary(transformState.getSize().height + 2 * viewportPadding) + , pitchFactor(std::cos(transformState.getPitch()) * transformState.getCameraToCenterDistance()) +{} + +float CollisionIndex::approximateTileDistance(const TileDistance& tileDistance, const float lastSegmentAngle, const float pixelsToTileUnits, const float cameraToAnchorDistance, const bool pitchWithMap) { + // This is a quick and dirty solution for chosing which collision circles to use (since collision circles are + // laid out in tile units). Ideally, I think we should generate collision circles on the fly in viewport coordinates + // at the time we do collision detection. + + // incidenceStretch is the ratio of how much y space a label takes up on a tile while drawn perpendicular to the viewport vs + // how much space it would take up if it were drawn flat on the tile + // Using law of sines, camera_to_anchor/sin(ground_angle) = camera_to_center/sin(incidence_angle) + // Incidence angle 90 -> head on, sin(incidence_angle) = 1, no stretch + // Incidence angle 1 -> very oblique, sin(incidence_angle) =~ 0, lots of stretch + // ground_angle = u_pitch + PI/2 -> sin(ground_angle) = cos(u_pitch) + // incidenceStretch = 1 / sin(incidenceAngle) + + const float incidenceStretch = pitchWithMap ? 1 : cameraToAnchorDistance / pitchFactor; + const float lastSegmentTile = tileDistance.lastSegmentViewportDistance * pixelsToTileUnits; + return tileDistance.prevTileDistance + + lastSegmentTile + + (incidenceStretch - 1) * lastSegmentTile * std::abs(std::sin(lastSegmentAngle)); +} + +bool CollisionIndex::isOffscreen(const CollisionBox& box) const { + return box.px2 < viewportPadding || box.px1 >= screenRightBoundary || box.py2 < viewportPadding || box.py1 >= screenBottomBoundary; +} + +bool CollisionIndex::isInsideGrid(const CollisionBox& box) const { + return box.px2 >= 0 && box.px1 < gridRightBoundary && box.py2 >= 0 && box.py1 < gridBottomBoundary; +} + + +std::pair<bool,bool> CollisionIndex::placeFeature(CollisionFeature& feature, + const mat4& posMatrix, + const mat4& labelPlaneMatrix, + const float textPixelRatio, + PlacedSymbol& symbol, + const float scale, + const float fontSize, + const bool allowOverlap, + const bool pitchWithMap, + const bool collisionDebug) { + if (!feature.alongLine) { + CollisionBox& box = feature.boxes.front(); + const auto projectedPoint = projectAndGetPerspectiveRatio(posMatrix, box.anchor); + const float tileToViewport = textPixelRatio * projectedPoint.second; + box.px1 = box.x1 * tileToViewport + projectedPoint.first.x; + box.py1 = box.y1 * tileToViewport + projectedPoint.first.y; + box.px2 = box.x2 * tileToViewport + projectedPoint.first.x; + box.py2 = box.y2 * tileToViewport + projectedPoint.first.y; + + if (!isInsideGrid(box) || + (!allowOverlap && collisionGrid.hitTest({{ box.px1, box.py1 }, { box.px2, box.py2 }}))) { + return { false, false }; + } + + return {true, isOffscreen(box)}; + } else { + return placeLineFeature(feature, posMatrix, labelPlaneMatrix, textPixelRatio, symbol, scale, fontSize, allowOverlap, pitchWithMap, collisionDebug); + } +} + +std::pair<bool,bool> CollisionIndex::placeLineFeature(CollisionFeature& feature, + const mat4& posMatrix, + const mat4& labelPlaneMatrix, + const float textPixelRatio, + PlacedSymbol& symbol, + const float scale, + const float fontSize, + const bool allowOverlap, + const bool pitchWithMap, + const bool collisionDebug) { + + const auto tileUnitAnchorPoint = symbol.anchorPoint; + const auto projectedAnchor = projectAnchor(posMatrix, tileUnitAnchorPoint); + + const float fontScale = fontSize / 24; + const float lineOffsetX = symbol.lineOffset[0] * fontSize; + const float lineOffsetY = symbol.lineOffset[1] * fontSize; + + const auto labelPlaneAnchorPoint = project(tileUnitAnchorPoint, labelPlaneMatrix).first; + + const auto firstAndLastGlyph = placeFirstAndLastGlyph( + fontScale, + lineOffsetX, + lineOffsetY, + /*flip*/ false, + labelPlaneAnchorPoint, + tileUnitAnchorPoint, + symbol, + labelPlaneMatrix, + /*return tile distance*/ true); + + bool collisionDetected = false; + bool inGrid = false; + bool entirelyOffscreen = true; + + const auto tileToViewport = projectedAnchor.first * textPixelRatio; + // pixelsToTileUnits is used for translating line geometry to tile units + // ... so we care about 'scale' but not 'perspectiveRatio' + // equivalent to pixel_to_tile_units + const auto pixelsToTileUnits = 1 / (textPixelRatio * scale); + + float firstTileDistance = 0, lastTileDistance = 0; + if (firstAndLastGlyph) { + firstTileDistance = approximateTileDistance(*(firstAndLastGlyph->first.tileDistance), firstAndLastGlyph->first.angle, pixelsToTileUnits, projectedAnchor.second, pitchWithMap); + lastTileDistance = approximateTileDistance(*(firstAndLastGlyph->second.tileDistance), firstAndLastGlyph->second.angle, pixelsToTileUnits, projectedAnchor.second, pitchWithMap); + } + + bool atLeastOneCirclePlaced = false; + for (size_t i = 0; i < feature.boxes.size(); i++) { + CollisionBox& circle = feature.boxes[i]; + const float boxSignedDistanceFromAnchor = circle.signedDistanceFromAnchor; + if (!firstAndLastGlyph || + (boxSignedDistanceFromAnchor < -firstTileDistance) || + (boxSignedDistanceFromAnchor > lastTileDistance)) { + // The label either doesn't fit on its line or we + // don't need to use this circle because the label + // doesn't extend this far. Either way, mark the circle unused. + circle.used = false; + continue; + } + + const auto projectedPoint = projectPoint(posMatrix, circle.anchor); + const float tileUnitRadius = (circle.x2 - circle.x1) / 2; + const float radius = tileUnitRadius * tileToViewport; + + if (atLeastOneCirclePlaced) { + const CollisionBox& previousCircle = feature.boxes[i - 1]; + const float dx = projectedPoint.x - previousCircle.px; + const float dy = projectedPoint.y - previousCircle.py; + // The circle edges touch when the distance between their centers is 2x the radius + // When the distance is 1x the radius, they're doubled up, and we could remove + // every other circle while keeping them all in touch. + // We actually start removing circles when the distance is √2x the radius: + // thinning the number of circles as much as possible is a major performance win, + // and the small gaps introduced don't make a very noticeable difference. + const bool placedTooDensely = radius * radius * 2 > dx * dx + dy * dy; + if (placedTooDensely) { + const bool atLeastOneMoreCircle = (i + 1) < feature.boxes.size(); + if (atLeastOneMoreCircle) { + const CollisionBox& nextCircle = feature.boxes[i + 1]; + const float nextBoxDistanceFromAnchor = nextCircle.signedDistanceFromAnchor; + if ((nextBoxDistanceFromAnchor > -firstTileDistance) && + (nextBoxDistanceFromAnchor < lastTileDistance)) { + // Hide significantly overlapping circles, unless this is the last one we can + // use, in which case we want to keep it in place even if it's tightly packed + // with the one before it. + circle.used = false; + continue; + } + } + } + } + + atLeastOneCirclePlaced = true; + circle.px1 = projectedPoint.x - radius; + circle.px2 = projectedPoint.x + radius; + circle.py1 = projectedPoint.y - radius; + circle.py2 = projectedPoint.y + radius; + + circle.used = true; + + circle.px = projectedPoint.x; + circle.py = projectedPoint.y; + circle.radius = radius; + + entirelyOffscreen &= isOffscreen(circle); + inGrid |= isInsideGrid(circle); + + if (!allowOverlap) { + if (collisionGrid.hitTest({{circle.px, circle.py}, circle.radius})) { + if (!collisionDebug) { + return {false, false}; + } else { + // Don't early exit if we're showing the debug circles because we still want to calculate + // which circles are in use + collisionDetected = true; + } + } + } + } + + return {!collisionDetected && firstAndLastGlyph && inGrid, entirelyOffscreen}; +} + + +void CollisionIndex::insertFeature(CollisionFeature& feature, bool ignorePlacement) { + if (feature.alongLine) { + for (auto& circle : feature.boxes) { + if (!circle.used) { + continue; + } + + if (ignorePlacement) { + ignoredGrid.insert(IndexedSubfeature(feature.indexedFeature), {{ circle.px, circle.py }, circle.radius}); + } else { + collisionGrid.insert(IndexedSubfeature(feature.indexedFeature), {{ circle.px, circle.py }, circle.radius}); + } + } + } else { + assert(feature.boxes.size() == 1); + auto& box = feature.boxes[0]; + if (ignorePlacement) { + ignoredGrid.insert(IndexedSubfeature(feature.indexedFeature), {{ box.px1, box.py1 }, { box.px2, box.py2 }}); + } else { + collisionGrid.insert(IndexedSubfeature(feature.indexedFeature), {{ box.px1, box.py1 }, { box.px2, box.py2 }}); + } + } +} + +bool polygonIntersectsBox(const LineString<float>& polygon, const GridIndex<IndexedSubfeature>::BBox& bbox) { + // This is just a wrapper that allows us to use the integer-based util::polygonIntersectsPolygon + // Conversion limits our query accuracy to single-pixel resolution + GeometryCoordinates integerPolygon; + for (const auto& point : polygon) { + integerPolygon.push_back(convertPoint<int16_t>(point)); + } + int16_t minX1 = bbox.min.x; + int16_t maxY1 = bbox.max.y; + int16_t minY1 = bbox.min.y; + int16_t maxX1 = bbox.max.x; + + auto bboxPoints = GeometryCoordinates { + { minX1, minY1 }, { maxX1, minY1 }, { maxX1, maxY1 }, { minX1, maxY1 } + }; + + return util::polygonIntersectsPolygon(integerPolygon, bboxPoints); +} + +std::vector<IndexedSubfeature> CollisionIndex::queryRenderedSymbols(const GeometryCoordinates& queryGeometry, const UnwrappedTileID& tileID, const std::string& sourceID) const { + std::vector<IndexedSubfeature> result; + if (queryGeometry.empty() || (collisionGrid.empty() && ignoredGrid.empty())) { + return result; + } + + mat4 posMatrix; + mat4 projMatrix; + transformState.getProjMatrix(projMatrix); + transformState.matrixFor(posMatrix, tileID); + matrix::multiply(posMatrix, projMatrix, posMatrix); + + // queryGeometry is specified in integer tile units, but in projecting we switch to float pixels + LineString<float> projectedQuery; + for (const auto& point : queryGeometry) { + auto projected = projectPoint(posMatrix, convertPoint<float>(point)); + projectedQuery.push_back(projected); + } + + auto envelope = mapbox::geometry::envelope(projectedQuery); + + using QueryResult = std::pair<IndexedSubfeature, GridIndex<IndexedSubfeature>::BBox>; + + std::vector<QueryResult> thisTileFeatures; + std::vector<QueryResult> features = collisionGrid.queryWithBoxes(envelope); + + for (auto& queryResult : features) { + auto& feature = queryResult.first; + if (feature.sourceID == sourceID && feature.tileID == tileID.canonical) { + // We only have to filter on the canonical ID because even if the feature is showing multiple times + // we treat it as one feature. + thisTileFeatures.push_back(queryResult); + } + } + + std::vector<QueryResult> ignoredFeatures = ignoredGrid.queryWithBoxes(envelope); + for (auto& queryResult : ignoredFeatures) { + auto& feature = queryResult.first; + if (feature.sourceID == sourceID && feature.tileID == tileID.canonical) { + thisTileFeatures.push_back(queryResult); + } + } + + std::unordered_map<std::string, std::unordered_map<std::string, std::unordered_set<std::size_t>>> sourceLayerFeatures; + for (auto& queryResult : thisTileFeatures) { + auto& feature = queryResult.first; + auto& bbox = queryResult.second; + + // Skip already seen features. + auto& seenFeatures = sourceLayerFeatures[feature.sourceLayerName][feature.bucketName]; + if (seenFeatures.find(feature.index) != seenFeatures.end()) + continue; + + seenFeatures.insert(feature.index); + + if (!polygonIntersectsBox(projectedQuery, bbox)) { + continue; + } + + result.push_back(feature); + } + + return result; + +} + +std::pair<float,float> CollisionIndex::projectAnchor(const mat4& posMatrix, const Point<float>& point) const { + vec4 p = {{ point.x, point.y, 0, 1 }}; + matrix::transformMat4(p, p, posMatrix); + return std::make_pair( + 0.5 + 0.5 * (transformState.getCameraToCenterDistance() / p[3]), + p[3] + ); +} + +std::pair<Point<float>,float> CollisionIndex::projectAndGetPerspectiveRatio(const mat4& posMatrix, const Point<float>& point) const { + vec4 p = {{ point.x, point.y, 0, 1 }}; + matrix::transformMat4(p, p, posMatrix); + return std::make_pair( + Point<float>( + (((p[0] / p[3] + 1) / 2) * transformState.getSize().width) + viewportPadding, + (((-p[1] / p[3] + 1) / 2) * transformState.getSize().height) + viewportPadding + ), + // See perspective ratio comment in symbol_sdf.vertex + // We're doing collision detection in viewport space so we need + // to scale down boxes in the distance + 0.5 + 0.5 * (transformState.getCameraToCenterDistance() / p[3]) + ); +} + +Point<float> CollisionIndex::projectPoint(const mat4& posMatrix, const Point<float>& point) const { + vec4 p = {{ point.x, point.y, 0, 1 }}; + matrix::transformMat4(p, p, posMatrix); + return Point<float>( + (((p[0] / p[3] + 1) / 2) * transformState.getSize().width) + viewportPadding, + (((-p[1] / p[3] + 1) / 2) * transformState.getSize().height) + viewportPadding + ); +} + +} // namespace mbgl diff --git a/src/mbgl/text/collision_index.hpp b/src/mbgl/text/collision_index.hpp new file mode 100644 index 0000000000..8653c1d76c --- /dev/null +++ b/src/mbgl/text/collision_index.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include <mbgl/geometry/feature_index.hpp> +#include <mbgl/text/collision_feature.hpp> +#include <mbgl/util/grid_index.hpp> +#include <mbgl/map/transform_state.hpp> + +namespace mbgl { + +class PlacedSymbol; + +struct TileDistance; + +class CollisionIndex { +public: + using CollisionGrid = GridIndex<IndexedSubfeature>; + + explicit CollisionIndex(const TransformState&); + + std::pair<bool,bool> placeFeature(CollisionFeature& feature, + const mat4& posMatrix, + const mat4& labelPlaneMatrix, + const float textPixelRatio, + PlacedSymbol& symbol, + const float scale, + const float fontSize, + const bool allowOverlap, + const bool pitchWithMap, + const bool collisionDebug); + + void insertFeature(CollisionFeature& feature, bool ignorePlacement); + + std::vector<IndexedSubfeature> queryRenderedSymbols(const GeometryCoordinates&, const UnwrappedTileID& tileID, const std::string& sourceID) const; + + +private: + bool isOffscreen(const CollisionBox&) const; + bool isInsideGrid(const CollisionBox&) const; + + std::pair<bool,bool> placeLineFeature(CollisionFeature& feature, + const mat4& posMatrix, + const mat4& labelPlaneMatrix, + const float textPixelRatio, + PlacedSymbol& symbol, + const float scale, + const float fontSize, + const bool allowOverlap, + const bool pitchWithMap, + const bool collisionDebug); + + float approximateTileDistance(const TileDistance& tileDistance, const float lastSegmentAngle, const float pixelsToTileUnits, const float cameraToAnchorDistance, const bool pitchWithMap); + + std::pair<float,float> projectAnchor(const mat4& posMatrix, const Point<float>& point) const; + std::pair<Point<float>,float> projectAndGetPerspectiveRatio(const mat4& posMatrix, const Point<float>& point) const; + Point<float> projectPoint(const mat4& posMatrix, const Point<float>& point) const; + + const TransformState transformState; + + CollisionGrid collisionGrid; + CollisionGrid ignoredGrid; + + const float screenRightBoundary; + const float screenBottomBoundary; + const float gridRightBoundary; + const float gridBottomBoundary; + + const float pitchFactor; +}; + +} // namespace mbgl diff --git a/src/mbgl/text/collision_tile.cpp b/src/mbgl/text/collision_tile.cpp deleted file mode 100644 index cc9b602f08..0000000000 --- a/src/mbgl/text/collision_tile.cpp +++ /dev/null @@ -1,267 +0,0 @@ -#include <mbgl/text/collision_tile.hpp> -#include <mbgl/geometry/feature_index.hpp> -#include <mbgl/math/log2.hpp> -#include <mbgl/util/constants.hpp> -#include <mbgl/util/math.hpp> -#include <mbgl/math/minmax.hpp> -#include <mbgl/util/intersection_tests.hpp> - -#include <mapbox/geometry/envelope.hpp> -#include <mapbox/geometry/multi_point.hpp> - -#include <cmath> - -namespace mbgl { - -CollisionTile::CollisionTile(PlacementConfig config_) : config(std::move(config_)) { - // Compute the transformation matrix. - const float angle_sin = std::sin(config.angle); - const float angle_cos = std::cos(config.angle); - rotationMatrix = { { angle_cos, -angle_sin, angle_sin, angle_cos } }; - reverseRotationMatrix = { { angle_cos, angle_sin, -angle_sin, angle_cos } }; - - perspectiveRatio = - 1.0f + - 0.5f * (util::division(config.cameraToTileDistance, config.cameraToCenterDistance, 1.0f) - - 1.0f); - - minScale /= perspectiveRatio; - maxScale /= perspectiveRatio; - - // We can only approximate here based on the y position of the tile - // The shaders calculate a more accurate "incidence_stretch" - // at render time to calculate an effective scale for collision - // purposes, but we still want to use the yStretch approximation - // here because we can't adjust the aspect ratio of the collision - // boxes at render time. - yStretch = util::max( - 1.0f, util::division(config.cameraToTileDistance, - config.cameraToCenterDistance * std::cos(config.pitch), 1.0f)); -} - -float CollisionTile::findPlacementScale(const Point<float>& anchor, const CollisionBox& box, const float boxMaxScale, const Point<float>& blockingAnchor, const CollisionBox& blocking) { - float minPlacementScale = minScale; - - // Find the lowest scale at which the two boxes can fit side by side without overlapping. - // Original algorithm: - - const float s1 = util::division(blocking.x1 - box.x2, anchor.x - blockingAnchor.x, - 1.0f); // scale at which new box is to the left of old box - const float s2 = util::division(blocking.x2 - box.x1, anchor.x - blockingAnchor.x, - 1.0f); // scale at which new box is to the right of old box - const float s3 = util::division((blocking.y1 - box.y2) * yStretch, anchor.y - blockingAnchor.y, - 1.0f); // scale at which new box is to the top of old box - const float s4 = util::division((blocking.y2 - box.y1) * yStretch, anchor.y - blockingAnchor.y, - 1.0f); // scale at which new box is to the bottom of old box - - float collisionFreeScale = util::min(util::max(s1, s2), util::max(s3, s4)); - - if (collisionFreeScale > blocking.maxScale) { - // After a box's maxScale the label has shrunk enough that the box is no longer needed to cover it, - // so unblock the new box at the scale that the old box disappears. - collisionFreeScale = blocking.maxScale; - } - - if (collisionFreeScale > boxMaxScale) { - // If the box can only be shown after it is visible, then the box can never be shown. - // But the label can be shown after this box is not visible. - collisionFreeScale = boxMaxScale; - } - - if (collisionFreeScale > minPlacementScale && - collisionFreeScale >= blocking.placementScale) { - // If this collision occurs at a lower scale than previously found collisions - // and the collision occurs while the other label is visible - - // this this is the lowest scale at which the label won't collide with anything - minPlacementScale = collisionFreeScale; - } - - return minPlacementScale; -} - -float CollisionTile::placeFeature(const CollisionFeature& feature, bool allowOverlap, bool avoidEdges) { - static const float infinity = std::numeric_limits<float>::infinity(); - static const std::array<CollisionBox, 4> edges {{ - // left - CollisionBox(Point<float>(0, 0), { 0, 0 }, 0, -infinity, 0, infinity, infinity), - // right - CollisionBox(Point<float>(util::EXTENT, 0), { 0, 0 }, 0, -infinity, 0, infinity, infinity), - // top - CollisionBox(Point<float>(0, 0), { 0, 0 }, -infinity, 0, infinity, 0, infinity), - // bottom - CollisionBox(Point<float>(0, util::EXTENT), { 0, 0 }, -infinity, 0, infinity, 0, infinity) - }}; - - float minPlacementScale = minScale; - - for (auto& box : feature.boxes) { - const auto anchor = util::matrixMultiply(rotationMatrix, box.anchor); - - const float boxMaxScale = box.adjustedMaxScale(rotationMatrix, yStretch); - - if (!allowOverlap) { - for (auto it = tree.qbegin(bgi::intersects(getTreeBox(anchor, box))); it != tree.qend(); ++it) { - const CollisionBox& blocking = std::get<1>(*it); - Point<float> blockingAnchor = util::matrixMultiply(rotationMatrix, blocking.anchor); - - minPlacementScale = util::max(minPlacementScale, findPlacementScale(anchor, box, boxMaxScale, blockingAnchor, blocking)); - if (minPlacementScale >= maxScale) return minPlacementScale; - } - } - - if (avoidEdges) { - const Point<float> rtl = util::matrixMultiply(reverseRotationMatrix, { box.x1, box.y1 }); - const Point<float> rtr = util::matrixMultiply(reverseRotationMatrix, { box.x2, box.y1 }); - const Point<float> rbl = util::matrixMultiply(reverseRotationMatrix, { box.x1, box.y2 }); - const Point<float> rbr = util::matrixMultiply(reverseRotationMatrix, { box.x2, box.y2 }); - CollisionBox rotatedBox(box.anchor, - box.offset, - util::min(rtl.x, rtr.x, rbl.x, rbr.x), - util::min(rtl.y, rtr.y, rbl.y, rbr.y), - util::max(rtl.x, rtr.x, rbl.x, rbr.x), - util::max(rtl.y, rtr.y, rbl.y, rbr.y), - boxMaxScale); - - for (auto& blocking : edges) { - minPlacementScale = util::max(minPlacementScale, findPlacementScale(box.anchor, rotatedBox, boxMaxScale, blocking.anchor, blocking)); - if (minPlacementScale >= maxScale) return minPlacementScale; - } - } - } - - return minPlacementScale; -} - -void CollisionTile::insertFeature(CollisionFeature& feature, float minPlacementScale, bool ignorePlacement) { - for (auto& box : feature.boxes) { - box.placementScale = minPlacementScale; - } - - if (minPlacementScale < maxScale) { - std::vector<CollisionTreeBox> treeBoxes; - for (auto& box : feature.boxes) { - CollisionBox adjustedBox = box; - box.maxScale = box.adjustedMaxScale(rotationMatrix, yStretch); - treeBoxes.emplace_back(getTreeBox(util::matrixMultiply(rotationMatrix, box.anchor), box), std::move(adjustedBox), feature.indexedFeature); - } - if (ignorePlacement) { - ignoredTree.insert(treeBoxes.begin(), treeBoxes.end()); - } else { - tree.insert(treeBoxes.begin(), treeBoxes.end()); - } - } - -} - -// +---------------------------+ As you zoom, the size of the symbol changes -// |(x1,y1) | | relative to the tile e.g. when zooming in, -// | | | the symbol gets smaller relative to the tile. -// | (x1',y1') v | -// | +-------+-------+ | The boxes inserted into the tree represents -// | | | | | the bounds at the integer zoom level (where -// | | | | | the symbol is biggest relative to the tile). -// | | | | | -// |---->+-------+-------+<----| This happens because placement is updated -// | | |(xa,ya)| | once every new integer zoom level e.g. -// | | | | | std::floor(oldZoom) != std::floor(newZoom). -// | | | | | -// | +-------+-------+ | Thus, they don't represent the exact bounds -// | ^ (x2',y2') | of the symbol at the current zoom level. For -// | | | calculating the bounds at current zoom level -// | | (x2,y2)| we must unscale the box using its center as -// +---------------------------+ transform origin. -Box CollisionTile::getTreeBox(const Point<float>& anchor, const CollisionBox& box, const float scale) { - assert(box.x1 <= box.x2 && box.y1 <= box.y2); - return Box{ - // When the 'perspectiveRatio' is high, we're effectively underzooming - // the tile because it's in the distance. - // In order to detect collisions that only happen while underzoomed, - // we have to query a larger portion of the grid. - // This extra work is offset by having a lower 'maxScale' bound - // Note that this adjustment ONLY affects the bounding boxes - // in the grid. It doesn't affect the boxes used for the - // minPlacementScale calculations. - CollisionPoint{ - anchor.x + box.x1 / scale * perspectiveRatio, - anchor.y + box.y1 / scale * yStretch * perspectiveRatio, - }, - CollisionPoint{ - anchor.x + box.x2 / scale * perspectiveRatio, - anchor.y + box.y2 / scale * yStretch * perspectiveRatio - } - }; -} - -std::vector<IndexedSubfeature> CollisionTile::queryRenderedSymbols(const GeometryCoordinates& queryGeometry, float scale) const { - std::vector<IndexedSubfeature> result; - if (queryGeometry.empty() || (tree.empty() && ignoredTree.empty())) { - return result; - } - - // Generate a rotated geometry out of the original query geometry. - // Scale has already been handled by the prior conversions. - GeometryCoordinates polygon; - for (const auto& point : queryGeometry) { - auto rotated = util::matrixMultiply(rotationMatrix, convertPoint<float>(point)); - polygon.push_back(convertPoint<int16_t>(rotated)); - } - - // Predicate for ruling out already seen features. - std::unordered_map<std::string, std::unordered_set<std::size_t>> sourceLayerFeatures; - auto seenFeature = [&] (const CollisionTreeBox& treeBox) -> bool { - const IndexedSubfeature& feature = std::get<2>(treeBox); - const auto& seenFeatures = sourceLayerFeatures[feature.sourceLayerName]; - return seenFeatures.find(feature.index) == seenFeatures.end(); - }; - - // "perspectiveRatio" is a tile-based approximation of how much larger symbols will - // be in the distance. It won't line up exactly with the actually rendered symbols - // Being exact would require running the collision detection logic in symbol_sdf.vertex - // in the CPU - const float perspectiveScale = scale / perspectiveRatio; - - // Account for the rounding done when updating symbol shader variables. - const float roundedScale = std::pow(2.0f, std::ceil(util::log2(perspectiveScale) * 10.0f) / 10.0f); - - // Check if feature is rendered (collision free) at current scale. - auto visibleAtScale = [&] (const CollisionTreeBox& treeBox) -> bool { - const CollisionBox& box = std::get<1>(treeBox); - return roundedScale >= box.placementScale && roundedScale <= box.adjustedMaxScale(rotationMatrix, yStretch); - }; - - // Check if query polygon intersects with the feature box at current scale. - auto intersectsAtScale = [&] (const CollisionTreeBox& treeBox) -> bool { - const CollisionBox& collisionBox = std::get<1>(treeBox); - const auto anchor = util::matrixMultiply(rotationMatrix, collisionBox.anchor); - - const int16_t x1 = anchor.x + (collisionBox.x1 / perspectiveScale); - const int16_t y1 = anchor.y + (collisionBox.y1 / perspectiveScale) * yStretch; - const int16_t x2 = anchor.x + (collisionBox.x2 / perspectiveScale); - const int16_t y2 = anchor.y + (collisionBox.y2 / perspectiveScale) * yStretch; - auto bbox = GeometryCoordinates { - { x1, y1 }, { x2, y1 }, { x2, y2 }, { x1, y2 } - }; - return util::polygonIntersectsPolygon(polygon, bbox); - }; - - auto predicates = bgi::satisfies(seenFeature) - && bgi::satisfies(visibleAtScale) - && bgi::satisfies(intersectsAtScale); - - auto queryTree = [&](const auto& tree_) { - for (auto it = tree_.qbegin(predicates); it != tree_.qend(); ++it) { - const IndexedSubfeature& feature = std::get<2>(*it); - auto& seenFeatures = sourceLayerFeatures[feature.sourceLayerName]; - seenFeatures.insert(feature.index); - result.push_back(feature); - } - }; - - queryTree(tree); - queryTree(ignoredTree); - - return result; -} - -} // namespace mbgl diff --git a/src/mbgl/text/collision_tile.hpp b/src/mbgl/text/collision_tile.hpp deleted file mode 100644 index 9868266aa2..0000000000 --- a/src/mbgl/text/collision_tile.hpp +++ /dev/null @@ -1,71 +0,0 @@ -#pragma once - -#include <mbgl/text/collision_feature.hpp> -#include <mbgl/text/placement_config.hpp> -#include <mbgl/tile/geometry_tile_data.hpp> - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-function" -#pragma GCC diagnostic ignored "-Wunused-parameter" -#pragma GCC diagnostic ignored "-Wunused-variable" -#pragma GCC diagnostic ignored "-Wshadow" -#ifdef __clang__ -#pragma GCC diagnostic ignored "-Wunknown-pragmas" -#endif -#pragma GCC diagnostic ignored "-Wpragmas" -#pragma GCC diagnostic ignored "-Wdeprecated-register" -#pragma GCC diagnostic ignored "-Wshorten-64-to-32" -#pragma GCC diagnostic ignored "-Wunused-local-typedefs" -#ifndef __clang__ -#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" -#pragma GCC diagnostic ignored "-Wmisleading-indentation" -#endif -#include <boost/geometry.hpp> -#include <boost/geometry/geometries/point.hpp> -#include <boost/geometry/geometries/box.hpp> -#include <boost/geometry/index/rtree.hpp> -#pragma GCC diagnostic pop - -namespace mbgl { - -namespace bg = boost::geometry; -namespace bgm = bg::model; -namespace bgi = bg::index; -using CollisionPoint = bgm::point<float, 2, bg::cs::cartesian>; -using Box = bgm::box<CollisionPoint>; -using CollisionTreeBox = std::tuple<Box, CollisionBox, IndexedSubfeature>; -using Tree = bgi::rtree<CollisionTreeBox, bgi::linear<16, 4>>; - -class IndexedSubfeature; - -class CollisionTile { -public: - explicit CollisionTile(PlacementConfig); - - float placeFeature(const CollisionFeature&, bool allowOverlap, bool avoidEdges); - void insertFeature(CollisionFeature&, float minPlacementScale, bool ignorePlacement); - - std::vector<IndexedSubfeature> queryRenderedSymbols(const GeometryCoordinates&, float scale) const; - - const PlacementConfig config; - - float minScale = 0.5f; - float maxScale = 2.0f; - float yStretch; - - std::array<float, 4> rotationMatrix; - std::array<float, 4> reverseRotationMatrix; - -private: - float findPlacementScale( - const Point<float>& anchor, const CollisionBox& box, const float boxMaxScale, - const Point<float>& blockingAnchor, const CollisionBox& blocking); - Box getTreeBox(const Point<float>& anchor, const CollisionBox& box, const float scale = 1.0); - - Tree tree; - Tree ignoredTree; - - float perspectiveRatio; -}; - -} // namespace mbgl diff --git a/src/mbgl/text/cross_tile_symbol_index.cpp b/src/mbgl/text/cross_tile_symbol_index.cpp new file mode 100644 index 0000000000..f88bab9d6f --- /dev/null +++ b/src/mbgl/text/cross_tile_symbol_index.cpp @@ -0,0 +1,189 @@ +#include <mbgl/text/cross_tile_symbol_index.hpp> +#include <mbgl/layout/symbol_instance.hpp> +#include <mbgl/renderer/buckets/symbol_bucket.hpp> +#include <mbgl/renderer/render_tile.hpp> +#include <mbgl/tile/tile.hpp> + +namespace mbgl { + + +TileLayerIndex::TileLayerIndex(OverscaledTileID coord_, std::vector<SymbolInstance>& symbolInstances, uint32_t bucketInstanceId_) + : coord(coord_), bucketInstanceId(bucketInstanceId_) { + for (SymbolInstance& symbolInstance : symbolInstances) { + indexedSymbolInstances[symbolInstance.key].emplace_back(symbolInstance.crossTileID, getScaledCoordinates(symbolInstance, coord)); + } + } + +Point<int64_t> TileLayerIndex::getScaledCoordinates(SymbolInstance& symbolInstance, const OverscaledTileID& childTileCoord) { + // Round anchor positions to roughly 4 pixel grid + const double roundingFactor = 512.0 / util::EXTENT / 2.0; + const double scale = roundingFactor / std::pow(2, childTileCoord.canonical.z - coord.canonical.z); + return { + static_cast<int64_t>(std::floor((childTileCoord.canonical.x * util::EXTENT + symbolInstance.anchor.point.x) * scale)), + static_cast<int64_t>(std::floor((childTileCoord.canonical.y * util::EXTENT + symbolInstance.anchor.point.y) * scale)) + }; +} + +void TileLayerIndex::findMatches(std::vector<SymbolInstance>& symbolInstances, const OverscaledTileID& newCoord, std::set<uint32_t>& zoomCrossTileIDs) { + float tolerance = coord.canonical.z < newCoord.canonical.z ? 1 : std::pow(2, coord.canonical.z - newCoord.canonical.z); + + for (auto& symbolInstance : symbolInstances) { + if (symbolInstance.crossTileID) { + // already has a match, skip + continue; + } + + auto it = indexedSymbolInstances.find(symbolInstance.key); + if (it == indexedSymbolInstances.end()) { + // No symbol with this key in this bucket + continue; + } + + auto scaledSymbolCoord = getScaledCoordinates(symbolInstance, newCoord); + + for (IndexedSymbolInstance& thisTileSymbol: it->second) { + // Return any symbol with the same keys whose coordinates are within 1 + // grid unit. (with a 4px grid, this covers a 12px by 12px area) + if (std::abs(thisTileSymbol.coord.x - scaledSymbolCoord.x) <= tolerance && + std::abs(thisTileSymbol.coord.y - scaledSymbolCoord.y) <= tolerance && + zoomCrossTileIDs.find(thisTileSymbol.crossTileID) == zoomCrossTileIDs.end()) { + // Once we've marked ourselves duplicate against this parent symbol, + // don't let any other symbols at the same zoom level duplicate against + // the same parent (see issue #10844) + zoomCrossTileIDs.insert(thisTileSymbol.crossTileID); + symbolInstance.crossTileID = thisTileSymbol.crossTileID; + break; + } + } + } +} + +CrossTileSymbolLayerIndex::CrossTileSymbolLayerIndex() { +} + +bool CrossTileSymbolLayerIndex::addBucket(const OverscaledTileID& tileID, SymbolBucket& bucket, uint32_t& maxCrossTileID) { + auto thisZoomIndexes = indexes[tileID.overscaledZ]; + auto previousIndex = thisZoomIndexes.find(tileID); + if (previousIndex != thisZoomIndexes.end()) { + if (previousIndex->second.bucketInstanceId == bucket.bucketInstanceId) { + return false; + } else { + // We're replacing this bucket with an updated version + // Remove the old bucket's "used crossTileIDs" now so that the new bucket can claim them. + // We have to keep the old index entries themselves until the end of 'addBucket' so + // that we can copy them with 'findMatches'. + removeBucketCrossTileIDs(tileID.overscaledZ, previousIndex->second); + } + } + + for (auto& symbolInstance: bucket.symbolInstances) { + symbolInstance.crossTileID = 0; + } + + for (auto& it : indexes) { + auto zoom = it.first; + auto zoomIndexes = it.second; + if (zoom > tileID.overscaledZ) { + for (auto& childIndex : zoomIndexes) { + if (childIndex.second.coord.isChildOf(tileID)) { + childIndex.second.findMatches(bucket.symbolInstances, tileID, usedCrossTileIDs[tileID.overscaledZ]); + } + } + } else { + auto parentTileID = tileID.scaledTo(zoom); + auto parentIndex = zoomIndexes.find(parentTileID); + if (parentIndex != zoomIndexes.end()) { + parentIndex->second.findMatches(bucket.symbolInstances, tileID, usedCrossTileIDs[tileID.overscaledZ]); + } + } + } + + for (auto& symbolInstance : bucket.symbolInstances) { + if (!symbolInstance.crossTileID) { + // symbol did not match any known symbol, assign a new id + symbolInstance.crossTileID = ++maxCrossTileID; + usedCrossTileIDs[tileID.overscaledZ].insert(symbolInstance.crossTileID); + } + } + + + indexes[tileID.overscaledZ].erase(tileID); + indexes[tileID.overscaledZ].emplace(tileID, TileLayerIndex(tileID, bucket.symbolInstances, bucket.bucketInstanceId)); + return true; +} + +void CrossTileSymbolLayerIndex::removeBucketCrossTileIDs(uint8_t zoom, const TileLayerIndex& removedBucket) { + for (auto key : removedBucket.indexedSymbolInstances) { + for (auto indexedSymbolInstance : key.second) { + usedCrossTileIDs[zoom].erase(indexedSymbolInstance.crossTileID); + } + } +} + +bool CrossTileSymbolLayerIndex::removeStaleBuckets(const std::unordered_set<uint32_t>& currentIDs) { + bool tilesChanged = false; + for (auto& zoomIndexes : indexes) { + for (auto it = zoomIndexes.second.begin(); it != zoomIndexes.second.end();) { + if (!currentIDs.count(it->second.bucketInstanceId)) { + removeBucketCrossTileIDs(zoomIndexes.first, it->second); + it = zoomIndexes.second.erase(it); + tilesChanged = true; + } else { + ++it; + } + } + } + return tilesChanged; +} + +CrossTileSymbolIndex::CrossTileSymbolIndex() {} + +bool CrossTileSymbolIndex::addLayer(RenderSymbolLayer& symbolLayer) { + + auto& layerIndex = layerIndexes[symbolLayer.getID()]; + + bool symbolBucketsChanged = false; + std::unordered_set<uint32_t> currentBucketIDs; + + for (RenderTile& renderTile : symbolLayer.renderTiles) { + if (!renderTile.tile.isRenderable()) { + continue; + } + + auto bucket = renderTile.tile.getBucket(*symbolLayer.baseImpl); + assert(dynamic_cast<SymbolBucket*>(bucket)); + SymbolBucket& symbolBucket = *reinterpret_cast<SymbolBucket*>(bucket); + + if (!symbolBucket.bucketInstanceId) { + symbolBucket.bucketInstanceId = ++maxBucketInstanceId; + } + + const bool bucketAdded = layerIndex.addBucket(renderTile.tile.id, symbolBucket, maxCrossTileID); + symbolBucketsChanged = symbolBucketsChanged || bucketAdded; + currentBucketIDs.insert(symbolBucket.bucketInstanceId); + } + + if (layerIndex.removeStaleBuckets(currentBucketIDs)) { + symbolBucketsChanged = true; + } + return symbolBucketsChanged; +} + +void CrossTileSymbolIndex::pruneUnusedLayers(const std::set<std::string>& usedLayers) { + std::vector<std::string> unusedLayers; + for (auto layerIndex : layerIndexes) { + if (usedLayers.find(layerIndex.first) == usedLayers.end()) { + unusedLayers.push_back(layerIndex.first); + } + } + for (auto unusedLayer : unusedLayers) { + layerIndexes.erase(unusedLayer); + } +} + +void CrossTileSymbolIndex::reset() { + layerIndexes.clear(); +} + +} // namespace mbgl + diff --git a/src/mbgl/text/cross_tile_symbol_index.hpp b/src/mbgl/text/cross_tile_symbol_index.hpp new file mode 100644 index 0000000000..541c2e3661 --- /dev/null +++ b/src/mbgl/text/cross_tile_symbol_index.hpp @@ -0,0 +1,69 @@ +#pragma once + +#include <mbgl/tile/tile_id.hpp> +#include <mbgl/util/geometry.hpp> +#include <mbgl/util/constants.hpp> +#include <mbgl/util/optional.hpp> + +#include <map> +#include <set> +#include <vector> +#include <string> +#include <memory> +#include <unordered_set> + +namespace mbgl { + +class SymbolInstance; +class RenderSymbolLayer; +class SymbolBucket; + +class IndexedSymbolInstance { +public: + IndexedSymbolInstance(uint32_t crossTileID_, Point<int64_t> coord_) + : crossTileID(crossTileID_), coord(coord_) + {} + + uint32_t crossTileID; + Point<int64_t> coord; +}; + +class TileLayerIndex { +public: + TileLayerIndex(OverscaledTileID coord, std::vector<SymbolInstance>&, uint32_t bucketInstanceId); + + Point<int64_t> getScaledCoordinates(SymbolInstance&, const OverscaledTileID&); + void findMatches(std::vector<SymbolInstance>&, const OverscaledTileID&, std::set<uint32_t>&); + + OverscaledTileID coord; + uint32_t bucketInstanceId; + std::map<std::u16string,std::vector<IndexedSymbolInstance>> indexedSymbolInstances; +}; + +class CrossTileSymbolLayerIndex { +public: + CrossTileSymbolLayerIndex(); + bool addBucket(const OverscaledTileID&, SymbolBucket&, uint32_t& maxCrossTileID); + bool removeStaleBuckets(const std::unordered_set<uint32_t>& currentIDs); +private: + void removeBucketCrossTileIDs(uint8_t zoom, const TileLayerIndex& removedBucket); + + std::map<uint8_t, std::map<OverscaledTileID,TileLayerIndex>> indexes; + std::map<uint8_t, std::set<uint32_t>> usedCrossTileIDs; +}; + +class CrossTileSymbolIndex { +public: + CrossTileSymbolIndex(); + + bool addLayer(RenderSymbolLayer&); + void pruneUnusedLayers(const std::set<std::string>&); + + void reset(); +private: + std::map<std::string, CrossTileSymbolLayerIndex> layerIndexes; + uint32_t maxCrossTileID = 0; + uint32_t maxBucketInstanceId = 0; +}; + +} // namespace mbgl diff --git a/src/mbgl/text/glyph.hpp b/src/mbgl/text/glyph.hpp index 6cccb72ebe..2c03da308a 100644 --- a/src/mbgl/text/glyph.hpp +++ b/src/mbgl/text/glyph.hpp @@ -13,6 +13,7 @@ #include <vector> #include <string> #include <map> +#include <set> namespace mbgl { @@ -75,10 +76,10 @@ class Shaping { explicit Shaping(float x, float y, WritingModeType writingMode_) : top(y), bottom(y), left(x), right(x), writingMode(writingMode_) {} std::vector<PositionedGlyph> positionedGlyphs; - int32_t top = 0; - int32_t bottom = 0; - int32_t left = 0; - int32_t right = 0; + float top = 0; + float bottom = 0; + float left = 0; + float right = 0; WritingModeType writingMode; explicit operator bool() const { return !positionedGlyphs.empty(); } diff --git a/src/mbgl/text/glyph_manager.cpp b/src/mbgl/text/glyph_manager.cpp index 59b019b547..3130418908 100644 --- a/src/mbgl/text/glyph_manager.cpp +++ b/src/mbgl/text/glyph_manager.cpp @@ -44,8 +44,9 @@ void GlyphManager::getGlyphs(GlyphRequestor& requestor, GlyphDependencies glyphD for (const auto& range : ranges) { auto it = entry.ranges.find(range); if (it == entry.ranges.end() || !it->second.parsed) { - GlyphRequest& request = requestRange(entry, fontStack, range); + GlyphRequest& request = entry.ranges[range]; request.requestors[&requestor] = dependencies; + requestRange(request, fontStack, range); } } } @@ -63,17 +64,14 @@ Glyph GlyphManager::generateLocalSDF(const FontStack& fontStack, GlyphID glyphID return local; } -GlyphManager::GlyphRequest& GlyphManager::requestRange(Entry& entry, const FontStack& fontStack, const GlyphRange& range) { - GlyphRequest& request = entry.ranges[range]; +void GlyphManager::requestRange(GlyphRequest& request, const FontStack& fontStack, const GlyphRange& range) { if (request.req) { - return request; + return; } request.req = fileSource.request(Resource::glyphs(glyphURL, fontStack, range), [this, fontStack, range](Response res) { processResponse(res, fontStack, range); }); - - return request; } void GlyphManager::processResponse(const Response& res, const FontStack& fontStack, const GlyphRange& range) { diff --git a/src/mbgl/text/glyph_manager.hpp b/src/mbgl/text/glyph_manager.hpp index ccc8d7e16e..84db2c4be5 100644 --- a/src/mbgl/text/glyph_manager.hpp +++ b/src/mbgl/text/glyph_manager.hpp @@ -61,7 +61,7 @@ private: std::unordered_map<FontStack, Entry, FontStackHash> entries; - GlyphRequest& requestRange(Entry&, const FontStack&, const GlyphRange&); + void requestRange(GlyphRequest&, const FontStack&, const GlyphRange&); void processResponse(const Response&, const FontStack&, const GlyphRange&); void notify(GlyphRequestor&, const GlyphDependencies&); diff --git a/src/mbgl/text/placement.cpp b/src/mbgl/text/placement.cpp new file mode 100644 index 0000000000..400fe79ac5 --- /dev/null +++ b/src/mbgl/text/placement.cpp @@ -0,0 +1,336 @@ +#include <mbgl/text/placement.hpp> +#include <mbgl/renderer/render_layer.hpp> +#include <mbgl/renderer/layers/render_symbol_layer.hpp> +#include <mbgl/renderer/render_tile.hpp> +#include <mbgl/tile/geometry_tile.hpp> +#include <mbgl/renderer/buckets/symbol_bucket.hpp> +#include <mbgl/renderer/bucket.hpp> + +namespace mbgl { + +OpacityState::OpacityState(bool placed_, bool skipFade) + : opacity((skipFade && placed_) ? 1 : 0) + , placed(placed_) +{ +} + +OpacityState::OpacityState(const OpacityState& prevState, float increment, bool placed_) : + opacity(::fmax(0, ::fmin(1, prevState.opacity + (prevState.placed ? increment : -increment)))), + placed(placed_) {} + +bool OpacityState::isHidden() const { + return opacity == 0 && !placed; +} + +JointOpacityState::JointOpacityState(bool placedText, bool placedIcon, bool skipFade) : + icon(OpacityState(placedIcon, skipFade)), + text(OpacityState(placedText, skipFade)) {} + +JointOpacityState::JointOpacityState(const JointOpacityState& prevOpacityState, float increment, bool placedText, bool placedIcon) : + icon(OpacityState(prevOpacityState.icon, increment, placedIcon)), + text(OpacityState(prevOpacityState.text, increment, placedText)) {} + +bool JointOpacityState::isHidden() const { + return icon.isHidden() && text.isHidden(); +} + +Placement::Placement(const TransformState& state_, MapMode mapMode_) + : collisionIndex(state_) + , state(state_) + , mapMode(mapMode_) + , recentUntil(TimePoint::min()) +{} + +void Placement::placeLayer(RenderSymbolLayer& symbolLayer, const mat4& projMatrix, bool showCollisionBoxes) { + + std::unordered_set<uint32_t> seenCrossTileIDs; + + for (RenderTile& renderTile : symbolLayer.renderTiles) { + if (!renderTile.tile.isRenderable()) { + continue; + } + + auto bucket = renderTile.tile.getBucket(*symbolLayer.baseImpl); + assert(dynamic_cast<SymbolBucket*>(bucket)); + SymbolBucket& symbolBucket = *reinterpret_cast<SymbolBucket*>(bucket); + + auto& layout = symbolBucket.layout; + + const float pixelsToTileUnits = renderTile.id.pixelsToTileUnits(1, state.getZoom()); + + const float scale = std::pow(2, state.getZoom() - renderTile.tile.id.overscaledZ); + const float textPixelRatio = (util::tileSize * renderTile.tile.id.overscaleFactor()) / util::EXTENT; + + mat4 posMatrix; + state.matrixFor(posMatrix, renderTile.id); + matrix::multiply(posMatrix, projMatrix, posMatrix); + + mat4 textLabelPlaneMatrix = getLabelPlaneMatrix(posMatrix, + layout.get<style::TextPitchAlignment>() == style::AlignmentType::Map, + layout.get<style::TextRotationAlignment>() == style::AlignmentType::Map, + state, + pixelsToTileUnits); + + mat4 iconLabelPlaneMatrix = getLabelPlaneMatrix(posMatrix, + layout.get<style::IconPitchAlignment>() == style::AlignmentType::Map, + layout.get<style::IconRotationAlignment>() == style::AlignmentType::Map, + state, + pixelsToTileUnits); + + placeLayerBucket(symbolBucket, posMatrix, textLabelPlaneMatrix, iconLabelPlaneMatrix, scale, textPixelRatio, showCollisionBoxes, seenCrossTileIDs, renderTile.tile.holdForFade()); + } +} + +void Placement::placeLayerBucket( + SymbolBucket& bucket, + const mat4& posMatrix, + const mat4& textLabelPlaneMatrix, + const mat4& iconLabelPlaneMatrix, + const float scale, + const float textPixelRatio, + const bool showCollisionBoxes, + std::unordered_set<uint32_t>& seenCrossTileIDs, + const bool holdingForFade) { + + auto partiallyEvaluatedTextSize = bucket.textSizeBinder->evaluateForZoom(state.getZoom()); + auto partiallyEvaluatedIconSize = bucket.iconSizeBinder->evaluateForZoom(state.getZoom()); + + const bool iconWithoutText = !bucket.hasTextData() || bucket.layout.get<style::TextOptional>(); + const bool textWithoutIcon = !bucket.hasIconData() || bucket.layout.get<style::IconOptional>(); + + for (auto& symbolInstance : bucket.symbolInstances) { + + if (seenCrossTileIDs.count(symbolInstance.crossTileID) == 0) { + if (holdingForFade) { + // Mark all symbols from this tile as "not placed", but don't add to seenCrossTileIDs, because we don't + // know yet if we have a duplicate in a parent tile that _should_ be placed. + placements.emplace(symbolInstance.crossTileID, JointPlacement(false, false, false)); + continue; + } + + bool placeText = false; + bool placeIcon = false; + bool offscreen = true; + + if (symbolInstance.placedTextIndex) { + PlacedSymbol& placedSymbol = bucket.text.placedSymbols.at(*symbolInstance.placedTextIndex); + const float fontSize = evaluateSizeForFeature(partiallyEvaluatedTextSize, placedSymbol); + + auto placed = collisionIndex.placeFeature(symbolInstance.textCollisionFeature, + posMatrix, textLabelPlaneMatrix, textPixelRatio, + placedSymbol, scale, fontSize, + bucket.layout.get<style::TextAllowOverlap>(), + bucket.layout.get<style::TextPitchAlignment>() == style::AlignmentType::Map, + showCollisionBoxes); + placeText = placed.first; + offscreen &= placed.second; + } + + if (symbolInstance.placedIconIndex) { + PlacedSymbol& placedSymbol = bucket.icon.placedSymbols.at(*symbolInstance.placedIconIndex); + const float fontSize = evaluateSizeForFeature(partiallyEvaluatedIconSize, placedSymbol); + + auto placed = collisionIndex.placeFeature(symbolInstance.iconCollisionFeature, + posMatrix, iconLabelPlaneMatrix, textPixelRatio, + placedSymbol, scale, fontSize, + bucket.layout.get<style::IconAllowOverlap>(), + bucket.layout.get<style::IconPitchAlignment>() == style::AlignmentType::Map, + showCollisionBoxes); + placeIcon = placed.first; + offscreen &= placed.second; + } + + // combine placements for icon and text + if (!iconWithoutText && !textWithoutIcon) { + placeText = placeIcon = placeText && placeIcon; + } else if (!textWithoutIcon) { + placeText = placeText && placeIcon; + } else if (!iconWithoutText) { + placeIcon = placeText && placeIcon; + } + + if (placeText) { + collisionIndex.insertFeature(symbolInstance.textCollisionFeature, bucket.layout.get<style::TextIgnorePlacement>()); + } + + if (placeIcon) { + collisionIndex.insertFeature(symbolInstance.iconCollisionFeature, bucket.layout.get<style::IconIgnorePlacement>()); + } + + assert(symbolInstance.crossTileID != 0); + + if (placements.find(symbolInstance.crossTileID) != placements.end()) { + // If there's a previous placement with this ID, it comes from a tile that's fading out + // Erase it so that the placement result from the non-fading tile supersedes it + placements.erase(symbolInstance.crossTileID); + } + + placements.emplace(symbolInstance.crossTileID, JointPlacement(placeText, placeIcon, offscreen || bucket.justReloaded)); + seenCrossTileIDs.insert(symbolInstance.crossTileID); + } + } + + bucket.justReloaded = false; +} + +bool Placement::commit(const Placement& prevPlacement, TimePoint now) { + commitTime = now; + + bool placementChanged = false; + + float increment = mapMode == MapMode::Continuous ? + std::chrono::duration<float>(commitTime - prevPlacement.commitTime) / Duration(std::chrono::milliseconds(300)) : + 1.0; + + // add the opacities from the current placement, and copy their current values from the previous placement + for (auto& jointPlacement : placements) { + auto prevOpacity = prevPlacement.opacities.find(jointPlacement.first); + if (prevOpacity != prevPlacement.opacities.end()) { + opacities.emplace(jointPlacement.first, JointOpacityState(prevOpacity->second, increment, jointPlacement.second.text, jointPlacement.second.icon)); + placementChanged = placementChanged || + jointPlacement.second.icon != prevOpacity->second.icon.placed || + jointPlacement.second.text != prevOpacity->second.text.placed; + } else { + opacities.emplace(jointPlacement.first, JointOpacityState(jointPlacement.second.text, jointPlacement.second.icon, jointPlacement.second.skipFade)); + placementChanged = placementChanged || jointPlacement.second.icon || jointPlacement.second.text; + } + } + + // copy and update values from the previous placement that aren't in the current placement but haven't finished fading + for (auto& prevOpacity : prevPlacement.opacities) { + if (opacities.find(prevOpacity.first) == opacities.end()) { + JointOpacityState jointOpacity(prevOpacity.second, increment, false, false); + if (!jointOpacity.isHidden()) { + opacities.emplace(prevOpacity.first, jointOpacity); + placementChanged = placementChanged || prevOpacity.second.icon.placed || prevOpacity.second.text.placed; + } + } + } + + return placementChanged; +} + +void Placement::updateLayerOpacities(RenderSymbolLayer& symbolLayer) { + std::set<uint32_t> seenCrossTileIDs; + for (RenderTile& renderTile : symbolLayer.renderTiles) { + if (!renderTile.tile.isRenderable()) { + continue; + } + + auto bucket = renderTile.tile.getBucket(*symbolLayer.baseImpl); + assert(dynamic_cast<SymbolBucket*>(bucket)); + SymbolBucket& symbolBucket = *reinterpret_cast<SymbolBucket*>(bucket); + updateBucketOpacities(symbolBucket, seenCrossTileIDs); + } +} + +void Placement::updateBucketOpacities(SymbolBucket& bucket, std::set<uint32_t>& seenCrossTileIDs) { + if (bucket.hasTextData()) bucket.text.opacityVertices.clear(); + if (bucket.hasIconData()) bucket.icon.opacityVertices.clear(); + if (bucket.hasCollisionBoxData()) bucket.collisionBox.dynamicVertices.clear(); + if (bucket.hasCollisionCircleData()) bucket.collisionCircle.dynamicVertices.clear(); + + JointOpacityState defaultOpacityState( + bucket.layout.get<style::TextAllowOverlap>(), + bucket.layout.get<style::IconAllowOverlap>(), + true); + + for (SymbolInstance& symbolInstance : bucket.symbolInstances) { + bool isDuplicate = seenCrossTileIDs.count(symbolInstance.crossTileID) > 0; + + auto it = opacities.find(symbolInstance.crossTileID); + auto opacityState = it != opacities.end() && !isDuplicate ? + it->second : + defaultOpacityState; + + if (it == opacities.end()) { + opacities.emplace(symbolInstance.crossTileID, defaultOpacityState); + } + + seenCrossTileIDs.insert(symbolInstance.crossTileID); + + if (symbolInstance.hasText) { + auto opacityVertex = SymbolOpacityAttributes::vertex(opacityState.text.placed, opacityState.text.opacity); + for (size_t i = 0; i < symbolInstance.horizontalGlyphQuads.size() * 4; i++) { + bucket.text.opacityVertices.emplace_back(opacityVertex); + } + for (size_t i = 0; i < symbolInstance.verticalGlyphQuads.size() * 4; i++) { + bucket.text.opacityVertices.emplace_back(opacityVertex); + } + if (symbolInstance.placedTextIndex) { + bucket.text.placedSymbols[*symbolInstance.placedTextIndex].hidden = opacityState.isHidden(); + } + if (symbolInstance.placedVerticalTextIndex) { + bucket.text.placedSymbols[*symbolInstance.placedVerticalTextIndex].hidden = opacityState.isHidden(); + } + } + if (symbolInstance.hasIcon) { + auto opacityVertex = SymbolOpacityAttributes::vertex(opacityState.icon.placed, opacityState.icon.opacity); + if (symbolInstance.iconQuad) { + bucket.icon.opacityVertices.emplace_back(opacityVertex); + bucket.icon.opacityVertices.emplace_back(opacityVertex); + bucket.icon.opacityVertices.emplace_back(opacityVertex); + bucket.icon.opacityVertices.emplace_back(opacityVertex); + } + if (symbolInstance.placedIconIndex) { + bucket.icon.placedSymbols[*symbolInstance.placedIconIndex].hidden = opacityState.isHidden(); + } + } + + auto updateCollisionBox = [&](const auto& feature, const bool placed) { + for (const CollisionBox& box : feature.boxes) { + if (feature.alongLine) { + auto dynamicVertex = CollisionBoxDynamicAttributes::vertex(placed, !box.used); + bucket.collisionCircle.dynamicVertices.emplace_back(dynamicVertex); + bucket.collisionCircle.dynamicVertices.emplace_back(dynamicVertex); + bucket.collisionCircle.dynamicVertices.emplace_back(dynamicVertex); + bucket.collisionCircle.dynamicVertices.emplace_back(dynamicVertex); + } else { + auto dynamicVertex = CollisionBoxDynamicAttributes::vertex(placed, false); + bucket.collisionBox.dynamicVertices.emplace_back(dynamicVertex); + bucket.collisionBox.dynamicVertices.emplace_back(dynamicVertex); + bucket.collisionBox.dynamicVertices.emplace_back(dynamicVertex); + bucket.collisionBox.dynamicVertices.emplace_back(dynamicVertex); + } + } + }; + updateCollisionBox(symbolInstance.textCollisionFeature, opacityState.text.placed); + updateCollisionBox(symbolInstance.iconCollisionFeature, opacityState.icon.placed); + } + + bucket.updateOpacity(); + bucket.sortFeatures(state.getAngle()); +} + +float Placement::symbolFadeChange(TimePoint now) const { + if (mapMode == MapMode::Continuous) { + return std::chrono::duration<float>(now - commitTime) / Duration(std::chrono::milliseconds(300)); + } else { + return 1.0; + } +} + +bool Placement::hasTransitions(TimePoint now) const { + return symbolFadeChange(now) < 1.0 || stale; +} + +bool Placement::stillRecent(TimePoint now) const { + return mapMode == MapMode::Continuous && recentUntil > now; +} +void Placement::setRecent(TimePoint now) { + stale = false; + if (mapMode == MapMode::Continuous) { + // Only set in continuous mode because "now" isn't defined in still mode + recentUntil = now + Duration(std::chrono::milliseconds(300)); + } +} + +void Placement::setStale() { + stale = true; +} + +const CollisionIndex& Placement::getCollisionIndex() const { + return collisionIndex; +} + +} // namespace mbgl diff --git a/src/mbgl/text/placement.hpp b/src/mbgl/text/placement.hpp new file mode 100644 index 0000000000..653ae352ed --- /dev/null +++ b/src/mbgl/text/placement.hpp @@ -0,0 +1,90 @@ +#pragma once + +#include <string> +#include <unordered_map> +#include <mbgl/util/chrono.hpp> +#include <mbgl/text/collision_index.hpp> +#include <mbgl/layout/symbol_projection.hpp> +#include <unordered_set> + +namespace mbgl { + +class RenderSymbolLayer; +class SymbolBucket; + +class OpacityState { +public: + OpacityState(bool placed, bool skipFade); + OpacityState(const OpacityState& prevOpacityState, float increment, bool placed); + bool isHidden() const; + float opacity; + bool placed; +}; + +class JointOpacityState { +public: + JointOpacityState(bool placedIcon, bool placedText, bool skipFade); + JointOpacityState(const JointOpacityState& prevOpacityState, float increment, bool placedIcon, bool placedText); + bool isHidden() const; + OpacityState icon; + OpacityState text; +}; + +class JointPlacement { +public: + JointPlacement(bool text_, bool icon_, bool skipFade_) + : text(text_), icon(icon_), skipFade(skipFade_) + {} + + const bool text; + const bool icon; + // skipFade = outside viewport, but within CollisionIndex::viewportPadding px of the edge + // Because these symbols aren't onscreen yet, we can skip the "fade in" animation, + // and if a subsequent viewport change brings them into view, they'll be fully + // visible right away. + const bool skipFade; +}; + +class Placement { +public: + Placement(const TransformState&, MapMode mapMode); + void placeLayer(RenderSymbolLayer&, const mat4&, bool showCollisionBoxes); + bool commit(const Placement& prevPlacement, TimePoint); + void updateLayerOpacities(RenderSymbolLayer&); + float symbolFadeChange(TimePoint now) const; + bool hasTransitions(TimePoint now) const; + + const CollisionIndex& getCollisionIndex() const; + + bool stillRecent(TimePoint now) const; + void setRecent(TimePoint now); + void setStale(); +private: + + void placeLayerBucket( + SymbolBucket&, + const mat4& posMatrix, + const mat4& textLabelPlaneMatrix, + const mat4& iconLabelPlaneMatrix, + const float scale, + const float pixelRatio, + const bool showCollisionBoxes, + std::unordered_set<uint32_t>& seenCrossTileIDs, + const bool holdingForFade); + + void updateBucketOpacities(SymbolBucket&, std::set<uint32_t>&); + + CollisionIndex collisionIndex; + + TransformState state; + MapMode mapMode; + TimePoint commitTime; + + std::unordered_map<uint32_t, JointPlacement> placements; + std::unordered_map<uint32_t, JointOpacityState> opacities; + + TimePoint recentUntil; + bool stale = false; +}; + +} // namespace mbgl diff --git a/src/mbgl/text/placement_config.hpp b/src/mbgl/text/placement_config.hpp deleted file mode 100644 index 48b24b5f41..0000000000 --- a/src/mbgl/text/placement_config.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -#include <mbgl/util/constants.hpp> - -namespace mbgl { - -class PlacementConfig { -public: - PlacementConfig(float angle_ = 0, float pitch_ = 0, float cameraToCenterDistance_ = 0, float cameraToTileDistance_ = 0, bool debug_ = false) - : angle(angle_), pitch(pitch_), cameraToCenterDistance(cameraToCenterDistance_), cameraToTileDistance(cameraToTileDistance_), debug(debug_) { - } - - bool operator==(const PlacementConfig& rhs) const { - return angle == rhs.angle && - pitch == rhs.pitch && - debug == rhs.debug && - ((pitch * util::RAD2DEG < 25) || - (cameraToCenterDistance == rhs.cameraToCenterDistance && cameraToTileDistance == rhs.cameraToTileDistance)); - } - - bool operator!=(const PlacementConfig& rhs) const { - return !operator==(rhs); - } - -public: - float angle; - float pitch; - float cameraToCenterDistance; - float cameraToTileDistance; - bool debug; -}; - -} // namespace mbgl diff --git a/src/mbgl/text/shaping.cpp b/src/mbgl/text/shaping.cpp index 5d688ea539..a8232836b6 100644 --- a/src/mbgl/text/shaping.cpp +++ b/src/mbgl/text/shaping.cpp @@ -313,7 +313,7 @@ void shapeLines(Shaping& shaping, align(shaping, justify, anchorAlign.horizontalAlign, anchorAlign.verticalAlign, maxLineLength, lineHeight, lines.size()); - const uint32_t height = lines.size() * lineHeight; + const float height = lines.size() * lineHeight; // Calculate the bounding box shaping.top += -anchorAlign.verticalAlign * height; diff --git a/src/mbgl/tile/custom_geometry_tile.cpp b/src/mbgl/tile/custom_geometry_tile.cpp new file mode 100644 index 0000000000..33962ad87d --- /dev/null +++ b/src/mbgl/tile/custom_geometry_tile.cpp @@ -0,0 +1,91 @@ +#include <mbgl/tile/custom_geometry_tile.hpp> +#include <mbgl/tile/geojson_tile_data.hpp> +#include <mbgl/renderer/query.hpp> +#include <mbgl/renderer/tile_parameters.hpp> +#include <mbgl/actor/scheduler.hpp> +#include <mbgl/style/filter_evaluator.hpp> +#include <mbgl/util/string.hpp> +#include <mbgl/tile/tile_observer.hpp> +#include <mbgl/style/custom_tile_loader.hpp> + +#include <mapbox/geojsonvt.hpp> + +namespace mbgl { + +CustomGeometryTile::CustomGeometryTile(const OverscaledTileID& overscaledTileID, + std::string sourceID_, + const TileParameters& parameters, + const style::CustomGeometrySource::TileOptions options_, + ActorRef<style::CustomTileLoader> loader_) + : GeometryTile(overscaledTileID, sourceID_, parameters), + necessity(TileNecessity::Optional), + options(options_), + loader(loader_), + mailbox(std::make_shared<Mailbox>(*Scheduler::GetCurrent())), + actorRef(*this, mailbox) { +} + +CustomGeometryTile::~CustomGeometryTile() { + loader.invoke(&style::CustomTileLoader::removeTile, id); +} + +void CustomGeometryTile::setTileData(const GeoJSON& geoJSON) { + + auto featureData = mapbox::geometry::feature_collection<int16_t>(); + if (geoJSON.is<FeatureCollection>() && !geoJSON.get<FeatureCollection>().empty()) { + const double scale = util::EXTENT / options.tileSize; + + mapbox::geojsonvt::TileOptions vtOptions; + vtOptions.extent = util::EXTENT; + vtOptions.buffer = ::round(scale * options.buffer); + vtOptions.tolerance = scale * options.tolerance; + featureData = mapbox::geojsonvt::geoJSONToTile(geoJSON, id.canonical.z, id.canonical.x, id.canonical.y, vtOptions, options.wrap, options.clip).features; + } else { + setNecessity(TileNecessity::Optional); + } + setData(std::make_unique<GeoJSONTileData>(std::move(featureData))); +} + +void CustomGeometryTile::invalidateTileData() { + stale = true; + observer->onTileChanged(*this); +} + +//Fetching tile data for custom sources is assumed to be an expensive operation. +// Only required tiles make fetchTile requests. Attempt to cancel a tile +// that is no longer required. +void CustomGeometryTile::setNecessity(TileNecessity newNecessity) { + if (newNecessity != necessity || stale ) { + necessity = newNecessity; + if (necessity == TileNecessity::Required) { + loader.invoke(&style::CustomTileLoader::fetchTile, id, actorRef); + stale = false; + } else if (!isRenderable()) { + loader.invoke(&style::CustomTileLoader::cancelTile, id); + } + } +} + +void CustomGeometryTile::querySourceFeatures( + std::vector<Feature>& result, + const SourceQueryOptions& queryOptions) { + + // Ignore the sourceLayer, there is only one + auto layer = getData()->getLayer({}); + + if (layer) { + auto featureCount = layer->featureCount(); + for (std::size_t i = 0; i < featureCount; i++) { + auto feature = layer->getFeature(i); + + // Apply filter, if any + if (queryOptions.filter && !(*queryOptions.filter)(*feature)) { + continue; + } + + result.push_back(convertFeature(*feature, id.canonical)); + } + } +} + +} // namespace mbgl diff --git a/src/mbgl/tile/custom_geometry_tile.hpp b/src/mbgl/tile/custom_geometry_tile.hpp new file mode 100644 index 0000000000..1df44e6b2a --- /dev/null +++ b/src/mbgl/tile/custom_geometry_tile.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include <mbgl/tile/geometry_tile.hpp> +#include <mbgl/style/sources/custom_geometry_source.hpp> +#include <mbgl/util/feature.hpp> +#include <mbgl/util/geojson.hpp> +#include <mbgl/actor/mailbox.hpp> + +namespace mbgl { + +class TileParameters; + +namespace style { +class CustomTileLoader; +} // namespace style + +class CustomGeometryTile: public GeometryTile { +public: + CustomGeometryTile(const OverscaledTileID&, + std::string sourceID, + const TileParameters&, + const style::CustomGeometrySource::TileOptions, + ActorRef<style::CustomTileLoader> loader); + ~CustomGeometryTile() override; + + void setTileData(const GeoJSON& data); + void invalidateTileData(); + + void setNecessity(TileNecessity) final; + + void querySourceFeatures( + std::vector<Feature>& result, + const SourceQueryOptions&) override; + +private: + bool stale = true; + TileNecessity necessity; + const style::CustomGeometrySource::TileOptions options; + ActorRef<style::CustomTileLoader> loader; + std::shared_ptr<Mailbox> mailbox; + ActorRef<CustomGeometryTile> actorRef; +}; + +} // namespace mbgl diff --git a/src/mbgl/tile/geometry_tile.cpp b/src/mbgl/tile/geometry_tile.cpp index 8c018ce3aa..a58c744065 100644 --- a/src/mbgl/tile/geometry_tile.cpp +++ b/src/mbgl/tile/geometry_tile.cpp @@ -15,7 +15,6 @@ #include <mbgl/renderer/image_atlas.hpp> #include <mbgl/storage/file_source.hpp> #include <mbgl/geometry/feature_index.hpp> -#include <mbgl/text/collision_tile.hpp> #include <mbgl/map/transform_state.hpp> #include <mbgl/style/filter_evaluator.hpp> #include <mbgl/util/logging.hpp> @@ -33,7 +32,7 @@ using namespace style; GeometryTile's 'correlationID' is used for ensuring the tile will be flagged as non-pending only when the placement coming from the last operation (as in - 'setData', 'setLayers', 'setPlacementConfig') occurs. This is important for + 'setData', 'setLayers', 'setShowCollisionBoxes') occurs. This is important for still mode rendering as we want to render only when all layout and placement operations are completed. @@ -52,13 +51,15 @@ GeometryTile::GeometryTile(const OverscaledTileID& id_, worker(parameters.workerScheduler, ActorRef<GeometryTile>(*this, mailbox), id_, + sourceID, obsolete, parameters.mode, - parameters.pixelRatio), + parameters.pixelRatio, + parameters.debugOptions & MapDebugOptions::Collision), glyphManager(parameters.glyphManager), imageManager(parameters.imageManager), - lastYStretch(1.0f), - mode(parameters.mode) { + mode(parameters.mode), + showCollisionBoxes(parameters.debugOptions & MapDebugOptions::Collision) { } GeometryTile::~GeometryTile() { @@ -89,25 +90,6 @@ void GeometryTile::setData(std::unique_ptr<const GeometryTileData> data_) { worker.invoke(&GeometryTileWorker::setData, std::move(data_), correlationID); } -void GeometryTile::setPlacementConfig(const PlacementConfig& desiredConfig) { - if (requestedConfig == desiredConfig) { - return; - } - - // Mark the tile as pending again if it was complete before to prevent signaling a complete - // state despite pending parse operations. - pending = true; - - ++correlationID; - requestedConfig = desiredConfig; - invokePlacement(); -} - -void GeometryTile::invokePlacement() { - if (requestedConfig) { - worker.invoke(&GeometryTileWorker::setPlacementConfig, *requestedConfig, correlationID); - } -} void GeometryTile::setLayers(const std::vector<Immutable<Layer::Impl>>& layers) { // Mark the tile as pending again if it was complete before to prevent signaling a complete @@ -134,14 +116,22 @@ void GeometryTile::setLayers(const std::vector<Immutable<Layer::Impl>>& layers) worker.invoke(&GeometryTileWorker::setLayers, std::move(impls), correlationID); } +void GeometryTile::setShowCollisionBoxes(const bool showCollisionBoxes_) { + if (showCollisionBoxes != showCollisionBoxes_) { + showCollisionBoxes = showCollisionBoxes_; + ++correlationID; + worker.invoke(&GeometryTileWorker::setShowCollisionBoxes, showCollisionBoxes, correlationID); + } +} + void GeometryTile::onLayout(LayoutResult result, const uint64_t resultCorrelationID) { - loaded = true; - renderable = true; + // Don't mark ourselves loaded or renderable until the first successful placement + // TODO: Ideally we'd render this tile without symbols as long as this tile wasn't + // replacing a tile at a different zoom that _did_ have symbols. (void)resultCorrelationID; nonSymbolBuckets = std::move(result.nonSymbolBuckets); - featureIndex = std::move(result.featureIndex); - data = std::move(result.tileData); - collisionTile.reset(); + pendingFeatureIndex = std::move(result.featureIndex); + pendingData = std::move(result.tileData); observer->onTileChanged(*this); } @@ -152,16 +142,13 @@ void GeometryTile::onPlacement(PlacementResult result, const uint64_t resultCorr pending = false; } symbolBuckets = std::move(result.symbolBuckets); - collisionTile = std::move(result.collisionTile); if (result.glyphAtlasImage) { glyphAtlasImage = std::move(*result.glyphAtlasImage); } if (result.iconAtlasImage) { iconAtlasImage = std::move(*result.iconAtlasImage); } - if (collisionTile.get()) { - lastYStretch = collisionTile->yStretch; - } + observer->onTileChanged(*this); } @@ -226,12 +213,22 @@ Bucket* GeometryTile::getBucket(const Layer::Impl& layer) const { return it->second.get(); } +void GeometryTile::commitFeatureIndex() { + if (pendingFeatureIndex) { + featureIndex = std::move(pendingFeatureIndex); + } + if (pendingData) { + data = std::move(pendingData); + } +} + void GeometryTile::queryRenderedFeatures( std::unordered_map<std::string, std::vector<Feature>>& result, const GeometryCoordinates& queryGeometry, const TransformState& transformState, const std::vector<const RenderLayer*>& layers, - const RenderedQueryOptions& options) { + const RenderedQueryOptions& options, + const CollisionIndex& collisionIndex) { if (!featureIndex || !data) return; @@ -251,9 +248,10 @@ void GeometryTile::queryRenderedFeatures( std::pow(2, transformState.getZoom() - id.overscaledZ), options, *data, - id.canonical, + id.toUnwrapped(), + sourceID, layers, - collisionTile.get(), + collisionIndex, additionalRadius); } @@ -293,11 +291,25 @@ void GeometryTile::querySourceFeatures( } } -float GeometryTile::yStretch() const { - // collisionTile gets reset in onLayout but we don't clear the symbolBuckets - // until a new placement result comes along, so keep the yStretch value in - // case we need to render them. - return lastYStretch; +bool GeometryTile::holdForFade() const { + return mode == MapMode::Continuous && + (fadeState == FadeState::NeedsFirstPlacement || fadeState == FadeState::NeedsSecondPlacement); +} + +void GeometryTile::markRenderedIdeal() { + fadeState = FadeState::Loaded; +} +void GeometryTile::markRenderedPreviously() { + if (fadeState == FadeState::Loaded) { + fadeState = FadeState::NeedsFirstPlacement; + } +} +void GeometryTile::performedFadePlacement() { + if (fadeState == FadeState::NeedsFirstPlacement) { + fadeState = FadeState::NeedsSecondPlacement; + } else if (fadeState == FadeState::NeedsSecondPlacement) { + fadeState = FadeState::CanRemove; + } } } // namespace mbgl diff --git a/src/mbgl/tile/geometry_tile.hpp b/src/mbgl/tile/geometry_tile.hpp index a478aad504..00a4aafadf 100644 --- a/src/mbgl/tile/geometry_tile.hpp +++ b/src/mbgl/tile/geometry_tile.hpp @@ -4,8 +4,6 @@ #include <mbgl/tile/geometry_tile_worker.hpp> #include <mbgl/renderer/image_manager.hpp> #include <mbgl/text/glyph_manager.hpp> -#include <mbgl/text/placement_config.hpp> -#include <mbgl/text/collision_tile.hpp> #include <mbgl/util/feature.hpp> #include <mbgl/util/throttler.hpp> #include <mbgl/actor/actor.hpp> @@ -36,9 +34,9 @@ public: void setError(std::exception_ptr); void setData(std::unique_ptr<const GeometryTileData>); - void setPlacementConfig(const PlacementConfig&) override; void setLayers(const std::vector<Immutable<style::Layer::Impl>>&) override; - + void setShowCollisionBoxes(const bool showCollisionBoxes) override; + void onGlyphsAvailable(GlyphMap) override; void onImagesAvailable(ImageMap, uint64_t imageCorrelationID) override; @@ -56,7 +54,8 @@ public: const GeometryCoordinates& queryGeometry, const TransformState&, const std::vector<const RenderLayer*>& layers, - const RenderedQueryOptions& options) override; + const RenderedQueryOptions& options, + const CollisionIndex& collisionIndex) override; void querySourceFeatures( std::vector<Feature>& result, @@ -82,16 +81,13 @@ public: class PlacementResult { public: std::unordered_map<std::string, std::shared_ptr<Bucket>> symbolBuckets; - std::unique_ptr<CollisionTile> collisionTile; optional<AlphaImage> glyphAtlasImage; optional<PremultipliedImage> iconAtlasImage; PlacementResult(std::unordered_map<std::string, std::shared_ptr<Bucket>> symbolBuckets_, - std::unique_ptr<CollisionTile> collisionTile_, optional<AlphaImage> glyphAtlasImage_, optional<PremultipliedImage> iconAtlasImage_) : symbolBuckets(std::move(symbolBuckets_)), - collisionTile(std::move(collisionTile_)), glyphAtlasImage(std::move(glyphAtlasImage_)), iconAtlasImage(std::move(iconAtlasImage_)) {} }; @@ -99,7 +95,12 @@ public: void onError(std::exception_ptr, uint64_t correlationID); - float yStretch() const override; + bool holdForFade() const override; + void markRenderedIdeal() override; + void markRenderedPreviously() override; + void performedFadePlacement() override; + + void commitFeatureIndex() override; protected: const GeometryTileData* getData() { @@ -108,7 +109,6 @@ protected: private: void markObsolete(); - void invokePlacement(); const std::string sourceID; @@ -122,21 +122,30 @@ private: ImageManager& imageManager; uint64_t correlationID = 0; - optional<PlacementConfig> requestedConfig; std::unordered_map<std::string, std::shared_ptr<Bucket>> nonSymbolBuckets; std::unique_ptr<FeatureIndex> featureIndex; + std::unique_ptr<FeatureIndex> pendingFeatureIndex; std::unique_ptr<const GeometryTileData> data; + std::unique_ptr<const GeometryTileData> pendingData; optional<AlphaImage> glyphAtlasImage; optional<PremultipliedImage> iconAtlasImage; std::unordered_map<std::string, std::shared_ptr<Bucket>> symbolBuckets; - std::unique_ptr<CollisionTile> collisionTile; - float lastYStretch; const MapMode mode; + + bool showCollisionBoxes; + + enum class FadeState { + Loaded, + NeedsFirstPlacement, + NeedsSecondPlacement, + CanRemove + }; + FadeState fadeState = FadeState::Loaded; public: optional<gl::Texture> glyphAtlasTexture; optional<gl::Texture> iconAtlasTexture; diff --git a/src/mbgl/tile/geometry_tile_worker.cpp b/src/mbgl/tile/geometry_tile_worker.cpp index 50429420c3..24841dd125 100644 --- a/src/mbgl/tile/geometry_tile_worker.cpp +++ b/src/mbgl/tile/geometry_tile_worker.cpp @@ -1,7 +1,6 @@ #include <mbgl/tile/geometry_tile_worker.hpp> #include <mbgl/tile/geometry_tile_data.hpp> #include <mbgl/tile/geometry_tile.hpp> -#include <mbgl/text/collision_tile.hpp> #include <mbgl/layout/symbol_layout.hpp> #include <mbgl/renderer/bucket_parameters.hpp> #include <mbgl/renderer/group_by_layout.hpp> @@ -24,20 +23,36 @@ using namespace style; GeometryTileWorker::GeometryTileWorker(ActorRef<GeometryTileWorker> self_, ActorRef<GeometryTile> parent_, OverscaledTileID id_, + const std::string& sourceID_, const std::atomic<bool>& obsolete_, const MapMode mode_, - const float pixelRatio_) + const float pixelRatio_, + const bool showCollisionBoxes_) : self(std::move(self_)), parent(std::move(parent_)), id(std::move(id_)), + sourceID(sourceID_), obsolete(obsolete_), mode(mode_), - pixelRatio(pixelRatio_) { + pixelRatio(pixelRatio_), + showCollisionBoxes(showCollisionBoxes_) { } GeometryTileWorker::~GeometryTileWorker() = default; /* + NOTE: The comments below are technically correct, but currently + conceptually misleading. The change to foreground label placement + means that: + (1) "placement" here is a misnomer: the remaining role of + "attemptPlacement" is symbol buffer generation + (2) Once a tile has completed layout, we will only run + "attemptPlacement" once + (3) Tiles won't be rendered until "attemptPlacement" has run once + + TODO: Simplify GeometryTileWorker to fit its new role + https://github.com/mapbox/mapbox-gl-native/issues/10457 + GeometryTileWorker is a state machine. This is its transition diagram. States are indicated by [state], lines are transitions triggered by messages, (parentheses) are actions taken on transition. @@ -116,9 +131,9 @@ void GeometryTileWorker::setLayers(std::vector<Immutable<Layer::Impl>> layers_, } } -void GeometryTileWorker::setPlacementConfig(PlacementConfig placementConfig_, uint64_t correlationID_) { +void GeometryTileWorker::setShowCollisionBoxes(bool showCollisionBoxes_, uint64_t correlationID_) { try { - placementConfig = std::move(placementConfig_); + showCollisionBoxes = showCollisionBoxes_; correlationID = correlationID_; switch (state) { @@ -372,7 +387,7 @@ bool GeometryTileWorker::hasPendingSymbolDependencies() const { } void GeometryTileWorker::attemptPlacement() { - if (!data || !layers || !placementConfig || hasPendingSymbolDependencies()) { + if (!data || !layers || hasPendingSymbolDependencies()) { return; } @@ -392,13 +407,13 @@ void GeometryTileWorker::attemptPlacement() { } symbolLayout->prepare(glyphMap, glyphAtlas.positions, - imageMap, imageAtlas.positions); + imageMap, imageAtlas.positions, + id, sourceID); } symbolLayoutsNeedPreparation = false; } - auto collisionTile = std::make_unique<CollisionTile>(*placementConfig); std::unordered_map<std::string, std::shared_ptr<Bucket>> buckets; for (auto& symbolLayout : symbolLayouts) { @@ -410,15 +425,19 @@ void GeometryTileWorker::attemptPlacement() { continue; } - std::shared_ptr<Bucket> bucket = symbolLayout->place(*collisionTile); + std::shared_ptr<SymbolBucket> bucket = symbolLayout->place(showCollisionBoxes); for (const auto& pair : symbolLayout->layerPaintProperties) { + if (!firstLoad) { + bucket->justReloaded = true; + } buckets.emplace(pair.first, bucket); } } + firstLoad = false; + parent.invoke(&GeometryTile::onPlacement, GeometryTile::PlacementResult { std::move(buckets), - std::move(collisionTile), std::move(glyphAtlasImage), std::move(iconAtlasImage), }, correlationID); diff --git a/src/mbgl/tile/geometry_tile_worker.hpp b/src/mbgl/tile/geometry_tile_worker.hpp index 1425daa7a1..0276392679 100644 --- a/src/mbgl/tile/geometry_tile_worker.hpp +++ b/src/mbgl/tile/geometry_tile_worker.hpp @@ -4,7 +4,6 @@ #include <mbgl/tile/tile_id.hpp> #include <mbgl/style/image_impl.hpp> #include <mbgl/text/glyph.hpp> -#include <mbgl/text/placement_config.hpp> #include <mbgl/actor/actor_ref.hpp> #include <mbgl/util/optional.hpp> #include <mbgl/util/immutable.hpp> @@ -28,14 +27,16 @@ public: GeometryTileWorker(ActorRef<GeometryTileWorker> self, ActorRef<GeometryTile> parent, OverscaledTileID, + const std::string&, const std::atomic<bool>&, const MapMode, - const float pixelRatio); + const float pixelRatio, + const bool showCollisionBoxes_); ~GeometryTileWorker(); void setLayers(std::vector<Immutable<style::Layer::Impl>>, uint64_t correlationID); void setData(std::unique_ptr<const GeometryTileData>, uint64_t correlationID); - void setPlacementConfig(PlacementConfig, uint64_t correlationID); + void setShowCollisionBoxes(bool showCollisionBoxes_, uint64_t correlationID_); void onGlyphsAvailable(GlyphMap glyphs); void onImagesAvailable(ImageMap images, uint64_t imageCorrelationID); @@ -57,6 +58,7 @@ private: ActorRef<GeometryTile> parent; const OverscaledTileID id; + const std::string sourceID; const std::atomic<bool>& obsolete; const MapMode mode; const float pixelRatio; @@ -75,7 +77,6 @@ private: // Outer optional indicates whether we've received it or not. optional<std::vector<Immutable<style::Layer::Impl>>> layers; optional<std::unique_ptr<const GeometryTileData>> data; - optional<PlacementConfig> placementConfig; bool symbolLayoutsNeedPreparation = false; std::vector<std::unique_ptr<SymbolLayout>> symbolLayouts; @@ -83,6 +84,9 @@ private: ImageDependencies pendingImageDependencies; GlyphMap glyphMap; ImageMap imageMap; + + bool showCollisionBoxes; + bool firstLoad = true; }; } // namespace mbgl diff --git a/src/mbgl/tile/raster_dem_tile.cpp b/src/mbgl/tile/raster_dem_tile.cpp new file mode 100644 index 0000000000..b270378ece --- /dev/null +++ b/src/mbgl/tile/raster_dem_tile.cpp @@ -0,0 +1,124 @@ +#include <mbgl/tile/raster_dem_tile.hpp> +#include <mbgl/tile/raster_dem_tile_worker.hpp> +#include <mbgl/tile/tile_observer.hpp> +#include <mbgl/tile/tile_loader_impl.hpp> +#include <mbgl/style/source.hpp> +#include <mbgl/storage/resource.hpp> +#include <mbgl/storage/response.hpp> +#include <mbgl/storage/file_source.hpp> +#include <mbgl/renderer/tile_parameters.hpp> +#include <mbgl/renderer/buckets/hillshade_bucket.hpp> +#include <mbgl/actor/scheduler.hpp> + +namespace mbgl { + +RasterDEMTile::RasterDEMTile(const OverscaledTileID& id_, + const TileParameters& parameters, + const Tileset& tileset) + : Tile(id_), + loader(*this, id_, parameters, tileset), + mailbox(std::make_shared<Mailbox>(*Scheduler::GetCurrent())), + worker(parameters.workerScheduler, + ActorRef<RasterDEMTile>(*this, mailbox)) { + + if ( id.canonical.y == 0 ){ + // this tile doesn't have upper neighboring tiles so marked those as backfilled + neighboringTiles = neighboringTiles | DEMTileNeighbors::NoUpper; + } + + if (id.canonical.y + 1 == std::pow(2, id.canonical.z)){ + // this tile doesn't have lower neighboring tiles so marked those as backfilled + neighboringTiles = neighboringTiles | DEMTileNeighbors::NoLower; + } +} + +RasterDEMTile::~RasterDEMTile() = default; + +void RasterDEMTile::setError(std::exception_ptr err) { + loaded = true; + observer->onTileError(*this, err); +} + +void RasterDEMTile::setMetadata(optional<Timestamp> modified_, optional<Timestamp> expires_) { + modified = modified_; + expires = expires_; +} + +void RasterDEMTile::setData(std::shared_ptr<const std::string> data) { + pending = true; + ++correlationID; + worker.invoke(&RasterDEMTileWorker::parse, data, correlationID); +} + +void RasterDEMTile::onParsed(std::unique_ptr<HillshadeBucket> result, const uint64_t resultCorrelationID) { + bucket = std::move(result); + loaded = true; + if (resultCorrelationID == correlationID) { + pending = false; + } + renderable = bucket ? true : false; + observer->onTileChanged(*this); +} + +void RasterDEMTile::onError(std::exception_ptr err, const uint64_t resultCorrelationID) { + loaded = true; + if (resultCorrelationID == correlationID) { + pending = false; + } + observer->onTileError(*this, err); +} + +void RasterDEMTile::upload(gl::Context& context) { + if (bucket) { + bucket->upload(context); + } +} + + +Bucket* RasterDEMTile::getBucket(const style::Layer::Impl&) const { + return bucket.get(); +} + +HillshadeBucket* RasterDEMTile::getBucket() const { + return bucket.get(); +} + +void RasterDEMTile::backfillBorder(const RasterDEMTile& borderTile, const DEMTileNeighbors mask) { + int32_t dx = borderTile.id.canonical.x - id.canonical.x; + const int8_t dy = borderTile.id.canonical.y - id.canonical.y; + const uint32_t dim = pow(2, id.canonical.z); + if (dx == 0 && dy == 0) return; + if (std::abs(dy) > 1) return; + // neighbor is in another world wrap + if (std::abs(dx) > 1) { + if (std::abs(int(dx + dim)) == 1) { + dx += dim; + } else if (std::abs(int(dx - dim)) == 1) { + dx -= dim; + } + } + const HillshadeBucket* borderBucket = borderTile.getBucket(); + if (borderBucket) { + const DEMData& borderDEM = borderBucket->getDEMData(); + DEMData& tileDEM = bucket->getDEMData(); + + tileDEM.backfillBorder(borderDEM, dx, dy); + // update the bitmask to indicate that this tiles have been backfilled by flipping the relevant bit + this->neighboringTiles = this->neighboringTiles | mask; + // mark HillshadeBucket.prepared as false so it runs through the prepare render pass + // with the new texture data we just backfilled + bucket->setPrepared(false); + } +} + +void RasterDEMTile::setMask(TileMask&& mask) { + if (bucket) { + bucket->setMask(std::move(mask)); + } +} + +void RasterDEMTile::setNecessity(TileNecessity necessity) { + loader.setNecessity(necessity); +} + +} // namespace mbgl diff --git a/src/mbgl/tile/raster_dem_tile.hpp b/src/mbgl/tile/raster_dem_tile.hpp new file mode 100644 index 0000000000..68f8a91e00 --- /dev/null +++ b/src/mbgl/tile/raster_dem_tile.hpp @@ -0,0 +1,104 @@ +#pragma once + +#include <mbgl/tile/tile.hpp> +#include <mbgl/tile/tile_loader.hpp> +#include <mbgl/tile/raster_dem_tile_worker.hpp> +#include <mbgl/actor/actor.hpp> + +namespace mbgl { + +class Tileset; +class TileParameters; +class HillshadeBucket; + +enum class DEMTileNeighbors : uint8_t { + // 0b00000000 + Empty = 0 << 1, + + // 0b00000001 + Left = 1 << 0, + // 0b00000010 + Right = 1 << 1, + // 0b00000100 + TopLeft = 1 << 2, + // 0b00001000 + TopCenter = 1 << 3, + // 0b00010000 + TopRight = 1 << 4, + // 0b00100000 + BottomLeft = 1 << 5, + // 0b01000000 + BottomCenter = 1 << 6, + // 0b10000000 + BottomRight = 1 << 7, + + // helper enums for tiles with no upper/lower neighbors + // and completely backfilled tiles + + // 0b00011100 + NoUpper = 0b00011100, + // 0b11100000 + NoLower = 0b11100000, + // 0b11111111 + Complete = 0b11111111 +}; + +inline DEMTileNeighbors operator|(DEMTileNeighbors a, DEMTileNeighbors b) { + return static_cast<DEMTileNeighbors>(int(a) | int(b)); +}; + +inline DEMTileNeighbors operator&(DEMTileNeighbors a, DEMTileNeighbors b) { + return static_cast<DEMTileNeighbors>(int(a) & int(b)); +} + +inline bool operator!=(DEMTileNeighbors a, DEMTileNeighbors b) { + return static_cast<unsigned char>(a) != static_cast<unsigned char>(b); +} + +namespace style { +class Layer; +} // namespace style + +class RasterDEMTile : public Tile { +public: + RasterDEMTile(const OverscaledTileID&, + const TileParameters&, + const Tileset&); + ~RasterDEMTile() override; + + void setNecessity(TileNecessity) final; + + void setError(std::exception_ptr); + void setMetadata(optional<Timestamp> modified, optional<Timestamp> expires); + void setData(std::shared_ptr<const std::string> data); + + void upload(gl::Context&) override; + Bucket* getBucket(const style::Layer::Impl&) const override; + + HillshadeBucket* getBucket() const; + void backfillBorder(const RasterDEMTile& borderTile, const DEMTileNeighbors mask); + + // neighboringTiles is a bitmask for which neighboring tiles have been backfilled + // there are max 8 possible neighboring tiles, so each bit represents one neighbor + DEMTileNeighbors neighboringTiles = DEMTileNeighbors::Empty; + + void setMask(TileMask&&) override; + + void onParsed(std::unique_ptr<HillshadeBucket> result, uint64_t correlationID); + void onError(std::exception_ptr, uint64_t correlationID); + +private: + TileLoader<RasterDEMTile> loader; + + std::shared_ptr<Mailbox> mailbox; + Actor<RasterDEMTileWorker> worker; + + uint64_t correlationID = 0; + + // Contains the Bucket object for the tile. Buckets are render + // objects and they get added by tile parsing operations. + std::unique_ptr<HillshadeBucket> bucket; + +}; + +} // namespace mbgl diff --git a/src/mbgl/tile/raster_dem_tile_worker.cpp b/src/mbgl/tile/raster_dem_tile_worker.cpp new file mode 100644 index 0000000000..ed8573788f --- /dev/null +++ b/src/mbgl/tile/raster_dem_tile_worker.cpp @@ -0,0 +1,27 @@ +#include <mbgl/tile/raster_dem_tile_worker.hpp> +#include <mbgl/tile/raster_dem_tile.hpp> +#include <mbgl/renderer/buckets/hillshade_bucket.hpp> +#include <mbgl/actor/actor.hpp> +#include <mbgl/util/premultiply.hpp> + +namespace mbgl { + +RasterDEMTileWorker::RasterDEMTileWorker(ActorRef<RasterDEMTileWorker>, ActorRef<RasterDEMTile> parent_) + : parent(std::move(parent_)) { +} + +void RasterDEMTileWorker::parse(std::shared_ptr<const std::string> data, uint64_t correlationID) { + if (!data) { + parent.invoke(&RasterDEMTile::onParsed, nullptr, correlationID); // No data; empty tile. + return; + } + + try { + auto bucket = std::make_unique<HillshadeBucket>(decodeImage(*data)); + parent.invoke(&RasterDEMTile::onParsed, std::move(bucket), correlationID); + } catch (...) { + parent.invoke(&RasterDEMTile::onError, std::current_exception(), correlationID); + } +} + +} // namespace mbgl diff --git a/src/mbgl/tile/raster_dem_tile_worker.hpp b/src/mbgl/tile/raster_dem_tile_worker.hpp new file mode 100644 index 0000000000..14fd1f43b5 --- /dev/null +++ b/src/mbgl/tile/raster_dem_tile_worker.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include <mbgl/actor/actor_ref.hpp> + +#include <memory> +#include <string> + +namespace mbgl { + +class RasterDEMTile; + +class RasterDEMTileWorker { +public: + RasterDEMTileWorker(ActorRef<RasterDEMTileWorker>, ActorRef<RasterDEMTile>); + + void parse(std::shared_ptr<const std::string> data, uint64_t correlationID); + +private: + ActorRef<RasterDEMTile> parent; +}; + +} // namespace mbgl diff --git a/src/mbgl/tile/raster_tile.cpp b/src/mbgl/tile/raster_tile.cpp index 85fcea77b7..ff23d4493e 100644 --- a/src/mbgl/tile/raster_tile.cpp +++ b/src/mbgl/tile/raster_tile.cpp @@ -24,9 +24,6 @@ RasterTile::RasterTile(const OverscaledTileID& id_, RasterTile::~RasterTile() = default; -void RasterTile::cancel() { -} - void RasterTile::setError(std::exception_ptr err) { loaded = true; observer->onTileError(*this, err); diff --git a/src/mbgl/tile/raster_tile.hpp b/src/mbgl/tile/raster_tile.hpp index 192769ed8f..e25329119a 100644 --- a/src/mbgl/tile/raster_tile.hpp +++ b/src/mbgl/tile/raster_tile.hpp @@ -20,7 +20,7 @@ public: RasterTile(const OverscaledTileID&, const TileParameters&, const Tileset&); - ~RasterTile() final; + ~RasterTile() override; void setNecessity(TileNecessity) final; @@ -28,8 +28,6 @@ public: void setMetadata(optional<Timestamp> modified, optional<Timestamp> expires); void setData(std::shared_ptr<const std::string> data); - void cancel() override; - void upload(gl::Context&) override; Bucket* getBucket(const style::Layer::Impl&) const override; diff --git a/src/mbgl/tile/tile.cpp b/src/mbgl/tile/tile.cpp index f36a472e72..88db2ba07c 100644 --- a/src/mbgl/tile/tile.cpp +++ b/src/mbgl/tile/tile.cpp @@ -18,6 +18,9 @@ void Tile::setObserver(TileObserver* observer_) { observer = observer_; } +void Tile::cancel() { +} + void Tile::setTriedCache() { triedOptional = true; observer->onTileChanged(*this); @@ -34,7 +37,8 @@ void Tile::queryRenderedFeatures( const GeometryCoordinates&, const TransformState&, const std::vector<const RenderLayer*>&, - const RenderedQueryOptions&) {} + const RenderedQueryOptions&, + const CollisionIndex&) {} void Tile::querySourceFeatures( std::vector<Feature>&, diff --git a/src/mbgl/tile/tile.hpp b/src/mbgl/tile/tile.hpp index 8be7c4d862..23365c6ae3 100644 --- a/src/mbgl/tile/tile.hpp +++ b/src/mbgl/tile/tile.hpp @@ -23,11 +23,12 @@ namespace mbgl { class DebugBucket; class TransformState; class TileObserver; -class PlacementConfig; class RenderLayer; class RenderedQueryOptions; class SourceQueryOptions; +class CollisionIndex; + namespace gl { class Context; } // namespace gl @@ -42,12 +43,12 @@ public: virtual void setNecessity(TileNecessity) {} // Mark this tile as no longer needed and cancel any pending work. - virtual void cancel() = 0; + virtual void cancel(); virtual void upload(gl::Context&) = 0; virtual Bucket* getBucket(const style::Layer::Impl&) const = 0; - virtual void setPlacementConfig(const PlacementConfig&) {} + virtual void setShowCollisionBoxes(const bool) {} virtual void setLayers(const std::vector<Immutable<style::Layer::Impl>>&) {} virtual void setMask(TileMask&&) {} @@ -56,7 +57,8 @@ public: const GeometryCoordinates& queryGeometry, const TransformState&, const std::vector<const RenderLayer*>&, - const RenderedQueryOptions& options); + const RenderedQueryOptions& options, + const CollisionIndex&); virtual void querySourceFeatures( std::vector<Feature>& result, @@ -92,7 +94,26 @@ public: bool isComplete() const { return loaded && !pending; } - + + // "holdForFade" is used to keep tiles in the render tree after they're no longer + // ideal tiles in order to allow symbols to fade out + virtual bool holdForFade() const { + return false; + } + // Set whenever this tile is used as an ideal tile + virtual void markRenderedIdeal() {} + // Set when the tile is removed from the ideal render set but may still be held for fading + virtual void markRenderedPreviously() {} + // Placement operation performed while this tile is fading + // We hold onto a tile for two placements: fading starts with the first placement + // and will have time to finish by the second placement. + virtual void performedFadePlacement() {} + + // FeatureIndexes are loaded asynchronously, but must be used with a CollisionIndex + // generated from the same data. Calling commitFeatureIndex signals the current + // CollisionIndex is up-to-date and allows us to start using the last loaded FeatureIndex + virtual void commitFeatureIndex() {} + void dumpDebugLogs() const; const OverscaledTileID id; @@ -101,8 +122,6 @@ public: // Contains the tile ID string for painting debug information. std::unique_ptr<DebugBucket> debugBucket; - - virtual float yStretch() const { return 1.0f; } protected: bool triedOptional = false; diff --git a/src/mbgl/tile/tile_cache.cpp b/src/mbgl/tile/tile_cache.cpp index 3fafb1259c..463d397608 100644 --- a/src/mbgl/tile/tile_cache.cpp +++ b/src/mbgl/tile/tile_cache.cpp @@ -33,13 +33,22 @@ void TileCache::add(const OverscaledTileID& key, std::unique_ptr<Tile> tile) { // purge oldest key/tile if necessary if (orderedKeys.size() > size) { - get(orderedKeys.front()); + pop(orderedKeys.front()); } assert(orderedKeys.size() <= size); } -std::unique_ptr<Tile> TileCache::get(const OverscaledTileID& key) { +Tile* TileCache::get(const OverscaledTileID& key) { + auto it = tiles.find(key); + if (it != tiles.end()) { + return it->second.get(); + } else { + return nullptr; + } +} + +std::unique_ptr<Tile> TileCache::pop(const OverscaledTileID& key) { std::unique_ptr<Tile> tile; diff --git a/src/mbgl/tile/tile_cache.hpp b/src/mbgl/tile/tile_cache.hpp index 80fe98a20c..88358b8cdc 100644 --- a/src/mbgl/tile/tile_cache.hpp +++ b/src/mbgl/tile/tile_cache.hpp @@ -17,7 +17,8 @@ public: void setSize(size_t); size_t getSize() const { return size; }; void add(const OverscaledTileID& key, std::unique_ptr<Tile> data); - std::unique_ptr<Tile> get(const OverscaledTileID& key); + std::unique_ptr<Tile> pop(const OverscaledTileID& key); + Tile* get(const OverscaledTileID& key); bool has(const OverscaledTileID& key); void clear(); diff --git a/src/mbgl/util/color.cpp b/src/mbgl/util/color.cpp index fcb7f4ec16..c8145d36e7 100644 --- a/src/mbgl/util/color.cpp +++ b/src/mbgl/util/color.cpp @@ -24,9 +24,9 @@ optional<Color> Color::parse(const std::string& s) { std::string Color::stringify() const { return "rgba(" + - util::toString(r * 255) + "," + - util::toString(g * 255) + "," + - util::toString(b * 255) + "," + + util::toString(r * 255 / a) + "," + + util::toString(g * 255 / a) + "," + + util::toString(b * 255 / a) + "," + util::toString(a) + ")"; } diff --git a/src/mbgl/util/compression.cpp b/src/mbgl/util/compression.cpp index 94089c1b26..30e813cbb8 100644 --- a/src/mbgl/util/compression.cpp +++ b/src/mbgl/util/compression.cpp @@ -1,6 +1,10 @@ #include <mbgl/util/compression.hpp> +#if defined(__QT__) && defined(_WINDOWS) +#include <QtZlib/zlib.h> +#else #include <zlib.h> +#endif #include <cstdio> #include <cstring> diff --git a/src/mbgl/util/grid_index.cpp b/src/mbgl/util/grid_index.cpp index b3afd3fdc8..afd469501d 100644 --- a/src/mbgl/util/grid_index.cpp +++ b/src/mbgl/util/grid_index.cpp @@ -3,83 +3,301 @@ #include <mbgl/math/minmax.hpp> #include <unordered_set> +#include <cmath> namespace mbgl { template <class T> -GridIndex<T>::GridIndex(int32_t extent_, int32_t n_, int32_t padding_) : - extent(extent_), - n(n_), - padding(padding_), - d(n + 2 * padding), - scale(double(n) / double(extent)), - min(-double(padding) / n * extent), - max(extent + double(padding) / n * extent) +GridIndex<T>::GridIndex(const float width_, const float height_, const int16_t cellSize_) : + width(width_), + height(height_), + xCellCount(std::ceil(width_ / cellSize_)), + yCellCount(std::ceil(height_ / cellSize_)), + xScale(xCellCount / width_), + yScale(yCellCount / height_) { - cells.resize(d * d); + boxCells.resize(xCellCount * yCellCount); + circleCells.resize(xCellCount * yCellCount); } template <class T> void GridIndex<T>::insert(T&& t, const BBox& bbox) { - size_t uid = elements.size(); + size_t uid = boxElements.size(); - auto cx1 = convertToCellCoord(bbox.min.x); - auto cy1 = convertToCellCoord(bbox.min.y); - auto cx2 = convertToCellCoord(bbox.max.x); - auto cy2 = convertToCellCoord(bbox.max.y); + auto cx1 = convertToXCellCoord(bbox.min.x); + auto cy1 = convertToYCellCoord(bbox.min.y); + auto cx2 = convertToXCellCoord(bbox.max.x); + auto cy2 = convertToYCellCoord(bbox.max.y); - int32_t x, y, cellIndex; + int16_t x, y, cellIndex; for (x = cx1; x <= cx2; ++x) { for (y = cy1; y <= cy2; ++y) { - cellIndex = d * y + x; - cells[cellIndex].push_back(uid); + cellIndex = xCellCount * y + x; + boxCells[cellIndex].push_back(uid); } } - elements.emplace_back(t, bbox); + boxElements.emplace_back(t, bbox); +} + +template <class T> +void GridIndex<T>::insert(T&& t, const BCircle& bcircle) { + size_t uid = circleElements.size(); + + auto cx1 = convertToXCellCoord(bcircle.center.x - bcircle.radius); + auto cy1 = convertToYCellCoord(bcircle.center.y - bcircle.radius); + auto cx2 = convertToXCellCoord(bcircle.center.x + bcircle.radius); + auto cy2 = convertToYCellCoord(bcircle.center.y + bcircle.radius); + + int16_t x, y, cellIndex; + for (x = cx1; x <= cx2; ++x) { + for (y = cy1; y <= cy2; ++y) { + cellIndex = xCellCount * y + x; + circleCells[cellIndex].push_back(uid); + } + } + + circleElements.emplace_back(t, bcircle); } template <class T> std::vector<T> GridIndex<T>::query(const BBox& queryBBox) const { std::vector<T> result; - std::unordered_set<size_t> seenUids; + query(queryBBox, [&](const T& t, const BBox&) -> bool { + result.push_back(t); + return false; + }); + return result; +} + +template <class T> +std::vector<std::pair<T, typename GridIndex<T>::BBox>> GridIndex<T>::queryWithBoxes(const BBox& queryBBox) const { + std::vector<std::pair<T, BBox>> result; + query(queryBBox, [&](const T& t, const BBox& bbox) -> bool { + result.push_back(std::make_pair(t, bbox)); + return false; + }); + return result; +} + +template <class T> +bool GridIndex<T>::hitTest(const BBox& queryBBox) const { + bool hit = false; + query(queryBBox, [&](const T&, const BBox&) -> bool { + hit = true; + return true; + }); + return hit; +} + +template <class T> +bool GridIndex<T>::hitTest(const BCircle& queryBCircle) const { + bool hit = false; + query(queryBCircle, [&](const T&, const BBox&) -> bool { + hit = true; + return true; + }); + return hit; +} - auto cx1 = convertToCellCoord(queryBBox.min.x); - auto cy1 = convertToCellCoord(queryBBox.min.y); - auto cx2 = convertToCellCoord(queryBBox.max.x); - auto cy2 = convertToCellCoord(queryBBox.max.y); +template <class T> +bool GridIndex<T>::noIntersection(const BBox& queryBBox) const { + return queryBBox.max.x < 0 || queryBBox.min.x >= width || queryBBox.max.y < 0 || queryBBox.min.y >= height; +} + +template <class T> +bool GridIndex<T>::completeIntersection(const BBox& queryBBox) const { + return queryBBox.min.x <= 0 && queryBBox.min.y <= 0 && width <= queryBBox.max.x && height <= queryBBox.max.y; +} + +template <class T> +typename GridIndex<T>::BBox GridIndex<T>::convertToBox(const BCircle& circle) const { + return BBox{{circle.center.x - circle.radius, circle.center.y - circle.radius}, + {circle.center.x + circle.radius, circle.center.y + circle.radius}}; +} + +template <class T> +void GridIndex<T>::query(const BBox& queryBBox, std::function<bool (const T&, const BBox&)> resultFn) const { + std::unordered_set<size_t> seenBoxes; + std::unordered_set<size_t> seenCircles; + + if (noIntersection(queryBBox)) { + return; + } else if (completeIntersection(queryBBox)) { + for (auto& element : boxElements) { + if (resultFn(element.first, element.second)) { + return; + } + } + for (auto& element : circleElements) { + if (resultFn(element.first, convertToBox(element.second))) { + return; + } + } + return; + } + + auto cx1 = convertToXCellCoord(queryBBox.min.x); + auto cy1 = convertToYCellCoord(queryBBox.min.y); + auto cx2 = convertToXCellCoord(queryBBox.max.x); + auto cy2 = convertToYCellCoord(queryBBox.max.y); - int32_t x, y, cellIndex; + int16_t x, y, cellIndex; for (x = cx1; x <= cx2; ++x) { for (y = cy1; y <= cy2; ++y) { - cellIndex = d * y + x; - for (auto uid : cells[cellIndex]) { - if (seenUids.count(uid) == 0) { - seenUids.insert(uid); + cellIndex = xCellCount * y + x; + // Look up other boxes + for (auto uid : boxCells[cellIndex]) { + if (seenBoxes.count(uid) == 0) { + seenBoxes.insert(uid); - auto& pair = elements.at(uid); + auto& pair = boxElements.at(uid); auto& bbox = pair.second; - if (queryBBox.min.x <= bbox.max.x && - queryBBox.min.y <= bbox.max.y && - queryBBox.max.x >= bbox.min.x && - queryBBox.max.y >= bbox.min.y) { + if (boxesCollide(queryBBox, bbox)) { + if (resultFn(pair.first, bbox)) { + return; + } + } + } + } + + // Look up circles + for (auto uid : circleCells[cellIndex]) { + if (seenCircles.count(uid) == 0) { + seenCircles.insert(uid); - result.push_back(pair.first); + auto& pair = circleElements.at(uid); + auto& bcircle = pair.second; + if (circleAndBoxCollide(bcircle, queryBBox)) { + if (resultFn(pair.first, convertToBox(bcircle))) { + return; + } } } } } } +} - return result; +template <class T> +void GridIndex<T>::query(const BCircle& queryBCircle, std::function<bool (const T&, const BBox&)> resultFn) const { + std::unordered_set<size_t> seenBoxes; + std::unordered_set<size_t> seenCircles; + + BBox queryBBox = convertToBox(queryBCircle); + if (noIntersection(queryBBox)) { + return; + } else if (completeIntersection(queryBBox)) { + for (auto& element : boxElements) { + if (resultFn(element.first, element.second)) { + return; + } + } + for (auto& element : circleElements) { + if (resultFn(element.first, convertToBox(element.second))) { + return; + } + } + } + + auto cx1 = convertToXCellCoord(queryBCircle.center.x - queryBCircle.radius); + auto cy1 = convertToYCellCoord(queryBCircle.center.y - queryBCircle.radius); + auto cx2 = convertToXCellCoord(queryBCircle.center.x + queryBCircle.radius); + auto cy2 = convertToYCellCoord(queryBCircle.center.y + queryBCircle.radius); + + int16_t x, y, cellIndex; + for (x = cx1; x <= cx2; ++x) { + for (y = cy1; y <= cy2; ++y) { + cellIndex = xCellCount * y + x; + // Look up boxes + for (auto uid : boxCells[cellIndex]) { + if (seenBoxes.count(uid) == 0) { + seenBoxes.insert(uid); + + auto& pair = boxElements.at(uid); + auto& bbox = pair.second; + if (circleAndBoxCollide(queryBCircle, bbox)) { + if (resultFn(pair.first, bbox)) { + return; + } + } + } + } + + // Look up other circles + for (auto uid : circleCells[cellIndex]) { + if (seenCircles.count(uid) == 0) { + seenCircles.insert(uid); + + auto& pair = circleElements.at(uid); + auto& bcircle = pair.second; + if (circlesCollide(queryBCircle, bcircle)) { + if (resultFn(pair.first, convertToBox(bcircle))) { + return; + } + } + } + } + } + } } +template <class T> +int16_t GridIndex<T>::convertToXCellCoord(const float x) const { + return util::max(0.0, util::min(xCellCount - 1.0, std::floor(x * xScale))); +} + +template <class T> +int16_t GridIndex<T>::convertToYCellCoord(const float y) const { + return util::max(0.0, util::min(yCellCount - 1.0, std::floor(y * yScale))); +} template <class T> -int32_t GridIndex<T>::convertToCellCoord(int32_t x) const { - return util::max(0.0, util::min(d - 1.0, std::floor(x * scale) + padding)); +bool GridIndex<T>::boxesCollide(const BBox& first, const BBox& second) const { + return first.min.x <= second.max.x && + first.min.y <= second.max.y && + first.max.x >= second.min.x && + first.max.y >= second.min.y; } +template <class T> +bool GridIndex<T>::circlesCollide(const BCircle& first, const BCircle& second) const { + auto dx = second.center.x - first.center.x; + auto dy = second.center.y - first.center.y; + auto bothRadii = first.radius + second.radius; + return (bothRadii * bothRadii) > (dx * dx + dy * dy); +} + +template <class T> +bool GridIndex<T>::circleAndBoxCollide(const BCircle& circle, const BBox& box) const { + auto halfRectWidth = (box.max.x - box.min.x) / 2; + auto distX = std::abs(circle.center.x - (box.min.x + halfRectWidth)); + if (distX > (halfRectWidth + circle.radius)) { + return false; + } + + auto halfRectHeight = (box.max.y - box.min.y) / 2; + auto distY = std::abs(circle.center.y - (box.min.y + halfRectHeight)); + if (distY > (halfRectHeight + circle.radius)) { + return false; + } + + if (distX <= halfRectWidth || distY <= halfRectHeight) { + return true; + } + + auto dx = distX - halfRectWidth; + auto dy = distY - halfRectHeight; + return (dx * dx + dy * dy) <= (circle.radius * circle.radius); +} + +template <class T> +bool GridIndex<T>::empty() const { + return boxElements.empty() && circleElements.empty(); +} + + template class GridIndex<IndexedSubfeature>; + } // namespace mbgl diff --git a/src/mbgl/util/grid_index.hpp b/src/mbgl/util/grid_index.hpp index 8ef8fb35b7..6ef2966bee 100644 --- a/src/mbgl/util/grid_index.hpp +++ b/src/mbgl/util/grid_index.hpp @@ -6,32 +6,100 @@ #include <cstdint> #include <cstddef> #include <vector> +#include <functional> namespace mbgl { +namespace geometry { + +template <typename T> +struct circle +{ + using point_type = mapbox::geometry::point<T>; + + constexpr circle(point_type const& center_, T const& radius_) + : center(center_), radius(radius_) + {} + + point_type center; + T radius; +}; + +template <typename T> +constexpr bool operator==(circle<T> const& lhs, circle<T> const& rhs) +{ + return lhs.center == rhs.center && lhs.radius == rhs.radius; +} + +template <typename T> +constexpr bool operator!=(circle<T> const& lhs, circle<T> const& rhs) +{ + return lhs.center != rhs.center || lhs.radius != rhs.radius; +} + +} // namespace geometry + + +/* + GridIndex is a data structure for testing the intersection of + circles and rectangles in a 2d plane. + It is optimized for rapid insertion and querying. + GridIndex splits the plane into a set of "cells" and keeps track + of which geometries intersect with each cell. At query time, + full geometry comparisons are only done for items that share + at least one cell. As long as the geometries are relatively + uniformly distributed across the plane, this greatly reduces + the number of comparisons necessary. +*/ + template <class T> class GridIndex { public: - GridIndex(int32_t extent_, int32_t n_, int32_t padding_); - using BBox = mapbox::geometry::box<int16_t>; + GridIndex(const float width_, const float height_, const int16_t cellSize_); + + using BBox = mapbox::geometry::box<float>; + using BCircle = geometry::circle<float>; void insert(T&& t, const BBox&); + void insert(T&& t, const BCircle&); + std::vector<T> query(const BBox&) const; + std::vector<std::pair<T,BBox>> queryWithBoxes(const BBox&) const; + + bool hitTest(const BBox&) const; + bool hitTest(const BCircle&) const; + + bool empty() const; private: - int32_t convertToCellCoord(int32_t x) const; - - const int32_t extent; - const int32_t n; - const int32_t padding; - const int32_t d; - const double scale; - const int32_t min; - const int32_t max; - - std::vector<std::pair<T, BBox>> elements; - std::vector<std::vector<size_t>> cells; + bool noIntersection(const BBox& queryBBox) const; + bool completeIntersection(const BBox& queryBBox) const; + BBox convertToBox(const BCircle& circle) const; + + void query(const BBox&, std::function<bool (const T&, const BBox&)>) const; + void query(const BCircle&, std::function<bool (const T&, const BBox&)>) const; + + int16_t convertToXCellCoord(const float x) const; + int16_t convertToYCellCoord(const float y) const; + + bool boxesCollide(const BBox&, const BBox&) const; + bool circlesCollide(const BCircle&, const BCircle&) const; + bool circleAndBoxCollide(const BCircle&, const BBox&) const; + + const float width; + const float height; + + const int16_t xCellCount; + const int16_t yCellCount; + const double xScale; + const double yScale; + + std::vector<std::pair<T, BBox>> boxElements; + std::vector<std::pair<T, BCircle>> circleElements; + + std::vector<std::vector<size_t>> boxCells; + std::vector<std::vector<size_t>> circleCells; }; diff --git a/src/mbgl/util/http_header.cpp b/src/mbgl/util/http_header.cpp index ce31a06c5e..5921edfb14 100644 --- a/src/mbgl/util/http_header.cpp +++ b/src/mbgl/util/http_header.cpp @@ -6,9 +6,12 @@ #pragma GCC diagnostic ignored "-Wunknown-pragmas" #pragma GCC diagnostic ignored "-Wunused-parameter" #pragma GCC diagnostic ignored "-Wshadow" +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wshorten-64-to-32" #include <boost/spirit/include/qi.hpp> #include <boost/spirit/include/phoenix_core.hpp> #include <boost/spirit/include/phoenix_operator.hpp> +#pragma clang diagnostic pop #pragma GCC diagnostic pop namespace mbgl { diff --git a/src/mbgl/util/http_timeout.cpp b/src/mbgl/util/http_timeout.cpp index 3456369250..04842e48be 100644 --- a/src/mbgl/util/http_timeout.cpp +++ b/src/mbgl/util/http_timeout.cpp @@ -1,6 +1,7 @@ #include <mbgl/util/http_timeout.hpp> #include <mbgl/util/constants.hpp> +#include <algorithm> #include <cassert> namespace mbgl { diff --git a/src/mbgl/util/mapbox.cpp b/src/mbgl/util/mapbox.cpp index 8cbc85d492..cdd51a293d 100644 --- a/src/mbgl/util/mapbox.cpp +++ b/src/mbgl/util/mapbox.cpp @@ -114,7 +114,7 @@ std::string normalizeTileURL(const std::string& baseURL, } std::string -canonicalizeTileURL(const std::string& str, const SourceType type, const uint16_t tileSize) { +canonicalizeTileURL(const std::string& str, const style::SourceType type, const uint16_t tileSize) { const char* version = "/v4/"; const size_t versionLen = strlen(version); @@ -133,7 +133,7 @@ canonicalizeTileURL(const std::string& str, const SourceType type, const uint16_ std::string result = "mapbox://tiles/"; result.append(str, path.directory.first + versionLen, path.directory.second - versionLen); result.append(str, path.filename.first, path.filename.second); - if (type == SourceType::Raster) { + if (type == style::SourceType::Raster || type == style::SourceType::RasterDEM) { result += tileSize == util::tileSize ? "@2x" : "{ratio}"; } @@ -171,7 +171,7 @@ canonicalizeTileURL(const std::string& str, const SourceType type, const uint16_ return result; } -void canonicalizeTileset(Tileset& tileset, const std::string& sourceURL, SourceType type, uint16_t tileSize) { +void canonicalizeTileset(Tileset& tileset, const std::string& sourceURL, style::SourceType type, uint16_t tileSize) { // TODO: Remove this hack by delivering proper URLs in the TileJSON to begin with. if (isMapboxURL(sourceURL)) { for (auto& url : tileset.tiles) { diff --git a/src/mbgl/util/mapbox.hpp b/src/mbgl/util/mapbox.hpp index f3dfdd0b01..aa128f2667 100644 --- a/src/mbgl/util/mapbox.hpp +++ b/src/mbgl/util/mapbox.hpp @@ -19,10 +19,10 @@ std::string normalizeGlyphsURL(const std::string& baseURL, const std::string& ur std::string normalizeTileURL(const std::string& baseURL, const std::string& url, const std::string& accessToken); // Return a "mapbox://tiles/..." URL (suitable for normalizeTileURL) for the given Mapbox tile URL. -std::string canonicalizeTileURL(const std::string& url, SourceType, uint16_t tileSize); +std::string canonicalizeTileURL(const std::string& url, style::SourceType, uint16_t tileSize); // Replace URL templates with "mapbox://tiles/..." URLs (suitable for normalizeTileURL). -void canonicalizeTileset(Tileset&, const std::string& url, SourceType, uint16_t tileSize); +void canonicalizeTileset(Tileset&, const std::string& url, style::SourceType, uint16_t tileSize); extern const uint64_t DEFAULT_OFFLINE_TILE_COUNT_LIMIT; diff --git a/src/mbgl/util/tile_cover.cpp b/src/mbgl/util/tile_cover.cpp index a5a1b1d70c..39b562d811 100644 --- a/src/mbgl/util/tile_cover.cpp +++ b/src/mbgl/util/tile_cover.cpp @@ -126,9 +126,9 @@ std::vector<UnwrappedTileID> tileCover(const Point<double>& tl, } // namespace -int32_t coveringZoomLevel(double zoom, SourceType type, uint16_t size) { +int32_t coveringZoomLevel(double zoom, style::SourceType type, uint16_t size) { zoom += std::log(util::tileSize / size) / std::log(2); - if (type == SourceType::Raster || type == SourceType::Video) { + if (type == style::SourceType::Raster || type == style::SourceType::Video) { return ::round(zoom); } else { return std::floor(zoom); @@ -182,10 +182,10 @@ uint64_t tileCount(const LatLngBounds& bounds, uint8_t zoom, uint16_t tileSize_) auto y1 = floor(sw.y/ tileSize_); auto y2 = floor((ne.y - 1) / tileSize_); - auto minX = std::fmax(std::min(x1, x2), 0); + auto minX = ::fmax(std::min(x1, x2), 0); auto maxX = std::max(x1, x2); auto minY = (std::pow(2, zoom) - 1) - std::max(y1, y2); - auto maxY = (std::pow(2, zoom) - 1) - std::fmax(std::min(y1, y2), 0); + auto maxY = (std::pow(2, zoom) - 1) - ::fmax(std::min(y1, y2), 0); return (maxX - minX + 1) * (maxY - minY + 1); } diff --git a/src/mbgl/util/tile_cover.hpp b/src/mbgl/util/tile_cover.hpp index 3c7a4ee44a..b2098b59b8 100644 --- a/src/mbgl/util/tile_cover.hpp +++ b/src/mbgl/util/tile_cover.hpp @@ -13,7 +13,7 @@ class LatLngBounds; namespace util { -int32_t coveringZoomLevel(double z, SourceType type, uint16_t tileSize); +int32_t coveringZoomLevel(double z, style::SourceType type, uint16_t tileSize); std::vector<UnwrappedTileID> tileCover(const TransformState&, int32_t z); std::vector<UnwrappedTileID> tileCover(const LatLngBounds&, int32_t z); diff --git a/src/parsedate/parsedate.c b/src/parsedate/parsedate.c index 46acceed75..7228c4edbc 100644 --- a/src/parsedate/parsedate.c +++ b/src/parsedate/parsedate.c @@ -418,7 +418,7 @@ static time_t my_timegm(struct my_tm *tm) { static const int month_days_cumulative [12] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; - int month, year, leap_days; + int month_, year, leap_days; if(tm->tm_year < 70) /* we don't support years before 1970 as they will cause this function @@ -426,14 +426,14 @@ static time_t my_timegm(struct my_tm *tm) return -1; year = tm->tm_year + 1900; - month = tm->tm_mon; - if(month < 0) { - year += (11 - month) / 12; - month = 11 - (11 - month) % 12; + month_ = tm->tm_mon; + if(month_ < 0) { + year += (11 - month_) / 12; + month_ = 11 - (11 - month_) % 12; } - else if(month >= 12) { - year -= month / 12; - month = month % 12; + else if(month_ >= 12) { + year -= month_ / 12; + month_ = month_ % 12; } leap_days = year - (tm->tm_mon <= 1); @@ -441,7 +441,7 @@ static time_t my_timegm(struct my_tm *tm) - (1969 / 4) + (1969 / 100) - (1969 / 400)); return ((((time_t) (year - 1970) * 365 - + leap_days + month_days_cumulative [month] + tm->tm_mday - 1) * 24 + + leap_days + month_days_cumulative [month_] + tm->tm_mday - 1) * 24 + tm->tm_hour) * 60 + tm->tm_min) * 60 + tm->tm_sec; } |