Merge branch 'master' of github.com:mapbox/mapbox-gl-native into multi-os
Conflicts: .travis.yml
This commit is contained in:
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1,8 +1,6 @@
|
||||
.DS_Store
|
||||
*.o
|
||||
*.xcodeproj
|
||||
*.o
|
||||
/out
|
||||
/mapnik-packaging
|
||||
/macosx/build
|
||||
/ios/build
|
||||
@@ -11,7 +9,6 @@
|
||||
/build
|
||||
/bin/node_modules
|
||||
/include/llmr/shader/shaders.hpp
|
||||
/include/llmr/style/resources.hpp
|
||||
/src/style/resources.cpp
|
||||
/src/shader/shaders_gl.cpp
|
||||
/src/shader/shaders_gles2.cpp
|
||||
/src/shader/shaders_gles2.cpp
|
||||
/bin/style.bin.js
|
||||
|
||||
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -1,3 +1,3 @@
|
||||
[submodule "ios/MVKMapKit"]
|
||||
path = ios/MVKMapKit
|
||||
url = git@github.com:mapbox/MVKMapKit.git
|
||||
[submodule "ios/mapbox-gl-cocoa"]
|
||||
path = ios/mapbox-gl-cocoa
|
||||
url = git@github.com:mapbox/mapbox-gl-cocoa.git
|
||||
|
||||
18
.travis.yml
18
.travis.yml
@@ -20,26 +20,26 @@ cache:
|
||||
- mapnik-packaging
|
||||
|
||||
before_install:
|
||||
- source ./scripts/install_build_deps_${TRAVIS_OS_NAME}.sh
|
||||
- source ./setup-libraries.sh
|
||||
- source ./scripts/setup_travis.sh
|
||||
|
||||
install:
|
||||
- rm -rf mapnik-packaging/out/packages
|
||||
- make setup
|
||||
|
||||
before_script:
|
||||
- if [[ "${TRAVIS_OS_NAME}" == "linux" ]] && [[ ${CXX} == "g++" ]]; then export CXX="g++-4.8" ; fi
|
||||
- rm -rf mapnik-packaging/out/packages
|
||||
|
||||
script:
|
||||
- make llmr BUILDTYPE=${BUILDTYPE}
|
||||
- make clean
|
||||
- make ${TRAVIS_OS_NAME} BUILDTYPE=${BUILDTYPE}
|
||||
- make clean
|
||||
- make linux BUILDTYPE=${BUILDTYPE}
|
||||
- make run-tests BUILDTYPE=${BUILDTYPE}
|
||||
# TODO - port this test to linux
|
||||
- if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then make run-headless-test BUILDTYPE=${BUILDTYPE} ; fi
|
||||
|
||||
notifications:
|
||||
- hipchat: WB52YkcbCGMbNcStsGeaoRO7cyBSK4wX2ZHo2Y4b@GL
|
||||
hipchat:
|
||||
rooms:
|
||||
secure: "ZKtTiFjXgXfrAO8eMU1nJBe46OBLgk0H7VAVmk84QwAb6DuoqT+mGMs+1yrWtjLwZI8vayZyaucupp1siM7lBWCOADx5GXA6GCCLOuE7fYLSNJFrXHa1u70IUBLOU8b5bDWRn8tcd5CM754R609l4ckTzBH3y4CIh9YNFrB0b+g="
|
||||
template:
|
||||
- '%{repository}#%{build_number} (%{branch} - %{commit} : %{author}): %{message}'
|
||||
|
||||
git:
|
||||
submodules: false
|
||||
|
||||
42
LICENSE.md
42
LICENSE.md
@@ -1,26 +1,24 @@
|
||||
Copyright (c), Mapbox
|
||||
mapbox-gl-native copyright (c) 2014, Mapbox.
|
||||
|
||||
All rights reserved.
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
- Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
- Redistributions in binary form must reproduce the above copyright notice, this
|
||||
list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
- Neither the name "Mapbox" nor the names of its contributors may be used to
|
||||
endorse or promote products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
27
Makefile
27
Makefile
@@ -4,7 +4,12 @@ BUILDTYPE ?= Release
|
||||
PYTHON ?= python
|
||||
V ?= 1
|
||||
|
||||
all: llmr
|
||||
all: setup
|
||||
|
||||
setup: config.gypi
|
||||
|
||||
config.gypi:
|
||||
./setup-libraries.sh
|
||||
|
||||
# Builds the regular library
|
||||
llmr: config.gypi llmr.gyp node
|
||||
@@ -28,14 +33,9 @@ run-tests: test
|
||||
$${FILE}; \
|
||||
done
|
||||
|
||||
test/%:
|
||||
test/%: build/test/Makefile
|
||||
$(MAKE) -C build/test BUILDTYPE=$(BUILDTYPE) V=$(V) $*
|
||||
build/$(BUILDTYPE)/test_$*
|
||||
|
||||
# Only runs headless test case
|
||||
run-headless-test: build/test/Makefile
|
||||
$(MAKE) -C build/test BUILDTYPE=$(BUILDTYPE) V=$(V) headless
|
||||
build/$(BUILDTYPE)/test_headless
|
||||
(cd build/$(BUILDTYPE) && ./test_$*)
|
||||
|
||||
|
||||
##### Makefile builds ##########################################################
|
||||
@@ -48,7 +48,7 @@ linux: config.gypi linux/llmr-app.gyp node
|
||||
|
||||
# Executes the Linux binary
|
||||
run-linux: linux
|
||||
build/$(BUILDTYPE)/llmr
|
||||
(cd build/$(BUILDTYPE) && ./mapbox-gl)
|
||||
|
||||
|
||||
|
||||
@@ -95,11 +95,12 @@ lproj: config.gypi linux/llmr-app.gyp clear_xcode_cache node
|
||||
clean: clear_xcode_cache
|
||||
-find ./deps/gyp -name "*.pyc" -exec rm {} \;
|
||||
-rm -rf ./build/
|
||||
-rm -f ./include/llmr/shader/shaders.hpp
|
||||
-rm -f ./include/llmr/style/resources.hpp
|
||||
-rm -f ./src/style/resources.cpp
|
||||
-rm -rf ./config.mk
|
||||
-rm -rf ./config.gypi
|
||||
|
||||
distclean: clean
|
||||
-rm -rf ./build
|
||||
-rm -rf ./config.mk
|
||||
-rm -rf ./config.gypi
|
||||
-rm -rf ./mapnik-packaging/osx/out/
|
||||
|
||||
.PHONY: llmr test linux
|
||||
|
||||
54
README.md
54
README.md
@@ -1,4 +1,4 @@
|
||||
[](https://magnum.travis-ci.com/mapbox/llmr-native)
|
||||
[](https://magnum.travis-ci.com/mapbox/mapbox-gl-native)
|
||||
|
||||
An OpenGL renderer for [Mapbox vector tiles](https://www.mapbox.com/blog/vector-tiles),
|
||||
implemented in C++11, currently targeting iOS, OS X, and Ubuntu Linux.
|
||||
@@ -6,11 +6,13 @@ implemented in C++11, currently targeting iOS, OS X, and Ubuntu Linux.
|
||||
# Depends
|
||||
|
||||
- Modern C++ compiler that supports `-std=c++11`
|
||||
- Boost headers
|
||||
- `libpng`
|
||||
- `libuv`
|
||||
- `libcurl` (depends on OpenSSL; Linux only)
|
||||
- `glfw3`
|
||||
- Boost
|
||||
- `libcurl` (depends on OpenSSL; Linux only)
|
||||
- `libboost_regex` (Linux only)
|
||||
- Homebrew (for build on OS X)
|
||||
- Python (for build only)
|
||||
- Node.js (for build only)
|
||||
|
||||
@@ -19,23 +21,30 @@ implemented in C++11, currently targeting iOS, OS X, and Ubuntu Linux.
|
||||
We use [mapnik-packaging](https://github.com/mapnik/mapnik-packaging) to build static libraries of
|
||||
dependencies.
|
||||
|
||||
First off: if you hit problems during development try:
|
||||
|
||||
make clean
|
||||
|
||||
This will clear cached build data and update to the latest versions of dependencies.
|
||||
|
||||
## OS X
|
||||
|
||||
Run `./setup-libraries.sh`
|
||||
First run:
|
||||
|
||||
This downloads all required dependencies, builds them and creates universal libraries that can be
|
||||
used on both OS X and iOS.
|
||||
make setup
|
||||
|
||||
This downloads all required dependencies, builds them and creates universal libraries that can be used on both OS X and iOS.
|
||||
|
||||
To create projects, you can run:
|
||||
- `make xproj`: Creates an Xcode project with OS X-specific handlers for HTTP downloads and
|
||||
settings storage. It uses GLFW for window handling.
|
||||
settings storage. It uses [GLFW](http://www.glfw.org) for window handling.
|
||||
- `make lproj`: Creates an Xcode project with platform-independent handlers for downloads
|
||||
and settings storage. This is what is also being built on Linux.
|
||||
- `make linux`: Builds the Linux GLFW application with `make`.
|
||||
|
||||
## iOS
|
||||
|
||||
iOS makes use of a Cocoa-specific API currently housed in [MVKMapKit](https://github.com/mapbox/MVKMapKit),
|
||||
iOS makes use of a Cocoa-specific API called [mapbox-gl-cocoa](https://github.com/mapbox/mapbox-gl-cocoa),
|
||||
which is included as a submodule and provides a `UIView` interface to the map view and some bundle resources.
|
||||
|
||||
First, pull down the submodule(s):
|
||||
@@ -43,9 +52,11 @@ First, pull down the submodule(s):
|
||||
git submodule init
|
||||
git submodule update
|
||||
|
||||
Then, because `libpng` isn't included in the iOS SDK, you will need to build a cross-architecture version
|
||||
yourself. Run `./setup-libraries.sh`, which is derived from Mapnik's cross-architecture build
|
||||
scripts. This will also run `./configure`.
|
||||
Then run:
|
||||
|
||||
make setup
|
||||
|
||||
This downloads all required dependencies, builds them and creates universal libraries that can be used on both OS X and iOS.
|
||||
|
||||
Lastly, `make iproj` to create and open an Xcode project with an iOS-specific view controller housing.
|
||||
|
||||
@@ -56,7 +67,7 @@ Target devices: iPhone 4 and above (4S, 5, 5c, 5s) and iPad 2 and above (3, 4, m
|
||||
Ensure you have git and other build essentials:
|
||||
|
||||
sudo apt-get update
|
||||
sudo apt-get install git cmake make pkg-config curl automake libtool xutils-dev
|
||||
sudo apt-get install git build-essential zlib1g-dev automake libtool xutils-dev make cmake pkg-config nodejs-legacy
|
||||
|
||||
Install a `-std=c++11` capable compiler
|
||||
|
||||
@@ -68,21 +79,22 @@ Install glfw3 dependencies:
|
||||
|
||||
sudo apt-get install libxi-dev libglu1-mesa-dev x11proto-randr-dev x11proto-xext-dev libxrandr-dev x11proto-xf86vidmode-dev libxxf86vm-dev libxcursor-dev
|
||||
|
||||
Build static dependencies:
|
||||
Then run:
|
||||
|
||||
./setup-libraries.sh
|
||||
make setup
|
||||
|
||||
This will automatically run configure for you and set the correct paths.
|
||||
This downloads all required dependencies, builds them and creates universal libraries that can be used on both OS X and iOS.
|
||||
|
||||
You can then proceed to build the library, tests, or a GLFW app itself.
|
||||
You can then proceed to build the library like:
|
||||
|
||||
make linux
|
||||
|
||||
# Style
|
||||
|
||||
We're currently embedding the stylesheet JSON in the binary. To create the C++
|
||||
file and the associated header, run
|
||||
The default stylesheet at `bin/style.js` is JSON and is processed into a minified version by the following script:
|
||||
|
||||
```
|
||||
bin/build-style.js
|
||||
node bin/build-style.js
|
||||
```
|
||||
|
||||
This is automatically taken care of as a build phase.
|
||||
@@ -106,3 +118,7 @@ This is automatically taken care of as a build phase.
|
||||
- Two-finger single-tap to zoom out one level
|
||||
- Single-tap to toggle the command palette visibility for resetting north & the transform, toggling debug, toggling styles, and locating the user
|
||||
- Double-tap, long-pressing the second, then pan up and down to "quick zoom" (iPhone only, meant for one-handed use)
|
||||
|
||||
# Other notes
|
||||
|
||||
Under early development, this project was called LLMR (Low-Level Map Renderer), in case you see any lingering references to it.
|
||||
|
||||
@@ -7,36 +7,6 @@ var mkdirp = require('./mkdirp');
|
||||
var name = 'style';
|
||||
var data = JSON.stringify(require(path.join(process.cwd(), process.argv[2])));
|
||||
|
||||
|
||||
var length = data.length;
|
||||
|
||||
var header = '// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED.\n\n';
|
||||
header += '#ifndef LLMR_STYLE_RESOURCES\n';
|
||||
header += '#define LLMR_STYLE_RESOURCES\n';
|
||||
header += '\n';
|
||||
header += 'namespace llmr {\n';
|
||||
header += 'namespace resources {\n';
|
||||
header += '\n';
|
||||
header += 'extern const unsigned char ' + name + '[];\n';
|
||||
header += 'extern const unsigned long ' + name + '_size;\n';
|
||||
header += '\n';
|
||||
header += '}\n';
|
||||
header += '}\n';
|
||||
header += '\n';
|
||||
header += '#endif\n';
|
||||
|
||||
var header_path = path.join(process.argv[3], 'include/llmr/style/resources.hpp');
|
||||
mkdirp.sync(path.dirname(header_path));
|
||||
fs.writeFileSync(header_path, header);
|
||||
|
||||
var code = '// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED.\n';
|
||||
code += '#include <llmr/style/resources.hpp>\n';
|
||||
code += '\n';
|
||||
code += 'using namespace llmr;\n';
|
||||
code += '\n';
|
||||
code += 'const unsigned char resources::' + name + '[] = R"JSON(' + data + ')JSON";\n';
|
||||
code += 'const unsigned long resources::' + name + '_size = sizeof(resources::' + name + ');\n';
|
||||
|
||||
var file_path = path.join(process.argv[3], 'src/style/resources.cpp');
|
||||
mkdirp.sync(path.dirname(file_path));
|
||||
fs.writeFileSync(file_path, code);
|
||||
var out_path = path.join(process.argv[3], 'bin/style.min.js');
|
||||
mkdirp.sync(path.dirname(out_path));
|
||||
fs.writeFileSync(out_path, data);
|
||||
|
||||
752
bin/style.js
752
bin/style.js
File diff suppressed because it is too large
Load Diff
18
common.gypi
18
common.gypi
@@ -5,27 +5,33 @@
|
||||
'CLANG_CXX_LIBRARY': 'libc++',
|
||||
'CLANG_CXX_LANGUAGE_STANDARD':'c++11',
|
||||
'GCC_VERSION': 'com.apple.compilers.llvm.clang.1_0',
|
||||
'GCC_ENABLE_CPP_EXCEPTIONS': 'YES',
|
||||
'GCC_ENABLE_CPP_RTTI':'YES',
|
||||
'OTHER_CPLUSPLUSFLAGS': [ '-Wall', '-Wextra' ],
|
||||
'GCC_WARN_PEDANTIC': 'YES',
|
||||
'GCC_WARN_UNINITIALIZED_AUTOS': 'YES_AGGRESSIVE'
|
||||
},
|
||||
'cflags_cc': ['-std=c++11'],
|
||||
'cflags_cc': ['-std=c++11', '-Wall', '-Wextra'],
|
||||
'configurations': {
|
||||
'Debug': {
|
||||
'cflags': [ '-g', '-O0' ],
|
||||
'cflags_cc': [ '-g', '-O0', '-fno-omit-frame-pointer','-fwrapv', '-fstack-protector-all', '-fno-common' ],
|
||||
'defines': [ 'DEBUG' ],
|
||||
'xcode_settings': {
|
||||
'GCC_OPTIMIZATION_LEVEL': '0',
|
||||
'GCC_GENERATE_DEBUGGING_SYMBOLS': 'YES',
|
||||
'OTHER_CPLUSPLUSFLAGS': [ '-Wall', '-Wextra', '-pedantic' ]
|
||||
'DEAD_CODE_STRIPPING': 'NO',
|
||||
'GCC_INLINES_ARE_PRIVATE_EXTERN': 'NO',
|
||||
'OTHER_CPLUSPLUSFLAGS': [ '-fno-omit-frame-pointer','-fwrapv', '-fstack-protector-all', '-fno-common']
|
||||
}
|
||||
},
|
||||
'Release': {
|
||||
'cflags': [ '-O3' ],
|
||||
'cflags_cc': [ '-O3' ],
|
||||
'defines': [ 'NDEBUG' ],
|
||||
'xcode_settings': {
|
||||
'GCC_OPTIMIZATION_LEVEL': '3',
|
||||
'GCC_GENERATE_DEBUGGING_SYMBOLS': 'NO',
|
||||
'DEAD_CODE_STRIPPING': 'YES',
|
||||
'GCC_INLINES_ARE_PRIVATE_EXTERN': 'YES',
|
||||
'OTHER_CPLUSPLUSFLAGS': [ '-Wall', '-Wextra', '-pedantic' ]
|
||||
'GCC_INLINES_ARE_PRIVATE_EXTERN': 'YES'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ class CURLRequest : public llmr::platform::Request {
|
||||
public:
|
||||
CURLRequest(const std::string &url,
|
||||
std::function<void(llmr::platform::Response *)> callback,
|
||||
uv_loop_t *loop)
|
||||
std::shared_ptr<uv::loop> loop)
|
||||
: Request(url, callback, loop) {}
|
||||
|
||||
CURL *curl = nullptr;
|
||||
@@ -190,32 +190,27 @@ void curl_perform(uv_poll_t *req, int /*status*/, int events) {
|
||||
}
|
||||
|
||||
int handle_socket(CURL * /*easy*/, curl_socket_t s, int action, void * /*userp*/, void *socketp) {
|
||||
curl_context *context;
|
||||
if (action == CURL_POLL_IN || action == CURL_POLL_OUT) {
|
||||
if (socketp) {
|
||||
context = (curl_context *)socketp;
|
||||
} else {
|
||||
context = create_curl_context(s);
|
||||
}
|
||||
curl_multi_assign(curl_multi, s, (void *)context);
|
||||
curl_context *context = nullptr;
|
||||
|
||||
if (socketp) {
|
||||
context = (curl_context *)socketp;
|
||||
} else if (action != CURL_POLL_REMOVE) {
|
||||
context = create_curl_context(s);
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case CURL_POLL_IN:
|
||||
uv_poll_start(&context->poll_handle, UV_READABLE, curl_perform);
|
||||
break;
|
||||
case CURL_POLL_OUT:
|
||||
uv_poll_start(&context->poll_handle, UV_WRITABLE, curl_perform);
|
||||
break;
|
||||
case CURL_POLL_REMOVE:
|
||||
if (socketp) {
|
||||
uv_poll_stop(&((curl_context *)socketp)->poll_handle);
|
||||
destroy_curl_context((curl_context *)socketp);
|
||||
if (context) {
|
||||
curl_multi_assign(curl_multi, s, (void *)context);
|
||||
if (action == CURL_POLL_IN || action == CURL_POLL_INOUT) {
|
||||
uv_poll_start(&context->poll_handle, UV_READABLE, curl_perform);
|
||||
}
|
||||
if (action == CURL_POLL_OUT || action == CURL_POLL_INOUT) {
|
||||
uv_poll_start(&context->poll_handle, UV_WRITABLE, curl_perform);
|
||||
}
|
||||
if (action == CURL_POLL_REMOVE && socketp) {
|
||||
uv_poll_stop(&context->poll_handle);
|
||||
destroy_curl_context(context);
|
||||
curl_multi_assign(curl_multi, s, NULL);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -358,7 +353,7 @@ void thread_init_cb() {
|
||||
std::shared_ptr<platform::Request>
|
||||
platform::request_http(const std::string &url,
|
||||
std::function<void(Response *)> callback,
|
||||
uv_loop_t *loop) {
|
||||
std::shared_ptr<uv::loop> loop) {
|
||||
using namespace request;
|
||||
init_thread_once(thread_init_cb);
|
||||
std::shared_ptr<CURLRequest> req = std::make_shared<CURLRequest>(url, callback, loop);
|
||||
|
||||
@@ -41,7 +41,7 @@ class FoundationRequest : public llmr::platform::Request {
|
||||
public:
|
||||
FoundationRequest(const std::string &url,
|
||||
std::function<void(llmr::platform::Response *)> callback,
|
||||
uv_loop_t *loop)
|
||||
std::shared_ptr<uv::loop> loop)
|
||||
: Request(url, callback, loop) {
|
||||
#if TARGET_OS_IPHONE
|
||||
active_tasks++;
|
||||
@@ -68,7 +68,7 @@ public:
|
||||
std::shared_ptr<llmr::platform::Request>
|
||||
llmr::platform::request_http(const std::string &url,
|
||||
std::function<void(Response *)> callback,
|
||||
uv_loop_t *loop) {
|
||||
std::shared_ptr<uv::loop> loop) {
|
||||
uv_once(&request_initialize, request_initialize_cb);
|
||||
|
||||
std::shared_ptr<FoundationRequest> req =
|
||||
@@ -99,7 +99,7 @@ llmr::platform::request_http(const std::string &url,
|
||||
}
|
||||
|
||||
(*req_ptr)->complete();
|
||||
|
||||
|
||||
delete req_ptr;
|
||||
}];
|
||||
|
||||
|
||||
@@ -73,8 +73,17 @@ void GLFWView::key(GLFWwindow *window, int key, int /*scancode*/, int action, in
|
||||
view->map->resetPosition();
|
||||
break;
|
||||
case GLFW_KEY_R:
|
||||
if (!mods)
|
||||
view->map->toggleRaster();
|
||||
if (!mods) {
|
||||
std::set<std::string> newAppliedClasses;
|
||||
if (view->map->getAppliedClasses().count("night")) {
|
||||
newAppliedClasses.insert("default");
|
||||
} else {
|
||||
newAppliedClasses.insert("default");
|
||||
newAppliedClasses.insert(newAppliedClasses.end(), "night");
|
||||
}
|
||||
view->map->setDefaultTransitionDuration(300);
|
||||
view->map->setAppliedClasses(newAppliedClasses);
|
||||
}
|
||||
break;
|
||||
case GLFW_KEY_N:
|
||||
if (!mods)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define LLMR_GEOMETRY_GLYPH_ATLAS
|
||||
|
||||
#include <llmr/geometry/binpack.hpp>
|
||||
#include <llmr/text/glyph_store.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <set>
|
||||
@@ -11,8 +12,6 @@
|
||||
|
||||
namespace llmr {
|
||||
|
||||
class VectorTileGlyph;
|
||||
|
||||
class GlyphAtlas {
|
||||
public:
|
||||
|
||||
@@ -30,7 +29,7 @@ public:
|
||||
|
||||
|
||||
Rect<uint16_t> addGlyph(uint64_t tile_id, const std::string& face_name,
|
||||
const VectorTileGlyph& glyph);
|
||||
const SDFGlyph& glyph);
|
||||
void removeGlyphs(uint64_t tile_id);
|
||||
void bind();
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ private:
|
||||
BinPack<dimension> bin;
|
||||
std::map<std::string, Rect<dimension>> images;
|
||||
std::set<std::string> uninitialized;
|
||||
char *data = nullptr;
|
||||
uint32_t *data = nullptr;
|
||||
std::atomic<bool> dirty;
|
||||
uint32_t texture = 0;
|
||||
uint32_t filter = 0;
|
||||
|
||||
128
include/llmr/map/filter_expression.hpp
Normal file
128
include/llmr/map/filter_expression.hpp
Normal file
@@ -0,0 +1,128 @@
|
||||
#ifndef LLMR_MAP_FILTER_EXPRESSION
|
||||
#define LLMR_MAP_FILTER_EXPRESSION
|
||||
|
||||
#include <llmr/style/value.hpp>
|
||||
#include <llmr/util/recursive_wrapper.hpp>
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace llmr {
|
||||
|
||||
enum class FilterOperator {
|
||||
Equal,
|
||||
NotEqual,
|
||||
Greater,
|
||||
GreaterEqual,
|
||||
Less,
|
||||
LessEqual
|
||||
};
|
||||
|
||||
enum class ExpressionOperator {
|
||||
Or,
|
||||
And
|
||||
};
|
||||
|
||||
|
||||
inline FilterOperator filterOperatorType(const std::string &op) {
|
||||
if (op == "!=" || op == "not") {
|
||||
return FilterOperator::NotEqual;
|
||||
} else if (op == "==" || op == "eq") {
|
||||
return FilterOperator::Equal;
|
||||
} else if (op == ">" || op == "gt") {
|
||||
return FilterOperator::Greater;
|
||||
} else if (op == ">=" || op == "gte") {
|
||||
return FilterOperator::GreaterEqual;
|
||||
} else if (op == "<" || op == "lt") {
|
||||
return FilterOperator::Less;
|
||||
} else if (op == "<=" || op == "lte") {
|
||||
return FilterOperator::LessEqual;
|
||||
} else {
|
||||
fprintf(stderr, "[WARNING] filter operator '%s' unrecognized\n", op.c_str());
|
||||
return FilterOperator::Equal;
|
||||
}
|
||||
}
|
||||
|
||||
inline ExpressionOperator expressionOperatorType(const std::string &op) {
|
||||
if (op == "&&" || op == "and") {
|
||||
return ExpressionOperator::And;
|
||||
} else if (op == "||" || op == "or") {
|
||||
return ExpressionOperator::Or;
|
||||
} else {
|
||||
fprintf(stderr, "[WARNING] expression operator '%s' unrecognized\n", op.c_str());
|
||||
return ExpressionOperator::Or;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class PropertyFilter;
|
||||
class PropertyExpression;
|
||||
|
||||
typedef util::variant<
|
||||
util::recursive_wrapper<PropertyFilter>,
|
||||
util::recursive_wrapper<PropertyExpression>,
|
||||
std::true_type
|
||||
> PropertyFilterExpression;
|
||||
|
||||
|
||||
class PropertyFilter {
|
||||
public:
|
||||
inline PropertyFilter(const std::string &field, FilterOperator op, const Value &value) : field(field), op(op), value(value) {};
|
||||
inline PropertyFilter(PropertyFilter &&filter)
|
||||
: field(std::move(filter.field)),
|
||||
op(filter.op),
|
||||
value(std::move(filter.value)) {
|
||||
}
|
||||
|
||||
// Returns true if the filter passes, even if the key is missing.
|
||||
inline bool isMissingFieldOkay() const {
|
||||
switch (op) {
|
||||
case FilterOperator::NotEqual:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool compare(const Value &other) const {
|
||||
switch (op) {
|
||||
case FilterOperator::Equal:
|
||||
return util::relaxed_equal(other, value);
|
||||
case FilterOperator::NotEqual:
|
||||
return !util::relaxed_equal(other, value);
|
||||
case FilterOperator::Greater:
|
||||
return util::relaxed_greater(other, value);
|
||||
case FilterOperator::GreaterEqual:
|
||||
return util::relaxed_greater_equal(other, value);
|
||||
case FilterOperator::Less:
|
||||
return util::relaxed_less(other, value);
|
||||
case FilterOperator::LessEqual:
|
||||
return util::relaxed_less_equal(other, value);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
std::string field;
|
||||
FilterOperator op = FilterOperator::Equal;
|
||||
Value value;
|
||||
};
|
||||
|
||||
class PropertyExpression {
|
||||
public:
|
||||
inline PropertyExpression() {}
|
||||
inline PropertyExpression(PropertyExpression &&expression)
|
||||
: op(expression.op),
|
||||
operands(std::move(expression.operands)) {
|
||||
}
|
||||
|
||||
public:
|
||||
ExpressionOperator op = ExpressionOperator::Or;
|
||||
std::vector<PropertyFilterExpression> operands;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <llmr/map/transform.hpp>
|
||||
#include <llmr/style/style.hpp>
|
||||
#include <llmr/geometry/glyph_atlas.hpp>
|
||||
#include <llmr/text/glyph_store.hpp>
|
||||
#include <llmr/renderer/painter.hpp>
|
||||
#include <llmr/util/noncopyable.hpp>
|
||||
#include <llmr/util/texturepool.hpp>
|
||||
@@ -51,6 +52,13 @@ public:
|
||||
void resize(uint16_t width, uint16_t height, float ratio = 1);
|
||||
void resize(uint16_t width, uint16_t height, float ratio, uint16_t fb_width, uint16_t fb_height);
|
||||
|
||||
// Styling
|
||||
void setAppliedClasses(std::set<std::string> appliedClasses);
|
||||
std::set<std::string> getAppliedClasses() const;
|
||||
void setDefaultTransitionDuration(uint64_t duration_milliseconds = 0);
|
||||
void setStyleJSON(std::string newStyleJSON);
|
||||
std::string getStyleJSON() const;
|
||||
|
||||
// Transition
|
||||
void cancelTransitions();
|
||||
|
||||
@@ -73,8 +81,8 @@ public:
|
||||
void resetZoom();
|
||||
void startScaling();
|
||||
void stopScaling();
|
||||
double getMinZoom();
|
||||
double getMaxZoom();
|
||||
double getMinZoom() const;
|
||||
double getMaxZoom() const;
|
||||
|
||||
// Rotation
|
||||
void rotateBy(double sx, double sy, double ex, double ey, double duration = 0);
|
||||
@@ -91,17 +99,15 @@ public:
|
||||
void toggleDebug();
|
||||
bool getDebug() const;
|
||||
|
||||
// TEMPORARY DEBUG API
|
||||
void toggleRaster();
|
||||
|
||||
public:
|
||||
inline const TransformState &getState() const { return state; }
|
||||
inline const Style &getStyle() const { return style; }
|
||||
inline GlyphAtlas &getGlyphAtlas() { return glyphAtlas; }
|
||||
inline SpriteAtlas &getSpriteAtlas() { return spriteAtlas; }
|
||||
inline uv_loop_t *getLoop() { return loop; }
|
||||
inline std::shared_ptr<const Style> getStyle() const { return style; }
|
||||
inline std::shared_ptr<GlyphAtlas> getGlyphAtlas() { return glyphAtlas; }
|
||||
inline std::shared_ptr<GlyphStore> getGlyphStore() { return glyphStore; }
|
||||
inline std::shared_ptr<SpriteAtlas> getSpriteAtlas() { return spriteAtlas; }
|
||||
inline std::shared_ptr<Texturepool> getTexturepool() { return texturepool; }
|
||||
inline std::shared_ptr<uv::loop> getLoop() { return loop; }
|
||||
inline time getAnimationTime() const { return animationTime; }
|
||||
inline Texturepool &getTexturepool() { return texturepool; }
|
||||
inline const Sources &getSources() { return sources; }
|
||||
|
||||
private:
|
||||
@@ -113,7 +119,6 @@ private:
|
||||
|
||||
// Setup
|
||||
void setup();
|
||||
void loadStyle(const uint8_t *const data, uint32_t bytes);
|
||||
|
||||
void updateTiles();
|
||||
void updateRenderState();
|
||||
@@ -149,14 +154,18 @@ private:
|
||||
Transform transform;
|
||||
TransformState state;
|
||||
|
||||
Texturepool texturepool;
|
||||
Style style;
|
||||
GlyphAtlas glyphAtlas;
|
||||
SpriteAtlas spriteAtlas;
|
||||
std::shared_ptr<Style> style;
|
||||
std::shared_ptr<GlyphAtlas> glyphAtlas;
|
||||
std::shared_ptr<GlyphStore> glyphStore;
|
||||
std::shared_ptr<SpriteAtlas> spriteAtlas;
|
||||
std::shared_ptr<Texturepool> texturepool;
|
||||
|
||||
Painter painter;
|
||||
|
||||
Sources sources;
|
||||
|
||||
std::string styleJSON = "";
|
||||
|
||||
bool debug = false;
|
||||
time animationTime = 0;
|
||||
|
||||
@@ -164,7 +173,7 @@ private:
|
||||
|
||||
private:
|
||||
bool async = false;
|
||||
uv_loop_t *loop = nullptr;
|
||||
std::shared_ptr<uv::loop> loop;
|
||||
uv_thread_t thread;
|
||||
uv_async_t *async_terminate = nullptr;
|
||||
uv_async_t *async_render = nullptr;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <forward_list>
|
||||
#include <string>
|
||||
#include <bitset>
|
||||
#include <memory>
|
||||
|
||||
namespace llmr {
|
||||
|
||||
|
||||
@@ -46,7 +46,9 @@ public:
|
||||
const std::string toString() const;
|
||||
|
||||
// Override this in the child class.
|
||||
virtual void beforeParse();
|
||||
virtual void parse() = 0;
|
||||
virtual void afterParse();
|
||||
virtual void render(Painter &painter, const LayerDescription& layer_desc) = 0;
|
||||
virtual bool hasData(const LayerDescription& layer_desc) const = 0;
|
||||
|
||||
|
||||
@@ -1,27 +1,39 @@
|
||||
#ifndef LLMR_MAP_TILE_PARSER
|
||||
#define LLMR_MAP_TILE_PARSER
|
||||
|
||||
#include <llmr/map/vector_tile_data.hpp>
|
||||
#include <llmr/map/vector_tile.hpp>
|
||||
#include <llmr/text/placement.hpp>
|
||||
#include <llmr/text/glyph_store.hpp>
|
||||
#include <llmr/text/glyph.hpp>
|
||||
#include <llmr/util/utf.hpp>
|
||||
|
||||
namespace llmr {
|
||||
|
||||
class Style;
|
||||
class GlyphAtlas;
|
||||
class GlyphStore;
|
||||
class SpriteAtlas;
|
||||
class VectorTileData;
|
||||
|
||||
class LayerDescription;
|
||||
class Bucket;
|
||||
|
||||
class TileParser {
|
||||
public:
|
||||
TileParser(const std::string& data, VectorTileData& tile, const Style& style, GlyphAtlas& glyphAtlas, SpriteAtlas &spriteAtlas);
|
||||
TileParser(const std::string &data, VectorTileData &tile,
|
||||
const std::shared_ptr<const Style> &style,
|
||||
const std::shared_ptr<GlyphAtlas> &glyphAtlas,
|
||||
const std::shared_ptr<GlyphStore> &glyphStore,
|
||||
const std::shared_ptr<SpriteAtlas> &spriteAtlas);
|
||||
|
||||
public:
|
||||
void parse();
|
||||
|
||||
private:
|
||||
bool obsolete() const;
|
||||
void parseGlyphs();
|
||||
void parseStyleLayers(const std::vector<LayerDescription>& layers);
|
||||
void addGlyph(uint64_t tileid, const std::string stackname, const std::u32string &string, const FontStack &fontStack, GlyphAtlas &glyphAtlas, GlyphPositions &face);
|
||||
std::unique_ptr<Bucket> createBucket(const BucketDescription& bucket_desc);
|
||||
std::unique_ptr<Bucket> createFillBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc);
|
||||
std::unique_ptr<Bucket> createLineBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc);
|
||||
@@ -33,10 +45,13 @@ private:
|
||||
private:
|
||||
const VectorTile vector_data;
|
||||
VectorTileData& tile;
|
||||
const Style& style;
|
||||
GlyphAtlas& glyphAtlas;
|
||||
SpriteAtlas &spriteAtlas;
|
||||
Faces faces;
|
||||
|
||||
// Cross-thread shared data.
|
||||
std::shared_ptr<const Style> style;
|
||||
std::shared_ptr<GlyphAtlas> glyphAtlas;
|
||||
std::shared_ptr<GlyphStore> glyphStore;
|
||||
std::shared_ptr<SpriteAtlas> spriteAtlas;
|
||||
|
||||
Placement placement;
|
||||
};
|
||||
|
||||
|
||||
@@ -39,8 +39,8 @@ public:
|
||||
double getScale() const;
|
||||
void startScaling();
|
||||
void stopScaling();
|
||||
double getMinZoom();
|
||||
double getMaxZoom();
|
||||
double getMinZoom() const;
|
||||
double getMaxZoom() const;
|
||||
|
||||
// Angle
|
||||
void rotateBy(double sx, double sy, double ex, double ey, time duration = 0);
|
||||
@@ -66,12 +66,12 @@ private:
|
||||
void _moveBy(double dx, double dy, time duration = 0);
|
||||
void _setScale(double scale, double cx, double cy, time duration = 0);
|
||||
void _setScaleXY(double new_scale, double xn, double yn, time duration = 0);
|
||||
void _setAngle(double angle, time duration = 0, bool disable_interaction = false);
|
||||
void _setAngle(double angle, time duration = 0);
|
||||
void _clearPanning();
|
||||
void _clearRotating();
|
||||
void _clearScaling();
|
||||
|
||||
void constrain(double& scale, double& y);
|
||||
void constrain(double& scale, double& y) const;
|
||||
|
||||
private:
|
||||
mutable uv::rwlock mtx;
|
||||
@@ -96,7 +96,6 @@ private:
|
||||
std::shared_ptr<util::transition> scale_timeout;
|
||||
std::shared_ptr<util::transition> rotate_timeout;
|
||||
std::shared_ptr<util::transition> pan_timeout;
|
||||
std::shared_ptr<util::transition> interaction_timeout;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -39,9 +39,6 @@ public:
|
||||
// Changing
|
||||
bool isChanging() const;
|
||||
|
||||
// Interactive
|
||||
bool isInteractive() const;
|
||||
|
||||
private:
|
||||
double pixel_x() const;
|
||||
double pixel_y() const;
|
||||
@@ -60,7 +57,6 @@ private:
|
||||
bool rotating = false;
|
||||
bool scaling = false;
|
||||
bool panning = false;
|
||||
bool interactive = true;
|
||||
|
||||
// map position
|
||||
double x = 0, y = 0;
|
||||
|
||||
@@ -5,8 +5,10 @@
|
||||
#include <llmr/util/vec.hpp>
|
||||
#include <llmr/style/value.hpp>
|
||||
#include <llmr/text/glyph.hpp>
|
||||
#include <llmr/map/filter_expression.hpp>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <set>
|
||||
#include <limits>
|
||||
|
||||
@@ -53,7 +55,12 @@ public:
|
||||
const pbf& operator*() const;
|
||||
|
||||
private:
|
||||
const FilteredVectorTileLayer& filter;
|
||||
bool matchesFilterExpression(const PropertyFilterExpression &filterExpression, const pbf &tags_pbf);
|
||||
bool matchesExpression(const PropertyExpression &expression, const pbf &tags_pbf);
|
||||
bool matchesFilter(const PropertyFilter &filter, const pbf &tags_pbf);
|
||||
|
||||
private:
|
||||
const FilteredVectorTileLayer& parent;
|
||||
bool valid = false;
|
||||
pbf feature;
|
||||
pbf data;
|
||||
@@ -68,8 +75,6 @@ public:
|
||||
private:
|
||||
const VectorTileLayer& layer;
|
||||
const BucketDescription& bucket_desc;
|
||||
int32_t key = -1;
|
||||
std::set<uint32_t> values;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream&, const GlyphPlacement& placement);
|
||||
@@ -82,39 +87,11 @@ public:
|
||||
std::string name;
|
||||
uint32_t extent = 4096;
|
||||
std::vector<std::string> keys;
|
||||
std::unordered_map<std::string, uint32_t> key_index;
|
||||
std::vector<Value> values;
|
||||
std::vector<std::string> faces;
|
||||
std::map<std::string, std::map<Value, Shaping>> shaping;
|
||||
};
|
||||
|
||||
class VectorTileGlyph {
|
||||
public:
|
||||
VectorTileGlyph();
|
||||
VectorTileGlyph(pbf data);
|
||||
|
||||
uint32_t id = 0;
|
||||
|
||||
// A signed distance field of the glyph with a border of 3 pixels.
|
||||
std::string bitmap;
|
||||
|
||||
// Glyph metrics
|
||||
GlyphMetrics metrics;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream&, const VectorTileGlyph& glyph);
|
||||
|
||||
class VectorTileFace {
|
||||
public:
|
||||
VectorTileFace(pbf data);
|
||||
|
||||
std::string name;
|
||||
std::string family;
|
||||
std::string style;
|
||||
std::vector<VectorTileGlyph> glyphs;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream&, const VectorTileFace& face);
|
||||
|
||||
class VectorTile {
|
||||
public:
|
||||
VectorTile();
|
||||
@@ -122,7 +99,6 @@ public:
|
||||
VectorTile& operator=(VectorTile&& other);
|
||||
|
||||
std::map<std::string, const VectorTileLayer> layers;
|
||||
std::map<std::string, const VectorTileFace> faces;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <llmr/geometry/line_buffer.hpp>
|
||||
#include <llmr/geometry/icon_buffer.hpp>
|
||||
#include <llmr/geometry/text_buffer.hpp>
|
||||
#include <llmr/map/tile_parser.hpp>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
@@ -25,7 +26,9 @@ public:
|
||||
VectorTileData(Tile::ID id, Map &map, const std::string url);
|
||||
~VectorTileData();
|
||||
|
||||
virtual void beforeParse();
|
||||
virtual void parse();
|
||||
virtual void afterParse();
|
||||
virtual void render(Painter &painter, const LayerDescription& layer_desc);
|
||||
virtual bool hasData(const LayerDescription& layer_desc) const;
|
||||
|
||||
@@ -44,6 +47,7 @@ protected:
|
||||
// They contain the location offsets in the buffers stored above
|
||||
std::unordered_map<std::string, std::unique_ptr<Bucket>> buckets;
|
||||
|
||||
std::unique_ptr<TileParser> parser;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
|
||||
typedef struct uv_loop_s uv_loop_t;
|
||||
|
||||
namespace uv {
|
||||
class loop;
|
||||
}
|
||||
|
||||
namespace llmr {
|
||||
|
||||
extern const char *kSpriteURL;
|
||||
@@ -25,9 +29,11 @@ struct Response {
|
||||
|
||||
// Makes an HTTP request of a URL, preferrably on a background thread, and calls a function with the
|
||||
// results in the original thread (which runs the libuv loop).
|
||||
// If the loop pointer is NULL, the callback function will be called on an arbitrary thread.
|
||||
// Returns a cancellable request.
|
||||
std::shared_ptr<Request> request_http(const std::string &url,
|
||||
std::function<void(Response *)> callback, uv_loop_t *loop);
|
||||
std::function<void(Response *)> callback,
|
||||
std::shared_ptr<uv::loop> loop = nullptr);
|
||||
|
||||
// Cancels an HTTP request.
|
||||
void cancel_request_http(const std::shared_ptr<Request> &req);
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
typedef struct uv_loop_s uv_loop_t;
|
||||
typedef struct uv_async_s uv_async_t;
|
||||
|
||||
namespace uv {
|
||||
class loop;
|
||||
}
|
||||
|
||||
namespace llmr {
|
||||
namespace platform {
|
||||
|
||||
@@ -21,7 +25,7 @@ class Request : public std::enable_shared_from_this<Request>, private util::nonc
|
||||
public:
|
||||
Request(const std::string &url,
|
||||
std::function<void(Response *)> callback,
|
||||
uv_loop_t *loop);
|
||||
std::shared_ptr<uv::loop> loop);
|
||||
~Request();
|
||||
|
||||
void complete();
|
||||
@@ -36,7 +40,7 @@ public:
|
||||
|
||||
public:
|
||||
uv_async_t *async = nullptr;
|
||||
uv_loop_t *loop = nullptr;
|
||||
std::shared_ptr<uv::loop> loop;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define LLMR_RENDERER_BUCKET
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <llmr/map/tile.hpp>
|
||||
#include <llmr/util/noncopyable.hpp>
|
||||
#include <llmr/renderer/prerendered_texture.hpp>
|
||||
|
||||
@@ -85,9 +85,9 @@ public:
|
||||
void renderText(TextBucket& bucket, const std::string& layer_name, const Tile::ID& id);
|
||||
void renderRaster(RasterBucket& bucket, const std::string& layer_name, const Tile::ID& id);
|
||||
|
||||
void preparePrerender(const GenericProperties &properties);
|
||||
void finishPrerender(const GenericProperties &properties);
|
||||
void renderPrerenderedTexture(Bucket &bucket, const GenericProperties &properties);
|
||||
void preparePrerender(PrerenderedTexture &texture);
|
||||
void finishPrerender(PrerenderedTexture &texture);
|
||||
void renderPrerenderedTexture(PrerenderedTexture &texture, const GenericProperties &properties);
|
||||
|
||||
void resize();
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <llmr/util/noncopyable.hpp>
|
||||
#include <llmr/platform/gl.hpp>
|
||||
#include <llmr/style/properties.hpp>
|
||||
|
||||
namespace llmr {
|
||||
|
||||
@@ -10,7 +11,7 @@ class Painter;
|
||||
|
||||
class PrerenderedTexture : private util::noncopyable {
|
||||
public:
|
||||
PrerenderedTexture(uint16_t size);
|
||||
PrerenderedTexture(const PrerenderProperties &properties);
|
||||
~PrerenderedTexture();
|
||||
|
||||
void bindTexture();
|
||||
@@ -21,12 +22,13 @@ public:
|
||||
|
||||
void blur(Painter& painter, uint16_t passes);
|
||||
|
||||
public:
|
||||
const PrerenderProperties properties;
|
||||
|
||||
private:
|
||||
const uint16_t size;
|
||||
GLint previous_fbo = 0;
|
||||
GLuint fbo = 0;
|
||||
GLuint texture = 0;
|
||||
bool mipmapped = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ class VertexArrayObject;
|
||||
|
||||
class RasterBucket : public Bucket {
|
||||
public:
|
||||
RasterBucket(Texturepool &texturepool);
|
||||
RasterBucket(const std::shared_ptr<Texturepool> &texturepool);
|
||||
|
||||
virtual void render(Painter& painter, const std::string& layer_name, const Tile::ID& id);
|
||||
virtual bool hasData() const;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <llmr/geometry/elements_buffer.hpp>
|
||||
#include <llmr/map/vector_tile.hpp>
|
||||
#include <llmr/text/types.hpp>
|
||||
#include <llmr/text/glyph.hpp>
|
||||
#include <memory>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
@@ -35,9 +36,9 @@ public:
|
||||
void addGlyphs(const PlacedGlyphs &glyphs, float placementZoom,
|
||||
PlacementRange placementRange, float zoom);
|
||||
|
||||
void addFeature(const VectorTileFeature &feature,
|
||||
const IndexedFaces &faces,
|
||||
const std::map<Value, Shaping> &shapings);
|
||||
void addFeature(const pbf &geometry,
|
||||
const GlyphPositions &face,
|
||||
const Shaping &shaping);
|
||||
|
||||
void drawGlyphs(TextShader &shader);
|
||||
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
#include <vector>
|
||||
#include <cmath>
|
||||
|
||||
#include "value.hpp"
|
||||
#include <llmr/util/vec.hpp>
|
||||
#include <llmr/map/filter_expression.hpp>
|
||||
#include <llmr/style/value.hpp>
|
||||
|
||||
namespace llmr {
|
||||
|
||||
@@ -68,6 +70,19 @@ inline TextPathType textPathType(const std::string& path) {
|
||||
else return TextPathType::Horizontal;
|
||||
};
|
||||
|
||||
inline float alignmentType(const std::string& alignment) {
|
||||
if (alignment == "right") return 1.0f;
|
||||
else if (alignment == "left") return 0.0f;
|
||||
else return 0.5;
|
||||
};
|
||||
|
||||
inline float verticalAlignmentType(const std::string& alignment) {
|
||||
if (alignment == "bottom") return 1.0f;
|
||||
else if (alignment == "top") return 0.0f;
|
||||
else return 0.5;
|
||||
};
|
||||
|
||||
|
||||
class BucketGeometryDescription {
|
||||
public:
|
||||
CapType cap = CapType::None;
|
||||
@@ -79,6 +94,12 @@ public:
|
||||
float miter_limit = 2.0f;
|
||||
float round_limit = 1.0f;
|
||||
TextPathType path = TextPathType::Horizontal;
|
||||
float alignment = 0.5;
|
||||
float vertical_alignment = 0.5;
|
||||
float line_height = 1.2 * 24;
|
||||
float max_width = 15.0f * 24;
|
||||
float letter_spacing = 0;
|
||||
vec2<float> translate {0, 0};
|
||||
float padding = 2.0f;
|
||||
float textMinDistance = 250.0f;
|
||||
float rotate = 0.0f; // what is this?
|
||||
@@ -86,6 +107,7 @@ public:
|
||||
bool alwaysVisible = false;
|
||||
};
|
||||
|
||||
|
||||
class BucketDescription {
|
||||
public:
|
||||
BucketType feature_type = BucketType::None;
|
||||
@@ -94,8 +116,8 @@ public:
|
||||
// Specify what data to pull into this bucket
|
||||
std::string source_name;
|
||||
std::string source_layer;
|
||||
std::string source_field;
|
||||
std::vector<Value> source_value;
|
||||
|
||||
PropertyFilterExpression filter = std::true_type();
|
||||
|
||||
// Specifies how the geometry for this bucket should be created
|
||||
BucketGeometryDescription geometry;
|
||||
|
||||
@@ -93,10 +93,16 @@ struct GenericClass {
|
||||
boost::optional<TranslateAnchor> translateAnchor;
|
||||
boost::optional<FunctionProperty> opacity;
|
||||
boost::optional<PropertyTransition> opacity_transition;
|
||||
boost::optional<bool> prerender;
|
||||
boost::optional<float> prerenderBuffer;
|
||||
boost::optional<uint16_t> prerenderSize;
|
||||
boost::optional<uint16_t> prerenderBlur;
|
||||
boost::optional<FunctionProperty> prerender;
|
||||
boost::optional<FunctionProperty> prerenderBuffer;
|
||||
boost::optional<FunctionProperty> prerenderSize;
|
||||
boost::optional<FunctionProperty> prerenderBlur;
|
||||
};
|
||||
|
||||
struct PrerenderProperties {
|
||||
float buffer = 1.0f / 32.0f;
|
||||
uint16_t size = 256;
|
||||
uint16_t blur = 0;
|
||||
};
|
||||
|
||||
struct GenericProperties {
|
||||
@@ -104,10 +110,25 @@ struct GenericProperties {
|
||||
std::array<float, 2> translate = {{ 0, 0 }};
|
||||
TranslateAnchor translateAnchor = TranslateAnchor::Map;
|
||||
float opacity = 1.0;
|
||||
bool prerender = false;
|
||||
float prerenderBuffer = 1.0f / 32.0f;
|
||||
uint16_t prerenderSize = 256;
|
||||
uint16_t prerenderBlur = 0;
|
||||
|
||||
// These are unresolved properties because the value here is per tile, so it might differ.
|
||||
boost::optional<FunctionProperty> prerender;
|
||||
boost::optional<FunctionProperty> prerenderBuffer;
|
||||
boost::optional<FunctionProperty> prerenderSize;
|
||||
boost::optional<FunctionProperty> prerenderBlur;
|
||||
|
||||
inline bool getPrerender(int8_t z) const {
|
||||
return prerender && prerender.get().evaluate<bool>(z);
|
||||
}
|
||||
|
||||
// Obtains prerender properties by integer zoom level.
|
||||
inline PrerenderProperties getPrerenderProperties(int8_t z) const {
|
||||
PrerenderProperties props;
|
||||
if (prerenderBuffer) props.buffer = prerenderBuffer.get().evaluate<float>(z);
|
||||
if (prerenderSize) props.size = prerenderSize.get().evaluate<uint16_t>(z);
|
||||
if (prerenderBlur) props.blur = prerenderBlur.get().evaluate<uint16_t>(z);
|
||||
return props;
|
||||
}
|
||||
|
||||
virtual bool isVisible() const {
|
||||
if (!enabled) { return false; }
|
||||
@@ -128,6 +149,7 @@ struct IconClass : public GenericClass {
|
||||
};
|
||||
|
||||
struct IconProperties : public GenericProperties {
|
||||
inline IconProperties() : GenericProperties() {}
|
||||
float size = 0;
|
||||
Color color = {{ 1, 1, 1, 1 }};
|
||||
std::string image;
|
||||
@@ -147,6 +169,7 @@ struct LineClass : public GenericClass {
|
||||
};
|
||||
|
||||
struct LineProperties : public GenericProperties {
|
||||
inline LineProperties() : GenericProperties() {}
|
||||
float width = 0;
|
||||
float offset = 0;
|
||||
Color color = {{ 0, 0, 0, 1 }};
|
||||
@@ -164,6 +187,7 @@ struct FillClass : public GenericClass {
|
||||
};
|
||||
|
||||
struct FillProperties : public GenericProperties {
|
||||
inline FillProperties() : GenericProperties() {}
|
||||
Winding winding = Winding::NonZero;
|
||||
bool antialias = true;
|
||||
Color fill_color = {{ 0, 0, 0, 1 }};
|
||||
@@ -187,6 +211,7 @@ struct TextClass : public GenericClass {
|
||||
};
|
||||
|
||||
struct TextProperties : public GenericProperties {
|
||||
inline TextProperties() : GenericProperties() {}
|
||||
Color color = {{ 0, 0, 0, 1 }};
|
||||
Color halo = {{ 1, 1, 1, 0.75 }};
|
||||
float halo_radius = 0.25f;
|
||||
@@ -202,6 +227,7 @@ struct BackgroundClass : public GenericClass {
|
||||
};
|
||||
|
||||
struct BackgroundProperties : public GenericProperties {
|
||||
inline BackgroundProperties() : GenericProperties() {}
|
||||
Color color = {{ 1, 1, 1, 1 }};
|
||||
};
|
||||
|
||||
@@ -209,6 +235,7 @@ struct RasterClass : public GenericClass {
|
||||
};
|
||||
|
||||
struct RasterProperties : public GenericProperties {
|
||||
inline RasterProperties() : GenericProperties() {}
|
||||
};
|
||||
|
||||
struct CompositeClass : public GenericClass {
|
||||
@@ -219,6 +246,12 @@ struct CompositeProperties : public GenericProperties {
|
||||
};
|
||||
|
||||
|
||||
const IconProperties defaultIconProperties;
|
||||
const LineProperties defaultLineProperties;
|
||||
const FillProperties defaultFillProperties;
|
||||
const TextProperties defaultTextProperties;
|
||||
const BackgroundProperties defaultBackgroundProperties;
|
||||
const RasterProperties defaultRasterProperties;
|
||||
const CompositeProperties defaultCompositeProperties;
|
||||
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ public:
|
||||
|
||||
void reset();
|
||||
|
||||
void loadJSON(const uint8_t *const data, size_t bytes);
|
||||
void loadJSON(const uint8_t *const data);
|
||||
|
||||
size_t layerCount() const;
|
||||
void cascade(float z);
|
||||
@@ -39,7 +39,7 @@ public:
|
||||
void updateTransitions(time now);
|
||||
void cancelTransitions();
|
||||
|
||||
void setDefaultTransitionDuration(uint64_t duration = 0);
|
||||
void setDefaultTransitionDuration(uint64_t duration_milliseconds = 0);
|
||||
|
||||
public:
|
||||
std::shared_ptr<Sprite> sprite;
|
||||
|
||||
@@ -39,9 +39,11 @@ private:
|
||||
RasterClass parseRasterClass(JSVal value);
|
||||
CompositeClass parseCompositeClass(JSVal value);
|
||||
|
||||
PropertyFilterExpression parseFilterOrExpression(JSVal value);
|
||||
|
||||
bool parseBoolean(JSVal value);
|
||||
std::string parseString(JSVal value);
|
||||
const JSVal& replaceConstant(const JSVal& value);
|
||||
JSVal replaceConstant(JSVal value);
|
||||
std::vector<FunctionProperty> parseArray(JSVal value, uint16_t expected_count);
|
||||
Color parseColor(JSVal value);
|
||||
Value parseValue(JSVal value);
|
||||
|
||||
@@ -14,9 +14,11 @@ Value parseValue(pbf data);
|
||||
|
||||
namespace util {
|
||||
|
||||
namespace detail {
|
||||
|
||||
inline int string_to_bool(std::string str) {
|
||||
std::transform(str.begin(), str.end(), str.begin(), ::tolower);
|
||||
if (str == "false" || str == "null" || !str.length()) return 0;
|
||||
if (str == "0" || str == "0.0" || str == "false" || str == "null" || !str.length()) return 0;
|
||||
else if (str == "true") return 1;
|
||||
else return -1;
|
||||
}
|
||||
@@ -52,44 +54,85 @@ template <> struct string_to_number<uint64_t> {
|
||||
}
|
||||
};
|
||||
|
||||
struct relaxed_equal_visitor {
|
||||
bool operator()(bool lhs, bool rhs) const { return lhs == rhs; }
|
||||
template <typename Operator>
|
||||
struct relaxed_operator_visitor {
|
||||
bool operator()(bool lhs, bool rhs) const { return Operator()(lhs, rhs); }
|
||||
|
||||
template <typename T, class = typename std::enable_if<std::is_arithmetic<T>::value>::type>
|
||||
bool operator()(bool lhs, T rhs) const { return lhs == bool(rhs); }
|
||||
bool operator()(bool lhs, T rhs) const { return Operator()(lhs, bool(rhs)); }
|
||||
|
||||
template <typename T, class = typename std::enable_if<std::is_arithmetic<T>::value>::type>
|
||||
bool operator()(T lhs, bool rhs) const { return bool(lhs) == rhs; }
|
||||
bool operator()(T lhs, bool rhs) const { return Operator()(bool(lhs), rhs); }
|
||||
|
||||
bool operator()(int64_t lhs, uint64_t rhs) const { return lhs < 0 ? false : uint64_t(lhs) == rhs; }
|
||||
bool operator()(uint64_t lhs, int64_t rhs) const { return rhs < 0 ? false : lhs == uint64_t(rhs); }
|
||||
bool operator()(int64_t lhs, uint64_t rhs) const { return lhs < 0 ? false : Operator()(uint64_t(lhs), rhs); }
|
||||
bool operator()(uint64_t lhs, int64_t rhs) const { return rhs < 0 ? false : Operator()(lhs, uint64_t(rhs)); }
|
||||
|
||||
template <typename T, class = typename std::enable_if<std::is_arithmetic<T>::value>::type>
|
||||
bool operator()(T lhs, const std::string &rhs) const try { return lhs == string_to_number<T>()(rhs); }
|
||||
bool operator()(const std::string &lhs, T rhs) const try { return Operator()(string_to_number<T>()(lhs), rhs); }
|
||||
catch(...) { return false; }
|
||||
|
||||
template <typename T, class = typename std::enable_if<std::is_arithmetic<T>::value>::type>
|
||||
bool operator()(const std::string &lhs, T rhs) const try { return string_to_number<T>()(lhs) == rhs; }
|
||||
bool operator()(T lhs, const std::string &rhs) const try { return Operator()(lhs, string_to_number<T>()(rhs)); }
|
||||
catch(...) { return false; }
|
||||
|
||||
template <typename T0, typename T1>
|
||||
bool operator()(T0 lhs, T1 rhs) const { return Operator()(lhs, rhs); }
|
||||
};
|
||||
|
||||
struct relaxed_equal_operator {
|
||||
template <typename T0, typename T1>
|
||||
bool operator()(T0 lhs, T1 rhs) const { return lhs == rhs; }
|
||||
};
|
||||
|
||||
struct relaxed_equal {
|
||||
relaxed_equal(Value const &lhs) : lhs(lhs) {}
|
||||
|
||||
bool operator()(Value const &rhs) const {
|
||||
return apply_visitor(lhs, rhs, relaxed_equal_visitor());
|
||||
}
|
||||
Value const &lhs;
|
||||
struct relaxed_greater_operator {
|
||||
template <typename T0, typename T1>
|
||||
bool operator()(T0 lhs, T1 rhs) const { return lhs > rhs; }
|
||||
};
|
||||
|
||||
struct relaxed_greater_equal_operator {
|
||||
template <typename T0, typename T1>
|
||||
bool operator()(T0 lhs, T1 rhs) const { return lhs >= rhs; }
|
||||
};
|
||||
|
||||
struct relaxed_less_operator {
|
||||
template <typename T0, typename T1>
|
||||
bool operator()(T0 lhs, T1 rhs) const { return lhs < rhs; }
|
||||
};
|
||||
|
||||
struct relaxed_less_equal_operator {
|
||||
template <typename T0, typename T1>
|
||||
bool operator()(T0 lhs, T1 rhs) const { return lhs <= rhs; }
|
||||
};
|
||||
|
||||
|
||||
} // end namespace detail
|
||||
|
||||
inline bool relaxed_equal(Value const &lhs, Value const &rhs) {
|
||||
return apply_visitor(lhs, rhs, detail::relaxed_operator_visitor<detail::relaxed_equal_operator>());
|
||||
}
|
||||
|
||||
inline bool relaxed_greater(Value const &lhs, Value const &rhs) {
|
||||
return apply_visitor(lhs, rhs, detail::relaxed_operator_visitor<detail::relaxed_greater_operator>());
|
||||
}
|
||||
|
||||
inline bool relaxed_greater_equal(Value const &lhs, Value const &rhs) {
|
||||
return apply_visitor(lhs, rhs, detail::relaxed_operator_visitor<detail::relaxed_greater_equal_operator>());
|
||||
}
|
||||
|
||||
inline bool relaxed_less(Value const &lhs, Value const &rhs) {
|
||||
return apply_visitor(lhs, rhs, detail::relaxed_operator_visitor<detail::relaxed_less_operator>());
|
||||
}
|
||||
|
||||
inline bool relaxed_less_equal(Value const &lhs, Value const &rhs) {
|
||||
return apply_visitor(lhs, rhs, detail::relaxed_operator_visitor<detail::relaxed_less_equal_operator>());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
T toNumber(const Value &value) {
|
||||
if (value.is<std::string>()) return util::string_to_number<T>()(value.get<std::string>());
|
||||
if (value.is<std::string>()) return util::detail::string_to_number<T>()(value.get<std::string>());
|
||||
else if (value.is<bool>()) return value.get<bool>();
|
||||
else if (value.is<int64_t>()) return value.get<int64_t>();
|
||||
else if (value.is<uint64_t>()) return value.get<uint64_t>();
|
||||
|
||||
@@ -9,6 +9,11 @@
|
||||
|
||||
namespace llmr {
|
||||
|
||||
typedef std::pair<uint16_t, uint16_t> GlyphRange;
|
||||
|
||||
// Note: this only works for the BMP
|
||||
GlyphRange getGlyphRange(uint32_t glyph);
|
||||
|
||||
struct GlyphMetrics {
|
||||
operator bool() const {
|
||||
return width == 0 && height == 0 && advance == 0;
|
||||
@@ -38,8 +43,6 @@ struct Glyph {
|
||||
};
|
||||
|
||||
typedef std::map<uint32_t, Glyph> GlyphPositions;
|
||||
typedef std::map<std::string, GlyphPositions> Faces;
|
||||
typedef std::vector<const GlyphPositions *> IndexedFaces;
|
||||
|
||||
class GlyphPlacement {
|
||||
public:
|
||||
@@ -48,8 +51,8 @@ public:
|
||||
|
||||
uint32_t face = 0;
|
||||
uint32_t glyph = 0;
|
||||
uint32_t x = 0;
|
||||
uint32_t y = 0;
|
||||
int32_t x = 0;
|
||||
int32_t y = 0;
|
||||
};
|
||||
|
||||
typedef std::vector<GlyphPlacement> Shaping;
|
||||
|
||||
90
include/llmr/text/glyph_store.hpp
Normal file
90
include/llmr/text/glyph_store.hpp
Normal file
@@ -0,0 +1,90 @@
|
||||
#ifndef LLMR_TEXT_GLYPH_STORE
|
||||
#define LLMR_TEXT_GLYPH_STORE
|
||||
|
||||
#include <llmr/text/glyph.hpp>
|
||||
#include <llmr/util/pbf.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <future>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace llmr {
|
||||
|
||||
|
||||
class SDFGlyph {
|
||||
public:
|
||||
uint32_t id = 0;
|
||||
|
||||
// A signed distance field of the glyph with a border of 3 pixels.
|
||||
std::string bitmap;
|
||||
|
||||
// Glyph metrics
|
||||
GlyphMetrics metrics;
|
||||
};
|
||||
|
||||
class FontStack {
|
||||
public:
|
||||
void insert(uint32_t id, const SDFGlyph &glyph);
|
||||
const std::map<uint32_t, GlyphMetrics> &getMetrics() const;
|
||||
const std::map<uint32_t, SDFGlyph> &getSDFs() const;
|
||||
const Shaping getShaping(const std::u32string &string,
|
||||
const float &maxWidth,
|
||||
const float &lineHeight,
|
||||
const float &alignment,
|
||||
const float &verticalAlignment,
|
||||
const float &letterSpacing) const;
|
||||
void lineWrap(Shaping &shaping,
|
||||
const float &lineHeight,
|
||||
const float &maxWidth,
|
||||
const float &alignment,
|
||||
const float &verticalAlignment) const;
|
||||
|
||||
private:
|
||||
std::map<uint32_t, std::string> bitmaps;
|
||||
std::map<uint32_t, GlyphMetrics> metrics;
|
||||
std::map<uint32_t, SDFGlyph> sdfs;
|
||||
mutable std::mutex mtx;
|
||||
};
|
||||
|
||||
class GlyphPBF {
|
||||
public:
|
||||
GlyphPBF(const std::string &fontStack, GlyphRange glyphRange);
|
||||
|
||||
void parse(FontStack &stack);
|
||||
|
||||
std::shared_future<GlyphPBF &> getFuture();
|
||||
|
||||
private:
|
||||
std::string data;
|
||||
std::promise<GlyphPBF &> promise;
|
||||
std::shared_future<GlyphPBF &> future;
|
||||
std::mutex mtx;
|
||||
};
|
||||
|
||||
// Manages Glyphrange PBF loading.
|
||||
class GlyphStore {
|
||||
public:
|
||||
// Block until all specified GlyphRanges of the specified font stack are loaded.
|
||||
void waitForGlyphRanges(const std::string &fontStack, const std::set<GlyphRange> &glyphRanges);
|
||||
|
||||
FontStack &getFontStack(const std::string &fontStack);
|
||||
|
||||
private:
|
||||
// Loads an individual glyph range from the font stack and adds it to rangeSets
|
||||
std::shared_future<GlyphPBF &> loadGlyphRange(const std::string &fontStack, std::map<GlyphRange, std::unique_ptr<GlyphPBF>> &rangeSets, GlyphRange range);
|
||||
|
||||
FontStack &createFontStack(const std::string &fontStack);
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, std::map<GlyphRange, std::unique_ptr<GlyphPBF>>> ranges;
|
||||
std::unordered_map<std::string, std::unique_ptr<FontStack>> stacks;
|
||||
std::mutex mtx;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -18,10 +18,8 @@ public:
|
||||
|
||||
void addFeature(TextBucket &bucket, const std::vector<Coordinate> &line,
|
||||
const BucketGeometryDescription &info,
|
||||
const IndexedFaces &faces,
|
||||
const GlyphPositions &face,
|
||||
const Shaping &shaping);
|
||||
float measureText(const IndexedFaces &faces,
|
||||
const Shaping &shaping);
|
||||
|
||||
private:
|
||||
const int8_t zoom;
|
||||
|
||||
@@ -19,6 +19,11 @@ extern const bool styleParseWarnings;
|
||||
extern const bool spriteWarnings;
|
||||
extern const bool renderWarnings;
|
||||
extern const bool renderTree;
|
||||
extern const bool labelTextMissingWarning;
|
||||
extern const bool missingFontStackWarning;
|
||||
extern const bool missingFontFaceWarning;
|
||||
extern const bool glyphWarning;
|
||||
extern const bool shapingWarning;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace llmr {
|
||||
class Raster : public std::enable_shared_from_this<Raster> {
|
||||
|
||||
public:
|
||||
Raster(Texturepool &texturepool);
|
||||
Raster(const std::shared_ptr<Texturepool> &texturepool);
|
||||
~Raster();
|
||||
|
||||
// load image data
|
||||
@@ -53,7 +53,7 @@ private:
|
||||
bool loaded = false;
|
||||
|
||||
// shared texture pool
|
||||
Texturepool &texturepool;
|
||||
std::shared_ptr<Texturepool> texturepool;
|
||||
|
||||
// min/mag filter
|
||||
uint32_t filter = 0;
|
||||
|
||||
132
include/llmr/util/recursive_wrapper.hpp
Normal file
132
include/llmr/util/recursive_wrapper.hpp
Normal file
@@ -0,0 +1,132 @@
|
||||
#ifndef LLMR_UTIL_VARIANT_RECURSIVE_WRAPPER_HPP
|
||||
#define LLMR_UTIL_VARIANT_RECURSIVE_WRAPPER_HPP
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace llmr {
|
||||
|
||||
namespace util {
|
||||
|
||||
template <typename T>
|
||||
class recursive_wrapper
|
||||
{
|
||||
public:
|
||||
|
||||
typedef T type;
|
||||
|
||||
private:
|
||||
|
||||
T* p_;
|
||||
|
||||
public:
|
||||
|
||||
~recursive_wrapper();
|
||||
recursive_wrapper();
|
||||
|
||||
recursive_wrapper(recursive_wrapper const& operand);
|
||||
recursive_wrapper(T const& operand);
|
||||
recursive_wrapper(recursive_wrapper&& operand);
|
||||
recursive_wrapper(T&& operand);
|
||||
|
||||
private:
|
||||
|
||||
void assign(const T& rhs);
|
||||
|
||||
public:
|
||||
|
||||
recursive_wrapper& operator=(const recursive_wrapper& rhs)
|
||||
{
|
||||
assign( rhs.get() );
|
||||
return *this;
|
||||
}
|
||||
|
||||
recursive_wrapper& operator=(const T& rhs)
|
||||
{
|
||||
assign( rhs );
|
||||
return *this;
|
||||
}
|
||||
|
||||
void swap(recursive_wrapper& operand) noexcept
|
||||
{
|
||||
T* temp = operand.p_;
|
||||
operand.p_ = p_;
|
||||
p_ = temp;
|
||||
}
|
||||
|
||||
|
||||
recursive_wrapper& operator=(recursive_wrapper&& rhs) noexcept
|
||||
{
|
||||
swap(rhs);
|
||||
return *this;
|
||||
}
|
||||
|
||||
recursive_wrapper& operator=(T&& rhs)
|
||||
{
|
||||
get() = std::move(rhs);
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
public:
|
||||
|
||||
T& get() { return *get_pointer(); }
|
||||
const T& get() const { return *get_pointer(); }
|
||||
|
||||
T* get_pointer() { return p_; }
|
||||
const T* get_pointer() const { return p_; }
|
||||
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
recursive_wrapper<T>::~recursive_wrapper()
|
||||
{
|
||||
delete p_;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
recursive_wrapper<T>::recursive_wrapper()
|
||||
: p_(new T)
|
||||
{
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
recursive_wrapper<T>::recursive_wrapper(recursive_wrapper const& operand)
|
||||
: p_(new T( operand.get() ))
|
||||
{
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
recursive_wrapper<T>::recursive_wrapper(T const& operand)
|
||||
: p_(new T(operand))
|
||||
{
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
recursive_wrapper<T>::recursive_wrapper(recursive_wrapper&& operand)
|
||||
: p_(new T( std::move(operand.get()) ))
|
||||
{
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
recursive_wrapper<T>::recursive_wrapper(T&& operand)
|
||||
: p_(new T( std::move(operand) ))
|
||||
{
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void recursive_wrapper<T>::assign(const T& rhs)
|
||||
{
|
||||
this->get() = rhs;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void swap(recursive_wrapper<T>& lhs, recursive_wrapper<T>& rhs) noexcept
|
||||
{
|
||||
lhs.swap(rhs);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // LLMR_UTIL_VARIANT_RECURSIVE_WRAPPER_HPP
|
||||
45
include/llmr/util/utf.hpp
Normal file
45
include/llmr/util/utf.hpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#ifndef LLMR_UTIL_UTF
|
||||
#define LLMR_UTIL_UTF
|
||||
|
||||
#include <memory>
|
||||
|
||||
// g++/libstdc++ is missing c++11 codecvt support
|
||||
#ifdef __linux__
|
||||
#include <boost/locale.hpp>
|
||||
#else
|
||||
#include <codecvt>
|
||||
#include <locale>
|
||||
#endif
|
||||
|
||||
namespace llmr {
|
||||
|
||||
namespace util {
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
class utf8_to_utf32 {
|
||||
public:
|
||||
explicit utf8_to_utf32() {}
|
||||
std::u32string convert(std::string const& utf8) {
|
||||
return boost::locale::conv::utf_to_utf<char32_t>(utf8);
|
||||
}
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
class utf8_to_utf32 {
|
||||
public:
|
||||
explicit utf8_to_utf32()
|
||||
: utf32conv_() {}
|
||||
std::u32string convert(std::string const& utf8) {
|
||||
return utf32conv_.from_bytes(utf8);
|
||||
}
|
||||
private:
|
||||
std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> utf32conv_;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
}}
|
||||
|
||||
#endif
|
||||
@@ -12,6 +12,16 @@
|
||||
|
||||
namespace uv {
|
||||
|
||||
class loop {
|
||||
public:
|
||||
inline loop() : l(uv_loop_new()) {}
|
||||
inline ~loop() { uv_loop_delete(l); }
|
||||
|
||||
inline uv_loop_t *operator*() { return l; }
|
||||
|
||||
private:
|
||||
uv_loop_t *l;
|
||||
};
|
||||
|
||||
class mutex {
|
||||
public:
|
||||
@@ -90,12 +100,13 @@ public:
|
||||
typedef void (*after_work_callback)(T &object);
|
||||
|
||||
template<typename... Args>
|
||||
work(uv_loop_t *loop, work_callback work_cb, after_work_callback after_work_cb, Args&&... args)
|
||||
: data(std::forward<Args>(args)...),
|
||||
work(const std::shared_ptr<loop> &loop, work_callback work_cb, after_work_callback after_work_cb, Args&&... args)
|
||||
: loop(loop),
|
||||
data(std::forward<Args>(args)...),
|
||||
work_cb(work_cb),
|
||||
after_work_cb(after_work_cb) {
|
||||
req.data = this;
|
||||
uv_queue_work(loop, &req, do_work, after_work);
|
||||
uv_queue_work(**loop, &req, do_work, after_work);
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -111,6 +122,7 @@ private:
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<uv::loop> loop;
|
||||
uv_work_t req;
|
||||
T data;
|
||||
work_callback work_cb;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.mapbox.llmr.native</string>
|
||||
<string>com.mapbox.${PRODUCT_NAME:rfc1034identifier}</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#import "MBXViewController.h"
|
||||
|
||||
#import "MVKMapView.h"
|
||||
#import "MGLMapView.h"
|
||||
|
||||
#import "../common/settings_nsuserdefaults.hpp"
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
@interface MBXViewController () <CLLocationManagerDelegate>
|
||||
|
||||
@property (nonatomic) MVKMapView *mapView;
|
||||
@property (nonatomic) MGLMapView *mapView;
|
||||
@property (nonatomic) BOOL debug;
|
||||
@property (nonatomic) UIView *palette;
|
||||
@property (nonatomic) CLLocationManager *locationManager;
|
||||
@@ -38,10 +38,12 @@ llmr::Settings_NSUserDefaults *settings = nullptr;
|
||||
{
|
||||
[super viewDidLoad];
|
||||
|
||||
self.mapView = [[MVKMapView alloc] initWithFrame:self.view.bounds];
|
||||
self.mapView = [[MGLMapView alloc] initWithFrame:self.view.bounds];
|
||||
self.mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
[self.view addSubview:self.mapView];
|
||||
|
||||
self.mapView.viewControllerForLayoutGuides = self;
|
||||
|
||||
settings = new llmr::Settings_NSUserDefaults();
|
||||
[self restoreState:nil];
|
||||
|
||||
@@ -85,7 +87,7 @@ llmr::Settings_NSUserDefaults *settings = nullptr;
|
||||
singleTap.numberOfTapsRequired = 1;
|
||||
[self.mapView addGestureRecognizer:singleTap];
|
||||
|
||||
NSArray *selectorNames = @[ @"unrotate", @"resetPosition", @"toggleDebug", @"toggleRaster", @"locateUser" ];
|
||||
NSArray *selectorNames = @[ @"unrotate", @"resetPosition", @"toggleDebug", @"toggleStyle", @"locateUser" ];
|
||||
CGFloat buttonSize = 40;
|
||||
CGFloat bufferSize = 20;
|
||||
CGFloat alpha = 0.75;
|
||||
@@ -166,18 +168,14 @@ llmr::Settings_NSUserDefaults *settings = nullptr;
|
||||
self.debug = ! self.debug;
|
||||
}
|
||||
|
||||
- (void)toggleRaster
|
||||
- (void)toggleStyle
|
||||
{
|
||||
[self.mapView toggleRaster];
|
||||
[self.mapView toggleStyle];
|
||||
}
|
||||
|
||||
- (void)locateUser
|
||||
{
|
||||
[NSObject cancelPreviousPerformRequestsWithTarget:self.locationManager selector:@selector(stopUpdatingLocation) object:nil];
|
||||
|
||||
[self.locationManager startUpdatingLocation];
|
||||
|
||||
[self.locationManager performSelector:@selector(stopUpdatingLocation) withObject:nil afterDelay:5.0];
|
||||
}
|
||||
|
||||
#pragma mark - Destruction
|
||||
@@ -203,7 +201,12 @@ llmr::Settings_NSUserDefaults *settings = nullptr;
|
||||
{
|
||||
CLLocation *latestLocation = locations.lastObject;
|
||||
|
||||
[self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(latestLocation.coordinate.latitude, latestLocation.coordinate.longitude) zoomLevel:17 animated:YES];
|
||||
if ([latestLocation distanceFromLocation:[[CLLocation alloc] initWithLatitude:self.mapView.centerCoordinate.latitude longitude:self.mapView.centerCoordinate.longitude]] > 100)
|
||||
{
|
||||
[self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(latestLocation.coordinate.latitude, latestLocation.coordinate.longitude) zoomLevel:17 animated:YES];
|
||||
}
|
||||
|
||||
[self.locationManager stopUpdatingLocation];
|
||||
}
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
Submodule ios/MVKMapKit deleted from 9c3aa2d919
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |
@@ -6,7 +6,7 @@
|
||||
'targets': [
|
||||
{
|
||||
"target_name": "iosapp",
|
||||
"product_name": "llmr",
|
||||
"product_name": "Mapbox GL",
|
||||
"type": "executable",
|
||||
"sources": [
|
||||
"./main.m",
|
||||
@@ -14,8 +14,10 @@
|
||||
"./MBXAppDelegate.m",
|
||||
"./MBXViewController.h",
|
||||
"./MBXViewController.mm",
|
||||
"./MVKMapKit/MVKMapKit/MVKMapView.h",
|
||||
"./MVKMapKit/MVKMapKit/MVKMapView.mm",
|
||||
'<!@(find mapbox-gl-cocoa/mapbox-gl-cocoa -type f -name "MGLMapView.*")',
|
||||
'<!@(find mapbox-gl-cocoa/mapbox-gl-cocoa -type f -name "MGLStyleFunctionValue.*")',
|
||||
'<!@(find mapbox-gl-cocoa/mapbox-gl-cocoa -type f -name "MGLTypes.*")',
|
||||
'<!@(find mapbox-gl-cocoa/mapbox-gl-cocoa -type f -name "*+MGLAdditions.*")',
|
||||
"../common/settings_nsuserdefaults.hpp",
|
||||
"../common/settings_nsuserdefaults.mm",
|
||||
"../common/foundation_request.h",
|
||||
@@ -25,7 +27,8 @@
|
||||
'mac_bundle': 1,
|
||||
'mac_bundle_resources': [
|
||||
'<!@(find img -type f)',
|
||||
'<!@(find MVKMapKit/MVKMapKit/Resources -type f)'
|
||||
'<!@(find mapbox-gl-cocoa/mapbox-gl-cocoa/Resources -type f)',
|
||||
'<(SHARED_INTERMEDIATE_DIR)/bin/style.min.js'
|
||||
],
|
||||
'link_settings': {
|
||||
'libraries': [
|
||||
|
||||
1
ios/mapbox-gl-cocoa
Submodule
1
ios/mapbox-gl-cocoa
Submodule
Submodule ios/mapbox-gl-cocoa added at 066a077f28
@@ -6,7 +6,7 @@
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'linuxapp',
|
||||
'product_name': 'llmr',
|
||||
'product_name': 'mapbox-gl',
|
||||
'type': 'executable',
|
||||
'sources': [
|
||||
'./main.cpp',
|
||||
@@ -44,13 +44,29 @@
|
||||
'libraries': [
|
||||
'<@(glfw3_libraries)',
|
||||
'<@(curl_libraries)',
|
||||
'-lboost_regex'
|
||||
],
|
||||
},
|
||||
}],
|
||||
],
|
||||
'dependencies': [
|
||||
'../llmr.gyp:llmr-x86',
|
||||
'default_stylesheet',
|
||||
],
|
||||
},
|
||||
{
|
||||
'target_name': 'default_stylesheet',
|
||||
'type': 'none',
|
||||
'hard_dependency': 1,
|
||||
'dependencies': [
|
||||
'../llmr.gyp:build_stylesheet'
|
||||
],
|
||||
'copies': [
|
||||
{
|
||||
'files': [ '<(SHARED_INTERMEDIATE_DIR)/bin/style.min.js' ],
|
||||
'destination': '<(PRODUCT_DIR)'
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#include <signal.h>
|
||||
#include <getopt.h>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
#include "../common/settings_json.hpp"
|
||||
#include "../common/glfw_view.hpp"
|
||||
@@ -40,6 +42,15 @@ int main(int argc, char *argv[]) {
|
||||
sigIntHandler.sa_flags = 0;
|
||||
sigaction(SIGINT, &sigIntHandler, NULL);
|
||||
|
||||
// read default stylesheet from disk
|
||||
std::ifstream stylefile("./style.min.js");
|
||||
if (!stylefile.good()) {
|
||||
fprintf(stderr, "Cannot read style file\n");
|
||||
return 1;
|
||||
}
|
||||
std::stringstream stylejson;
|
||||
stylejson << stylefile.rdbuf();
|
||||
|
||||
view = new GLFWView();
|
||||
llmr::Map map(*view);
|
||||
|
||||
@@ -49,6 +60,9 @@ int main(int argc, char *argv[]) {
|
||||
map.setAngle(settings.angle);
|
||||
map.setDebug(settings.debug);
|
||||
|
||||
// Load style
|
||||
map.setStyleJSON(stylejson.str());
|
||||
|
||||
int ret = view->run();
|
||||
|
||||
// Save settings
|
||||
|
||||
13
llmr.gyp
13
llmr.gyp
@@ -44,20 +44,15 @@
|
||||
'bin/style.js',
|
||||
],
|
||||
'outputs': [
|
||||
'<(SHARED_INTERMEDIATE_DIR)/include/llmr/style/resources.hpp',
|
||||
'<(SHARED_INTERMEDIATE_DIR)/src/style/resources.cpp'
|
||||
'<(SHARED_INTERMEDIATE_DIR)/bin/style.min.js'
|
||||
],
|
||||
'action': ['<@(node)', 'bin/build-style.js', '<@(_inputs)', '<(SHARED_INTERMEDIATE_DIR)']
|
||||
}
|
||||
],
|
||||
'direct_dependent_settings': {
|
||||
'sources': [
|
||||
'<(SHARED_INTERMEDIATE_DIR)/include/llmr/style/resources.hpp',
|
||||
'<(SHARED_INTERMEDIATE_DIR)/src/style/resources.cpp'
|
||||
'<(SHARED_INTERMEDIATE_DIR)/bin/style.min.js'
|
||||
],
|
||||
'include_dirs':[
|
||||
'<(SHARED_INTERMEDIATE_DIR)/include/',
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -135,7 +130,7 @@
|
||||
'shaders',
|
||||
],
|
||||
'sources': [
|
||||
'<!@(find src/ \( -name "*.cpp" ! -name resources.cpp ! -name shaders.hpp ! -name resources.hpp ! -name shaders_gles2.cpp ! -name shaders_gl.cpp \))',
|
||||
'<!@(find src/ \( -name "*.cpp" ! -name shaders.hpp ! -name shaders_gles2.cpp ! -name shaders_gl.cpp \))',
|
||||
'<!@(find src -name "*.c")',
|
||||
'<!@(find src -name "*.h")',
|
||||
'<!@(find include -name "*.hpp")',
|
||||
@@ -190,7 +185,7 @@
|
||||
}, {
|
||||
'libraries': [
|
||||
'<@(png_libraries)',
|
||||
'<@(uv_libraries)',
|
||||
'<@(uv_libraries)'
|
||||
]
|
||||
}]
|
||||
]
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'osxapp',
|
||||
'product_name': 'llmr',
|
||||
'product_name': 'Mapbox GL',
|
||||
'type': 'executable',
|
||||
'sources': [
|
||||
'./main.mm',
|
||||
@@ -21,6 +21,7 @@
|
||||
'mac_bundle': 1,
|
||||
'mac_bundle_resources': [
|
||||
'Icon.icns',
|
||||
'<(SHARED_INTERMEDIATE_DIR)/bin/style.min.js'
|
||||
],
|
||||
'xcode_settings': {
|
||||
'SDKROOT': 'macosx',
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "../common/settings_nsuserdefaults.hpp"
|
||||
#include "../common/glfw_view.hpp"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
int main() {
|
||||
GLFWView view;
|
||||
llmr::Map map(view);
|
||||
@@ -10,7 +12,13 @@ int main() {
|
||||
map.setLonLatZoom(settings.longitude, settings.latitude, settings.zoom);
|
||||
map.setAngle(settings.angle);
|
||||
map.setDebug(settings.debug);
|
||||
map.toggleRaster();
|
||||
|
||||
// Load style
|
||||
NSString *path = [[NSBundle mainBundle] pathForResource:@"style.min" ofType:@"js"];
|
||||
NSString *json = [NSString stringWithContentsOfFile:path
|
||||
encoding:[NSString defaultCStringEncoding]
|
||||
error:nil];
|
||||
map.setStyleJSON((std::string)[json cStringUsingEncoding:[NSString defaultCStringEncoding]]);
|
||||
|
||||
// fprintf(stderr, "lon: %f, lat: %f, zoom: %f, angle: %f, debug: %d\n", settings.l)
|
||||
|
||||
|
||||
33
proto/glyphs.proto
Normal file
33
proto/glyphs.proto
Normal file
@@ -0,0 +1,33 @@
|
||||
// Protocol Version 1
|
||||
|
||||
package llmr.glyphs;
|
||||
|
||||
option optimize_for = LITE_RUNTIME;
|
||||
|
||||
// Stores a glyph with metrics and optional SDF bitmap information.
|
||||
message glyph {
|
||||
required uint32 id = 1;
|
||||
|
||||
// A signed distance field of the glyph with a border of 3 pixels.
|
||||
optional bytes bitmap = 2;
|
||||
|
||||
// Glyph metrics.
|
||||
required uint32 width = 3;
|
||||
required uint32 height = 4;
|
||||
required sint32 left = 5;
|
||||
required sint32 top = 6;
|
||||
required uint32 advance = 7;
|
||||
}
|
||||
|
||||
// Stores fontstack information and a list of faces.
|
||||
message fontstack {
|
||||
required string name = 1;
|
||||
required string range = 2;
|
||||
repeated glyph glyphs = 3;
|
||||
}
|
||||
|
||||
message glyphs {
|
||||
repeated fontstack stacks = 1;
|
||||
|
||||
extensions 16 to 8191;
|
||||
}
|
||||
@@ -75,43 +75,6 @@ message feature {
|
||||
optional uint32 vertex_count = 6;
|
||||
}
|
||||
|
||||
// Stores a glyph with metrics and optional SDF bitmap information.
|
||||
message glyph {
|
||||
required uint32 id = 1;
|
||||
|
||||
// A signed distance field of the glyph with a border of 3 pixels.
|
||||
optional bytes bitmap = 2;
|
||||
|
||||
// Glyph metrics.
|
||||
required uint32 width = 3;
|
||||
required uint32 height = 4;
|
||||
required sint32 left = 5;
|
||||
required sint32 top = 6;
|
||||
required uint32 advance = 7;
|
||||
}
|
||||
|
||||
// Stores font face information and a list of glyphs.
|
||||
message face {
|
||||
required string family = 1;
|
||||
required string style = 2;
|
||||
repeated glyph glyphs = 5;
|
||||
}
|
||||
|
||||
// Stores the shaping information for the label with a given text
|
||||
message label {
|
||||
// The original value ID this shaping information is for.
|
||||
required uint32 text = 1;
|
||||
|
||||
// References the index of the font stack in the layer's fontstack array.
|
||||
required uint32 stack = 2;
|
||||
|
||||
// Parallel arrays of face ID, glyph ID and position.
|
||||
repeated uint32 faces = 3 [packed = true];
|
||||
repeated uint32 glyphs = 4 [packed = true];
|
||||
repeated uint32 x = 5 [packed = true];
|
||||
repeated uint32 y = 6 [packed = true];
|
||||
}
|
||||
|
||||
message layer {
|
||||
// Any compliant implementation must first read the version
|
||||
// number encoded in this message and choose the correct
|
||||
@@ -136,18 +99,11 @@ message layer {
|
||||
// Total vertex count in this layer.
|
||||
optional uint32 vertex_count = 6;
|
||||
|
||||
// Shaping information for labels contained in this tile.
|
||||
repeated string faces = 7;
|
||||
repeated label labels = 8;
|
||||
repeated string stacks = 9;
|
||||
|
||||
extensions 16 to max;
|
||||
}
|
||||
|
||||
message tile {
|
||||
repeated layer layers = 3;
|
||||
|
||||
repeated face faces = 4;
|
||||
|
||||
extensions 16 to 8191;
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
if [[ `lsb_release -r` =~ "12.04" ]]; then
|
||||
sudo add-apt-repository --yes ppa:ubuntu-toolchain-r/test
|
||||
fi
|
||||
sudo apt-get update -y
|
||||
sudo apt-get -y install gcc-4.8 g++-4.8
|
||||
sudo apt-get -y install pkg-config nodejs cmake automake libtool xutils-dev git
|
||||
sudo apt-get -y install libxi-dev libglu1-mesa-dev x11proto-randr-dev x11proto-xext-dev libxrandr-dev x11proto-xf86vidmode-dev libxxf86vm-dev libxcursor-dev
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
brew install autoconf automake libtool makedepend cmake pkg-config node git
|
||||
51
scripts/setup_travis.sh
Executable file
51
scripts/setup_travis.sh
Executable file
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
set -o pipefail
|
||||
|
||||
# enable testing locally (not on travis)
|
||||
if [[ "${TRAVIS_OS_NAME:-false}" == false ]]; then
|
||||
if [[ $(uname -s) == "Darwin" ]]; then
|
||||
TRAVIS_OS_NAME="osx"
|
||||
elif [[ $(uname -s) == "Darwin" ]]; then
|
||||
TRAVIS_OS_NAME="linux"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then
|
||||
|
||||
if [[ `lsb_release -r` =~ "12.04" ]]; then
|
||||
sudo add-apt-repository --yes ppa:ubuntu-toolchain-r/test
|
||||
fi
|
||||
sudo apt-get update -y
|
||||
sudo apt-get -y install gcc-4.8 g++-4.8
|
||||
sudo apt-get -y install pkg-config nodejs cmake automake libtool xutils-dev git
|
||||
sudo apt-get -y install libxi-dev libglu1-mesa-dev x11proto-randr-dev x11proto-xext-dev libxrandr-dev x11proto-xf86vidmode-dev libxxf86vm-dev libxcursor-dev
|
||||
|
||||
# use g++ that supports c++11
|
||||
if [[ ${CXX} == "g++" ]]; then
|
||||
export CXX="g++-4.8" ;
|
||||
fi
|
||||
|
||||
# Tf building in debug then turn on sanitizers.
|
||||
# They should both work with latest g++/clang++
|
||||
# but it's not worth a standalone matrix so here
|
||||
# we just test address->gcc and thread->clang for
|
||||
# some limited coverage
|
||||
if [[ ${BUILDTYPE} == "Debug" ]]; then
|
||||
if [[ ${CXX} == "g++" ]]; then
|
||||
export CXXFLAGS="-fsanitize=address ${CXXFLAGS}"
|
||||
export CFLAGS="-fsanitize=address ${CFLAGS}"
|
||||
export LDFLAGS="-fsanitize=address ${LDFLAGS}"
|
||||
elif [[ ${CXX} == "clang++" ]]; then
|
||||
export CXXFLAGS="-fsanitize=thread -fPIC ${CXXFLAGS}"
|
||||
export CFLAGS="-fsanitize=thread ${CFLAGS}"
|
||||
export LDFLAGS="-fsanitize=thread -pie ${LDFLAGS}"
|
||||
fi
|
||||
fi
|
||||
elif [[ ${TRAVIS_OS_NAME} == "osx" ]]; then
|
||||
brew install autoconf automake libtool makedepend cmake pkg-config node git
|
||||
fi
|
||||
|
||||
# turn off a few warnings
|
||||
export CXXFLAGS="-Wno-unknown-warning-option -Wno-unused-local-typedefs -Wno-unknown-pragmas ${CXXFLAGS}";
|
||||
@@ -19,7 +19,9 @@ ensure_dep pkg-config
|
||||
ensure_dep node
|
||||
if [ ${UNAME} = 'Darwin' ]; then
|
||||
ensure_dep makedepend
|
||||
ensure_dep glibtool
|
||||
if [[ ! `which libtool` ]] && [[ ! `which glibtool` ]]; then
|
||||
MISSING_DEPS="$MISSING_DEPS libtool"
|
||||
fi
|
||||
elif [ ${UNAME} = 'Linux' ]; then
|
||||
ensure_dep libtool
|
||||
ensure_dep gccmakedep
|
||||
@@ -32,7 +34,7 @@ if [[ $MISSING_DEPS != "" ]]; then
|
||||
echo 'and then re-run ./setup-libraries.sh'
|
||||
elif [ ${UNAME} = 'Linux' ]; then
|
||||
echo "Missing build deps: ${MISSING_DEPS}"
|
||||
echo 'Please run "sudo apt-get install automake libtool xutils-dev cmake pkg-config libxi-dev libglu1-mesa-dev x11proto-randr-dev x11proto-xext-dev libxrandr-dev x11proto-xf86vidmode-dev libxxf86vm-dev libxcursor-dev nodejs-legacy"'
|
||||
echo 'Please run "sudo apt-get install git build-essential zlib1g-dev automake libtool xutils-dev make cmake pkg-config nodejs-legacy libxi-dev libglu1-mesa-dev x11proto-randr-dev x11proto-xext-dev libxrandr-dev x11proto-xf86vidmode-dev libxxf86vm-dev libxcursor-dev"'
|
||||
echo 'and then re-run ./setup-libraries.sh'
|
||||
fi
|
||||
exit 1
|
||||
@@ -74,7 +76,7 @@ source MacOSX.sh
|
||||
if [ ! -f out/build-cpp11-libcpp-x86_64/lib/libuv.a ] ; then ./scripts/build_libuv.sh ; fi
|
||||
if [ ! -f out/build-cpp11-libcpp-x86_64/lib/libssl.a ] ; then ./scripts/build_openssl.sh ; fi
|
||||
if [ ! -f out/build-cpp11-libcpp-x86_64/lib/libcurl.a ] ; then ./scripts/build_curl.sh ; fi
|
||||
if [ ! -d out/build-cpp11-libcpp-x86_64/include/boost ] ; then ./scripts/build_boost.sh `pwd`/../../src/ `pwd`/../../linux/ `pwd`/../../common/ ; fi
|
||||
if [ ! -d out/build-cpp11-libcpp-x86_64/include/boost ] ; then ./scripts/build_boost.sh `pwd`/../../src/ `pwd`/../../include/ `pwd`/../../linux/ `pwd`/../../common/ ; fi
|
||||
echo ' ...done'
|
||||
|
||||
./scripts/make_universal.sh
|
||||
@@ -92,7 +94,7 @@ source Linux.sh
|
||||
if [ ! -f out/build-cpp11-libstdcpp-gcc-x86_64/lib/libuv.a ] ; then ./scripts/build_libuv.sh ; fi
|
||||
if [ ! -f out/build-cpp11-libstdcpp-gcc-x86_64/lib/libssl.a ] ; then ./scripts/build_openssl.sh ; fi
|
||||
if [ ! -f out/build-cpp11-libstdcpp-gcc-x86_64/lib/libcurl.a ] ; then ./scripts/build_curl.sh ; fi
|
||||
if [ ! -d out/build-cpp11-libstdcpp-gcc-x86_64/include/boost ] ; then ./scripts/build_boost.sh `pwd`/../../src/ `pwd`/../../linux/ `pwd`/../../common/ ; fi
|
||||
if [ ! -f out/build-cpp11-libstdcpp-gcc-x86_64/lib/libboost_regex.a ] ; then ./scripts/build_boost.sh --with-regex ; fi
|
||||
|
||||
cd ../../
|
||||
./configure \
|
||||
|
||||
@@ -2152,13 +2152,13 @@ void Clipper::IntersectEdges(TEdge *e1, TEdge *e2,
|
||||
else if (e1->PolyTyp != e2->PolyTyp)
|
||||
{
|
||||
//toggle subj open path OutIdx on/off when Abs(clip.WndCnt) == 1 ...
|
||||
if ((e1->WindDelta == 0) && abs(e2->WindCnt) == 1 &&
|
||||
if ((e1->WindDelta == 0) && std::abs(e2->WindCnt) == 1 &&
|
||||
(m_ClipType != ctUnion || e2->WindCnt2 == 0))
|
||||
{
|
||||
AddOutPt(e1, Pt);
|
||||
if (e1Contributing) e1->OutIdx = Unassigned;
|
||||
}
|
||||
else if ((e2->WindDelta == 0) && (abs(e1->WindCnt) == 1) &&
|
||||
else if ((e2->WindDelta == 0) && (std::abs(e1->WindCnt) == 1) &&
|
||||
(m_ClipType != ctUnion || e1->WindCnt2 == 0))
|
||||
{
|
||||
AddOutPt(e2, Pt);
|
||||
|
||||
@@ -22,7 +22,7 @@ GlyphAtlas::~GlyphAtlas() {
|
||||
}
|
||||
|
||||
Rect<uint16_t> GlyphAtlas::addGlyph(uint64_t tile_id, const std::string& face_name,
|
||||
const VectorTileGlyph& glyph) {
|
||||
const SDFGlyph& glyph) {
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
|
||||
// Use constant value for now.
|
||||
@@ -137,6 +137,8 @@ void GlyphAtlas::bind() {
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, width, height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, data);
|
||||
dirty = false;
|
||||
|
||||
#if defined(DEBUG)
|
||||
// platform::show_debug_image("Glyph Atlas", data, width, height);
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
@@ -30,24 +30,36 @@ bool SpriteAtlas::resize(const float newRatio) {
|
||||
pixelRatio = newRatio;
|
||||
|
||||
if (data) {
|
||||
char *old_data = data;
|
||||
uint32_t *old_data = data;
|
||||
|
||||
data = nullptr;
|
||||
allocate();
|
||||
|
||||
dimension w = static_cast<dimension>(width * newRatio);
|
||||
dimension h = static_cast<dimension>(height * newRatio);
|
||||
float s = std::pow(oldRatio / newRatio, 2);
|
||||
const int old_w = width * oldRatio;
|
||||
const int old_h = height * oldRatio;
|
||||
const int new_w = width * newRatio;
|
||||
const int new_h = height * newRatio;
|
||||
|
||||
// Basic image scaling. TODO: Replace this with better image scaling.
|
||||
uint32_t *img_new = reinterpret_cast<uint32_t *>(data);
|
||||
uint32_t *img_old = reinterpret_cast<uint32_t *>(old_data);
|
||||
for (size_t i = 0, length = w * h; i < length; i++) {
|
||||
img_new[i] = img_old[static_cast<size_t>(s * i)];
|
||||
const uint32_t *img_old = reinterpret_cast<const uint32_t *>(old_data);
|
||||
|
||||
for (int y = 0; y < new_h; y++) {
|
||||
const int old_yoffset = ((y * old_h) / new_h) * old_w;
|
||||
const int new_yoffset = y * new_w;
|
||||
for (int x = 0; x < new_w; x++) {
|
||||
const int old_x = (x * old_w) / new_w;
|
||||
img_new[new_yoffset + x] = img_old[old_yoffset + old_x];
|
||||
}
|
||||
}
|
||||
|
||||
free(old_data);
|
||||
dirty = true;
|
||||
|
||||
// Mark all sprite images as in need of update
|
||||
for (const auto &pair : images) {
|
||||
uninitialized.emplace(pair.first);
|
||||
}
|
||||
}
|
||||
|
||||
return dirty;
|
||||
@@ -185,7 +197,7 @@ void SpriteAtlas::allocate() {
|
||||
if (!data) {
|
||||
dimension w = static_cast<dimension>(width * pixelRatio);
|
||||
dimension h = static_cast<dimension>(height * pixelRatio);
|
||||
data = (char *)calloc(w * h, sizeof(uint32_t));
|
||||
data = (uint32_t *)calloc(w * h, sizeof(uint32_t));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,8 +282,9 @@ void SpriteAtlas::bind(bool linear) {
|
||||
data // const GLvoid * data
|
||||
);
|
||||
|
||||
#if defined(DEBUG)
|
||||
// platform::show_color_debug_image("Sprite Atlas", data, width, height, width * pixelRatio, height * pixelRatio);
|
||||
|
||||
#endif
|
||||
dirty = false;
|
||||
}
|
||||
};
|
||||
|
||||
147
src/map/map.cpp
147
src/map/map.cpp
@@ -1,7 +1,6 @@
|
||||
#include <llmr/map/map.hpp>
|
||||
#include <llmr/map/source.hpp>
|
||||
#include <llmr/platform/platform.hpp>
|
||||
#include <llmr/style/resources.hpp>
|
||||
#include <llmr/style/sprite.hpp>
|
||||
#include <llmr/util/transition.hpp>
|
||||
#include <llmr/util/time.hpp>
|
||||
@@ -9,6 +8,7 @@
|
||||
#include <llmr/util/clip_ids.hpp>
|
||||
#include <llmr/util/string.hpp>
|
||||
#include <llmr/util/constants.hpp>
|
||||
#include <llmr/util/uv.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
@@ -19,12 +19,13 @@ using namespace llmr;
|
||||
Map::Map(View& view)
|
||||
: view(view),
|
||||
transform(),
|
||||
texturepool(),
|
||||
style(),
|
||||
glyphAtlas(1024, 1024),
|
||||
spriteAtlas(512, 512),
|
||||
style(std::make_shared<Style>()),
|
||||
glyphAtlas(std::make_shared<GlyphAtlas>(1024, 1024)),
|
||||
glyphStore(std::make_shared<GlyphStore>()),
|
||||
spriteAtlas(std::make_shared<SpriteAtlas>(512, 512)),
|
||||
texturepool(std::make_shared<Texturepool>()),
|
||||
painter(*this),
|
||||
loop(uv_loop_new()) {
|
||||
loop(std::make_shared<uv::loop>()) {
|
||||
|
||||
view.initialize(this);
|
||||
|
||||
@@ -35,8 +36,9 @@ Map::Map(View& view)
|
||||
}
|
||||
|
||||
Map::~Map() {
|
||||
uv_loop_delete(loop);
|
||||
loop = nullptr;
|
||||
if (async) {
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
void Map::start() {
|
||||
@@ -46,15 +48,15 @@ void Map::start() {
|
||||
|
||||
// Setup async notifications
|
||||
async_terminate = new uv_async_t();
|
||||
uv_async_init(loop, async_terminate, terminate);
|
||||
async_terminate->data = loop;
|
||||
uv_async_init(**loop, async_terminate, terminate);
|
||||
async_terminate->data = **loop;
|
||||
|
||||
async_render = new uv_async_t();
|
||||
uv_async_init(loop, async_render, render);
|
||||
uv_async_init(**loop, async_render, render);
|
||||
async_render->data = this;
|
||||
|
||||
async_cleanup = new uv_async_t();
|
||||
uv_async_init(loop, async_cleanup, cleanup);
|
||||
uv_async_init(**loop, async_cleanup, cleanup);
|
||||
async_cleanup->data = this;
|
||||
|
||||
uv_thread_create(&thread, [](void *arg) {
|
||||
@@ -64,7 +66,10 @@ void Map::start() {
|
||||
}
|
||||
|
||||
void Map::stop() {
|
||||
uv_async_send(async_terminate);
|
||||
if (async_terminate != nullptr) {
|
||||
uv_async_send(async_terminate);
|
||||
}
|
||||
|
||||
uv_thread_join(&thread);
|
||||
|
||||
uv_close((uv_handle_t *)async_terminate, delete_async);
|
||||
@@ -75,7 +80,7 @@ void Map::stop() {
|
||||
async_cleanup = nullptr;
|
||||
|
||||
// Run the event loop once to make sure our async delete handlers are called.
|
||||
uv_run(loop, UV_RUN_ONCE);
|
||||
uv_run(**loop, UV_RUN_ONCE);
|
||||
|
||||
async = false;
|
||||
}
|
||||
@@ -87,7 +92,7 @@ void Map::delete_async(uv_handle_t *handle) {
|
||||
void Map::run() {
|
||||
setup();
|
||||
prepare();
|
||||
uv_run(loop, UV_RUN_DEFAULT);
|
||||
uv_run(**loop, UV_RUN_DEFAULT);
|
||||
|
||||
// If the map rendering wasn't started asynchronously, we perform one render
|
||||
// *after* all events have been processed.
|
||||
@@ -99,7 +104,7 @@ void Map::run() {
|
||||
void Map::rerender() {
|
||||
// We only send render events if we want to continuously update the map
|
||||
// (== async rendering).
|
||||
if (async) {
|
||||
if (async && async_render != nullptr) {
|
||||
uv_async_send(async_render);
|
||||
}
|
||||
}
|
||||
@@ -119,7 +124,9 @@ void Map::swapped() {
|
||||
}
|
||||
|
||||
void Map::cleanup() {
|
||||
uv_async_send(async_cleanup);
|
||||
if (async_cleanup != nullptr) {
|
||||
uv_async_send(async_cleanup);
|
||||
}
|
||||
}
|
||||
|
||||
void Map::cleanup(uv_async_t *async) {
|
||||
@@ -163,7 +170,7 @@ void Map::setup() {
|
||||
sources.emplace("outdoors",
|
||||
std::unique_ptr<Source>(new Source(*this,
|
||||
painter,
|
||||
"http://api-maps-gl.tilestream.net/v3/mapbox.mapbox-terrain-v1,mapbox.mapbox-streets-v5/%d/%d/%d.gl.pbf",
|
||||
"http://a.tiles.mapbox.com/v3/mapbox.mapbox-terrain-v1,mapbox.mapbox-streets-v5/%d/%d/%d.vector.pbf",
|
||||
Source::Type::vector,
|
||||
{{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 }},
|
||||
512,
|
||||
@@ -171,25 +178,20 @@ void Map::setup() {
|
||||
14,
|
||||
true)));
|
||||
|
||||
sources.emplace("satellite",
|
||||
std::unique_ptr<Source>(new Source(*this,
|
||||
painter,
|
||||
"https://a.tiles.mapbox.com/v3/justin.hh0gkdfm/%d/%d/%d%s.png256",
|
||||
Source::Type::raster,
|
||||
{{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 }},
|
||||
256,
|
||||
0,
|
||||
21,
|
||||
false)));
|
||||
|
||||
loadStyle(resources::style, resources::style_size);
|
||||
setStyleJSON(styleJSON);
|
||||
}
|
||||
|
||||
void Map::loadStyle(const uint8_t *const data, uint32_t bytes) {
|
||||
style.loadJSON(data, bytes);
|
||||
void Map::setStyleJSON(std::string newStyleJSON) {
|
||||
styleJSON.swap(newStyleJSON);
|
||||
style->cancelTransitions();
|
||||
style->loadJSON((const uint8_t *)styleJSON.c_str());
|
||||
update();
|
||||
}
|
||||
|
||||
std::string Map::getStyleJSON() const {
|
||||
return styleJSON;
|
||||
}
|
||||
|
||||
#pragma mark - Size
|
||||
|
||||
// Note: This function is called from another thread. Make sure you only call threadsafe functions!
|
||||
@@ -312,11 +314,11 @@ void Map::stopScaling() {
|
||||
update();
|
||||
}
|
||||
|
||||
double Map::getMinZoom() {
|
||||
double Map::getMinZoom() const {
|
||||
return transform.getMinZoom();
|
||||
}
|
||||
|
||||
double Map::getMaxZoom() {
|
||||
double Map::getMaxZoom() const {
|
||||
return transform.getMaxZoom();
|
||||
}
|
||||
|
||||
@@ -385,25 +387,22 @@ bool Map::getDebug() const {
|
||||
return debug;
|
||||
}
|
||||
|
||||
void Map::toggleRaster() {
|
||||
style.setDefaultTransitionDuration(300);
|
||||
style.cancelTransitions();
|
||||
void Map::setAppliedClasses(std::set<std::string> appliedClasses) {
|
||||
style->cancelTransitions();
|
||||
|
||||
auto it = sources.find("satellite");
|
||||
if (it != sources.end()) {
|
||||
Source &satellite_source = *it->second;
|
||||
if (satellite_source.enabled) {
|
||||
satellite_source.enabled = false;
|
||||
style.appliedClasses.erase("satellite");
|
||||
} else {
|
||||
satellite_source.enabled = true;
|
||||
style.appliedClasses.insert("satellite");
|
||||
}
|
||||
}
|
||||
style->appliedClasses.swap(appliedClasses);
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
std::set<std::string> Map::getAppliedClasses() const {
|
||||
return style->appliedClasses;
|
||||
}
|
||||
|
||||
void Map::setDefaultTransitionDuration(uint64_t duration_milliseconds) {
|
||||
style->setDefaultTransitionDuration(duration_milliseconds);
|
||||
}
|
||||
|
||||
void Map::updateTiles() {
|
||||
for (auto &pair : sources) {
|
||||
const std::unique_ptr<Source> &source = pair.second;
|
||||
@@ -451,36 +450,37 @@ void Map::prepare() {
|
||||
oldState.getFramebufferHeight() != state.getFramebufferHeight();
|
||||
|
||||
if (pixelRatioChanged) {
|
||||
style.sprite = std::make_shared<Sprite>(*this, state.getPixelRatio());
|
||||
style.sprite->load(kSpriteURL);
|
||||
style->sprite = std::make_shared<Sprite>(*this, state.getPixelRatio());
|
||||
style->sprite->load(kSpriteURL);
|
||||
|
||||
spriteAtlas.resize(state.getPixelRatio());
|
||||
spriteAtlas->resize(state.getPixelRatio());
|
||||
}
|
||||
|
||||
if (pixelRatioChanged || dimensionsChanged) {
|
||||
painter.clearFramebuffers();
|
||||
}
|
||||
|
||||
style.cascade(state.getNormalizedZoom());
|
||||
style->cascade(state.getNormalizedZoom());
|
||||
|
||||
// Update style transitions.
|
||||
animationTime = util::now();
|
||||
if (style.needsTransition()) {
|
||||
style.updateTransitions(animationTime);
|
||||
if (style->needsTransition()) {
|
||||
style->updateTransitions(animationTime);
|
||||
update();
|
||||
}
|
||||
|
||||
// Allow the sprite atlas to potentially pull new sprite images if needed.
|
||||
if (style.sprite && style.sprite->isLoaded()) {
|
||||
spriteAtlas.update(*style.sprite);
|
||||
if (style->sprite && style->sprite->isLoaded()) {
|
||||
spriteAtlas->update(*style->sprite);
|
||||
}
|
||||
|
||||
updateTiles();
|
||||
}
|
||||
|
||||
void Map::render() {
|
||||
#if defined(DEBUG)
|
||||
std::vector<std::string> debug;
|
||||
|
||||
#endif
|
||||
painter.clear();
|
||||
|
||||
painter.resetFramebuffer();
|
||||
@@ -493,6 +493,7 @@ void Map::render() {
|
||||
|
||||
painter.drawClippingMasks(sources);
|
||||
|
||||
#if defined(DEBUG)
|
||||
// Generate debug information
|
||||
size_t source_id = 0;
|
||||
for (const auto &pair : sources) {
|
||||
@@ -501,10 +502,10 @@ void Map::render() {
|
||||
debug.emplace_back(util::sprintf<100>("source %d: %d\n", source_id, count));
|
||||
source_id++;
|
||||
}
|
||||
|
||||
#endif
|
||||
// Actually render the layers
|
||||
if (debug::renderTree) { std::cout << "{" << std::endl; indent++; }
|
||||
renderLayers(style.layers);
|
||||
renderLayers(style->layers);
|
||||
if (debug::renderTree) { std::cout << "}" << std::endl; indent--; }
|
||||
|
||||
// Finalize the rendering, e.g. by calling debug render calls per tile.
|
||||
@@ -520,7 +521,9 @@ void Map::render() {
|
||||
|
||||
painter.renderMatte();
|
||||
|
||||
#if defined(DEBUG)
|
||||
painter.renderDebugText(debug);
|
||||
#endif
|
||||
|
||||
// Schedule another rerender when we definitely need a next frame.
|
||||
if (transform.needsTransition()) {
|
||||
@@ -573,19 +576,25 @@ void Map::renderLayers(const std::vector<LayerDescription>& layers) {
|
||||
template <typename Styles>
|
||||
typename Styles::const_iterator find_style(const Styles &styles, const LayerDescription &layer_desc) {
|
||||
auto it = styles.find(layer_desc.name);
|
||||
#if defined(DEBUG)
|
||||
if (it == styles.end()) {
|
||||
if (debug::renderWarnings) {
|
||||
fprintf(stderr, "[WARNING] can't find class named '%s' in style\n",
|
||||
layer_desc.name.c_str());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return it;
|
||||
}
|
||||
|
||||
template <typename Styles>
|
||||
bool is_invisible(const Styles &styles, const LayerDescription &layer_desc) {
|
||||
auto it = find_style(styles, layer_desc);
|
||||
if (it == styles.end()) { return true; }
|
||||
if (it == styles.end()) {
|
||||
// We don't have a style, so we fall back to the default style which is
|
||||
// always visible.
|
||||
return false;
|
||||
}
|
||||
return !it->second.isVisible();
|
||||
}
|
||||
|
||||
@@ -593,8 +602,8 @@ void Map::renderLayer(const LayerDescription& layer_desc, RenderPass pass) {
|
||||
if (layer_desc.child_layer.size()) {
|
||||
// This is a layer group. We render them during our translucent render pass.
|
||||
if (pass == Translucent) {
|
||||
auto it = find_style(style.computed.composites, layer_desc);
|
||||
const CompositeProperties &properties = (it != style.computed.composites.end()) ? it->second : defaultCompositeProperties;
|
||||
auto it = find_style(style->computed.composites, layer_desc);
|
||||
const CompositeProperties &properties = (it != style->computed.composites.end()) ? it->second : defaultCompositeProperties;
|
||||
if (properties.isVisible()) {
|
||||
gl::group group(std::string("group: ") + layer_desc.name);
|
||||
|
||||
@@ -623,31 +632,31 @@ void Map::renderLayer(const LayerDescription& layer_desc, RenderPass pass) {
|
||||
// This is a singular layer. Try to find the bucket associated with
|
||||
// this layer and render it.
|
||||
|
||||
auto bucket_it = style.buckets.find(layer_desc.bucket_name);
|
||||
if (bucket_it != style.buckets.end()) {
|
||||
auto bucket_it = style->buckets.find(layer_desc.bucket_name);
|
||||
if (bucket_it != style->buckets.end()) {
|
||||
const BucketDescription &bucket_desc = bucket_it->second;
|
||||
|
||||
// Abort early if we can already deduce from the bucket type that
|
||||
// we're not going to render anything anyway during this pass.
|
||||
switch (bucket_desc.type) {
|
||||
case BucketType::Fill:
|
||||
if (is_invisible(style.computed.fills, layer_desc)) return;
|
||||
if (is_invisible(style->computed.fills, layer_desc)) return;
|
||||
break;
|
||||
case BucketType::Line:
|
||||
if (pass == Opaque) return;
|
||||
if (is_invisible(style.computed.lines, layer_desc)) return;
|
||||
if (is_invisible(style->computed.lines, layer_desc)) return;
|
||||
break;
|
||||
case BucketType::Icon:
|
||||
if (pass == Opaque) return;
|
||||
if (is_invisible(style.computed.icons, layer_desc)) return;
|
||||
if (is_invisible(style->computed.icons, layer_desc)) return;
|
||||
break;
|
||||
case BucketType::Text:
|
||||
if (pass == Opaque) return;
|
||||
if (is_invisible(style.computed.texts, layer_desc)) return;
|
||||
if (is_invisible(style->computed.texts, layer_desc)) return;
|
||||
break;
|
||||
case BucketType::Raster:
|
||||
if (pass == Translucent) return;
|
||||
if (is_invisible(style.computed.rasters, layer_desc)) return;
|
||||
if (is_invisible(style->computed.rasters, layer_desc)) return;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -42,7 +42,9 @@ void TileData::request() {
|
||||
// Schedule tile parsing in another thread
|
||||
tile->reparse();
|
||||
} else {
|
||||
#if defined(DEBUG)
|
||||
fprintf(stderr, "[%s] tile loading failed: %d, %s\n", tile->url.c_str(), res->code, res->error_message.c_str());
|
||||
#endif
|
||||
}
|
||||
}, map.getLoop());
|
||||
}
|
||||
@@ -54,12 +56,23 @@ void TileData::cancel() {
|
||||
}
|
||||
}
|
||||
|
||||
void TileData::beforeParse() {}
|
||||
|
||||
void TileData::reparse() {
|
||||
beforeParse();
|
||||
|
||||
// We're creating a new work request. The work request deletes itself after it executed
|
||||
// the after work handler
|
||||
new uv::work<std::shared_ptr<TileData>>(
|
||||
map.getLoop(),
|
||||
[](std::shared_ptr<TileData> &tile) { tile->parse(); },
|
||||
[](std::shared_ptr<TileData> &tile) { tile->map.update(); },
|
||||
[](std::shared_ptr<TileData> &tile) {
|
||||
tile->parse();
|
||||
},
|
||||
[](std::shared_ptr<TileData> &tile) {
|
||||
tile->afterParse();
|
||||
tile->map.update();
|
||||
},
|
||||
shared_from_this());
|
||||
}
|
||||
|
||||
void TileData::afterParse() {}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <llmr/map/tile_parser.hpp>
|
||||
|
||||
#include <llmr/map/vector_tile_data.hpp>
|
||||
#include <llmr/style/style.hpp>
|
||||
#include <llmr/renderer/fill_bucket.hpp>
|
||||
#include <llmr/renderer/line_bucket.hpp>
|
||||
@@ -9,43 +10,59 @@
|
||||
#include <llmr/util/raster.hpp>
|
||||
#include <llmr/util/constants.hpp>
|
||||
#include <llmr/geometry/glyph_atlas.hpp>
|
||||
#include <llmr/text/glyph_store.hpp>
|
||||
#include <llmr/text/glyph.hpp>
|
||||
|
||||
#include <llmr/util/std.hpp>
|
||||
#include <llmr/util/utf.hpp>
|
||||
|
||||
#ifdef __linux__
|
||||
#include <boost/regex.hpp>
|
||||
namespace regex_impl = boost;
|
||||
#else
|
||||
#include <regex>
|
||||
namespace regex_impl = std;
|
||||
#endif
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
|
||||
TileParser::TileParser(const std::string& data, VectorTileData& tile, const Style& style, GlyphAtlas& glyphAtlas, SpriteAtlas &spriteAtlas)
|
||||
TileParser::TileParser(const std::string &data, VectorTileData &tile,
|
||||
const std::shared_ptr<const Style> &style,
|
||||
const std::shared_ptr<GlyphAtlas> &glyphAtlas,
|
||||
const std::shared_ptr<GlyphStore> &glyphStore,
|
||||
const std::shared_ptr<SpriteAtlas> &spriteAtlas)
|
||||
: vector_data(pbf((const uint8_t *)data.data(), data.size())),
|
||||
tile(tile),
|
||||
style(style),
|
||||
glyphAtlas(glyphAtlas),
|
||||
glyphStore(glyphStore),
|
||||
spriteAtlas(spriteAtlas),
|
||||
placement(tile.id.z) {
|
||||
parseGlyphs();
|
||||
parseStyleLayers(style.layers);
|
||||
}
|
||||
|
||||
bool TileParser::obsolete() const {
|
||||
return tile.state == TileData::State::obsolete;
|
||||
void TileParser::parse() {
|
||||
parseStyleLayers(style->layers);
|
||||
}
|
||||
|
||||
void TileParser::parseGlyphs() {
|
||||
for (const std::pair<std::string, const VectorTileFace> pair : vector_data.faces) {
|
||||
const std::string &name = pair.first;
|
||||
const VectorTileFace &face = pair.second;
|
||||
bool TileParser::obsolete() const { return tile.state == TileData::State::obsolete; }
|
||||
|
||||
GlyphPositions &glyphs = faces[name];
|
||||
for (const VectorTileGlyph &glyph : face.glyphs) {
|
||||
const Rect<uint16_t> rect =
|
||||
glyphAtlas.addGlyph(tile.id.to_uint64(), name, glyph);
|
||||
glyphs.emplace(glyph.id, Glyph{rect, glyph.metrics});
|
||||
void TileParser::addGlyph(uint64_t tileid, const std::string stackname,
|
||||
const std::u32string &string, const FontStack &fontStack,
|
||||
GlyphAtlas &glyphAtlas, GlyphPositions &face) {
|
||||
const std::map<uint32_t, SDFGlyph> &sdfs = fontStack.getSDFs();
|
||||
// Loop through all characters and add glyph to atlas, positions.
|
||||
for (uint32_t chr : string) {
|
||||
auto sdf_it = sdfs.find(chr);
|
||||
if (sdf_it != sdfs.end()) {
|
||||
const SDFGlyph& sdf = sdf_it->second;
|
||||
const Rect<uint16_t> rect = glyphAtlas.addGlyph(tileid, stackname, sdf);
|
||||
face.emplace(chr, Glyph{rect, sdf.metrics});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TileParser::parseStyleLayers(const std::vector<LayerDescription>& layers) {
|
||||
for (const LayerDescription& layer_desc : layers) {
|
||||
void TileParser::parseStyleLayers(const std::vector<LayerDescription> &layers) {
|
||||
for (const LayerDescription &layer_desc : layers) {
|
||||
// Cancel early when parsing.
|
||||
if (obsolete()) {
|
||||
return;
|
||||
@@ -61,12 +78,11 @@ void TileParser::parseStyleLayers(const std::vector<LayerDescription>& layers) {
|
||||
// parse this bucket.
|
||||
auto bucket_it = tile.buckets.find(layer_desc.bucket_name);
|
||||
if (bucket_it == tile.buckets.end()) {
|
||||
auto bucket_it = style.buckets.find(layer_desc.bucket_name);
|
||||
auto bucket_it = style->buckets.find(layer_desc.bucket_name);
|
||||
if (layer_desc.bucket_name == "background") {
|
||||
// background is a special, fake bucket
|
||||
continue;
|
||||
}
|
||||
else if (bucket_it != style.buckets.end()) {
|
||||
} else if (bucket_it != style->buckets.end()) {
|
||||
// Only create the new bucket if we have an actual specification
|
||||
// for it.
|
||||
std::unique_ptr<Bucket> bucket = createBucket(bucket_it->second);
|
||||
@@ -88,10 +104,10 @@ void TileParser::parseStyleLayers(const std::vector<LayerDescription>& layers) {
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Bucket> TileParser::createBucket(const BucketDescription& bucket_desc) {
|
||||
std::unique_ptr<Bucket> TileParser::createBucket(const BucketDescription &bucket_desc) {
|
||||
auto layer_it = vector_data.layers.find(bucket_desc.source_layer);
|
||||
if (layer_it != vector_data.layers.end()) {
|
||||
const VectorTileLayer& layer = layer_it->second;
|
||||
const VectorTileLayer &layer = layer_it->second;
|
||||
if (bucket_desc.type == BucketType::Fill) {
|
||||
return createFillBucket(layer, bucket_desc);
|
||||
} else if (bucket_desc.type == BucketType::Line) {
|
||||
@@ -116,10 +132,12 @@ std::unique_ptr<Bucket> TileParser::createBucket(const BucketDescription& bucket
|
||||
}
|
||||
|
||||
template <class Bucket>
|
||||
void TileParser::addBucketFeatures(Bucket& bucket, const VectorTileLayer& layer, const BucketDescription& bucket_desc) {
|
||||
void TileParser::addBucketFeatures(Bucket &bucket, const VectorTileLayer &layer,
|
||||
const BucketDescription &bucket_desc) {
|
||||
FilteredVectorTileLayer filtered_layer(layer, bucket_desc);
|
||||
for (pbf feature : filtered_layer) {
|
||||
if (obsolete()) return;
|
||||
if (obsolete())
|
||||
return;
|
||||
|
||||
while (feature.next(4)) { // geometry
|
||||
pbf geometry_pbf = feature.message();
|
||||
@@ -132,60 +150,123 @@ void TileParser::addBucketFeatures(Bucket& bucket, const VectorTileLayer& layer,
|
||||
}
|
||||
}
|
||||
|
||||
template <class Bucket, typename ...Args>
|
||||
void TileParser::addBucketFeatures(Bucket& bucket, const VectorTileLayer& layer, const BucketDescription& bucket_desc, Args&& ...args) {
|
||||
template <class Bucket, typename... Args>
|
||||
void TileParser::addBucketFeatures(Bucket &bucket, const VectorTileLayer &layer,
|
||||
const BucketDescription &bucket_desc, Args &&... args) {
|
||||
FilteredVectorTileLayer filtered_layer(layer, bucket_desc);
|
||||
for (const pbf& feature_pbf : filtered_layer) {
|
||||
if (obsolete()) return;
|
||||
bucket->addFeature({ feature_pbf, layer }, std::forward<Args>(args)...);
|
||||
for (const pbf &feature_pbf : filtered_layer) {
|
||||
if (obsolete())
|
||||
return;
|
||||
bucket->addFeature({feature_pbf, layer}, std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Bucket> TileParser::createFillBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc) {
|
||||
std::unique_ptr<Bucket> TileParser::createFillBucket(const VectorTileLayer &layer,
|
||||
const BucketDescription &bucket_desc) {
|
||||
std::unique_ptr<FillBucket> bucket = std::make_unique<FillBucket>(
|
||||
tile.fillVertexBuffer, tile.triangleElementsBuffer, tile.lineElementsBuffer, bucket_desc);
|
||||
addBucketFeatures(bucket, layer, bucket_desc);
|
||||
return obsolete() ? nullptr : std::move(bucket);
|
||||
}
|
||||
|
||||
std::unique_ptr<Bucket> TileParser::createLineBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc) {
|
||||
std::unique_ptr<Bucket> TileParser::createLineBucket(const VectorTileLayer &layer,
|
||||
const BucketDescription &bucket_desc) {
|
||||
std::unique_ptr<LineBucket> bucket = std::make_unique<LineBucket>(
|
||||
tile.lineVertexBuffer, tile.triangleElementsBuffer, tile.pointElementsBuffer, bucket_desc);
|
||||
addBucketFeatures(bucket, layer, bucket_desc);
|
||||
return obsolete() ? nullptr : std::move(bucket);
|
||||
}
|
||||
|
||||
std::unique_ptr<Bucket> TileParser::createIconBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc) {
|
||||
std::unique_ptr<IconBucket> bucket = std::make_unique<IconBucket>(
|
||||
tile.iconVertexBuffer, bucket_desc);
|
||||
addBucketFeatures(bucket, layer, bucket_desc, spriteAtlas);
|
||||
std::unique_ptr<Bucket> TileParser::createIconBucket(const VectorTileLayer &layer,
|
||||
const BucketDescription &bucket_desc) {
|
||||
std::unique_ptr<IconBucket> bucket =
|
||||
std::make_unique<IconBucket>(tile.iconVertexBuffer, bucket_desc);
|
||||
addBucketFeatures(bucket, layer, bucket_desc, *spriteAtlas);
|
||||
return obsolete() ? nullptr : std::move(bucket);
|
||||
}
|
||||
|
||||
std::unique_ptr<Bucket> TileParser::createTextBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc) {
|
||||
|
||||
// Determine the correct text stack.
|
||||
if (!layer.shaping.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// TODO: currently hardcoded to use the first font stack.
|
||||
const std::map<Value, Shaping>& shaping = layer.shaping.begin()->second;
|
||||
|
||||
const Faces& const_faces = faces;
|
||||
|
||||
IndexedFaces faces;
|
||||
for (const std::string& face : layer.faces) {
|
||||
auto it = const_faces.find(face);
|
||||
if (it == const_faces.end()) {
|
||||
// This layer references an unknown face.
|
||||
return nullptr;
|
||||
}
|
||||
faces.push_back(&it->second);
|
||||
}
|
||||
typedef std::pair<uint16_t, uint16_t> GlyphRange;
|
||||
|
||||
std::unique_ptr<Bucket> TileParser::createTextBucket(const VectorTileLayer &layer,
|
||||
const BucketDescription &bucket_desc) {
|
||||
std::unique_ptr<TextBucket> bucket = std::make_unique<TextBucket>(
|
||||
tile.textVertexBuffer, tile.triangleElementsBuffer, bucket_desc, placement);
|
||||
addBucketFeatures(bucket, layer, bucket_desc, faces, shaping);
|
||||
|
||||
util::utf8_to_utf32 ucs4conv;
|
||||
regex_impl::regex token_regex("\\{\\{(\\w+)\\}\\}");
|
||||
const auto tokens_end = regex_impl::sregex_token_iterator();
|
||||
|
||||
|
||||
std::vector<std::pair<std::u32string, pbf>> labels;
|
||||
|
||||
// Determine and load glyph ranges
|
||||
{
|
||||
std::set<GlyphRange> ranges;
|
||||
|
||||
FilteredVectorTileLayer filtered_layer(layer, bucket_desc);
|
||||
for (const pbf &feature_pbf : filtered_layer) {
|
||||
if (obsolete())
|
||||
return nullptr;
|
||||
VectorTileFeature feature{feature_pbf, layer};
|
||||
|
||||
const std::string &field = bucket_desc.geometry.field;
|
||||
std::string source_string;
|
||||
source_string.reserve(field.size());
|
||||
|
||||
bool token = false;
|
||||
for (auto token_it = regex_impl::sregex_token_iterator(field.begin(), field.end(), token_regex, {-1, 1}); token_it != tokens_end; ++token_it, token = !token) {
|
||||
if (!token_it->matched) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (token) {
|
||||
auto it_prop = feature.properties.find(token_it->str());
|
||||
if (it_prop == feature.properties.end()) {
|
||||
// feature does not have the correct property
|
||||
if (debug::labelTextMissingWarning) {
|
||||
fprintf(stderr,
|
||||
"[WARNING] feature doesn't have property '%s' required for labelling\n",
|
||||
token_it->str().c_str());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
source_string += toString(it_prop->second);
|
||||
} else {
|
||||
source_string += token_it->str();
|
||||
}
|
||||
}
|
||||
|
||||
const std::u32string string = ucs4conv.convert(source_string);
|
||||
|
||||
// Loop through all characters of this text and collect unique codepoints.
|
||||
for (uint32_t chr : string) {
|
||||
ranges.insert(getGlyphRange(chr));
|
||||
}
|
||||
|
||||
labels.emplace_back(string, feature.geometry);
|
||||
}
|
||||
|
||||
glyphStore->waitForGlyphRanges(bucket_desc.geometry.font, ranges);
|
||||
}
|
||||
|
||||
// Create a copy!
|
||||
const FontStack &fontStack = glyphStore->getFontStack(bucket_desc.geometry.font);
|
||||
GlyphPositions face;
|
||||
|
||||
// Shape and place all labels.
|
||||
for (const std::pair<std::u32string, pbf> &label : labels) {
|
||||
|
||||
// Shape labels.
|
||||
const Shaping shaping = fontStack.getShaping(label.first, bucket_desc.geometry.max_width,
|
||||
bucket_desc.geometry.line_height, bucket_desc.geometry.alignment,
|
||||
bucket_desc.geometry.vertical_alignment, bucket_desc.geometry.letter_spacing);
|
||||
|
||||
// Place labels.
|
||||
addGlyph(tile.id.to_uint64(), bucket_desc.geometry.font, label.first, fontStack, *glyphAtlas,
|
||||
face);
|
||||
|
||||
bucket->addFeature(label.second, face, shaping);
|
||||
}
|
||||
|
||||
return std::move(bucket);
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ bool Transform::resize(const uint16_t w, const uint16_t h, const float ratio,
|
||||
current.pixelRatio = final.pixelRatio = ratio;
|
||||
current.framebuffer[0] = final.framebuffer[0] = fb_w;
|
||||
current.framebuffer[1] = final.framebuffer[1] = fb_h;
|
||||
if (!canRotate()) _setAngle(0);
|
||||
if (!canRotate() && current.angle) _setAngle(0);
|
||||
constrain(current.scale, current.y);
|
||||
platform::notify_map_change();
|
||||
return true;
|
||||
@@ -60,9 +60,9 @@ void Transform::_moveBy(const double dx, const double dy, const time duration) {
|
||||
|
||||
// Un-rotate when rotated and panning far enough to show off-world in corners.
|
||||
double w = final.scale * util::tileSize / 2;
|
||||
double m = std::sqrt(pow((current.width / 2), 2) + pow((current.height / 2), 2));
|
||||
double x = std::abs(sqrt(pow(final.x, 2) + pow(final.y, 2)));
|
||||
if (current.angle && w - x < m) _setAngle(0, 500_milliseconds, true);
|
||||
double m = std::sqrt(std::pow((current.width / 2), 2) + pow((current.height / 2), 2));
|
||||
double x = std::abs(sqrt(std::pow(final.x, 2) + std::pow(final.y, 2)));
|
||||
if (current.angle && w - x < m) _setAngle(0);
|
||||
|
||||
if (duration == 0) {
|
||||
current.x = final.x;
|
||||
@@ -206,7 +206,7 @@ void Transform::stopScaling() {
|
||||
_clearScaling();
|
||||
}
|
||||
|
||||
double Transform::getMinZoom() {
|
||||
double Transform::getMinZoom() const {
|
||||
double test_scale = current.scale;
|
||||
double test_y = current.y;
|
||||
constrain(test_scale, test_y);
|
||||
@@ -214,7 +214,7 @@ double Transform::getMinZoom() {
|
||||
return std::log2(std::fmin(min_scale, test_scale));
|
||||
}
|
||||
|
||||
double Transform::getMaxZoom() {
|
||||
double Transform::getMaxZoom() const {
|
||||
return std::log2(max_scale);
|
||||
}
|
||||
|
||||
@@ -272,7 +272,7 @@ void Transform::_setScaleXY(const double new_scale, const double xn, const doubl
|
||||
constrain(final.scale, final.y);
|
||||
|
||||
// Undo rotation at low zooms.
|
||||
if (!canRotate() && current.angle) _setAngle(0, 500_milliseconds, true);
|
||||
if (!canRotate() && current.angle) _setAngle(0);
|
||||
|
||||
if (duration == 0) {
|
||||
current.scale = final.scale;
|
||||
@@ -299,7 +299,7 @@ void Transform::_setScaleXY(const double new_scale, const double xn, const doubl
|
||||
|
||||
#pragma mark - Constraints
|
||||
|
||||
void Transform::constrain(double& scale, double& y) {
|
||||
void Transform::constrain(double& scale, double& y) const {
|
||||
// Constrain minimum zoom to avoid zooming out far enough to show off-world areas.
|
||||
if (scale < (current.height / util::tileSize)) scale = (current.height / util::tileSize);
|
||||
|
||||
@@ -367,7 +367,7 @@ void Transform::setAngle(const double new_angle, const double cx, const double c
|
||||
}
|
||||
}
|
||||
|
||||
void Transform::_setAngle(double new_angle, const time duration, bool disable_interaction) {
|
||||
void Transform::_setAngle(double new_angle, const time duration) {
|
||||
// This is only called internally, so we don't need a lock here.
|
||||
|
||||
while (new_angle > M_PI)
|
||||
@@ -382,18 +382,10 @@ void Transform::_setAngle(double new_angle, const time duration, bool disable_in
|
||||
|
||||
if (duration == 0) {
|
||||
current.angle = final.angle;
|
||||
transitions.remove(interaction_timeout);
|
||||
current.interactive = true;
|
||||
} else {
|
||||
time start = util::now();
|
||||
transitions.emplace_front(std::make_shared<util::ease_transition<double>>(
|
||||
current.angle, final.angle, current.angle, start, duration));
|
||||
if (disable_interaction) {
|
||||
transitions.remove(interaction_timeout);
|
||||
current.interactive = false;
|
||||
interaction_timeout = std::make_shared<util::timeout<bool>>(true, current.interactive, start, duration);
|
||||
transitions.emplace_front(interaction_timeout);
|
||||
}
|
||||
}
|
||||
|
||||
platform::notify_map_change();
|
||||
@@ -434,7 +426,7 @@ void Transform::_clearRotating() {
|
||||
}
|
||||
|
||||
bool Transform::canRotate() {
|
||||
return (current.scale >= MIN_ROTATE_SCALE);
|
||||
return (current.scale > MIN_ROTATE_SCALE);
|
||||
}
|
||||
|
||||
#pragma mark - Transition
|
||||
|
||||
@@ -122,13 +122,6 @@ bool TransformState::isChanging() const {
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Interactive
|
||||
|
||||
bool TransformState::isInteractive() const {
|
||||
return interactive;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - (private helper functions)
|
||||
|
||||
|
||||
|
||||
@@ -70,9 +70,6 @@ VectorTile::VectorTile(pbf tile) {
|
||||
if (tile.tag == 3) { // layer
|
||||
VectorTileLayer layer(tile.message());
|
||||
layers.emplace(layer.name, std::forward<VectorTileLayer>(layer));
|
||||
} else if (tile.tag == 4) { // face
|
||||
VectorTileFace face(tile.message());
|
||||
faces.emplace(face.name, std::forward<VectorTileFace>(face));
|
||||
} else {
|
||||
tile.skip();
|
||||
}
|
||||
@@ -86,218 +83,141 @@ VectorTile& VectorTile::operator=(VectorTile && other) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
VectorTileFace::VectorTileFace(pbf face) {
|
||||
while (face.next()) {
|
||||
if (face.tag == 1) { // family
|
||||
family = face.string();
|
||||
} else if (face.tag == 2) { // style
|
||||
style = face.string();
|
||||
} else if (face.tag == 5) { // glyphs
|
||||
glyphs.emplace_back(face.message());
|
||||
} else {
|
||||
face.skip();
|
||||
}
|
||||
}
|
||||
name = family + " " + style;
|
||||
}
|
||||
|
||||
|
||||
std::ostream& llmr::operator<<(std::ostream& os, const VectorTileFace& face) {
|
||||
os << "Face: " << face.name << std::endl;
|
||||
for (const VectorTileGlyph& glyph : face.glyphs) {
|
||||
os << " - " << glyph;
|
||||
}
|
||||
return os;
|
||||
}
|
||||
|
||||
|
||||
// -1 overflows to maximum value
|
||||
VectorTileGlyph::VectorTileGlyph() {}
|
||||
|
||||
VectorTileGlyph::VectorTileGlyph(pbf glyph) {
|
||||
while (glyph.next()) {
|
||||
if (glyph.tag == 1) { // id
|
||||
id = glyph.varint();
|
||||
} else if (glyph.tag == 2) { // bitmap
|
||||
bitmap = glyph.string();
|
||||
} else if (glyph.tag == 3) { // width
|
||||
metrics.width = glyph.varint();
|
||||
} else if (glyph.tag == 4) { // height
|
||||
metrics.height = glyph.varint();
|
||||
} else if (glyph.tag == 5) { // left
|
||||
metrics.left = glyph.svarint();
|
||||
} else if (glyph.tag == 6) { // top
|
||||
metrics.top = glyph.svarint();
|
||||
} else if (glyph.tag == 7) { // advance
|
||||
metrics.advance = glyph.varint();
|
||||
} else {
|
||||
glyph.skip();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::ostream& llmr::operator<<(std::ostream& os, const VectorTileGlyph& glyph) {
|
||||
os << "Glyph " << glyph.id << ": " << glyph.metrics.width << "x" << glyph.metrics.height <<
|
||||
" (" << glyph.metrics.left << "/" << glyph.metrics.top << ") +" << glyph.metrics.advance << std::endl;
|
||||
return os;
|
||||
}
|
||||
|
||||
class VectorTileLabel {
|
||||
public:
|
||||
VectorTileLabel(pbf data);
|
||||
|
||||
uint32_t text = 0;
|
||||
uint32_t stack = 0;
|
||||
std::vector<GlyphPlacement> placements;
|
||||
};
|
||||
|
||||
VectorTileLabel::VectorTileLabel(pbf label) {
|
||||
pbf faces, glyphs, x, y;
|
||||
|
||||
while (label.next()) {
|
||||
if (label.tag == 1) { // text
|
||||
text = label.varint();
|
||||
} else if (label.tag == 2) { // stack
|
||||
stack = label.varint();
|
||||
} else if (label.tag == 3) { // faces
|
||||
faces = label.message();
|
||||
} else if (label.tag == 4) { // glyphs
|
||||
glyphs = label.message();
|
||||
} else if (label.tag == 5) { // x
|
||||
x = label.message();
|
||||
} else if (label.tag == 6) { // y
|
||||
y = label.message();
|
||||
} else {
|
||||
label.skip();
|
||||
}
|
||||
}
|
||||
|
||||
while (faces && glyphs && x && y) {
|
||||
placements.emplace_back(faces.varint(), glyphs.varint(), x.varint(), y.varint());
|
||||
}
|
||||
}
|
||||
|
||||
VectorTileLayer::VectorTileLayer(pbf layer) : data(layer) {
|
||||
std::vector<std::string> stacks;
|
||||
std::vector<VectorTileLabel> labels;
|
||||
|
||||
while (layer.next()) {
|
||||
if (layer.tag == 1) { // name
|
||||
name = layer.string();
|
||||
} else if (layer.tag == 3) { // keys
|
||||
keys.emplace_back(layer.string());
|
||||
key_index.emplace(keys.back(), keys.size() - 1);
|
||||
} else if (layer.tag == 4) { // values
|
||||
values.emplace_back(std::move(parseValue(layer.message())));
|
||||
} else if (layer.tag == 5) { // extent
|
||||
extent = layer.varint();
|
||||
} else if (layer.tag == 7) { // faces
|
||||
faces.emplace_back(layer.string());
|
||||
} else if (layer.tag == 8) { // labels
|
||||
labels.emplace_back(layer.message());
|
||||
} else if (layer.tag == 9) { // stacks
|
||||
stacks.emplace_back(layer.string());
|
||||
} else {
|
||||
layer.skip();
|
||||
}
|
||||
}
|
||||
|
||||
// Build index of [stack][text] => shaping information
|
||||
for (VectorTileLabel& label : labels) {
|
||||
if (values.size() <= label.text || stacks.size() <= label.stack) {
|
||||
// The indices are out of range. Skip this label.
|
||||
continue;
|
||||
}
|
||||
|
||||
const Value& text = values[label.text];
|
||||
const std::string& stack = stacks[label.stack];
|
||||
|
||||
shaping[stack][text].swap(label.placements);
|
||||
}
|
||||
}
|
||||
|
||||
std::ostream& llmr::operator<<(std::ostream& os, const GlyphPlacement& placement) {
|
||||
os << "Glyph " << placement.face << "/" << placement.glyph << ": " << placement.x << "/" << placement.y << std::endl;
|
||||
return os;
|
||||
}
|
||||
|
||||
FilteredVectorTileLayer::FilteredVectorTileLayer(const VectorTileLayer& layer, const BucketDescription& bucket_desc)
|
||||
: layer(layer),
|
||||
bucket_desc(bucket_desc) {
|
||||
// If we filter further by field/value, parse the key/value indices first
|
||||
// because protobuf doesn't mandate a particular key order.
|
||||
if (bucket_desc.source_field.size()) {
|
||||
// Get the ID of the desired layer key.
|
||||
auto key_it = std::find(layer.keys.begin(), layer.keys.end(), bucket_desc.source_field);
|
||||
if (key_it != layer.keys.end()) {
|
||||
key = (int32_t)(key_it - layer.keys.begin());
|
||||
}
|
||||
// Get the ID of the desired layer value(s), comparing as strings in
|
||||
// order to avoid numeric type mismatches between the layer and the
|
||||
// style definition.
|
||||
for (const Value& source_value : bucket_desc.source_value) {
|
||||
auto value_it = std::find_if(layer.values.begin(), layer.values.end(), util::relaxed_equal(source_value));
|
||||
if (value_it != layer.values.end()) {
|
||||
values.insert((uint32_t)(value_it - layer.values.begin()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FilteredVectorTileLayer::iterator FilteredVectorTileLayer::begin() const {
|
||||
if (bucket_desc.source_field.size() && (key < 0 || values.empty())) {
|
||||
// There are no valid values that we could possibly find. Abort early.
|
||||
return end();
|
||||
} else {
|
||||
return iterator(*this, layer.data);
|
||||
}
|
||||
return iterator(*this, layer.data);
|
||||
}
|
||||
|
||||
FilteredVectorTileLayer::iterator FilteredVectorTileLayer::end() const {
|
||||
return iterator(*this, pbf(layer.data.end, 0));
|
||||
}
|
||||
|
||||
FilteredVectorTileLayer::iterator::iterator(const FilteredVectorTileLayer& filter, const pbf& data)
|
||||
: filter(filter),
|
||||
FilteredVectorTileLayer::iterator::iterator(const FilteredVectorTileLayer& parent, const pbf& data)
|
||||
: parent(parent),
|
||||
feature(pbf()),
|
||||
data(data) {
|
||||
operator++();
|
||||
}
|
||||
|
||||
bool FilteredVectorTileLayer::iterator::matchesFilterExpression(const PropertyFilterExpression &filterExpression, const pbf &tags_pbf) {
|
||||
if (filterExpression.is<util::recursive_wrapper<PropertyFilter>>()) {
|
||||
return matchesFilter(filterExpression.get<util::recursive_wrapper<PropertyFilter>>().get(), tags_pbf);
|
||||
} else if (filterExpression.is<util::recursive_wrapper<PropertyExpression>>()) {
|
||||
return matchesExpression(filterExpression.get<util::recursive_wrapper<PropertyExpression>>().get(), tags_pbf);
|
||||
} else if (filterExpression.is<std::true_type>()) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool FilteredVectorTileLayer::iterator::matchesFilter(const PropertyFilter &filter, const pbf &const_tags_pbf) {
|
||||
auto field_it = parent.layer.key_index.find(filter.field);
|
||||
if (field_it != parent.layer.key_index.end()) {
|
||||
const uint32_t filter_key = field_it->second;
|
||||
|
||||
// Now loop through all the key/value pair tags.
|
||||
// tags are packed varints. They should have an even length.
|
||||
pbf tags_pbf = const_tags_pbf;
|
||||
while (tags_pbf) {
|
||||
uint32_t tag_key = tags_pbf.varint();
|
||||
if (!tags_pbf) {
|
||||
// This should not happen; otherwise the vector tile
|
||||
// is invalid.
|
||||
throw std::runtime_error("uneven number of feature tag ids");
|
||||
}
|
||||
uint32_t tag_val = tags_pbf.varint();
|
||||
|
||||
if (tag_key == filter_key) {
|
||||
if (parent.layer.values.size() > tag_val) {
|
||||
const Value &value = parent.layer.values[tag_val];
|
||||
return filter.compare(value);
|
||||
} else {
|
||||
throw std::runtime_error("feature references out of range value");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The feature doesn't contain the field that we're looking to compare.
|
||||
// Depending on the filter, this may still be okay.
|
||||
return filter.isMissingFieldOkay();
|
||||
}
|
||||
|
||||
bool FilteredVectorTileLayer::iterator::matchesExpression(const PropertyExpression &expression, const pbf &tags_pbf) {
|
||||
if (expression.op == ExpressionOperator::Or) {
|
||||
for (const PropertyFilterExpression &filterExpression : expression.operands) {
|
||||
if (matchesFilterExpression(filterExpression, tags_pbf)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else if (expression.op == ExpressionOperator::And) {
|
||||
for (const PropertyFilterExpression &filterExpression : expression.operands) {
|
||||
if (!matchesFilterExpression(filterExpression, tags_pbf)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void FilteredVectorTileLayer::iterator::operator++() {
|
||||
valid = false;
|
||||
|
||||
const PropertyFilterExpression &expression = parent.bucket_desc.filter;
|
||||
|
||||
while (data.next(2)) { // feature
|
||||
feature = data.message();
|
||||
pbf feature_pbf = feature;
|
||||
BucketType type = BucketType::None;
|
||||
|
||||
bool skip = false;
|
||||
while (!skip && feature_pbf.next()) {
|
||||
bool matched = false;
|
||||
|
||||
// Treat the absence of any expression filters as a match.
|
||||
if (expression.is<std::true_type>()) {
|
||||
matched = true;
|
||||
}
|
||||
|
||||
while (feature_pbf.next()) {
|
||||
if (feature_pbf.tag == 2) { // tags
|
||||
if (filter.key < 0) {
|
||||
// We want to parse the entire layer anyway
|
||||
if (matched) {
|
||||
// There is no filter, so we want to parse the entire layer anyway.
|
||||
feature_pbf.skip();
|
||||
} else {
|
||||
// We only want to parse some features.
|
||||
skip = true;
|
||||
|
||||
// tags are packed varints. They should have an even length.
|
||||
pbf tags_pbf = feature_pbf.message();
|
||||
while (tags_pbf) {
|
||||
int32_t tag_key = tags_pbf.varint();
|
||||
if (tags_pbf) {
|
||||
uint32_t tag_val = tags_pbf.varint();
|
||||
// Now check if the tag_key/tag_val pair is included
|
||||
// in the set of key/values that we are looking for.
|
||||
if (filter.key == tag_key && filter.values.find(tag_val) != filter.values.end()) {
|
||||
skip = false;
|
||||
break; // feature_pbf loop
|
||||
}
|
||||
} else {
|
||||
// This should not happen; otherwise the vector tile
|
||||
// is invalid.
|
||||
throw std::runtime_error("uneven number of feature tag ids");
|
||||
}
|
||||
const pbf tags_pbf = feature_pbf.message();
|
||||
matched = matchesFilterExpression(expression, tags_pbf);
|
||||
if (!matched) {
|
||||
break; // feature_pbf loop early
|
||||
}
|
||||
}
|
||||
} else if (feature_pbf.tag == 3) { // geometry type
|
||||
@@ -308,7 +228,8 @@ void FilteredVectorTileLayer::iterator::operator++() {
|
||||
default: type = BucketType::None; break;
|
||||
}
|
||||
|
||||
if (type != filter.bucket_desc.feature_type) {
|
||||
if (type != parent.bucket_desc.feature_type) {
|
||||
matched = false;
|
||||
break; // feature_pbf loop early
|
||||
}
|
||||
} else {
|
||||
@@ -316,7 +237,7 @@ void FilteredVectorTileLayer::iterator::operator++() {
|
||||
}
|
||||
}
|
||||
|
||||
if (!skip && type == filter.bucket_desc.feature_type) {
|
||||
if (matched) {
|
||||
valid = true;
|
||||
return; // data loop
|
||||
} else {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include <llmr/map/vector_tile_data.hpp>
|
||||
|
||||
#include <llmr/util/std.hpp>
|
||||
#include <llmr/map/map.hpp>
|
||||
#include <llmr/map/tile_parser.hpp>
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
@@ -10,7 +9,11 @@ VectorTileData::VectorTileData(Tile::ID id, Map &map, const std::string url)
|
||||
}
|
||||
|
||||
VectorTileData::~VectorTileData() {
|
||||
map.getGlyphAtlas().removeGlyphs(id.to_uint64());
|
||||
map.getGlyphAtlas()->removeGlyphs(id.to_uint64());
|
||||
}
|
||||
|
||||
void VectorTileData::beforeParse() {
|
||||
parser = std::make_unique<TileParser>(data, *this, map.getStyle(), map.getGlyphAtlas(), map.getGlyphStore(), map.getSpriteAtlas());
|
||||
}
|
||||
|
||||
void VectorTileData::parse() {
|
||||
@@ -22,9 +25,11 @@ void VectorTileData::parse() {
|
||||
// Parsing creates state that is encapsulated in TileParser. While parsing,
|
||||
// the TileParser object writes results into this objects. All other state
|
||||
// is going to be discarded afterwards.
|
||||
TileParser parser(data, *this, map.getStyle(), map.getGlyphAtlas(), map.getSpriteAtlas());
|
||||
parser->parse();
|
||||
} catch (const std::exception& ex) {
|
||||
#if defined(DEBUG)
|
||||
fprintf(stderr, "[%p] exception [%d/%d/%d]... failed: %s\n", this, id.z, id.x, id.y, ex.what());
|
||||
#endif
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
@@ -34,6 +39,10 @@ void VectorTileData::parse() {
|
||||
}
|
||||
}
|
||||
|
||||
void VectorTileData::afterParse() {
|
||||
parser.reset();
|
||||
}
|
||||
|
||||
void VectorTileData::render(Painter &painter, const LayerDescription& layer_desc) {
|
||||
auto databucket_it = buckets.find(layer_desc.bucket_name);
|
||||
if (databucket_it != buckets.end()) {
|
||||
|
||||
@@ -1,36 +1,44 @@
|
||||
#include <llmr/platform/request.hpp>
|
||||
#include <llmr/platform/platform.hpp>
|
||||
#include <llmr/util/std.hpp>
|
||||
#include <uv.h>
|
||||
#include <llmr/util/uv.hpp>
|
||||
|
||||
using namespace llmr::platform;
|
||||
|
||||
Request::Request(const std::string &url,
|
||||
std::function<void(Response *)> callback,
|
||||
uv_loop_t *loop)
|
||||
std::shared_ptr<uv::loop> loop)
|
||||
: url(url),
|
||||
res(std::make_unique<Response>(callback)),
|
||||
cancelled(false),
|
||||
loop(loop) {
|
||||
// Add a check handle without a callback to keep the default loop running.
|
||||
// We don't have a real handler attached to the default loop right from the
|
||||
// beginning, because we're using asynchronous messaging to perform the actual
|
||||
// request in the request thread. Only after the request is complete, we
|
||||
// create an actual work request that is attached to the default loop.
|
||||
async = new uv_async_t();
|
||||
async->data = new std::unique_ptr<Response>();
|
||||
uv_async_init(loop, async, complete);
|
||||
if (loop) {
|
||||
// Add a check handle without a callback to keep the default loop running.
|
||||
// We don't have a real handler attached to the default loop right from the
|
||||
// beginning, because we're using asynchronous messaging to perform the actual
|
||||
// request in the request thread. Only after the request is complete, we
|
||||
// create an actual work request that is attached to the default loop.
|
||||
async = new uv_async_t();
|
||||
async->data = new std::unique_ptr<Response>();
|
||||
uv_async_init(**loop, async, complete);
|
||||
}
|
||||
}
|
||||
|
||||
Request::~Request() {
|
||||
}
|
||||
|
||||
void Request::complete() {
|
||||
// We're scheduling the response callback to be invoked in the event loop.
|
||||
// Since the Request object will be deleted before the callback is invoked,
|
||||
// we move over the Response object to be owned by the async handle.
|
||||
((std::unique_ptr<Response> *)async->data)->swap(res);
|
||||
uv_async_send(async);
|
||||
if (loop) {
|
||||
// We're scheduling the response callback to be invoked in the event loop.
|
||||
// Since the Request object will be deleted before the callback is invoked,
|
||||
// we move over the Response object to be owned by the async handle.
|
||||
((std::unique_ptr<Response> *)async->data)->swap(res);
|
||||
uv_async_send(async);
|
||||
} else {
|
||||
// We're calling the response callback immediately. We're currently on an
|
||||
// arbitrary thread, but that's okay.
|
||||
res->callback(res.get());
|
||||
}
|
||||
}
|
||||
|
||||
void Request::complete(uv_async_t *async) {
|
||||
|
||||
@@ -182,18 +182,24 @@ void FillBucket::tessellate() {
|
||||
if (a != TESS_UNDEF && b != TESS_UNDEF && c != TESS_UNDEF) {
|
||||
triangleElementsBuffer.add(triangleIndex + a, triangleIndex + b, triangleIndex + c);
|
||||
} else {
|
||||
#if defined(DEBUG)
|
||||
// TODO: We're missing a vertex that was not part of the line.
|
||||
fprintf(stderr, "undefined element buffer\n");
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
#if defined(DEBUG)
|
||||
fprintf(stderr, "undefined element buffer\n");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
triangleGroup.vertex_length += total_vertex_count;
|
||||
triangleGroup.elements_length += triangle_count;
|
||||
} else {
|
||||
#if defined(DEBUG)
|
||||
fprintf(stderr, "tessellation failed\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
// We're adding the total vertex count *after* we added additional vertices
|
||||
|
||||
@@ -353,7 +353,14 @@ bool LineBucket::hasData() const {
|
||||
}
|
||||
|
||||
bool LineBucket::hasPoints() const {
|
||||
return !pointGroups.empty();
|
||||
if (!pointGroups.empty()) {
|
||||
for (const point_group_type& group : pointGroups) {
|
||||
if (group.elements_length) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void LineBucket::drawLines(LineShader& shader) {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
#include <llmr/renderer/painter.hpp>
|
||||
|
||||
#include <llmr/map/map.hpp>
|
||||
|
||||
#include <llmr/util/std.hpp>
|
||||
#include <llmr/util/string.hpp>
|
||||
#include <llmr/util/timer.hpp>
|
||||
#include <llmr/util/time.hpp>
|
||||
#include <llmr/util/clip_ids.hpp>
|
||||
#include <llmr/util/constants.hpp>
|
||||
#if defined(DEBUG)
|
||||
#include <llmr/util/timer.hpp>
|
||||
#endif
|
||||
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
@@ -29,8 +29,9 @@ bool Painter::needsAnimation() const {
|
||||
}
|
||||
|
||||
void Painter::setup() {
|
||||
#if defined(DEBUG)
|
||||
util::timer timer("painter setup");
|
||||
|
||||
#endif
|
||||
setupShaders();
|
||||
|
||||
assert(iconShader);
|
||||
@@ -212,15 +213,14 @@ void Painter::renderMatte() {
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glStencilFunc(GL_EQUAL, 0x0, 0xFF);
|
||||
|
||||
// Color white = {{ 0.9, 0.9, 0.9, 1 }};
|
||||
Color white = {{ 1, 1, 0, 1 }};
|
||||
Color matte = {{ 0, 0, 0, 1 }};
|
||||
|
||||
useProgram(plainShader->program);
|
||||
plainShader->setMatrix(nativeMatrix);
|
||||
|
||||
// Draw the clipping mask
|
||||
matteArray.bind(*plainShader, tileStencilBuffer, BUFFER_OFFSET(0));
|
||||
plainShader->setColor(white);
|
||||
plainShader->setColor(matte);
|
||||
glDrawArrays(GL_TRIANGLES, 0, (GLsizei)tileStencilBuffer.index());
|
||||
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
|
||||
@@ -15,11 +15,13 @@ void Painter::drawClippingMasks(const Sources &sources) {
|
||||
glDepthRange(1.0f, 1.0f);
|
||||
glStencilMask(0xFF);
|
||||
|
||||
Color background = map.getStyle().computed.background.color;
|
||||
background[0] *= map.getStyle().computed.background.opacity;
|
||||
background[1] *= map.getStyle().computed.background.opacity;
|
||||
background[2] *= map.getStyle().computed.background.opacity;
|
||||
background[3] *= map.getStyle().computed.background.opacity;
|
||||
const BackgroundProperties &properties = map.getStyle()->computed.background;
|
||||
Color background = properties.color;
|
||||
const float opacity = properties.opacity;
|
||||
background[0] *= opacity;
|
||||
background[1] *= opacity;
|
||||
background[2] *= opacity;
|
||||
background[3] *= opacity;
|
||||
plainShader->setColor(background);
|
||||
|
||||
coveringPlainArray.bind(*plainShader, tileStencilBuffer, BUFFER_OFFSET(0));
|
||||
|
||||
@@ -65,9 +65,9 @@ void Painter::renderFill(FillBucket& bucket, const FillProperties& properties, c
|
||||
}
|
||||
|
||||
if ((fill_color[3] >= 1.0f) == (pass == Opaque)) {
|
||||
auto &sprite = map.getStyle().sprite;
|
||||
const std::shared_ptr<Sprite> &sprite = map.getStyle()->sprite;
|
||||
if (properties.image.size() && sprite) {
|
||||
auto &spriteAtlas = map.getSpriteAtlas();
|
||||
SpriteAtlas &spriteAtlas = *map.getSpriteAtlas();
|
||||
Rect<uint16_t> imagePos = spriteAtlas.getImage(properties.image, *sprite);
|
||||
|
||||
|
||||
@@ -144,25 +144,25 @@ void Painter::renderFill(FillBucket& bucket, const std::string& layer_name, cons
|
||||
// Abort early.
|
||||
if (!bucket.hasData()) return;
|
||||
|
||||
const std::unordered_map<std::string, FillProperties> &fill_properties = map.getStyle().computed.fills;
|
||||
const std::unordered_map<std::string, FillProperties> &fill_properties = map.getStyle()->computed.fills;
|
||||
const std::unordered_map<std::string, FillProperties>::const_iterator fill_properties_it = fill_properties.find(layer_name);
|
||||
if (fill_properties_it == fill_properties.end()) return;
|
||||
|
||||
const FillProperties& properties = fill_properties_it->second;
|
||||
const FillProperties &properties = fill_properties_it != fill_properties.end()
|
||||
? fill_properties_it->second
|
||||
: defaultFillProperties;
|
||||
if (!properties.enabled) return;
|
||||
|
||||
|
||||
if (properties.prerender) {
|
||||
if (properties.prerender && properties.getPrerender(id.z)) {
|
||||
if (pass == Translucent) {
|
||||
// Buffer value around the 0..4096 extent that will be drawn into the 256x256 pixel
|
||||
// texture. We later scale the texture so that the actual bounds will align with this
|
||||
// tile's bounds. The reason we do this is so that the
|
||||
|
||||
if (!bucket.prerendered) {
|
||||
bucket.prerendered = std::make_unique<PrerenderedTexture>(properties.prerenderSize);
|
||||
const PrerenderProperties prerender = properties.getPrerenderProperties(id.z);
|
||||
bucket.prerendered = std::make_unique<PrerenderedTexture>(prerender);
|
||||
bucket.prerendered->bindFramebuffer();
|
||||
|
||||
preparePrerender(properties);
|
||||
preparePrerender(*bucket.prerendered);
|
||||
|
||||
const FillProperties modifiedProperties = [&]{
|
||||
FillProperties modifiedProperties = properties;
|
||||
@@ -172,7 +172,7 @@ void Painter::renderFill(FillBucket& bucket, const std::string& layer_name, cons
|
||||
|
||||
// When drawing the fill, we want to draw a buffer around too, so we
|
||||
// essentially downscale everyting, and then upscale it later when rendering.
|
||||
const int buffer = 4096 * properties.prerenderBuffer;
|
||||
const int buffer = prerender.buffer * 4096.0f;
|
||||
const mat4 vtxMatrix = [&]{
|
||||
mat4 vtxMatrix;
|
||||
matrix::ortho(vtxMatrix, -buffer, 4096 + buffer, -4096 - buffer, buffer, 0, 1);
|
||||
@@ -186,17 +186,16 @@ void Painter::renderFill(FillBucket& bucket, const std::string& layer_name, cons
|
||||
setTranslucent();
|
||||
renderFill(bucket, modifiedProperties, id, vtxMatrix);
|
||||
|
||||
|
||||
if (properties.prerenderBlur > 0) {
|
||||
bucket.prerendered->blur(*this, properties.prerenderBlur);
|
||||
if (prerender.blur > 0) {
|
||||
bucket.prerendered->blur(*this, prerender.blur);
|
||||
}
|
||||
|
||||
// RESET STATE
|
||||
bucket.prerendered->unbindFramebuffer();
|
||||
finishPrerender(properties);
|
||||
finishPrerender(*bucket.prerendered);
|
||||
}
|
||||
|
||||
renderPrerenderedTexture(bucket, properties);
|
||||
renderPrerenderedTexture(*bucket.prerendered, properties);
|
||||
}
|
||||
} else {
|
||||
const mat4 &vtxMatrix = translatedMatrix(properties.translate, id, properties.translateAnchor);
|
||||
|
||||
@@ -11,11 +11,12 @@ void Painter::renderIcon(IconBucket& bucket, const std::string& layer_name, cons
|
||||
if (!bucket.hasData()) return;
|
||||
if (pass == Opaque) return;
|
||||
|
||||
const std::unordered_map<std::string, IconProperties> &icon_properties = map.getStyle().computed.icons;
|
||||
const std::unordered_map<std::string, IconProperties> &icon_properties = map.getStyle()->computed.icons;
|
||||
const std::unordered_map<std::string, IconProperties>::const_iterator icon_properties_it = icon_properties.find(layer_name);
|
||||
if (icon_properties_it == icon_properties.end()) return;
|
||||
|
||||
const IconProperties& properties = icon_properties_it->second;
|
||||
const IconProperties &properties = icon_properties_it != icon_properties.end()
|
||||
? icon_properties_it->second
|
||||
: defaultIconProperties;
|
||||
if (!properties.enabled) return;
|
||||
|
||||
Color color = properties.color;
|
||||
@@ -26,7 +27,7 @@ void Painter::renderIcon(IconBucket& bucket, const std::string& layer_name, cons
|
||||
|
||||
const mat4 &vtxMatrix = translatedMatrix(properties.translate, id, properties.translateAnchor);
|
||||
|
||||
SpriteAtlas &spriteAtlas = map.getSpriteAtlas();
|
||||
SpriteAtlas &spriteAtlas = *map.getSpriteAtlas();
|
||||
|
||||
useProgram(iconShader->program);
|
||||
iconShader->setMatrix(vtxMatrix);
|
||||
|
||||
@@ -9,11 +9,12 @@ void Painter::renderLine(LineBucket& bucket, const std::string& layer_name, cons
|
||||
if (pass == Opaque) return;
|
||||
if (!bucket.hasData()) return;
|
||||
|
||||
const std::unordered_map<std::string, LineProperties> &line_properties = map.getStyle().computed.lines;
|
||||
const std::unordered_map<std::string, LineProperties> &line_properties = map.getStyle()->computed.lines;
|
||||
const std::unordered_map<std::string, LineProperties>::const_iterator line_properties_it = line_properties.find(layer_name);
|
||||
if (line_properties_it == line_properties.end()) return;
|
||||
|
||||
const LineProperties& properties = line_properties_it->second;
|
||||
const LineProperties &properties = line_properties_it != line_properties.end()
|
||||
? line_properties_it->second
|
||||
: defaultLineProperties;
|
||||
if (!properties.enabled) return;
|
||||
|
||||
float width = properties.width;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
void Painter::preparePrerender(const GenericProperties &properties) {
|
||||
void Painter::preparePrerender(PrerenderedTexture &texture) {
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glDisable(GL_STENCIL_TEST);
|
||||
|
||||
@@ -13,18 +13,18 @@ void Painter::preparePrerender(const GenericProperties &properties) {
|
||||
#endif
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
glViewport(0, 0, properties.prerenderSize, properties.prerenderSize);
|
||||
glViewport(0, 0, texture.properties.size, texture.properties.size);
|
||||
}
|
||||
|
||||
void Painter::finishPrerender(const GenericProperties &properties) {
|
||||
void Painter::finishPrerender(PrerenderedTexture &texture) {
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glEnable(GL_STENCIL_TEST);
|
||||
|
||||
glViewport(0, 0, gl_viewport[0], gl_viewport[1]);
|
||||
}
|
||||
|
||||
void Painter::renderPrerenderedTexture(Bucket &bucket, const GenericProperties &properties) {
|
||||
const int buffer = 4096 * properties.prerenderBuffer;
|
||||
void Painter::renderPrerenderedTexture(PrerenderedTexture &texture, const GenericProperties &properties) {
|
||||
const int buffer = texture.properties.buffer * 4096.0f;
|
||||
|
||||
// draw the texture on a quad
|
||||
useProgram(rasterShader->program);
|
||||
@@ -37,7 +37,7 @@ void Painter::renderPrerenderedTexture(Bucket &bucket, const GenericProperties &
|
||||
rasterShader->setImage(0);
|
||||
rasterShader->setBuffer(buffer);
|
||||
rasterShader->setOpacity(properties.opacity);
|
||||
bucket.prerendered->bindTexture();
|
||||
texture.bindTexture();
|
||||
coveringRasterArray.bind(*rasterShader, tileStencilBuffer, BUFFER_OFFSET(0));
|
||||
glDrawArrays(GL_TRIANGLES, 0, (GLsizei)tileStencilBuffer.index());
|
||||
}
|
||||
|
||||
@@ -7,11 +7,12 @@ using namespace llmr;
|
||||
void Painter::renderRaster(RasterBucket& bucket, const std::string& layer_name, const Tile::ID& /*id*/) {
|
||||
if (pass == Translucent) return;
|
||||
|
||||
const std::unordered_map<std::string, RasterProperties> &raster_properties = map.getStyle().computed.rasters;
|
||||
const std::unordered_map<std::string, RasterProperties> &raster_properties = map.getStyle()->computed.rasters;
|
||||
const std::unordered_map<std::string, RasterProperties>::const_iterator raster_properties_it = raster_properties.find(layer_name);
|
||||
if (raster_properties_it == raster_properties.end()) return;
|
||||
|
||||
const RasterProperties& properties = raster_properties_it->second;
|
||||
const RasterProperties &properties = raster_properties_it != raster_properties.end()
|
||||
? raster_properties_it->second
|
||||
: defaultRasterProperties;
|
||||
if (!properties.enabled) return;
|
||||
|
||||
depthMask(false);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <llmr/renderer/painter.hpp>
|
||||
#include <llmr/renderer/text_bucket.hpp>
|
||||
#include <llmr/map/map.hpp>
|
||||
#include <cmath>
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
@@ -9,11 +10,12 @@ void Painter::renderText(TextBucket& bucket, const std::string& layer_name, cons
|
||||
if (pass == Opaque) return;
|
||||
if (!bucket.hasData()) return;
|
||||
|
||||
const std::unordered_map<std::string, TextProperties> &text_properties = map.getStyle().computed.texts;
|
||||
const std::unordered_map<std::string, TextProperties> &text_properties = map.getStyle()->computed.texts;
|
||||
const std::unordered_map<std::string, TextProperties>::const_iterator text_properties_it = text_properties.find(layer_name);
|
||||
if (text_properties_it == text_properties.end()) return;
|
||||
|
||||
const TextProperties& properties = text_properties_it->second;
|
||||
const TextProperties &properties = text_properties_it != text_properties.end()
|
||||
? text_properties_it->second
|
||||
: defaultTextProperties;
|
||||
if (!properties.enabled) return;
|
||||
|
||||
mat4 exMatrix;
|
||||
@@ -37,9 +39,10 @@ void Painter::renderText(TextBucket& bucket, const std::string& layer_name, cons
|
||||
textShader->setMatrix(vtxMatrix);
|
||||
textShader->setExtrudeMatrix(exMatrix);
|
||||
|
||||
map.getGlyphAtlas().bind();
|
||||
textShader->setTextureSize({{static_cast<float>(map.getGlyphAtlas().width),
|
||||
static_cast<float>(map.getGlyphAtlas().height)}});
|
||||
GlyphAtlas &glyphAtlas = *map.getGlyphAtlas();
|
||||
glyphAtlas.bind();
|
||||
textShader->setTextureSize({{static_cast<float>(glyphAtlas.width),
|
||||
static_cast<float>(glyphAtlas.height)}});
|
||||
|
||||
// Convert the -pi..pi to an int8 range.
|
||||
float angle = std::round((map.getState().getAngle() + rotate) / M_PI * 128);
|
||||
@@ -66,8 +69,7 @@ void Painter::renderText(TextBucket& bucket, const std::string& layer_name, cons
|
||||
history[0].z = history[1].z;
|
||||
}
|
||||
|
||||
size_t frameLen = history.size();
|
||||
assert("there should never be less than three frames in the history" && frameLen >= 3);
|
||||
assert("there should never be less than three frames in the history" && (history.size() >= 3));
|
||||
|
||||
// Find the range of zoom levels we want to fade between
|
||||
float startingZ = history.front().z;
|
||||
@@ -81,7 +83,9 @@ void Painter::renderText(TextBucket& bucket, const std::string& layer_name, cons
|
||||
timeDiff = lastFrame.timestamp - history[1].timestamp;
|
||||
float fadedist = zoomDiff / (timeDiff / duration);
|
||||
|
||||
if (isnan(fadedist)) fprintf(stderr, "fadedist should never be NaN\n");
|
||||
#if defined(DEBUG)
|
||||
if (std::isnan(fadedist)) fprintf(stderr, "fadedist should never be NaN\n");
|
||||
#endif
|
||||
|
||||
// At end of a zoom when the zoom stops changing continue pretending to zoom at that speed
|
||||
// bump is how much farther it would have been if it had continued zooming at the same rate
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
PrerenderedTexture::PrerenderedTexture(uint16_t size)
|
||||
: size(size) {
|
||||
PrerenderedTexture::PrerenderedTexture(const PrerenderProperties &properties)
|
||||
: properties(properties) {
|
||||
}
|
||||
|
||||
PrerenderedTexture::~PrerenderedTexture() {
|
||||
@@ -28,11 +28,6 @@ void PrerenderedTexture::bindTexture() {
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
|
||||
// if (!mipmapped) {
|
||||
// glGenerateMipmap(GL_TEXTURE_2D);
|
||||
// mipmapped = true;
|
||||
// }
|
||||
}
|
||||
|
||||
void PrerenderedTexture::bindFramebuffer() {
|
||||
@@ -45,7 +40,7 @@ void PrerenderedTexture::bindFramebuffer() {
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, properties.size, properties.size, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
@@ -95,7 +90,7 @@ void PrerenderedTexture::blur(Painter& painter, uint16_t passes) {
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, properties.size, properties.size, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
|
||||
@@ -113,7 +108,7 @@ void PrerenderedTexture::blur(Painter& painter, uint16_t passes) {
|
||||
#endif
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
painter.gaussianShader->setOffset({{ 1.0f / float(size), 0 }});
|
||||
painter.gaussianShader->setOffset({{ 1.0f / float(properties.size), 0 }});
|
||||
glBindTexture(GL_TEXTURE_2D, original_texture);
|
||||
painter.coveringGaussianArray.bind(*painter.gaussianShader, painter.tileStencilBuffer, BUFFER_OFFSET(0));
|
||||
glDrawArrays(GL_TRIANGLES, 0, (GLsizei)painter.tileStencilBuffer.index());
|
||||
@@ -127,7 +122,7 @@ void PrerenderedTexture::blur(Painter& painter, uint16_t passes) {
|
||||
#endif
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
painter.gaussianShader->setOffset({{ 0, 1.0f / float(size) }});
|
||||
painter.gaussianShader->setOffset({{ 0, 1.0f / float(properties.size) }});
|
||||
glBindTexture(GL_TEXTURE_2D, secondary_texture);
|
||||
painter.coveringGaussianArray.bind(*painter.gaussianShader, painter.tileStencilBuffer, BUFFER_OFFSET(0));
|
||||
glDrawArrays(GL_TRIANGLES, 0, (GLsizei)painter.tileStencilBuffer.index());
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
RasterBucket::RasterBucket(Texturepool &texturepool)
|
||||
RasterBucket::RasterBucket(const std::shared_ptr<Texturepool> &texturepool)
|
||||
: raster(texturepool) {
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include <llmr/style/style.hpp>
|
||||
#include <llmr/map/vector_tile.hpp>
|
||||
#include <llmr/text/placement.hpp>
|
||||
#include <llmr/text/glyph_store.hpp>
|
||||
#include <llmr/util/constants.hpp>
|
||||
|
||||
#include <llmr/util/math.hpp>
|
||||
#include <llmr/platform/gl.hpp>
|
||||
@@ -97,44 +99,28 @@ void TextBucket::addGlyphs(const PlacedGlyphs &glyphs, float placementZoom,
|
||||
}
|
||||
};
|
||||
|
||||
void TextBucket::addFeature(const VectorTileFeature &feature,
|
||||
const IndexedFaces &faces,
|
||||
const std::map<Value, Shaping> &shapings) {
|
||||
auto it_prop = feature.properties.find(geom_desc.field);
|
||||
if (it_prop == feature.properties.end()) {
|
||||
// feature does not have the correct property
|
||||
return;
|
||||
}
|
||||
const Value &value = it_prop->second;
|
||||
|
||||
auto it_shaping = shapings.find(value);
|
||||
if (it_shaping == shapings.end()) {
|
||||
// we lack shaping information for this label
|
||||
return;
|
||||
}
|
||||
const Shaping &shaping = it_shaping->second;
|
||||
|
||||
// std::cerr << "we have shaping for " << value << std::endl;
|
||||
|
||||
void TextBucket::addFeature(const pbf &geom_pbf,
|
||||
const GlyphPositions &face,
|
||||
const Shaping &shaping) {
|
||||
// Decode all lines.
|
||||
std::vector<Coordinate> line;
|
||||
Geometry::command cmd;
|
||||
|
||||
Coordinate coord;
|
||||
pbf geom = feature.geometry;
|
||||
pbf geom(geom_pbf);
|
||||
Geometry geometry(geom);
|
||||
int32_t x, y;
|
||||
while ((cmd = geometry.next(x, y)) != Geometry::end) {
|
||||
if (cmd == Geometry::move_to) {
|
||||
if (!line.empty()) {
|
||||
placement.addFeature(*this, line, geom_desc, faces, shaping);
|
||||
placement.addFeature(*this, line, geom_desc, face, shaping);
|
||||
line.clear();
|
||||
}
|
||||
}
|
||||
line.emplace_back(x, y);
|
||||
}
|
||||
if (line.size()) {
|
||||
placement.addFeature(*this, line, geom_desc, faces, shaping);
|
||||
placement.addFeature(*this, line, geom_desc, face, shaping);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,9 @@ GaussianShader::GaussianShader()
|
||||
shaders[GAUSSIAN_SHADER].fragment
|
||||
) {
|
||||
if (!valid) {
|
||||
#if defined(DEBUG)
|
||||
fprintf(stderr, "invalid raster shader\n");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,9 @@ RasterShader::RasterShader()
|
||||
shaders[RASTER_SHADER].fragment
|
||||
) {
|
||||
if (!valid) {
|
||||
#if defined(DEBUG)
|
||||
fprintf(stderr, "invalid raster shader\n");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#include <llmr/shader/shader.hpp>
|
||||
#include <llmr/platform/gl.hpp>
|
||||
#if defined(DEBUG)
|
||||
#include <llmr/util/timer.hpp>
|
||||
|
||||
#endif
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
@@ -11,18 +12,23 @@ using namespace llmr;
|
||||
Shader::Shader(const GLchar *vertSource, const GLchar *fragSource)
|
||||
: valid(false),
|
||||
program(0) {
|
||||
#if defined(DEBUG)
|
||||
util::timer timer("shader compilation");
|
||||
|
||||
#endif
|
||||
|
||||
GLuint vertShader;
|
||||
if (!compileShader(&vertShader, GL_VERTEX_SHADER, vertSource)) {
|
||||
#if defined(DEBUG)
|
||||
fprintf(stderr, "Vertex shader failed to compile: %s\n", vertSource);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
GLuint fragShader;
|
||||
if (!compileShader(&fragShader, GL_FRAGMENT_SHADER, fragSource)) {
|
||||
#if defined(DEBUG)
|
||||
fprintf(stderr, "Fragment shader failed to compile: %s\n", fragSource);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -80,7 +86,9 @@ Shader::Shader(const GLchar *vertSource, const GLchar *fragSource)
|
||||
|
||||
glGetProgramiv(program, GL_VALIDATE_STATUS, &status);
|
||||
if (status == 0) {
|
||||
#if defined(DEBUG)
|
||||
fprintf(stderr, "Program failed to validate\n");
|
||||
#endif
|
||||
glDeleteShader(vertShader);
|
||||
vertShader = 0;
|
||||
glDeleteShader(fragShader);
|
||||
|
||||
@@ -2,15 +2,12 @@
|
||||
|
||||
#include <iostream>
|
||||
|
||||
|
||||
std::ostream& llmr::operator<<(std::ostream& os, const BucketDescription& bucket) {
|
||||
os << "Bucket:" << std::endl;
|
||||
os << " - type: " << (uint32_t)bucket.type << std::endl;
|
||||
os << " - source_name: " << bucket.source_name << std::endl;
|
||||
os << " - source_layer: " << bucket.source_layer << std::endl;
|
||||
os << " - source_field: " << bucket.source_field << std::endl;
|
||||
for (const Value& value : bucket.source_value) {
|
||||
os << " - source_value: " << value << std::endl;
|
||||
}
|
||||
os << " - cap: " << (uint32_t)bucket.geometry.cap << std::endl;
|
||||
os << " - join: " << (uint32_t)bucket.geometry.join << std::endl;
|
||||
os << " - font: " << bucket.geometry.font << std::endl;
|
||||
|
||||
@@ -29,9 +29,11 @@ float functions::stops(float z, const std::vector<float>& stops) {
|
||||
if (stops.size() % 2 != 0) return 0;
|
||||
|
||||
bool smaller = false;
|
||||
float smaller_z, smaller_val;
|
||||
float smaller_z = 0.0;
|
||||
float smaller_val = 0.0;
|
||||
bool larger = false;
|
||||
float larger_z, larger_val;
|
||||
float larger_z = 0.0;
|
||||
float larger_val = 0.0;
|
||||
|
||||
for (uint32_t i = 0; i < stops.size(); i += 2) {
|
||||
float stop_z = stops[i];
|
||||
|
||||
@@ -54,7 +54,9 @@ void Sprite::complete(std::shared_ptr<Sprite> &sprite) {
|
||||
if (sprite->raster && sprite->pos.size()) {
|
||||
sprite->loaded = true;
|
||||
sprite->map.update();
|
||||
#if defined(DEBUG)
|
||||
fprintf(stderr, "sprite loaded\n");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,21 +51,10 @@ void Style::cascadeProperties(GenericProperties &properties, const GenericClass&
|
||||
}
|
||||
}
|
||||
|
||||
if (klass.prerender) {
|
||||
properties.prerender = klass.prerender.get();
|
||||
}
|
||||
|
||||
if (klass.prerenderBuffer) {
|
||||
properties.prerenderBuffer = klass.prerenderBuffer.get();
|
||||
}
|
||||
|
||||
if (klass.prerenderSize) {
|
||||
properties.prerenderSize = klass.prerenderSize.get();
|
||||
}
|
||||
|
||||
if (klass.prerenderBlur) {
|
||||
properties.prerenderBlur = klass.prerenderBlur.get();
|
||||
}
|
||||
properties.prerender = klass.prerender;
|
||||
properties.prerenderBuffer = klass.prerenderBuffer;
|
||||
properties.prerenderSize = klass.prerenderSize;
|
||||
properties.prerenderBlur = klass.prerenderBlur;
|
||||
}
|
||||
|
||||
|
||||
@@ -965,21 +954,15 @@ size_t Style::layerCount() const {
|
||||
return count;
|
||||
}
|
||||
|
||||
void Style::setDefaultTransitionDuration(uint64_t duration) {
|
||||
default_transition_duration = duration;
|
||||
void Style::setDefaultTransitionDuration(uint64_t duration_milliseconds) {
|
||||
default_transition_duration = duration_milliseconds;
|
||||
}
|
||||
|
||||
void Style::loadJSON(const uint8_t *const data, size_t bytes) {
|
||||
void Style::loadJSON(const uint8_t *const data) {
|
||||
uv::writelock lock(mtx);
|
||||
|
||||
rapidjson::Document doc;
|
||||
|
||||
if (bytes <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data[bytes - 1] != 0) {
|
||||
throw exception("style JSON string is not 0-terminated");
|
||||
}
|
||||
|
||||
doc.Parse<0>((const char *const)data);
|
||||
|
||||
const rapidjson::Document& document = doc;
|
||||
|
||||
@@ -20,6 +20,56 @@ void StyleParser::parseBuckets(JSVal value, std::map<std::string, BucketDescript
|
||||
}
|
||||
}
|
||||
|
||||
PropertyFilterExpression StyleParser::parseFilterOrExpression(JSVal value) {
|
||||
if (value.IsArray()) {
|
||||
// This is an expression.
|
||||
util::recursive_wrapper<PropertyExpression> expression;
|
||||
for (rapidjson::SizeType i = 0; i < value.Size(); ++i) {
|
||||
JSVal filter_item = value[i];
|
||||
|
||||
if (filter_item.IsString()) {
|
||||
expression.get().op = expressionOperatorType({ filter_item.GetString(), filter_item.GetStringLength() });
|
||||
} else {
|
||||
expression.get().operands.emplace_back(parseFilterOrExpression(filter_item));
|
||||
}
|
||||
}
|
||||
return std::move(expression);
|
||||
} else if (value.IsObject() && value.HasMember("field") && value.HasMember("value")) {
|
||||
// This is a filter.
|
||||
JSVal field = value["field"];
|
||||
JSVal val = value["value"];
|
||||
|
||||
if (!field.IsString()) {
|
||||
throw Style::exception("field name must be a string");
|
||||
}
|
||||
|
||||
const std::string field_name { field.GetString(), field.GetStringLength() };
|
||||
const FilterOperator op = [&]{
|
||||
if (value.HasMember("operator")) {
|
||||
JSVal op_val = value["operator"];
|
||||
return filterOperatorType({ op_val.GetString(), op_val.GetStringLength() });
|
||||
} else {
|
||||
return FilterOperator::Equal;
|
||||
}
|
||||
}();
|
||||
|
||||
if (val.IsArray()) {
|
||||
// The filter has several values, so it's an OR sub-expression.
|
||||
util::recursive_wrapper<PropertyExpression> expression;
|
||||
for (rapidjson::SizeType i = 0; i < val.Size(); ++i) {
|
||||
expression.get().operands.emplace_back(util::recursive_wrapper<PropertyFilter>(PropertyFilter { field_name, op, parseValue(val[i]) }));
|
||||
}
|
||||
|
||||
return std::move(expression);
|
||||
} else {
|
||||
// The filter only has a single value, so it is a real filter.
|
||||
return std::move(util::recursive_wrapper<PropertyFilter>(PropertyFilter { field_name, op, parseValue(val) }));
|
||||
}
|
||||
}
|
||||
|
||||
return std::true_type();
|
||||
}
|
||||
|
||||
BucketDescription StyleParser::parseBucket(JSVal value) {
|
||||
BucketDescription bucket;
|
||||
|
||||
@@ -52,20 +102,6 @@ BucketDescription StyleParser::parseBucket(JSVal value) {
|
||||
} else {
|
||||
throw Style::exception("layer name must be a string");
|
||||
}
|
||||
} else if (name == "field") {
|
||||
if (value.IsString()) {
|
||||
bucket.source_field = { value.GetString(), value.GetStringLength() };
|
||||
} else {
|
||||
throw Style::exception("field name must be a string");
|
||||
}
|
||||
} else if (name == "value") {
|
||||
if (value.IsArray()) {
|
||||
for (rapidjson::SizeType i = 0; i < value.Size(); ++i) {
|
||||
bucket.source_value.push_back(parseValue(value[i]));
|
||||
}
|
||||
} else {
|
||||
bucket.source_value.push_back(parseValue(value));
|
||||
}
|
||||
} else if (name == "cap") {
|
||||
if (value.IsString()) {
|
||||
bucket.geometry.cap = capType({ value.GetString(), value.GetStringLength() });
|
||||
@@ -102,6 +138,43 @@ BucketDescription StyleParser::parseBucket(JSVal value) {
|
||||
} else {
|
||||
throw Style::exception("curve must be a string");
|
||||
}
|
||||
} else if (name == "alignment") {
|
||||
if (value.IsString()) {
|
||||
bucket.geometry.alignment = alignmentType({ value.GetString(), value.GetStringLength() });
|
||||
} else {
|
||||
throw Style::exception("alignment must be a string");
|
||||
}
|
||||
} else if (name == "translate") {
|
||||
if (value.IsArray()) {
|
||||
bucket.geometry.translate.x = value[(rapidjson::SizeType)0].GetDouble() * 24;
|
||||
bucket.geometry.translate.y = value[(rapidjson::SizeType)1].GetDouble() * -24;
|
||||
} else {
|
||||
throw Style::exception("translate must be a string");
|
||||
}
|
||||
} else if (name == "verticalAlignment") {
|
||||
if (value.IsString()) {
|
||||
bucket.geometry.vertical_alignment = verticalAlignmentType({ value.GetString(), value.GetStringLength() });
|
||||
} else {
|
||||
throw Style::exception("verticalAlignment must be a string");
|
||||
}
|
||||
} else if (name == "lineHeight") {
|
||||
if (value.IsNumber()) {
|
||||
bucket.geometry.line_height = value.GetDouble() * 24;
|
||||
} else {
|
||||
throw Style::exception("line height must be a number");
|
||||
}
|
||||
} else if (name == "maxWidth") {
|
||||
if (value.IsNumber()) {
|
||||
bucket.geometry.max_width = value.GetDouble() * 24;
|
||||
} else {
|
||||
throw Style::exception("max width must be a number");
|
||||
}
|
||||
} else if (name == "letterSpacing") {
|
||||
if (value.IsNumber()) {
|
||||
bucket.geometry.letter_spacing = value.GetDouble() * 24;
|
||||
} else {
|
||||
throw Style::exception("letter spacing must be a number");
|
||||
}
|
||||
} else if (name == "miterLimit") {
|
||||
if (value.IsNumber()) {
|
||||
bucket.geometry.miter_limit = value.GetDouble();
|
||||
@@ -130,6 +203,13 @@ BucketDescription StyleParser::parseBucket(JSVal value) {
|
||||
|
||||
}
|
||||
|
||||
if (value.HasMember("filter")) {
|
||||
bucket.filter = std::move(parseFilterOrExpression(value["filter"]));
|
||||
} else {
|
||||
// Allow a filter triple to be specified at the bucket root as well.
|
||||
bucket.filter = std::move(parseFilterOrExpression(value));
|
||||
}
|
||||
|
||||
if (bucket.feature_type == BucketType::None) {
|
||||
bucket.feature_type = bucket.type;
|
||||
}
|
||||
@@ -342,7 +422,7 @@ std::string StyleParser::parseString(JSVal value) {
|
||||
return { value.GetString(), value.GetStringLength() };
|
||||
}
|
||||
|
||||
const JSVal& StyleParser::replaceConstant(const JSVal& value) {
|
||||
JSVal StyleParser::replaceConstant(JSVal value) {
|
||||
if (value.IsString()) {
|
||||
const std::string string_value { value.GetString(), value.GetStringLength() };
|
||||
auto it = constants.find(string_value);
|
||||
@@ -520,19 +600,19 @@ void StyleParser::parseGenericClass(GenericClass &klass, JSVal value) {
|
||||
}
|
||||
|
||||
if (value.HasMember("prerender")) {
|
||||
klass.prerender = parseBoolean(value["prerender"]);
|
||||
klass.prerender = parseFunction(value["prerender"]);
|
||||
}
|
||||
|
||||
if (value.HasMember("prerender-buffer")) {
|
||||
klass.prerenderBuffer = toNumber<double>(parseValue(value["prerender-buffer"]));
|
||||
klass.prerenderBuffer = parseFunction(value["prerender-buffer"]);
|
||||
}
|
||||
|
||||
if (value.HasMember("prerender-size")) {
|
||||
klass.prerenderSize = toNumber<uint64_t>(parseValue(value["prerender-size"]));
|
||||
klass.prerenderSize = parseFunction(value["prerender-size"]);
|
||||
}
|
||||
|
||||
if (value.HasMember("prerender-blur")) {
|
||||
klass.prerenderBlur = toNumber<uint64_t>(parseValue(value["prerender-blur"]));
|
||||
klass.prerenderBlur = parseFunction(value["prerender-blur"]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -714,4 +794,6 @@ Value StyleParser::parseValue(JSVal value) {
|
||||
throw Style::exception("value cannot be an object or array");
|
||||
return false;
|
||||
}
|
||||
throw Style::exception("unhandled value type in style");
|
||||
return false;
|
||||
}
|
||||
|
||||
17
src/text/glyph.cpp
Normal file
17
src/text/glyph.cpp
Normal file
@@ -0,0 +1,17 @@
|
||||
#include <llmr/text/glyph.hpp>
|
||||
|
||||
namespace llmr {
|
||||
|
||||
// Note: this only works for the BMP
|
||||
// Note: we could use a binary lookup table to get averaged constant time lookups, however,
|
||||
// most of our lookups are going to be within the first 3 ranges listed here, so this is
|
||||
// likely faster.
|
||||
GlyphRange getGlyphRange(uint32_t glyph) {
|
||||
unsigned start = (glyph/256) * 256;
|
||||
unsigned end = (start + 255);
|
||||
if (start > 65280) start = 65280;
|
||||
if (end > 65533) end = 65533;
|
||||
return { start, end };
|
||||
}
|
||||
|
||||
}
|
||||
258
src/text/glyph_store.cpp
Normal file
258
src/text/glyph_store.cpp
Normal file
@@ -0,0 +1,258 @@
|
||||
#include <llmr/text/glyph_store.hpp>
|
||||
|
||||
#include <llmr/util/std.hpp>
|
||||
#include <llmr/util/string.hpp>
|
||||
#include <llmr/util/utf.hpp>
|
||||
#include <llmr/util/pbf.hpp>
|
||||
#include <llmr/platform/platform.hpp>
|
||||
#include <uv.h>
|
||||
#include <algorithm>
|
||||
|
||||
namespace llmr {
|
||||
|
||||
|
||||
void FontStack::insert(uint32_t id, const SDFGlyph &glyph) {
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
metrics.emplace(id, glyph.metrics);
|
||||
bitmaps.emplace(id, glyph.bitmap);
|
||||
sdfs.emplace(id, glyph);
|
||||
}
|
||||
|
||||
const std::map<uint32_t, GlyphMetrics> &FontStack::getMetrics() const {
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
return metrics;
|
||||
}
|
||||
|
||||
const std::map<uint32_t, SDFGlyph> &FontStack::getSDFs() const {
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
return sdfs;
|
||||
}
|
||||
|
||||
const Shaping FontStack::getShaping(const std::u32string &string, const float &maxWidth,
|
||||
const float &lineHeight, const float &alignment, const float &verticalAlignment,
|
||||
const float &letterSpacing) const {
|
||||
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
uint32_t i = 0;
|
||||
uint32_t x = 0;
|
||||
Shaping shaping;
|
||||
// Loop through all characters of this label and shape.
|
||||
for (uint32_t chr : string) {
|
||||
shaping.emplace_back(0, chr, x, 0);
|
||||
i++;
|
||||
auto metric = metrics.find(chr);
|
||||
if (metric != metrics.end()) {
|
||||
x += metric->second.advance + letterSpacing;
|
||||
}
|
||||
}
|
||||
|
||||
lineWrap(shaping, lineHeight, maxWidth, alignment, verticalAlignment);
|
||||
|
||||
return shaping;
|
||||
}
|
||||
|
||||
void alignVertically(Shaping &shaping, const uint32_t &lines, const float &lineHeight, const float &verticalAlign) {
|
||||
const float dy = -(lineHeight * (lines - 1) + 24) * verticalAlign - 5;
|
||||
for (GlyphPlacement &shape : shaping) {
|
||||
shape.y += dy;
|
||||
}
|
||||
}
|
||||
|
||||
void alignHorizontally(Shaping &shaping, const std::map<uint32_t, GlyphMetrics> &metrics,
|
||||
const uint32_t &start, const uint32_t &end, const float &alignment) {
|
||||
auto & shape = shaping.at(end);
|
||||
auto metric = metrics.find(shape.glyph);
|
||||
if (metric != metrics.end()) {
|
||||
uint32_t lastAdvance = metric->second.advance;
|
||||
int32_t lineIndent = (shape.x + lastAdvance) * alignment;
|
||||
for (uint32_t j = start; j <= end; j++) {
|
||||
shaping[j].x -= lineIndent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FontStack::lineWrap(Shaping &shaping, const float &lineHeight, const float &maxWidth,
|
||||
const float &alignment, const float &verticalAlignment) const {
|
||||
|
||||
std::size_t num_shapes = shaping.size();
|
||||
if (!num_shapes) {
|
||||
return;
|
||||
}
|
||||
uint32_t lastSafeBreak = 0;
|
||||
uint32_t lengthBeforeCurrentLine = 0;
|
||||
uint32_t lineStartIndex = 0;
|
||||
uint32_t line = 0;
|
||||
|
||||
for (uint32_t i = 0; i < shaping.size(); i++) {
|
||||
GlyphPlacement &shape = shaping[i];
|
||||
shape.x -= lengthBeforeCurrentLine;
|
||||
shape.y += lineHeight * line;
|
||||
|
||||
if (shape.x > maxWidth && lastSafeBreak > 0) {
|
||||
|
||||
uint32_t lineLength = shaping[lastSafeBreak + 1].x;
|
||||
|
||||
for (uint32_t k = lastSafeBreak + 1; k <= i; k++) {
|
||||
shaping[k].y += lineHeight;
|
||||
shaping[k].x -= lineLength;
|
||||
}
|
||||
|
||||
if (alignment) {
|
||||
alignHorizontally(shaping, metrics, lineStartIndex, lastSafeBreak - 1, alignment);
|
||||
}
|
||||
|
||||
lineStartIndex = lastSafeBreak + 1;
|
||||
lastSafeBreak = 0;
|
||||
lengthBeforeCurrentLine += lineLength;
|
||||
line++;
|
||||
}
|
||||
if (shape.glyph == 32) {
|
||||
lastSafeBreak = i;
|
||||
}
|
||||
}
|
||||
alignHorizontally(shaping, metrics, lineStartIndex, shaping.size() - 1, alignment);
|
||||
alignVertically(shaping, line + 1, lineHeight, verticalAlignment);
|
||||
}
|
||||
|
||||
GlyphPBF::GlyphPBF(const std::string &fontStack, GlyphRange glyphRange)
|
||||
: future(promise.get_future().share())
|
||||
{
|
||||
// Load the glyph set URL
|
||||
std::string url = util::sprintf<255>("http://mapbox.s3.amazonaws.com/gl-glyphs-256/%s/%d-%d.pbf", fontStack.c_str(), glyphRange.first, glyphRange.second);
|
||||
|
||||
// TODO: Find more reliable URL normalization function
|
||||
std::replace(url.begin(), url.end(), ' ', '+');
|
||||
|
||||
#if defined(DEBUG)
|
||||
fprintf(stderr, "%s\n", url.c_str());
|
||||
#endif
|
||||
|
||||
platform::request_http(url, [&](platform::Response *res) {
|
||||
if (res->code != 200) {
|
||||
// Something went wrong with loading the glyph pbf. Pass on the error to the future listeners.
|
||||
const std::string msg = util::sprintf<255>("[ERROR] failed to load glyphs (%d): %s\n", res->code, res->error_message.c_str());
|
||||
promise.set_exception(std::make_exception_ptr(std::runtime_error(msg)));
|
||||
} else {
|
||||
// Transfer the data to the GlyphSet and signal its availability.
|
||||
// Once it is available, the caller will need to call parse() to actually
|
||||
// parse the data we received. We are not doing this here since this callback is being
|
||||
// called from another (unknown) thread.
|
||||
data.swap(res->body);
|
||||
promise.set_value(*this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
std::shared_future<GlyphPBF &> GlyphPBF::getFuture() {
|
||||
return future;
|
||||
}
|
||||
|
||||
void GlyphPBF::parse(FontStack &stack) {
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
|
||||
if (!data.size()) {
|
||||
// If there is no data, this means we either haven't received any data, or
|
||||
// we have already parsed the data.
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse the glyph PBF
|
||||
pbf glyphs_pbf(reinterpret_cast<const uint8_t *>(data.data()), data.size());
|
||||
|
||||
while (glyphs_pbf.next()) {
|
||||
if (glyphs_pbf.tag == 1) { // stacks
|
||||
pbf fontstack_pbf = glyphs_pbf.message();
|
||||
while (fontstack_pbf.next()) {
|
||||
if (fontstack_pbf.tag == 3) { // glyphs
|
||||
pbf glyph_pbf = fontstack_pbf.message();
|
||||
|
||||
SDFGlyph glyph;
|
||||
|
||||
while (glyph_pbf.next()) {
|
||||
if (glyph_pbf.tag == 1) { // id
|
||||
glyph.id = glyph_pbf.varint();
|
||||
} else if (glyph_pbf.tag == 2) { // bitmap
|
||||
glyph.bitmap = glyph_pbf.string();
|
||||
} else if (glyph_pbf.tag == 3) { // width
|
||||
glyph.metrics.width = glyph_pbf.varint();
|
||||
} else if (glyph_pbf.tag == 4) { // height
|
||||
glyph.metrics.height = glyph_pbf.varint();
|
||||
} else if (glyph_pbf.tag == 5) { // left
|
||||
glyph.metrics.left = glyph_pbf.svarint();
|
||||
} else if (glyph_pbf.tag == 6) { // top
|
||||
glyph.metrics.top = glyph_pbf.svarint();
|
||||
} else if (glyph_pbf.tag == 7) { // advance
|
||||
glyph.metrics.advance = glyph_pbf.varint();
|
||||
} else {
|
||||
glyph_pbf.skip();
|
||||
}
|
||||
}
|
||||
|
||||
stack.insert(glyph.id, glyph);
|
||||
} else {
|
||||
fontstack_pbf.skip();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
glyphs_pbf.skip();
|
||||
}
|
||||
}
|
||||
|
||||
data.clear();
|
||||
}
|
||||
|
||||
|
||||
void GlyphStore::waitForGlyphRanges(const std::string &fontStack, const std::set<GlyphRange> &glyphRanges) {
|
||||
// We are implementing a blocking wait with futures: Every GlyphSet has a future that we are
|
||||
// waiting for until it is loaded.
|
||||
|
||||
FontStack *stack = nullptr;
|
||||
|
||||
std::vector<std::shared_future<GlyphPBF &>> futures;
|
||||
futures.reserve(glyphRanges.size());
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
auto &rangeSets = ranges[fontStack];
|
||||
|
||||
stack = &createFontStack(fontStack);
|
||||
|
||||
// Attempt to load the glyph range. If the GlyphSet already exists, we are getting back
|
||||
// the same shared_future.
|
||||
for (GlyphRange range : glyphRanges) {
|
||||
futures.emplace_back(loadGlyphRange(fontStack, rangeSets, range));
|
||||
}
|
||||
}
|
||||
|
||||
// Now that we potentially created all GlyphSets, we are waiting for the results, one by one.
|
||||
// When we get a result (or the GlyphSet is aready loaded), we are attempting to parse the
|
||||
// GlyphSet.
|
||||
for (std::shared_future<GlyphPBF &> &future : futures) {
|
||||
future.get().parse(*stack);
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_future<GlyphPBF &> GlyphStore::loadGlyphRange(const std::string &name, std::map<GlyphRange, std::unique_ptr<GlyphPBF>> &rangeSets, const GlyphRange range) {
|
||||
auto range_it = rangeSets.find(range);
|
||||
if (range_it == rangeSets.end()) {
|
||||
// We don't have this glyph set yet for this font stack.
|
||||
range_it = rangeSets.emplace(range, std::make_unique<GlyphPBF>(name, range)).first;
|
||||
}
|
||||
|
||||
return range_it->second->getFuture();
|
||||
}
|
||||
|
||||
FontStack &GlyphStore::createFontStack(const std::string &fontStack) {
|
||||
auto stack_it = stacks.find(fontStack);
|
||||
if (stack_it == stacks.end()) {
|
||||
stack_it = stacks.emplace(fontStack, std::make_unique<FontStack>()).first;
|
||||
}
|
||||
return *stack_it->second.get();
|
||||
}
|
||||
|
||||
FontStack &GlyphStore::getFontStack(const std::string &fontStack) {
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
return createFontStack(fontStack);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -35,21 +35,12 @@ bool byScale(const Anchor &a, const Anchor &b) { return a.scale < b.scale; }
|
||||
static const Glyph null_glyph;
|
||||
|
||||
inline const Glyph &getGlyph(const GlyphPlacement &placed,
|
||||
const IndexedFaces &faces) {
|
||||
if (placed.face < faces.size()) {
|
||||
const GlyphPositions &face = *faces[placed.face];
|
||||
if (&face) {
|
||||
auto it = face.find(placed.glyph);
|
||||
if (it != face.end()) {
|
||||
return it->second;
|
||||
} else {
|
||||
fprintf(stderr, "glyph %d does not exist\n", placed.glyph);
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "face pointer is null\n");
|
||||
}
|
||||
const GlyphPositions &face) {
|
||||
auto it = face.find(placed.glyph);
|
||||
if (it != face.end()) {
|
||||
return it->second;
|
||||
} else {
|
||||
fprintf(stderr, "face does not exist\n");
|
||||
fprintf(stderr, "glyph %d does not exist\n", placed.glyph);
|
||||
}
|
||||
|
||||
return null_glyph;
|
||||
@@ -146,15 +137,12 @@ void getSegmentGlyphs(std::back_insert_iterator<GlyphInstances> glyphs,
|
||||
}
|
||||
|
||||
void getGlyphs(PlacedGlyphs &glyphs, GlyphBoxes &boxes,
|
||||
Anchor &anchor, float advance, const Shaping &shaping,
|
||||
const IndexedFaces &faces, float fontScale,
|
||||
Anchor &anchor, vec2<float> origin, const Shaping &shaping,
|
||||
const GlyphPositions &face, float fontScale,
|
||||
bool horizontal, const std::vector<Coordinate> &line,
|
||||
float maxAngleDelta, float rotate) {
|
||||
// The total text advance is the width of this label.
|
||||
|
||||
// TODO: figure out correct ascender height.
|
||||
vec2<float> origin{-advance / 2, -17};
|
||||
|
||||
// TODO: allow setting an alignment
|
||||
// var alignment = 'center';
|
||||
// if (alignment == 'center') {
|
||||
@@ -166,7 +154,7 @@ void getGlyphs(PlacedGlyphs &glyphs, GlyphBoxes &boxes,
|
||||
const uint32_t buffer = 3;
|
||||
|
||||
for (const GlyphPlacement &placed : shaping) {
|
||||
const Glyph &glyph = getGlyph(placed, faces);
|
||||
const Glyph &glyph = getGlyph(placed, face);
|
||||
if (!glyph) {
|
||||
// This glyph is empty and doesn't have any pixels that we'd need to
|
||||
// show.
|
||||
@@ -259,7 +247,7 @@ void getGlyphs(PlacedGlyphs &glyphs, GlyphBoxes &boxes,
|
||||
void Placement::addFeature(TextBucket& bucket,
|
||||
const std::vector<Coordinate> &line,
|
||||
const BucketGeometryDescription &info,
|
||||
const IndexedFaces &faces,
|
||||
const GlyphPositions &face,
|
||||
const Shaping &shaping) {
|
||||
|
||||
const bool horizontal = info.path == TextPathType::Horizontal;
|
||||
@@ -270,11 +258,8 @@ void Placement::addFeature(TextBucket& bucket,
|
||||
const float fontScale =
|
||||
(tileExtent / util::tileSize) / (glyphSize / info.size);
|
||||
|
||||
const float advance = measureText(faces, shaping);
|
||||
Anchors anchors;
|
||||
|
||||
// fprintf(stderr, "adding feature with advance %f\n", advance);
|
||||
|
||||
if (line.size() == 1) {
|
||||
// Point labels
|
||||
anchors = Anchors{{Anchor{
|
||||
@@ -294,7 +279,7 @@ void Placement::addFeature(TextBucket& bucket,
|
||||
PlacedGlyphs glyphs;
|
||||
GlyphBoxes boxes;
|
||||
|
||||
getGlyphs(glyphs, boxes, anchor, advance, shaping, faces, fontScale, horizontal,
|
||||
getGlyphs(glyphs, boxes, anchor, info.translate, shaping, face, fontScale, horizontal,
|
||||
line, maxAngleDelta, rotate);
|
||||
PlacementProperty place =
|
||||
collision.place(boxes, anchor, anchor.scale, maxPlacementScale,
|
||||
@@ -304,16 +289,3 @@ void Placement::addFeature(TextBucket& bucket,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float Placement::measureText(const IndexedFaces &faces,
|
||||
const Shaping &shaping) {
|
||||
float advance = 0;
|
||||
|
||||
// TODO: advance is not calculated correctly. we should instead use the
|
||||
// bounding box of the glyph placement.
|
||||
for (const GlyphPlacement &shape : shaping) {
|
||||
advance += getGlyph(shape, faces).metrics.advance;
|
||||
}
|
||||
|
||||
return advance;
|
||||
}
|
||||
|
||||
@@ -154,8 +154,12 @@ void propagate(std::map<Tile::ID, ClipID> &mapping, std::list<TileHierarchy> &ar
|
||||
item.clip.mask >>= parent.length;
|
||||
item.clip.mask |= parent.mask;
|
||||
item.clip.length += parent.length;
|
||||
#if defined(DEBUG)
|
||||
auto result = mapping.emplace(item.id, item.clip);
|
||||
assert("Tried to insert a duplicate item" && result.second == true);
|
||||
#else
|
||||
mapping.emplace(item.id, item.clip);
|
||||
#endif
|
||||
propagate(mapping, item.children, const_cast<const ClipID &>(item.clip));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,8 +2,26 @@
|
||||
|
||||
const float llmr::util::tileSize = 512.0f;
|
||||
|
||||
#if defined(DEBUG)
|
||||
const bool llmr::debug::tileParseWarnings = false;
|
||||
const bool llmr::debug::styleParseWarnings = false;
|
||||
const bool llmr::debug::spriteWarnings = false;
|
||||
const bool llmr::debug::renderWarnings = false;
|
||||
const bool llmr::debug::renderTree = false;
|
||||
const bool llmr::debug::labelTextMissingWarning = true;
|
||||
const bool llmr::debug::missingFontStackWarning = true;
|
||||
const bool llmr::debug::missingFontFaceWarning = true;
|
||||
const bool llmr::debug::glyphWarning = true;
|
||||
const bool llmr::debug::shapingWarning = true;
|
||||
#else
|
||||
const bool llmr::debug::tileParseWarnings = false;
|
||||
const bool llmr::debug::styleParseWarnings = false;
|
||||
const bool llmr::debug::spriteWarnings = false;
|
||||
const bool llmr::debug::renderWarnings = false;
|
||||
const bool llmr::debug::renderTree = false;
|
||||
const bool llmr::debug::labelTextMissingWarning = false;
|
||||
const bool llmr::debug::missingFontStackWarning = false;
|
||||
const bool llmr::debug::missingFontFaceWarning = false;
|
||||
const bool llmr::debug::glyphWarning = false;
|
||||
const bool llmr::debug::shapingWarning = false;
|
||||
#endif
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <png.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
|
||||
|
||||
std::string llmr::util::compress_png(int width, int height, void *rgba, bool flip) {
|
||||
@@ -137,7 +138,7 @@ Image::Image(const std::string &data) {
|
||||
png_size_t rowbytes = png_get_rowbytes(png, info);
|
||||
assert(width * 4 == rowbytes);
|
||||
|
||||
img = (char *)malloc(width * height * 4);
|
||||
img = (char *)std::malloc(width * height * 4);
|
||||
char *surface = img;
|
||||
assert(surface);
|
||||
|
||||
@@ -160,7 +161,7 @@ Image::Image(const std::string &data) {
|
||||
fprintf(stderr, "loading PNG failed: %s\n", e.what());
|
||||
png_destroy_read_struct(&png, &info, nullptr);
|
||||
if (img) {
|
||||
free(img);
|
||||
std::free(img);
|
||||
img = nullptr;
|
||||
}
|
||||
width = 0;
|
||||
@@ -170,7 +171,7 @@ Image::Image(const std::string &data) {
|
||||
|
||||
Image::~Image() {
|
||||
if (img) {
|
||||
free(img);
|
||||
std::free(img);
|
||||
img = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
Raster::Raster(Texturepool &texturepool)
|
||||
Raster::Raster(const std::shared_ptr<Texturepool> &texturepool)
|
||||
: texturepool(texturepool)
|
||||
{}
|
||||
|
||||
Raster::~Raster() {
|
||||
if (textured) {
|
||||
texturepool.removeTextureID(texture);
|
||||
texturepool->removeTextureID(texture);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ void Raster::bind(bool linear) {
|
||||
}
|
||||
|
||||
if (img && !textured) {
|
||||
texture = texturepool.getTextureID();
|
||||
texture = texturepool->getTextureID();
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <llmr/util/threadpool.hpp>
|
||||
#include <llmr/util/std.hpp>
|
||||
#include <thread>
|
||||
#include <memory>
|
||||
|
||||
using namespace llmr::util;
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user