Merge branch 'master' of github.com:mapbox/llmr-native
This commit is contained in:
7
.gitignore
vendored
7
.gitignore
vendored
@@ -7,4 +7,9 @@
|
||||
/ios/build
|
||||
/config.gypi
|
||||
/config.mk
|
||||
/build
|
||||
/build
|
||||
/node_modules
|
||||
/include/llmr/shader/shaders.hpp
|
||||
/include/llmr/style/resources.hpp
|
||||
/src/shader/shaders.cpp
|
||||
/src/style/resources.cpp
|
||||
|
||||
14
Makefile
14
Makefile
@@ -9,6 +9,10 @@ all: llmr
|
||||
llmr: config.gypi src llmr.gyp
|
||||
@if [ ! -f out ]; then echo 'please run ./configure first'; else $(MAKE) -C out BUILDTYPE=Release V=$(V) llmr-x86; fi;
|
||||
|
||||
gtest: config.gypi src llmr.gyp
|
||||
deps/run_gyp llmr.gyp -Goutput_dir=./out/ --depth=. --generator-output=./build/gtest -f make
|
||||
make -C build/gtest gtest V=$(V)
|
||||
|
||||
# build OS X app with pure make
|
||||
app: config.gypi src macosx/llmr-app.gyp
|
||||
deps/run_gyp macosx/llmr-app.gyp -Goutput_dir=./out/ --depth=. --generator-output=./build/macosx-make -f make
|
||||
@@ -58,6 +62,13 @@ isim: config.gypi src ios/llmr-app.gyp
|
||||
# does not work
|
||||
#"/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/Applications/iPhone Simulator.app/Contents/MacOS/iPhone Simulator" -SimulateApplication ios/build/Release-iphonesimulator/llmr.app/llmr
|
||||
|
||||
test: config.gypi src test/test.gyp
|
||||
deps/run_gyp test/test.gyp -Goutput_dir=. --depth=. --generator-output=./build/test -f make
|
||||
make -C build/test V=$(V)
|
||||
@for FILE in build/test/Release/test_*; do \
|
||||
$${FILE}; \
|
||||
done
|
||||
|
||||
clean:
|
||||
-rm -rf out
|
||||
-rm -rf build
|
||||
@@ -71,7 +82,4 @@ distclean:
|
||||
-rm -rf macosx/llmr-app.xcodeproj
|
||||
-rm -rf ios/llmr-app.xcodeproj
|
||||
|
||||
test: all
|
||||
echo test
|
||||
|
||||
.PHONY: test linux
|
||||
|
||||
@@ -4,12 +4,17 @@
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
module.exports = function() {
|
||||
var glsl;
|
||||
try { glsl = require('glsl-optimizer'); } catch(err) {}
|
||||
|
||||
module.exports = function(param) {
|
||||
var name;
|
||||
var shaders = {};
|
||||
|
||||
var shaderFiles = fs.readdirSync('src/shader');
|
||||
|
||||
|
||||
// Load shaders
|
||||
for (var i = 0; i < shaderFiles.length; i++) {
|
||||
var parts = shaderFiles[i].match(/^(.+)\.(vertex|fragment)\.glsl$/);
|
||||
if (parts) {
|
||||
@@ -22,6 +27,48 @@ module.exports = function() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Optimize shader
|
||||
if (glsl) {
|
||||
var preamble = '';
|
||||
var target = glsl.TARGET_OPENGL;
|
||||
if (param == 'gles2') {
|
||||
target = glsl.TARGET_OPENGLES20;
|
||||
preamble = 'precision highp float;';
|
||||
} else if (param == 'gles3') {
|
||||
target = glsl.TARGET_OPENGLES30;
|
||||
preamble = 'precision highp float;';
|
||||
} else {
|
||||
preamble = '#version 120';
|
||||
}
|
||||
|
||||
var compiler = new glsl.Compiler(target);
|
||||
for (name in shaders) {
|
||||
var vertex_shader = new glsl.Shader(compiler, glsl.VERTEX_SHADER, preamble + '\n' + shaders[name].vertex);
|
||||
if (vertex_shader.compiled()) {
|
||||
shaders[name].vertex = vertex_shader.output();
|
||||
} else {
|
||||
console.warn('failed to optimize %s vertex shader', name);
|
||||
process.exit(1);
|
||||
}
|
||||
vertex_shader.dispose();
|
||||
|
||||
var fragment_shader = new glsl.Shader(compiler, glsl.FRAGMENT_SHADER, preamble + '\n' + shaders[name].fragment);
|
||||
if (fragment_shader.compiled()) {
|
||||
shaders[name].fragment = fragment_shader.output();
|
||||
} else {
|
||||
console.warn('failed to optimize %s fragment shader', name);
|
||||
process.exit(1);
|
||||
}
|
||||
fragment_shader.dispose();
|
||||
}
|
||||
compiler.dispose();
|
||||
} else {
|
||||
console.warn('Not optimizing shaders');
|
||||
}
|
||||
|
||||
|
||||
// Save to file
|
||||
var lines = [];
|
||||
var consts = [];
|
||||
for (var name in shaders) {
|
||||
@@ -69,6 +116,7 @@ module.exports = function() {
|
||||
code += '\n};\n';
|
||||
|
||||
fs.writeFileSync('src/shader/shaders.cpp', code);
|
||||
|
||||
};
|
||||
|
||||
module.exports();
|
||||
module.exports(process.argv[2]);
|
||||
|
||||
64
bin/style.js
64
bin/style.js
@@ -87,10 +87,45 @@ module.exports = {
|
||||
"value": ["Restaurant"],
|
||||
"type": "point"
|
||||
},
|
||||
"country_label": {
|
||||
"source": "streets",
|
||||
"type": "text",
|
||||
"layer": "country_label",
|
||||
"feature_type": "point",
|
||||
"padding": 10,
|
||||
"text_field": "name",
|
||||
"path": "horizontal",
|
||||
"font": "Open Sans, Jomolhari, Siyam Rupali, Alef, Arial Unicode MS",
|
||||
"fontSize": 16
|
||||
},
|
||||
"place_label": {
|
||||
"source": "streets",
|
||||
"type": "text",
|
||||
"layer": "place_label",
|
||||
"feature_type": "point",
|
||||
"text_field": "name",
|
||||
"path": "horizontal",
|
||||
"font": "Open Sans, Jomolhari, Siyam Rupali, Alef, Arial Unicode MS",
|
||||
"fontSize": 18,
|
||||
"alwaysVisible": true
|
||||
},
|
||||
"road_label": {
|
||||
"source": "streets",
|
||||
"type": "text",
|
||||
"layer": "road_label",
|
||||
"feature_type": "line",
|
||||
"text_field": "name",
|
||||
"path": "curve",
|
||||
"font": "Open Sans, Jomolhari, Siyam Rupali, Alef, Arial Unicode MS",
|
||||
"fontSize": 12,
|
||||
"textMinDistance": 250,
|
||||
"maxAngleDelta": 1.04, // radians
|
||||
},
|
||||
},
|
||||
"sprite": "img/maki-sprite",
|
||||
"constants": {
|
||||
"park": "#c8df9f",
|
||||
"text": "#000000",
|
||||
},
|
||||
"structure": [
|
||||
{ "name": "park", "bucket": "park" },
|
||||
@@ -105,6 +140,9 @@ module.exports = {
|
||||
{ "name": "embassy_poi", "bucket": "embassy_poi" },
|
||||
{ "name": "park_poi", "bucket": "park_poi" },
|
||||
{ "name": "restaurant_poi", "bucket": "restaurant_poi" },
|
||||
{ "name": "country_label", "bucket": "country_label" },
|
||||
{ "name": "place_label", "bucket": "place_label" },
|
||||
{ "name": "road_label", "bucket": "road_label" },
|
||||
],
|
||||
"classes": [
|
||||
{
|
||||
@@ -156,8 +194,8 @@ module.exports = {
|
||||
{ z: 0, val: 0.5 },
|
||||
{ z: 13, val: 0.5 },
|
||||
{ z: 16, val: 2 },
|
||||
{ z: 20, val: 32 },
|
||||
{ z: 30, val: 32 }
|
||||
{ z: 20, val: 16 },
|
||||
{ z: 30, val: 16 }
|
||||
],
|
||||
},
|
||||
"road_large": {
|
||||
@@ -169,8 +207,8 @@ module.exports = {
|
||||
{ z: 11, val: 0.5 },
|
||||
{ z: 13, val: 1 },
|
||||
{ z: 16, val: 4 },
|
||||
{ z: 20, val: 64 },
|
||||
{ z: 30, val: 64 }
|
||||
{ z: 20, val: 32 },
|
||||
{ z: 30, val: 32 }
|
||||
],
|
||||
},
|
||||
"alcohol_poi": {
|
||||
@@ -208,6 +246,24 @@ module.exports = {
|
||||
"image": "restaurant",
|
||||
"opacity": [ "linear", 15, 0, 1.0, 0, 0.75 ]
|
||||
},
|
||||
"country_label": {
|
||||
"type": "text",
|
||||
"stroke": [ 1, 1, 1, 0.7 ],
|
||||
"color": "text",
|
||||
"size": 16
|
||||
},
|
||||
"place_label": {
|
||||
"type": "text",
|
||||
"stroke": [1,1,1,0.7],
|
||||
"color": "text",
|
||||
"size": 18
|
||||
},
|
||||
"road_label": {
|
||||
"type": "text",
|
||||
"color": "text",
|
||||
"stroke": [1,1,1,0.7],
|
||||
"size": ["exponential", 14, 8, 1, 8, 12]
|
||||
},
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
13
common.gypi
13
common.gypi
@@ -9,16 +9,19 @@
|
||||
'cflags_cc': ['-std=c++11'],
|
||||
'configurations': {
|
||||
'Debug': {
|
||||
'cflags': [ '-g', '-O0' ],
|
||||
'cflags': [ '-g', '-O0', '-I<(boost_root)/include' ],
|
||||
'defines': [ 'DEBUG' ],
|
||||
'xcode_settings': {
|
||||
'OTHER_CPLUSPLUSFLAGS': [ '-g']
|
||||
'OTHER_CPLUSPLUSFLAGS': [ '-g', '-I<(boost_root)/include' ]
|
||||
}
|
||||
},
|
||||
'Release': {
|
||||
'cflags': [ '-O3'],
|
||||
'defines': [ 'NDEBUG' ]
|
||||
'cflags': [ '-O3', '-I<(boost_root)/include' ],
|
||||
'defines': [ 'NDEBUG' ],
|
||||
'xcode_settings': {
|
||||
'OTHER_CPLUSPLUSFLAGS': [ '-I<(boost_root)/include' ]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,12 +68,10 @@ public:
|
||||
glfwSetKeyCallback(window, key);
|
||||
}
|
||||
|
||||
|
||||
static void character(GLFWwindow *window, unsigned int codepoint) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
static void key(GLFWwindow *window, int key, int scancode, int action, int mods) {
|
||||
MapView *mapView = (MapView *)glfwGetWindowUserPointer(window);
|
||||
|
||||
@@ -95,7 +93,6 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void scroll(GLFWwindow *window, double xoffset, double yoffset) {
|
||||
MapView *mapView = (MapView *)glfwGetWindowUserPointer(window);
|
||||
double delta = yoffset * 40;
|
||||
@@ -234,3 +231,42 @@ public:
|
||||
llmr::Settings& settings;
|
||||
llmr::Map map;
|
||||
};
|
||||
|
||||
namespace llmr {
|
||||
namespace platform {
|
||||
|
||||
double time() {
|
||||
return glfwGetTime();
|
||||
}
|
||||
|
||||
|
||||
void show_debug_image(std::string name, const char *data, size_t width, size_t height) {
|
||||
static GLFWwindow *debug_window = nullptr;
|
||||
if (!debug_window) {
|
||||
debug_window = glfwCreateWindow(width, height, name.c_str(), nullptr, nullptr);
|
||||
if (!debug_window) {
|
||||
glfwTerminate();
|
||||
fprintf(stderr, "Failed to initialize window\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
GLFWwindow *current_window = glfwGetCurrentContext();
|
||||
|
||||
glfwSetWindowSize(debug_window, width, height);
|
||||
glfwMakeContextCurrent(debug_window);
|
||||
|
||||
int fb_width, fb_height;
|
||||
glfwGetFramebufferSize(debug_window, &fb_width, &fb_height);
|
||||
float scale = (float)fb_width / (float)width;
|
||||
|
||||
glPixelZoom(scale, -scale);
|
||||
glRasterPos2f(-1.0f, 1.0f);
|
||||
glDrawPixels(width, height, GL_LUMINANCE, GL_UNSIGNED_BYTE, data);
|
||||
glfwSwapBuffers(debug_window);
|
||||
|
||||
glfwMakeContextCurrent(current_window);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
9
configure
vendored
9
configure
vendored
@@ -19,6 +19,11 @@ parser.add_option("--debug",
|
||||
dest="debug",
|
||||
help="Also build debug build")
|
||||
|
||||
parser.add_option("--boost",
|
||||
action="store",
|
||||
dest="boost_root",
|
||||
help="Path to boost (defaults to /usr/local)")
|
||||
|
||||
parser.add_option("--glfw3",
|
||||
action="store",
|
||||
dest="glfw3",
|
||||
@@ -55,6 +60,10 @@ def pkg_config(pkg):
|
||||
return (libs, cflags)
|
||||
|
||||
def configure_llmr(o):
|
||||
if options.boost_root:
|
||||
o['variables']['boost_root'] = options.boost_root
|
||||
else:
|
||||
o['variables']['boost_root'] = '/usr/local'
|
||||
o['target_defaults']['default_configuration'] = 'Debug' if options.debug else 'Release'
|
||||
|
||||
def configure_glfw3(o):
|
||||
|
||||
9592
deps/gtest/gtest-all.cc
vendored
Normal file
9592
deps/gtest/gtest-all.cc
vendored
Normal file
File diff suppressed because it is too large
Load Diff
14
deps/gtest/gtest.gyp
vendored
Normal file
14
deps/gtest/gtest.gyp
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
'targets': [{
|
||||
'target_name': 'gtest',
|
||||
'type': 'static_library',
|
||||
'sources': [
|
||||
'gtest-all.cc'
|
||||
],
|
||||
'direct_dependent_settings': {
|
||||
'include_dirs': [
|
||||
'.',
|
||||
],
|
||||
},
|
||||
}]
|
||||
}
|
||||
20061
deps/gtest/gtest/gtest.h
vendored
Normal file
20061
deps/gtest/gtest/gtest.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
25
include/llmr/geometry/anchor.hpp
Normal file
25
include/llmr/geometry/anchor.hpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#ifndef LLMR_GEOMETRY_ANCHOR
|
||||
#define LLMR_GEOMETRY_ANCHOR
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace llmr {
|
||||
|
||||
struct Anchor {
|
||||
float x = 0.0f;
|
||||
float y = 0.0f;
|
||||
float angle = 0.0f;
|
||||
float scale = 0.0f;
|
||||
int segment = -1;
|
||||
|
||||
explicit Anchor(float x, float y, float angle, float scale)
|
||||
: x(x), y(y), angle(angle), scale(scale) {}
|
||||
explicit Anchor(float x, float y, float angle, float scale, int segment)
|
||||
: x(x), y(y), angle(angle), scale(scale), segment(segment) {}
|
||||
};
|
||||
|
||||
typedef std::vector<Anchor> Anchors;
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -2,23 +2,17 @@
|
||||
#define LLMR_GEOMETRY_BINPACK
|
||||
|
||||
#include <llmr/util/noncopyable.hpp>
|
||||
#include <llmr/util/rect.hpp>
|
||||
#include <cstdint>
|
||||
#include <list>
|
||||
|
||||
namespace llmr {
|
||||
|
||||
template <typename T>
|
||||
struct Rect {
|
||||
Rect(T x, T y, T w, T h) : x(x), y(y), w(w), h(h) {}
|
||||
T x = 0, y = 0;
|
||||
T w = 0, h = 0;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class BinPack : private noncopyable {
|
||||
class BinPack : private util::noncopyable {
|
||||
public:
|
||||
BinPack(T width, T height)
|
||||
: free(1, { 0, 0, width, height }) {}
|
||||
: free(1, Rect<uint16_t>{ 0, 0, width, height }) {}
|
||||
public:
|
||||
Rect<T> allocate(T width, T height) {
|
||||
// Find the smallest free rect angle
|
||||
@@ -35,7 +29,7 @@ public:
|
||||
|
||||
if (smallest == free.end()) {
|
||||
// There's no space left for this char.
|
||||
return { 0, 0, 0, 0 };
|
||||
return Rect<uint16_t>{ 0, 0, 0, 0 };
|
||||
} else {
|
||||
Rect<T> rect = *smallest;
|
||||
free.erase(smallest);
|
||||
@@ -60,7 +54,7 @@ public:
|
||||
if (rect.h > height) free.emplace_back(rect.x, rect.y + height, width, rect.h - height);
|
||||
}
|
||||
|
||||
return { rect.x, rect.y, width, height };
|
||||
return Rect<uint16_t>{ rect.x, rect.y, width, height };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <cstdlib>
|
||||
#include <cassert>
|
||||
#include <llmr/platform/gl.hpp>
|
||||
#include <llmr/util/noncopyable.hpp>
|
||||
|
||||
namespace llmr {
|
||||
|
||||
@@ -12,7 +13,7 @@ template <
|
||||
int bufferType = GL_ARRAY_BUFFER,
|
||||
size_t defaultLength = 8192
|
||||
>
|
||||
class Buffer {
|
||||
class Buffer : private util::noncopyable {
|
||||
public:
|
||||
~Buffer() {
|
||||
if (array) {
|
||||
@@ -47,7 +48,7 @@ public:
|
||||
|
||||
protected:
|
||||
// increase the buffer size by at least /required/ bytes.
|
||||
void *addElement() {
|
||||
inline void *addElement() {
|
||||
assert("Buffer is already bound to GPU" && buffer == 0);
|
||||
if (length < pos + itemSize) {
|
||||
while (length < pos + itemSize) length += defaultLength;
|
||||
|
||||
52
include/llmr/geometry/glyph_atlas.hpp
Normal file
52
include/llmr/geometry/glyph_atlas.hpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#ifndef LLMR_GEOMETRY_GLYPH_ATLAS
|
||||
#define LLMR_GEOMETRY_GLYPH_ATLAS
|
||||
|
||||
#include <llmr/geometry/binpack.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
|
||||
namespace llmr {
|
||||
|
||||
class VectorTileGlyph;
|
||||
|
||||
class GlyphAtlas {
|
||||
public:
|
||||
|
||||
private:
|
||||
struct GlyphValue {
|
||||
GlyphValue(const Rect<uint16_t>& rect, uint64_t id)
|
||||
: rect(rect), ids({ id }) {}
|
||||
Rect<uint16_t> rect;
|
||||
std::set<uint64_t> ids;
|
||||
};
|
||||
|
||||
public:
|
||||
GlyphAtlas(uint16_t width, uint16_t height);
|
||||
~GlyphAtlas();
|
||||
|
||||
|
||||
Rect<uint16_t> addGlyph(uint64_t tile_id, const std::string& face_name,
|
||||
const VectorTileGlyph& glyph);
|
||||
void removeGlyphs(uint64_t tile_id);
|
||||
void bind();
|
||||
|
||||
public:
|
||||
const uint16_t width = 0;
|
||||
const uint16_t height = 0;
|
||||
|
||||
private:
|
||||
std::mutex mtx;
|
||||
BinPack<uint16_t> bin;
|
||||
std::map<std::string, std::map<uint32_t, GlyphValue>> index;
|
||||
char *const data = nullptr;
|
||||
std::atomic<bool> dirty;
|
||||
uint32_t texture = 0;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
13
include/llmr/geometry/interpolate.hpp
Normal file
13
include/llmr/geometry/interpolate.hpp
Normal file
@@ -0,0 +1,13 @@
|
||||
#ifndef LLMR_GEOMETRY_INTERPOLATE
|
||||
#define LLMR_GEOMETRY_INTERPOLATE
|
||||
|
||||
#include <llmr/geometry/anchor.hpp>
|
||||
#include <llmr/util/math.hpp>
|
||||
|
||||
namespace llmr {
|
||||
|
||||
Anchors interpolate(const std::vector<Coordinate> &vertices, float spacing,
|
||||
float minScale = 0.0f, int start = 0);
|
||||
}
|
||||
|
||||
#endif
|
||||
25
include/llmr/geometry/text_buffer.hpp
Normal file
25
include/llmr/geometry/text_buffer.hpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#ifndef LLMR_GEOMETRY_TEXT_BUFFER
|
||||
#define LLMR_GEOMETRY_TEXT_BUFFER
|
||||
|
||||
#include "buffer.hpp"
|
||||
#include <array>
|
||||
|
||||
namespace llmr {
|
||||
|
||||
class TextVertexBuffer : public Buffer <
|
||||
16,
|
||||
GL_ARRAY_BUFFER,
|
||||
32768
|
||||
> {
|
||||
public:
|
||||
typedef int16_t vertex_type;
|
||||
|
||||
static const double angleFactor;
|
||||
|
||||
size_t add(int16_t x, int16_t y, float ox, float oy, uint16_t tx, uint16_t ty, float angle, float minzoom, std::array<float, 2> range, float maxzoom, float labelminzoom);
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -5,6 +5,8 @@
|
||||
#include <llmr/map/tile_data.hpp>
|
||||
#include <llmr/map/transform.hpp>
|
||||
#include <llmr/style/style.hpp>
|
||||
#include <llmr/style/style.hpp>
|
||||
#include <llmr/geometry/glyph_atlas.hpp>
|
||||
#include <llmr/renderer/painter.hpp>
|
||||
#include <llmr/util/noncopyable.hpp>
|
||||
#include <llmr/util/texturepool.hpp>
|
||||
@@ -76,6 +78,7 @@ private:
|
||||
Transform transform;
|
||||
Texturepool texturepool;
|
||||
Style style;
|
||||
GlyphAtlas glyphAtlas;
|
||||
Painter painter;
|
||||
|
||||
int32_t min_zoom;
|
||||
|
||||
@@ -5,10 +5,21 @@
|
||||
#include <llmr/util/vec.hpp>
|
||||
#include <llmr/util/mat4.hpp>
|
||||
#include <llmr/util/noncopyable.hpp>
|
||||
#include <llmr/geometry/glyph_atlas.hpp>
|
||||
#include <llmr/geometry/debug_font_buffer.hpp>
|
||||
#include <llmr/geometry/vao.hpp>
|
||||
#include <llmr/map/vector_tile.hpp>
|
||||
#include <llmr/platform/platform.hpp>
|
||||
|
||||
#include <llmr/renderer/bucket.hpp>
|
||||
|
||||
#include <llmr/geometry/vertex_buffer.hpp>
|
||||
#include <llmr/geometry/elements_buffer.hpp>
|
||||
#include <llmr/geometry/fill_buffer.hpp>
|
||||
#include <llmr/geometry/line_buffer.hpp>
|
||||
#include <llmr/geometry/point_buffer.hpp>
|
||||
#include <llmr/geometry/text_buffer.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <forward_list>
|
||||
#include <string>
|
||||
@@ -21,17 +32,10 @@ namespace llmr {
|
||||
|
||||
class Style;
|
||||
class Raster;
|
||||
class Bucket;
|
||||
class LayerDescription;
|
||||
class BucketDescription;
|
||||
class VectorTile;
|
||||
class VectorTileLayer;
|
||||
class FillVertexBuffer;
|
||||
class LineVertexBuffer;
|
||||
class PointVertexBuffer;
|
||||
class TriangleElementsBuffer;
|
||||
class LineElementsBuffer;
|
||||
class PointElementsBuffer;
|
||||
|
||||
|
||||
class PlainShader;
|
||||
|
||||
class TileData : public std::enable_shared_from_this<TileData>,
|
||||
@@ -53,20 +57,11 @@ public:
|
||||
};
|
||||
|
||||
public:
|
||||
TileData(Tile::ID id, const Style& style, const bool use_raster = false, const bool use_retina = false);
|
||||
TileData(Tile::ID id, const Style& style, GlyphAtlas& glyphAtlas, const bool use_raster = false, const bool use_retina = false);
|
||||
~TileData();
|
||||
|
||||
// Start loading the tile.
|
||||
void request();
|
||||
|
||||
// Other functions
|
||||
bool parse();
|
||||
void parseStyleLayers(const VectorTile& tile, const std::vector<LayerDescription>& layers);
|
||||
std::shared_ptr<Bucket> createBucket(const VectorTile& tile, const BucketDescription& bucket_desc);
|
||||
std::shared_ptr<Bucket> createFillBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc);
|
||||
std::shared_ptr<Bucket> createLineBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc);
|
||||
std::shared_ptr<Bucket> createPointBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc);
|
||||
|
||||
void cancel();
|
||||
|
||||
const std::string toString() const;
|
||||
@@ -82,21 +77,24 @@ public:
|
||||
DebugFontBuffer debugFontBuffer;
|
||||
VertexArrayObject debugFontArray;
|
||||
|
||||
std::shared_ptr<FillVertexBuffer> fillVertexBuffer;
|
||||
std::shared_ptr<LineVertexBuffer> lineVertexBuffer;
|
||||
std::shared_ptr<PointVertexBuffer> pointVertexBuffer;
|
||||
FillVertexBuffer fillVertexBuffer;
|
||||
LineVertexBuffer lineVertexBuffer;
|
||||
PointVertexBuffer pointVertexBuffer;
|
||||
TextVertexBuffer textVertexBuffer;
|
||||
|
||||
std::shared_ptr<TriangleElementsBuffer> triangleElementsBuffer;
|
||||
std::shared_ptr<LineElementsBuffer> lineElementsBuffer;
|
||||
std::shared_ptr<PointElementsBuffer> pointElementsBuffer;
|
||||
TriangleElementsBuffer triangleElementsBuffer;
|
||||
LineElementsBuffer lineElementsBuffer;
|
||||
PointElementsBuffer pointElementsBuffer;
|
||||
|
||||
// Holds the buckets of this tile.
|
||||
// They contain the location offsets in the buffers stored above
|
||||
std::map<std::string, std::shared_ptr<Bucket>> buckets;
|
||||
std::map<std::string, std::unique_ptr<Bucket>> buckets;
|
||||
|
||||
private:
|
||||
// Source data
|
||||
std::string data;
|
||||
const Style& style;
|
||||
GlyphAtlas& glyphAtlas;
|
||||
platform::Request *req = nullptr;
|
||||
};
|
||||
|
||||
|
||||
35
include/llmr/map/tile_parser.hpp
Normal file
35
include/llmr/map/tile_parser.hpp
Normal file
@@ -0,0 +1,35 @@
|
||||
#ifndef LLMR_MAP_TILE_PARSER
|
||||
#define LLMR_MAP_TILE_PARSER
|
||||
|
||||
#include <llmr/map/tile_data.hpp>
|
||||
#include <llmr/text/placement.hpp>
|
||||
|
||||
namespace llmr {
|
||||
|
||||
class TileParser {
|
||||
public:
|
||||
TileParser(const std::string& data, TileData& tile, const Style& style, GlyphAtlas& glyphAtlas);
|
||||
|
||||
private:
|
||||
bool obsolete() const;
|
||||
void parseGlyphs();
|
||||
void parseStyleLayers(const std::vector<LayerDescription>& layers);
|
||||
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);
|
||||
std::unique_ptr<Bucket> createPointBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc);
|
||||
std::unique_ptr<Bucket> createTextBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc);
|
||||
template <class Bucket> void addBucketFeatures(Bucket& bucket, const VectorTileLayer& layer, const BucketDescription& bucket_desc);
|
||||
|
||||
private:
|
||||
const VectorTile data;
|
||||
TileData& tile;
|
||||
const Style& style;
|
||||
GlyphAtlas& glyphAtlas;
|
||||
Faces faces;
|
||||
Placement placement;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -37,6 +37,7 @@ public:
|
||||
// Getters
|
||||
void matrixFor(mat4& matrix, const Tile::ID& id) const;
|
||||
float getZoom() const;
|
||||
float getNormalizedZoom() const;
|
||||
int32_t getIntegerZoom() const;
|
||||
double getScale() const;
|
||||
double getAngle() const;
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
#define LLMR_MAP_VECTOR_TILE
|
||||
|
||||
#include <llmr/util/pbf.hpp>
|
||||
#include <llmr/util/vec.hpp>
|
||||
#include <llmr/style/value.hpp>
|
||||
#include <llmr/text/glyph.hpp>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <set>
|
||||
@@ -15,24 +17,27 @@ class VectorTileLayer;
|
||||
|
||||
struct pbf;
|
||||
|
||||
struct Coordinate {
|
||||
struct null {};
|
||||
|
||||
Coordinate() : x(0), y(0) {}
|
||||
Coordinate(int16_t x, int16_t y) : x(x), y(y) {}
|
||||
Coordinate(null) : x(std::numeric_limits<int16_t>::min()), y(std::numeric_limits<int16_t>::min()) {}
|
||||
int16_t x;
|
||||
int16_t y;
|
||||
|
||||
inline bool operator==(const Coordinate& other) const {
|
||||
return x == other.x && y == other.y;
|
||||
}
|
||||
|
||||
inline operator bool() const {
|
||||
return x != std::numeric_limits<int16_t>::min() && y != std::numeric_limits<int16_t>::min();
|
||||
}
|
||||
enum class FeatureType {
|
||||
Unknown = 0,
|
||||
Point = 1,
|
||||
LineString = 2,
|
||||
Polygon = 3
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream&, const FeatureType& type);
|
||||
|
||||
class VectorTileFeature {
|
||||
public:
|
||||
VectorTileFeature(pbf feature, const VectorTileLayer& layer);
|
||||
|
||||
uint64_t id = 0;
|
||||
FeatureType type = FeatureType::Unknown;
|
||||
std::map<std::string, Value> properties;
|
||||
pbf geometry;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream&, const VectorTileFeature& feature);
|
||||
|
||||
/*
|
||||
* Allows iterating over the features of a VectorTileLayer using a
|
||||
* BucketDescription as filter. Only features matching the descriptions will
|
||||
@@ -67,6 +72,8 @@ private:
|
||||
std::set<uint32_t> values;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream&, const GlyphPlacement& placement);
|
||||
|
||||
class VectorTileLayer {
|
||||
public:
|
||||
VectorTileLayer(pbf data);
|
||||
@@ -76,8 +83,38 @@ public:
|
||||
uint32_t extent = 4096;
|
||||
std::vector<std::string> keys;
|
||||
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();
|
||||
@@ -85,8 +122,11 @@ public:
|
||||
VectorTile& operator=(VectorTile&& other);
|
||||
|
||||
std::map<std::string, const VectorTileLayer> layers;
|
||||
std::map<std::string, const VectorTileFace> faces;
|
||||
};
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -37,6 +37,9 @@ double time();
|
||||
// Performs routine cleanup operation and is called on every loop iteration.
|
||||
void cleanup();
|
||||
|
||||
// Shows an RGBA image with the specified dimensions in a named window.
|
||||
void show_debug_image(std::string name, const char *data, size_t width, size_t height);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ class BucketDescription;
|
||||
class OutlineShader;
|
||||
class PlainShader;
|
||||
class PatternShader;
|
||||
struct Coordinate;
|
||||
struct pbf;
|
||||
|
||||
class FillBucket : public Bucket {
|
||||
@@ -39,9 +38,9 @@ class FillBucket : public Bucket {
|
||||
typedef ElementGroup triangle_group_type;
|
||||
typedef ElementGroup line_group_type;
|
||||
public:
|
||||
FillBucket(const std::shared_ptr<FillVertexBuffer>& vertexBuffer,
|
||||
const std::shared_ptr<TriangleElementsBuffer>& triangleElementsBuffer,
|
||||
const std::shared_ptr<LineElementsBuffer>& lineElementsBuffer,
|
||||
FillBucket(FillVertexBuffer& vertexBuffer,
|
||||
TriangleElementsBuffer& triangleElementsBuffer,
|
||||
LineElementsBuffer& lineElementsBuffer,
|
||||
const BucketDescription& bucket_desc);
|
||||
~FillBucket();
|
||||
|
||||
@@ -63,9 +62,9 @@ private:
|
||||
TESStesselator *tesselator;
|
||||
ClipperLib::Clipper clipper;
|
||||
|
||||
std::shared_ptr<FillVertexBuffer> vertexBuffer;
|
||||
std::shared_ptr<TriangleElementsBuffer> triangleElementsBuffer;
|
||||
std::shared_ptr<LineElementsBuffer> lineElementsBuffer;
|
||||
FillVertexBuffer& vertexBuffer;
|
||||
TriangleElementsBuffer& triangleElementsBuffer;
|
||||
LineElementsBuffer& lineElementsBuffer;
|
||||
|
||||
// hold information on where the vertices are located in the FillBuffer
|
||||
const size_t vertex_start;
|
||||
|
||||
31
include/llmr/renderer/frame_history.hpp
Normal file
31
include/llmr/renderer/frame_history.hpp
Normal file
@@ -0,0 +1,31 @@
|
||||
#ifndef LLMR_RENDERER_FRAME_HISTORY
|
||||
#define LLMR_RENDERER_FRAME_HISTORY
|
||||
|
||||
#include <deque>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
|
||||
#include <llmr/platform/platform.hpp>
|
||||
|
||||
namespace llmr {
|
||||
|
||||
struct FrameSnapshot {
|
||||
explicit inline FrameSnapshot(float time, float z) : time(time), z(z) {}
|
||||
float time;
|
||||
float z;
|
||||
};
|
||||
|
||||
class FrameHistory {
|
||||
public:
|
||||
// Record frame history that will be used to calculate fading params
|
||||
void record(float zoom);
|
||||
|
||||
bool needsAnimation(float duration) const;
|
||||
|
||||
public:
|
||||
std::deque<FrameSnapshot> history;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <llmr/style/bucket_description.hpp>
|
||||
#include <llmr/geometry/vao.hpp>
|
||||
#include <llmr/geometry/elements_buffer.hpp>
|
||||
#include <llmr/geometry/line_buffer.hpp>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
@@ -15,16 +16,15 @@ class LineVertexBuffer;
|
||||
class TriangleElementsBuffer;
|
||||
class LineShader;
|
||||
class LinejoinShader;
|
||||
struct Coordinate;
|
||||
struct pbf;
|
||||
|
||||
class LineBucket : public Bucket {
|
||||
typedef ElementGroup triangle_group_type;
|
||||
typedef ElementGroup point_group_type;
|
||||
public:
|
||||
LineBucket(const std::shared_ptr<LineVertexBuffer>& vertexBuffer,
|
||||
const std::shared_ptr<TriangleElementsBuffer>& triangleElementsBuffer,
|
||||
const std::shared_ptr<PointElementsBuffer>& pointElementsBuffer,
|
||||
LineBucket(LineVertexBuffer& vertexBuffer,
|
||||
TriangleElementsBuffer& triangleElementsBuffer,
|
||||
PointElementsBuffer& pointElementsBuffer,
|
||||
const BucketDescription& bucket_desc);
|
||||
|
||||
virtual void render(Painter& painter, const std::string& layer_name, const Tile::ID& id);
|
||||
@@ -43,9 +43,9 @@ public:
|
||||
|
||||
private:
|
||||
|
||||
std::shared_ptr<LineVertexBuffer> vertexBuffer;
|
||||
std::shared_ptr<TriangleElementsBuffer> triangleElementsBuffer;
|
||||
std::shared_ptr<PointElementsBuffer> pointElementsBuffer;
|
||||
LineVertexBuffer& vertexBuffer;
|
||||
TriangleElementsBuffer& triangleElementsBuffer;
|
||||
PointElementsBuffer& pointElementsBuffer;
|
||||
|
||||
const size_t vertex_start;
|
||||
const size_t triangle_elements_start;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <llmr/geometry/vertex_buffer.hpp>
|
||||
#include <llmr/util/mat4.hpp>
|
||||
#include <llmr/util/noncopyable.hpp>
|
||||
#include <llmr/renderer/frame_history.hpp>
|
||||
|
||||
#include <llmr/shader/plain_shader.hpp>
|
||||
#include <llmr/shader/outline_shader.hpp>
|
||||
@@ -14,6 +15,7 @@
|
||||
#include <llmr/shader/linejoin_shader.hpp>
|
||||
#include <llmr/shader/point_shader.hpp>
|
||||
#include <llmr/shader/raster_shader.hpp>
|
||||
#include <llmr/shader/text_shader.hpp>
|
||||
|
||||
namespace llmr {
|
||||
|
||||
@@ -25,10 +27,11 @@ class Tile;
|
||||
class FillBucket;
|
||||
class LineBucket;
|
||||
class PointBucket;
|
||||
class TextBucket;
|
||||
|
||||
class Painter : private util::noncopyable {
|
||||
public:
|
||||
Painter(Transform& transform, Settings& settings, Style& style);
|
||||
Painter(Transform& transform, Settings& settings, Style& style, GlyphAtlas& glyphAtlas);
|
||||
|
||||
void setup();
|
||||
void clear();
|
||||
@@ -38,12 +41,15 @@ public:
|
||||
void renderFill(FillBucket& bucket, const std::string& layer_name, const Tile::ID& id);
|
||||
void renderLine(LineBucket& bucket, const std::string& layer_name, const Tile::ID& id);
|
||||
void renderPoint(PointBucket& bucket, const std::string& layer_name, const Tile::ID& id);
|
||||
void renderText(TextBucket& bucket, const std::string& layer_name, const Tile::ID& id);
|
||||
|
||||
void resize(int width, int height);
|
||||
|
||||
void prepareClippingMask();
|
||||
void drawClippingMask(const mat4& matrix, uint8_t clip_id, bool opaque = true);
|
||||
void finishClippingMask();
|
||||
|
||||
bool needsAnimation() const;
|
||||
private:
|
||||
void setupShaders();
|
||||
void renderRaster(const std::shared_ptr<TileData>& tile);
|
||||
@@ -65,8 +71,9 @@ private:
|
||||
Transform& transform;
|
||||
Settings& settings;
|
||||
Style& style;
|
||||
GlyphAtlas& glyphAtlas;
|
||||
|
||||
|
||||
FrameHistory frameHistory;
|
||||
|
||||
uint32_t gl_program = 0;
|
||||
float gl_lineWidth = 0;
|
||||
@@ -82,6 +89,7 @@ private:
|
||||
std::unique_ptr<PatternShader> patternShader;
|
||||
std::unique_ptr<PointShader> pointShader;
|
||||
std::unique_ptr<RasterShader> rasterShader;
|
||||
std::unique_ptr<TextShader> textShader;
|
||||
|
||||
// Set up the stencil quad we're using to generate the stencil mask.
|
||||
VertexBuffer tileStencilBuffer = {
|
||||
|
||||
@@ -19,12 +19,11 @@ class Style;
|
||||
class PointVertexBuffer;
|
||||
class BucketDescription;
|
||||
class PointShader;
|
||||
struct Coordinate;
|
||||
struct pbf;
|
||||
|
||||
class PointBucket : public Bucket {
|
||||
public:
|
||||
PointBucket(const std::shared_ptr<PointVertexBuffer>& vertexBuffer,
|
||||
PointBucket(PointVertexBuffer& vertexBuffer,
|
||||
const BucketDescription& bucket_desc);
|
||||
|
||||
virtual void render(Painter& painter, const std::string& layer_name, const Tile::ID& id);
|
||||
@@ -39,7 +38,8 @@ public:
|
||||
const BucketGeometryDescription geometry;
|
||||
|
||||
private:
|
||||
std::shared_ptr<PointVertexBuffer> vertexBuffer;
|
||||
|
||||
PointVertexBuffer& vertexBuffer;
|
||||
VertexArrayObject array;
|
||||
|
||||
const size_t vertex_start;
|
||||
|
||||
61
include/llmr/renderer/text_bucket.hpp
Normal file
61
include/llmr/renderer/text_bucket.hpp
Normal file
@@ -0,0 +1,61 @@
|
||||
#ifndef LLMR_RENDERER_TEXTBUCKET
|
||||
#define LLMR_RENDERER_TEXTBUCKET
|
||||
|
||||
#include "bucket.hpp"
|
||||
#include <llmr/style/bucket_description.hpp>
|
||||
#include <llmr/geometry/vao.hpp>
|
||||
#include <llmr/geometry/elements_buffer.hpp>
|
||||
#include <llmr/map/vector_tile.hpp>
|
||||
#include <llmr/text/types.hpp>
|
||||
#include <memory>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
namespace llmr {
|
||||
|
||||
class Style;
|
||||
class TextVertexBuffer;
|
||||
class TriangleElementsBuffer;
|
||||
class TextShader;
|
||||
class Placement;
|
||||
struct pbf;
|
||||
|
||||
class TextBucket : public Bucket {
|
||||
typedef ElementGroup triangle_group_type;
|
||||
|
||||
public:
|
||||
TextBucket(
|
||||
TextVertexBuffer &vertexBuffer,
|
||||
TriangleElementsBuffer &triangleElementsBuffer,
|
||||
const BucketDescription &bucket_desc, Placement &placement);
|
||||
|
||||
virtual void render(Painter &painter, const std::string &layer_name,
|
||||
const Tile::ID &id);
|
||||
|
||||
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);
|
||||
|
||||
bool empty() const;
|
||||
|
||||
void drawGlyphs(TextShader &shader);
|
||||
|
||||
public:
|
||||
const BucketGeometryDescription &geom_desc;
|
||||
|
||||
private:
|
||||
TextVertexBuffer& vertexBuffer;
|
||||
TriangleElementsBuffer& triangleElementsBuffer;
|
||||
Placement &placement;
|
||||
|
||||
const size_t vertex_start;
|
||||
const size_t triangle_elements_start;
|
||||
|
||||
std::vector<triangle_group_type> triangleGroups;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,28 +0,0 @@
|
||||
// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED.
|
||||
|
||||
#ifndef LLMR_SHADER_SHADERS
|
||||
#define LLMR_SHADER_SHADERS
|
||||
|
||||
namespace llmr {
|
||||
|
||||
struct shader_source {
|
||||
const char *vertex;
|
||||
const char *fragment;
|
||||
};
|
||||
|
||||
enum {
|
||||
LINE_SHADER,
|
||||
LINEJOIN_SHADER,
|
||||
OUTLINE_SHADER,
|
||||
PATTERN_SHADER,
|
||||
PLAIN_SHADER,
|
||||
POINT_SHADER,
|
||||
RASTER_SHADER,
|
||||
SHADER_COUNT
|
||||
};
|
||||
|
||||
extern const shader_source shaders[SHADER_COUNT];
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
72
include/llmr/shader/text_shader.hpp
Normal file
72
include/llmr/shader/text_shader.hpp
Normal file
@@ -0,0 +1,72 @@
|
||||
#ifndef LLMR_SHADER_TEXT_SHADER
|
||||
#define LLMR_SHADER_TEXT_SHADER
|
||||
|
||||
#include "shader.hpp"
|
||||
|
||||
namespace llmr {
|
||||
|
||||
class TextShader : public Shader {
|
||||
public:
|
||||
TextShader();
|
||||
|
||||
void bind(char *offset);
|
||||
|
||||
void setColor(float r, float g, float b, float a);
|
||||
void setColor(const std::array<float, 4> &color);
|
||||
void setBuffer(float buffer);
|
||||
void setGamma(float gamma);
|
||||
void setExtrudeMatrix(const std::array<float, 16> &exmatrix);
|
||||
void setAngle(float angle);
|
||||
void setZoom(float zoom);
|
||||
void setFlip(float flip);
|
||||
void setFadeDist(float fadedist);
|
||||
void setMinFadeZoom(float minfadezoom);
|
||||
void setMaxFadeZoom(float maxfadezoom);
|
||||
void setFadeZoom(float fadezoom);
|
||||
void setTextureSize(const std::array<float, 2> &texsize);
|
||||
|
||||
private:
|
||||
int32_t a_pos = -1;
|
||||
int32_t a_offset = -1;
|
||||
int32_t a_data1 = -1;
|
||||
int32_t a_data2 = -1;
|
||||
|
||||
std::array<float, 4> color = {{}};
|
||||
int32_t u_color = -1;
|
||||
|
||||
float buffer = 0.0f;
|
||||
int32_t u_buffer = -1;
|
||||
|
||||
float gamma = 0.0f;
|
||||
int32_t u_gamma = -1;
|
||||
|
||||
std::array<float, 16> exmatrix = {{}};
|
||||
int32_t u_exmatrix = -1;
|
||||
|
||||
float angle = 0.0f;
|
||||
int32_t u_angle = -1;
|
||||
|
||||
float zoom = 0.0f;
|
||||
int32_t u_zoom = -1;
|
||||
|
||||
float flip = 0.0f;
|
||||
int32_t u_flip = -1;
|
||||
|
||||
float fadedist = 0.0f;
|
||||
int32_t u_fadedist = -1;
|
||||
|
||||
float minfadezoom = 0.0f;
|
||||
int32_t u_minfadezoom = -1;
|
||||
|
||||
float maxfadezoom = 0.0f;
|
||||
int32_t u_maxfadezoom = -1;
|
||||
|
||||
float fadezoom = 0.0f;
|
||||
int32_t u_fadezoom = -1;
|
||||
|
||||
std::array<float, 2> texsize = {{}};
|
||||
int32_t u_texsize = -1;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cmath>
|
||||
|
||||
#include "value.hpp"
|
||||
|
||||
@@ -12,7 +13,8 @@ enum class BucketType {
|
||||
None = 0,
|
||||
Fill = 1,
|
||||
Line = 2,
|
||||
Point = 3
|
||||
Point = 3,
|
||||
Text = 4
|
||||
};
|
||||
|
||||
enum class CapType {
|
||||
@@ -29,11 +31,17 @@ enum class JoinType {
|
||||
Round = 3
|
||||
};
|
||||
|
||||
enum class TextPathType {
|
||||
Horizontal = 0,
|
||||
Curve = 1
|
||||
};
|
||||
|
||||
|
||||
inline BucketType bucketType(const std::string& type) {
|
||||
if (type == "fill") return BucketType::Fill;
|
||||
else if (type == "line") return BucketType::Line;
|
||||
else if (type == "point") return BucketType::Point;
|
||||
else if (type == "text") return BucketType::Text;
|
||||
else return BucketType::None;
|
||||
}
|
||||
|
||||
@@ -52,18 +60,32 @@ inline JoinType joinType(const std::string& join) {
|
||||
else return JoinType::None;
|
||||
}
|
||||
|
||||
inline TextPathType textPathType(const std::string& path) {
|
||||
if (path == "horizontal") return TextPathType::Horizontal;
|
||||
else if (path == "curve") return TextPathType::Curve;
|
||||
else return TextPathType::Horizontal;
|
||||
};
|
||||
|
||||
class BucketGeometryDescription {
|
||||
public:
|
||||
CapType cap = CapType::None;
|
||||
JoinType join = JoinType::None;
|
||||
std::string font;
|
||||
std::string text_field;
|
||||
float font_size = 0.0f;
|
||||
float miter_limit = 2.0f;
|
||||
float round_limit = 1.0f;
|
||||
TextPathType path = TextPathType::Horizontal;
|
||||
float padding = 2.0f;
|
||||
float textMinDistance = 250.0f;
|
||||
float rotate = 0.0f; // what is this?
|
||||
float maxAngleDelta = M_PI;
|
||||
bool alwaysVisible = false;
|
||||
};
|
||||
|
||||
class BucketDescription {
|
||||
public:
|
||||
BucketType feature_type = BucketType::None;
|
||||
BucketType type = BucketType::None;
|
||||
|
||||
// Specify what data to pull into this bucket
|
||||
|
||||
@@ -14,6 +14,7 @@ public:
|
||||
std::map<std::string, FillClass> fill;
|
||||
std::map<std::string, LineClass> line;
|
||||
std::map<std::string, PointClass> point;
|
||||
std::map<std::string, TextClass> text;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -45,6 +45,9 @@ bool stops(float z, const std::vector<bool>& values);
|
||||
float linear(float z, const std::vector<float>& values);
|
||||
bool linear(float z, const std::vector<bool>& values);
|
||||
|
||||
float exponential(float z, const std::vector<float>& values);
|
||||
bool exponential(float z, const std::vector<bool>& values);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +64,7 @@ struct FunctionProperty {
|
||||
};
|
||||
|
||||
struct PointClass {
|
||||
FunctionProperty<bool> hidden;
|
||||
FunctionProperty<bool> enabled = true;
|
||||
FunctionProperty<float> size;
|
||||
Color color = {{ 0, 0, 0, 1 }};
|
||||
FunctionProperty<float> opacity = 1;
|
||||
@@ -69,7 +72,7 @@ struct PointClass {
|
||||
};
|
||||
|
||||
struct PointProperties {
|
||||
bool hidden = false;
|
||||
bool enabled = true;
|
||||
float size = 0;
|
||||
Color color = {{ 0, 0, 0, 1 }};
|
||||
float opacity = 1.0;
|
||||
@@ -77,7 +80,7 @@ struct PointProperties {
|
||||
};
|
||||
|
||||
struct LineClass {
|
||||
FunctionProperty<bool> hidden;
|
||||
FunctionProperty<bool> enabled = true;
|
||||
FunctionProperty<float> width;
|
||||
FunctionProperty<float> offset;
|
||||
Color color = {{ 0, 0, 0, 1 }};
|
||||
@@ -85,7 +88,7 @@ struct LineClass {
|
||||
};
|
||||
|
||||
struct LineProperties {
|
||||
bool hidden = false;
|
||||
bool enabled = true;
|
||||
float width = 0;
|
||||
float offset = 0;
|
||||
Color color = {{ 0, 0, 0, 1 }};
|
||||
@@ -93,7 +96,7 @@ struct LineProperties {
|
||||
};
|
||||
|
||||
struct FillClass {
|
||||
FunctionProperty<bool> hidden;
|
||||
FunctionProperty<bool> enabled = true;
|
||||
Winding winding = Winding::NonZero;
|
||||
FunctionProperty<bool> antialias = true;
|
||||
Color fill_color = {{ 0, 0, 0, 1 }};
|
||||
@@ -103,7 +106,7 @@ struct FillClass {
|
||||
};
|
||||
|
||||
struct FillProperties {
|
||||
bool hidden = false;
|
||||
bool enabled = true;
|
||||
Winding winding = Winding::NonZero;
|
||||
bool antialias = true;
|
||||
Color fill_color = {{ 0, 0, 0, 1 }};
|
||||
@@ -112,6 +115,26 @@ struct FillProperties {
|
||||
std::string image;
|
||||
};
|
||||
|
||||
struct TextClass {
|
||||
FunctionProperty<bool> enabled = true;
|
||||
Color color = {{ 0, 0, 0, 1 }};
|
||||
Color halo = {{ 1, 1, 1, 0.75 }};
|
||||
FunctionProperty<float> haloRadius = 0.25f;
|
||||
FunctionProperty<float> size = 12.0f;
|
||||
FunctionProperty<float> rotate = 0.0f;
|
||||
FunctionProperty<bool> alwaysVisible = false;
|
||||
};
|
||||
|
||||
struct TextProperties {
|
||||
bool enabled = true;
|
||||
Color color = {{ 0, 0, 0, 1 }};
|
||||
Color halo = {{ 1, 1, 1, 0.75 }};
|
||||
float haloRadius = 0.25f;
|
||||
float size = 12.0f;
|
||||
float rotate = 0.0f;
|
||||
bool alwaysVisible = false;
|
||||
};
|
||||
|
||||
struct BackgroundClass {
|
||||
Color color = {{ 1, 1, 1, 1 }};
|
||||
};
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED.
|
||||
|
||||
#ifndef LLMR_STYLE_RESOURCES
|
||||
#define LLMR_STYLE_RESOURCES
|
||||
|
||||
namespace llmr {
|
||||
namespace resources {
|
||||
|
||||
extern const unsigned char style[];
|
||||
extern const unsigned long style_size;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -56,6 +56,7 @@ public:
|
||||
std::map<std::string, FillProperties> fills;
|
||||
std::map<std::string, LineProperties> lines;
|
||||
std::map<std::string, PointProperties> points;
|
||||
std::map<std::string, TextProperties> texts;
|
||||
} computed;
|
||||
};
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ private:
|
||||
FillClass parseFillClass(JSVal value);
|
||||
LineClass parseLineClass(JSVal value);
|
||||
PointClass parsePointClass(JSVal value);
|
||||
TextClass parseTextClass(JSVal value);
|
||||
BackgroundClass parseBackgroundClass(JSVal value);
|
||||
|
||||
bool parseBoolean(JSVal value);
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
|
||||
namespace llmr {
|
||||
|
||||
typedef ::util::variant<bool, int64_t, uint64_t, double, std::string> Value;
|
||||
typedef util::variant<bool, int64_t, uint64_t, double, std::string> Value;
|
||||
|
||||
std::string toString(const Value& value);
|
||||
|
||||
Value parseValue(pbf data);
|
||||
|
||||
|
||||
31
include/llmr/text/collision.hpp
Normal file
31
include/llmr/text/collision.hpp
Normal file
@@ -0,0 +1,31 @@
|
||||
#ifndef LLMR_TEXT_COLLISION
|
||||
#define LLMR_TEXT_COLLISION
|
||||
|
||||
#include <llmr/text/types.hpp>
|
||||
|
||||
namespace llmr {
|
||||
|
||||
class Collision {
|
||||
|
||||
public:
|
||||
Collision();
|
||||
~Collision();
|
||||
|
||||
PlacementProperty place(const PlacedGlyphs &boxes,
|
||||
const CollisionAnchor &anchor,
|
||||
float minPlacementScale, float maxPlacementScale,
|
||||
float padding, bool horizontal, bool alwaysVisible);
|
||||
float getPlacementScale(const GlyphBoxes &glyphs, float minPlacementScale,
|
||||
float maxPlacementScale, float pad);
|
||||
PlacementRange getPlacementRange(const GlyphBoxes &glyphs,
|
||||
float placementScale);
|
||||
void insert(const GlyphBoxes &glyphs, const CollisionAnchor &anchor,
|
||||
float placementScale, const PlacementRange &placementRange,
|
||||
bool horizontal, float padding);
|
||||
|
||||
private:
|
||||
void *tree;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
58
include/llmr/text/glyph.hpp
Normal file
58
include/llmr/text/glyph.hpp
Normal file
@@ -0,0 +1,58 @@
|
||||
#ifndef LLMR_TEXT_GLYPH
|
||||
#define LLMR_TEXT_GLYPH
|
||||
|
||||
#include <llmr/util/rect.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
namespace llmr {
|
||||
|
||||
struct GlyphMetrics {
|
||||
operator bool() const {
|
||||
return width == 0 && height == 0 && advance == 0;
|
||||
}
|
||||
|
||||
// Glyph metrics.
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
int32_t left = 0;
|
||||
int32_t top = 0;
|
||||
uint32_t advance = 0;
|
||||
|
||||
};
|
||||
|
||||
struct Glyph {
|
||||
inline explicit Glyph() : rect(0, 0, 0, 0), metrics() {}
|
||||
inline explicit Glyph(const Rect<uint16_t> &rect,
|
||||
const GlyphMetrics &metrics)
|
||||
: rect(rect), metrics(metrics) {}
|
||||
|
||||
operator bool() const {
|
||||
return !metrics && !rect;
|
||||
}
|
||||
|
||||
const Rect<uint16_t> rect;
|
||||
const GlyphMetrics metrics;
|
||||
};
|
||||
|
||||
typedef std::map<uint32_t, Glyph> GlyphPositions;
|
||||
typedef std::map<std::string, GlyphPositions> Faces;
|
||||
typedef std::vector<const GlyphPositions *> IndexedFaces;
|
||||
|
||||
class GlyphPlacement {
|
||||
public:
|
||||
inline explicit GlyphPlacement(uint32_t face, uint32_t glyph, uint32_t x, uint32_t y)
|
||||
: face(face), glyph(glyph), x(x), y(y) {}
|
||||
|
||||
uint32_t face = 0;
|
||||
uint32_t glyph = 0;
|
||||
uint32_t x = 0;
|
||||
uint32_t y = 0;
|
||||
};
|
||||
|
||||
typedef std::vector<GlyphPlacement> Shaping;
|
||||
}
|
||||
|
||||
#endif
|
||||
39
include/llmr/text/placement.hpp
Normal file
39
include/llmr/text/placement.hpp
Normal file
@@ -0,0 +1,39 @@
|
||||
#ifndef LLMR_TEXT_PLACEMENT
|
||||
#define LLMR_TEXT_PLACEMENT
|
||||
|
||||
#include <llmr/text/collision.hpp>
|
||||
#include <llmr/geometry/geometry.hpp>
|
||||
#include <llmr/util/vec.hpp>
|
||||
#include <llmr/text/glyph.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace llmr {
|
||||
|
||||
class TextBucket;
|
||||
class BucketGeometryDescription;
|
||||
|
||||
class Placement {
|
||||
public:
|
||||
Placement(int8_t zoom);
|
||||
|
||||
void addFeature(TextBucket &bucket, const std::vector<Coordinate> &line,
|
||||
const BucketGeometryDescription &info,
|
||||
const IndexedFaces &faces,
|
||||
const Shaping &shaping);
|
||||
float measureText(const IndexedFaces &faces,
|
||||
const Shaping &shaping);
|
||||
|
||||
private:
|
||||
const int8_t zoom;
|
||||
Collision collision;
|
||||
const float zOffset;
|
||||
const float maxPlacementScale;
|
||||
|
||||
public:
|
||||
static const int tileExtent;
|
||||
static const int glyphSize;
|
||||
static const float minScale;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
54
include/llmr/text/rotation_range.hpp
Normal file
54
include/llmr/text/rotation_range.hpp
Normal file
@@ -0,0 +1,54 @@
|
||||
#ifndef LLMR_TEXT_ROTATION_RANGE
|
||||
#define LLMR_TEXT_ROTATION_RANGE
|
||||
|
||||
#include <llmr/util/math.hpp>
|
||||
#include <llmr/text/types.hpp>
|
||||
|
||||
#include <vector>
|
||||
#include <cassert>
|
||||
|
||||
namespace llmr {
|
||||
|
||||
/*
|
||||
* Combine an array of collision ranges to form a continuous
|
||||
* range that includes 0. Collisions within the ignoreRange are ignored
|
||||
*/
|
||||
CollisionRange mergeCollisions(const CollisionList &collisions,
|
||||
PlacementRange ignoreRange);
|
||||
|
||||
/*
|
||||
* Calculate collision ranges for two rotating boxes.e
|
||||
*/
|
||||
CollisionList rotatingRotatingCollisions(const CollisionRect &a,
|
||||
const CollisionRect &b,
|
||||
const CollisionAnchor &anchorToAnchor);
|
||||
|
||||
/*
|
||||
* Return the intersection points of a circle and a line segment;
|
||||
*/
|
||||
void circleEdgeCollisions(std::back_insert_iterator<CollisionAngles> angles,
|
||||
const CollisionPoint &corner, float radius,
|
||||
const CollisionPoint &p1, const CollisionPoint &p2);
|
||||
|
||||
/*
|
||||
* Calculate the ranges for which the corner,
|
||||
* rotatated around the anchor, is within the box;
|
||||
*/
|
||||
void cornerBoxCollisions(std::back_insert_iterator<CollisionList> collisions,
|
||||
const CollisionPoint &corner,
|
||||
const CollisionCorners &boxCorners, bool flip = false);
|
||||
|
||||
/*
|
||||
* Calculate collision ranges for a rotating box and a fixed box;
|
||||
*/
|
||||
CollisionList rotatingFixedCollisions(const CollisionRect &rotating,
|
||||
const CollisionRect &fixed);
|
||||
|
||||
/*
|
||||
* Calculate the range a box conflicts with a second box
|
||||
*/
|
||||
CollisionRange rotationRange(const GlyphBox &inserting,
|
||||
const PlacementBox &blocker, float scale);
|
||||
}
|
||||
|
||||
#endif
|
||||
113
include/llmr/text/types.hpp
Normal file
113
include/llmr/text/types.hpp
Normal file
@@ -0,0 +1,113 @@
|
||||
#ifndef LLMR_TEXT_TYPES
|
||||
#define LLMR_TEXT_TYPES
|
||||
|
||||
#include <llmr/util/vec.hpp>
|
||||
#include <llmr/util/rect.hpp>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
namespace llmr {
|
||||
|
||||
typedef vec2<float> CollisionPoint;
|
||||
typedef vec2<float> CollisionAnchor;
|
||||
|
||||
typedef std::array<float, 2> PlacementRange;
|
||||
typedef float CollisionAngle;
|
||||
typedef std::vector<CollisionAngle> CollisionAngles;
|
||||
typedef std::array<CollisionAngle, 2> CollisionRange;
|
||||
typedef std::vector<CollisionRange> CollisionList;
|
||||
typedef std::array<CollisionPoint, 4> CollisionCorners;
|
||||
|
||||
struct CollisionRect {
|
||||
CollisionPoint tl;
|
||||
CollisionPoint br;
|
||||
inline explicit CollisionRect() {}
|
||||
inline explicit CollisionRect(CollisionPoint::Type ax,
|
||||
CollisionPoint::Type ay,
|
||||
CollisionPoint::Type bx,
|
||||
CollisionPoint::Type by)
|
||||
: tl(ax, ay), br(bx, by) {}
|
||||
inline explicit CollisionRect(const CollisionPoint &tl,
|
||||
const CollisionPoint &br)
|
||||
: tl(tl), br(br) {}
|
||||
};
|
||||
|
||||
// These are the glyph boxes that we want to have placed.
|
||||
struct GlyphBox {
|
||||
explicit GlyphBox() {}
|
||||
explicit GlyphBox(const CollisionRect &bbox, const CollisionRect &box,
|
||||
float minScale)
|
||||
: bbox(bbox), box(box), minScale(minScale) {}
|
||||
explicit GlyphBox(const CollisionRect &box, float minScale, float maxScale,
|
||||
const CollisionAnchor &anchor, bool rotate)
|
||||
: anchor(anchor),
|
||||
box(box),
|
||||
rotate(rotate),
|
||||
minScale(minScale),
|
||||
maxScale(maxScale) {}
|
||||
|
||||
CollisionAnchor anchor;
|
||||
CollisionRect bbox;
|
||||
CollisionRect box;
|
||||
bool rotate = false;
|
||||
float minScale = 0.0f;
|
||||
float maxScale = std::numeric_limits<float>::infinity();
|
||||
};
|
||||
|
||||
typedef std::vector<GlyphBox> GlyphBoxes;
|
||||
|
||||
|
||||
// TODO: Transform the vec2<float>s to vec2<int16_t> to save bytes
|
||||
struct PlacedGlyph {
|
||||
explicit PlacedGlyph(const vec2<float> &tl, const vec2<float> &tr,
|
||||
const vec2<float> &bl, const vec2<float> &br,
|
||||
const Rect<uint16_t> &tex, uint32_t width,
|
||||
uint32_t height, float angle, const GlyphBox &glyphBox)
|
||||
: tl(tl),
|
||||
tr(tr),
|
||||
bl(bl),
|
||||
br(br),
|
||||
tex(tex),
|
||||
width(width),
|
||||
height(height),
|
||||
angle(angle),
|
||||
glyphBox(glyphBox) {}
|
||||
|
||||
vec2<float> tl, tr, bl, br;
|
||||
Rect<uint16_t> tex;
|
||||
uint32_t width, height;
|
||||
float angle;
|
||||
GlyphBox glyphBox;
|
||||
};
|
||||
|
||||
typedef std::vector<PlacedGlyph> PlacedGlyphs;
|
||||
|
||||
// These are the placed boxes contained in the rtree.
|
||||
struct PlacementBox {
|
||||
CollisionAnchor anchor;
|
||||
CollisionRect bbox;
|
||||
CollisionRect box;
|
||||
bool rotate = false;
|
||||
PlacementRange placementRange = {{0.0f, 0.0f}};
|
||||
float placementScale = 0.0f;
|
||||
float maxScale = std::numeric_limits<float>::infinity();
|
||||
float padding = 0.0f;
|
||||
};
|
||||
|
||||
struct PlacementProperty {
|
||||
explicit PlacementProperty() {}
|
||||
explicit PlacementProperty(float zoom, const PlacementRange &rotationRange)
|
||||
: zoom(zoom), rotationRange(rotationRange) {}
|
||||
|
||||
inline operator bool() const {
|
||||
return !isnan(zoom) && zoom != std::numeric_limits<float>::infinity() &&
|
||||
rotationRange[0] != rotationRange[1];
|
||||
}
|
||||
|
||||
float zoom = std::numeric_limits<float>::infinity();
|
||||
PlacementRange rotationRange = {{0.0f, 0.0f}};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -2,16 +2,75 @@
|
||||
#define LLMR_UTIL_MATH
|
||||
|
||||
#include <cmath>
|
||||
#include <array>
|
||||
|
||||
#include "vec.hpp"
|
||||
|
||||
namespace llmr {
|
||||
namespace util {
|
||||
|
||||
inline double angle_between(double ax, double ay, double bx, double by) {
|
||||
|
||||
template <typename T>
|
||||
inline T max(T a, T b) {
|
||||
return b > a ? b : a;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline T max(T a, T b, T c) {
|
||||
return max(max(a, b), c);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline T max(T a, T b, T c, T d) {
|
||||
return max(max(a, b), max(c, d));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline T min(T a, T b) {
|
||||
return b < a ? b : a;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline T min(T a, T b, T c) {
|
||||
return min(min(a, b), c);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline T min(T a, T b, T c, T d) {
|
||||
return min(min(a, b), min(c, d));
|
||||
}
|
||||
|
||||
// Find the angle of the two vectors, solving the formula for the cross product
|
||||
// a x b = |a||b|sin(θ) for θ.
|
||||
template <typename T = double, typename S>
|
||||
inline T angle_between(S ax, S ay, S bx, S by) {
|
||||
return atan2((ax * by - ay * bx), ax * bx + ay * by);
|
||||
}
|
||||
|
||||
template <typename T = double, typename S>
|
||||
inline T angle_between(const vec2<S>& a, const vec2<S>& b) {
|
||||
return angle_between(a.x, a.y, b.x, b.y);
|
||||
}
|
||||
|
||||
template <typename T = double, typename S>
|
||||
inline T angle_to(const vec2<S>& a, const vec2<S>& b) {
|
||||
return atan2(a.y - b.y, a.x - b.x);
|
||||
}
|
||||
|
||||
template <typename T, typename S1, typename S2>
|
||||
inline T interp(S1 a, S2 b, T t) {
|
||||
return (a * ((T)1 - t)) + (b * t);
|
||||
}
|
||||
|
||||
// Reflect an angle around 0 degrees
|
||||
template <typename T>
|
||||
inline std::array<T, 2> flip(const std::array<T, 2>& c) {
|
||||
return {{
|
||||
static_cast<T>(2 * M_PI - c[0]),
|
||||
static_cast<T>(2 * M_PI - c[1])
|
||||
}};
|
||||
}
|
||||
|
||||
template <typename T, typename S1, typename S2>
|
||||
inline vec2<T> normal(const S1& a, const S2& b) {
|
||||
T dx = b.x - a.x;
|
||||
@@ -28,6 +87,12 @@ inline T dist(const S1& a, const S2& b) {
|
||||
return c;
|
||||
}
|
||||
|
||||
// Take the magnitude of vector a.
|
||||
template <typename T = double, typename S>
|
||||
inline T mag(const S& a) {
|
||||
return sqrt(a.x * a.x + a.y * a.y);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
16
include/llmr/util/rect.hpp
Normal file
16
include/llmr/util/rect.hpp
Normal file
@@ -0,0 +1,16 @@
|
||||
#ifndef LLMR_UTIL_RECT
|
||||
#define LLMR_UTIL_RECT
|
||||
|
||||
namespace llmr {
|
||||
|
||||
template <typename T>
|
||||
struct Rect {
|
||||
explicit Rect(T x, T y, T w, T h) : x(x), y(y), w(w), h(h) {}
|
||||
T x = 0, y = 0;
|
||||
T w = 0, h = 0;
|
||||
|
||||
operator bool() const { return w == 0 || h == 0; }
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -16,6 +16,8 @@
|
||||
#define VARIANT_INLINE __attribute__((noinline))
|
||||
#endif
|
||||
|
||||
namespace llmr {
|
||||
|
||||
namespace util {
|
||||
|
||||
namespace detail {
|
||||
@@ -273,12 +275,12 @@ public:
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
VARIANT_INLINE void is() const
|
||||
VARIANT_INLINE bool is() const
|
||||
{
|
||||
return (type_id == detail::type_traits<T, Types...>::id);
|
||||
}
|
||||
|
||||
VARIANT_INLINE void valid() const
|
||||
VARIANT_INLINE bool valid() const
|
||||
{
|
||||
return (type_id != detail::invalid_value);
|
||||
}
|
||||
@@ -377,4 +379,6 @@ operator<< (std::basic_ostream<charT,traits>& out, Variant const& rhs)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // UTIL_VARIANT_HPP
|
||||
|
||||
@@ -4,28 +4,75 @@
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
|
||||
namespace llmr {
|
||||
|
||||
template <typename T = double>
|
||||
struct vec2 {
|
||||
struct null {};
|
||||
typedef T Type;
|
||||
|
||||
T x, y;
|
||||
|
||||
inline vec2() {}
|
||||
|
||||
template<typename U = T, typename std::enable_if<std::numeric_limits<U>::has_quiet_NaN, int>::type = 0>
|
||||
inline vec2(null) : x(std::numeric_limits<T>::quiet_NaN()), y(std::numeric_limits<T>::quiet_NaN()) {}
|
||||
|
||||
template<typename U = T, typename std::enable_if<!std::numeric_limits<U>::has_quiet_NaN, int>::type = 0>
|
||||
inline vec2(null) : x(std::numeric_limits<T>::min()), y(std::numeric_limits<T>::min()) {}
|
||||
|
||||
inline vec2(const vec2& o) : x(o.x), y(o.y) {}
|
||||
|
||||
template<typename U>
|
||||
inline vec2(const U& u) : x(u.x), y(u.y) {}
|
||||
|
||||
inline vec2(T x, T y) : x(x), y(y) {}
|
||||
|
||||
inline bool operator==(const vec2& rhs) const {
|
||||
return x == rhs.x && y == rhs.y;
|
||||
}
|
||||
|
||||
template <typename O>
|
||||
inline typename std::enable_if<std::is_arithmetic<O>::value, vec2>::type
|
||||
operator*(O o) const {
|
||||
return {x * o, y * o};
|
||||
}
|
||||
|
||||
template <typename O>
|
||||
inline typename std::enable_if<std::is_arithmetic<O>::value, vec2>::type &
|
||||
operator*=(O o) {
|
||||
x *= o;
|
||||
y *= o;
|
||||
}
|
||||
|
||||
template <typename O>
|
||||
inline typename std::enable_if<std::is_arithmetic<O>::value, vec2>::type
|
||||
operator-(O o) const {
|
||||
return {x - o, y - o};
|
||||
}
|
||||
|
||||
template <typename O>
|
||||
inline typename std::enable_if<!std::is_arithmetic<O>::value, vec2>::type
|
||||
operator-(const O &o) const {
|
||||
return {x - o.x, y - o.y};
|
||||
}
|
||||
|
||||
template <typename M>
|
||||
inline vec2 matMul(const M &m) const {
|
||||
return {m[0] * x + m[1] * y, m[2] * x + m[3] * y};
|
||||
}
|
||||
|
||||
template<typename U = T, typename std::enable_if<std::numeric_limits<U>::has_quiet_NaN, int>::type = 0>
|
||||
inline operator bool() const {
|
||||
return !isnan(x) && !isnan(y);
|
||||
}
|
||||
|
||||
template<typename U = T, typename std::enable_if<!std::numeric_limits<U>::has_quiet_NaN, int>::type = 0>
|
||||
inline operator bool() const {
|
||||
return x != std::numeric_limits<T>::min() && y != std::numeric_limits<T>::min();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T = double>
|
||||
@@ -58,6 +105,8 @@ struct box {
|
||||
vec2<double> center;
|
||||
};
|
||||
|
||||
typedef vec2<int16_t> Coordinate;
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -110,9 +110,5 @@ void cancel_request_http(Request *request) {
|
||||
}
|
||||
}
|
||||
|
||||
double time() {
|
||||
return glfwGetTime();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
39
llmr.gyp
39
llmr.gyp
@@ -1,12 +1,12 @@
|
||||
{
|
||||
'includes': [
|
||||
'./common.gypi',
|
||||
'./config.gypi'
|
||||
'./config.gypi',
|
||||
],
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'before_build_actions',
|
||||
'type':'none',
|
||||
'target_name': 'shaders_gl',
|
||||
'type': 'none',
|
||||
'actions': [
|
||||
{
|
||||
'action_name': 'Build Shaders',
|
||||
@@ -17,8 +17,31 @@
|
||||
'include/llmr/shader/shaders.hpp',
|
||||
'src/shader/shaders.cpp'
|
||||
],
|
||||
'action': ['bin/build-shaders.js'],
|
||||
},
|
||||
'action': ['bin/build-shaders.js', 'gl'],
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'target_name': 'shaders_gles2',
|
||||
'type': 'none',
|
||||
'actions': [
|
||||
{
|
||||
'action_name': 'Build Shaders',
|
||||
'inputs': [
|
||||
'<!@(find src -name "*.glsl")'
|
||||
],
|
||||
'outputs': [
|
||||
'include/llmr/shader/shaders.hpp',
|
||||
'src/shader/shaders.cpp'
|
||||
],
|
||||
'action': ['bin/build-shaders.js', 'gles2'],
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'target_name': 'build_stylesheet',
|
||||
'type': 'none',
|
||||
'actions': [
|
||||
{
|
||||
'action_name': 'Build Stylesheet',
|
||||
'inputs': [
|
||||
@@ -37,7 +60,8 @@
|
||||
'product_name': 'llmr-x86',
|
||||
'type': 'static_library',
|
||||
'dependencies': [
|
||||
'before_build_actions'
|
||||
'build_stylesheet',
|
||||
'shaders_gl',
|
||||
],
|
||||
'sources': [
|
||||
'<!@(find src -name "*.cpp")',
|
||||
@@ -95,7 +119,8 @@
|
||||
'product_name': 'llmr-ios',
|
||||
'type': 'static_library',
|
||||
'dependencies': [
|
||||
'before_build_actions'
|
||||
'build_stylesheet',
|
||||
'shaders_gles2',
|
||||
],
|
||||
'sources': [
|
||||
'<!@(find src -name "*.cpp")',
|
||||
|
||||
@@ -84,9 +84,5 @@ void cancel_request_http(Request *request)
|
||||
}];
|
||||
}
|
||||
|
||||
double time() {
|
||||
return glfwGetTime();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
7
package.json
Normal file
7
package.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "llmr-native",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"glsl-optimizer": "https://github.com/kkaefer/glsl-optimizer/archive/amalgamation.zip"
|
||||
}
|
||||
}
|
||||
142
src/geometry/glyph_atlas.cpp
Normal file
142
src/geometry/glyph_atlas.cpp
Normal file
@@ -0,0 +1,142 @@
|
||||
#include <llmr/geometry/glyph_atlas.hpp>
|
||||
#include <llmr/map/vector_tile.hpp>
|
||||
#include <llmr/platform/gl.hpp>
|
||||
#include <llmr/platform/platform.hpp>
|
||||
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
GlyphAtlas::GlyphAtlas(uint16_t width, uint16_t height)
|
||||
: width(width),
|
||||
height(height),
|
||||
bin(width, height),
|
||||
data(new char[width *height]),
|
||||
dirty(true) {
|
||||
}
|
||||
|
||||
GlyphAtlas::~GlyphAtlas() {
|
||||
delete[] data;
|
||||
}
|
||||
|
||||
Rect<uint16_t> GlyphAtlas::addGlyph(uint64_t tile_id, const std::string& face_name,
|
||||
const VectorTileGlyph& glyph) {
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
|
||||
// Use constant value for now.
|
||||
const uint8_t buffer = 3;
|
||||
|
||||
std::map<uint32_t, GlyphValue>& face = index[face_name];
|
||||
std::map<uint32_t, GlyphValue>::iterator it = face.find(glyph.id);
|
||||
|
||||
// The glyph is already in this texture.
|
||||
if (it != face.end()) {
|
||||
GlyphValue& value = it->second;
|
||||
value.ids.insert(tile_id);
|
||||
return value.rect;
|
||||
}
|
||||
|
||||
// The glyph bitmap has zero width.
|
||||
if (!glyph.bitmap.size()) {
|
||||
return Rect<uint16_t>{ 0, 0, 0, 0 };
|
||||
}
|
||||
|
||||
uint16_t buffered_width = glyph.metrics.width + buffer * 2;
|
||||
uint16_t buffered_height = glyph.metrics.height + buffer * 2;
|
||||
|
||||
// Add a 1px border around every image.
|
||||
uint16_t pack_width = buffered_width;
|
||||
uint16_t pack_height = buffered_height;
|
||||
|
||||
// Increase to next number divisible by 4, but at least 1.
|
||||
// This is so we can scale down the texture coordinates and pack them
|
||||
// into 2 bytes rather than 4 bytes.
|
||||
pack_width += (4 - pack_width % 4);
|
||||
pack_height += (4 - pack_height % 4);
|
||||
|
||||
Rect<uint16_t> rect = bin.allocate(pack_width, pack_height);
|
||||
if (rect.w == 0) {
|
||||
fprintf(stderr, "glyph bitmap overflow");
|
||||
return rect;
|
||||
}
|
||||
|
||||
assert(rect.x + rect.w <= width);
|
||||
assert(rect.y + rect.h <= height);
|
||||
|
||||
face.emplace(glyph.id, GlyphValue { rect, tile_id });
|
||||
|
||||
// Copy the bitmap
|
||||
char *target = data;
|
||||
const char *source = glyph.bitmap.data();
|
||||
for (uint32_t y = 0; y < buffered_height; y++) {
|
||||
uint32_t y1 = width * (rect.y + y) + rect.x;
|
||||
uint32_t y2 = buffered_width * y;
|
||||
for (uint32_t x = 0; x < buffered_width; x++) {
|
||||
target[y1 + x] = source[y2 + x];
|
||||
}
|
||||
}
|
||||
|
||||
dirty = true;
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
void GlyphAtlas::removeGlyphs(uint64_t tile_id) {
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
|
||||
for (auto& faces : index) {
|
||||
std::map<uint32_t, GlyphValue>& face = faces.second;
|
||||
for (auto it = face.begin(); it != face.end(); /* we advance in the body */) {
|
||||
GlyphValue& value = it->second;
|
||||
value.ids.erase(tile_id);
|
||||
|
||||
if (!value.ids.size()) {
|
||||
const Rect<uint16_t>& rect = value.rect;
|
||||
|
||||
// Clear out the bitmap.
|
||||
char *target = data;
|
||||
for (uint32_t y = 0; y < rect.h; y++) {
|
||||
uint32_t y1 = width * (rect.y + y) + rect.x;
|
||||
for (uint32_t x = 0; x < rect.w; x++) {
|
||||
target[y1 + x] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
dirty = true;
|
||||
|
||||
bin.release(rect);
|
||||
|
||||
// Make sure to post-increment the iterator: This will return the
|
||||
// current iterator, but will go to the next position before we
|
||||
// erase the element from the map. That way, the iterator stays
|
||||
// valid.
|
||||
face.erase(it++);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GlyphAtlas::bind() {
|
||||
if (!texture) {
|
||||
glGenTextures(1, &texture);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
} else {
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
}
|
||||
|
||||
if (dirty) {
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, width, height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, data);
|
||||
dirty = false;
|
||||
|
||||
// platform::show_debug_image("Glyph Atlas", data, width, height);
|
||||
}
|
||||
};
|
||||
42
src/geometry/interpolate.cpp
Normal file
42
src/geometry/interpolate.cpp
Normal file
@@ -0,0 +1,42 @@
|
||||
#include <llmr/geometry/interpolate.hpp>
|
||||
|
||||
namespace llmr {
|
||||
|
||||
Anchors interpolate(const std::vector<Coordinate> &vertices, float spacing,
|
||||
float minScale, int start) {
|
||||
float distance = 0.0f;
|
||||
float markedDistance = 0.0f;
|
||||
int added = start;
|
||||
|
||||
Anchors points;
|
||||
|
||||
auto end = vertices.end() - 1;
|
||||
int i = 0;
|
||||
for (auto it = vertices.begin(); it != end; it++, i++) {
|
||||
const Coordinate &a = *(it), b = *(it + 1);
|
||||
|
||||
float segmentDist = util::dist<float>(a, b);
|
||||
float angle = util::angle_to(b, a);
|
||||
|
||||
while (markedDistance + spacing < distance + segmentDist) {
|
||||
markedDistance += spacing;
|
||||
|
||||
float t = (markedDistance - distance) / segmentDist,
|
||||
x = util::interp(a.x, b.x, t), y = util::interp(a.y, b.y, t),
|
||||
s = added % 8 == 0 ? minScale : added % 4 == 0
|
||||
? 2
|
||||
: added % 2 == 0 ? 4 : 8;
|
||||
|
||||
if (x >= 0 && x < 4096 && y >= 0 && y < 4096) {
|
||||
points.emplace_back(x, y, angle, s, i);
|
||||
}
|
||||
|
||||
added++;
|
||||
}
|
||||
|
||||
distance += segmentDist;
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
}
|
||||
35
src/geometry/text_buffer.cpp
Normal file
35
src/geometry/text_buffer.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
#include <llmr/geometry/text_buffer.hpp>
|
||||
#include <llmr/platform/gl.hpp>
|
||||
#include <llmr/util/math.hpp>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
const double TextVertexBuffer::angleFactor = 128.0 / M_PI;
|
||||
|
||||
size_t TextVertexBuffer::add(int16_t x, int16_t y, float ox, float oy, uint16_t tx, uint16_t ty, float angle, float minzoom, std::array<float, 2> range, float maxzoom, float labelminzoom) {
|
||||
size_t idx = index();
|
||||
void *data = addElement();
|
||||
|
||||
int16_t *shorts = static_cast<int16_t *>(data);
|
||||
shorts[0] = x;
|
||||
shorts[1] = y;
|
||||
shorts[2] = round(ox * 64); // use 1/64 pixels for placement
|
||||
shorts[3] = round(oy * 64);
|
||||
|
||||
uint8_t *ubytes = static_cast<uint8_t *>(data);
|
||||
// a_data1
|
||||
ubytes[8] = tx / 4;
|
||||
ubytes[9] = ty / 4;
|
||||
ubytes[10] = labelminzoom * 10;
|
||||
ubytes[11] = (int16_t)round(angle * angleFactor) % 256;
|
||||
|
||||
// a_data2
|
||||
ubytes[12] = minzoom * 10; // 1/10 zoom levels: z16 == 160.
|
||||
ubytes[13] = fmin(maxzoom, 25) * 10; // 1/10 zoom levels: z16 == 160.
|
||||
ubytes[14] = util::max((int16_t)round(range[0] * angleFactor), (int16_t)0) % 256;
|
||||
ubytes[15] = util::min((int16_t)round(range[1] * angleFactor), (int16_t)255) % 256;
|
||||
|
||||
return idx;
|
||||
}
|
||||
@@ -13,9 +13,10 @@ using namespace llmr;
|
||||
Map::Map(Settings& settings)
|
||||
: settings(settings),
|
||||
transform(),
|
||||
style(),
|
||||
painter(transform, settings, style),
|
||||
texturepool(),
|
||||
style(),
|
||||
glyphAtlas(1024, 1024),
|
||||
painter(transform, settings, style, glyphAtlas),
|
||||
min_zoom(0),
|
||||
max_zoom((use_raster ? kTileRasterMaxZoom : kTileVectorMaxZoom)) {
|
||||
}
|
||||
@@ -45,7 +46,7 @@ void Map::loadSettings() {
|
||||
transform.setAngle(settings.angle);
|
||||
transform.setScale(settings.scale);
|
||||
transform.setLonLat(settings.longitude, settings.latitude);
|
||||
style.cascade(transform.getZoom());
|
||||
style.cascade(transform.getNormalizedZoom());
|
||||
update();
|
||||
}
|
||||
|
||||
@@ -86,7 +87,7 @@ void Map::stopPanning() {
|
||||
|
||||
void Map::scaleBy(double ds, double cx, double cy, double duration) {
|
||||
transform.scaleBy(ds, cx, cy, duration);
|
||||
style.cascade(transform.getZoom());
|
||||
style.cascade(transform.getNormalizedZoom());
|
||||
update();
|
||||
|
||||
transform.getLonLat(settings.longitude, settings.latitude);
|
||||
@@ -136,7 +137,7 @@ void Map::getLonLat(double& lon, double& lat) const {
|
||||
|
||||
void Map::setLonLatZoom(double lon, double lat, double zoom, double duration) {
|
||||
transform.setLonLatZoom(lon, lat, zoom, duration);
|
||||
style.cascade(transform.getZoom());
|
||||
style.cascade(transform.getNormalizedZoom());
|
||||
update();
|
||||
|
||||
transform.getLonLat(settings.longitude, settings.latitude);
|
||||
@@ -150,7 +151,7 @@ void Map::getLonLatZoom(double& lon, double& lat, double& zoom) const {
|
||||
|
||||
void Map::setScale(double scale, double cx, double cy, double duration) {
|
||||
transform.setScale(scale, cx, cy, duration);
|
||||
style.cascade(transform.getZoom());
|
||||
style.cascade(transform.getNormalizedZoom());
|
||||
update();
|
||||
|
||||
transform.getLonLat(settings.longitude, settings.latitude);
|
||||
@@ -160,7 +161,7 @@ void Map::setScale(double scale, double cx, double cy, double duration) {
|
||||
|
||||
void Map::setZoom(double zoom, double duration) {
|
||||
transform.setZoom(zoom, duration);
|
||||
style.cascade(transform.getZoom());
|
||||
style.cascade(transform.getNormalizedZoom());
|
||||
update();
|
||||
|
||||
transform.getLonLat(settings.longitude, settings.latitude);
|
||||
@@ -214,7 +215,7 @@ void Map::resetPosition() {
|
||||
transform.setAngle(0);
|
||||
transform.setLonLat(0, 0);
|
||||
transform.setZoom(0);
|
||||
style.cascade(transform.getZoom());
|
||||
style.cascade(transform.getNormalizedZoom());
|
||||
update();
|
||||
|
||||
transform.getLonLat(settings.longitude, settings.latitude);
|
||||
@@ -284,7 +285,7 @@ TileData::State Map::addTile(const Tile::ID& id) {
|
||||
|
||||
if (!new_tile.data) {
|
||||
// If we don't find working tile data, we're just going to load it.
|
||||
new_tile.data = std::make_shared<TileData>(normalized_id, style, use_raster, (pixel_ratio > 1.0));
|
||||
new_tile.data = std::make_shared<TileData>(normalized_id, style, glyphAtlas, use_raster, (pixel_ratio > 1.0));
|
||||
new_tile.data->request();
|
||||
tile_data.push_front(new_tile.data);
|
||||
}
|
||||
@@ -482,5 +483,5 @@ bool Map::render() {
|
||||
|
||||
painter.renderMatte();
|
||||
|
||||
return changed || transform.needsAnimation();
|
||||
return changed || transform.needsAnimation() || painter.needsAnimation();
|
||||
}
|
||||
|
||||
@@ -1,35 +1,24 @@
|
||||
#include <llmr/map/tile_data.hpp>
|
||||
#include <llmr/map/tile_parser.hpp>
|
||||
|
||||
#include <llmr/style/style.hpp>
|
||||
#include <llmr/map/vector_tile.hpp>
|
||||
#include <llmr/geometry/fill_buffer.hpp>
|
||||
#include <llmr/geometry/line_buffer.hpp>
|
||||
#include <llmr/geometry/point_buffer.hpp>
|
||||
#include <llmr/geometry/text_buffer.hpp>
|
||||
#include <llmr/geometry/elements_buffer.hpp>
|
||||
#include <llmr/renderer/fill_bucket.hpp>
|
||||
#include <llmr/renderer/line_bucket.hpp>
|
||||
#include <llmr/renderer/point_bucket.hpp>
|
||||
#include <llmr/util/pbf.hpp>
|
||||
#include <llmr/util/raster.hpp>
|
||||
#include <llmr/util/string.hpp>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
TileData::TileData(Tile::ID id, const Style& style, const bool use_raster, const bool use_retina)
|
||||
TileData::TileData(Tile::ID id, const Style& style, GlyphAtlas& glyphAtlas, const bool use_raster, const bool use_retina)
|
||||
: id(id),
|
||||
state(State::initial),
|
||||
use_raster(use_raster),
|
||||
use_retina(use_retina),
|
||||
state(State::initial),
|
||||
raster(),
|
||||
fillVertexBuffer(std::make_shared<FillVertexBuffer>()),
|
||||
lineVertexBuffer(std::make_shared<LineVertexBuffer>()),
|
||||
pointVertexBuffer(std::make_shared<PointVertexBuffer>()),
|
||||
triangleElementsBuffer(std::make_shared<TriangleElementsBuffer>()),
|
||||
lineElementsBuffer(std::make_shared<LineElementsBuffer>()),
|
||||
pointElementsBuffer(std::make_shared<PointElementsBuffer>()),
|
||||
style(style) {
|
||||
style(style),
|
||||
glyphAtlas(glyphAtlas) {
|
||||
|
||||
// Initialize tile debug coordinates
|
||||
char coord[32];
|
||||
@@ -38,6 +27,7 @@ TileData::TileData(Tile::ID id, const Style& style, const bool use_raster, const
|
||||
}
|
||||
|
||||
TileData::~TileData() {
|
||||
glyphAtlas.removeGlyphs((uint64_t)id);
|
||||
cancel();
|
||||
}
|
||||
|
||||
@@ -96,15 +86,10 @@ bool TileData::parse() {
|
||||
}
|
||||
|
||||
try {
|
||||
double parse_time_start = platform::time();
|
||||
VectorTile tile(pbf((const uint8_t *)data.data(), data.size()));
|
||||
parseStyleLayers(tile, style.layers);
|
||||
double parse_time = (platform::time() - parse_time_start) * 1000.0;
|
||||
if (state == State::obsolete) {
|
||||
// fprintf(stderr, "[%p] parsing [%d/%d/%d] cancelled after %8.3fms\n", this, id.z, id.x, id.y, parse_time);
|
||||
} else {
|
||||
// fprintf(stderr, "[%p] parsing [%d/%d/%d] took %8.3fms\n", this, id.z, id.x, id.y, parse_time);
|
||||
}
|
||||
// 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, style, glyphAtlas);
|
||||
} catch (const std::exception& ex) {
|
||||
fprintf(stderr, "[%p] exception [%d/%d/%d]... failed: %s\n", this, id.z, id.x, id.y, ex.what());
|
||||
cancel();
|
||||
@@ -119,115 +104,3 @@ bool TileData::parse() {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void TileData::parseStyleLayers(const VectorTile& tile, const std::vector<LayerDescription>& layers) {
|
||||
for (const LayerDescription& layer_desc : layers) {
|
||||
// Cancel early when parsing.
|
||||
if (state == State::obsolete) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (layer_desc.child_layer.size()) {
|
||||
// This is a layer group.
|
||||
// TODO: create framebuffer
|
||||
parseStyleLayers(tile, layer_desc.child_layer);
|
||||
// TODO: render framebuffer on previous framebuffer
|
||||
} else {
|
||||
// This is a singular layer. Check if this bucket already exists. If not,
|
||||
// parse this bucket.
|
||||
auto bucket_it = buckets.find(layer_desc.bucket_name);
|
||||
if (bucket_it == buckets.end()) {
|
||||
auto bucket_it = style.buckets.find(layer_desc.bucket_name);
|
||||
if (bucket_it != style.buckets.end()) {
|
||||
// Only create the new bucket if we have an actual specification
|
||||
// for it.
|
||||
std::shared_ptr<Bucket> bucket = createBucket(tile, bucket_it->second);
|
||||
if (bucket) {
|
||||
// Bucket creation might fail because the data tile may not
|
||||
// contain any data that falls into this bucket.
|
||||
buckets[layer_desc.bucket_name] = bucket;
|
||||
}
|
||||
} else {
|
||||
// There is no proper specification for this bucket, even though
|
||||
// it is referenced in the stylesheet.
|
||||
fprintf(stderr, "Stylesheet specifies bucket %s, but it is not defined\n", layer_desc.bucket_name.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Bucket> TileData::createBucket(const VectorTile& tile, const BucketDescription& bucket_desc) {
|
||||
auto layer_it = tile.layers.find(bucket_desc.source_layer);
|
||||
if (layer_it != tile.layers.end()) {
|
||||
const VectorTileLayer& layer = layer_it->second;
|
||||
if (bucket_desc.type == BucketType::Fill) {
|
||||
return createFillBucket(layer, bucket_desc);
|
||||
} else if (bucket_desc.type == BucketType::Line) {
|
||||
return createLineBucket(layer, bucket_desc);
|
||||
} else if (bucket_desc.type == BucketType::Point) {
|
||||
return createPointBucket(layer, bucket_desc);
|
||||
} else {
|
||||
// TODO: create other bucket types.
|
||||
}
|
||||
} else {
|
||||
// The layer specified in the bucket does not exist. Do nothing.
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<Bucket> TileData::createFillBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc) {
|
||||
std::shared_ptr<FillBucket> bucket = std::make_shared<FillBucket>(fillVertexBuffer, triangleElementsBuffer, lineElementsBuffer, bucket_desc);
|
||||
|
||||
FilteredVectorTileLayer filtered_layer(layer, bucket_desc);
|
||||
for (pbf feature : filtered_layer) {
|
||||
if (state == State::obsolete) return nullptr;
|
||||
|
||||
while (feature.next(4)) { // geometry
|
||||
pbf geometry_pbf = feature.message();
|
||||
if (geometry_pbf) {
|
||||
bucket->addGeometry(geometry_pbf);
|
||||
bucket->tessellate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bucket;
|
||||
}
|
||||
|
||||
std::shared_ptr<Bucket> TileData::createLineBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc) {
|
||||
std::shared_ptr<LineBucket> bucket = std::make_shared<LineBucket>(lineVertexBuffer, triangleElementsBuffer, pointElementsBuffer, bucket_desc);
|
||||
|
||||
FilteredVectorTileLayer filtered_layer(layer, bucket_desc);
|
||||
for (pbf feature : filtered_layer) {
|
||||
if (state == State::obsolete) return nullptr;
|
||||
|
||||
while (feature.next(4)) { // geometry
|
||||
pbf geometry_pbf = feature.message();
|
||||
if (geometry_pbf) {
|
||||
bucket->addGeometry(geometry_pbf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bucket;
|
||||
}
|
||||
|
||||
std::shared_ptr<Bucket> TileData::createPointBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc) {
|
||||
std::shared_ptr<PointBucket> bucket = std::make_shared<PointBucket>(pointVertexBuffer, bucket_desc);
|
||||
|
||||
FilteredVectorTileLayer filtered_layer(layer, bucket_desc);
|
||||
for (pbf feature : filtered_layer) {
|
||||
if (state == State::obsolete) return nullptr;
|
||||
|
||||
while (feature.next(4)) { // geometry
|
||||
pbf geometry_pbf = feature.message();
|
||||
if (geometry_pbf) {
|
||||
bucket->addGeometry(geometry_pbf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bucket;
|
||||
}
|
||||
|
||||
168
src/map/tile_parser.cpp
Normal file
168
src/map/tile_parser.cpp
Normal file
@@ -0,0 +1,168 @@
|
||||
#include <llmr/map/tile_parser.hpp>
|
||||
|
||||
#include <llmr/style/style.hpp>
|
||||
#include <llmr/renderer/fill_bucket.hpp>
|
||||
#include <llmr/renderer/line_bucket.hpp>
|
||||
#include <llmr/renderer/point_bucket.hpp>
|
||||
#include <llmr/renderer/text_bucket.hpp>
|
||||
|
||||
#include <llmr/util/std.hpp>
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
|
||||
TileParser::TileParser(const std::string& data, TileData& tile, const Style& style, GlyphAtlas& glyphAtlas)
|
||||
: data(pbf((const uint8_t *)data.data(), data.size())),
|
||||
tile(tile),
|
||||
style(style),
|
||||
glyphAtlas(glyphAtlas),
|
||||
placement(tile.id.z) {
|
||||
parseGlyphs();
|
||||
parseStyleLayers(style.layers);
|
||||
}
|
||||
|
||||
bool TileParser::obsolete() const {
|
||||
return tile.state == TileData::State::obsolete;
|
||||
}
|
||||
|
||||
void TileParser::parseGlyphs() {
|
||||
for (const std::pair<std::string, const VectorTileFace> pair : data.faces) {
|
||||
const std::string &name = pair.first;
|
||||
const VectorTileFace &face = pair.second;
|
||||
|
||||
GlyphPositions &glyphs = faces[name];
|
||||
for (const VectorTileGlyph &glyph : face.glyphs) {
|
||||
const Rect<uint16_t> rect =
|
||||
glyphAtlas.addGlyph((uint64_t)tile.id, name, glyph);
|
||||
glyphs.emplace(glyph.id, Glyph{rect, glyph.metrics});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TileParser::parseStyleLayers(const std::vector<LayerDescription>& layers) {
|
||||
for (const LayerDescription& layer_desc : layers) {
|
||||
// Cancel early when parsing.
|
||||
if (obsolete()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (layer_desc.child_layer.size()) {
|
||||
// This is a layer group.
|
||||
// TODO: create framebuffer
|
||||
parseStyleLayers(layer_desc.child_layer);
|
||||
// TODO: render framebuffer on previous framebuffer
|
||||
} else {
|
||||
// This is a singular layer. Check if this bucket already exists. If not,
|
||||
// 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);
|
||||
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);
|
||||
if (bucket) {
|
||||
// Bucket creation might fail because the data tile may not
|
||||
// contain any data that falls into this bucket.
|
||||
tile.buckets[layer_desc.bucket_name] = std::move(bucket);
|
||||
}
|
||||
} else {
|
||||
// There is no proper specification for this bucket, even though
|
||||
// it is referenced in the stylesheet.
|
||||
fprintf(stderr, "Stylesheet specifies bucket %s, but it is not defined\n", layer_desc.bucket_name.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Bucket> TileParser::createBucket(const BucketDescription& bucket_desc) {
|
||||
auto layer_it = data.layers.find(bucket_desc.source_layer);
|
||||
if (layer_it != data.layers.end()) {
|
||||
const VectorTileLayer& layer = layer_it->second;
|
||||
if (bucket_desc.type == BucketType::Fill) {
|
||||
return createFillBucket(layer, bucket_desc);
|
||||
} else if (bucket_desc.type == BucketType::Line) {
|
||||
return createLineBucket(layer, bucket_desc);
|
||||
} else if (bucket_desc.type == BucketType::Point) {
|
||||
return createPointBucket(layer, bucket_desc);
|
||||
} else if (bucket_desc.type == BucketType::Text) {
|
||||
return createTextBucket(layer, bucket_desc);
|
||||
} else {
|
||||
throw std::runtime_error("unknown bucket type");
|
||||
}
|
||||
} else {
|
||||
// The layer specified in the bucket does not exist. Do nothing.
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <class Bucket>
|
||||
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;
|
||||
|
||||
while (feature.next(4)) { // geometry
|
||||
pbf geometry_pbf = feature.message();
|
||||
if (geometry_pbf) {
|
||||
bucket->addGeometry(geometry_pbf);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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<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::createPointBucket(const VectorTileLayer& layer, const BucketDescription& bucket_desc) {
|
||||
std::unique_ptr<PointBucket> bucket = std::make_unique<PointBucket>(
|
||||
tile.pointVertexBuffer, bucket_desc);
|
||||
addBucketFeatures(bucket, layer, bucket_desc);
|
||||
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);
|
||||
}
|
||||
|
||||
std::unique_ptr<TextBucket> bucket = std::make_unique<TextBucket>(
|
||||
tile.textVertexBuffer, tile.triangleElementsBuffer, bucket_desc, placement);
|
||||
|
||||
FilteredVectorTileLayer filtered_layer(layer, bucket_desc);
|
||||
for (const pbf& feature_pbf : filtered_layer) {
|
||||
if (obsolete()) return nullptr;
|
||||
bucket->addFeature({ feature_pbf, layer }, faces, shaping);
|
||||
}
|
||||
|
||||
return std::move(bucket);
|
||||
}
|
||||
@@ -256,6 +256,10 @@ float Transform::getZoom() const {
|
||||
return log(scale) / M_LN2;
|
||||
}
|
||||
|
||||
float Transform::getNormalizedZoom() const {
|
||||
return log(scale * util::tileSize / 256.0f) / M_LN2;
|
||||
}
|
||||
|
||||
int32_t Transform::getIntegerZoom() const {
|
||||
return floor(log(scale) / M_LN2);
|
||||
}
|
||||
|
||||
@@ -2,9 +2,72 @@
|
||||
#include <llmr/style/bucket_description.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
|
||||
std::ostream& llmr::operator<<(std::ostream& os, const FeatureType& type) {
|
||||
switch (type) {
|
||||
case FeatureType::Unknown: return os << "Unknown";
|
||||
case FeatureType::Point: return os << "Point";
|
||||
case FeatureType::LineString: return os << "LineString";
|
||||
case FeatureType::Polygon: return os << "Polygon";
|
||||
default: return os << "Invalid";
|
||||
}
|
||||
}
|
||||
|
||||
VectorTileFeature::VectorTileFeature(pbf feature, const VectorTileLayer& layer) {
|
||||
while (feature.next()) {
|
||||
if (feature.tag == 1) { // id
|
||||
id = feature.varint<uint64_t>();
|
||||
} else if (feature.tag == 2) { // tags
|
||||
// tags are packed varints. They should have an even length.
|
||||
pbf tags = feature.message();
|
||||
while (tags) {
|
||||
uint32_t tag_key = tags.varint();
|
||||
|
||||
if (layer.keys.size() <= tag_key) {
|
||||
// TODO: Throw correct exception
|
||||
fprintf(stderr, "feature referenced out of range key\n");
|
||||
break;
|
||||
}
|
||||
|
||||
if (tags) {
|
||||
uint32_t tag_val = tags.varint();
|
||||
if (layer.values.size() <= tag_val) {
|
||||
// TODO: Throw correct exception
|
||||
fprintf(stderr, "feature referenced out of range value\n");
|
||||
break;
|
||||
}
|
||||
|
||||
properties.emplace(layer.keys[tag_key], layer.values[tag_val]);
|
||||
} else {
|
||||
// TODO: Throw correct exception
|
||||
fprintf(stderr, "uneven number of feature tag ids\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (feature.tag == 3) { // type
|
||||
type = (FeatureType)feature.varint();
|
||||
} else if (feature.tag == 4) { // geometry
|
||||
geometry = feature.message();
|
||||
} else {
|
||||
feature.skip();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::ostream& llmr::operator<<(std::ostream& os, const VectorTileFeature& feature) {
|
||||
os << "Feature(" << feature.id << "): " << feature.type << std::endl;
|
||||
for (const auto& prop : feature.properties) {
|
||||
os << " - " << prop.first << ": " << prop.second << std::endl;
|
||||
}
|
||||
return os;
|
||||
}
|
||||
|
||||
|
||||
VectorTile::VectorTile() {}
|
||||
|
||||
|
||||
@@ -13,6 +76,9 @@ 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();
|
||||
}
|
||||
@@ -26,8 +92,101 @@ 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();
|
||||
@@ -37,10 +196,34 @@ VectorTileLayer::VectorTileLayer(pbf layer) : data(layer) {
|
||||
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)
|
||||
@@ -129,7 +312,7 @@ void FilteredVectorTileLayer::iterator::operator++() {
|
||||
default: type = BucketType::None; break;
|
||||
}
|
||||
|
||||
if (type != filter.bucket_desc.type) {
|
||||
if (type != filter.bucket_desc.feature_type) {
|
||||
break; // feature_pbf loop early
|
||||
}
|
||||
} else {
|
||||
@@ -137,7 +320,7 @@ void FilteredVectorTileLayer::iterator::operator++() {
|
||||
}
|
||||
}
|
||||
|
||||
if (!skip && type == filter.bucket_desc.type) {
|
||||
if (!skip && type == filter.bucket_desc.feature_type) {
|
||||
valid = true;
|
||||
return; // data loop
|
||||
} else {
|
||||
|
||||
@@ -5,4 +5,4 @@ const char *llmr::kTileRasterURL = "https://a.tiles.mapbox.com/v3/justin.map-pgy
|
||||
const char *llmr::kSpriteURL = "https://dl.dropboxusercontent.com/u/575564/sprite";
|
||||
|
||||
const int32_t llmr::kTileVectorMaxZoom = 14;
|
||||
const int32_t llmr::kTileRasterMaxZoom = 21;
|
||||
const int32_t llmr::kTileRasterMaxZoom = 21;
|
||||
|
||||
@@ -30,9 +30,9 @@ void FillBucket::free(void *, void *ptr) {
|
||||
::free(ptr);
|
||||
}
|
||||
|
||||
FillBucket::FillBucket(const std::shared_ptr<FillVertexBuffer>& vertexBuffer,
|
||||
const std::shared_ptr<TriangleElementsBuffer>& triangleElementsBuffer,
|
||||
const std::shared_ptr<LineElementsBuffer>& lineElementsBuffer,
|
||||
FillBucket::FillBucket(FillVertexBuffer& vertexBuffer,
|
||||
TriangleElementsBuffer& triangleElementsBuffer,
|
||||
LineElementsBuffer& lineElementsBuffer,
|
||||
const BucketDescription& bucket_desc)
|
||||
: geom_desc(bucket_desc.geometry),
|
||||
allocator(new TESSalloc {
|
||||
@@ -51,9 +51,9 @@ tesselator(tessNewTess(allocator)),
|
||||
vertexBuffer(vertexBuffer),
|
||||
triangleElementsBuffer(triangleElementsBuffer),
|
||||
lineElementsBuffer(lineElementsBuffer),
|
||||
vertex_start(vertexBuffer->index()),
|
||||
triangle_elements_start(triangleElementsBuffer->index()),
|
||||
line_elements_start(lineElementsBuffer->index()) {
|
||||
vertex_start(vertexBuffer.index()),
|
||||
triangle_elements_start(triangleElementsBuffer.index()),
|
||||
line_elements_start(lineElementsBuffer.index()) {
|
||||
assert(tesselator);
|
||||
}
|
||||
|
||||
@@ -88,6 +88,8 @@ void FillBucket::addGeometry(pbf& geom) {
|
||||
line.clear();
|
||||
hasVertices = true;
|
||||
}
|
||||
|
||||
tessellate();
|
||||
}
|
||||
|
||||
void FillBucket::tessellate() {
|
||||
@@ -129,12 +131,12 @@ void FillBucket::tessellate() {
|
||||
for (const ClipperLib::IntPoint& pt : polygon) {
|
||||
line.push_back(pt.X);
|
||||
line.push_back(pt.Y);
|
||||
vertexBuffer->add(pt.X, pt.Y);
|
||||
vertexBuffer.add(pt.X, pt.Y);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < group_count; i++) {
|
||||
const size_t prev_i = (i == 0 ? group_count : i) - 1;
|
||||
lineElementsBuffer->add(lineIndex + prev_i, lineIndex + i);
|
||||
lineElementsBuffer.add(lineIndex + prev_i, lineIndex + i);
|
||||
}
|
||||
|
||||
lineIndex += group_count;
|
||||
@@ -153,7 +155,7 @@ void FillBucket::tessellate() {
|
||||
|
||||
for (size_t i = 0; i < vertex_count; ++i) {
|
||||
if (vertex_indices[i] == TESS_UNDEF) {
|
||||
vertexBuffer->add(round(vertices[i * 2]), round(vertices[i * 2 + 1]));
|
||||
vertexBuffer.add(round(vertices[i * 2]), round(vertices[i * 2 + 1]));
|
||||
vertex_indices[i] = (TESSindex)total_vertex_count;
|
||||
total_vertex_count++;
|
||||
}
|
||||
@@ -178,7 +180,7 @@ void FillBucket::tessellate() {
|
||||
const TESSindex c = vertex_indices[element_group[2]];
|
||||
|
||||
if (a != TESS_UNDEF && b != TESS_UNDEF && c != TESS_UNDEF) {
|
||||
triangleElementsBuffer->add(triangleIndex + a, triangleIndex + b, triangleIndex + c);
|
||||
triangleElementsBuffer.add(triangleIndex + a, triangleIndex + b, triangleIndex + c);
|
||||
} else {
|
||||
// TODO: We're missing a vertex that was not part of the line.
|
||||
fprintf(stderr, "undefined element buffer\n");
|
||||
@@ -209,34 +211,34 @@ bool FillBucket::empty() const {
|
||||
}
|
||||
|
||||
void FillBucket::drawElements(PlainShader& shader) {
|
||||
char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer->itemSize);
|
||||
char *elements_index = BUFFER_OFFSET(triangle_elements_start * triangleElementsBuffer->itemSize);
|
||||
char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize);
|
||||
char *elements_index = BUFFER_OFFSET(triangle_elements_start * triangleElementsBuffer.itemSize);
|
||||
for (triangle_group_type& group : triangleGroups) {
|
||||
group.array.bind(shader, *vertexBuffer, *triangleElementsBuffer, vertex_index);
|
||||
group.array.bind(shader, vertexBuffer, triangleElementsBuffer, vertex_index);
|
||||
glDrawElements(GL_TRIANGLES, group.elements_length * 3, GL_UNSIGNED_SHORT, elements_index);
|
||||
vertex_index += group.vertex_length * vertexBuffer->itemSize;
|
||||
elements_index += group.elements_length * triangleElementsBuffer->itemSize;
|
||||
vertex_index += group.vertex_length * vertexBuffer.itemSize;
|
||||
elements_index += group.elements_length * triangleElementsBuffer.itemSize;
|
||||
}
|
||||
}
|
||||
|
||||
void FillBucket::drawElements(PatternShader& shader) {
|
||||
char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer->itemSize);
|
||||
char *elements_index = BUFFER_OFFSET(triangle_elements_start * triangleElementsBuffer->itemSize);
|
||||
char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize);
|
||||
char *elements_index = BUFFER_OFFSET(triangle_elements_start * triangleElementsBuffer.itemSize);
|
||||
for (triangle_group_type& group : triangleGroups) {
|
||||
group.array.bind(shader, *vertexBuffer, *triangleElementsBuffer, vertex_index);
|
||||
group.array.bind(shader, vertexBuffer, triangleElementsBuffer, vertex_index);
|
||||
glDrawElements(GL_TRIANGLES, group.elements_length * 3, GL_UNSIGNED_SHORT, elements_index);
|
||||
vertex_index += group.vertex_length * vertexBuffer->itemSize;
|
||||
elements_index += group.elements_length * triangleElementsBuffer->itemSize;
|
||||
vertex_index += group.vertex_length * vertexBuffer.itemSize;
|
||||
elements_index += group.elements_length * triangleElementsBuffer.itemSize;
|
||||
}
|
||||
}
|
||||
|
||||
void FillBucket::drawVertices(OutlineShader& shader) {
|
||||
char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer->itemSize);
|
||||
char *elements_index = BUFFER_OFFSET(line_elements_start * lineElementsBuffer->itemSize);
|
||||
char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize);
|
||||
char *elements_index = BUFFER_OFFSET(line_elements_start * lineElementsBuffer.itemSize);
|
||||
for (line_group_type& group : lineGroups) {
|
||||
group.array.bind(shader, *vertexBuffer, *lineElementsBuffer, vertex_index);
|
||||
group.array.bind(shader, vertexBuffer, lineElementsBuffer, vertex_index);
|
||||
glDrawElements(GL_LINES, group.elements_length * 2, GL_UNSIGNED_SHORT, elements_index);
|
||||
vertex_index += group.vertex_length * vertexBuffer->itemSize;
|
||||
elements_index += group.elements_length * lineElementsBuffer->itemSize;
|
||||
vertex_index += group.vertex_length * vertexBuffer.itemSize;
|
||||
elements_index += group.elements_length * lineElementsBuffer.itemSize;
|
||||
}
|
||||
}
|
||||
|
||||
47
src/renderer/frame_history.cpp
Normal file
47
src/renderer/frame_history.cpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#include <llmr/renderer/frame_history.hpp>
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
// Record frame history that will be used to calculate fading params
|
||||
void FrameHistory::record(float zoom) {
|
||||
// first frame ever
|
||||
if (!history.size()) {
|
||||
history.emplace_back(FrameSnapshot{0.0f, zoom});
|
||||
history.emplace_back(FrameSnapshot{0.0f, zoom});
|
||||
}
|
||||
|
||||
if (history.size() > 0 || history.back().z != zoom) {
|
||||
history.emplace_back(FrameSnapshot{static_cast<float>(platform::time() * 1000), zoom});
|
||||
}
|
||||
}
|
||||
|
||||
bool FrameHistory::needsAnimation(const float duration) const {
|
||||
if (!history.size()) return false;
|
||||
|
||||
// If we have a value that is older than duration and whose z value is the
|
||||
// same as the most current z value, and if all values inbetween have the
|
||||
// same z value, we don't need animation, otherwise we probably do.
|
||||
const FrameSnapshot &pivot = history.back();
|
||||
|
||||
int i = -1;
|
||||
while (history.size() > i + 1 &&
|
||||
history[i + 1].time + duration < pivot.time) {
|
||||
i++;
|
||||
}
|
||||
|
||||
if (i < 0) {
|
||||
// There is no frame that is older than the duration time, so we need to
|
||||
// check all frames.
|
||||
i = 0;
|
||||
}
|
||||
|
||||
// Make sure that all subsequent snapshots have the same zoom as the last
|
||||
// pivot element.
|
||||
for (; history.size() > i; i++) {
|
||||
if (history[i].z != pivot.z) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
#include <llmr/renderer/line_bucket.hpp>
|
||||
#include <llmr/geometry/line_buffer.hpp>
|
||||
#include <llmr/geometry/elements_buffer.hpp>
|
||||
#include <llmr/geometry/geometry.hpp>
|
||||
|
||||
@@ -18,17 +17,17 @@ struct geometry_too_long_exception : std::exception {};
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
LineBucket::LineBucket(const std::shared_ptr<LineVertexBuffer>& vertexBuffer,
|
||||
const std::shared_ptr<TriangleElementsBuffer>& triangleElementsBuffer,
|
||||
const std::shared_ptr<PointElementsBuffer>& pointElementsBuffer,
|
||||
LineBucket::LineBucket(LineVertexBuffer& vertexBuffer,
|
||||
TriangleElementsBuffer& triangleElementsBuffer,
|
||||
PointElementsBuffer& pointElementsBuffer,
|
||||
const BucketDescription& bucket_desc)
|
||||
: geometry(bucket_desc.geometry),
|
||||
vertexBuffer(vertexBuffer),
|
||||
triangleElementsBuffer(triangleElementsBuffer),
|
||||
pointElementsBuffer(pointElementsBuffer),
|
||||
vertex_start(vertexBuffer->index()),
|
||||
triangle_elements_start(triangleElementsBuffer->index()),
|
||||
point_elements_start(pointElementsBuffer->index())
|
||||
vertex_start(vertexBuffer.index()),
|
||||
triangle_elements_start(triangleElementsBuffer.index()),
|
||||
point_elements_start(pointElementsBuffer.index())
|
||||
{
|
||||
}
|
||||
|
||||
@@ -101,7 +100,7 @@ void LineBucket::addGeometry(const std::vector<Coordinate>& vertices) {
|
||||
nextNormal = util::normal<double>(currentVertex, lastVertex);
|
||||
}
|
||||
|
||||
int32_t start_vertex = (int32_t)vertexBuffer->index();
|
||||
int32_t start_vertex = (int32_t)vertexBuffer.index();
|
||||
|
||||
std::vector<TriangleElement> triangle_store;
|
||||
std::vector<PointElement> point_store;
|
||||
@@ -172,7 +171,7 @@ void LineBucket::addGeometry(const std::vector<Coordinate>& vertices) {
|
||||
// Add offset square begin cap.
|
||||
if (!prevVertex && beginCap == CapType::Square) {
|
||||
// Add first vertex
|
||||
e3 = (int32_t)vertexBuffer->add(currentVertex.x, currentVertex.y, // vertex pos
|
||||
e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos
|
||||
flip * (prevNormal.x + prevNormal.y), flip * (-prevNormal.x + prevNormal.y), // extrude normal
|
||||
0, 0, distance) - start_vertex; // texture normal
|
||||
|
||||
@@ -180,7 +179,7 @@ void LineBucket::addGeometry(const std::vector<Coordinate>& vertices) {
|
||||
e1 = e2; e2 = e3;
|
||||
|
||||
// Add second vertex
|
||||
e3 = (int32_t)vertexBuffer->add(currentVertex.x, currentVertex.y, // vertex pos
|
||||
e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos
|
||||
flip * (prevNormal.x - prevNormal.y), flip * (prevNormal.x + prevNormal.y), // extrude normal
|
||||
0, 1, distance) - start_vertex; // texture normal
|
||||
|
||||
@@ -191,7 +190,7 @@ void LineBucket::addGeometry(const std::vector<Coordinate>& vertices) {
|
||||
// Add offset square end cap.
|
||||
else if (!nextVertex && endCap == CapType::Square) {
|
||||
// Add first vertex
|
||||
e3 = (int32_t)vertexBuffer->add(currentVertex.x, currentVertex.y, // vertex pos
|
||||
e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos
|
||||
nextNormal.x - flip * nextNormal.y, flip * nextNormal.x + nextNormal.y, // extrude normal
|
||||
0, 0, distance) - start_vertex; // texture normal
|
||||
|
||||
@@ -199,7 +198,7 @@ void LineBucket::addGeometry(const std::vector<Coordinate>& vertices) {
|
||||
e1 = e2; e2 = e3;
|
||||
|
||||
// Add second vertex
|
||||
e3 = (int32_t)vertexBuffer->add(currentVertex.x, currentVertex.y, // vertex pos
|
||||
e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos
|
||||
nextNormal.x + flip * nextNormal.y, -flip * nextNormal.x + nextNormal.y, // extrude normal
|
||||
0, 1, distance) - start_vertex; // texture normal
|
||||
|
||||
@@ -225,7 +224,7 @@ void LineBucket::addGeometry(const std::vector<Coordinate>& vertices) {
|
||||
}
|
||||
|
||||
// Add first vertex
|
||||
e3 = (int32_t)vertexBuffer->add(currentVertex.x, currentVertex.y, // vertex pos
|
||||
e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos
|
||||
flip * joinNormal.x, flip * joinNormal.y, // extrude normal
|
||||
0, 0, distance) - start_vertex; // texture normal
|
||||
|
||||
@@ -233,7 +232,7 @@ void LineBucket::addGeometry(const std::vector<Coordinate>& vertices) {
|
||||
e1 = e2; e2 = e3;
|
||||
|
||||
// Add second vertex
|
||||
e3 = (int32_t)vertexBuffer->add(currentVertex.x, currentVertex.y, // vertex pos
|
||||
e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos
|
||||
-flip * joinNormal.x, -flip * joinNormal.y, // extrude normal
|
||||
0, 1, distance) - start_vertex; // texture normal
|
||||
|
||||
@@ -249,7 +248,7 @@ void LineBucket::addGeometry(const std::vector<Coordinate>& vertices) {
|
||||
else {
|
||||
// Close up the previous line
|
||||
// Add first vertex
|
||||
e3 = (int32_t)vertexBuffer->add(currentVertex.x, currentVertex.y, // vertex pos
|
||||
e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos
|
||||
flip * prevNormal.y, -flip * prevNormal.x, // extrude normal
|
||||
0, 0, distance) - start_vertex; // texture normal
|
||||
|
||||
@@ -257,7 +256,7 @@ void LineBucket::addGeometry(const std::vector<Coordinate>& vertices) {
|
||||
e1 = e2; e2 = e3;
|
||||
|
||||
// Add second vertex.
|
||||
e3 = (int32_t)vertexBuffer->add(currentVertex.x, currentVertex.y, // vertex pos
|
||||
e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos
|
||||
-flip * prevNormal.y, flip * prevNormal.x, // extrude normal
|
||||
0, 1, distance) - start_vertex; // texture normal
|
||||
|
||||
@@ -287,7 +286,7 @@ void LineBucket::addGeometry(const std::vector<Coordinate>& vertices) {
|
||||
|
||||
// Start the new quad.
|
||||
// Add first vertex
|
||||
e3 = (int32_t)vertexBuffer->add(currentVertex.x, currentVertex.y, // vertex pos
|
||||
e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos
|
||||
-flip * nextNormal.y, flip * nextNormal.x, // extrude normal
|
||||
0, 0, distance) - start_vertex; // texture normal
|
||||
|
||||
@@ -295,7 +294,7 @@ void LineBucket::addGeometry(const std::vector<Coordinate>& vertices) {
|
||||
e1 = e2; e2 = e3;
|
||||
|
||||
// Add second vertex
|
||||
e3 = (int32_t)vertexBuffer->add(currentVertex.x, currentVertex.y, // vertex pos
|
||||
e3 = (int32_t)vertexBuffer.add(currentVertex.x, currentVertex.y, // vertex pos
|
||||
flip * nextNormal.y, -flip * nextNormal.x, // extrude normal
|
||||
0, 1, distance) - start_vertex; // texture normal
|
||||
|
||||
@@ -304,7 +303,7 @@ void LineBucket::addGeometry(const std::vector<Coordinate>& vertices) {
|
||||
}
|
||||
}
|
||||
|
||||
size_t end_vertex = vertexBuffer->index();
|
||||
size_t end_vertex = vertexBuffer.index();
|
||||
size_t vertex_count = end_vertex - start_vertex;
|
||||
|
||||
// Store the triangle/line groups.
|
||||
@@ -316,7 +315,7 @@ void LineBucket::addGeometry(const std::vector<Coordinate>& vertices) {
|
||||
|
||||
triangle_group_type& group = triangleGroups.back();
|
||||
for (const TriangleElement& triangle : triangle_store) {
|
||||
triangleElementsBuffer->add(
|
||||
triangleElementsBuffer.add(
|
||||
group.vertex_length + triangle.a,
|
||||
group.vertex_length + triangle.b,
|
||||
group.vertex_length + triangle.c
|
||||
@@ -336,7 +335,7 @@ void LineBucket::addGeometry(const std::vector<Coordinate>& vertices) {
|
||||
|
||||
point_group_type& group = pointGroups.back();
|
||||
for (PointElement point : point_store) {
|
||||
pointElementsBuffer->add(group.vertex_length + point);
|
||||
pointElementsBuffer.add(group.vertex_length + point);
|
||||
}
|
||||
|
||||
group.vertex_length += vertex_count;
|
||||
@@ -357,23 +356,23 @@ bool LineBucket::hasPoints() const {
|
||||
}
|
||||
|
||||
void LineBucket::drawLines(LineShader& shader) {
|
||||
char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer->itemSize);
|
||||
char *elements_index = BUFFER_OFFSET(triangle_elements_start * triangleElementsBuffer->itemSize);
|
||||
char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize);
|
||||
char *elements_index = BUFFER_OFFSET(triangle_elements_start * triangleElementsBuffer.itemSize);
|
||||
for (triangle_group_type& group : triangleGroups) {
|
||||
group.array.bind(shader, *vertexBuffer, *triangleElementsBuffer, vertex_index);
|
||||
group.array.bind(shader, vertexBuffer, triangleElementsBuffer, vertex_index);
|
||||
glDrawElements(GL_TRIANGLES, group.elements_length * 3, GL_UNSIGNED_SHORT, elements_index);
|
||||
vertex_index += group.vertex_length * vertexBuffer->itemSize;
|
||||
elements_index += group.elements_length * triangleElementsBuffer->itemSize;
|
||||
vertex_index += group.vertex_length * vertexBuffer.itemSize;
|
||||
elements_index += group.elements_length * triangleElementsBuffer.itemSize;
|
||||
}
|
||||
}
|
||||
|
||||
void LineBucket::drawPoints(LinejoinShader& shader) {
|
||||
char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer->itemSize);
|
||||
char *elements_index = BUFFER_OFFSET(point_elements_start * pointElementsBuffer->itemSize);
|
||||
char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize);
|
||||
char *elements_index = BUFFER_OFFSET(point_elements_start * pointElementsBuffer.itemSize);
|
||||
for (point_group_type& group : pointGroups) {
|
||||
group.array.bind(shader, *vertexBuffer, *pointElementsBuffer, vertex_index);
|
||||
group.array.bind(shader, vertexBuffer, pointElementsBuffer, vertex_index);
|
||||
glDrawElements(GL_POINTS, group.elements_length, GL_UNSIGNED_SHORT, elements_index);
|
||||
vertex_index += group.vertex_length * vertexBuffer->itemSize;
|
||||
elements_index += group.elements_length * pointElementsBuffer->itemSize;
|
||||
vertex_index += group.vertex_length * vertexBuffer.itemSize;
|
||||
elements_index += group.elements_length * pointElementsBuffer.itemSize;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <llmr/renderer/fill_bucket.hpp>
|
||||
#include <llmr/renderer/line_bucket.hpp>
|
||||
#include <llmr/renderer/point_bucket.hpp>
|
||||
#include <llmr/renderer/text_bucket.hpp>
|
||||
|
||||
#include <llmr/map/transform.hpp>
|
||||
#include <llmr/map/settings.hpp>
|
||||
@@ -21,10 +22,15 @@ using namespace llmr;
|
||||
|
||||
#define BUFFER_OFFSET(i) ((char *)nullptr + (i))
|
||||
|
||||
Painter::Painter(Transform& transform, Settings& settings, Style& style)
|
||||
Painter::Painter(Transform& transform, Settings& settings, Style& style, GlyphAtlas& glyphAtlas)
|
||||
: transform(transform),
|
||||
settings(settings),
|
||||
style(style) {
|
||||
style(style),
|
||||
glyphAtlas(glyphAtlas) {
|
||||
}
|
||||
|
||||
bool Painter::needsAnimation() const {
|
||||
return frameHistory.needsAnimation(300);
|
||||
}
|
||||
|
||||
void Painter::setup() {
|
||||
@@ -37,6 +43,7 @@ void Painter::setup() {
|
||||
assert(linejoinShader);
|
||||
assert(patternShader);
|
||||
assert(rasterShader);
|
||||
assert(textShader);
|
||||
|
||||
|
||||
// Blending
|
||||
@@ -64,6 +71,7 @@ void Painter::setupShaders() {
|
||||
patternShader = std::make_unique<PatternShader>();
|
||||
pointShader = std::make_unique<PointShader>();
|
||||
rasterShader = std::make_unique<RasterShader>();
|
||||
textShader = std::make_unique<TextShader>();
|
||||
}
|
||||
|
||||
void Painter::resize(int width, int height) {
|
||||
@@ -149,6 +157,8 @@ void Painter::render(const Tile& tile) {
|
||||
return;
|
||||
}
|
||||
|
||||
frameHistory.record(transform.getNormalizedZoom());
|
||||
|
||||
matrix = tile.matrix;
|
||||
glStencilFunc(GL_EQUAL, tile.clip_id, 0xFF);
|
||||
|
||||
@@ -229,7 +239,7 @@ void Painter::renderFill(FillBucket& bucket, const std::string& layer_name, cons
|
||||
if (bucket.empty()) return;
|
||||
|
||||
const FillProperties& properties = style.computed.fills[layer_name];
|
||||
if (properties.hidden) return;
|
||||
if (!properties.enabled) return;
|
||||
|
||||
Color fill_color = properties.fill_color;
|
||||
fill_color[0] *= properties.opacity;
|
||||
@@ -356,7 +366,7 @@ void Painter::renderLine(LineBucket& bucket, const std::string& layer_name, cons
|
||||
if (bucket.empty()) return;
|
||||
|
||||
const LineProperties& properties = style.computed.lines[layer_name];
|
||||
if (properties.hidden) return;
|
||||
if (!properties.enabled) return;
|
||||
|
||||
float width = properties.width;
|
||||
float offset = properties.offset / 2;
|
||||
@@ -437,7 +447,7 @@ void Painter::renderPoint(PointBucket& bucket, const std::string& layer_name, co
|
||||
if (pass == Opaque) return;
|
||||
|
||||
const PointProperties& properties = style.computed.points[layer_name];
|
||||
if (properties.hidden) return;
|
||||
if (!properties.enabled) return;
|
||||
|
||||
Color color = properties.color;
|
||||
color[0] *= properties.opacity;
|
||||
@@ -473,6 +483,106 @@ void Painter::renderPoint(PointBucket& bucket, const std::string& layer_name, co
|
||||
bucket.drawPoints(*pointShader);
|
||||
}
|
||||
|
||||
void Painter::renderText(TextBucket& bucket, const std::string& layer_name, const Tile::ID& id) {
|
||||
// Abort early.
|
||||
if (pass == Opaque) return;
|
||||
if (bucket.empty()) return;
|
||||
|
||||
const TextProperties& properties = style.computed.texts[layer_name];
|
||||
if (!properties.enabled) return;
|
||||
|
||||
mat4 exMatrix;
|
||||
matrix::copy(exMatrix, projMatrix);
|
||||
if (bucket.geom_desc.path == TextPathType::Curve) {
|
||||
matrix::rotate_z(exMatrix, exMatrix, transform.getAngle());
|
||||
}
|
||||
|
||||
const float rotate = properties.rotate;
|
||||
if (rotate != 0.0f) {
|
||||
matrix::rotate_z(exMatrix, exMatrix, rotate);
|
||||
}
|
||||
|
||||
// If layerStyle.size > bucket.info.fontSize then labels may collide
|
||||
float fontSize = fmin(properties.size, bucket.geom_desc.font_size);
|
||||
matrix::scale(exMatrix, exMatrix, fontSize / 24.0f, fontSize / 24.0f, 1.0f);
|
||||
|
||||
useProgram(textShader->program);
|
||||
textShader->setMatrix(matrix);
|
||||
textShader->setExtrudeMatrix(exMatrix);
|
||||
|
||||
glyphAtlas.bind();
|
||||
textShader->setTextureSize({{ static_cast<float>(glyphAtlas.width), static_cast<float>(glyphAtlas.height) }});
|
||||
|
||||
textShader->setGamma(2.5f / fontSize / transform.pixelRatio);
|
||||
|
||||
// Convert the -pi..pi to an int8 range.
|
||||
float angle = round((transform.getAngle() + rotate) / M_PI * 128);
|
||||
|
||||
// adjust min/max zooms for variable font sies
|
||||
float zoomAdjust = log(fontSize / bucket.geom_desc.font_size) / log(2);
|
||||
|
||||
textShader->setAngle((int32_t)(angle + 256) % 256);
|
||||
textShader->setFlip(bucket.geom_desc.path == TextPathType::Curve ? 1 : 0);
|
||||
textShader->setZoom((transform.getNormalizedZoom() - zoomAdjust) * 10); // current zoom level
|
||||
|
||||
// Label fading
|
||||
const float duration = 300.0f;
|
||||
const float currentTime = platform::time() * 1000;
|
||||
|
||||
std::deque<FrameSnapshot> &history = frameHistory.history;
|
||||
|
||||
// Remove frames until only one is outside the duration, or until there are only three
|
||||
while (history.size() > 3 && history[1].time + duration < currentTime) {
|
||||
history.pop_front();
|
||||
}
|
||||
|
||||
if (history[1].time + duration < currentTime) {
|
||||
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);
|
||||
|
||||
// Find the range of zoom levels we want to fade between
|
||||
float startingZ = history.front().z;
|
||||
const FrameSnapshot lastFrame = history.back();
|
||||
float endingZ = lastFrame.z;
|
||||
float lowZ = fmin(startingZ, endingZ);
|
||||
float highZ = fmax(startingZ, endingZ);
|
||||
|
||||
// Calculate the speed of zooming, and how far it would zoom in terms of zoom levels in one duration
|
||||
float zoomDiff = endingZ - history[1].z,
|
||||
timeDiff = lastFrame.time - history[1].time;
|
||||
if (timeDiff > duration) timeDiff = 1;
|
||||
float fadedist = zoomDiff / (timeDiff / duration);
|
||||
|
||||
if (isnan(fadedist)) fprintf(stderr, "fadedist should never be NaN");
|
||||
|
||||
// At end of a zoom when the zoom stops changing continue pretending to zoom at that speed
|
||||
// bump is how much farther it would have been if it had continued zooming at the same rate
|
||||
float bump = (currentTime - lastFrame.time) / duration * fadedist;
|
||||
|
||||
textShader->setFadeDist(fadedist * 10);
|
||||
textShader->setMinFadeZoom(floor(lowZ * 10));
|
||||
textShader->setMaxFadeZoom(floor(highZ * 10));
|
||||
textShader->setFadeZoom((transform.getZoom() + bump) * 10);
|
||||
|
||||
// We're drawing in the translucent pass which is bottom-to-top, so we need
|
||||
// to draw the halo first.
|
||||
if (properties.halo[3] > 0.0f) {
|
||||
textShader->setColor(properties.halo);
|
||||
textShader->setBuffer(properties.haloRadius);
|
||||
glDepthRange(strata, 1.0f);
|
||||
bucket.drawGlyphs(*textShader);
|
||||
}
|
||||
|
||||
// Then, we draw the text over the halo
|
||||
textShader->setColor(properties.color);
|
||||
textShader->setBuffer((256.0f - 64.0f) / 256.0f);
|
||||
glDepthRange(strata + strata_epsilon, 1.0f);
|
||||
bucket.drawGlyphs(*textShader);
|
||||
}
|
||||
|
||||
void Painter::renderDebug(const TileData::Ptr& tile_data) {
|
||||
// Disable depth test and don't count this towards the depth buffer,
|
||||
// but *don't* disable stencil test, as we want to clip the red tile border
|
||||
|
||||
@@ -15,11 +15,11 @@ struct geometry_too_long_exception : std::exception {};
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
PointBucket::PointBucket(const std::shared_ptr<PointVertexBuffer>& vertexBuffer,
|
||||
PointBucket::PointBucket(PointVertexBuffer& vertexBuffer,
|
||||
const BucketDescription& bucket_desc)
|
||||
: geometry(bucket_desc.geometry),
|
||||
vertexBuffer(vertexBuffer),
|
||||
vertex_start(vertexBuffer->index()) {
|
||||
vertex_start(vertexBuffer.index()) {
|
||||
}
|
||||
|
||||
void PointBucket::addGeometry(pbf& geom) {
|
||||
@@ -28,13 +28,13 @@ void PointBucket::addGeometry(pbf& geom) {
|
||||
int32_t x, y;
|
||||
while ((cmd = geometry.next(x, y)) != Geometry::end) {
|
||||
if (cmd == Geometry::move_to) {
|
||||
vertexBuffer->add(x, y);
|
||||
vertexBuffer.add(x, y);
|
||||
} else {
|
||||
fprintf(stderr, "other command than move_to in point geometry\n");
|
||||
}
|
||||
}
|
||||
|
||||
vertex_end = vertexBuffer->index();
|
||||
vertex_end = vertexBuffer.index();
|
||||
}
|
||||
|
||||
void PointBucket::render(Painter& painter, const std::string& layer_name, const Tile::ID& id) {
|
||||
@@ -46,7 +46,7 @@ bool PointBucket::hasPoints() const {
|
||||
}
|
||||
|
||||
void PointBucket::drawPoints(PointShader& shader) {
|
||||
char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer->itemSize);
|
||||
array.bind(shader, *vertexBuffer, vertex_index);
|
||||
char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize);
|
||||
array.bind(shader, vertexBuffer, vertex_index);
|
||||
glDrawArrays(GL_POINTS, 0, (GLsizei)(vertex_end - vertex_start));
|
||||
}
|
||||
|
||||
163
src/renderer/text_bucket.cpp
Normal file
163
src/renderer/text_bucket.cpp
Normal file
@@ -0,0 +1,163 @@
|
||||
#include <llmr/renderer/text_bucket.hpp>
|
||||
#include <llmr/geometry/text_buffer.hpp>
|
||||
#include <llmr/geometry/elements_buffer.hpp>
|
||||
#include <llmr/geometry/geometry.hpp>
|
||||
|
||||
#include <llmr/renderer/painter.hpp>
|
||||
#include <llmr/shader/text_shader.hpp>
|
||||
#include <llmr/style/style.hpp>
|
||||
#include <llmr/map/vector_tile.hpp>
|
||||
#include <llmr/text/placement.hpp>
|
||||
|
||||
#include <llmr/util/math.hpp>
|
||||
#include <llmr/platform/gl.hpp>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#define BUFFER_OFFSET(i) ((char *) nullptr + (i))
|
||||
|
||||
#include <cassert>
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
TextBucket::TextBucket(
|
||||
TextVertexBuffer &vertexBuffer,
|
||||
TriangleElementsBuffer &triangleElementsBuffer,
|
||||
const BucketDescription &bucket_desc, Placement &placement)
|
||||
: geom_desc(bucket_desc.geometry),
|
||||
vertexBuffer(vertexBuffer),
|
||||
triangleElementsBuffer(triangleElementsBuffer),
|
||||
placement(placement),
|
||||
vertex_start(vertexBuffer.index()),
|
||||
triangle_elements_start(triangleElementsBuffer.index()) {}
|
||||
|
||||
void TextBucket::addGlyphs(const PlacedGlyphs &glyphs, float placementZoom,
|
||||
PlacementRange placementRange, float zoom) {
|
||||
placementZoom += zoom;
|
||||
|
||||
for (const PlacedGlyph &glyph : glyphs) {
|
||||
const auto &tl = glyph.tl;
|
||||
const auto &tr = glyph.tr;
|
||||
const auto &bl = glyph.bl;
|
||||
const auto &br = glyph.br;
|
||||
const auto &tex = glyph.tex;
|
||||
const auto &width = glyph.width;
|
||||
const auto &height = glyph.height;
|
||||
const auto &angle = glyph.angle;
|
||||
|
||||
float minZoom = util::max(
|
||||
static_cast<float>(zoom + log(glyph.glyphBox.minScale) / log(2)),
|
||||
placementZoom);
|
||||
float maxZoom = util::min(
|
||||
static_cast<float>(zoom + log(glyph.glyphBox.maxScale) / log(2)),
|
||||
25.0f);
|
||||
const auto &glyphAnchor = glyph.glyphBox.anchor;
|
||||
|
||||
if (maxZoom <= minZoom)
|
||||
continue;
|
||||
|
||||
// Lower min zoom so that while fading out the label
|
||||
// it can be shown outside of collision-free zoom levels
|
||||
if (minZoom == placementZoom) {
|
||||
minZoom = 0;
|
||||
}
|
||||
|
||||
const int glyph_vertex_length = 4;
|
||||
|
||||
if (!triangleGroups.size() ||
|
||||
(triangleGroups.back().vertex_length + glyph_vertex_length >
|
||||
65535)) {
|
||||
// Move to a new group because the old one can't hold the geometry.
|
||||
triangleGroups.emplace_back();
|
||||
}
|
||||
|
||||
// We're generating triangle fans, so we always start with the first
|
||||
// coordinate in this polygon.
|
||||
triangle_group_type &triangleGroup = triangleGroups.back();
|
||||
uint32_t triangleIndex = triangleGroup.vertex_length;
|
||||
|
||||
// coordinates (2 triangles)
|
||||
vertexBuffer.add(glyphAnchor.x, glyphAnchor.y, tl.x, tl.y, tex.x,
|
||||
tex.y, angle, minZoom, placementRange, maxZoom,
|
||||
placementZoom);
|
||||
vertexBuffer.add(glyphAnchor.x, glyphAnchor.y, tr.x, tr.y,
|
||||
tex.x + width, tex.y, angle, minZoom, placementRange,
|
||||
maxZoom, placementZoom);
|
||||
vertexBuffer.add(glyphAnchor.x, glyphAnchor.y, bl.x, bl.y, tex.x,
|
||||
tex.y + height, angle, minZoom, placementRange,
|
||||
maxZoom, placementZoom);
|
||||
vertexBuffer.add(glyphAnchor.x, glyphAnchor.y, br.x, br.y,
|
||||
tex.x + width, tex.y + height, angle, minZoom,
|
||||
placementRange, maxZoom, placementZoom);
|
||||
|
||||
// add the two triangles, referencing the four coordinates we just
|
||||
// inserted.
|
||||
triangleElementsBuffer.add(triangleIndex + 0, triangleIndex + 1,
|
||||
triangleIndex + 2);
|
||||
triangleElementsBuffer.add(triangleIndex + 1, triangleIndex + 2,
|
||||
triangleIndex + 3);
|
||||
|
||||
triangleGroup.vertex_length += glyph_vertex_length;
|
||||
triangleGroup.elements_length += 2;
|
||||
}
|
||||
};
|
||||
|
||||
void TextBucket::addFeature(const VectorTileFeature &feature,
|
||||
const IndexedFaces &faces,
|
||||
const std::map<Value, Shaping> &shapings) {
|
||||
auto it_prop = feature.properties.find(geom_desc.text_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;
|
||||
|
||||
// Decode all lines.
|
||||
std::vector<Coordinate> line;
|
||||
Geometry::command cmd;
|
||||
|
||||
Coordinate coord;
|
||||
pbf geom = feature.geometry;
|
||||
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);
|
||||
line.clear();
|
||||
}
|
||||
}
|
||||
line.emplace_back(x, y);
|
||||
}
|
||||
if (line.size()) {
|
||||
placement.addFeature(*this, line, geom_desc, faces, shaping);
|
||||
}
|
||||
}
|
||||
|
||||
void TextBucket::render(Painter &painter, const std::string &layer_name,
|
||||
const Tile::ID &id) {
|
||||
painter.renderText(*this, layer_name, id);
|
||||
}
|
||||
|
||||
bool TextBucket::empty() const { return triangleGroups.empty(); }
|
||||
|
||||
void TextBucket::drawGlyphs(TextShader &shader) {
|
||||
char *vertex_index = BUFFER_OFFSET(vertex_start * vertexBuffer.itemSize);
|
||||
char *elements_index =
|
||||
BUFFER_OFFSET(triangle_elements_start * triangleElementsBuffer.itemSize);
|
||||
for (triangle_group_type &group : triangleGroups) {
|
||||
group.array.bind(shader, vertexBuffer, triangleElementsBuffer, vertex_index);
|
||||
glDrawElements(GL_TRIANGLES, group.elements_length * 3, GL_UNSIGNED_SHORT, elements_index);
|
||||
vertex_index += group.vertex_length * vertexBuffer.itemSize;
|
||||
elements_index += group.elements_length * triangleElementsBuffer.itemSize;
|
||||
}
|
||||
}
|
||||
@@ -102,20 +102,9 @@ bool Shader::compileShader(GLuint *shader, GLenum type, const GLchar *source) {
|
||||
GLint status;
|
||||
|
||||
*shader = glCreateShader(type);
|
||||
|
||||
#if defined(GL_ES_VERSION_2_0)
|
||||
// Add OpenGL ES precision premable
|
||||
const GLchar *preamble = "precision highp float;\n\n";
|
||||
#else
|
||||
// Desktop GLSL
|
||||
const GLchar *preamble = "#version 120\n\n";
|
||||
#endif
|
||||
|
||||
const GLchar *strings[] = { preamble, source };
|
||||
const GLint lengths[] = { (GLint)strlen(preamble), (GLint)strlen(source) };
|
||||
|
||||
|
||||
glShaderSource(*shader, 2, strings, lengths);
|
||||
const GLchar *strings[] = { source };
|
||||
const GLint lengths[] = { (GLint)strlen(source) };
|
||||
glShaderSource(*shader, 1, strings, lengths);
|
||||
|
||||
glCompileShader(*shader);
|
||||
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED.
|
||||
#include <llmr/shader/shaders.hpp>
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
const shader_source llmr::shaders[SHADER_COUNT] = {
|
||||
{
|
||||
"// floor(127 / 2) == 63.0\n// the maximum allowed miter limit is 2.0 at the moment. the extrude normal is\n// stored in a byte (-128..127). we scale regular normals up to length 63, but\n// there are also \"special\" normals that have a bigger length (of up to 126 in\n// this case).\n// #define scale 63.0\n#define scale 0.015873016\n\nattribute vec2 a_pos;\nattribute vec2 a_extrude;\n// attribute float a_linesofar;\n\n// posmatrix is for the vertex position, exmatrix is for rotating and projecting\n// the extrusion vector.\nuniform mat4 u_matrix;\nuniform mat4 u_exmatrix;\n\n// shared\nuniform float u_ratio;\nuniform vec2 u_linewidth;\nuniform vec4 u_color;\n\nvarying vec2 v_normal;\n// varying float v_linesofar;\n\nvoid main() {\n // We store the texture normals in the most insignificant bit\n // transform y so that 0 => -1 and 1 => 1\n // In the texture normal, x is 0 if the normal points straight up/down and 1 if it's a round cap\n // y is 1 if the normal points up, and -1 if it points down\n vec2 normal = mod(a_pos, 2.0);\n normal.y = sign(normal.y - 0.5);\n v_normal = normal;\n\n // Scale the extrusion vector down to a normal and then up by the line width\n // of this vertex.\n vec4 dist = vec4(u_linewidth.s * a_extrude * scale, 0.0, 0.0);\n\n // Remove the texture normal bit of the position before scaling it with the\n // model/view matrix. Add the extrusion vector *after* the model/view matrix\n // because we're extruding the line in pixel space, regardless of the current\n // tile's zoom level.\n gl_Position = u_matrix * vec4(floor(a_pos * 0.5), 0.0, 1.0) + u_exmatrix * dist;\n // v_linesofar = a_linesofar * u_ratio;\n}",
|
||||
"uniform vec2 u_linewidth;\nuniform vec4 u_color;\n\nuniform vec2 u_dasharray;\n\nvarying vec2 v_normal;\n// varying float v_linesofar;\n\nvoid main() {\n // Calculate the distance of the pixel from the line in pixels.\n float dist = length(v_normal) * u_linewidth.s;\n\n // Calculate the antialiasing fade factor. This is either when fading in\n // the line in case of an offset line (v_linewidth.t) or when fading out\n // (v_linewidth.s)\n float alpha = clamp(min(dist - (u_linewidth.t - 1.0), u_linewidth.s - dist), 0.0, 1.0);\n\n // Calculate the antialiasing fade factor based on distance to the dash.\n // Only affects alpha when line is dashed\n // float pos = mod(v_linesofar, u_dasharray.x + u_dasharray.y);\n // alpha *= max(step(0.0, -u_dasharray.y), clamp(min(pos, u_dasharray.x - pos), 0.0, 1.0));\n\n gl_FragColor = u_color * alpha;\n}",
|
||||
},
|
||||
{
|
||||
"attribute vec2 a_pos;\n\nuniform mat4 u_matrix;\nuniform vec2 u_world;\nuniform float u_size;\n\nvarying vec2 v_pos;\n\nvoid main() {\n gl_Position = u_matrix * vec4(floor(a_pos / 2.0), 0.0, 1.0);\n v_pos = (gl_Position.xy + 1.0) * u_world;\n gl_PointSize = u_size;\n}\n",
|
||||
"uniform vec4 u_color;\nuniform vec2 u_linewidth;\n\nvarying vec2 v_pos;\n\nvoid main() {\n float dist = length(v_pos - gl_FragCoord.xy);\n\n // Calculate the antialiasing fade factor. This is either when fading in\n // the line in case of an offset line (v_linewidth.t) or when fading out\n // (v_linewidth.s)\n float alpha = clamp(min(dist - (u_linewidth.t - 1.0), u_linewidth.s - dist), 0.0, 1.0);\n gl_FragColor = u_color * alpha;\n}\n",
|
||||
},
|
||||
{
|
||||
"attribute vec2 a_pos;\nuniform mat4 u_matrix;\nuniform vec2 u_world;\n\nvarying vec2 v_pos;\n\nvoid main() {\n gl_Position = u_matrix * vec4(a_pos, 0, 1);\n v_pos = (gl_Position.xy + 1.0) / 2.0 * u_world;\n}\n",
|
||||
"uniform vec4 u_color;\n\nvarying vec2 v_pos;\n\nvoid main() {\n float dist = length(v_pos - gl_FragCoord.xy);\n float alpha = smoothstep(1.0, 0.0, dist);\n gl_FragColor = u_color * alpha;\n}\n",
|
||||
},
|
||||
{
|
||||
"uniform mat4 u_matrix;\n\nattribute vec2 a_pos;\n\nvarying vec2 v_pos;\n\nvoid main() {\n v_pos = a_pos;\n gl_Position = u_matrix * vec4(a_pos, 0, 1);\n}\n",
|
||||
"uniform vec4 u_color;\n\nuniform vec2 u_offset;\nuniform vec2 u_pattern_size;\nuniform vec2 u_pattern_tl;\nuniform vec2 u_pattern_br;\nuniform float u_mix;\n\n\nuniform sampler2D u_image;\n\nvarying vec2 v_pos;\n\nvoid main() {\n\n vec2 imagecoord = mod((v_pos + u_offset) / u_pattern_size, 1.0);\n vec2 pos = mix(u_pattern_tl, u_pattern_br, imagecoord);\n vec4 color1 = texture2D(u_image, pos);\n\n vec2 imagecoord2 = mod(imagecoord * 2.0, 1.0);\n vec2 pos2 = mix(u_pattern_tl, u_pattern_br, imagecoord2);\n vec4 color2 = texture2D(u_image, pos2);\n\n vec4 color = mix(color1, color2, u_mix);\n gl_FragColor = color + u_color * (1.0 - color.a);\n}\n",
|
||||
},
|
||||
{
|
||||
"attribute vec2 a_pos;\n\nuniform mat4 u_matrix;\n\nvoid main() {\n gl_Position = u_matrix * vec4(a_pos, 0, 1);\n}\n",
|
||||
"uniform vec4 u_color;\n\nvoid main() {\n gl_FragColor = u_color;\n}\n",
|
||||
},
|
||||
{
|
||||
"attribute vec2 a_pos;\n\nuniform mat4 u_matrix;\nuniform float u_size;\n\nvoid main() {\n gl_Position = u_matrix * vec4(a_pos, 0, 1);\n gl_PointSize = u_size;\n}\n",
|
||||
"#define root2 1.4142135623730951\n\nuniform sampler2D u_image;\nuniform vec2 u_tl;\nuniform vec2 u_br;\nuniform vec4 u_color;\n\nuniform vec2 pos;\nuniform float inbounds;\nuniform vec4 color;\n\nvoid main() {\n vec2 pos = (gl_PointCoord * 2.0 - 1.0) * root2 / 2.0 + 0.5;\n\n float inbounds = step(0.0, pos.x) * step(0.0, pos.y) *\n (1.0 - step(1.0, pos.x)) * (1.0 - step(1.0, pos.y));\n\n vec4 color = texture2D(u_image, mix(u_tl, u_br, pos)) * inbounds;\n\n gl_FragColor = color * u_color;\n}\n",
|
||||
},
|
||||
{
|
||||
"uniform mat4 u_matrix;\n\nattribute vec2 a_pos;\n\nvarying vec2 v_pos;\n\nvoid main() {\n gl_Position = u_matrix * vec4(a_pos, 0, 1);\n v_pos = a_pos / 4096.0;\n}\n",
|
||||
"uniform sampler2D u_image;\nuniform float u_opacity;\n\nvarying vec2 v_pos;\n\nvoid main() {\n gl_FragColor = texture2D(u_image, v_pos) * u_opacity;\n}\n",
|
||||
}
|
||||
};
|
||||
13
src/shader/text.fragment.glsl
Normal file
13
src/shader/text.fragment.glsl
Normal file
@@ -0,0 +1,13 @@
|
||||
uniform sampler2D u_texture;
|
||||
uniform vec4 u_color;
|
||||
uniform float u_buffer;
|
||||
uniform float u_gamma;
|
||||
|
||||
varying vec2 v_tex;
|
||||
varying float v_alpha;
|
||||
|
||||
void main() {
|
||||
float dist = texture2D(u_texture, v_tex).a;
|
||||
float alpha = smoothstep(u_buffer - u_gamma, u_buffer + u_gamma, dist) * v_alpha;
|
||||
gl_FragColor = u_color * alpha;
|
||||
}
|
||||
69
src/shader/text.vertex.glsl
Normal file
69
src/shader/text.vertex.glsl
Normal file
@@ -0,0 +1,69 @@
|
||||
attribute vec2 a_pos;
|
||||
attribute vec2 a_offset;
|
||||
attribute vec4 a_data1;
|
||||
attribute vec4 a_data2;
|
||||
|
||||
|
||||
// posmatrix is for the vertex position, exmatrix is for rotating and projecting
|
||||
// the extrusion vector.
|
||||
uniform mat4 u_matrix;
|
||||
uniform mat4 u_exmatrix;
|
||||
uniform float u_angle;
|
||||
uniform float u_zoom;
|
||||
uniform float u_flip;
|
||||
uniform float u_fadedist;
|
||||
uniform float u_minfadezoom;
|
||||
uniform float u_maxfadezoom;
|
||||
uniform float u_fadezoom;
|
||||
|
||||
uniform vec2 u_texsize;
|
||||
|
||||
varying vec2 v_tex;
|
||||
varying float v_alpha;
|
||||
|
||||
void main() {
|
||||
vec2 a_tex = a_data1.xy;
|
||||
float a_labelminzoom = a_data1[2];
|
||||
float a_angle = a_data1[3];
|
||||
vec2 a_zoom = a_data2.st;
|
||||
vec2 a_range = a_data2.pq;
|
||||
|
||||
float rev = 0.0;
|
||||
|
||||
// u_angle is angle of the map, -128..128 representing 0..2PI
|
||||
// a_angle is angle of the label, 0..256 representing 0..2PI, where 0 is horizontal text
|
||||
float rotated = mod(a_angle + u_angle, 256.0);
|
||||
// if the label rotates with the map, and if the rotated label is upside down, hide it
|
||||
if (u_flip > 0.0 && rotated >= 64.0 && rotated < 192.0) rev = 1.0;
|
||||
|
||||
// If the label should be invisible, we move the vertex outside
|
||||
// of the view plane so that the triangle gets clipped. This makes it easier
|
||||
// for us to create degenerate triangle strips.
|
||||
// u_zoom is the current zoom level adjusted for the change in font size
|
||||
float z = 2.0 - step(a_zoom[0], u_zoom) - (1.0 - step(a_zoom[1], u_zoom)) + rev;
|
||||
|
||||
// fade out labels
|
||||
float alpha = clamp((u_fadezoom - a_labelminzoom) / u_fadedist, 0.0, 1.0);
|
||||
|
||||
if (u_fadedist >= 0.0) {
|
||||
v_alpha = alpha;
|
||||
} else {
|
||||
v_alpha = 1.0 - alpha;
|
||||
}
|
||||
if (u_maxfadezoom < a_labelminzoom) {
|
||||
v_alpha = 0.0;
|
||||
}
|
||||
if (u_minfadezoom >= a_labelminzoom) {
|
||||
v_alpha = 1.0;
|
||||
}
|
||||
|
||||
// if label has been faded out, clip it
|
||||
z += step(v_alpha, 0.0);
|
||||
|
||||
// all the angles are 0..256 representing 0..2PI
|
||||
// hide if (angle >= a_rangeend && angle < rangestart)
|
||||
z += (1.0 - step(a_range[0], u_angle)) * step(a_range[1], u_angle);
|
||||
|
||||
gl_Position = u_matrix * vec4(a_pos, 0, 1) + u_exmatrix * vec4(a_offset / 64.0, z, 0);
|
||||
v_tex = a_tex * 4.0 / u_texsize;
|
||||
}
|
||||
157
src/shader/text_shader.cpp
Normal file
157
src/shader/text_shader.cpp
Normal file
@@ -0,0 +1,157 @@
|
||||
#include <llmr/shader/text_shader.hpp>
|
||||
#include <llmr/shader/shaders.hpp>
|
||||
#include <llmr/platform/gl.hpp>
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
TextShader::TextShader()
|
||||
: Shader(
|
||||
shaders[TEXT_SHADER].vertex,
|
||||
shaders[TEXT_SHADER].fragment
|
||||
) {
|
||||
if (!valid) {
|
||||
fprintf(stderr, "invalid text shader\n");
|
||||
return;
|
||||
}
|
||||
|
||||
a_pos = glGetAttribLocation(program, "a_pos");
|
||||
a_offset = glGetAttribLocation(program, "a_offset");
|
||||
a_data1 = glGetAttribLocation(program, "a_data1");
|
||||
a_data2 = glGetAttribLocation(program, "a_data2");
|
||||
|
||||
u_matrix = glGetUniformLocation(program, "u_matrix");
|
||||
u_color = glGetUniformLocation(program, "u_color");
|
||||
u_buffer = glGetUniformLocation(program, "u_buffer");
|
||||
u_gamma = glGetUniformLocation(program, "u_gamma");
|
||||
u_exmatrix = glGetUniformLocation(program, "u_exmatrix");
|
||||
u_angle = glGetUniformLocation(program, "u_angle");
|
||||
u_zoom = glGetUniformLocation(program, "u_zoom");
|
||||
u_flip = glGetUniformLocation(program, "u_flip");
|
||||
u_fadedist = glGetUniformLocation(program, "u_fadedist");
|
||||
u_minfadezoom = glGetUniformLocation(program, "u_minfadezoom");
|
||||
u_maxfadezoom = glGetUniformLocation(program, "u_maxfadezoom");
|
||||
u_fadezoom = glGetUniformLocation(program, "u_fadezoom");
|
||||
u_texsize = glGetUniformLocation(program, "u_texsize");
|
||||
|
||||
// fprintf(stderr, "TextShader:\n");
|
||||
// fprintf(stderr, " - a_pos: %d\n", a_pos);
|
||||
// fprintf(stderr, " - a_offset: %d\n", a_offset);
|
||||
// fprintf(stderr, " - a_data1: %d\n", a_tex);
|
||||
// fprintf(stderr, " - a_data2: %d\n", a_tex);
|
||||
// fprintf(stderr, " - u_color: %d\n", u_color);
|
||||
// fprintf(stderr, " - u_buffer: %d\n", u_buffer);
|
||||
// fprintf(stderr, " - u_gamma: %d\n", u_gamma);
|
||||
// fprintf(stderr, " - u_exmatrix: %d\n", u_exmatrix);
|
||||
// fprintf(stderr, " - u_angle: %d\n", u_angle);
|
||||
// fprintf(stderr, " - u_zoom: %d\n", u_zoom);
|
||||
// fprintf(stderr, " - u_flip: %d\n", u_flip);
|
||||
// fprintf(stderr, " - u_fadedist: %d\n", u_fadedist);
|
||||
// fprintf(stderr, " - u_minfadezoom: %d\n", u_minfadezoom);
|
||||
// fprintf(stderr, " - u_maxfadezoom: %d\n", u_maxfadezoom);
|
||||
// fprintf(stderr, " - u_fadezoom: %d\n", u_fadezoom);
|
||||
// fprintf(stderr, " - u_texsize: %d\n", u_texsize);
|
||||
}
|
||||
|
||||
void TextShader::bind(char *offset) {
|
||||
glEnableVertexAttribArray(a_pos);
|
||||
glVertexAttribPointer(a_pos, 2, GL_SHORT, false, 16, offset + 0);
|
||||
|
||||
glEnableVertexAttribArray(a_offset);
|
||||
glVertexAttribPointer(a_offset, 2, GL_SHORT, false, 16, offset + 4);
|
||||
|
||||
glEnableVertexAttribArray(a_data1);
|
||||
glVertexAttribPointer(a_data1, 4, GL_UNSIGNED_BYTE, false, 16, offset + 8);
|
||||
|
||||
glEnableVertexAttribArray(a_data2);
|
||||
glVertexAttribPointer(a_data2, 4, GL_UNSIGNED_BYTE, false, 16, offset + 12);
|
||||
}
|
||||
|
||||
void TextShader::setColor(const std::array<float, 4>& new_color) {
|
||||
if (color != new_color) {
|
||||
glUniform4fv(u_color, 1, new_color.data());
|
||||
color = new_color;
|
||||
}
|
||||
}
|
||||
|
||||
void TextShader::setColor(float r, float g, float b, float a) {
|
||||
setColor({{ r, g, b, a }});
|
||||
}
|
||||
|
||||
void TextShader::setBuffer(float new_buffer) {
|
||||
if (buffer != new_buffer) {
|
||||
glUniform1f(u_buffer, new_buffer);
|
||||
buffer = new_buffer;
|
||||
}
|
||||
}
|
||||
|
||||
void TextShader::setGamma(float new_gamma) {
|
||||
if (gamma != new_gamma) {
|
||||
glUniform1f(u_gamma, new_gamma);
|
||||
gamma = new_gamma;
|
||||
}
|
||||
}
|
||||
|
||||
void TextShader::setExtrudeMatrix(const std::array<float, 16> &new_exmatrix) {
|
||||
if (exmatrix != new_exmatrix) {
|
||||
glUniformMatrix4fv(u_exmatrix, 1, GL_FALSE, new_exmatrix.data());
|
||||
exmatrix = new_exmatrix;
|
||||
}
|
||||
}
|
||||
|
||||
void TextShader::setAngle(float new_angle) {
|
||||
if (angle != new_angle) {
|
||||
glUniform1f(u_angle, new_angle);
|
||||
angle = new_angle;
|
||||
}
|
||||
}
|
||||
|
||||
void TextShader::setZoom(float new_zoom) {
|
||||
if (zoom != new_zoom) {
|
||||
glUniform1f(u_zoom, new_zoom);
|
||||
zoom = new_zoom;
|
||||
}
|
||||
}
|
||||
|
||||
void TextShader::setFlip(float new_flip) {
|
||||
if (flip != new_flip) {
|
||||
glUniform1f(u_flip, new_flip);
|
||||
flip = new_flip;
|
||||
}
|
||||
}
|
||||
|
||||
void TextShader::setFadeDist(float new_fadedist) {
|
||||
if (fadedist != new_fadedist) {
|
||||
glUniform1f(u_fadedist, new_fadedist);
|
||||
fadedist = new_fadedist;
|
||||
}
|
||||
}
|
||||
|
||||
void TextShader::setMinFadeZoom(float new_minfadezoom) {
|
||||
if (minfadezoom != new_minfadezoom) {
|
||||
glUniform1f(u_minfadezoom, new_minfadezoom);
|
||||
minfadezoom = new_minfadezoom;
|
||||
}
|
||||
}
|
||||
|
||||
void TextShader::setMaxFadeZoom(float new_maxfadezoom) {
|
||||
if (maxfadezoom != new_maxfadezoom) {
|
||||
glUniform1f(u_maxfadezoom, new_maxfadezoom);
|
||||
maxfadezoom = new_maxfadezoom;
|
||||
}
|
||||
}
|
||||
|
||||
void TextShader::setFadeZoom(float new_fadezoom) {
|
||||
if (fadezoom != new_fadezoom) {
|
||||
glUniform1f(u_fadezoom, new_fadezoom);
|
||||
fadezoom = new_fadezoom;
|
||||
}
|
||||
}
|
||||
|
||||
void TextShader::setTextureSize(const std::array<float, 2> &new_texsize) {
|
||||
if (texsize != new_texsize) {
|
||||
glUniform2fv(u_texsize, 1, new_texsize.data());
|
||||
texsize = new_texsize;
|
||||
}
|
||||
}
|
||||
@@ -89,3 +89,23 @@ bool functions::linear(float z, const std::vector<bool>&) {
|
||||
// TODO
|
||||
return false;
|
||||
}
|
||||
|
||||
float functions::exponential(float z, const std::vector<float>& values) {
|
||||
if (values.size() != 5) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const float z_base = values[0];
|
||||
const float val = values[1];
|
||||
const float slope = values[2];
|
||||
const float min = values[3];
|
||||
const float max = values[4];
|
||||
|
||||
return fmin(fmax(min, val + pow(1.75, (z - z_base)) * slope), max);
|
||||
// return fmin(fmax(min, val + (z - z_base) * slope), max);
|
||||
}
|
||||
|
||||
bool functions::exponential(float z, const std::vector<bool>&) {
|
||||
// TODO
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED.
|
||||
#include <llmr/style/resources.hpp>
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
const unsigned char resources::style[] = R"JSON({"buckets":{"water":{"source":"streets","layer":"water","type":"fill"},"road_large":{"source":"streets","layer":"road","field":"class","value":["motorway","main"],"type":"line","cap":"round","join":"round"},"road_regular":{"source":"streets","layer":"road","field":"class","value":"street","type":"line","cap":"round","join":"round"},"road_limited":{"source":"streets","layer":"road","field":"class","value":"street_limited","type":"line","cap":"round","join":"round"},"park":{"source":"streets","layer":"landuse","field":"class","value":"park","type":"fill"},"wood":{"source":"streets","layer":"landuse","field":"class","value":"wood","type":"fill"},"school":{"source":"streets","layer":"landuse","field":"class","value":"school","type":"fill"},"cemetery":{"source":"streets","layer":"landuse","field":"class","value":"cemetery","type":"fill"},"industrial":{"source":"streets","layer":"landuse","field":"class","value":"industrial","type":"fill"},"building":{"source":"streets","layer":"building","type":"fill"},"alcohol_poi":{"source":"streets","layer":"poi_label","field":"type","value":["Alcohol"],"type":"point"},"cafe_poi":{"source":"streets","layer":"poi_label","field":"type","value":["Cafe"],"type":"point"},"embassy_poi":{"source":"streets","layer":"poi_label","field":"type","value":["Embassy"],"type":"point"},"park_poi":{"source":"streets","layer":"poi_label","field":"type","value":["Park"],"type":"point"},"restaurant_poi":{"source":"streets","layer":"poi_label","field":"type","value":["Restaurant"],"type":"point"}},"sprite":"img/maki-sprite","constants":{"park":"#c8df9f"},"structure":[{"name":"park","bucket":"park"},{"name":"wood","bucket":"wood"},{"name":"water","bucket":"water"},{"name":"building","bucket":"building"},{"name":"road_limited","bucket":"road_limited"},{"name":"road_regular","bucket":"road_regular"},{"name":"road_large","bucket":"road_large"},{"name":"alcohol_poi","bucket":"alcohol_poi"},{"name":"cafe_poi","bucket":"cafe_poi"},{"name":"embassy_poi","bucket":"embassy_poi"},{"name":"park_poi","bucket":"park_poi"},{"name":"restaurant_poi","bucket":"restaurant_poi"}],"classes":[{"name":"default","layers":{"background":{"type":"background","color":"#FFFFFF"},"park":{"type":"fill","color":"park","antialias":true,"image":"park"},"wood":{"type":"fill","color":"#33AA66","antialias":true,"opacity":0.1},"water":{"type":"fill","color":"#bae0e9","stroke":"#73b6e6","antialias":true,"image":"water_etsy"},"building":{"type":"fill","color":"#000000","antialias":true,"opacity":["linear",13,0,0.1,0,0.1]},"road_limited":{"type":"line","color":"#BBBBBB","width":["stops",{"z":0,"val":1},{"z":30,"val":1}]},"road_regular":{"type":"line","color":"#999999","width":["stops",{"z":0,"val":0.5},{"z":13,"val":0.5},{"z":16,"val":2},{"z":20,"val":32},{"z":30,"val":32}]},"road_large":{"type":"line","color":"#666666","width":["stops",{"z":0,"val":0.5},{"z":11,"val":0.5},{"z":13,"val":1},{"z":16,"val":4},{"z":20,"val":64},{"z":30,"val":64}]},"alcohol_poi":{"type":"point","color":"#cccccc","size":18,"image":"alcohol-shop","opacity":["linear",15,0,1,0,0.75]},"cafe_poi":{"type":"point","color":"#cccccc","size":18,"image":"cafe","opacity":["linear",15,0,1,0,0.75]},"embassy_poi":{"type":"point","color":"#cccccc","size":18,"image":"embassy","opacity":["linear",15,0,1,0,0.75]},"park_poi":{"type":"point","color":"#cccccc","size":18,"image":"park","opacity":["linear",15,0,1,0,0.75]},"restaurant_poi":{"type":"point","color":"#cccccc","size":18,"image":"restaurant","opacity":["linear",15,0,1,0,0.75]}}}]})JSON";
|
||||
const unsigned long resources::style_size = sizeof(resources::style);
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <llmr/style/style.hpp>
|
||||
#include <llmr/style/style_parser.hpp>
|
||||
#include <csscolorparser/csscolorparser.hpp>
|
||||
|
||||
#include <rapidjson/document.h>
|
||||
|
||||
@@ -36,7 +37,7 @@ void Style::cascade(float z) {
|
||||
// TODO: This should be restricted to fill styles that have actual
|
||||
// values so as to not override with default values.
|
||||
llmr::FillProperties& fill = computed.fills[layer_name];
|
||||
fill.hidden = layer.hidden(z);
|
||||
fill.enabled = layer.enabled(z);
|
||||
fill.winding = layer.winding;
|
||||
fill.antialias = layer.antialias(z);
|
||||
fill.fill_color = layer.fill_color;
|
||||
@@ -53,7 +54,7 @@ void Style::cascade(float z) {
|
||||
// TODO: This should be restricted to line styles that have actual
|
||||
// values so as to not override with default values.
|
||||
llmr::LineProperties& stroke = computed.lines[layer_name];
|
||||
stroke.hidden = layer.hidden(z);
|
||||
stroke.enabled = layer.enabled(z);
|
||||
stroke.width = layer.width(z);
|
||||
stroke.offset = layer.offset(z);
|
||||
stroke.color = layer.color;
|
||||
@@ -68,13 +69,29 @@ void Style::cascade(float z) {
|
||||
// TODO: This should be restricted to point styles that have actual
|
||||
// values so as to not override with default values.
|
||||
llmr::PointProperties& point = computed.points[layer_name];
|
||||
point.hidden = layer.hidden(z);
|
||||
point.enabled = layer.enabled(z);
|
||||
point.color = layer.color;
|
||||
point.size = layer.size(z);
|
||||
point.opacity = layer.opacity(z);
|
||||
point.image = layer.image;
|
||||
}
|
||||
|
||||
for (const auto& text_pair : sheetClass.text) {
|
||||
const std::string& layer_name = text_pair.first;
|
||||
const llmr::TextClass& layer = text_pair.second;
|
||||
|
||||
// TODO: This should be restricted to point styles that have actual
|
||||
// values so as to not override with default values.
|
||||
llmr::TextProperties& text = computed.texts[layer_name];
|
||||
text.enabled = layer.enabled(z);
|
||||
text.color = layer.color;
|
||||
text.size = layer.size(z);
|
||||
text.halo = layer.halo;
|
||||
text.haloRadius = layer.haloRadius(z);
|
||||
text.rotate = layer.rotate(z);
|
||||
text.alwaysVisible = layer.alwaysVisible(z);
|
||||
}
|
||||
|
||||
// Cascade background
|
||||
computed.background.color = sheetClass.background.color;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,12 @@ BucketDescription StyleParser::parseBucket(JSVal value) {
|
||||
} else {
|
||||
throw Style::exception("bucket type must be a string");
|
||||
}
|
||||
} else if (name == "feature_type") {
|
||||
if (value.IsString()) {
|
||||
bucket.feature_type = bucketType({ value.GetString(), value.GetStringLength() });
|
||||
} else {
|
||||
throw Style::exception("feature type must be a string");
|
||||
}
|
||||
} else if (name == "source") {
|
||||
if (value.IsString()) {
|
||||
bucket.source_name = { value.GetString(), value.GetStringLength() };
|
||||
@@ -81,6 +87,18 @@ BucketDescription StyleParser::parseBucket(JSVal value) {
|
||||
} else {
|
||||
throw Style::exception("font size must be a number");
|
||||
}
|
||||
} else if (name == "text_field") {
|
||||
if (value.IsString()) {
|
||||
bucket.geometry.text_field = { value.GetString(), value.GetStringLength() };
|
||||
} else {
|
||||
throw Style::exception("text field must be a string");
|
||||
}
|
||||
} else if (name == "path") {
|
||||
if (value.IsString()) {
|
||||
bucket.geometry.path = textPathType({ value.GetString(), value.GetStringLength() });
|
||||
} else {
|
||||
throw Style::exception("curve must be a string");
|
||||
}
|
||||
} else if (name == "miterLimit") {
|
||||
if (value.IsNumber()) {
|
||||
bucket.geometry.miter_limit = value.GetDouble();
|
||||
@@ -93,7 +111,24 @@ BucketDescription StyleParser::parseBucket(JSVal value) {
|
||||
} else {
|
||||
throw Style::exception("round limit must be a number");
|
||||
}
|
||||
} else if (name == "textMinDistance") {
|
||||
if (value.IsNumber()) {
|
||||
bucket.geometry.textMinDistance = value.GetDouble();
|
||||
} else {
|
||||
throw Style::exception("text min distance must be a number");
|
||||
}
|
||||
} else if (name == "maxAngleDelta") {
|
||||
if (value.IsNumber()) {
|
||||
bucket.geometry.maxAngleDelta = value.GetDouble();
|
||||
} else {
|
||||
throw Style::exception("max angle delta must be a number");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (bucket.feature_type == BucketType::None) {
|
||||
bucket.feature_type = bucket.type;
|
||||
}
|
||||
|
||||
return bucket;
|
||||
@@ -217,6 +252,8 @@ void StyleParser::parseClass(const std::string& name, JSVal value, ClassDescript
|
||||
class_desc.line.insert({ name, std::forward<LineClass>(parseLineClass(value)) });
|
||||
} else if (type_name == "point") {
|
||||
class_desc.point.insert({ name, std::forward<PointClass>(parsePointClass(value)) });
|
||||
} else if (type_name == "text") {
|
||||
class_desc.text.insert({ name, std::forward<TextClass>(parseTextClass(value)) });
|
||||
} else if (type_name == "background") {
|
||||
class_desc.background = parseBackgroundClass(value);
|
||||
} else {
|
||||
@@ -250,24 +287,35 @@ std::string StyleParser::parseString(JSVal value) {
|
||||
}
|
||||
|
||||
Color StyleParser::parseColor(JSVal value) {
|
||||
if (!value.IsString()) {
|
||||
if (value.IsArray()) {
|
||||
// [ r, g, b, a] array
|
||||
if (value.Size() != 4) {
|
||||
throw Style::exception("color array must have four elements");
|
||||
}
|
||||
|
||||
JSVal r = value[(rapidjson::SizeType)0], g = value[1], b = value[2], a = value[3];
|
||||
if (!r.IsNumber() || !g.IsNumber() || !b.IsNumber() || !a.IsNumber()) {
|
||||
throw Style::exception("color values must be numbers");
|
||||
}
|
||||
|
||||
return {{static_cast<float>(r.GetDouble()),
|
||||
static_cast<float>(g.GetDouble()),
|
||||
static_cast<float>(b.GetDouble()),
|
||||
static_cast<float>(a.GetDouble())}};
|
||||
|
||||
} else if (!value.IsString()) {
|
||||
throw Style::exception("color value must be a string");
|
||||
}
|
||||
|
||||
const std::string str { value.GetString(), value.GetStringLength() };
|
||||
const std::string str{value.GetString(), value.GetStringLength()};
|
||||
|
||||
auto it = constants.find(str);
|
||||
if (it != constants.end()) {
|
||||
return parseColor(*it->second);
|
||||
} else {
|
||||
CSSColorParser::Color css_color = CSSColorParser::parse(str);
|
||||
return {{
|
||||
(float)css_color.r / 255,
|
||||
(float)css_color.g / 255,
|
||||
(float)css_color.b / 255,
|
||||
css_color.a
|
||||
}
|
||||
};
|
||||
return {{(float)css_color.r / 255, (float)css_color.g / 255,
|
||||
(float)css_color.b / 255, css_color.a}};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,6 +329,8 @@ typename FunctionProperty<T>::fn StyleParser::parseFunctionType(JSVal type) {
|
||||
return &functions::linear;
|
||||
} else if (t == "stops") {
|
||||
return &functions::stops;
|
||||
} else if (t == "exponential") {
|
||||
return &functions::exponential;
|
||||
} else {
|
||||
throw Style::exception("unknown function type");
|
||||
}
|
||||
@@ -378,8 +428,8 @@ FunctionProperty<bool> StyleParser::parseBoolFunction(JSVal value) {
|
||||
FillClass StyleParser::parseFillClass(JSVal value) {
|
||||
FillClass klass;
|
||||
|
||||
if (value.HasMember("hidden")) {
|
||||
klass.hidden = parseBoolFunction(value["hidden"]);
|
||||
if (value.HasMember("enabled")) {
|
||||
klass.enabled = parseBoolFunction(value["enabled"]);
|
||||
}
|
||||
|
||||
if (value.HasMember("color")) {
|
||||
@@ -410,8 +460,8 @@ FillClass StyleParser::parseFillClass(JSVal value) {
|
||||
LineClass StyleParser::parseLineClass(JSVal value) {
|
||||
LineClass klass;
|
||||
|
||||
if (value.HasMember("hidden")) {
|
||||
klass.hidden = parseBoolFunction(value["hidden"]);
|
||||
if (value.HasMember("enabled")) {
|
||||
klass.enabled = parseBoolFunction(value["enabled"]);
|
||||
}
|
||||
|
||||
if (value.HasMember("color")) {
|
||||
@@ -432,8 +482,8 @@ LineClass StyleParser::parseLineClass(JSVal value) {
|
||||
PointClass StyleParser::parsePointClass(JSVal value) {
|
||||
PointClass klass;
|
||||
|
||||
if (value.HasMember("hidden")) {
|
||||
klass.hidden = parseBoolFunction(value["hidden"]);
|
||||
if (value.HasMember("enabled")) {
|
||||
klass.enabled = parseBoolFunction(value["enabled"]);
|
||||
}
|
||||
|
||||
if (value.HasMember("color")) {
|
||||
@@ -455,6 +505,41 @@ PointClass StyleParser::parsePointClass(JSVal value) {
|
||||
return klass;
|
||||
}
|
||||
|
||||
|
||||
TextClass StyleParser::parseTextClass(JSVal value) {
|
||||
TextClass klass;
|
||||
|
||||
if (value.HasMember("enabled")) {
|
||||
klass.enabled = parseBoolFunction(value["enabled"]);
|
||||
}
|
||||
|
||||
if (value.HasMember("color")) {
|
||||
klass.color = parseColor(value["color"]);
|
||||
}
|
||||
|
||||
if (value.HasMember("stroke")) {
|
||||
klass.halo = parseColor(value["stroke"]);
|
||||
}
|
||||
|
||||
if (value.HasMember("strokeWidth")) {
|
||||
klass.haloRadius = parseFloatFunction(value["strokeWidth"]);
|
||||
}
|
||||
|
||||
if (value.HasMember("size")) {
|
||||
klass.size = parseFloatFunction(value["size"]);
|
||||
}
|
||||
|
||||
if (value.HasMember("rotate")) {
|
||||
klass.rotate = parseFloatFunction(value["rotate"]);
|
||||
}
|
||||
|
||||
if (value.HasMember("alwaysVisible")) {
|
||||
klass.alwaysVisible = parseBoolFunction(value["alwaysVisible"]);
|
||||
}
|
||||
|
||||
return klass;
|
||||
}
|
||||
|
||||
BackgroundClass StyleParser::parseBackgroundClass(JSVal value) {
|
||||
BackgroundClass klass;
|
||||
|
||||
|
||||
@@ -26,3 +26,12 @@ llmr::Value llmr::parseValue(pbf data) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string llmr::toString(const llmr::Value& value) {
|
||||
if (value.is<std::string>()) return value.get<std::string>();
|
||||
else if (value.is<bool>()) return value.get<bool>() ? "true" : "false";
|
||||
else if (value.is<int64_t>()) return std::to_string(value.get<int64_t>());
|
||||
else if (value.is<uint64_t>()) return std::to_string(value.get<uint64_t>());
|
||||
else if (value.is<double>()) return std::to_string(value.get<double>());
|
||||
else return "null";
|
||||
}
|
||||
|
||||
321
src/text/collision.cpp
Normal file
321
src/text/collision.cpp
Normal file
@@ -0,0 +1,321 @@
|
||||
#include <llmr/text/collision.hpp>
|
||||
#include <llmr/text/rotation_range.hpp>
|
||||
#include <llmr/util/math.hpp>
|
||||
|
||||
#include <boost/geometry.hpp>
|
||||
#include <boost/geometry/geometries/point.hpp>
|
||||
#include <boost/geometry/geometries/box.hpp>
|
||||
#include <boost/geometry/index/rtree.hpp>
|
||||
|
||||
namespace llmr {
|
||||
namespace bg = boost::geometry;
|
||||
namespace bgm = bg::model;
|
||||
namespace bgi = bg::index;
|
||||
typedef bgm::point<float, 2, bg::cs::cartesian> Point;
|
||||
typedef bgm::box<Point> Box;
|
||||
typedef std::pair<Box, PlacementBox> PlacementValue;
|
||||
typedef bgi::rtree<PlacementValue, bgi::quadratic<16>> Tree;
|
||||
}
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
Collision::~Collision() {
|
||||
delete ((Tree *)tree);
|
||||
}
|
||||
|
||||
Collision::Collision() : tree(new Tree()) {
|
||||
const float m = 4096;
|
||||
|
||||
// Hack to prevent cross-tile labels
|
||||
insert(
|
||||
{{GlyphBox{
|
||||
CollisionRect{CollisionPoint{0, 0}, CollisionPoint{0, m * 8}},
|
||||
CollisionRect{CollisionPoint{0, 0}, CollisionPoint{0, m * 8}}, 0},
|
||||
GlyphBox{
|
||||
CollisionRect{CollisionPoint{0, 0}, CollisionPoint{m * 8, 0}},
|
||||
CollisionRect{CollisionPoint{0, 0}, CollisionPoint{m * 8, 0}},
|
||||
0}}},
|
||||
CollisionAnchor(0, 0), 1, {{M_PI * 2, 0}}, false, 2);
|
||||
|
||||
insert({{GlyphBox{
|
||||
CollisionRect{CollisionPoint{-m * 8, 0}, CollisionPoint{0, 0}},
|
||||
CollisionRect{CollisionPoint{-m * 8, 0}, CollisionPoint{0, 0}},
|
||||
0},
|
||||
GlyphBox{
|
||||
CollisionRect{CollisionPoint{0, -m * 8}, CollisionPoint{0, 0}},
|
||||
CollisionRect{CollisionPoint{0, -m * 8}, CollisionPoint{0, 0}},
|
||||
0}}},
|
||||
CollisionAnchor{m, m}, 1, {{M_PI * 2, 0}}, false, 2);
|
||||
}
|
||||
|
||||
GlyphBox getMergedGlyphs(const PlacedGlyphs &placed_glyphs, bool horizontal,
|
||||
const CollisionAnchor &anchor) {
|
||||
GlyphBox mergedGlyphs;
|
||||
const float inf = std::numeric_limits<float>::infinity();
|
||||
mergedGlyphs.box = CollisionRect{{inf, inf}, {-inf, -inf}};
|
||||
mergedGlyphs.rotate = horizontal;
|
||||
mergedGlyphs.anchor = anchor;
|
||||
|
||||
CollisionRect &box = mergedGlyphs.box;
|
||||
for (const PlacedGlyph &placed_glyph : placed_glyphs) {
|
||||
const CollisionRect &gbox = placed_glyph.glyphBox.box;
|
||||
box.tl.x = util::min(box.tl.x, gbox.tl.x);
|
||||
box.tl.y = util::min(box.tl.y, gbox.tl.y);
|
||||
box.br.x = util::max(box.br.x, gbox.br.x);
|
||||
box.br.y = util::max(box.br.y, gbox.br.y);
|
||||
mergedGlyphs.minScale =
|
||||
util::max(mergedGlyphs.minScale, placed_glyph.glyphBox.minScale);
|
||||
}
|
||||
|
||||
return mergedGlyphs;
|
||||
}
|
||||
|
||||
PlacementProperty Collision::place(const PlacedGlyphs &placed_glyphs,
|
||||
const CollisionAnchor &anchor,
|
||||
float minPlacementScale,
|
||||
float maxPlacementScale, float padding,
|
||||
bool horizontal, bool alwaysVisible) {
|
||||
|
||||
float minScale = std::numeric_limits<float>::infinity();
|
||||
for (const PlacedGlyph &placed_glyph : placed_glyphs) {
|
||||
minScale = util::min(minScale, placed_glyph.glyphBox.minScale);
|
||||
}
|
||||
minPlacementScale = util::max(minPlacementScale, minScale);
|
||||
|
||||
// Collision checks between rotating and fixed labels are
|
||||
// relatively expensive, so we use one box per label, not per glyph
|
||||
// for horizontal labels.
|
||||
GlyphBoxes glyphs;
|
||||
if (horizontal) {
|
||||
glyphs.push_back(getMergedGlyphs(placed_glyphs, horizontal, anchor));
|
||||
} else {
|
||||
for (const PlacedGlyph &placed_glyph : placed_glyphs) {
|
||||
glyphs.push_back(placed_glyph.glyphBox);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate bboxes for all the glyphs
|
||||
for (GlyphBox &glyph : glyphs) {
|
||||
if (horizontal) {
|
||||
const CollisionRect &box = glyph.box;
|
||||
float x12 = box.tl.x * box.tl.x, y12 = box.tl.y * box.tl.y,
|
||||
x22 = box.br.x * box.br.x, y22 = box.br.y * box.br.y,
|
||||
diag = sqrt(
|
||||
util::max(x12 + y12, x12 + y22, x22 + y12, x22 + y22));
|
||||
|
||||
glyph.bbox = CollisionRect{{-diag, -diag}, {diag, diag}};
|
||||
} else {
|
||||
glyph.bbox = glyph.box;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the minimum scale the entire label can be shown without
|
||||
// collisions
|
||||
float scale = alwaysVisible ? minPlacementScale
|
||||
: getPlacementScale(glyphs, minPlacementScale,
|
||||
maxPlacementScale, padding);
|
||||
|
||||
// Return if the label can never be placed without collision
|
||||
if (scale < 0 || scale == std::numeric_limits<float>::infinity()) {
|
||||
return PlacementProperty{};
|
||||
}
|
||||
|
||||
// Calculate the range it is safe to rotate all glyphs
|
||||
PlacementRange rotationRange = getPlacementRange(glyphs, scale);
|
||||
insert(glyphs, anchor, scale, rotationRange, horizontal, padding);
|
||||
|
||||
float zoom = log(scale) / log(2);
|
||||
|
||||
return PlacementProperty{zoom, rotationRange};
|
||||
}
|
||||
|
||||
float Collision::getPlacementScale(const GlyphBoxes &glyphs,
|
||||
float minPlacementScale,
|
||||
float maxPlacementScale, float pad) {
|
||||
|
||||
for (const GlyphBox &glyph : glyphs) {
|
||||
const CollisionRect &bbox = glyph.bbox;
|
||||
const CollisionRect &box = glyph.box;
|
||||
const CollisionAnchor &anchor = glyph.anchor;
|
||||
|
||||
if (anchor.x < 0 || anchor.x > 4096 || anchor.y < 0 ||
|
||||
anchor.y > 4096) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
float minScale = fmax(minPlacementScale, glyph.minScale);
|
||||
float maxScale = glyph.maxScale;
|
||||
|
||||
if (minScale >= maxScale) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Compute the scaled bounding box of the unrotated glyph
|
||||
float minPlacedX = anchor.x + bbox.tl.x / minScale;
|
||||
float minPlacedY = anchor.y + bbox.tl.y / minScale;
|
||||
float maxPlacedX = anchor.x + bbox.br.x / minScale;
|
||||
float maxPlacedY = anchor.y + bbox.br.y / minScale;
|
||||
|
||||
Box query_box{Point{minPlacedX, minPlacedY},
|
||||
Point{maxPlacedX, maxPlacedY}};
|
||||
|
||||
std::vector<PlacementValue> blocking;
|
||||
((Tree *)tree)
|
||||
->query(bgi::intersects(query_box), std::back_inserter(blocking));
|
||||
|
||||
if (blocking.size()) {
|
||||
const CollisionAnchor &na = anchor; // new anchor
|
||||
const CollisionRect &nb = box; // new box
|
||||
|
||||
for (const PlacementValue &value : blocking) {
|
||||
const PlacementBox &placement = value.second;
|
||||
const CollisionAnchor &oa = placement.anchor; // old anchor
|
||||
const CollisionRect &ob = placement.box; // old box
|
||||
|
||||
// If anchors are identical, we're going to skip the label.
|
||||
// NOTE: this isn't right because there can be glyphs with
|
||||
// the same anchor but differing box offsets.
|
||||
if (na == oa) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// todo: unhardcode the 8 = tileExtent/tileSize
|
||||
float padding = fmax(pad, placement.padding) * 8.0f;
|
||||
|
||||
// Original algorithm:
|
||||
float s1 = (ob.tl.x - nb.br.x - padding) /
|
||||
(na.x - oa.x); // scale at which new box is to the
|
||||
// left of old box
|
||||
float s2 = (ob.br.x - nb.tl.x + padding) /
|
||||
(na.x - oa.x); // scale at which new box is to the
|
||||
// right of old box
|
||||
float s3 = (ob.tl.y - nb.br.y - padding) /
|
||||
(na.y - oa.y); // scale at which new box is to the
|
||||
// top of old box
|
||||
float s4 = (ob.br.y - nb.tl.y + padding) /
|
||||
(na.y - oa.y); // scale at which new box is to the
|
||||
// bottom of old box
|
||||
|
||||
if (isnan(s1) || isnan(s2)) {
|
||||
s1 = s2 = 1;
|
||||
}
|
||||
if (isnan(s3) || isnan(s4)) {
|
||||
s3 = s4 = 1;
|
||||
}
|
||||
|
||||
float collisionFreeScale = fmin(fmax(s1, s2), fmax(s3, s4));
|
||||
|
||||
// Only update label's min scale if the glyph was
|
||||
// restricted by a collision
|
||||
if (collisionFreeScale > minPlacementScale &&
|
||||
collisionFreeScale > minScale &&
|
||||
collisionFreeScale < maxScale &&
|
||||
collisionFreeScale < placement.maxScale) {
|
||||
minPlacementScale = collisionFreeScale;
|
||||
}
|
||||
|
||||
if (minPlacementScale > maxPlacementScale) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return minPlacementScale;
|
||||
}
|
||||
|
||||
PlacementRange Collision::getPlacementRange(const GlyphBoxes &glyphs,
|
||||
float placementScale) {
|
||||
PlacementRange placementRange = {{2.0f * M_PI, 0}};
|
||||
|
||||
for (const GlyphBox &glyph : glyphs) {
|
||||
const CollisionRect &bbox = glyph.bbox;
|
||||
const CollisionAnchor &anchor = glyph.anchor;
|
||||
|
||||
float minPlacedX = anchor.x + bbox.tl.x / placementScale;
|
||||
float minPlacedY = anchor.y + bbox.tl.y / placementScale;
|
||||
float maxPlacedX = anchor.x + bbox.br.x / placementScale;
|
||||
float maxPlacedY = anchor.y + bbox.br.y / placementScale;
|
||||
|
||||
Box query_box{Point{minPlacedX, minPlacedY},
|
||||
Point{maxPlacedX, maxPlacedY}};
|
||||
|
||||
std::vector<PlacementValue> blocking;
|
||||
((Tree *)tree)
|
||||
->query(bgi::intersects(query_box), std::back_inserter(blocking));
|
||||
|
||||
for (const PlacementValue &value : blocking) {
|
||||
const Box &s = value.first;
|
||||
const PlacementBox &b = value.second;
|
||||
|
||||
float x1, x2, y1, y2, intersectX, intersectY;
|
||||
|
||||
// Adjust and compare bboxes to see if the glyphs might intersect
|
||||
if (placementScale > b.placementScale) {
|
||||
x1 = b.anchor.x + b.bbox.tl.x / placementScale;
|
||||
y1 = b.anchor.y + b.bbox.tl.y / placementScale;
|
||||
x2 = b.anchor.x + b.bbox.br.x / placementScale;
|
||||
y2 = b.anchor.y + b.bbox.br.y / placementScale;
|
||||
intersectX = x1 < maxPlacedX && x2 > minPlacedX;
|
||||
intersectY = y1 < maxPlacedY && y2 > minPlacedY;
|
||||
} else {
|
||||
x1 = anchor.x + bbox.tl.x / b.placementScale;
|
||||
y1 = anchor.y + bbox.tl.y / b.placementScale;
|
||||
x2 = anchor.x + bbox.br.x / b.placementScale;
|
||||
y2 = anchor.y + bbox.br.y / b.placementScale;
|
||||
|
||||
intersectX = x1 < s.max_corner().get<0>() &&
|
||||
x2 > s.min_corner().get<0>();
|
||||
intersectY = y1 < s.max_corner().get<1>() &&
|
||||
y2 > s.min_corner().get<1>();
|
||||
}
|
||||
|
||||
// If they can't intersect, skip more expensive rotation calculation
|
||||
if (!(intersectX && intersectY))
|
||||
continue;
|
||||
|
||||
float scale = fmax(placementScale, b.placementScale);
|
||||
// TODO? glyph.box or glyph.bbox?
|
||||
CollisionRange range = rotationRange(glyph, b, scale);
|
||||
|
||||
placementRange[0] = fmin(placementRange[0], range[0]);
|
||||
placementRange[1] = fmax(placementRange[1], range[1]);
|
||||
}
|
||||
}
|
||||
|
||||
return placementRange;
|
||||
};
|
||||
|
||||
void Collision::insert(const GlyphBoxes &glyphs, const CollisionAnchor &anchor,
|
||||
float placementScale,
|
||||
const PlacementRange &placementRange, bool horizontal,
|
||||
float padding) {
|
||||
assert(placementScale != std::numeric_limits<float>::infinity());
|
||||
|
||||
for (const GlyphBox &glyph : glyphs) {
|
||||
const CollisionRect &bbox = glyph.bbox;
|
||||
const CollisionRect &box = glyph.box;
|
||||
|
||||
float minScale = fmax(placementScale, glyph.minScale);
|
||||
|
||||
Box bounds{Point{anchor.x + bbox.tl.x / minScale,
|
||||
anchor.y + bbox.tl.y / minScale},
|
||||
Point{anchor.x + bbox.br.x / minScale,
|
||||
anchor.y + bbox.br.y / minScale}};
|
||||
|
||||
PlacementBox placement;
|
||||
placement.anchor = anchor;
|
||||
placement.box = box;
|
||||
placement.bbox = bbox;
|
||||
placement.rotate = horizontal;
|
||||
placement.placementRange = placementRange;
|
||||
placement.placementScale = minScale;
|
||||
placement.maxScale = glyph.maxScale;
|
||||
placement.padding = padding;
|
||||
|
||||
assert(!isnan(bounds.min_corner().get<0>()) && !isnan(bounds.min_corner().get<1>()));
|
||||
assert(!isnan(bounds.max_corner().get<0>()) && !isnan(bounds.max_corner().get<1>()));
|
||||
|
||||
((Tree *)tree)->insert({bounds, placement});
|
||||
}
|
||||
}
|
||||
326
src/text/placement.cpp
Normal file
326
src/text/placement.cpp
Normal file
@@ -0,0 +1,326 @@
|
||||
#include <llmr/text/placement.hpp>
|
||||
#include <llmr/map/vector_tile.hpp>
|
||||
#include <llmr/geometry/interpolate.hpp>
|
||||
#include <llmr/style/bucket_description.hpp>
|
||||
#include <llmr/renderer/text_bucket.hpp>
|
||||
#include <llmr/util/math.hpp>
|
||||
#include <llmr/util/constants.hpp>
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
const int Placement::tileExtent = 4096;
|
||||
const int Placement::glyphSize =
|
||||
24; // size in pixels of this glyphs in the tile
|
||||
const float Placement::minScale = 0.125; // underscale by 3 zoom levels
|
||||
|
||||
Placement::Placement(int8_t zoom)
|
||||
: zoom(zoom),
|
||||
zOffset(log(256.0 / util::tileSize) / log(2)),
|
||||
|
||||
// Calculate the maximum scale we can go down in our fake-3d rtree so that
|
||||
// placement still makes sense. This is calculated so that the minimum
|
||||
// placement zoom can be at most 25.5 (we use an unsigned integer x10 to
|
||||
// store the minimum zoom).
|
||||
//
|
||||
// We don't want to place labels all the way to 25.5. This lets too many
|
||||
// glyphs be placed, slowing down collision checking. Only place labels if
|
||||
// they will show up within the intended zoom range of the tile.
|
||||
// TODO make this not hardcoded to 3
|
||||
maxPlacementScale(exp(log(2) * util::min((25.5 - zoom), 3.0))) {}
|
||||
|
||||
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");
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "face does not exist\n");
|
||||
}
|
||||
|
||||
return null_glyph;
|
||||
}
|
||||
|
||||
struct GlyphInstance {
|
||||
explicit GlyphInstance(const vec2<float> &anchor) : anchor(anchor) {}
|
||||
explicit GlyphInstance(const vec2<float> &anchor, float offset,
|
||||
float minScale, float maxScale, float angle)
|
||||
: anchor(anchor),
|
||||
offset(offset),
|
||||
minScale(minScale),
|
||||
maxScale(maxScale),
|
||||
angle(angle) {}
|
||||
|
||||
const vec2<float> anchor;
|
||||
const float offset = 0.0f;
|
||||
const float minScale = Placement::minScale;
|
||||
const float maxScale = std::numeric_limits<float>::infinity();
|
||||
const float angle = 0.0f;
|
||||
};
|
||||
|
||||
typedef std::vector<GlyphInstance> GlyphInstances;
|
||||
|
||||
void getSegmentGlyphs(std::back_insert_iterator<GlyphInstances> glyphs,
|
||||
Anchor &anchor, float offset,
|
||||
const std::vector<Coordinate> &line, int segment,
|
||||
int8_t direction, float maxAngleDelta) {
|
||||
const bool upsideDown = direction < 0;
|
||||
|
||||
if (offset < 0)
|
||||
direction *= -1;
|
||||
|
||||
if (direction > 0)
|
||||
segment++;
|
||||
|
||||
vec2<float> newAnchor = anchor;
|
||||
|
||||
if (line.size() <= segment) {
|
||||
return;
|
||||
}
|
||||
vec2<float> end = line[segment];
|
||||
float prevscale = std::numeric_limits<float>::infinity();
|
||||
float prevAngle = 0.0f;
|
||||
|
||||
offset = fabs(offset);
|
||||
|
||||
const float placementScale = anchor.scale;
|
||||
|
||||
while (true) {
|
||||
const float dist = util::dist<float>(newAnchor, end);
|
||||
const float scale = offset / dist;
|
||||
float angle = -atan2(end.x - newAnchor.x, end.y - newAnchor.y) +
|
||||
direction * M_PI / 2.0f;
|
||||
if (upsideDown)
|
||||
angle += M_PI;
|
||||
|
||||
// Don't place around sharp corners
|
||||
float angleDiff = fmod((angle - prevAngle), (2.0f * M_PI));
|
||||
if (prevAngle && angleDiff > maxAngleDelta) {
|
||||
anchor.scale = prevscale;
|
||||
break;
|
||||
}
|
||||
|
||||
glyphs = GlyphInstance{
|
||||
/* anchor */ newAnchor,
|
||||
/* offset */ static_cast<float>(upsideDown ? M_PI : 0.0),
|
||||
/* minScale */ scale,
|
||||
/* maxScale */ prevscale,
|
||||
/* angle */ static_cast<float>(
|
||||
fmod((angle + 2.0 * M_PI), (2.0 * M_PI)))};
|
||||
|
||||
if (scale <= placementScale)
|
||||
break;
|
||||
|
||||
newAnchor = end;
|
||||
|
||||
// skip duplicate nodes
|
||||
while (newAnchor == end) {
|
||||
segment += direction;
|
||||
if (line.size() <= segment) {
|
||||
anchor.scale = scale;
|
||||
return;
|
||||
}
|
||||
end = line[segment];
|
||||
}
|
||||
|
||||
vec2<float> normal = util::normal<float>(newAnchor, end) * dist;
|
||||
newAnchor = newAnchor - normal;
|
||||
|
||||
prevscale = scale;
|
||||
prevAngle = angle;
|
||||
}
|
||||
}
|
||||
|
||||
PlacedGlyphs getGlyphs(Anchor &anchor, float advance, const Shaping &shaping,
|
||||
const IndexedFaces &faces, 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') {
|
||||
// origin.x -= advance / 2;
|
||||
// } else if (alignment == 'right') {
|
||||
// origin.x -= advance;
|
||||
// }
|
||||
|
||||
PlacedGlyphs glyphs;
|
||||
|
||||
const uint32_t buffer = 3;
|
||||
|
||||
for (const GlyphPlacement &placed : shaping) {
|
||||
const Glyph &glyph = getGlyph(placed, faces);
|
||||
if (!glyph) {
|
||||
// This glyph is empty and doesn't have any pixels that we'd need to
|
||||
// show.
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t width = glyph.metrics.width;
|
||||
uint32_t height = glyph.metrics.height;
|
||||
|
||||
width += buffer * 2;
|
||||
height += buffer * 2;
|
||||
|
||||
// Increase to next number divisible by 4, but at least 1.
|
||||
// This is so we can scale down the texture coordinates and pack them
|
||||
// into 2 bytes rather than 4 bytes.
|
||||
width += (4 - width % 4);
|
||||
height += (4 - height % 4);
|
||||
|
||||
float x =
|
||||
(origin.x + placed.x + glyph.metrics.left - buffer + width / 2) *
|
||||
fontScale;
|
||||
|
||||
GlyphInstances glyphInstances;
|
||||
if (anchor.segment >= 0) {
|
||||
getSegmentGlyphs(std::back_inserter(glyphInstances), anchor, x,
|
||||
line, anchor.segment, 1, maxAngleDelta);
|
||||
getSegmentGlyphs(std::back_inserter(glyphInstances), anchor, x,
|
||||
line, anchor.segment, -1, maxAngleDelta);
|
||||
|
||||
} else {
|
||||
|
||||
glyphInstances.emplace_back(GlyphInstance{anchor});
|
||||
}
|
||||
|
||||
const float x1 = origin.x + placed.x + glyph.metrics.left - buffer;
|
||||
const float y1 = origin.y + placed.y - glyph.metrics.top - buffer;
|
||||
const float x2 = x1 + width;
|
||||
const float y2 = y1 + height;
|
||||
|
||||
const vec2<float> otl{x1, y1};
|
||||
const vec2<float> otr{x2, y1};
|
||||
const vec2<float> obl{x1, y2};
|
||||
const vec2<float> obr{x2, y2};
|
||||
|
||||
const CollisionRect obox{fontScale *x1, fontScale *y1,
|
||||
fontScale *x2, fontScale *y2};
|
||||
|
||||
for (const GlyphInstance &instance : glyphInstances) {
|
||||
vec2<float> tl = otl;
|
||||
vec2<float> tr = otr;
|
||||
vec2<float> bl = obl;
|
||||
vec2<float> br = obr;
|
||||
|
||||
CollisionRect box = obox;
|
||||
|
||||
// Clamp to -90/+90 degrees
|
||||
const float angle = instance.angle + rotate;
|
||||
|
||||
if (angle) {
|
||||
// Compute the transformation matrix.
|
||||
float angle_sin = sin(angle);
|
||||
float angle_cos = cos(angle);
|
||||
std::array<float, 4> matrix = {
|
||||
{angle_cos, -angle_sin, angle_sin, angle_cos}};
|
||||
|
||||
tl = tl.matMul(matrix);
|
||||
tr = tr.matMul(matrix);
|
||||
bl = bl.matMul(matrix);
|
||||
br = br.matMul(matrix);
|
||||
|
||||
// Calculate the rotated glyph's bounding box offsets from the
|
||||
// anchor point.
|
||||
box = CollisionRect{
|
||||
fontScale * util::min(tl.x, tr.x, bl.x, br.x),
|
||||
fontScale * util::min(tl.y, tr.y, bl.y, br.y),
|
||||
fontScale * util::max(tl.x, tr.x, bl.x, br.x),
|
||||
fontScale * util::max(tl.y, tr.y, bl.y, br.y)};
|
||||
}
|
||||
|
||||
// Remember the glyph for later insertion.
|
||||
glyphs.emplace_back(PlacedGlyph{
|
||||
tl, tr, bl, br, glyph.rect, width, height,
|
||||
static_cast<float>(
|
||||
fmod((anchor.angle + rotate + instance.offset + 2 * M_PI),
|
||||
(2 * M_PI))),
|
||||
GlyphBox{box, instance.minScale, instance.maxScale,
|
||||
instance.anchor, horizontal}});
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent label from extending past the end of the line
|
||||
for (PlacedGlyph &g : glyphs) {
|
||||
g.glyphBox.minScale = util::max(g.glyphBox.minScale, anchor.scale);
|
||||
}
|
||||
|
||||
return glyphs;
|
||||
}
|
||||
|
||||
void Placement::addFeature(TextBucket& bucket,
|
||||
const std::vector<Coordinate> &line,
|
||||
const BucketGeometryDescription &info,
|
||||
const IndexedFaces &faces,
|
||||
const Shaping &shaping) {
|
||||
|
||||
const bool horizontal = info.path == TextPathType::Horizontal;
|
||||
const float padding = info.padding;
|
||||
const float maxAngleDelta = info.maxAngleDelta;
|
||||
const float textMinDistance = info.textMinDistance;
|
||||
const float rotate = info.rotate;
|
||||
const float fontScale =
|
||||
(tileExtent / util::tileSize) / (glyphSize / info.font_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{
|
||||
static_cast<float>(line[0].x), static_cast<float>(line[0].y),
|
||||
0, minScale}}};
|
||||
|
||||
} else {
|
||||
// Line labels
|
||||
anchors = interpolate(line, textMinDistance, minScale);
|
||||
|
||||
// Sort anchors by segment so that we can start placement with
|
||||
// the anchors that can be shown at the lowest zoom levels.
|
||||
std::sort(anchors.begin(), anchors.end(), byScale);
|
||||
}
|
||||
|
||||
for (Anchor anchor : anchors) {
|
||||
PlacedGlyphs glyphs =
|
||||
getGlyphs(anchor, advance, shaping, faces, fontScale, horizontal,
|
||||
line, maxAngleDelta, rotate);
|
||||
PlacementProperty place =
|
||||
collision.place(glyphs, anchor, anchor.scale, maxPlacementScale,
|
||||
padding, horizontal, info.alwaysVisible);
|
||||
if (place) {
|
||||
bucket.addGlyphs(glyphs, place.zoom, place.rotationRange,
|
||||
zoom - zOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
260
src/text/rotation_range.cpp
Normal file
260
src/text/rotation_range.cpp
Normal file
@@ -0,0 +1,260 @@
|
||||
#include <llmr/text/rotation_range.hpp>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace llmr {
|
||||
|
||||
/*
|
||||
* Combine an array of collision ranges to form a continuous
|
||||
* range that includes 0. Collisions within the ignoreRange are ignored
|
||||
*/
|
||||
CollisionRange mergeCollisions(const CollisionList &collisions,
|
||||
PlacementRange ignoreRange) {
|
||||
// find continuous interval including 0 that doesn't have any collisions
|
||||
float min = 2.0f * M_PI;
|
||||
float max = 0.0f;
|
||||
|
||||
for (CollisionRange collision : collisions) {
|
||||
bool entryOutside =
|
||||
ignoreRange[0] <= collision[0] && collision[0] <= ignoreRange[1];
|
||||
bool exitOutside =
|
||||
ignoreRange[0] <= collision[1] && collision[1] <= ignoreRange[1];
|
||||
|
||||
if (entryOutside && exitOutside) {
|
||||
// no collision, since blocker is out of range
|
||||
} else if (entryOutside) {
|
||||
min = util::min(min, ignoreRange[1]);
|
||||
max = util::max(max, collision[1]);
|
||||
} else if (exitOutside) {
|
||||
min = util::min(min, collision[0]);
|
||||
max = util::max(max, ignoreRange[0]);
|
||||
} else {
|
||||
min = util::min(min, collision[0]);
|
||||
max = util::max(max, collision[1]);
|
||||
}
|
||||
}
|
||||
|
||||
return {{min, max}};
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate collision ranges for two rotating boxes.
|
||||
*/
|
||||
CollisionList
|
||||
rotatingRotatingCollisions(const CollisionRect &a, const CollisionRect &b,
|
||||
const CollisionAnchor &anchorToAnchor) {
|
||||
const float d = util::mag<float>(anchorToAnchor);
|
||||
const float d_sq = d * d;
|
||||
|
||||
const CollisionAnchor horizontal = {1, 0};
|
||||
const float angleBetweenAnchors =
|
||||
util::angle_between<float>(anchorToAnchor, horizontal);
|
||||
|
||||
// Calculate angles at which collisions may occur
|
||||
const std::array<float, 8> c = {{
|
||||
// top/bottom
|
||||
/*[0]*/ static_cast<float>(asin((float)(a.br.y - b.tl.y) / d)),
|
||||
/*[1]*/ static_cast<float>(asin((float)(a.br.y - b.tl.y) / d) + M_PI),
|
||||
/*[2]*/ static_cast<float>(2 * M_PI -
|
||||
asin((float)(-a.tl.y + b.br.y) / d)),
|
||||
/*[3]*/ static_cast<float>(M_PI - asin((float)(-a.tl.y + b.br.y) / d)),
|
||||
|
||||
// left/right
|
||||
/*[4]*/ static_cast<float>(2 * M_PI -
|
||||
acos((float)(a.br.x - b.tl.x) / d)),
|
||||
/*[5]*/ static_cast<float>(acos((float)(a.br.x - b.tl.x) / d)),
|
||||
/*[6]*/ static_cast<float>(M_PI - acos((float)(-a.tl.x + b.br.x) / d)),
|
||||
/*[7]*/ static_cast<float>(M_PI +
|
||||
acos((float)(-a.tl.x + b.br.x) / d))}};
|
||||
|
||||
const float rl = a.br.x - b.tl.x;
|
||||
const float lr = -a.tl.x + b.br.x;
|
||||
const float tb = a.br.y - b.tl.y;
|
||||
const float bt = -a.tl.y + b.br.y;
|
||||
|
||||
// Calculate the distance squared of the diagonal which will be used
|
||||
// to check if the boxes are close enough for collisions to occur at each
|
||||
// angle
|
||||
// todo, triple check these
|
||||
const std::array<float, 8> e = {{
|
||||
// top/bottom
|
||||
/*[0]*/ static_cast<float>(rl * rl + tb * tb),
|
||||
/*[1]*/ static_cast<float>(lr * lr + tb * tb),
|
||||
/*[2]*/ static_cast<float>(rl * rl + bt * bt),
|
||||
/*[3]*/ static_cast<float>(lr * lr + bt * bt),
|
||||
|
||||
// left/right
|
||||
/*[4]*/ static_cast<float>(rl * rl + tb * tb),
|
||||
/*[5]*/ static_cast<float>(rl * rl + bt * bt),
|
||||
/*[6]*/ static_cast<float>(lr * lr + bt * bt),
|
||||
/*[7]*/ static_cast<float>(lr * lr + tb * tb)}};
|
||||
|
||||
std::vector<float> f;
|
||||
for (size_t i = 0; i < c.size(); i++) {
|
||||
// Check if they are close enough to collide
|
||||
if (!isnan(c[i]) && d_sq <= e[i]) {
|
||||
// So far, angles have been calulated as relative to the vector
|
||||
// between anchors.
|
||||
// Convert the angles to angles from north.
|
||||
f.push_back(
|
||||
fmod((c[i] + angleBetweenAnchors + 2 * M_PI), (2 * M_PI)));
|
||||
}
|
||||
}
|
||||
|
||||
assert(f.size() % 2 == 0);
|
||||
|
||||
// Group the collision angles by two
|
||||
// each group represents a range where the two boxes collide
|
||||
CollisionList collisions;
|
||||
std::sort(f.begin(), f.end());
|
||||
for (size_t k = 0; k < f.size(); k += 2) {
|
||||
collisions.push_back({{f[k], f[k + 1]}});
|
||||
}
|
||||
|
||||
return collisions;
|
||||
}
|
||||
|
||||
double getAngle(const CollisionPoint &p1, const CollisionPoint &p2,
|
||||
CollisionAngle d, const CollisionPoint &corner) {
|
||||
return fmod(util::angle_between(util::interp(p1.x, p2.x, d),
|
||||
util::interp(p1.y, p2.y, d), corner.x,
|
||||
corner.y) +
|
||||
2 * M_PI,
|
||||
2 * M_PI);
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the intersection points of a circle and a line segment;
|
||||
*/
|
||||
void circleEdgeCollisions(std::back_insert_iterator<CollisionAngles> angles,
|
||||
const CollisionPoint &corner, float radius,
|
||||
const CollisionPoint &p1, const CollisionPoint &p2) {
|
||||
const CollisionPoint::Type edgeX = p2.x - p1.x;
|
||||
const CollisionPoint::Type edgeY = p2.y - p1.y;
|
||||
|
||||
const CollisionAngle a = edgeX * edgeX + edgeY * edgeY;
|
||||
const CollisionAngle b = (edgeX * p1.x + edgeY * p1.y) * 2;
|
||||
const CollisionAngle c = p1.x * p1.x + p1.y * p1.y - radius * radius;
|
||||
|
||||
const CollisionAngle discriminant = b * b - 4 * a * c;
|
||||
|
||||
// a collision exists only if line intersects circle at two points
|
||||
if (discriminant > 0) {
|
||||
CollisionAngle x1 = (-b - sqrt(discriminant)) / (2 * a);
|
||||
CollisionAngle x2 = (-b + sqrt(discriminant)) / (2 * a);
|
||||
|
||||
// only add points if within line segment
|
||||
// hack to handle floating point representations of 0 and 1
|
||||
if (0 < x1 && x1 < 1) {
|
||||
angles = getAngle(p1, p2, x1, corner);
|
||||
}
|
||||
|
||||
if (0 < x2 && x2 < 1) {
|
||||
angles = getAngle(p1, p2, x2, corner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate the ranges for which the corner,
|
||||
* rotatated around the anchor, is within the box;
|
||||
*/
|
||||
void cornerBoxCollisions(std::back_insert_iterator<CollisionList> collisions,
|
||||
const CollisionPoint &corner,
|
||||
const CollisionCorners &boxCorners, bool flip) {
|
||||
float radius = util::mag<float>(corner);
|
||||
|
||||
CollisionAngles angles;
|
||||
|
||||
// Calculate the points at which the corners intersect with the edges
|
||||
for (size_t i = 0, j = 3; i < 4; j = i++) {
|
||||
circleEdgeCollisions(std::back_inserter(angles), corner, radius,
|
||||
boxCorners[j], boxCorners[i]);
|
||||
}
|
||||
|
||||
if (angles.size() % 2 != 0) {
|
||||
// TODO fix
|
||||
// This could get hit when a point intersects very close to a corner
|
||||
// and floating point issues cause only one of the entry or exit to be
|
||||
// counted
|
||||
throw std::runtime_error("expecting an even number of intersections");
|
||||
}
|
||||
|
||||
std::sort(angles.begin(), angles.end());
|
||||
|
||||
// Group by pairs, where each represents a range where a collision occurs
|
||||
for (size_t k = 0; k < angles.size(); k += 2) {
|
||||
CollisionRange range = {{angles[k], angles[k + 1]}};
|
||||
if (flip) {
|
||||
range = util::flip(range);
|
||||
}
|
||||
collisions = range;
|
||||
}
|
||||
}
|
||||
|
||||
CollisionCorners getCorners(const CollisionRect &a) {
|
||||
return {{{a.tl.x, a.tl.y},
|
||||
{a.tl.x, a.br.y},
|
||||
{a.br.x, a.br.y},
|
||||
{a.br.x, a.tl.y}}};
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate collision ranges for a rotating box and a fixed box;
|
||||
*/
|
||||
CollisionList rotatingFixedCollisions(const CollisionRect &rotating,
|
||||
const CollisionRect &fixed) {
|
||||
const auto cornersR = getCorners(rotating);
|
||||
const auto cornersF = getCorners(fixed);
|
||||
|
||||
// A collision occurs when, and only at least one corner from one of the
|
||||
// boxes is within the other box. Calculate these ranges for each corner.
|
||||
|
||||
CollisionList collisions;
|
||||
|
||||
for (size_t i = 0; i < 4; i++) {
|
||||
cornerBoxCollisions(std::back_inserter(collisions), cornersR[i],
|
||||
cornersF);
|
||||
cornerBoxCollisions(std::back_inserter(collisions), cornersF[i],
|
||||
cornersR, true);
|
||||
}
|
||||
|
||||
return collisions;
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate the range a box conflicts with a second box
|
||||
*/
|
||||
CollisionRange rotationRange(const GlyphBox &inserting,
|
||||
const PlacementBox &blocker, float scale) {
|
||||
CollisionList collisions;
|
||||
|
||||
const GlyphBox &a = inserting;
|
||||
const PlacementBox &b = blocker;
|
||||
|
||||
// Instead of scaling the boxes, we move the anchors
|
||||
CollisionAnchor relativeAnchor{
|
||||
static_cast<float>((b.anchor.x - a.anchor.x) * scale),
|
||||
static_cast<float>((b.anchor.y - a.anchor.y) * scale)};
|
||||
|
||||
// Generate a list of collision interval
|
||||
if (a.rotate && b.rotate) {
|
||||
collisions = rotatingRotatingCollisions(a.box, b.box, relativeAnchor);
|
||||
} else if (a.rotate) {
|
||||
const CollisionRect box {
|
||||
b.box.tl.x + relativeAnchor.x, b.box.tl.y + relativeAnchor.y,
|
||||
b.box.br.x + relativeAnchor.x, b.box.br.y + relativeAnchor.y};
|
||||
collisions = rotatingFixedCollisions(a.box, box);
|
||||
} else if (b.rotate) {
|
||||
const CollisionRect box {
|
||||
a.box.tl.x - relativeAnchor.x, a.box.tl.y - relativeAnchor.y,
|
||||
a.box.br.x - relativeAnchor.x, a.box.br.y - relativeAnchor.y};
|
||||
collisions = rotatingFixedCollisions(b.box, box);
|
||||
} else {
|
||||
// collisions remains empty
|
||||
}
|
||||
|
||||
// Find and return the continous are around 0 where there are no collisions
|
||||
return mergeCollisions(collisions, blocker.placementRange);
|
||||
}
|
||||
}
|
||||
6
test/main.cpp
Normal file
6
test/main.cpp
Normal file
@@ -0,0 +1,6 @@
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
GTEST_API_ int main(int argc, char *argv[]) {
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
137
test/rotation_range.cpp
Normal file
137
test/rotation_range.cpp
Normal file
@@ -0,0 +1,137 @@
|
||||
#include <iostream>
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include <llmr/text/rotation_range.hpp>
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
double deg(double x) { return x / M_PI * 180.0; }
|
||||
|
||||
TEST(RotationRange, mergeCollisions) {
|
||||
// merge overlapping ranges
|
||||
EXPECT_EQ(
|
||||
CollisionRange({{1.0 / 8.0 * M_PI, 6.0 / 8.0 * M_PI}}),
|
||||
mergeCollisions(
|
||||
CollisionList(
|
||||
{{CollisionRange({{3.0 / 8.0 * M_PI, 5.0 / 8.0 * M_PI}}),
|
||||
CollisionRange({{4.0 / 8.0 * M_PI, 6.0 / 8.0 * M_PI}}),
|
||||
CollisionRange({{1.0 / 8.0 * M_PI, 2.0 / 8.0 * M_PI}})}}),
|
||||
PlacementRange({{2.0 * M_PI, 0.0}})));
|
||||
|
||||
// ignore collision within ignore range
|
||||
EXPECT_EQ(
|
||||
CollisionRange({{5.0 / 4.0 * M_PI, 6.0 / 4.0 * M_PI}}),
|
||||
mergeCollisions(CollisionList({{CollisionRange({{M_PI / 2, M_PI}}),
|
||||
CollisionRange({{5.0 / 4.0 * M_PI,
|
||||
6.0 / 4.0 * M_PI}})}}),
|
||||
PlacementRange({{0, M_PI}})));
|
||||
|
||||
// crop collision that ends within ignore range
|
||||
EXPECT_EQ(CollisionRange({{1.0 / 2.0 * M_PI, 3.0 / 4.0 * M_PI}}),
|
||||
mergeCollisions(
|
||||
CollisionList({{CollisionRange({{1.0 / 2.0 * M_PI, M_PI}})}}),
|
||||
PlacementRange({{3.0 / 4.0 * M_PI, 3.0 / 2.0 * M_PI}})));
|
||||
|
||||
// crop collision that starts within ignore range
|
||||
EXPECT_EQ(CollisionRange({{3.0 / 4.0 * M_PI, M_PI}}),
|
||||
mergeCollisions(
|
||||
CollisionList({{CollisionRange({{1.0 / 2.0 * M_PI, M_PI}})}}),
|
||||
PlacementRange({{1.0 / 4.0 * M_PI, 3.0 / 4.0 * M_PI}})));
|
||||
}
|
||||
|
||||
TEST(RotationRange, rotatingFixedCollisions) {
|
||||
// returns collisions
|
||||
|
||||
auto collisions = rotatingFixedCollisions(
|
||||
CollisionRect{CollisionPoint{-1, 0}, CollisionPoint{0, 1}},
|
||||
CollisionRect{CollisionPoint{1.4142, -10}, CollisionPoint{10, 10}});
|
||||
|
||||
EXPECT_EQ(1, collisions.size());
|
||||
EXPECT_EQ(135, round(deg(collisions.front()[0])));
|
||||
EXPECT_EQ(135, round(deg(collisions.front()[1])));
|
||||
}
|
||||
|
||||
TEST(RotationRange, cornerBoxCollisions) {
|
||||
{
|
||||
// returns intersections in sorted order as angles 0..2PI
|
||||
CollisionList list;
|
||||
cornerBoxCollisions(
|
||||
std::back_inserter(list), CollisionPoint{1, 1},
|
||||
CollisionCorners{{CollisionPoint{0, 0}, CollisionPoint{0, 10},
|
||||
CollisionPoint{10, 10}, CollisionPoint{10, 0}}});
|
||||
EXPECT_EQ(1, list.size());
|
||||
EXPECT_EQ((CollisionRange{{M_PI / 4.0, M_PI * 7.0 / 4.0}}), list[0]);
|
||||
}
|
||||
|
||||
{
|
||||
// handles no intersections
|
||||
CollisionList list;
|
||||
cornerBoxCollisions(
|
||||
std::back_inserter(list), CollisionPoint{200, 200},
|
||||
CollisionCorners{{CollisionPoint{1, 1}, CollisionPoint{1, 10},
|
||||
CollisionPoint{10, 10}, CollisionPoint{10, 1}}});
|
||||
EXPECT_EQ(0, list.size());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RotationRange, circleEdgeCollisions) {
|
||||
{
|
||||
// handles two intersection points
|
||||
CollisionAngles list;
|
||||
circleEdgeCollisions(std::back_inserter(list), CollisionPoint{0, 1}, 1,
|
||||
CollisionPoint{-10, 0}, CollisionPoint{10, 0});
|
||||
std::sort(list.begin(), list.end());
|
||||
EXPECT_EQ(2, list.size());
|
||||
EXPECT_EQ(static_cast<float>(M_PI / 2), list[0]);
|
||||
EXPECT_EQ(static_cast<float>(M_PI * 3.0 / 2.0), list[1]);
|
||||
}
|
||||
|
||||
{
|
||||
// handles one intersection point
|
||||
CollisionAngles list;
|
||||
circleEdgeCollisions(std::back_inserter(list), CollisionPoint{0, 1}, 1,
|
||||
CollisionPoint{0, 0}, CollisionPoint{10, 0});
|
||||
EXPECT_EQ(1, list.size());
|
||||
EXPECT_EQ(static_cast<float>(M_PI / 2), list[0]);
|
||||
}
|
||||
|
||||
{
|
||||
// only returns intersections within the line segment
|
||||
CollisionAngles list;
|
||||
circleEdgeCollisions(std::back_inserter(list), CollisionPoint{0, 1}, 1,
|
||||
CollisionPoint{3, 1}, CollisionPoint{30, 1});
|
||||
EXPECT_EQ(0, list.size());
|
||||
}
|
||||
|
||||
{
|
||||
// doesnt count tangetial intersections as collisions
|
||||
CollisionAngles list;
|
||||
circleEdgeCollisions(std::back_inserter(list), CollisionPoint{0, 1}, 1,
|
||||
CollisionPoint{-10, 1}, CollisionPoint{10, 1});
|
||||
EXPECT_EQ(0, list.size());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RotationRange, rotatingRotatingCollisions) {
|
||||
{
|
||||
// basically works
|
||||
CollisionList c = rotatingRotatingCollisions(
|
||||
CollisionRect{{-1, 0}, {1, 0}}, CollisionRect{{-1, 0}, {1, 0}},
|
||||
CollisionAnchor{1, 1});
|
||||
|
||||
EXPECT_EQ(2, c.size());
|
||||
EXPECT_EQ(135, round(deg(c[0][0])));
|
||||
EXPECT_EQ(135, round(deg(c[0][1])));
|
||||
EXPECT_EQ(315, round(deg(c[1][0])));
|
||||
EXPECT_EQ(315, round(deg(c[1][1])));
|
||||
}
|
||||
|
||||
{
|
||||
// checks if the two boxes are close enough to collide at that angle
|
||||
CollisionList c = rotatingRotatingCollisions(
|
||||
CollisionRect{{-1, 0}, {1, 0}}, CollisionRect{{-1, 0}, {1, 0}},
|
||||
CollisionAnchor{2, 2});
|
||||
|
||||
EXPECT_EQ(0, c.size());
|
||||
}
|
||||
}
|
||||
21
test/test.gyp
Normal file
21
test/test.gyp
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
'includes': [
|
||||
'../common.gypi',
|
||||
'../config.gypi'
|
||||
],
|
||||
'targets': [
|
||||
{
|
||||
"target_name": "rotation_range",
|
||||
"product_name": "test_rotation_range",
|
||||
"type": "executable",
|
||||
"sources": [
|
||||
"./main.cpp",
|
||||
"./rotation_range.cpp",
|
||||
],
|
||||
"dependencies": [
|
||||
"../deps/gtest/gtest.gyp:gtest",
|
||||
"../llmr.gyp:llmr-x86"
|
||||
]
|
||||
},
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user