Merge branch 'master' of github.com:mapbox/mapbox-gl-native into multi-os

Conflicts:
	.travis.yml
This commit is contained in:
Dane Springmeyer
2014-06-03 11:17:40 -07:00
104 changed files with 2901 additions and 1189 deletions

7
.gitignore vendored
View File

@@ -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
View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -1,4 +1,4 @@
[![Travis](https://api.travis-ci.com/mapbox/llmr-native.svg?token=yZ9zvfZwYkJNWivRLoyX)](https://magnum.travis-ci.com/mapbox/llmr-native)
[![Travis](https://api.travis-ci.com/mapbox/mapbox-gl-native.svg?token=yZ9zvfZwYkJNWivRLoyX)](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.

View File

@@ -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);

File diff suppressed because it is too large Load Diff

View File

@@ -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'
}
}
}

View File

@@ -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);

View File

@@ -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;
}];

View File

@@ -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)

View File

@@ -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();

View File

@@ -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;

View 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

View File

@@ -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;

View File

@@ -9,6 +9,7 @@
#include <forward_list>
#include <string>
#include <bitset>
#include <memory>
namespace llmr {

View File

@@ -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;

View File

@@ -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;
};

View File

@@ -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;
};
}

View File

@@ -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;

View File

@@ -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;
};

View File

@@ -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;
};
}

View File

@@ -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);

View File

@@ -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;
};
}
}

View File

@@ -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>

View File

@@ -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();

View File

@@ -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;
};
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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>();

View File

@@ -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;

View 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

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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;

View 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
View 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

View File

@@ -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;

View File

@@ -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>

View File

@@ -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

View File

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@@ -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

Submodule ios/mapbox-gl-cocoa added at 066a077f28

View File

@@ -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)'
}
]
}
],
}

View File

@@ -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

View File

@@ -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)'
]
}]
]

View File

@@ -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',

View File

@@ -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
View 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;
}

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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
View 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}";

View File

@@ -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 \

View File

@@ -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);

View File

@@ -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
}
};

View File

@@ -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;
}
};

View File

@@ -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;

View File

@@ -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() {}

View File

@@ -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);
}

View File

@@ -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

View File

@@ -122,13 +122,6 @@ bool TransformState::isChanging() const {
}
#pragma mark - Interactive
bool TransformState::isInteractive() const {
return interactive;
}
#pragma mark - (private helper functions)

View File

@@ -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 {

View File

@@ -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()) {

View File

@@ -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) {

View File

@@ -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

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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));

View File

@@ -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);

View File

@@ -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);

View File

@@ -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;

View File

@@ -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());
}

View File

@@ -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);

View File

@@ -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

View File

@@ -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());

View File

@@ -3,7 +3,7 @@
using namespace llmr;
RasterBucket::RasterBucket(Texturepool &texturepool)
RasterBucket::RasterBucket(const std::shared_ptr<Texturepool> &texturepool)
: raster(texturepool) {
}

View File

@@ -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);
}
}

View File

@@ -12,7 +12,9 @@ GaussianShader::GaussianShader()
shaders[GAUSSIAN_SHADER].fragment
) {
if (!valid) {
#if defined(DEBUG)
fprintf(stderr, "invalid raster shader\n");
#endif
return;
}

View File

@@ -12,7 +12,9 @@ RasterShader::RasterShader()
shaders[RASTER_SHADER].fragment
) {
if (!valid) {
#if defined(DEBUG)
fprintf(stderr, "invalid raster shader\n");
#endif
return;
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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];

View File

@@ -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
}
}

View File

@@ -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;

View File

@@ -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
View 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
View 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);
}
}

View File

@@ -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;
}

View File

@@ -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));
};
}

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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