From b44302af2aac3aa8ad7ee13be7c44fd1334cc81c Mon Sep 17 00:00:00 2001 From: "Thiago Marcos P. Santos" Date: Mon, 27 Nov 2017 17:24:25 +0200 Subject: Bump Mapbox GL Native mapbox-gl-native @ cf3357ea4517e74ba3a63434c330a1506064b130 --- deps/any/8fef1e9/include/linb/any.hpp | 458 --------------------- include/mbgl/map/mode.hpp | 3 +- .../conversion/custom_geometry_source_options.hpp | 64 +++ include/mbgl/style/layer.hpp | 4 +- include/mbgl/style/source.hpp | 4 +- .../mbgl/style/sources/custom_geometry_source.hpp | 56 +++ include/mbgl/style/sources/geojson_source.hpp | 2 + include/mbgl/style/types.hpp | 3 +- include/mbgl/tile/tile_id.hpp | 6 +- include/mbgl/util/any.hpp | 10 - include/mbgl/util/unique_any.hpp | 275 +++++++++++++ mapbox-gl-native.pro | 37 +- platform/default/mbgl/map/map_snapshotter.cpp | 2 +- platform/default/mbgl/storage/offline_download.cpp | 2 + platform/qt/include/qmapbox.hpp | 4 + platform/qt/src/qmapboxgl.cpp | 14 +- platform/qt/src/qt_logging.cpp | 12 + qt_attribution.json | 13 - src/mbgl/annotation/render_annotation_source.cpp | 5 +- src/mbgl/annotation/render_annotation_source.hpp | 3 +- src/mbgl/geometry/feature_index.cpp | 28 +- src/mbgl/geometry/feature_index.hpp | 33 +- src/mbgl/gl/context.cpp | 13 +- src/mbgl/gl/context.hpp | 23 +- src/mbgl/gl/index_buffer.hpp | 1 + src/mbgl/layout/symbol_instance.cpp | 33 +- src/mbgl/layout/symbol_instance.hpp | 16 +- src/mbgl/layout/symbol_layout.cpp | 275 ++++++------- src/mbgl/layout/symbol_layout.hpp | 22 +- src/mbgl/layout/symbol_projection.cpp | 156 ++++--- src/mbgl/layout/symbol_projection.hpp | 34 +- src/mbgl/map/map.cpp | 6 +- src/mbgl/programs/attributes.hpp | 2 + src/mbgl/programs/collision_box_program.hpp | 156 ++++++- src/mbgl/programs/programs.hpp | 4 +- src/mbgl/programs/symbol_program.cpp | 12 +- src/mbgl/programs/symbol_program.hpp | 34 +- src/mbgl/programs/uniforms.hpp | 1 + src/mbgl/renderer/buckets/circle_bucket.cpp | 2 +- src/mbgl/renderer/buckets/symbol_bucket.cpp | 167 +++++++- src/mbgl/renderer/buckets/symbol_bucket.hpp | 58 ++- src/mbgl/renderer/frame_history.cpp | 81 ---- src/mbgl/renderer/frame_history.hpp | 41 -- src/mbgl/renderer/layers/render_circle_layer.cpp | 2 +- src/mbgl/renderer/layers/render_symbol_layer.cpp | 82 ++-- src/mbgl/renderer/paint_parameters.cpp | 2 - src/mbgl/renderer/paint_parameters.hpp | 5 +- src/mbgl/renderer/render_layer.hpp | 11 +- src/mbgl/renderer/render_source.cpp | 3 + src/mbgl/renderer/render_source.hpp | 4 +- src/mbgl/renderer/renderer_impl.cpp | 114 +++-- src/mbgl/renderer/renderer_impl.hpp | 13 +- .../sources/render_custom_geometry_source.cpp | 86 ++++ .../sources/render_custom_geometry_source.hpp | 50 +++ .../renderer/sources/render_geojson_source.cpp | 5 +- .../renderer/sources/render_geojson_source.hpp | 3 +- src/mbgl/renderer/sources/render_image_source.cpp | 3 +- src/mbgl/renderer/sources/render_image_source.hpp | 3 +- src/mbgl/renderer/sources/render_raster_source.cpp | 3 +- src/mbgl/renderer/sources/render_raster_source.hpp | 3 +- src/mbgl/renderer/sources/render_vector_source.cpp | 5 +- src/mbgl/renderer/sources/render_vector_source.hpp | 3 +- src/mbgl/renderer/tile_pyramid.cpp | 44 +- src/mbgl/renderer/tile_pyramid.hpp | 3 +- src/mbgl/shaders/collision_box.cpp | 67 +-- src/mbgl/shaders/collision_circle.cpp | 83 ++++ src/mbgl/shaders/collision_circle.hpp | 16 + src/mbgl/shaders/preludes.cpp | 4 + src/mbgl/shaders/symbol_icon.cpp | 24 +- src/mbgl/shaders/symbol_sdf.cpp | 54 +-- src/mbgl/style/custom_tile_loader.cpp | 107 +++++ src/mbgl/style/custom_tile_loader.hpp | 45 ++ src/mbgl/style/sources/custom_geometry_source.cpp | 45 ++ .../style/sources/custom_geometry_source_impl.cpp | 40 ++ .../style/sources/custom_geometry_source_impl.hpp | 29 ++ src/mbgl/style/types.cpp | 1 + src/mbgl/text/collision_feature.cpp | 81 ++-- src/mbgl/text/collision_feature.hpp | 48 ++- src/mbgl/text/collision_index.cpp | 359 ++++++++++++++++ src/mbgl/text/collision_index.hpp | 70 ++++ src/mbgl/text/collision_tile.cpp | 267 ------------ src/mbgl/text/collision_tile.hpp | 71 ---- src/mbgl/text/cross_tile_symbol_index.cpp | 165 ++++++++ src/mbgl/text/cross_tile_symbol_index.hpp | 64 +++ src/mbgl/text/glyph.hpp | 8 +- src/mbgl/text/placement.cpp | 332 +++++++++++++++ src/mbgl/text/placement.hpp | 91 ++++ src/mbgl/text/placement_config.hpp | 33 -- src/mbgl/text/shaping.cpp | 2 +- src/mbgl/tile/custom_geometry_tile.cpp | 81 ++++ src/mbgl/tile/custom_geometry_tile.hpp | 34 ++ src/mbgl/tile/geometry_tile.cpp | 93 +++-- src/mbgl/tile/geometry_tile.hpp | 33 +- src/mbgl/tile/geometry_tile_worker.cpp | 34 +- src/mbgl/tile/geometry_tile_worker.hpp | 11 +- src/mbgl/tile/tile.cpp | 3 +- src/mbgl/tile/tile.hpp | 26 +- src/mbgl/util/grid_index.cpp | 292 +++++++++++-- src/mbgl/util/grid_index.hpp | 96 ++++- 99 files changed, 3705 insertions(+), 1726 deletions(-) delete mode 100644 deps/any/8fef1e9/include/linb/any.hpp create mode 100644 include/mbgl/style/conversion/custom_geometry_source_options.hpp create mode 100644 include/mbgl/style/sources/custom_geometry_source.hpp delete mode 100644 include/mbgl/util/any.hpp create mode 100644 include/mbgl/util/unique_any.hpp create mode 100755 platform/qt/src/qt_logging.cpp delete mode 100644 src/mbgl/renderer/frame_history.cpp delete mode 100644 src/mbgl/renderer/frame_history.hpp create mode 100644 src/mbgl/renderer/sources/render_custom_geometry_source.cpp create mode 100644 src/mbgl/renderer/sources/render_custom_geometry_source.hpp create mode 100644 src/mbgl/shaders/collision_circle.cpp create mode 100644 src/mbgl/shaders/collision_circle.hpp create mode 100644 src/mbgl/style/custom_tile_loader.cpp create mode 100644 src/mbgl/style/custom_tile_loader.hpp create mode 100644 src/mbgl/style/sources/custom_geometry_source.cpp create mode 100644 src/mbgl/style/sources/custom_geometry_source_impl.cpp create mode 100644 src/mbgl/style/sources/custom_geometry_source_impl.hpp create mode 100644 src/mbgl/text/collision_index.cpp create mode 100644 src/mbgl/text/collision_index.hpp delete mode 100644 src/mbgl/text/collision_tile.cpp delete mode 100644 src/mbgl/text/collision_tile.hpp create mode 100644 src/mbgl/text/cross_tile_symbol_index.cpp create mode 100644 src/mbgl/text/cross_tile_symbol_index.hpp create mode 100644 src/mbgl/text/placement.cpp create mode 100644 src/mbgl/text/placement.hpp delete mode 100644 src/mbgl/text/placement_config.hpp create mode 100644 src/mbgl/tile/custom_geometry_tile.cpp create mode 100644 src/mbgl/tile/custom_geometry_tile.hpp diff --git a/deps/any/8fef1e9/include/linb/any.hpp b/deps/any/8fef1e9/include/linb/any.hpp deleted file mode 100644 index c852c1bdd4..0000000000 --- a/deps/any/8fef1e9/include/linb/any.hpp +++ /dev/null @@ -1,458 +0,0 @@ -// -// Implementation of N4562 std::experimental::any (merged into C++17) for C++11 compilers. -// -// See also: -// + http://en.cppreference.com/w/cpp/any -// + http://en.cppreference.com/w/cpp/experimental/any -// + http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4562.html#any -// + https://cplusplus.github.io/LWG/lwg-active.html#2509 -// -// -// Copyright (c) 2016 Denilson das Mercês Amorim -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -#ifndef LINB_ANY_HPP -#define LINB_ANY_HPP -#pragma once -#include -#include -#include - -namespace linb -{ - -class bad_any_cast : public std::bad_cast -{ -public: - const char* what() const noexcept override - { - return "bad any cast"; - } -}; - -class any final -{ -public: - /// Constructs an object of type any with an empty state. - any() : - vtable(nullptr) - { - } - - /// Constructs an object of type any with an equivalent state as other. - any(const any& rhs) : - vtable(rhs.vtable) - { - if(!rhs.empty()) - { - rhs.vtable->copy(rhs.storage, this->storage); - } - } - - /// Constructs an object of type any with a state equivalent to the original state of other. - /// rhs is left in a valid but otherwise unspecified state. - any(any&& rhs) noexcept : - vtable(rhs.vtable) - { - if(!rhs.empty()) - { - rhs.vtable->move(rhs.storage, this->storage); - rhs.vtable = nullptr; - } - } - - /// Same effect as this->clear(). - ~any() - { - this->clear(); - } - - /// Constructs an object of type any that contains an object of type T direct-initialized with std::forward(value). - /// - /// T shall satisfy the CopyConstructible requirements, otherwise the program is ill-formed. - /// This is because an `any` may be copy constructed into another `any` at any time, so a copy should always be allowed. - template::type, any>::value>::type> - any(ValueType&& value) - { - static_assert(std::is_copy_constructible::type>::value, - "T shall satisfy the CopyConstructible requirements."); - this->construct(std::forward(value)); - } - - /// Has the same effect as any(rhs).swap(*this). No effects if an exception is thrown. - any& operator=(const any& rhs) - { - any(rhs).swap(*this); - return *this; - } - - /// Has the same effect as any(std::move(rhs)).swap(*this). - /// - /// The state of *this is equivalent to the original state of rhs and rhs is left in a valid - /// but otherwise unspecified state. - any& operator=(any&& rhs) noexcept - { - any(std::move(rhs)).swap(*this); - return *this; - } - - /// Has the same effect as any(std::forward(value)).swap(*this). No effect if a exception is thrown. - /// - /// T shall satisfy the CopyConstructible requirements, otherwise the program is ill-formed. - /// This is because an `any` may be copy constructed into another `any` at any time, so a copy should always be allowed. - template::type, any>::value>::type> - any& operator=(ValueType&& value) - { - static_assert(std::is_copy_constructible::type>::value, - "T shall satisfy the CopyConstructible requirements."); - any(std::forward(value)).swap(*this); - return *this; - } - - /// If not empty, destroys the contained object. - void clear() noexcept - { - if(!empty()) - { - this->vtable->destroy(storage); - this->vtable = nullptr; - } - } - - /// Returns true if *this has no contained object, otherwise false. - bool empty() const noexcept - { - return this->vtable == nullptr; - } - - /// If *this has a contained object of type T, typeid(T); otherwise typeid(void). - const std::type_info& type() const noexcept - { - return empty()? typeid(void) : this->vtable->type(); - } - - /// Exchange the states of *this and rhs. - void swap(any& rhs) noexcept - { - if(this->vtable != rhs.vtable) - { - any tmp(std::move(rhs)); - - // move from *this to rhs. - rhs.vtable = this->vtable; - if(this->vtable != nullptr) - { - this->vtable->move(this->storage, rhs.storage); - //this->vtable = nullptr; -- uneeded, see below - } - - // move from tmp (previously rhs) to *this. - this->vtable = tmp.vtable; - if(tmp.vtable != nullptr) - { - tmp.vtable->move(tmp.storage, this->storage); - tmp.vtable = nullptr; - } - } - else // same types - { - if(this->vtable != nullptr) - this->vtable->swap(this->storage, rhs.storage); - } - } - -private: // Storage and Virtual Method Table - - union storage_union - { - using stack_storage_t = typename std::aligned_storage<2 * sizeof(void*), std::alignment_of::value>::type; - - void* dynamic; - stack_storage_t stack; // 2 words for e.g. shared_ptr - }; - - /// Base VTable specification. - struct vtable_type - { - // Note: The caller is responssible for doing .vtable = nullptr after destructful operations - // such as destroy() and/or move(). - - /// The type of the object this vtable is for. - const std::type_info& (*type)() noexcept; - - /// Destroys the object in the union. - /// The state of the union after this call is unspecified, caller must ensure not to use src anymore. - void(*destroy)(storage_union&) noexcept; - - /// Copies the **inner** content of the src union into the yet unitialized dest union. - /// As such, both inner objects will have the same state, but on separate memory locations. - void(*copy)(const storage_union& src, storage_union& dest); - - /// Moves the storage from src to the yet unitialized dest union. - /// The state of src after this call is unspecified, caller must ensure not to use src anymore. - void(*move)(storage_union& src, storage_union& dest) noexcept; - - /// Exchanges the storage between lhs and rhs. - void(*swap)(storage_union& lhs, storage_union& rhs) noexcept; - }; - - /// VTable for dynamically allocated storage. - template - struct vtable_dynamic - { - static const std::type_info& type() noexcept - { - return typeid(T); - } - - static void destroy(storage_union& storage) noexcept - { - //assert(reinterpret_cast(storage.dynamic)); - delete reinterpret_cast(storage.dynamic); - } - - static void copy(const storage_union& src, storage_union& dest) - { - dest.dynamic = new T(*reinterpret_cast(src.dynamic)); - } - - static void move(storage_union& src, storage_union& dest) noexcept - { - dest.dynamic = src.dynamic; - src.dynamic = nullptr; - } - - static void swap(storage_union& lhs, storage_union& rhs) noexcept - { - // just exchage the storage pointers. - std::swap(lhs.dynamic, rhs.dynamic); - } - }; - - /// VTable for stack allocated storage. - template - struct vtable_stack - { - static const std::type_info& type() noexcept - { - return typeid(T); - } - - static void destroy(storage_union& storage) noexcept - { - reinterpret_cast(&storage.stack)->~T(); - } - - static void copy(const storage_union& src, storage_union& dest) - { - new (&dest.stack) T(reinterpret_cast(src.stack)); - } - - static void move(storage_union& src, storage_union& dest) noexcept - { - // one of the conditions for using vtable_stack is a nothrow move constructor, - // so this move constructor will never throw a exception. - new (&dest.stack) T(std::move(reinterpret_cast(src.stack))); - destroy(src); - } - - static void swap(storage_union& lhs, storage_union& rhs) noexcept - { - std::swap(reinterpret_cast(lhs.stack), reinterpret_cast(rhs.stack)); - } - }; - - /// Whether the type T must be dynamically allocated or can be stored on the stack. - template - struct requires_allocation : - std::integral_constant::value // N4562 §6.3/3 [any.class] - && sizeof(T) <= sizeof(storage_union::stack) - && std::alignment_of::value <= std::alignment_of::value)> - {}; - - /// Returns the pointer to the vtable of the type T. - template - static vtable_type* vtable_for_type() - { - using VTableType = typename std::conditional::value, vtable_dynamic, vtable_stack>::type; - static vtable_type table = { - VTableType::type, VTableType::destroy, - VTableType::copy, VTableType::move, - VTableType::swap, - }; - return &table; - } - -protected: - template - friend const T* any_cast(const any* operand) noexcept; - template - friend T* any_cast(any* operand) noexcept; - - /// Same effect as is_same(this->type(), t); - bool is_typed(const std::type_info& t) const - { - return is_same(this->type(), t); - } - - /// Checks if two type infos are the same. - /// - /// If ANY_IMPL_FAST_TYPE_INFO_COMPARE is defined, checks only the address of the - /// type infos, otherwise does an actual comparision. Checking addresses is - /// only a valid approach when there's no interaction with outside sources - /// (other shared libraries and such). - static bool is_same(const std::type_info& a, const std::type_info& b) - { -#ifdef ANY_IMPL_FAST_TYPE_INFO_COMPARE - return &a == &b; -#else - return a == b; -#endif - } - - /// Casts (with no type_info checks) the storage pointer as const T*. - template - const T* cast() const noexcept - { - return requires_allocation::type>::value? - reinterpret_cast(storage.dynamic) : - reinterpret_cast(&storage.stack); - } - - /// Casts (with no type_info checks) the storage pointer as T*. - template - T* cast() noexcept - { - return requires_allocation::type>::value? - reinterpret_cast(storage.dynamic) : - reinterpret_cast(&storage.stack); - } - -private: - storage_union storage; // on offset(0) so no padding for align - vtable_type* vtable; - - template - typename std::enable_if::value>::type - do_construct(ValueType&& value) - { - storage.dynamic = new T(std::forward(value)); - } - - template - typename std::enable_if::value>::type - do_construct(ValueType&& value) - { - new (&storage.stack) T(std::forward(value)); - } - - /// Chooses between stack and dynamic allocation for the type decay_t, - /// assigns the correct vtable, and constructs the object on our storage. - template - void construct(ValueType&& value) - { - using T = typename std::decay::type; - - this->vtable = vtable_for_type(); - - do_construct(std::forward(value)); - } -}; - - - -namespace detail -{ - template - inline ValueType any_cast_move_if_true(typename std::remove_reference::type* p, std::true_type) - { - return std::move(*p); - } - - template - inline ValueType any_cast_move_if_true(typename std::remove_reference::type* p, std::false_type) - { - return *p; - } -} - -/// Performs *any_cast>>(&operand), or throws bad_any_cast on failure. -template -inline ValueType any_cast(const any& operand) -{ - auto p = any_cast::type>::type>(&operand); - if(p == nullptr) throw bad_any_cast(); - return *p; -} - -/// Performs *any_cast>(&operand), or throws bad_any_cast on failure. -template -inline ValueType any_cast(any& operand) -{ - auto p = any_cast::type>(&operand); - if(p == nullptr) throw bad_any_cast(); - return *p; -} - -/// -/// If ANY_IMPL_ANYCAST_MOVEABLE is not defined, does as N4562 specifies: -/// Performs *any_cast>(&operand), or throws bad_any_cast on failure. -/// -/// If ANY_IMPL_ANYCAST_MOVEABLE is defined, does as LWG Defect 2509 specifies: -/// If ValueType is MoveConstructible and isn't a lvalue reference, performs -/// std::move(*any_cast>(&operand)), otherwise -/// *any_cast>(&operand). Throws bad_any_cast on failure. -/// -template -inline ValueType any_cast(any&& operand) -{ -#ifdef ANY_IMPL_ANY_CAST_MOVEABLE - // https://cplusplus.github.io/LWG/lwg-active.html#2509 - using can_move = std::integral_constant::value - && !std::is_lvalue_reference::value>; -#else - using can_move = std::false_type; -#endif - - auto p = any_cast::type>(&operand); - if(p == nullptr) throw bad_any_cast(); - return detail::any_cast_move_if_true(p, can_move()); -} - -/// If operand != nullptr && operand->type() == typeid(ValueType), a pointer to the object -/// contained by operand, otherwise nullptr. -template -inline const T* any_cast(const any* operand) noexcept -{ - if(operand == nullptr || !operand->is_typed(typeid(T))) - return nullptr; - else - return operand->cast(); -} - -/// If operand != nullptr && operand->type() == typeid(ValueType), a pointer to the object -/// contained by operand, otherwise nullptr. -template -inline T* any_cast(any* operand) noexcept -{ - if(operand == nullptr || !operand->is_typed(typeid(T))) - return nullptr; - else - return operand->cast(); -} - -} - -namespace std -{ - inline void swap(linb::any& lhs, linb::any& rhs) noexcept - { - lhs.swap(rhs); - } -} - -#endif \ No newline at end of file diff --git a/include/mbgl/map/mode.hpp b/include/mbgl/map/mode.hpp index 256d152e43..4ee289d855 100644 --- a/include/mbgl/map/mode.hpp +++ b/include/mbgl/map/mode.hpp @@ -11,7 +11,8 @@ using EnumType = uint32_t; enum class MapMode : EnumType { Continuous, // continually updating map - Still, // a once-off still image + Static, // a once-off still image of an arbitrary viewport + Tile // a once-off still image of a single tile }; // We can choose to constrain the map both horizontally or vertically, or only diff --git a/include/mbgl/style/conversion/custom_geometry_source_options.hpp b/include/mbgl/style/conversion/custom_geometry_source_options.hpp new file mode 100644 index 0000000000..73b141e799 --- /dev/null +++ b/include/mbgl/style/conversion/custom_geometry_source_options.hpp @@ -0,0 +1,64 @@ +#pragma once + +#include +#include + +namespace mbgl { +namespace style { +namespace conversion { + +template <> +struct Converter { + + template + optional operator()(const V& value, Error& error) const { + CustomGeometrySource::Options options; + + const auto minzoomValue = objectMember(value, "minzoom"); + if (minzoomValue) { + if (toNumber(*minzoomValue)) { + options.zoomRange.min = static_cast(*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.zoomRange.max = static_cast(*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.tileOptions.buffer = static_cast(*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.tileOptions.tolerance = static_cast(*toNumber(*toleranceValue)); + } else { + error = { "GeoJSON source tolerance value must be a number" }; + return {}; + } + } + + return { options }; + } + +}; + +} // namespace conversion +} // namespace style +} // namespace mbgl diff --git a/include/mbgl/style/layer.hpp b/include/mbgl/style/layer.hpp index c6a3c0e735..eb2dbf830b 100644 --- a/include/mbgl/style/layer.hpp +++ b/include/mbgl/style/layer.hpp @@ -1,7 +1,7 @@ #pragma once #include -#include +#include #include #include #include @@ -126,7 +126,7 @@ public: // For use in SDK bindings, which store a reference to a platform-native peer // object here, so that separately-obtained references to this object share // identical platform-native peers. - any peer; + util::unique_any peer; }; } // namespace style diff --git a/include/mbgl/style/source.hpp b/include/mbgl/style/source.hpp index cec9619451..0b6a6c72d9 100644 --- a/include/mbgl/style/source.hpp +++ b/include/mbgl/style/source.hpp @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include @@ -76,7 +76,7 @@ public: // For use in SDK bindings, which store a reference to a platform-native peer // object here, so that separately-obtained references to this object share // identical platform-native peers. - any peer; + util::unique_any peer; }; } // namespace style diff --git a/include/mbgl/style/sources/custom_geometry_source.hpp b/include/mbgl/style/sources/custom_geometry_source.hpp new file mode 100644 index 0000000000..a0b990b44b --- /dev/null +++ b/include/mbgl/style/sources/custom_geometry_source.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace mbgl { + +class OverscaledTileID; +class CanonicalTileID; +template +class Actor; + +namespace style { + +using TileFunction = std::function; + +class CustomTileLoader; + +class CustomGeometrySource : public Source { +public: + struct TileOptions { + double tolerance = 0.375; + uint16_t tileSize = util::tileSize; + uint16_t buffer = 128; + }; + + struct Options { + TileFunction fetchTileFunction; + TileFunction cancelTileFunction; + Range zoomRange = { 0, 18}; + TileOptions tileOptions; + }; +public: + CustomGeometrySource(std::string id, CustomGeometrySource::Options options); + ~CustomGeometrySource() final; + void loadDescription(FileSource&) final; + void setTileData(const CanonicalTileID&, const GeoJSON&); + void invalidateTile(const CanonicalTileID&); + void invalidateRegion(const LatLngBounds&); + // Private implementation + class Impl; + const Impl& impl() const; +private: + std::unique_ptr> loader; +}; + +template <> +inline bool Source::is() const { + return getType() == SourceType::CustomVector; +} + +} // namespace style +} // namespace mbgl diff --git a/include/mbgl/style/sources/geojson_source.hpp b/include/mbgl/style/sources/geojson_source.hpp index 5bdf1ef957..372e7c7a78 100644 --- a/include/mbgl/style/sources/geojson_source.hpp +++ b/include/mbgl/style/sources/geojson_source.hpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace mbgl { @@ -14,6 +15,7 @@ struct GeoJSONOptions { // GeoJSON-VT options uint8_t minzoom = 0; uint8_t maxzoom = 18; + uint16_t tileSize = util::tileSize; uint16_t buffer = 128; double tolerance = 0.375; diff --git a/include/mbgl/style/types.hpp b/include/mbgl/style/types.hpp index 2ed95f08b8..6fe457e181 100644 --- a/include/mbgl/style/types.hpp +++ b/include/mbgl/style/types.hpp @@ -13,7 +13,8 @@ enum class SourceType : uint8_t { GeoJSON, Video, Annotations, - Image + Image, + CustomVector }; enum class VisibilityType : bool { diff --git a/include/mbgl/tile/tile_id.hpp b/include/mbgl/tile/tile_id.hpp index 0457dd3a07..11fb5ce537 100644 --- a/include/mbgl/tile/tile_id.hpp +++ b/include/mbgl/tile/tile_id.hpp @@ -30,9 +30,9 @@ public: CanonicalTileID scaledTo(uint8_t z) const; std::array children() const; - const uint8_t z; - const uint32_t x; - const uint32_t y; + uint8_t z; + uint32_t x; + uint32_t y; }; ::std::ostream& operator<<(::std::ostream& os, const CanonicalTileID& rhs); diff --git a/include/mbgl/util/any.hpp b/include/mbgl/util/any.hpp deleted file mode 100644 index eea64b188a..0000000000 --- a/include/mbgl/util/any.hpp +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#include - -namespace mbgl { - -using linb::any; -using linb::any_cast; - -} // namespace mbgl diff --git a/include/mbgl/util/unique_any.hpp b/include/mbgl/util/unique_any.hpp new file mode 100644 index 0000000000..d488930a03 --- /dev/null +++ b/include/mbgl/util/unique_any.hpp @@ -0,0 +1,275 @@ +#pragma once + +#include +#include +#include +namespace mbgl { +namespace util { + +class bad_any_cast : public std::bad_cast { +public: + const char* what() const noexcept override { + return "bad any_cast<>()"; + } +}; +/** + * A variant of `std::any` for non-copyable types. + * + * Use `unique_any` for non-copyable types (e.g. `std::unique_ptr`) + * or to ensure that no copies are made of copyable types that are + * moved in. + * + * `uniqe_any` differs from `std::any` in that it does not support copy construction + * or copy assignment. It also does not require the contained type to be copy + * constructible. + * + * The `any_cast()` methods work similar to `std::any_cast()` except that + * non-copyable types may only be cast to references. + * + * Example usage: + * unique_any u1(3); + * auto u2 = unique_any(std::move(u1)); // u1 is moved from + * int i = any_cast(u2); + * + * unique_any u2; + * u2 = std::unique_ptr(new int); + * std::unique_ptr iPtr = any_cast>(std::move(u2)); + * + * Inspired by linb::any (https://github.com/thelink2012/any) and the + * libc++ implementation (https://github.com/llvm-mirror/libcxx). + */ +class unique_any final +{ +public: + unique_any() = default; + + //Copy constructor (deleted) + unique_any(const unique_any& rhs) = delete; + + unique_any(unique_any&& rhs) : vtable(rhs.vtable) { + if (vtable) { + vtable->move(std::move(rhs.storage), storage); + } + rhs.vtable = nullptr; + } + + // Constructs with a direct-initilizated object of type ValueType + template , + typename = std::enable_if_t::value> > + unique_any(ValueType&& value) { + create(std::forward(value)); + } + + ~unique_any() { + reset(); + } + + unique_any& operator=(unique_any&& rhs) { + unique_any(std::move(rhs)).swap(*this); + return *this; + } + + template , unique_any>::value> > + unique_any& operator=(ValueType&& rhs) { + unique_any(std::forward(rhs)).swap(*this); + return *this; + } + + void reset() { + if (vtable) { + vtable->destroy(storage); + vtable = nullptr; + } + } + + void swap(unique_any& rhs) { + if (this == &rhs) { + return; + } else { + unique_any tmp(std::move(rhs)); + rhs.vtable = vtable; + if (rhs.vtable) { + rhs.vtable->move(std::move(storage), rhs.storage); + } + vtable = tmp.vtable; + if (vtable) { + vtable->move(std::move(tmp.storage), storage); + } + } + } + + const std::type_info& type() const { + return !has_value()? typeid(void) : vtable->type(); + } + + bool has_value() const { + return vtable != nullptr; + } + +private: + + union Storage { + using StackStorage = std::aligned_storage_t<3*sizeof(void*), std::alignment_of::value>; + Storage() = default; + + void * dynamic { nullptr }; + StackStorage stack; + }; + + template + struct AllocateOnStack : std::integral_constant::value <= std::alignment_of::value + && std::is_nothrow_move_constructible::value> { + }; + + struct VTable { + virtual ~VTable() = default; + virtual void move(Storage&& src, Storage& dest) = 0; + virtual void destroy(Storage&) = 0; + virtual const std::type_info& type() = 0; + }; + + template + struct VTableHeap : public VTable { + void move(Storage&& src, Storage& dest) override { + destroy(dest); + dest.dynamic = src.dynamic; + } + + void destroy(Storage& s) override { + if (s.dynamic) { + delete reinterpret_cast(s.dynamic); + } + s.dynamic = nullptr; + } + + const std::type_info& type() override { + return typeid(ValueType); + } + }; + + template + struct VTableStack : public VTable { + void move(Storage&& src, Storage& dest) override { + auto srcValue = reinterpret_cast(src.stack); + new (static_cast(&dest.stack)) ValueType(std::move(srcValue)); + srcValue.~ValueType(); + } + + void destroy(Storage& s) override { + reinterpret_cast(s.stack).~ValueType(); + } + + const std::type_info& type() override { + return typeid(ValueType); + } + }; + + template + static VTable* vtableForType() { + using VTableType = std::conditional_t::value, VTableStack, VTableHeap >; + static VTableType vtable; + return &vtable; + } + + template + std::enable_if_t::value> + createStorage(ValueType&& value) { + new (static_cast(&storage.stack)) _Vt(std::forward(value)); + } + + template + std::enable_if_t::value> + createStorage(ValueType&& value) { + storage.dynamic = static_cast(new _Vt(std::forward(value))); + } + + template + void create(ValueType&& value) { + using _Vt = std::decay_t; + vtable = vtableForType<_Vt>(); + createStorage(std::forward(value)); + } + + VTable* vtable { nullptr }; + Storage storage; + +protected: + template + friend const ValueType* any_cast(const unique_any* operand) ; + + template + friend ValueType* any_cast(unique_any* operand) ; + + template > + ValueType* cast() + { + return reinterpret_cast( + AllocateOnStack<_Vt>::value ? &storage.stack : storage.dynamic); + } +}; + +template +inline const ValueType* any_cast(const unique_any* any) +{ + return any_cast(const_cast(any)); +} + +template +inline ValueType* any_cast(unique_any* any) +{ + if(any == nullptr || any->type() != typeid(ValueType)) + return nullptr; + else + return any->cast(); +} + +template > +inline ValueType any_cast(const unique_any& any) +{ + static_assert(std::is_constructible::value, + "any_cast type can't construct copy of contained object"); + auto temp = any_cast<_Vt>(&any); + if (temp == nullptr) { + throw bad_any_cast(); + } + return static_cast(*temp); +} + +template > +inline ValueType any_cast(unique_any& any) +{ + static_assert(std::is_constructible::value, + "any_cast type can't construct copy of contained object"); + auto temp = any_cast<_Vt>(&any); + if (temp == nullptr) { + throw bad_any_cast(); + } + return static_cast(*temp); +} + +template > +inline ValueType any_cast(unique_any&& any) +{ + auto temp = any_cast<_Vt>(&any); + if (temp == nullptr) { + throw bad_any_cast(); + } + auto retValue = static_cast(std::move(*temp)); + any.reset(); + return std::move(retValue); +} + +} // namespace util +} // namespace mbgl + +namespace std { + +inline void swap(mbgl::util::unique_any& lhs, mbgl::util::unique_any& rhs) { + lhs.swap(rhs); +} + +} // namespace std diff --git a/mapbox-gl-native.pro b/mapbox-gl-native.pro index bfbf0622d5..08d53e4ae7 100644 --- a/mapbox-gl-native.pro +++ b/mapbox-gl-native.pro @@ -68,6 +68,7 @@ SOURCES += \ platform/qt/src/qmapboxgl_renderer_frontend_p.cpp \ platform/qt/src/qt_geojson.cpp \ platform/qt/src/qt_image.cpp \ + platform/qt/src/qt_logging.cpp \ platform/qt/src/run_loop.cpp \ platform/qt/src/sqlite3.cpp \ platform/qt/src/string_stdlib.cpp \ @@ -129,7 +130,6 @@ SOURCES += \ src/mbgl/renderer/buckets/raster_bucket.cpp \ src/mbgl/renderer/buckets/symbol_bucket.cpp \ src/mbgl/renderer/cross_faded_property_evaluator.cpp \ - src/mbgl/renderer/frame_history.cpp \ src/mbgl/renderer/group_by_layout.cpp \ src/mbgl/renderer/image_atlas.cpp \ src/mbgl/renderer/image_manager.cpp \ @@ -150,6 +150,7 @@ SOURCES += \ src/mbgl/renderer/renderer.cpp \ src/mbgl/renderer/renderer_backend.cpp \ src/mbgl/renderer/renderer_impl.cpp \ + src/mbgl/renderer/sources/render_custom_geometry_source.cpp \ src/mbgl/renderer/sources/render_geojson_source.cpp \ src/mbgl/renderer/sources/render_image_source.cpp \ src/mbgl/renderer/sources/render_raster_source.cpp \ @@ -158,6 +159,7 @@ SOURCES += \ src/mbgl/renderer/tile_pyramid.cpp \ src/mbgl/shaders/circle.cpp \ src/mbgl/shaders/collision_box.cpp \ + src/mbgl/shaders/collision_circle.cpp \ src/mbgl/shaders/debug.cpp \ src/mbgl/shaders/extrusion_texture.cpp \ src/mbgl/shaders/fill.cpp \ @@ -193,6 +195,7 @@ SOURCES += \ src/mbgl/style/conversion/source.cpp \ src/mbgl/style/conversion/tileset.cpp \ src/mbgl/style/conversion/transition_options.cpp \ + src/mbgl/style/custom_tile_loader.cpp \ src/mbgl/style/expression/array_assertion.cpp \ src/mbgl/style/expression/assertion.cpp \ src/mbgl/style/expression/at.cpp \ @@ -249,6 +252,8 @@ SOURCES += \ src/mbgl/style/parser.cpp \ src/mbgl/style/source.cpp \ src/mbgl/style/source_impl.cpp \ + src/mbgl/style/sources/custom_geometry_source.cpp \ + src/mbgl/style/sources/custom_geometry_source_impl.cpp \ src/mbgl/style/sources/geojson_source.cpp \ src/mbgl/style/sources/geojson_source_impl.cpp \ src/mbgl/style/sources/image_source.cpp \ @@ -262,14 +267,17 @@ SOURCES += \ src/mbgl/style/types.cpp \ src/mbgl/text/check_max_angle.cpp \ src/mbgl/text/collision_feature.cpp \ - src/mbgl/text/collision_tile.cpp \ + src/mbgl/text/collision_index.cpp \ + src/mbgl/text/cross_tile_symbol_index.cpp \ src/mbgl/text/get_anchors.cpp \ src/mbgl/text/glyph.cpp \ src/mbgl/text/glyph_atlas.cpp \ src/mbgl/text/glyph_manager.cpp \ src/mbgl/text/glyph_pbf.cpp \ + src/mbgl/text/placement.cpp \ src/mbgl/text/quads.cpp \ src/mbgl/text/shaping.cpp \ + src/mbgl/tile/custom_geometry_tile.cpp \ src/mbgl/tile/geojson_tile.cpp \ src/mbgl/tile/geometry_tile.cpp \ src/mbgl/tile/geometry_tile_data.cpp \ @@ -315,17 +323,16 @@ SOURCES += \ src/mbgl/util/version.cpp \ src/mbgl/util/work_request.cpp \ src/parsedate/parsedate.c \ - platform/default/asset_file_source.cpp \ - platform/default/default_file_source.cpp \ - platform/default/file_source_request.cpp \ - platform/default/local_file_source.cpp \ - platform/default/logging_stderr.cpp \ - platform/default/mbgl/storage/offline.cpp \ - platform/default/mbgl/storage/offline_database.cpp \ - platform/default/mbgl/storage/offline_download.cpp \ - platform/default/mbgl/util/default_thread_pool.cpp \ - platform/default/mbgl/util/shared_thread_pool.cpp \ - platform/default/online_file_source.cpp + platform/default/asset_file_source.cpp \ + platform/default/default_file_source.cpp \ + platform/default/file_source_request.cpp \ + platform/default/local_file_source.cpp \ + platform/default/mbgl/storage/offline.cpp \ + platform/default/mbgl/storage/offline_database.cpp \ + platform/default/mbgl/storage/offline_download.cpp \ + platform/default/mbgl/util/default_thread_pool.cpp \ + platform/default/mbgl/util/shared_thread_pool.cpp \ + platform/default/online_file_source.cpp HEADERS += \ platform/qt/include/qmapbox.hpp \ @@ -341,8 +348,6 @@ HEADERS += \ platform/qt/src/timer_impl.hpp \ INCLUDEPATH += \ - deps/any/8fef1e9 \ - deps/any/8fef1e9/include \ deps/boost/1.62.0 \ deps/boost/1.62.0/include \ deps/cheap-ruler/2.5.3 \ @@ -384,4 +389,4 @@ INCLUDEPATH += \ src QMAKE_CXXFLAGS += \ - -DMBGL_VERSION_REV=\\\"qt-v1.1.1\\\" + -DMBGL_VERSION_REV=\\\"qt-v1.2.0\\\" diff --git a/platform/default/mbgl/map/map_snapshotter.cpp b/platform/default/mbgl/map/map_snapshotter.cpp index 7b4ec5913b..9341c23cfd 100644 --- a/platform/default/mbgl/map/map_snapshotter.cpp +++ b/platform/default/mbgl/map/map_snapshotter.cpp @@ -50,7 +50,7 @@ MapSnapshotter::Impl::Impl(FileSource& fileSource, const optional region, const optional programCacheDir) : frontend(size, pixelRatio, fileSource, scheduler, programCacheDir) - , map(frontend, MapObserver::nullObserver(), size, pixelRatio, fileSource, scheduler, MapMode::Still) { + , map(frontend, MapObserver::nullObserver(), size, pixelRatio, fileSource, scheduler, MapMode::Static) { map.getStyle().loadURL(styleURL); diff --git a/platform/default/mbgl/storage/offline_download.cpp b/platform/default/mbgl/storage/offline_download.cpp index ff61114888..8bb16993a5 100644 --- a/platform/default/mbgl/storage/offline_download.cpp +++ b/platform/default/mbgl/storage/offline_download.cpp @@ -129,6 +129,7 @@ OfflineRegionStatus OfflineDownload::getStatus() const { case SourceType::Video: case SourceType::Annotations: + case SourceType::CustomVector: break; } } @@ -214,6 +215,7 @@ void OfflineDownload::activateDownload() { case SourceType::Video: case SourceType::Annotations: + case SourceType::CustomVector: break; } } diff --git a/platform/qt/include/qmapbox.hpp b/platform/qt/include/qmapbox.hpp index d138f4057b..2bb5d8705c 100644 --- a/platform/qt/include/qmapbox.hpp +++ b/platform/qt/include/qmapbox.hpp @@ -27,6 +27,7 @@ struct Q_DECL_EXPORT Feature { PolygonType }; + /*! Class constructor. */ Feature(Type type_ = PointType, const CoordinatesCollections& geometry_ = CoordinatesCollections(), const QVariantMap& properties_ = QVariantMap(), const QVariant& id_ = QVariant()) : type(type_), geometry(geometry_), properties(properties_), id(id_) {} @@ -45,6 +46,7 @@ struct Q_DECL_EXPORT ShapeAnnotationGeometry { MultiPolygonType }; + /*! Class constructor. */ ShapeAnnotationGeometry(Type type_ = LineStringType, const CoordinatesCollections& geometry_ = CoordinatesCollections()) : type(type_), geometry(geometry_) {} @@ -58,6 +60,7 @@ struct Q_DECL_EXPORT SymbolAnnotation { }; struct Q_DECL_EXPORT LineAnnotation { + /*! Class constructor. */ LineAnnotation(const ShapeAnnotationGeometry& geometry_ = ShapeAnnotationGeometry(), float opacity_ = 1.0f, float width_ = 1.0f, const QColor& color_ = Qt::black) : geometry(geometry_), opacity(opacity_), width(width_), color(color_) {} @@ -69,6 +72,7 @@ struct Q_DECL_EXPORT LineAnnotation { }; struct Q_DECL_EXPORT FillAnnotation { + /*! Class constructor. */ FillAnnotation(const ShapeAnnotationGeometry& geometry_ = ShapeAnnotationGeometry(), float opacity_ = 1.0f, const QColor& color_ = Qt::black, const QVariant& outlineColor_ = QVariant()) : geometry(geometry_), opacity(opacity_), color(color_), outlineColor(outlineColor_) {} diff --git a/platform/qt/src/qmapboxgl.cpp b/platform/qt/src/qmapboxgl.cpp index c9f85adf4c..cc1d88e22f 100644 --- a/platform/qt/src/qmapboxgl.cpp +++ b/platform/qt/src/qmapboxgl.cpp @@ -155,9 +155,11 @@ std::unique_ptr toStyleImage(const QString &id, const QImage reset before each rendering. Use this mode if the intention is to only draw a fullscreen map. - \value SharedGLContext The OpenGL context is shared and the state will be restored - before rendering. This mode is safer when OpenGL calls are performed prior of after - we call QMapboxGL::render for rendering a map. + \value SharedGLContext The OpenGL context is shared and the state will be + marked dirty - which invalidates any previously assumed GL state. The + embedder is responsible for clearing up the viewport prior to calling + QMapboxGL::render. The embedder is also responsible for resetting its own + GL state after QMapboxGL::render has finished, if needed. \sa contextMode() */ @@ -386,7 +388,7 @@ std::function QMapboxGLSettings::resourceTran } /*! - Sets the resource transformation callback. + Sets the resource \a transform callback. When given, resource transformation callback will be used to transform the requested resource URLs before they are requested from internet. This can be @@ -887,7 +889,7 @@ void QMapboxGL::removeAnnotation(QMapbox::AnnotationID id) } /*! - Sets a layout \a property \a value to an existing \a layer. The \a property_ string can be any + Sets a layout \a property_ \a value to an existing \a layer. The \a property_ string can be any as defined by the \l {https://www.mapbox.com/mapbox-gl-style-spec/} {Mapbox style specification} for layout properties. @@ -938,7 +940,7 @@ void QMapboxGL::setLayoutProperty(const QString& layer, const QString& property_ } /*! - Sets a paint \a property_ \a value to an existing \a layer. The \a property string can be any + Sets a paint \a property_ \a value to an existing \a layer. The \a property_ string can be any as defined by the \l {https://www.mapbox.com/mapbox-gl-style-spec/} {Mapbox style specification} for paint properties. diff --git a/platform/qt/src/qt_logging.cpp b/platform/qt/src/qt_logging.cpp new file mode 100755 index 0000000000..acbe9562d0 --- /dev/null +++ b/platform/qt/src/qt_logging.cpp @@ -0,0 +1,12 @@ +#include +#include + +#include + +namespace mbgl { + +void Log::platformRecord(EventSeverity severity, const std::string &msg) { + qWarning() << "[" << Enum::toString(severity) << "] " << QString::fromStdString(msg); +} + +} // namespace mbgl diff --git a/qt_attribution.json b/qt_attribution.json index d17904850a..4a50d4651c 100644 --- a/qt_attribution.json +++ b/qt_attribution.json @@ -49,19 +49,6 @@ "License": "MIT and UIUC (BSD-like)", "Copyright": "Copyright (c) 2009-2014 libc++ developers" }, - { - "Id": "mapboxgl-any", - "Name": "Any", - "QDocModule": "qtlocation", - "QtUsage": "Used in the Mapbox GL plugin of Qt Location.", - - "Path": "deps/any", - "Description": "Implementation of N4562 std::experimental::any for C++11 compilers.", - "Homepage": "https://github.com/thelink2012/any", - "LicenseId": "BSL-1.0", - "License": "Boost Software License 1.0", - "Copyright": "Copyright (c) 2016 Denilson das Mercês Amorim" - }, { "Id": "mapboxgl-boost", "Name": "Boost", diff --git a/src/mbgl/annotation/render_annotation_source.cpp b/src/mbgl/annotation/render_annotation_source.cpp index ba80be0da0..a0b69af8d5 100644 --- a/src/mbgl/annotation/render_annotation_source.cpp +++ b/src/mbgl/annotation/render_annotation_source.cpp @@ -63,8 +63,9 @@ std::unordered_map> RenderAnnotationSource::queryRenderedFeatures(const ScreenLineString& geometry, const TransformState& transformState, const std::vector& 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 RenderAnnotationSource::querySourceFeatures(const SourceQueryOptions&) const { diff --git a/src/mbgl/annotation/render_annotation_source.hpp b/src/mbgl/annotation/render_annotation_source.hpp index 6209c943f1..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& layers, - const RenderedQueryOptions& options) const final; + const RenderedQueryOptions& options, + const CollisionIndex& collisionIndex) const final; std::vector querySourceFeatures(const SourceQueryOptions&) const final; 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 #include #include -#include +#include #include #include #include @@ -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(envelope.min), convertPoint(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& 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 box = mapbox::geometry::envelope(queryGeometry); - std::vector features = grid.query({ box.min - additionalRadius, box.max + additionalRadius }); + std::vector features = grid.query({ convertPoint(box.min - additionalRadius), + convertPoint(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 symbolFeatures = collisionTile->queryRenderedSymbols(queryGeometry, scale); + std::vector 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 #include +#include #include #include @@ -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 CollisionTile*, + const CollisionIndex&, const float additionalQueryRadius) const; static optional translateQueryGeometry( diff --git a/src/mbgl/gl/context.cpp b/src/mbgl/gl/context.cpp index a4f9cead0e..cc5aa014ed 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(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); 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> getBinaryProgram(ProgramID) const; template - VertexBuffer createVertexBuffer(VertexVector&& v, const BufferUsage usage=BufferUsage::StaticDraw) { + VertexBuffer createVertexBuffer(VertexVector&& v, const BufferUsage usage = BufferUsage::StaticDraw) { return VertexBuffer { v.vertexSize(), createVertexBuffer(v.data(), v.byteSize(), usage) @@ -75,11 +75,18 @@ public: } template - IndexBuffer createIndexBuffer(IndexVector&& v) { + IndexBuffer createIndexBuffer(IndexVector&& v, const BufferUsage usage = BufferUsage::StaticDraw) { return IndexBuffer { - createIndexBuffer(v.data(), v.byteSize()) + v.indexSize(), + createIndexBuffer(v.data(), v.byteSize(), usage) }; } + + template + void updateIndexBuffer(IndexBuffer& buffer, IndexVector&& v) { + assert(v.indexSize() == buffer.indexCount); + updateIndexBuffer(buffer.buffer, v.data(), v.byteSize()); + } template Renderbuffer 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 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/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 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 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 textOffset_, const float iconBoxScale, const float iconPadding, - const SymbolPlacementType iconPlacement, const std::array 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 #include + namespace mbgl { class Anchor; @@ -18,7 +19,6 @@ public: optional 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 textOffset, const float iconBoxScale, const float iconPadding, - style::SymbolPlacementType iconPlacement, const std::array 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 iconQuad; CollisionFeature textCollisionFeature; CollisionFeature iconCollisionFeature; @@ -45,6 +47,12 @@ public: std::size_t featureIndex; std::array textOffset; std::array iconOffset; + std::u16string key; + bool isDuplicate; + optional placedTextIndex; + optional placedVerticalTextIndex; + optional 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..fc1ede56ff 100644 --- a/src/mbgl/layout/symbol_layout.cpp +++ b/src/mbgl/layout/symbol_layout.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include #include #include @@ -43,8 +42,8 @@ SymbolLayout::SymbolLayout(const BucketParameters& parameters, std::unique_ptr 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), @@ -179,7 +178,8 @@ 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() == AlignmentType::Map && layout.get() == SymbolPlacementType::Line; @@ -248,7 +248,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 +261,9 @@ void SymbolLayout::addFeature(const std::size_t index, const SymbolFeature& feature, const std::pair& shapedTextOrientations, optional 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 +290,10 @@ void SymbolLayout::addFeature(const std::size_t index, const SymbolPlacementType textPlacement = layout.get() != AlignmentType::Map ? SymbolPlacementType::Point : layout.get(); - const SymbolPlacementType iconPlacement = layout.get() != AlignmentType::Map - ? SymbolPlacementType::Point - : layout.get(); + 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 +314,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 +392,93 @@ bool SymbolLayout::anchorIsTooClose(const std::u16string& text, const float repe return false; } -std::unique_ptr SymbolLayout::place(CollisionTile& collisionTile) { - auto bucket = std::make_unique(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() != AlignmentType::Map - ? SymbolPlacementType::Point - : layout.get(); - const SymbolPlacementType iconPlacement = layout.get() != AlignmentType::Map - ? SymbolPlacementType::Point - : layout.get(); +// 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 CalculateTileDistances(const GeometryCoordinates& line, const Anchor& anchor) { + std::vector tileDistances(line.size()); + if (anchor.segment != -1) { + auto sumForwardLength = util::dist(anchor.point, line[anchor.segment + 1]); + auto sumBackwardLength = util::dist(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(line[i + 1], line[i]); + } + } + for (auto i = anchor.segment; i >= 0; i--) { + tileDistances[i] = sumBackwardLength; + if (i > 0) { + sumBackwardLength += util::dist(line[i - 1], line[i]); + } + } + } + return tileDistances; +} +std::unique_ptr SymbolLayout::place(const bool showCollisionBoxes) { const bool mayOverlap = layout.get() || layout.get() || layout.get() || layout.get(); + + auto bucket = std::make_unique(layout, layerPaintProperties, textSize, iconSize, zoom, sdfIcons, iconsNeedLinear, mayOverlap, std::move(symbolInstances)); - const bool keepUpright = layout.get(); - - // 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() || !hasText; - const bool textWithoutIcon = layout.get() || !hasIcon; - - // Calculate the scales at which the text and icon can be placed without collision. - - float glyphScale = hasText ? - collisionTile.placeFeature(symbolInstance.textCollisionFeature, - layout.get(), layout.get()) : - collisionTile.minScale; - float iconScale = hasIcon ? - collisionTile.placeFeature(symbolInstance.iconCollisionFeature, - layout.get(), layout.get()) : - 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()); - 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 sizeData = bucket->textSizeBinder->getVertexSizeData(feature); + const Range 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()); - if (iconScale < collisionTile.maxScale && symbolInstance.iconQuad) { + if (symbolInstance.iconQuad) { const Range 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()); + 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 +488,17 @@ std::unique_ptr SymbolLayout::place(CollisionTile& collisionTile) } } - if (collisionTile.config.debug) { - addToDebugBuffers(collisionTile, *bucket); + if (showCollisionBoxes) { + addToDebugBuffers(*bucket); } return bucket; } template -void SymbolLayout::addSymbol(Buffer& buffer, +size_t SymbolLayout::addSymbol(Buffer& buffer, const Range 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 +509,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::max()) { buffer.segments.emplace_back(buffer.vertices.vertexSize(), buffer.triangles.indexSize()); } @@ -548,11 +525,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 +547,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(bucket.collisionCircle) : + static_cast(bucket.collisionBox); for (const CollisionBox &box : feature.boxes) { auto& anchor = box.anchor; - Point tl{box.x1, box.y1 * yStretch}; - Point tr{box.x2, box.y1 * yStretch}; - Point bl{box.x1, box.y2 * yStretch}; - Point 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 tl{box.x1, box.y1}; + Point tr{box.x2, box.y1}; + Point bl{box.x1, box.y2}; + Point 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::max()) { - collisionBox.segments.emplace_back(collisionBox.vertices.vertexSize(), collisionBox.lines.indexSize()); + if (collisionBuffer.segments.empty() || collisionBuffer.segments.back().vertexLength + vertexLength > std::numeric_limits::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 place(CollisionTile&); + std::unique_ptr place(const bool showCollisionBoxes); bool hasSymbolInstances() const; std::map> layerPaintProperties; + const std::string bucketName; + std::vector symbolInstances; + private: void addFeature(const size_t, const SymbolFeature&, const std::pair& shapedTextOrientations, optional shapedIcon, - const GlyphPositionMap&); + const GlyphPositionMap&, + const OverscaledTileID&, + const std::string&); bool anchorIsTooClose(const std::u16string& text, const float repeatDistance, const Anchor&); std::map> compareText; - void addToDebugBuffers(CollisionTile&, SymbolBucket&); + void addToDebugBuffers(SymbolBucket&); // Adds placed items to the buffer. template - void addSymbol(Buffer&, + size_t addSymbol(Buffer&, const Range 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 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 symbolInstances; std::vector 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 279d251f8f..ee6385c93c 100644 --- a/src/mbgl/layout/symbol_projection.cpp +++ b/src/mbgl/layout/symbol_projection.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include @@ -93,9 +92,6 @@ namespace mbgl { return m; } - - typedef std::pair,float> PointAndCameraDistance; - PointAndCameraDistance project(const Point& 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& clippingBuffer, const FrameHistory& frameHistory) { + bool isVisible(const vec4& anchorPos, const std::array& 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& anchorPoint, const float angle, const float placementZoom, + void addDynamicAttributes(const Point& anchorPoint, const float angle, gl::VertexVector& 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& dynamicVertexArray) { const Point offscreenPoint = { -INFINITY, -INFINITY }; for (size_t i = 0; i < numGlyphs; i++) { - addDynamicAttributes(offscreenPoint, 0, 25, dynamicVertexArray); + addDynamicAttributes(offscreenPoint, 0, dynamicVertexArray); } } - struct PlacedGlyph { - PlacedGlyph(Point point_, float angle_) : point(point_), angle(angle_) {} - Point point; - float angle; - }; - enum PlacementResult { OK, NotEnoughRoom, - NeedsFlipping + NeedsFlipping, + UseVertical }; Point projectTruncatedLineSegment(const Point& previousTilePoint, const Point& currentTilePoint, const Point& previousProjectedPoint, const float minimumLength, const mat4& projectionMatrix) { @@ -165,7 +156,7 @@ namespace mbgl { } optional placeGlyphAlongLine(const float offsetX, const float lineOffsetX, const float lineOffsetY, const bool flip, - const Point& projectedAnchorPoint, const Point& tileAnchorPoint, const uint16_t anchorSegment, const GeometryCoordinates& line, const mat4& labelPlaneMatrix) { + const Point& projectedAnchorPoint, const Point& tileAnchorPoint, const uint16_t anchorSegment, const GeometryCoordinates& line, const std::vector& 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 current = projectedAnchorPoint; Point 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(line.size())) return {}; + if (currentIndex < 0 || currentIndex >= static_cast(line.size())) { + return {}; + } prev = current; PointAndCameraDistance projection = project(convertPoint(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() + }}; + } + + optional> placeFirstAndLastGlyph(const float fontScale, + const float lineOffsetX, + const float lineOffsetY, + const bool flip, + const Point& anchorPoint, + const Point& tileAnchorPoint, + const PlacedSymbol& symbol, + const mat4& labelPlaneMatrix, + const bool returnTileDistance) { + + const float firstGlyphOffset = symbol.glyphOffsets.front(); + const float lastGlyphOffset = symbol.glyphOffsets.back();; + + optional firstPlacedGlyph = placeGlyphAlongLine(fontScale * firstGlyphOffset, lineOffsetX, lineOffsetY, flip, anchorPoint, tileAnchorPoint, symbol.segment, symbol.line, symbol.tileDistances, labelPlaneMatrix, returnTileDistance); + if (!firstPlacedGlyph) + return optional>(); + + optional lastPlacedGlyph = placeGlyphAlongLine(fontScale * lastGlyphOffset, lineOffsetX, lineOffsetY, flip, anchorPoint, tileAnchorPoint, symbol.segment, symbol.line, symbol.tileDistances, labelPlaneMatrix, returnTileDistance); + if (!lastPlacedGlyph) + return optional>(); + + return std::make_pair(*firstPlacedGlyph, *lastPlacedGlyph); + } + + optional requiresOrientationChange(const WritingModeType writingModes, const Point& firstPoint, const Point& 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& dynamicVertexArray, - const Point& projectedAnchorPoint) { + const Point& 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 placedGlyphs; if (symbol.glyphOffsets.size() > 1) { - const float firstGlyphOffset = symbol.glyphOffsets.front(); - const float lastGlyphOffset = symbol.glyphOffsets.back(); - - optional firstPlacedGlyph = placeGlyphAlongLine(fontScale * firstGlyphOffset, lineOffsetX, lineOffsetY, flip, projectedAnchorPoint, symbol.anchorPoint, symbol.segment, symbol.line, labelPlaneMatrix); - if (!firstPlacedGlyph) - return PlacementResult::NotEnoughRoom; - - optional lastPlacedGlyph = placeGlyphAlongLine(fontScale * lastGlyphOffset, lineOffsetX, lineOffsetY, flip, projectedAnchorPoint, symbol.anchorPoint, symbol.segment, symbol.line, labelPlaneMatrix); - if (!lastPlacedGlyph) + const optional> firstAndLastGlyph = + placeFirstAndLastGlyph(fontScale, lineOffsetX, lineOffsetY, flip, projectedAnchorPoint, symbol.anchorPoint, symbol, labelPlaneMatrix, false); + if (!firstAndLastGlyph) { return PlacementResult::NotEnoughRoom; + } - const Point firstPoint = project(firstPlacedGlyph->point, glCoordMatrix).first; - const Point lastPoint = project(lastPlacedGlyph->point, glCoordMatrix).first; + const Point firstPoint = project(firstAndLastGlyph->first.point, glCoordMatrix).first; + const Point 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 { // Only a single glyph to place // So, determine whether to flip based on projected angle of the line segment it's on @@ -285,14 +332,15 @@ 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; } } assert(symbol.glyphOffsets.size() == 1); // We are relying on SymbolInstance.hasText filtering out symbols without any glyphs at all const float glyphOffsetX = symbol.glyphOffsets.front(); optional 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; @@ -300,7 +348,7 @@ namespace mbgl { } for (auto& placedGlyph : placedGlyphs) { - addDynamicAttributes(placedGlyph.point, placedGlyph.angle, symbol.placementZoom, dynamicVertexArray); + addDynamicAttributes(placedGlyph.point, placedGlyph.angle, dynamicVertexArray); } return PlacementResult::OK; @@ -309,7 +357,7 @@ namespace mbgl { void reprojectLineLabels(gl::VertexVector& dynamicVertexArray, const std::vector& 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()); @@ -325,19 +373,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 ? @@ -346,11 +406,13 @@ namespace mbgl { const Point 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..8535014f22 100644 --- a/src/mbgl/layout/symbol_projection.hpp +++ b/src/mbgl/layout/symbol_projection.hpp @@ -8,18 +8,48 @@ 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(Point point_, float angle_, optional tileDistance_) + : point(point_), angle(angle_), tileDistance(std::move(tileDistance_)) + {} + Point point; + float angle; + optional 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,float>; + PointAndCameraDistance project(const Point& point, const mat4& matrix); void reprojectLineLabels(gl::VertexVector&, const std::vector&, const mat4& posMatrix, const style::SymbolPropertyValues&, - const RenderTile&, const SymbolSizeBinder& sizeBinder, const TransformState&, const FrameHistory& frameHistory); + const RenderTile&, const SymbolSizeBinder& sizeBinder, const TransformState&); + + optional> placeFirstAndLastGlyph(const float fontScale, + const float lineOffsetX, + const float lineOffsetY, + const bool flip, + const Point& anchorPoint, + const Point& 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 378bd40ab7..5aa534724f 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; } @@ -794,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/programs/attributes.hpp b/src/mbgl/programs/attributes.hpp index d023ec7d83..437ae2195c 100644 --- a/src/mbgl/programs/attributes.hpp +++ b/src/mbgl/programs/attributes.hpp @@ -30,6 +30,8 @@ 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(uint8_t, 1, a_fade_opacity); +MBGL_DEFINE_ATTRIBUTE(uint8_t, 2, a_placed); template struct a_data { diff --git a/src/mbgl/programs/collision_box_program.hpp b/src/mbgl/programs/collision_box_program.hpp index ba99e0c087..8d712a3df3 100644 --- a/src/mbgl/programs/collision_box_program.hpp +++ b/src/mbgl/programs/collision_box_program.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -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>; + attributes::a_extrude>; + +struct CollisionBoxDynamicAttributes : gl::Attributes { + static Vertex vertex(bool placed, bool notUsed) { + return Vertex { + {{ static_cast(placed), static_cast(notUsed) }} + }; + } +}; class CollisionBoxProgram : public Program< shaders::collision_box, gl::Line, - CollisionBoxAttributes, + gl::ConcatenateAttributes, 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 a, Point anchor, Point o, float maxzoom, float placementZoom) { - return LayoutVertex { + static CollisionBoxLayoutAttributes::Vertex vertex(Point a, Point anchor, Point o) { + return CollisionBoxLayoutAttributes::Vertex { {{ static_cast(a.x), static_cast(a.y) @@ -53,13 +51,131 @@ public: {{ static_cast(::round(o.x)), static_cast(::round(o.y)) + }} + }; + } + + template + void draw(gl::Context& context, + DrawMode drawMode, + gl::DepthMode depthMode, + gl::StencilMode stencilMode, + gl::ColorMode colorMode, + const UniformValues& uniformValues, + const gl::VertexBuffer& layoutVertexBuffer, + const gl::VertexBuffer& dynamicVertexBuffer, + const gl::IndexBuffer& indexBuffer, + const SegmentVector& 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, + gl::Uniforms< + uniforms::u_matrix, + uniforms::u_extrude_scale, + uniforms::u_camera_to_center_distance>, + style::Properties<>> +{ +public: + using Program::Program; + + static CollisionBoxLayoutAttributes::Vertex vertex(Point a, Point anchor, Point o) { + return CollisionBoxLayoutAttributes::Vertex { + {{ + static_cast(a.x), + static_cast(a.y) + }}, + {{ + static_cast(anchor.x), + static_cast(anchor.y) }}, {{ - static_cast(maxzoom * 10), - static_cast(placementZoom * 10) + static_cast(::round(o.x)), + static_cast(::round(o.y)) }} }; } + + template + void draw(gl::Context& context, + DrawMode drawMode, + gl::DepthMode depthMode, + gl::StencilMode stencilMode, + gl::ColorMode colorMode, + const UniformValues& uniformValues, + const gl::VertexBuffer& layoutVertexBuffer, + const gl::VertexBuffer& dynamicVertexBuffer, + const gl::IndexBuffer& indexBuffer, + const SegmentVector& 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/programs.hpp b/src/mbgl/programs/programs.hpp index 37ced32745..d769defaaf 100644 --- a/src/mbgl/programs/programs.hpp +++ b/src/mbgl/programs/programs.hpp @@ -32,7 +32,8 @@ public: symbolIconSDF(context, programParameters), symbolGlyph(context, programParameters), debug(context, programParameters), - collisionBox(context, programParameters) { + collisionBox(context, programParameters), + collisionCircle(context, programParameters) { } ProgramMap circle; @@ -53,6 +54,7 @@ public: DebugProgram debug; CollisionBoxProgram collisionBox; + CollisionCircleProgram collisionCircle; }; } // 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 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& pixelsToGLUnits, const bool alongLine, const RenderTile& tile, - const TransformState& state) + const TransformState& state, + const float symbolFadeChange) { return makeValues( isText, @@ -111,7 +112,8 @@ SymbolIconProgram::uniformValues(const bool isText, pixelsToGLUnits, alongLine, tile, - state + state, + symbolFadeChange ); } @@ -124,6 +126,7 @@ typename SymbolSDFProgram::UniformValues SymbolSDFProgram::UniformValues SymbolSDFProgram { - static Vertex vertex(Point anchorPoint, float labelAngle, float labelminzoom) { + static Vertex vertex(Point anchorPoint, float labelAngle) { return Vertex { {{ anchorPoint.x, anchorPoint.y, - static_cast(mbgl::attributes::packUint8Pair( - static_cast(std::fmod(labelAngle + 2 * M_PI, 2 * M_PI) / (2 * M_PI) * 255), - static_cast(labelminzoom * 10))) + labelAngle }} }; } }; + +struct SymbolOpacityAttributes : gl::Attributes { + static Vertex vertex(bool placed, float opacity) { + return Vertex { + {{ static_cast((static_cast(opacity * 127) << 1) | static_cast(placed)) }} + }; + } +}; struct ZoomEvaluatedSize { bool isZoomConstant; @@ -247,7 +253,7 @@ public: using LayoutAttributes = LayoutAttrs; using LayoutVertex = typename LayoutAttributes::Vertex; - using LayoutAndSizeAttributes = gl::ConcatenateAttributes; + using LayoutAndSizeAttributes = gl::ConcatenateAttributes>; using PaintProperties = PaintProps; using PaintPropertyBinders = typename PaintProperties::Binders; @@ -281,6 +287,7 @@ public: const UniformValues& uniformValues, const gl::VertexBuffer& layoutVertexBuffer, const gl::VertexBuffer& dynamicLayoutVertexBuffer, + const gl::VertexBuffer& opacityVertexBuffer, const SymbolSizeBinder& symbolSizeBinder, const gl::IndexBuffer& indexBuffer, const SegmentVector& segments, @@ -294,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); @@ -330,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, @@ -350,7 +360,8 @@ public: const std::array& pixelsToGLUnits, const bool alongLine, const RenderTile&, - const TransformState&); + const TransformState&, + const float symbolFadeChange); }; enum class SymbolSDFPart { @@ -370,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, @@ -394,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, @@ -420,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..184f42e504 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); diff --git a/src/mbgl/renderer/buckets/circle_bucket.cpp b/src/mbgl/renderer/buckets/circle_bucket.cpp index 04126990b3..d23f0861f4 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::max()) { 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& iconSize, float zoom, bool sdfIcons_, - bool iconsNeedLinear_) + bool iconsNeedLinear_, + bool sortFeaturesByY_, + const std::vector&& 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& 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 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..4abea90508 100644 --- a/src/mbgl/renderer/buckets/symbol_bucket.hpp +++ b/src/mbgl/renderer/buckets/symbol_bucket.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include @@ -18,18 +19,22 @@ namespace mbgl { class PlacedSymbol { public: PlacedSymbol(Point anchorPoint_, uint16_t segment_, float lowerSize_, float upperSize_, - std::array lineOffset_, float placementZoom_, bool useVerticalMode_, GeometryCoordinates line_) : + std::array lineOffset_, WritingModeType writingModes_, GeometryCoordinates line_, std::vector 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 anchorPoint; uint16_t segment; float lowerSize; float upperSize; std::array lineOffset; - float placementZoom; - bool useVerticalMode; + WritingModeType writingModes; GeometryCoordinates line; + std::vector tileDistances; std::vector glyphOffsets; + bool hidden; + size_t vertexStartIndex; }; class SymbolBucket : public Bucket { @@ -40,17 +45,33 @@ public: const style::DataDrivenPropertyValue& iconSize, float zoom, bool sdfIcons, - bool iconsNeedLinear); + bool iconsNeedLinear, + bool sortFeaturesByY, + const std::vector&&); 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 sortedAngle; + + bool staticUploaded = false; + bool placementChangesUploaded = false; + bool dynamicUploaded = false; + bool sortUploaded = false; + + std::vector symbolInstances; std::map vertices; gl::VertexVector dynamicVertices; + gl::VertexVector opacityVertices; gl::IndexVector triangles; SegmentVector segments; std::vector placedSymbols; optional> vertexBuffer; optional> dynamicVertexBuffer; + optional> opacityVertexBuffer; optional> indexBuffer; } text; @@ -75,6 +98,7 @@ public: struct IconBuffer { gl::VertexVector vertices; gl::VertexVector dynamicVertices; + gl::VertexVector opacityVertices; gl::IndexVector triangles; SegmentVector segments; std::vector placedSymbols; @@ -82,18 +106,30 @@ public: optional> vertexBuffer; optional> dynamicVertexBuffer; + optional> opacityVertexBuffer; optional> indexBuffer; } icon; - struct CollisionBoxBuffer { - gl::VertexVector vertices; - gl::IndexVector lines; - SegmentVector segments; + struct CollisionBuffer { + gl::VertexVector vertices; + gl::VertexVector dynamicVertices; + SegmentVector segments; - optional> vertexBuffer; - optional> dynamicVertexBuffer; + optional> vertexBuffer; + optional> dynamicVertexBuffer; + }; + + struct CollisionBoxBuffer : public CollisionBuffer { + gl::IndexVector lines; optional> indexBuffer; } collisionBox; + + struct CollisionCircleBuffer : public CollisionBuffer { + gl::IndexVector triangles; + optional> indexBuffer; + } collisionCircle; + + uint32_t bucketInstanceId = 0; }; } // 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 -#include -#include - -#include - -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 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 - -#include -#include -#include -#include -#include - -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 changeTimes; - std::array changeOpacities; - AlphaImage opacities{ { 256, 1 } }; - - int16_t previousZoomIndex = 0; - TimePoint previousTime; - TimePoint time; - bool firstFrame = true; - bool dirty = true; - - mbgl::optional texture; -}; - -} // namespace mbgl diff --git a/src/mbgl/renderer/layers/render_circle_layer.cpp b/src/mbgl/renderer/layers/render_circle_layer.cpp index e7b022f3ee..fe2e7cd42d 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(), diff --git a/src/mbgl/renderer/layers/render_symbol_layer.cpp b/src/mbgl/renderer/layers/render_symbol_layer.cpp index 1376e8a3d8..04fcb2c3ab 100644 --- a/src/mbgl/renderer/layers/render_symbol_layer.cpp +++ b/src/mbgl/renderer/layers/render_symbol_layer.cpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include #include @@ -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 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,41 @@ 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 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(), + CollisionBoxProgram::UniformValues { + uniforms::u_matrix::Value{ tile.matrix }, + uniforms::u_extrude_scale::Value{ extrudeScale }, + 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..58dd5597a5 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), diff --git a/src/mbgl/renderer/paint_parameters.hpp b/src/mbgl/renderer/paint_parameters.hpp index 60f5af4e9a..5c934c2239 100644 --- a/src/mbgl/renderer/paint_parameters.hpp +++ b/src/mbgl/renderer/paint_parameters.hpp @@ -17,7 +17,6 @@ namespace mbgl { class RendererBackend; class UpdateParameters; class RenderStaticData; -class FrameHistory; class Programs; class TransformState; class ImageManager; @@ -33,7 +32,6 @@ public: const UpdateParameters&, const EvaluatedLight&, RenderStaticData&, - FrameHistory&, ImageManager&, LineAtlas&); @@ -44,7 +42,6 @@ public: const EvaluatedLight& evaluatedLight; RenderStaticData& staticData; - FrameHistory& frameHistory; ImageManager& imageManager; LineAtlas& lineAtlas; @@ -74,6 +71,8 @@ public: uint32_t currentLayer; float depthRangeSize; const float depthEpsilon = 1.0f / (1 << 16); + + float symbolFadeChange; }; } // namespace mbgl 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> 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> renderTiles; - }; } // namespace mbgl diff --git a/src/mbgl/renderer/render_source.cpp b/src/mbgl/renderer/render_source.cpp index 7723a1c7ca..6624bb7d96 100644 --- a/src/mbgl/renderer/render_source.cpp +++ b/src/mbgl/renderer/render_source.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include namespace mbgl { @@ -27,6 +28,8 @@ std::unique_ptr RenderSource::create(Immutable impl) return std::make_unique(staticImmutableCast(impl)); case SourceType::Image: return std::make_unique(staticImmutableCast(impl)); + case SourceType::CustomVector: + return std::make_unique(staticImmutableCast(impl)); } // Not reachable, but placate GCC. diff --git a/src/mbgl/renderer/render_source.hpp b/src/mbgl/renderer/render_source.hpp index 8293923ff6..8c84af4f1e 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& layers, - const RenderedQueryOptions& options) const = 0; + const RenderedQueryOptions& options, + const CollisionIndex& collisionIndex) const = 0; virtual std::vector querySourceFeatures(const SourceQueryOptions&) const = 0; diff --git a/src/mbgl/renderer/renderer_impl.cpp b/src/mbgl/renderer/renderer_impl.cpp index 5987e69374..aa138df662 100644 --- a/src/mbgl/renderer/renderer_impl.cpp +++ b/src/mbgl/renderer/renderer_impl.cpp @@ -56,7 +56,8 @@ Renderer::Impl::Impl(RendererBackend& backend_, , imageImpls(makeMutable>>()) , sourceImpls(makeMutable>>()) , layerImpls(makeMutable>>()) - , renderLight(makeMutable()) { + , renderLight(makeMutable()) + , placement(std::make_unique(TransformState{}, MapMode::Static)) { glyphManager->setObserver(this); } @@ -80,7 +81,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; @@ -252,13 +253,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; } @@ -334,11 +334,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> sortedTilesForInsertion; @@ -348,35 +352,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; } } @@ -385,9 +367,47 @@ 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()) { + if (crossTileSymbolIndex.addLayer(*it->layer.as())) symbolBucketsChanged = true; + } + } + + bool placementChanged = false; + if (!placement->stillRecent(parameters.timePoint)) { + auto newPlacement = std::make_unique(parameters.state, parameters.mapMode); + for (auto it = order.rbegin(); it != order.rend(); ++it) { + if (it->layer.is()) { + newPlacement->placeLayer(*it->layer.as(), parameters.projMatrix, parameters.debugOptions & MapDebugOptions::Collision); + } + } + + placementChanged = newPlacement->commit(*placement, parameters.timePoint); + 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()) { + placement->updateLayerOpacities(*it->layer.as()); + } + } + } // - UPLOAD PASS ------------------------------------------------------------------------------- // Uploads all required buffers and images before we do any actual rendering. @@ -396,8 +416,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()) { @@ -607,7 +626,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) { @@ -647,7 +666,7 @@ std::vector Renderer::Impl::queryRenderedFeatures(const ScreenLineStrin std::unordered_map> 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())); } } @@ -727,7 +746,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; } @@ -738,9 +757,30 @@ bool Renderer::Impl::hasTransitions() const { } } + if (placement->hasTransitions(timePoint)) { + return true; + } + + if (fadingTiles) { + return true; + } + return false; } +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 720e01ed53..4f8139791c 100644 --- a/src/mbgl/renderer/renderer_impl.hpp +++ b/src/mbgl/renderer/renderer_impl.hpp @@ -4,13 +4,14 @@ #include #include #include -#include #include #include #include #include #include +#include #include +#include #include #include @@ -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,8 @@ private: void onTileChanged(RenderSource&, const OverscaledTileID&) override; void onTileError(RenderSource&, const OverscaledTileID&, std::exception_ptr) override; + void updateFadingTiles(); + friend class Renderer; RendererBackend& backend; @@ -91,7 +95,6 @@ private: }; RenderState renderState = RenderState::Never; - FrameHistory frameHistory; ZoomHistory zoomHistory; TransformState transformState; @@ -108,7 +111,11 @@ private: std::unordered_map> renderLayers; RenderLight renderLight; + CrossTileSymbolIndex crossTileSymbolIndex; + std::unique_ptr placement; + bool contextLost = false; + bool fadingTiles = false; }; } // 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..111f0234ed --- /dev/null +++ b/src/mbgl/renderer/sources/render_custom_geometry_source.cpp @@ -0,0 +1,86 @@ +#include +#include +#include +#include + +#include +#include + +namespace mbgl { + +using namespace style; + +RenderCustomGeometrySource::RenderCustomGeometrySource(Immutable impl_) + : RenderSource(impl_) { + tilePyramid.setObserver(this); +} + +const style::CustomGeometrySource::Impl& RenderCustomGeometrySource::impl() const { + return static_cast(*baseImpl); +} + +bool RenderCustomGeometrySource::isLoaded() const { + return tilePyramid.isLoaded(); +} + +void RenderCustomGeometrySource::update(Immutable baseImpl_, + const std::vector>& 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(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> RenderCustomGeometrySource::getRenderTiles() { + return tilePyramid.getRenderTiles(); +} + +std::unordered_map> +RenderCustomGeometrySource::queryRenderedFeatures(const ScreenLineString& geometry, + const TransformState& transformState, + const std::vector& layers, + const RenderedQueryOptions& options, + const CollisionIndex& collisionIndex) const { + return tilePyramid.queryRenderedFeatures(geometry, transformState, layers, options, collisionIndex); +} + +std::vector 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 +#include +#include + +namespace mbgl { + +class RenderCustomGeometrySource : public RenderSource { +public: + RenderCustomGeometrySource(Immutable); + + bool isLoaded() const final; + + void update(Immutable, + const std::vector>&, + bool needsRendering, + bool needsRelayout, + const TileParameters&) final; + + void startRender(PaintParameters&) final; + void finishRender(PaintParameters&) final; + + std::vector> getRenderTiles() final; + + std::unordered_map> + queryRenderedFeatures(const ScreenLineString& geometry, + const TransformState& transformState, + const std::vector& layers, + const RenderedQueryOptions& options, + const CollisionIndex& collisionIndex) const final; + + std::vector + 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() 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 504db78ea3..d07cfcdc41 100644 --- a/src/mbgl/renderer/sources/render_geojson_source.cpp +++ b/src/mbgl/renderer/sources/render_geojson_source.cpp @@ -84,8 +84,9 @@ std::unordered_map> RenderGeoJSONSource::queryRenderedFeatures(const ScreenLineString& geometry, const TransformState& transformState, const std::vector& 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 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 b2e06c68d4..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& layers, - const RenderedQueryOptions& options) const final; + const RenderedQueryOptions& options, + const CollisionIndex&) const final; std::vector querySourceFeatures(const SourceQueryOptions&) const final; diff --git a/src/mbgl/renderer/sources/render_image_source.cpp b/src/mbgl/renderer/sources/render_image_source.cpp index 9140e01711..b5c42584e0 100644 --- a/src/mbgl/renderer/sources/render_image_source.cpp +++ b/src/mbgl/renderer/sources/render_image_source.cpp @@ -84,7 +84,8 @@ std::unordered_map> RenderImageSource::queryRenderedFeatures(const ScreenLineString&, const TransformState&, const std::vector&, - const RenderedQueryOptions&) const { + const RenderedQueryOptions&, + const CollisionIndex&) const { return std::unordered_map> {}; } diff --git a/src/mbgl/renderer/sources/render_image_source.hpp b/src/mbgl/renderer/sources/render_image_source.hpp index 8d80838c3b..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& layers, - const RenderedQueryOptions& options) const final; + const RenderedQueryOptions& options, + const CollisionIndex& collisionIndex) const final; std::vector querySourceFeatures(const SourceQueryOptions&) const final; diff --git a/src/mbgl/renderer/sources/render_raster_source.cpp b/src/mbgl/renderer/sources/render_raster_source.cpp index bcd719365d..f11f9b7aed 100644 --- a/src/mbgl/renderer/sources/render_raster_source.cpp +++ b/src/mbgl/renderer/sources/render_raster_source.cpp @@ -74,7 +74,8 @@ std::unordered_map> RenderRasterSource::queryRenderedFeatures(const ScreenLineString&, const TransformState&, const std::vector&, - const RenderedQueryOptions&) const { + const RenderedQueryOptions&, + const CollisionIndex& ) const { return std::unordered_map> {}; } diff --git a/src/mbgl/renderer/sources/render_raster_source.hpp b/src/mbgl/renderer/sources/render_raster_source.hpp index e1bf5798ff..25041fde43 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& layers, - const RenderedQueryOptions& options) const final; + const RenderedQueryOptions& options, + const CollisionIndex& collisionIndex) const final; std::vector querySourceFeatures(const SourceQueryOptions&) const final; diff --git a/src/mbgl/renderer/sources/render_vector_source.cpp b/src/mbgl/renderer/sources/render_vector_source.cpp index ca3071c6b0..49f8fdff2c 100644 --- a/src/mbgl/renderer/sources/render_vector_source.cpp +++ b/src/mbgl/renderer/sources/render_vector_source.cpp @@ -77,8 +77,9 @@ std::unordered_map> RenderVectorSource::queryRenderedFeatures(const ScreenLineString& geometry, const TransformState& transformState, const std::vector& 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 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 ac319a167e..4a992e854f 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& layers, - const RenderedQueryOptions& options) const final; + const RenderedQueryOptions& options, + const CollisionIndex& collisionIndex) const final; std::vector querySourceFeatures(const SourceQueryOptions&) const final; diff --git a/src/mbgl/renderer/tile_pyramid.cpp b/src/mbgl/renderer/tile_pyramid.cpp index 3e2311089d..c1566d12a5 100644 --- a/src/mbgl/renderer/tile_pyramid.cpp +++ b/src/mbgl/renderer/tile_pyramid.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include #include @@ -120,6 +119,7 @@ void TilePyramid::update(const std::vector>& 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 retain; + std::set rendered; auto retainTileFn = [&](Tile& tile, TileNecessity necessity) -> void { if (retain.emplace(tile.id).second) { @@ -148,8 +148,17 @@ void TilePyramid::update(const std::vector>& layer } return tiles.emplace(tileID, std::move(tile)).first->second.get(); }; + + std::map 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(); @@ -161,6 +170,18 @@ void TilePyramid::update(const std::vector>& 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 = @@ -177,6 +198,13 @@ void TilePyramid::update(const std::vector>& layer auto tilesIt = tiles.begin(); auto retainIt = retain.begin(); while (tilesIt != tiles.end()) { + auto renderedIt = rendered.find(tilesIt->first.toUnwrapped()); + if (renderedIt == rendered.end()) { + // Since this tile isn't in the render set, crossTileIDs won't be kept + // updated by CrossTileSymbolIndex. We need to reset the stored crossTileIDs + // so they're not reused if/when this tile is re-added to the render set + tilesIt->second->resetCrossTileIDs(); + } if (retainIt == retain.end() || tilesIt->first < *retainIt) { if (!needsRelayout) { tilesIt->second->setNecessity(TileNecessity::Optional); @@ -193,20 +221,15 @@ void TilePyramid::update(const std::vector>& 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> TilePyramid::queryRenderedFeatures(const ScreenLineString& geometry, const TransformState& transformState, const std::vector& layers, - const RenderedQueryOptions& options) const { + const RenderedQueryOptions& options, + const CollisionIndex& collisionIndex) const { std::unordered_map> result; if (renderTiles.empty() || geometry.empty()) { return result; @@ -249,7 +272,8 @@ std::unordered_map> 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 ac4572b103..feab8a838c 100644 --- a/src/mbgl/renderer/tile_pyramid.hpp +++ b/src/mbgl/renderer/tile_pyramid.hpp @@ -51,7 +51,8 @@ public: queryRenderedFeatures(const ScreenLineString& geometry, const TransformState& transformState, const std::vector&, - const RenderedQueryOptions& options) const; + const RenderedQueryOptions& options, + const CollisionIndex& collisionIndex) const; std::vector querySourceFeatures(const SourceQueryOptions&) const; 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..1e85d99a33 --- /dev/null +++ b/src/mbgl/shaders/collision_circle.cpp @@ -0,0 +1,83 @@ +// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED. + +#include + +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 = 0.5 + 0.5 * (camera_to_anchor_distance / u_camera_to_center_distance); + + 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( + +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 = 3.0; + 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/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/style/custom_tile_loader.cpp b/src/mbgl/style/custom_tile_loader.cpp new file mode 100644 index 0000000000..d5bebf0086 --- /dev/null +++ b/src/mbgl/style/custom_tile_loader.cpp @@ -0,0 +1,107 @@ +#include + +namespace mbgl { +namespace style { + +CustomTileLoader::CustomTileLoader(const TileFunction& fetchTileFn, const TileFunction& cancelTileFn) { + fetchTileFunction = fetchTileFn; + cancelTileFunction = cancelTileFn; +} + +void CustomTileLoader::fetchTile(const OverscaledTileID& tileID, ActorRef callbackRef) { + auto cachedTileData = dataCache.find(tileID.canonical); + if (cachedTileData != dataCache.end()) { + callbackRef.invoke(&SetTileDataFunction::operator(), *(cachedTileData->second)); + } + auto tileCallbacks = tileCallbackMap.find(tileID.canonical); + if (tileCallbacks == tileCallbackMap.end()) { + auto tuple = std::make_tuple(tileID.overscaledZ, tileID.wrap, callbackRef); + tileCallbackMap.insert({ tileID.canonical, std::vector(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) = callbackRef; + return; + } + } + tileCallbacks->second.emplace_back(std::make_tuple(tileID.overscaledZ, tileID.wrap, callbackRef)); + } + 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; + dataCache[tileID] = std::make_unique(std::move(data));; + for (auto tuple : iter->second) { + auto actor = std::get<2>(tuple); + actor.invoke(&SetTileDataFunction::operator(), data); + } +} + +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(&SetTileDataFunction::operator(), mapbox::geojson::feature_collection()); + invokeTileCancel(tileID); + } + tileCallbackMap.erase(tileCallbacks); + dataCache.erase(tileID); +} + +void CustomTileLoader::invalidateRegion(const LatLngBounds& bounds, Range ) { + for (auto idtuple= tileCallbackMap.begin(); idtuple != tileCallbackMap.end(); idtuple++) { + const LatLngBounds tileBounds(idtuple->first); + if (tileBounds.intersects(bounds) || bounds.contains(tileBounds) || tileBounds.contains(bounds)) { + for (auto iter = idtuple->second.begin(); iter != idtuple->second.end(); iter++) { + auto actor = std::get<2>(*iter); + actor.invoke(&SetTileDataFunction::operator(), mapbox::geojson::feature_collection()); + 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..149da69cfa --- /dev/null +++ b/src/mbgl/style/custom_tile_loader.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +namespace mbgl { +namespace style { + +using SetTileDataFunction = std::function; + +class CustomTileLoader : private util::noncopyable { +public: + + using OverscaledIDFunctionTuple = std::tuple>; + + CustomTileLoader(const TileFunction& fetchTileFn, const TileFunction& cancelTileFn); + + void fetchTile(const OverscaledTileID& tileID, ActorRef callbackRef); + 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); + +private: + void invokeTileFetch(const CanonicalTileID& tileID); + void invokeTileCancel(const CanonicalTileID& tileID); + + TileFunction fetchTileFunction; + TileFunction cancelTileFunction; + std::unordered_map> tileCallbackMap; + // Keep around a cache of tile data to serve back for wrapped and over-zooomed tiles + std::map> dataCache; + +}; + +} // 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..ab46843d38 --- /dev/null +++ b/src/mbgl/style/sources/custom_geometry_source.cpp @@ -0,0 +1,45 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mbgl { +namespace style { + +CustomGeometrySource::CustomGeometrySource(std::string id, + const CustomGeometrySource::Options options) + : Source(makeMutable(std::move(id), options)), + loader(std::make_unique>(*sharedThreadPool(), options.fetchTileFunction, options.cancelTileFunction)) { +} + +CustomGeometrySource::~CustomGeometrySource() = default; + +const CustomGeometrySource::Impl& CustomGeometrySource::impl() const { + return static_cast(*baseImpl); +} + +void CustomGeometrySource::loadDescription(FileSource&) { + baseImpl = makeMutable(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 +#include + +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 loaderRef_) + : Source::Impl(impl), + tileOptions(impl.tileOptions), + zoomRange(impl.zoomRange), + loaderRef(loaderRef_){ + +} + +optional CustomGeometrySource::Impl::getAttribution() const { + return {}; +} + +CustomGeometrySource::TileOptions CustomGeometrySource::Impl::getTileOptions() const { + return tileOptions; +} + +Range CustomGeometrySource::Impl::getZoomRange() const { + return zoomRange; +} + +optional> 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 +#include +#include +#include + +namespace mbgl { +namespace style { + +class CustomGeometrySource::Impl : public Source::Impl { +public: + Impl(std::string id, CustomGeometrySource::Options options); + Impl(const Impl&, ActorRef); + + optional getAttribution() const final; + + CustomGeometrySource::TileOptions getTileOptions() const; + Range getZoomRange() const; + optional> getTileLoader() const; + +private: + CustomGeometrySource::TileOptions tileOptions; + Range zoomRange; + optional> loaderRef; +}; + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/types.cpp b/src/mbgl/style/types.cpp index 0a1781e01b..cd5e597fc0 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, { 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(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(util::unit(convertPoint(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{ 0, 0 }, x1, y1, x2, y2, std::numeric_limits::infinity()); + boxes.emplace_back(anchor.point, Point{ 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::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(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(anchorPoint), -boxSize / 2, -boxSize / 2, boxSize / 2, boxSize / 2, paddedAnchorDistance, boxSize / 2); } } -float CollisionBox::adjustedMaxScale(const std::array& 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 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 _anchor, Point _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& rotationMatrix, const float yStretch) const; + CollisionBox(Point _anchor, Point _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 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 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 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..fee28b5873 --- /dev/null +++ b/src/mbgl/text/collision_index.cpp @@ -0,0 +1,359 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include // For PlacedSymbol: pull out to another location + +#include + +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 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 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; + // equivalent to pixel_to_tile_units + const auto pixelsToTileUnits = tileToViewport / 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& polygon, const GridIndex::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(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 CollisionIndex::queryRenderedSymbols(const GeometryCoordinates& queryGeometry, const UnwrappedTileID& tileID, const std::string& sourceID) const { + std::vector 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 projectedQuery; + for (const auto& point : queryGeometry) { + auto projected = projectPoint(posMatrix, convertPoint(point)); + projectedQuery.push_back(projected); + } + + auto envelope = mapbox::geometry::envelope(projectedQuery); + + using QueryResult = std::pair::BBox>; + + std::vector thisTileFeatures; + std::vector 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 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> sourceLayerFeatures; + for (auto& queryResult : thisTileFeatures) { + auto& feature = queryResult.first; + auto& bbox = queryResult.second; + + // Skip already seen features. + auto& seenFeatures = sourceLayerFeatures[feature.sourceLayerName]; + 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 CollisionIndex::projectAnchor(const mat4& posMatrix, const Point& point) const { + vec4 p = {{ point.x, point.y, 0, 1 }}; + matrix::transformMat4(p, p, posMatrix); + return std::make_pair( + 0.5 + 0.5 * (p[3] / transformState.getCameraToCenterDistance()), + p[3] + ); +} + +std::pair,float> CollisionIndex::projectAndGetPerspectiveRatio(const mat4& posMatrix, const Point& point) const { + vec4 p = {{ point.x, point.y, 0, 1 }}; + matrix::transformMat4(p, p, posMatrix); + return std::make_pair( + Point( + (((p[0] / p[3] + 1) / 2) * transformState.getSize().width) + viewportPadding, + (((-p[1] / p[3] + 1) / 2) * transformState.getSize().height) + viewportPadding + ), + 0.5 + 0.5 * (p[3] / transformState.getCameraToCenterDistance()) + ); +} + +Point CollisionIndex::projectPoint(const mat4& posMatrix, const Point& point) const { + vec4 p = {{ point.x, point.y, 0, 1 }}; + matrix::transformMat4(p, p, posMatrix); + return Point( + (((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 +#include +#include +#include + +namespace mbgl { + +class PlacedSymbol; + +struct TileDistance; + +class CollisionIndex { +public: + using CollisionGrid = GridIndex; + + explicit CollisionIndex(const TransformState&); + + std::pair 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 queryRenderedSymbols(const GeometryCoordinates&, const UnwrappedTileID& tileID, const std::string& sourceID) const; + + +private: + bool isOffscreen(const CollisionBox&) const; + bool isInsideGrid(const CollisionBox&) const; + + std::pair 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 projectAnchor(const mat4& posMatrix, const Point& point) const; + std::pair,float> projectAndGetPerspectiveRatio(const mat4& posMatrix, const Point& point) const; + Point projectPoint(const mat4& posMatrix, const Point& 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 -#include -#include -#include -#include -#include -#include - -#include -#include - -#include - -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& anchor, const CollisionBox& box, const float boxMaxScale, const Point& 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::infinity(); - static const std::array edges {{ - // left - CollisionBox(Point(0, 0), { 0, 0 }, 0, -infinity, 0, infinity, infinity), - // right - CollisionBox(Point(util::EXTENT, 0), { 0, 0 }, 0, -infinity, 0, infinity, infinity), - // top - CollisionBox(Point(0, 0), { 0, 0 }, -infinity, 0, infinity, 0, infinity), - // bottom - CollisionBox(Point(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 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 rtl = util::matrixMultiply(reverseRotationMatrix, { box.x1, box.y1 }); - const Point rtr = util::matrixMultiply(reverseRotationMatrix, { box.x2, box.y1 }); - const Point rbl = util::matrixMultiply(reverseRotationMatrix, { box.x1, box.y2 }); - const Point 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 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& 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 CollisionTile::queryRenderedSymbols(const GeometryCoordinates& queryGeometry, float scale) const { - std::vector 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(point)); - polygon.push_back(convertPoint(rotated)); - } - - // Predicate for ruling out already seen features. - std::unordered_map> 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 -#include -#include - -#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 -#include -#include -#include -#pragma GCC diagnostic pop - -namespace mbgl { - -namespace bg = boost::geometry; -namespace bgm = bg::model; -namespace bgi = bg::index; -using CollisionPoint = bgm::point; -using Box = bgm::box; -using CollisionTreeBox = std::tuple; -using Tree = bgi::rtree>; - -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 queryRenderedSymbols(const GeometryCoordinates&, float scale) const; - - const PlacementConfig config; - - float minScale = 0.5f; - float maxScale = 2.0f; - float yStretch; - - std::array rotationMatrix; - std::array reverseRotationMatrix; - -private: - float findPlacementScale( - const Point& anchor, const CollisionBox& box, const float boxMaxScale, - const Point& blockingAnchor, const CollisionBox& blocking); - Box getTreeBox(const Point& 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..177615857f --- /dev/null +++ b/src/mbgl/text/cross_tile_symbol_index.cpp @@ -0,0 +1,165 @@ +#include +#include +#include +#include +#include + +namespace mbgl { + + +TileLayerIndex::TileLayerIndex(OverscaledTileID coord_, std::vector& symbolInstances, uint32_t bucketInstanceId_) + : coord(coord_), bucketInstanceId(bucketInstanceId_) { + for (SymbolInstance& symbolInstance : symbolInstances) { + indexedSymbolInstances[symbolInstance.key].emplace_back(symbolInstance.crossTileID, getScaledCoordinates(symbolInstance, coord)); + } + } + +Point 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(std::floor((childTileCoord.canonical.x * util::EXTENT + symbolInstance.anchor.point.x) * scale)), + static_cast(std::floor((childTileCoord.canonical.y * util::EXTENT + symbolInstance.anchor.point.y) * scale)) + }; +} + +void TileLayerIndex::findMatches(std::vector& symbolInstances, const OverscaledTileID& newCoord) { + 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) { + + symbolInstance.crossTileID = thisTileSymbol.crossTileID; + break; + } + } + } +} + +CrossTileSymbolLayerIndex::CrossTileSymbolLayerIndex() { +} + +void CrossTileSymbolLayerIndex::addBucket(const OverscaledTileID& coord, SymbolBucket& bucket, uint32_t& maxCrossTileID) { + if (bucket.bucketInstanceId) return; + bucket.bucketInstanceId = ++maxBucketInstanceId; + + uint8_t minZoom = 25; + uint8_t maxZoom = 0; + for (auto& it : indexes) { + auto z = it.first; + minZoom = std::min(minZoom, z); + maxZoom = std::max(maxZoom, z); + } + + + // make all higher-res child tiles block duplicate labels in this tile + for (auto z = maxZoom; z > coord.overscaledZ; z--) { + auto zoomIndexes = indexes.find(z); + if (zoomIndexes != indexes.end()) { + for (auto& childIndex : zoomIndexes->second) { + if (!childIndex.second.coord.isChildOf(coord)) { + continue; + } + childIndex.second.findMatches(bucket.symbolInstances, coord); + } + } + if (z == 0) { + break; + } + } + + // make this tile block duplicate labels in lower-res parent tiles + for (auto z = coord.overscaledZ; z >= minZoom; z--) { + auto parentCoord = coord.scaledTo(z); + auto zoomIndexes = indexes.find(z); + if (zoomIndexes != indexes.end()) { + auto parentIndex = zoomIndexes->second.find(parentCoord); + if (parentIndex != zoomIndexes->second.end()) { + parentIndex->second.findMatches(bucket.symbolInstances, coord); + } + } + if (z == 0) { + break; + } + } + + for (auto& symbolInstance : bucket.symbolInstances) { + if (!symbolInstance.crossTileID) { + // symbol did not match any known symbol, assign a new id + symbolInstance.crossTileID = ++maxCrossTileID; + } + } + + indexes[coord.overscaledZ].emplace(coord, TileLayerIndex(coord, bucket.symbolInstances, bucket.bucketInstanceId)); +} + +bool CrossTileSymbolLayerIndex::removeStaleBuckets(const std::unordered_set& currentIDs) { + bool tilesChanged = false; + for (auto& zoomIndexes : indexes) { + for (auto it = zoomIndexes.second.begin(); it != zoomIndexes.second.end();) { + if (!currentIDs.count(it->second.bucketInstanceId)) { + 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 currentBucketIDs; + + for (RenderTile& renderTile : symbolLayer.renderTiles) { + if (!renderTile.tile.isRenderable()) { + continue; + } + + auto bucket = renderTile.tile.getBucket(*symbolLayer.baseImpl); + assert(dynamic_cast(bucket)); + SymbolBucket& symbolBucket = *reinterpret_cast(bucket); + + if (!symbolBucket.bucketInstanceId) { + symbolBucketsChanged = true; + } + layerIndex.addBucket(renderTile.tile.id, symbolBucket, maxCrossTileID); + currentBucketIDs.insert(symbolBucket.bucketInstanceId); + } + + if (layerIndex.removeStaleBuckets(currentBucketIDs)) { + symbolBucketsChanged = true; + } + return symbolBucketsChanged; +} + +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..c67cd37e00 --- /dev/null +++ b/src/mbgl/text/cross_tile_symbol_index.hpp @@ -0,0 +1,64 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace mbgl { + +class SymbolInstance; +class RenderSymbolLayer; +class SymbolBucket; + +class IndexedSymbolInstance { +public: + IndexedSymbolInstance(uint32_t crossTileID_, Point coord_) + : crossTileID(crossTileID_), coord(coord_) + {} + + uint32_t crossTileID; + Point coord; +}; + +class TileLayerIndex { +public: + TileLayerIndex(OverscaledTileID coord, std::vector&, uint32_t bucketInstanceId); + + Point getScaledCoordinates(SymbolInstance&, const OverscaledTileID&); + void findMatches(std::vector&, const OverscaledTileID&); + + OverscaledTileID coord; + uint32_t bucketInstanceId; + std::map> indexedSymbolInstances; +}; + +class CrossTileSymbolLayerIndex { +public: + CrossTileSymbolLayerIndex(); + void addBucket(const OverscaledTileID&, SymbolBucket&, uint32_t& maxCrossTileID); + bool removeStaleBuckets(const std::unordered_set& currentIDs); +private: + std::map> indexes; + uint32_t maxBucketInstanceId = 0; +}; + +class CrossTileSymbolIndex { +public: + CrossTileSymbolIndex(); + + bool addLayer(RenderSymbolLayer&); + + void reset(); +private: + std::map layerIndexes; + uint32_t maxCrossTileID = 0; +}; + +} // namespace mbgl diff --git a/src/mbgl/text/glyph.hpp b/src/mbgl/text/glyph.hpp index 6cccb72ebe..08ff82a20a 100644 --- a/src/mbgl/text/glyph.hpp +++ b/src/mbgl/text/glyph.hpp @@ -75,10 +75,10 @@ class Shaping { explicit Shaping(float x, float y, WritingModeType writingMode_) : top(y), bottom(y), left(x), right(x), writingMode(writingMode_) {} std::vector 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/placement.cpp b/src/mbgl/text/placement.cpp new file mode 100644 index 0000000000..9284e213c2 --- /dev/null +++ b/src/mbgl/text/placement.cpp @@ -0,0 +1,332 @@ +#include +#include +#include +#include +#include +#include +#include + +namespace mbgl { + +OpacityState::OpacityState(bool placed_, bool offscreen) + : opacity((offscreen && placed_) ? 1 : 0) + , placed(placed_) +{ +} + +OpacityState::OpacityState(const OpacityState& prevState, float increment, bool placed_) : + opacity(std::fmax(0, std::fmin(1, prevState.opacity + (prevState.placed ? increment : -increment)))), + placed(placed_) {} + +bool OpacityState::isHidden() const { + return opacity == 0 && !placed; +} + +JointOpacityState::JointOpacityState(bool placedIcon, bool placedText, bool offscreen) : + icon(OpacityState(placedIcon, offscreen)), + text(OpacityState(placedText, offscreen)) {} + +JointOpacityState::JointOpacityState(const JointOpacityState& prevOpacityState, float increment, bool placedIcon, bool placedText) : + 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 seenCrossTileIDs; + + for (RenderTile& renderTile : symbolLayer.renderTiles) { + if (!renderTile.tile.isRenderable()) { + continue; + } + + auto bucket = renderTile.tile.getBucket(*symbolLayer.baseImpl); + assert(dynamic_cast(bucket)); + SymbolBucket& symbolBucket = *reinterpret_cast(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::EXTENT / (util::tileSize * renderTile.tile.id.overscaleFactor()); + + mat4 posMatrix; + state.matrixFor(posMatrix, renderTile.id); + matrix::multiply(posMatrix, projMatrix, posMatrix); + + mat4 textLabelPlaneMatrix = getLabelPlaneMatrix(posMatrix, + layout.get() == style::AlignmentType::Map, + layout.get() == style::AlignmentType::Map, + state, + pixelsToTileUnits); + + mat4 iconLabelPlaneMatrix = getLabelPlaneMatrix(posMatrix, + layout.get() == style::AlignmentType::Map, + layout.get() == 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& 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(); + const bool textWithoutIcon = !bucket.hasIconData() || bucket.layout.get(); + + 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(), + bucket.layout.get() == 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(), + bucket.layout.get() == 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()); + } + + if (placeIcon) { + collisionIndex.insertFeature(symbolInstance.iconCollisionFeature, bucket.layout.get()); + } + + 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)); + seenCrossTileIDs.insert(symbolInstance.crossTileID); + } + } +} + +bool Placement::commit(const Placement& prevPlacement, TimePoint now) { + commitTime = now; + + bool placementChanged = false; + + float increment = mapMode == MapMode::Continuous ? + std::chrono::duration(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.icon, jointPlacement.second.text)); + placementChanged = placementChanged || + jointPlacement.second.icon != prevOpacity->second.icon.placed || + jointPlacement.second.text != prevOpacity->second.text.placed; + } else { + opacities.emplace(jointPlacement.first, JointOpacityState(jointPlacement.second.icon, jointPlacement.second.text, jointPlacement.second.offscreen)); + 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 seenCrossTileIDs; + for (RenderTile& renderTile : symbolLayer.renderTiles) { + if (!renderTile.tile.isRenderable()) { + continue; + } + + auto bucket = renderTile.tile.getBucket(*symbolLayer.baseImpl); + assert(dynamic_cast(bucket)); + SymbolBucket& symbolBucket = *reinterpret_cast(bucket); + updateBucketOpacities(symbolBucket, seenCrossTileIDs); + } +} + +void Placement::updateBucketOpacities(SymbolBucket& bucket, std::set& 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(); + + for (SymbolInstance& symbolInstance : bucket.symbolInstances) { + auto opacityState = seenCrossTileIDs.count(symbolInstance.crossTileID) == 0 ? + getOpacity(symbolInstance.crossTileID) : + JointOpacityState(false, false, false); + + 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()); +} + +JointOpacityState Placement::getOpacity(uint32_t crossTileSymbolID) const { + auto it = opacities.find(crossTileSymbolID); + if (it != opacities.end()) { + return it->second; + } else { + return JointOpacityState(false, false, false); + } + +} + +float Placement::symbolFadeChange(TimePoint now) const { + if (mapMode == MapMode::Continuous) { + return std::chrono::duration(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..bcc20f15a4 --- /dev/null +++ b/src/mbgl/text/placement.hpp @@ -0,0 +1,91 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace mbgl { + +class RenderSymbolLayer; +class SymbolBucket; + +class OpacityState { +public: + OpacityState(bool placed, bool offscreen); + OpacityState(const OpacityState& prevOpacityState, float increment, bool placed); + bool isHidden() const; + float opacity; + bool placed; +}; + +class JointOpacityState { +public: + JointOpacityState(bool placedIcon, bool placedText, bool offscreen); + 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 offscreen_) + : text(text_), icon(icon_), offscreen(offscreen_) + {} + + const bool text; + const bool icon; + // offscreen = 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 offscreen; +}; + +class Placement { +public: + Placement(const TransformState&, MapMode mapMode); + void placeLayer(RenderSymbolLayer&, const mat4&, bool showCollisionBoxes); + bool commit(const Placement& prevPlacement, TimePoint); + void updateLayerOpacities(RenderSymbolLayer&); + JointOpacityState getOpacity(uint32_t crossTileSymbolID) const; + 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& seenCrossTileIDs, + const bool holdingForFade); + + void updateBucketOpacities(SymbolBucket&, std::set&); + + CollisionIndex collisionIndex; + + TransformState state; + MapMode mapMode; + TimePoint commitTime; + + std::unordered_map placements; + std::unordered_map 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 - -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..0d0ff5be61 --- /dev/null +++ b/src/mbgl/tile/custom_geometry_tile.cpp @@ -0,0 +1,81 @@ +#include +#include +#include +#include +#include +#include + +#include + +namespace mbgl { + +CustomGeometryTile::CustomGeometryTile(const OverscaledTileID& overscaledTileID, + std::string sourceID_, + const TileParameters& parameters, + const style::CustomGeometrySource::TileOptions options_, + ActorRef loader_) + : GeometryTile(overscaledTileID, sourceID_, parameters), + necessity(TileNecessity::Optional), + options(options_), + loader(loader_), + actor(*Scheduler::GetCurrent(), std::bind(&CustomGeometryTile::setTileData, this, std::placeholders::_1)) { +} + +CustomGeometryTile::~CustomGeometryTile() { + loader.invoke(&style::CustomTileLoader::removeTile, id); +} + +void CustomGeometryTile::setTileData(const GeoJSON& geoJSON) { + + auto featureData = mapbox::geometry::feature_collection(); + if (geoJSON.is() && !geoJSON.get().empty()) { + const double scale = util::EXTENT / options.tileSize; + + mapbox::geojsonvt::TileOptions vtOptions; + vtOptions.extent = util::EXTENT; + vtOptions.buffer = std::round(scale * options.buffer); + vtOptions.tolerance = scale * options.tolerance; + featureData = mapbox::geojsonvt::geoJSONToTile(geoJSON, id.canonical.z, id.canonical.x, id.canonical.y, vtOptions).features; + } else { + setNecessity(TileNecessity::Optional); + } + setData(std::make_unique(std::move(featureData))); +} + +//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) { + necessity = newNecessity; + if (necessity == TileNecessity::Required) { + loader.invoke(&style::CustomTileLoader::fetchTile, id, actor.self()); + } else if (!isRenderable()) { + loader.invoke(&style::CustomTileLoader::cancelTile, id); + } + } +} + +void CustomGeometryTile::querySourceFeatures( + std::vector& 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..66cc412e8c --- /dev/null +++ b/src/mbgl/tile/custom_geometry_tile.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include + +namespace mbgl { + +class TileParameters; + +class CustomGeometryTile: public GeometryTile { +public: + CustomGeometryTile(const OverscaledTileID&, + std::string sourceID, + const TileParameters&, + const style::CustomGeometrySource::TileOptions, + ActorRef loader); + ~CustomGeometryTile() override; + void setTileData(const GeoJSON& data); + + void setNecessity(TileNecessity) final; + + void querySourceFeatures( + std::vector& result, + const SourceQueryOptions&) override; + +private: + TileNecessity necessity; + const style::CustomGeometrySource::TileOptions options; + ActorRef loader; + Actor actor; +}; + +} // namespace mbgl diff --git a/src/mbgl/tile/geometry_tile.cpp b/src/mbgl/tile/geometry_tile.cpp index 8c018ce3aa..701e7cf2d6 100644 --- a/src/mbgl/tile/geometry_tile.cpp +++ b/src/mbgl/tile/geometry_tile.cpp @@ -15,7 +15,6 @@ #include #include #include -#include #include #include #include @@ -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(*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 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>& 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>& 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(); 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); } @@ -231,7 +218,8 @@ void GeometryTile::queryRenderedFeatures( const GeometryCoordinates& queryGeometry, const TransformState& transformState, const std::vector& layers, - const RenderedQueryOptions& options) { + const RenderedQueryOptions& options, + const CollisionIndex& collisionIndex) { if (!featureIndex || !data) return; @@ -251,9 +239,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 +282,37 @@ 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; +void GeometryTile::resetCrossTileIDs() { + for (auto& bucket : symbolBuckets) { + auto symbolBucket = dynamic_cast(bucket.second.get()); + if (symbolBucket && symbolBucket->bucketInstanceId) { + symbolBucket->bucketInstanceId = 0; + for (auto& symbolInstance : symbolBucket->symbolInstances) { + symbolInstance.crossTileID = 0; + } + } + } +} + +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..6f871819a4 100644 --- a/src/mbgl/tile/geometry_tile.hpp +++ b/src/mbgl/tile/geometry_tile.hpp @@ -4,8 +4,6 @@ #include #include #include -#include -#include #include #include #include @@ -36,9 +34,9 @@ public: void setError(std::exception_ptr); void setData(std::unique_ptr); - void setPlacementConfig(const PlacementConfig&) override; void setLayers(const std::vector>&) 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& layers, - const RenderedQueryOptions& options) override; + const RenderedQueryOptions& options, + const CollisionIndex& collisionIndex) override; void querySourceFeatures( std::vector& result, @@ -82,16 +81,13 @@ public: class PlacementResult { public: std::unordered_map> symbolBuckets; - std::unique_ptr collisionTile; optional glyphAtlasImage; optional iconAtlasImage; PlacementResult(std::unordered_map> symbolBuckets_, - std::unique_ptr collisionTile_, optional glyphAtlasImage_, optional 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; + void resetCrossTileIDs() override; + + bool holdForFade() const override; + void markRenderedIdeal() override; + void markRenderedPreviously() override; + void performedFadePlacement() override; protected: const GeometryTileData* getData() { @@ -108,7 +109,6 @@ protected: private: void markObsolete(); - void invokePlacement(); const std::string sourceID; @@ -122,7 +122,6 @@ private: ImageManager& imageManager; uint64_t correlationID = 0; - optional requestedConfig; std::unordered_map> nonSymbolBuckets; std::unique_ptr featureIndex; @@ -132,11 +131,19 @@ private: optional iconAtlasImage; std::unordered_map> symbolBuckets; - std::unique_ptr collisionTile; - float lastYStretch; const MapMode mode; + + bool showCollisionBoxes; + + enum class FadeState { + Loaded, + NeedsFirstPlacement, + NeedsSecondPlacement, + CanRemove + }; + FadeState fadeState = FadeState::Loaded; public: optional glyphAtlasTexture; optional iconAtlasTexture; diff --git a/src/mbgl/tile/geometry_tile_worker.cpp b/src/mbgl/tile/geometry_tile_worker.cpp index 50429420c3..cf74bf3647 100644 --- a/src/mbgl/tile/geometry_tile_worker.cpp +++ b/src/mbgl/tile/geometry_tile_worker.cpp @@ -1,7 +1,6 @@ #include #include #include -#include #include #include #include @@ -24,20 +23,36 @@ using namespace style; GeometryTileWorker::GeometryTileWorker(ActorRef self_, ActorRef parent_, OverscaledTileID id_, + const std::string& sourceID_, const std::atomic& 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> 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(*placementConfig); std::unordered_map> buckets; for (auto& symbolLayout : symbolLayouts) { @@ -410,7 +425,7 @@ void GeometryTileWorker::attemptPlacement() { continue; } - std::shared_ptr bucket = symbolLayout->place(*collisionTile); + std::shared_ptr bucket = symbolLayout->place(showCollisionBoxes); for (const auto& pair : symbolLayout->layerPaintProperties) { buckets.emplace(pair.first, bucket); } @@ -418,7 +433,6 @@ void GeometryTileWorker::attemptPlacement() { 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..cc86248cec 100644 --- a/src/mbgl/tile/geometry_tile_worker.hpp +++ b/src/mbgl/tile/geometry_tile_worker.hpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include #include @@ -28,14 +27,16 @@ public: GeometryTileWorker(ActorRef self, ActorRef parent, OverscaledTileID, + const std::string&, const std::atomic&, const MapMode, - const float pixelRatio); + const float pixelRatio, + const bool showCollisionBoxes_); ~GeometryTileWorker(); void setLayers(std::vector>, uint64_t correlationID); void setData(std::unique_ptr, 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 parent; const OverscaledTileID id; + const std::string sourceID; const std::atomic& obsolete; const MapMode mode; const float pixelRatio; @@ -75,7 +77,6 @@ private: // Outer optional indicates whether we've received it or not. optional>> layers; optional> data; - optional placementConfig; bool symbolLayoutsNeedPreparation = false; std::vector> symbolLayouts; @@ -83,6 +84,8 @@ private: ImageDependencies pendingImageDependencies; GlyphMap glyphMap; ImageMap imageMap; + + bool showCollisionBoxes; }; } // namespace mbgl diff --git a/src/mbgl/tile/tile.cpp b/src/mbgl/tile/tile.cpp index f36a472e72..85899a98cb 100644 --- a/src/mbgl/tile/tile.cpp +++ b/src/mbgl/tile/tile.cpp @@ -34,7 +34,8 @@ void Tile::queryRenderedFeatures( const GeometryCoordinates&, const TransformState&, const std::vector&, - const RenderedQueryOptions&) {} + const RenderedQueryOptions&, + const CollisionIndex&) {} void Tile::querySourceFeatures( std::vector&, diff --git a/src/mbgl/tile/tile.hpp b/src/mbgl/tile/tile.hpp index 8be7c4d862..1186b74111 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 @@ -47,7 +48,7 @@ public: 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>&) {} virtual void setMask(TileMask&&) {} @@ -56,7 +57,8 @@ public: const GeometryCoordinates& queryGeometry, const TransformState&, const std::vector&, - const RenderedQueryOptions& options); + const RenderedQueryOptions& options, + const CollisionIndex&); virtual void querySourceFeatures( std::vector& result, @@ -92,6 +94,22 @@ 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() {} + + virtual void resetCrossTileIDs() {}; void dumpDebugLogs() const; @@ -101,8 +119,6 @@ public: // Contains the tile ID string for painting debug information. std::unique_ptr debugBucket; - - virtual float yStretch() const { return 1.0f; } protected: bool triedOptional = false; 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 #include +#include namespace mbgl { template -GridIndex::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::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 void GridIndex::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 +void GridIndex::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 std::vector GridIndex::query(const BBox& queryBBox) const { std::vector result; - std::unordered_set seenUids; + query(queryBBox, [&](const T& t, const BBox&) -> bool { + result.push_back(t); + return false; + }); + return result; +} + +template +std::vector::BBox>> GridIndex::queryWithBoxes(const BBox& queryBBox) const { + std::vector> result; + query(queryBBox, [&](const T& t, const BBox& bbox) -> bool { + result.push_back(std::make_pair(t, bbox)); + return false; + }); + return result; +} + +template +bool GridIndex::hitTest(const BBox& queryBBox) const { + bool hit = false; + query(queryBBox, [&](const T&, const BBox&) -> bool { + hit = true; + return true; + }); + return hit; +} + +template +bool GridIndex::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 +bool GridIndex::noIntersection(const BBox& queryBBox) const { + return queryBBox.max.x < 0 || queryBBox.min.x >= width || queryBBox.max.y < 0 || queryBBox.min.y >= height; +} + +template +bool GridIndex::completeIntersection(const BBox& queryBBox) const { + return queryBBox.min.x <= 0 && queryBBox.min.y <= 0 && width <= queryBBox.max.x && height <= queryBBox.max.y; +} + +template +typename GridIndex::BBox GridIndex::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 +void GridIndex::query(const BBox& queryBBox, std::function resultFn) const { + std::unordered_set seenBoxes; + std::unordered_set 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 +void GridIndex::query(const BCircle& queryBCircle, std::function resultFn) const { + std::unordered_set seenBoxes; + std::unordered_set 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 +int16_t GridIndex::convertToXCellCoord(const float x) const { + return util::max(0.0, util::min(xCellCount - 1.0, std::floor(x * xScale))); +} + +template +int16_t GridIndex::convertToYCellCoord(const float y) const { + return util::max(0.0, util::min(yCellCount - 1.0, std::floor(y * yScale))); +} template -int32_t GridIndex::convertToCellCoord(int32_t x) const { - return util::max(0.0, util::min(d - 1.0, std::floor(x * scale) + padding)); +bool GridIndex::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 +bool GridIndex::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 +bool GridIndex::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 +bool GridIndex::empty() const { + return boxElements.empty() && circleElements.empty(); +} + + template class GridIndex; + } // 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 #include #include +#include namespace mbgl { +namespace geometry { + +template +struct circle +{ + using point_type = mapbox::geometry::point; + + constexpr circle(point_type const& center_, T const& radius_) + : center(center_), radius(radius_) + {} + + point_type center; + T radius; +}; + +template +constexpr bool operator==(circle const& lhs, circle const& rhs) +{ + return lhs.center == rhs.center && lhs.radius == rhs.radius; +} + +template +constexpr bool operator!=(circle const& lhs, circle 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 GridIndex { public: - GridIndex(int32_t extent_, int32_t n_, int32_t padding_); - using BBox = mapbox::geometry::box; + GridIndex(const float width_, const float height_, const int16_t cellSize_); + + using BBox = mapbox::geometry::box; + using BCircle = geometry::circle; void insert(T&& t, const BBox&); + void insert(T&& t, const BCircle&); + std::vector query(const BBox&) const; + std::vector> 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> elements; - std::vector> 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) const; + void query(const BCircle&, std::function) 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> boxElements; + std::vector> circleElements; + + std::vector> boxCells; + std::vector> circleCells; }; -- cgit v1.2.1