Merge branch 'master' of github.com:mapbox/llmr-native into clear-caches
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
namespace llmr {
|
||||
|
||||
@@ -68,8 +69,8 @@ private:
|
||||
const int32_t min_zoom;
|
||||
const int32_t max_zoom;
|
||||
|
||||
std::forward_list<Tile> tiles;
|
||||
std::forward_list<std::weak_ptr<TileData>> tile_data;
|
||||
std::map<Tile::ID, std::unique_ptr<Tile>> tiles;
|
||||
std::map<Tile::ID, std::weak_ptr<TileData>> tile_data;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -127,6 +127,7 @@ struct TextClass {
|
||||
FunctionProperty size = 12.0f;
|
||||
FunctionProperty rotate = 0.0f;
|
||||
FunctionProperty alwaysVisible = false;
|
||||
FunctionProperty opacity = 1;
|
||||
};
|
||||
|
||||
struct TextProperties {
|
||||
@@ -138,6 +139,7 @@ struct TextProperties {
|
||||
float size = 12.0f;
|
||||
float rotate = 0.0f;
|
||||
bool alwaysVisible = false;
|
||||
float opacity = 1.0;
|
||||
};
|
||||
|
||||
struct BackgroundClass {
|
||||
|
||||
@@ -11,6 +11,23 @@ template<typename T, typename ...Args>
|
||||
return ::std::unique_ptr<T>(new T(::std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
|
||||
template <typename Container, typename ForwardIterator, typename Predicate>
|
||||
void erase_if(Container &container, ForwardIterator it, const ForwardIterator end, Predicate pred) {
|
||||
while (it != end) {
|
||||
if (pred(*it)) {
|
||||
container.erase(it++);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Container, typename Predicate>
|
||||
void erase_if(Container &container, Predicate pred) {
|
||||
erase_if(container, container.begin(), container.end(), pred);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -21,4 +21,4 @@
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
@end
|
||||
|
||||
@@ -36,6 +36,19 @@ MBXViewController *viewController = nullptr;
|
||||
|
||||
#pragma mark - Setup
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(saveState) name:UIApplicationDidEnterBackgroundNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(restoreState) name:UIApplicationWillEnterForegroundNotification object:nil];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)loadView
|
||||
{
|
||||
[super loadView];
|
||||
@@ -77,11 +90,29 @@ MBXViewController *viewController = nullptr;
|
||||
|
||||
// Load settings
|
||||
settings = new llmr::Settings_NSUserDefaults();
|
||||
map->setLonLatZoom(settings->longitude, settings->latitude, settings->zoom);
|
||||
map->setAngle(settings->angle);
|
||||
map->setDebug(settings->debug);
|
||||
[self restoreState];
|
||||
}
|
||||
|
||||
- (void)saveState
|
||||
{
|
||||
if (map && settings)
|
||||
{
|
||||
map->getLonLatZoom(settings->longitude, settings->latitude, settings->zoom);
|
||||
settings->angle = map->getAngle();
|
||||
settings->debug = map->getDebug();
|
||||
settings->save();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)restoreState
|
||||
{
|
||||
if (map && settings) {
|
||||
settings->load();
|
||||
map->setLonLatZoom(settings->longitude, settings->latitude, settings->zoom);
|
||||
map->setAngle(settings->angle);
|
||||
map->setDebug(settings->debug);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setupInteraction
|
||||
{
|
||||
@@ -234,16 +265,12 @@ MBXViewController *viewController = nullptr;
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
|
||||
if (settings)
|
||||
{
|
||||
// Save settings
|
||||
if (map)
|
||||
{
|
||||
map->getLonLatZoom(settings->longitude, settings->latitude, settings->zoom);
|
||||
settings->angle = map->getAngle();
|
||||
settings->debug = map->getDebug();
|
||||
settings->save();
|
||||
}
|
||||
[self saveState];
|
||||
delete settings;
|
||||
settings = nullptr;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
"type": "executable",
|
||||
"sources": [
|
||||
"./main.m",
|
||||
"./MBXAppDelegate.h",
|
||||
"./MBXAppDelegate.m",
|
||||
"./MBXViewController.h",
|
||||
"./MBXViewController.mm",
|
||||
"../common/settings_nsuserdefaults.hpp",
|
||||
"../common/settings_nsuserdefaults.mm",
|
||||
|
||||
@@ -484,6 +484,16 @@ void Map::renderLayers(const std::vector<LayerDescription>& layers, RenderPass p
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Styles>
|
||||
bool is_invisible(const Styles &styles, const LayerDescription &layer_desc) {
|
||||
auto it = styles.find(layer_desc.name);
|
||||
if (it == styles.end()) return true;
|
||||
const auto &properties = it->second;
|
||||
if (!properties.enabled) return true;
|
||||
if (properties.opacity <= 0) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void Map::renderLayer(const LayerDescription& layer_desc, RenderPass pass) {
|
||||
if (layer_desc.child_layer.size()) {
|
||||
gl::group group(std::string("group: ") + layer_desc.name);
|
||||
@@ -501,13 +511,25 @@ void Map::renderLayer(const LayerDescription& layer_desc, RenderPass pass) {
|
||||
// Abort early if we can already deduce from the bucket type that
|
||||
// we're not going to render anything anyway during this pass.
|
||||
switch (bucket_desc.type) {
|
||||
case BucketType::Fill:
|
||||
if (is_invisible(style.computed.fills, layer_desc)) return;
|
||||
break;
|
||||
case BucketType::Line:
|
||||
if (pass == Opaque) return;
|
||||
if (is_invisible(style.computed.lines, layer_desc)) return;
|
||||
break;
|
||||
case BucketType::Point:
|
||||
if (pass == Opaque) return;
|
||||
if (is_invisible(style.computed.points, layer_desc)) return;
|
||||
break;
|
||||
case BucketType::Text:
|
||||
if (pass == Opaque) return;
|
||||
if (is_invisible(style.computed.texts, layer_desc)) return;
|
||||
break;
|
||||
case BucketType::Raster:
|
||||
if (pass == Translucent) return;
|
||||
if (is_invisible(style.computed.lines, layer_desc)) return;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <llmr/util/string.hpp>
|
||||
#include <llmr/util/texturepool.hpp>
|
||||
#include <llmr/util/vec.hpp>
|
||||
#include <llmr/util/std.hpp>
|
||||
#include <llmr/geometry/glyph_atlas.hpp>
|
||||
|
||||
|
||||
@@ -33,7 +34,8 @@ bool Source::update() {
|
||||
}
|
||||
|
||||
void Source::updateClipIDs(const std::map<Tile::ID, ClipID> &mapping) {
|
||||
std::for_each(tiles.begin(), tiles.end(), [&mapping](Tile &tile) {
|
||||
std::for_each(tiles.begin(), tiles.end(), [&mapping](std::pair<const Tile::ID, std::unique_ptr<Tile>> &pair) {
|
||||
Tile &tile = *pair.second;
|
||||
auto it = mapping.find(tile.id);
|
||||
if (it != mapping.end()) {
|
||||
tile.clip = it->second;
|
||||
@@ -47,7 +49,8 @@ size_t Source::prepareRender(const TransformState &transform) {
|
||||
if (!enabled) return 0;
|
||||
|
||||
size_t masks = 0;
|
||||
for (Tile& tile : tiles) {
|
||||
for (std::pair<const Tile::ID, std::unique_ptr<Tile>> &pair : tiles) {
|
||||
Tile &tile = *pair.second;
|
||||
gl::group group(util::sprintf<32>("mask %d/%d/%d", tile.id.z, tile.id.y, tile.id.z));
|
||||
transform.matrixFor(tile.matrix, tile.id);
|
||||
matrix::multiply(tile.matrix, painter.projMatrix, tile.matrix);
|
||||
@@ -62,7 +65,8 @@ void Source::render(const LayerDescription& layer_desc, const BucketDescription
|
||||
if (!enabled) return;
|
||||
|
||||
gl::group group(std::string("layer: ") + layer_desc.name);
|
||||
for (const Tile& tile : tiles) {
|
||||
for (std::pair<const Tile::ID, std::unique_ptr<Tile>> &pair : tiles) {
|
||||
Tile &tile = *pair.second;
|
||||
if (tile.data && tile.data->state == TileData::State::parsed) {
|
||||
painter.renderTileLayer(tile, layer_desc);
|
||||
}
|
||||
@@ -72,7 +76,8 @@ void Source::render(const LayerDescription& layer_desc, const BucketDescription
|
||||
void Source::finishRender() {
|
||||
if (!enabled) return;
|
||||
|
||||
for (const Tile& tile : tiles) {
|
||||
for (std::pair<const Tile::ID, std::unique_ptr<Tile>> &pair : tiles) {
|
||||
Tile &tile = *pair.second;
|
||||
painter.renderTileDebug(tile);
|
||||
}
|
||||
}
|
||||
@@ -80,14 +85,18 @@ void Source::finishRender() {
|
||||
|
||||
std::forward_list<Tile::ID> Source::getIDs() const {
|
||||
std::forward_list<Tile::ID> ptrs;
|
||||
std::transform(tiles.begin(), tiles.end(), std::front_inserter(ptrs), [](const Tile &tile) {
|
||||
|
||||
std::transform(tiles.begin(), tiles.end(), std::front_inserter(ptrs), [](const std::pair<const Tile::ID, std::unique_ptr<Tile>> &pair) {
|
||||
Tile &tile = *pair.second;
|
||||
return tile.id;
|
||||
});
|
||||
return ptrs;
|
||||
}
|
||||
|
||||
TileData::State Source::hasTile(const Tile::ID& id) {
|
||||
for (const Tile& tile : tiles) {
|
||||
auto it = tiles.find(id);
|
||||
if (it != tiles.end()) {
|
||||
Tile &tile = *it->second;
|
||||
if (tile.id == id && tile.data) {
|
||||
return tile.data->state;
|
||||
}
|
||||
@@ -103,20 +112,17 @@ TileData::State Source::addTile(const Tile::ID& id) {
|
||||
return state;
|
||||
}
|
||||
|
||||
tiles.emplace_front(id);
|
||||
Tile& new_tile = tiles.front();
|
||||
auto pos = tiles.emplace(id, std::make_unique<Tile>(id));
|
||||
Tile& new_tile = *pos.first->second;
|
||||
|
||||
// We couldn't find the tile in the list. Create a new one.
|
||||
// Try to find the associated TileData object.
|
||||
const Tile::ID normalized_id = id.normalized();
|
||||
|
||||
auto it = std::find_if(tile_data.begin(), tile_data.end(), [normalized_id](const std::weak_ptr<TileData>& tile_data) {
|
||||
return !tile_data.expired() && tile_data.lock()->id == normalized_id;
|
||||
});
|
||||
|
||||
auto it = tile_data.find(normalized_id);
|
||||
if (it != tile_data.end()) {
|
||||
// Create a shared_ptr handle. Note that this might be empty!
|
||||
new_tile.data = it->lock();
|
||||
new_tile.data = it->second.lock();
|
||||
}
|
||||
|
||||
if (new_tile.data && new_tile.data->state == TileData::State::obsolete) {
|
||||
@@ -138,7 +144,7 @@ TileData::State Source::addTile(const Tile::ID& id) {
|
||||
}
|
||||
|
||||
new_tile.data->request();
|
||||
tile_data.push_front(new_tile.data);
|
||||
tile_data.emplace(new_tile.data->id, new_tile.data);
|
||||
}
|
||||
|
||||
return new_tile.data->state;
|
||||
@@ -251,31 +257,26 @@ bool Source::updateTiles() {
|
||||
|
||||
// Remove tiles that we definitely don't need, i.e. tiles that are not on
|
||||
// the required list.
|
||||
std::forward_list<Tile::ID> retain_data;
|
||||
tiles.remove_if([&retain, &retain_data, &changed](const Tile & tile) {
|
||||
std::set<Tile::ID> retain_data;
|
||||
std::erase_if(tiles, [&retain, &retain_data, &changed](std::pair<const Tile::ID, std::unique_ptr<Tile>> &pair) {
|
||||
Tile &tile = *pair.second;
|
||||
bool obsolete = std::find(retain.begin(), retain.end(), tile.id) == retain.end();
|
||||
if (obsolete) {
|
||||
changed = true;
|
||||
} else {
|
||||
retain_data.push_front(tile.data->id);
|
||||
retain_data.insert(tile.data->id);
|
||||
}
|
||||
return obsolete;
|
||||
});
|
||||
|
||||
// Sort tiles by zoom level, front to back.
|
||||
// We're painting front-to-back, so we want to draw more detailed tiles first
|
||||
// before filling in other parts with lower zoom levels.
|
||||
tiles.sort([](const Tile & a, const Tile & b) {
|
||||
return a.id.z < b.id.z;
|
||||
});
|
||||
|
||||
// Remove all the expired pointers from the list.
|
||||
tile_data.remove_if([&retain_data](const std::weak_ptr<TileData>& tile_data) {
|
||||
const std::shared_ptr<TileData> tile = tile_data.lock();
|
||||
// Remove all the expired pointers from the set.
|
||||
std::erase_if(tile_data, [&retain_data](std::pair<const Tile::ID, std::weak_ptr<TileData>> &pair) {
|
||||
const std::shared_ptr<TileData> tile = pair.second.lock();
|
||||
if (!tile) {
|
||||
return true;
|
||||
}
|
||||
bool obsolete = std::find(retain_data.begin(), retain_data.end(), tile->id) == retain_data.end();
|
||||
|
||||
bool obsolete = retain_data.find(tile->id) == retain_data.end();
|
||||
if (obsolete) {
|
||||
tile->cancel();
|
||||
return true;
|
||||
|
||||
@@ -1,29 +1,16 @@
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
|
||||
|
||||
#include <llmr/renderer/painter.hpp>
|
||||
#include <llmr/util/std.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/renderer/raster_bucket.hpp>
|
||||
|
||||
#include <llmr/map/map.hpp>
|
||||
#include <llmr/map/transform.hpp>
|
||||
#include <llmr/geometry/debug_font_buffer.hpp>
|
||||
#include <llmr/geometry/glyph_atlas.hpp>
|
||||
#include <llmr/platform/gl.hpp>
|
||||
#include <llmr/style/style.hpp>
|
||||
#include <llmr/style/sprite.hpp>
|
||||
#include <llmr/util/raster.hpp>
|
||||
|
||||
#include <llmr/util/std.hpp>
|
||||
#include <llmr/util/string.hpp>
|
||||
#include <llmr/util/timer.hpp>
|
||||
#include <llmr/util/time.hpp>
|
||||
#include <llmr/util/clip_ids.hpp>
|
||||
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
#define BUFFER_OFFSET(i) ((char *)nullptr + (i))
|
||||
@@ -130,42 +117,6 @@ void Painter::changeMatrix() {
|
||||
matrix::multiply(nativeMatrix, projMatrix, nativeMatrix);
|
||||
}
|
||||
|
||||
void Painter::prepareClippingMask() {
|
||||
gl::start_group("clipping masks");
|
||||
useProgram(plainShader->program);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
depthMask(false);
|
||||
glDepthRange(1.0f, 1.0f);
|
||||
|
||||
coveringPlainArray.bind(*plainShader, tileStencilBuffer, BUFFER_OFFSET(0));
|
||||
}
|
||||
|
||||
void Painter::drawClippingMask(const mat4& matrix, const ClipID &clip) {
|
||||
plainShader->setMatrix(matrix);
|
||||
|
||||
GLint id = clip.mask.to_ulong();
|
||||
GLuint mask = clipMask[clip.length];
|
||||
glStencilFunc(GL_ALWAYS, id, mask);
|
||||
glStencilMask(0xFF);
|
||||
|
||||
Color background = map.getStyle().computed.background.color;
|
||||
background[0] *= map.getStyle().computed.background.opacity;
|
||||
background[1] *= map.getStyle().computed.background.opacity;
|
||||
background[2] *= map.getStyle().computed.background.opacity;
|
||||
background[3] *= map.getStyle().computed.background.opacity;
|
||||
|
||||
plainShader->setColor(background);
|
||||
|
||||
glDrawArrays(GL_TRIANGLES, 0, (GLsizei)tileStencilBuffer.index());
|
||||
}
|
||||
|
||||
void Painter::finishClippingMask() {
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
depthMask(true);
|
||||
glStencilMask(0x0);
|
||||
gl::end_group();
|
||||
}
|
||||
|
||||
void Painter::clear() {
|
||||
gl::group group("clear");
|
||||
glStencilMask(0xFF);
|
||||
@@ -219,16 +170,6 @@ void Painter::renderTileLayer(const Tile& tile, const LayerDescription &layer_de
|
||||
}
|
||||
}
|
||||
|
||||
void Painter::renderTileDebug(const Tile& tile) {
|
||||
gl::group group(util::sprintf<32>("debug %d/%d/%d", tile.id.z, tile.id.y, tile.id.z));
|
||||
assert(tile.data);
|
||||
if (debug) {
|
||||
prepareTile(tile);
|
||||
renderDebugText(tile.data->debugBucket);
|
||||
renderDebugFrame();
|
||||
}
|
||||
}
|
||||
|
||||
void Painter::translateLayer(std::array<float, 2> translation, bool reverse) {
|
||||
if (translation[0] || translation[1]) {
|
||||
if (reverse) {
|
||||
@@ -243,483 +184,6 @@ void Painter::translateLayer(std::array<float, 2> translation, bool reverse) {
|
||||
}
|
||||
}
|
||||
|
||||
void Painter::renderRaster(RasterBucket& bucket, const std::string& layer_name, const Tile::ID& /*id*/) {
|
||||
if (pass == Translucent) return;
|
||||
|
||||
auto raster_properties = map.getStyle().computed.rasters;
|
||||
auto raster_properties_it = raster_properties.find(layer_name);
|
||||
if (raster_properties_it == raster_properties.end()) return;
|
||||
|
||||
const RasterProperties& properties = raster_properties_it->second;
|
||||
if (!properties.enabled) return;
|
||||
|
||||
depthMask(false);
|
||||
|
||||
useProgram(rasterShader->program);
|
||||
rasterShader->setMatrix(matrix);
|
||||
rasterShader->setOpacity(1);
|
||||
// rasterShader->setOpacity(properties.opacity * tile_data->raster->opacity);
|
||||
|
||||
glDepthRange(strata + strata_epsilon, 1.0f);
|
||||
bucket.drawRaster(*rasterShader, tileStencilBuffer, coveringRasterArray);
|
||||
|
||||
depthMask(true);
|
||||
}
|
||||
|
||||
void Painter::renderFill(FillBucket& bucket, const std::string& layer_name, const Tile::ID& id) {
|
||||
// Abort early.
|
||||
if (!bucket.hasData()) return;
|
||||
|
||||
auto fill_properties = map.getStyle().computed.fills;
|
||||
auto fill_properties_it = fill_properties.find(layer_name);
|
||||
if (fill_properties_it == fill_properties.end()) return;
|
||||
|
||||
const FillProperties& properties = fill_properties_it->second;
|
||||
if (!properties.enabled) return;
|
||||
|
||||
Color fill_color = properties.fill_color;
|
||||
fill_color[0] *= properties.opacity;
|
||||
fill_color[1] *= properties.opacity;
|
||||
fill_color[2] *= properties.opacity;
|
||||
fill_color[3] *= properties.opacity;
|
||||
|
||||
Color stroke_color = properties.stroke_color;
|
||||
stroke_color[0] *= properties.opacity;
|
||||
stroke_color[1] *= properties.opacity;
|
||||
stroke_color[2] *= properties.opacity;
|
||||
stroke_color[3] *= properties.opacity;
|
||||
|
||||
bool outline = properties.antialias && properties.stroke_color != properties.fill_color;
|
||||
bool fringeline = properties.antialias && properties.stroke_color == properties.fill_color;
|
||||
if (fringeline) {
|
||||
outline = true;
|
||||
stroke_color = fill_color;
|
||||
}
|
||||
|
||||
translateLayer(properties.translate);
|
||||
|
||||
// Because we're drawing top-to-bottom, and we update the stencil mask
|
||||
// below, we have to draw the outline first (!)
|
||||
if (outline && pass == Translucent) {
|
||||
useProgram(outlineShader->program);
|
||||
outlineShader->setMatrix(matrix);
|
||||
lineWidth(2.0f); // This is always fixed and does not depend on the pixelRatio!
|
||||
|
||||
outlineShader->setColor(stroke_color);
|
||||
|
||||
// Draw the entire line
|
||||
outlineShader->setWorld({{
|
||||
static_cast<float>(map.getState().getFramebufferWidth()),
|
||||
static_cast<float>(map.getState().getFramebufferHeight())
|
||||
}});
|
||||
glDepthRange(strata, 1.0f);
|
||||
bucket.drawVertices(*outlineShader);
|
||||
} else if (fringeline) {
|
||||
// // We're only drawing to the first seven bits (== support a maximum of
|
||||
// // 127 overlapping polygons in one place before we get rendering errors).
|
||||
// glStencilMask(0x3F);
|
||||
// glClear(GL_STENCIL_BUFFER_BIT);
|
||||
|
||||
// // Draw front facing triangles. Wherever the 0x80 bit is 1, we are
|
||||
// // increasing the lower 7 bits by one if the triangle is a front-facing
|
||||
// // triangle. This means that all visible polygons should be in CCW
|
||||
// // orientation, while all holes (see below) are in CW orientation.
|
||||
// glStencilFunc(GL_EQUAL, 0x80, 0x80);
|
||||
|
||||
// // When we do a nonzero fill, we count the number of times a pixel is
|
||||
// // covered by a counterclockwise polygon, and subtract the number of
|
||||
// // times it is "uncovered" by a clockwise polygon.
|
||||
// glStencilOp(GL_KEEP, GL_KEEP, GL_INCR_WRAP);
|
||||
}
|
||||
|
||||
if ((fill_color[3] >= 1.0f) == (pass == Opaque)) {
|
||||
auto &sprite = map.getStyle().sprite;
|
||||
if (properties.image.size() && sprite && sprite->isLoaded()) {
|
||||
// Draw texture fill
|
||||
ImagePosition imagePos = sprite->getPosition(properties.image, true);
|
||||
|
||||
float factor = 8.0 / pow(2, map.getState().getIntegerZoom() - id.z);
|
||||
float mix = fmod(map.getState().getZoom(), 1.0);
|
||||
|
||||
std::array<float, 2> imageSize = {{
|
||||
imagePos.size.x * factor,
|
||||
imagePos.size.y *factor
|
||||
}
|
||||
};
|
||||
|
||||
std::array<float, 2> offset = {{
|
||||
(float)fmod(id.x * 4096, imageSize[0]),
|
||||
(float)fmod(id.y * 4096, imageSize[1])
|
||||
}
|
||||
};
|
||||
|
||||
useProgram(patternShader->program);
|
||||
patternShader->setMatrix(matrix);
|
||||
patternShader->setOffset(offset);
|
||||
patternShader->setPatternSize(imageSize);
|
||||
patternShader->setPatternTopLeft({{ imagePos.tl.x, imagePos.tl.y }});
|
||||
patternShader->setPatternBottomRight({{ imagePos.br.x, imagePos.br.y }});
|
||||
patternShader->setColor(fill_color);
|
||||
patternShader->setMix(mix);
|
||||
sprite->raster.bind(true);
|
||||
|
||||
// Draw the actual triangles into the color & stencil buffer.
|
||||
glDepthRange(strata + strata_epsilon, 1.0f);
|
||||
bucket.drawElements(*patternShader);
|
||||
} else {
|
||||
// Only draw the fill when it's either opaque and we're drawing opaque
|
||||
// fragments or when it's translucent and we're drawing translucent
|
||||
// fragments
|
||||
// Draw filling rectangle.
|
||||
useProgram(plainShader->program);
|
||||
plainShader->setMatrix(matrix);
|
||||
plainShader->setColor(fill_color);
|
||||
|
||||
// Draw the actual triangles into the color & stencil buffer.
|
||||
glDepthRange(strata + strata_epsilon, 1.0f);
|
||||
bucket.drawElements(*plainShader);
|
||||
}
|
||||
}
|
||||
|
||||
// Because we're drawing top-to-bottom, and we update the stencil mask
|
||||
// below, we have to draw the outline first (!)
|
||||
if (fringeline && pass == Translucent) {
|
||||
useProgram(outlineShader->program);
|
||||
outlineShader->setMatrix(matrix);
|
||||
lineWidth(2.0f); // This is always fixed and does not depend on the pixelRatio!
|
||||
|
||||
outlineShader->setColor(fill_color);
|
||||
|
||||
// Draw the entire line
|
||||
outlineShader->setWorld({{
|
||||
static_cast<float>(map.getState().getFramebufferWidth()),
|
||||
static_cast<float>(map.getState().getFramebufferHeight())
|
||||
}});
|
||||
|
||||
glDepthRange(strata + strata_epsilon, 1.0f);
|
||||
bucket.drawVertices(*outlineShader);
|
||||
}
|
||||
|
||||
translateLayer(properties.translate, true);
|
||||
}
|
||||
|
||||
void Painter::renderLine(LineBucket& bucket, const std::string& layer_name, const Tile::ID& /*id*/) {
|
||||
// Abort early.
|
||||
if (pass == Opaque) return;
|
||||
if (!bucket.hasData()) return;
|
||||
|
||||
auto line_properties = map.getStyle().computed.lines;
|
||||
auto line_properties_it = line_properties.find(layer_name);
|
||||
if (line_properties_it == line_properties.end()) return;
|
||||
|
||||
const LineProperties& properties = line_properties_it->second;
|
||||
if (!properties.enabled) return;
|
||||
|
||||
float width = properties.width;
|
||||
float offset = properties.offset / 2;
|
||||
|
||||
// These are the radii of the line. We are limiting it to 16, which will result
|
||||
// in a point size of 64 on retina.
|
||||
float inset = fmin((fmax(-1, offset - width / 2 - 0.5) + 1), 16.0f);
|
||||
float outset = fmin(offset + width / 2 + 0.5, 16.0f);
|
||||
|
||||
Color color = properties.color;
|
||||
color[0] *= properties.opacity;
|
||||
color[1] *= properties.opacity;
|
||||
color[2] *= properties.opacity;
|
||||
color[3] *= properties.opacity;
|
||||
|
||||
float dash_length = properties.dash_array[0];
|
||||
float dash_gap = properties.dash_array[1];
|
||||
|
||||
translateLayer(properties.translate);
|
||||
|
||||
glDepthRange(strata, 1.0f);
|
||||
|
||||
// We're only drawing end caps + round line joins if the line is > 2px. Otherwise, they aren't visible anyway.
|
||||
if (bucket.hasPoints() && outset > 1.0f) {
|
||||
useProgram(linejoinShader->program);
|
||||
linejoinShader->setMatrix(matrix);
|
||||
linejoinShader->setColor(color);
|
||||
linejoinShader->setWorld({{
|
||||
map.getState().getFramebufferWidth() * 0.5f,
|
||||
map.getState().getFramebufferHeight() * 0.5f
|
||||
}
|
||||
});
|
||||
linejoinShader->setLineWidth({{
|
||||
((outset - 0.25f) * map.getState().getPixelRatio()),
|
||||
((inset - 0.25f) * map.getState().getPixelRatio())
|
||||
}
|
||||
});
|
||||
|
||||
float pointSize = ceil(map.getState().getPixelRatio() * outset * 2.0);
|
||||
#if defined(GL_ES_VERSION_2_0)
|
||||
linejoinShader->setSize(pointSize);
|
||||
#else
|
||||
glPointSize(pointSize);
|
||||
#endif
|
||||
bucket.drawPoints(*linejoinShader);
|
||||
}
|
||||
|
||||
// var imagePos = properties.image && imageSprite.getPosition(properties.image);
|
||||
bool imagePos = false;
|
||||
if (imagePos) {
|
||||
// var factor = 8 / Math.pow(2, painter.transform.zoom - params.z);
|
||||
|
||||
// imageSprite.bind(gl, true);
|
||||
|
||||
// //factor = Math.pow(2, 4 - painter.transform.zoom + params.z);
|
||||
// gl.switchShader(painter.linepatternShader, painter.translatedMatrix || painter.posMatrix, painter.extrudeMatrix);
|
||||
// shader = painter.linepatternShader;
|
||||
// glUniform2fv(painter.linepatternShader.u_pattern_size, [imagePos.size[0] * factor, imagePos.size[1] ]);
|
||||
// glUniform2fv(painter.linepatternShader.u_pattern_tl, imagePos.tl);
|
||||
// glUniform2fv(painter.linepatternShader.u_pattern_br, imagePos.br);
|
||||
// glUniform1f(painter.linepatternShader.u_fade, painter.transform.z % 1.0);
|
||||
|
||||
} else {
|
||||
useProgram(lineShader->program);
|
||||
lineShader->setMatrix(matrix);
|
||||
lineShader->setExtrudeMatrix(extrudeMatrix);
|
||||
lineShader->setDashArray({{ dash_length, dash_gap }});
|
||||
lineShader->setLineWidth({{ outset, inset }});
|
||||
lineShader->setRatio(map.getState().getPixelRatio());
|
||||
lineShader->setColor(color);
|
||||
bucket.drawLines(*lineShader);
|
||||
}
|
||||
|
||||
translateLayer(properties.translate, true);
|
||||
}
|
||||
|
||||
void Painter::renderPoint(PointBucket& bucket, const std::string& layer_name, const Tile::ID& /*id&*/) {
|
||||
// Abort early.
|
||||
if (!bucket.hasData()) return;
|
||||
if (pass == Opaque) return;
|
||||
|
||||
auto point_properties = map.getStyle().computed.points;
|
||||
auto point_properties_it = point_properties.find(layer_name);
|
||||
if (point_properties_it == point_properties.end()) return;
|
||||
|
||||
const PointProperties& properties = point_properties_it->second;
|
||||
if (!properties.enabled) return;
|
||||
|
||||
translateLayer(properties.translate);
|
||||
|
||||
Color color = properties.color;
|
||||
color[0] *= properties.opacity;
|
||||
color[1] *= properties.opacity;
|
||||
color[2] *= properties.opacity;
|
||||
color[3] *= properties.opacity;
|
||||
|
||||
auto &sprite = map.getStyle().sprite;
|
||||
ImagePosition imagePos;
|
||||
|
||||
if (properties.image.length() && sprite && sprite->isLoaded()) {
|
||||
std::string sized_image = properties.image;
|
||||
if (properties.size) {
|
||||
sized_image.append("-");
|
||||
sized_image.append(std::to_string(static_cast<int>(std::round(properties.size))));
|
||||
}
|
||||
imagePos = sprite->getPosition(sized_image, false);
|
||||
}
|
||||
|
||||
if (!imagePos.size) {
|
||||
useProgram(dotShader->program);
|
||||
dotShader->setMatrix(matrix);
|
||||
dotShader->setColor(color);
|
||||
|
||||
const float pointSize = (properties.radius ? properties.radius * 2 : 8) * map.getState().getPixelRatio();
|
||||
#if defined(GL_ES_VERSION_2_0)
|
||||
dotShader->setSize(pointSize);
|
||||
#else
|
||||
glPointSize(pointSize);
|
||||
glEnable(GL_POINT_SPRITE);
|
||||
#endif
|
||||
dotShader->setBlur((properties.blur ? properties.blur : 1.5) / pointSize);
|
||||
|
||||
glDepthRange(strata, 1.0f);
|
||||
bucket.drawPoints(*dotShader);
|
||||
} else {
|
||||
useProgram(pointShader->program);
|
||||
pointShader->setMatrix(matrix);
|
||||
pointShader->setColor(color);
|
||||
|
||||
pointShader->setImage(0);
|
||||
pointShader->setPointTopLeft({{ imagePos.tl.x, imagePos.tl.y }});
|
||||
pointShader->setPointBottomRight({{ imagePos.br.x, imagePos.br.y }});
|
||||
|
||||
sprite->raster.bind(map.getState().isChanging());
|
||||
|
||||
const float pointSize = (properties.size ? properties.size : (imagePos.size.x / map.getState().getPixelRatio())) * 1.4142135623730951 * map.getState().getPixelRatio();
|
||||
#if defined(GL_ES_VERSION_2_0)
|
||||
pointShader->setSize(pointSize);
|
||||
#else
|
||||
glPointSize(pointSize);
|
||||
glEnable(GL_POINT_SPRITE);
|
||||
#endif
|
||||
|
||||
glDepthRange(strata, 1.0f);
|
||||
bucket.drawPoints(*pointShader);
|
||||
}
|
||||
|
||||
translateLayer(properties.translate, true);
|
||||
}
|
||||
|
||||
void Painter::renderText(TextBucket& bucket, const std::string& layer_name, const Tile::ID& /*id*/) {
|
||||
// Abort early.
|
||||
if (pass == Opaque) return;
|
||||
if (!bucket.hasData()) return;
|
||||
|
||||
auto text_properties = map.getStyle().computed.texts;
|
||||
auto text_properties_it = text_properties.find(layer_name);
|
||||
if (text_properties_it == text_properties.end()) return;
|
||||
|
||||
const TextProperties& properties = text_properties_it->second;
|
||||
if (!properties.enabled) return;
|
||||
|
||||
translateLayer(properties.translate);
|
||||
|
||||
mat4 exMatrix;
|
||||
matrix::copy(exMatrix, projMatrix);
|
||||
if (bucket.geom_desc.path == TextPathType::Curve) {
|
||||
matrix::rotate_z(exMatrix, exMatrix, map.getState().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);
|
||||
|
||||
map.getGlyphAtlas().bind();
|
||||
textShader->setTextureSize({{static_cast<float>(map.getGlyphAtlas().width),
|
||||
static_cast<float>(map.getGlyphAtlas().height)}});
|
||||
|
||||
textShader->setGamma(2.5f / fontSize / map.getState().getPixelRatio());
|
||||
|
||||
// Convert the -pi..pi to an int8 range.
|
||||
float angle = round((map.getState().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((map.getState().getNormalizedZoom() - zoomAdjust) * 10); // current zoom level
|
||||
|
||||
// Label fading
|
||||
const time duration = 300_milliseconds;
|
||||
const time currentTime = util::now();
|
||||
|
||||
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].timestamp + duration < currentTime) {
|
||||
history.pop_front();
|
||||
}
|
||||
|
||||
if (history[1].timestamp + 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.timestamp - history[1].timestamp;
|
||||
if (timeDiff > duration) timeDiff = 1;
|
||||
float fadedist = zoomDiff / (timeDiff / duration);
|
||||
|
||||
if (isnan(fadedist)) fprintf(stderr, "fadedist should never be NaN\n");
|
||||
|
||||
// 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.timestamp) / duration * fadedist;
|
||||
|
||||
textShader->setFadeDist(fadedist * 10);
|
||||
textShader->setMinFadeZoom(floor(lowZ * 10));
|
||||
textShader->setMaxFadeZoom(floor(highZ * 10));
|
||||
textShader->setFadeZoom((map.getState().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);
|
||||
|
||||
translateLayer(properties.translate, true);
|
||||
}
|
||||
|
||||
void Painter::renderDebugText(DebugBucket& bucket) {
|
||||
gl::group group("debug text");
|
||||
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
|
||||
useProgram(plainShader->program);
|
||||
plainShader->setMatrix(matrix);
|
||||
|
||||
// Draw white outline
|
||||
plainShader->setColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
lineWidth(4.0f * map.getState().getPixelRatio());
|
||||
bucket.drawLines(*plainShader);
|
||||
|
||||
#ifndef GL_ES_VERSION_2_0
|
||||
// Draw line "end caps"
|
||||
glPointSize(2);
|
||||
bucket.drawPoints(*plainShader);
|
||||
#endif
|
||||
|
||||
// Draw black text.
|
||||
plainShader->setColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
lineWidth(2.0f * map.getState().getPixelRatio());
|
||||
bucket.drawLines(*plainShader);
|
||||
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
}
|
||||
|
||||
void Painter::renderDebugFrame() {
|
||||
gl::group group("debug frame");
|
||||
|
||||
// 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
|
||||
// to the tile viewport.
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
|
||||
useProgram(plainShader->program);
|
||||
plainShader->setMatrix(matrix);
|
||||
|
||||
// draw tile outline
|
||||
tileBorderArray.bind(*plainShader, tileBorderBuffer, BUFFER_OFFSET(0));
|
||||
plainShader->setColor(1.0f, 0.0f, 0.0f, 1.0f);
|
||||
lineWidth(4.0f * map.getState().getPixelRatio());
|
||||
glDrawArrays(GL_LINE_STRIP, 0, (GLsizei)tileBorderBuffer.index());
|
||||
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
}
|
||||
|
||||
void Painter::renderMatte() {
|
||||
gl::group group("matte");
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
@@ -738,38 +202,3 @@ void Painter::renderMatte() {
|
||||
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
}
|
||||
|
||||
|
||||
void Painter::renderDebugText(const std::vector<std::string> &strings) {
|
||||
gl::group group("debug text");
|
||||
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glStencilFunc(GL_ALWAYS, 0xFF, 0xFF);
|
||||
|
||||
useProgram(plainShader->program);
|
||||
plainShader->setMatrix(nativeMatrix);
|
||||
|
||||
DebugFontBuffer debugFontBuffer;
|
||||
int line = 25;
|
||||
for (const std::string &str : strings) {
|
||||
debugFontBuffer.addText(str.c_str(), 10, line, 0.75);
|
||||
line += 20;
|
||||
}
|
||||
|
||||
// draw debug info
|
||||
VertexArrayObject debugFontArray;
|
||||
debugFontArray.bind(*plainShader, debugFontBuffer, BUFFER_OFFSET(0));
|
||||
plainShader->setColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
lineWidth(4.0f * map.getState().getPixelRatio());
|
||||
glDrawArrays(GL_LINES, 0, (GLsizei)debugFontBuffer.index());
|
||||
#ifndef GL_ES_VERSION_2_0
|
||||
glPointSize(2);
|
||||
glDrawArrays(GL_POINTS, 0, (GLsizei)debugFontBuffer.index());
|
||||
#endif
|
||||
plainShader->setColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
lineWidth(2.0f * map.getState().getPixelRatio());
|
||||
glDrawArrays(GL_LINES, 0, (GLsizei)debugFontBuffer.index());
|
||||
|
||||
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
}
|
||||
42
src/renderer/painter_clipping.cpp
Normal file
42
src/renderer/painter_clipping.cpp
Normal file
@@ -0,0 +1,42 @@
|
||||
#include <llmr/renderer/painter.hpp>
|
||||
#include <llmr/renderer/fill_bucket.hpp>
|
||||
#include <llmr/map/map.hpp>
|
||||
#include <llmr/util/clip_ids.hpp>
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
void Painter::prepareClippingMask() {
|
||||
gl::start_group("clipping masks");
|
||||
useProgram(plainShader->program);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
depthMask(false);
|
||||
glDepthRange(1.0f, 1.0f);
|
||||
|
||||
coveringPlainArray.bind(*plainShader, tileStencilBuffer, BUFFER_OFFSET(0));
|
||||
}
|
||||
|
||||
void Painter::drawClippingMask(const mat4& matrix, const ClipID &clip) {
|
||||
plainShader->setMatrix(matrix);
|
||||
|
||||
GLint id = clip.mask.to_ulong();
|
||||
GLuint mask = clipMask[clip.length];
|
||||
glStencilFunc(GL_ALWAYS, id, mask);
|
||||
glStencilMask(0xFF);
|
||||
|
||||
Color background = map.getStyle().computed.background.color;
|
||||
background[0] *= map.getStyle().computed.background.opacity;
|
||||
background[1] *= map.getStyle().computed.background.opacity;
|
||||
background[2] *= map.getStyle().computed.background.opacity;
|
||||
background[3] *= map.getStyle().computed.background.opacity;
|
||||
|
||||
plainShader->setColor(background);
|
||||
|
||||
glDrawArrays(GL_TRIANGLES, 0, (GLsizei)tileStencilBuffer.index());
|
||||
}
|
||||
|
||||
void Painter::finishClippingMask() {
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
depthMask(true);
|
||||
glStencilMask(0x0);
|
||||
gl::end_group();
|
||||
}
|
||||
97
src/renderer/painter_debug.cpp
Normal file
97
src/renderer/painter_debug.cpp
Normal file
@@ -0,0 +1,97 @@
|
||||
#include <llmr/renderer/painter.hpp>
|
||||
#include <llmr/renderer/debug_bucket.hpp>
|
||||
#include <llmr/map/map.hpp>
|
||||
#include <llmr/util/string.hpp>
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
void Painter::renderTileDebug(const Tile& tile) {
|
||||
gl::group group(util::sprintf<32>("debug %d/%d/%d", tile.id.z, tile.id.y, tile.id.z));
|
||||
assert(tile.data);
|
||||
if (debug) {
|
||||
prepareTile(tile);
|
||||
renderDebugText(tile.data->debugBucket);
|
||||
renderDebugFrame();
|
||||
}
|
||||
}
|
||||
|
||||
void Painter::renderDebugText(DebugBucket& bucket) {
|
||||
gl::group group("debug text");
|
||||
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
|
||||
useProgram(plainShader->program);
|
||||
plainShader->setMatrix(matrix);
|
||||
|
||||
// Draw white outline
|
||||
plainShader->setColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
lineWidth(4.0f * map.getState().getPixelRatio());
|
||||
bucket.drawLines(*plainShader);
|
||||
|
||||
#ifndef GL_ES_VERSION_2_0
|
||||
// Draw line "end caps"
|
||||
glPointSize(2);
|
||||
bucket.drawPoints(*plainShader);
|
||||
#endif
|
||||
|
||||
// Draw black text.
|
||||
plainShader->setColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
lineWidth(2.0f * map.getState().getPixelRatio());
|
||||
bucket.drawLines(*plainShader);
|
||||
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
}
|
||||
|
||||
void Painter::renderDebugFrame() {
|
||||
gl::group group("debug frame");
|
||||
|
||||
// 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
|
||||
// to the tile viewport.
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
|
||||
useProgram(plainShader->program);
|
||||
plainShader->setMatrix(matrix);
|
||||
|
||||
// draw tile outline
|
||||
tileBorderArray.bind(*plainShader, tileBorderBuffer, BUFFER_OFFSET(0));
|
||||
plainShader->setColor(1.0f, 0.0f, 0.0f, 1.0f);
|
||||
lineWidth(4.0f * map.getState().getPixelRatio());
|
||||
glDrawArrays(GL_LINE_STRIP, 0, (GLsizei)tileBorderBuffer.index());
|
||||
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
}
|
||||
|
||||
void Painter::renderDebugText(const std::vector<std::string> &strings) {
|
||||
gl::group group("debug text");
|
||||
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glStencilFunc(GL_ALWAYS, 0xFF, 0xFF);
|
||||
|
||||
useProgram(plainShader->program);
|
||||
plainShader->setMatrix(nativeMatrix);
|
||||
|
||||
DebugFontBuffer debugFontBuffer;
|
||||
int line = 25;
|
||||
for (const std::string &str : strings) {
|
||||
debugFontBuffer.addText(str.c_str(), 10, line, 0.75);
|
||||
line += 20;
|
||||
}
|
||||
|
||||
// draw debug info
|
||||
VertexArrayObject debugFontArray;
|
||||
debugFontArray.bind(*plainShader, debugFontBuffer, BUFFER_OFFSET(0));
|
||||
plainShader->setColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
lineWidth(4.0f * map.getState().getPixelRatio());
|
||||
glDrawArrays(GL_LINES, 0, (GLsizei)debugFontBuffer.index());
|
||||
#ifndef GL_ES_VERSION_2_0
|
||||
glPointSize(2);
|
||||
glDrawArrays(GL_POINTS, 0, (GLsizei)debugFontBuffer.index());
|
||||
#endif
|
||||
plainShader->setColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
lineWidth(2.0f * map.getState().getPixelRatio());
|
||||
glDrawArrays(GL_LINES, 0, (GLsizei)debugFontBuffer.index());
|
||||
|
||||
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
}
|
||||
143
src/renderer/painter_fill.cpp
Normal file
143
src/renderer/painter_fill.cpp
Normal file
@@ -0,0 +1,143 @@
|
||||
#include <llmr/renderer/painter.hpp>
|
||||
#include <llmr/renderer/fill_bucket.hpp>
|
||||
#include <llmr/map/map.hpp>
|
||||
#include <llmr/style/sprite.hpp>
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
void Painter::renderFill(FillBucket& bucket, const std::string& layer_name, const Tile::ID& id) {
|
||||
// Abort early.
|
||||
if (!bucket.hasData()) return;
|
||||
|
||||
auto fill_properties = map.getStyle().computed.fills;
|
||||
auto fill_properties_it = fill_properties.find(layer_name);
|
||||
if (fill_properties_it == fill_properties.end()) return;
|
||||
|
||||
const FillProperties& properties = fill_properties_it->second;
|
||||
if (!properties.enabled) return;
|
||||
|
||||
Color fill_color = properties.fill_color;
|
||||
fill_color[0] *= properties.opacity;
|
||||
fill_color[1] *= properties.opacity;
|
||||
fill_color[2] *= properties.opacity;
|
||||
fill_color[3] *= properties.opacity;
|
||||
|
||||
Color stroke_color = properties.stroke_color;
|
||||
stroke_color[0] *= properties.opacity;
|
||||
stroke_color[1] *= properties.opacity;
|
||||
stroke_color[2] *= properties.opacity;
|
||||
stroke_color[3] *= properties.opacity;
|
||||
|
||||
bool outline = properties.antialias && properties.stroke_color != properties.fill_color;
|
||||
bool fringeline = properties.antialias && properties.stroke_color == properties.fill_color;
|
||||
if (fringeline) {
|
||||
outline = true;
|
||||
stroke_color = fill_color;
|
||||
}
|
||||
|
||||
translateLayer(properties.translate);
|
||||
|
||||
// Because we're drawing top-to-bottom, and we update the stencil mask
|
||||
// below, we have to draw the outline first (!)
|
||||
if (outline && pass == Translucent) {
|
||||
useProgram(outlineShader->program);
|
||||
outlineShader->setMatrix(matrix);
|
||||
lineWidth(2.0f); // This is always fixed and does not depend on the pixelRatio!
|
||||
|
||||
outlineShader->setColor(stroke_color);
|
||||
|
||||
// Draw the entire line
|
||||
outlineShader->setWorld({{
|
||||
static_cast<float>(map.getState().getFramebufferWidth()),
|
||||
static_cast<float>(map.getState().getFramebufferHeight())
|
||||
}});
|
||||
glDepthRange(strata, 1.0f);
|
||||
bucket.drawVertices(*outlineShader);
|
||||
} else if (fringeline) {
|
||||
// // We're only drawing to the first seven bits (== support a maximum of
|
||||
// // 127 overlapping polygons in one place before we get rendering errors).
|
||||
// glStencilMask(0x3F);
|
||||
// glClear(GL_STENCIL_BUFFER_BIT);
|
||||
|
||||
// // Draw front facing triangles. Wherever the 0x80 bit is 1, we are
|
||||
// // increasing the lower 7 bits by one if the triangle is a front-facing
|
||||
// // triangle. This means that all visible polygons should be in CCW
|
||||
// // orientation, while all holes (see below) are in CW orientation.
|
||||
// glStencilFunc(GL_EQUAL, 0x80, 0x80);
|
||||
|
||||
// // When we do a nonzero fill, we count the number of times a pixel is
|
||||
// // covered by a counterclockwise polygon, and subtract the number of
|
||||
// // times it is "uncovered" by a clockwise polygon.
|
||||
// glStencilOp(GL_KEEP, GL_KEEP, GL_INCR_WRAP);
|
||||
}
|
||||
|
||||
if ((fill_color[3] >= 1.0f) == (pass == Opaque)) {
|
||||
auto &sprite = map.getStyle().sprite;
|
||||
if (properties.image.size() && sprite && sprite->isLoaded()) {
|
||||
// Draw texture fill
|
||||
ImagePosition imagePos = sprite->getPosition(properties.image, true);
|
||||
|
||||
float factor = 8.0 / pow(2, map.getState().getIntegerZoom() - id.z);
|
||||
float mix = fmod(map.getState().getZoom(), 1.0);
|
||||
|
||||
std::array<float, 2> imageSize = {{
|
||||
imagePos.size.x * factor,
|
||||
imagePos.size.y *factor
|
||||
}
|
||||
};
|
||||
|
||||
std::array<float, 2> offset = {{
|
||||
(float)fmod(id.x * 4096, imageSize[0]),
|
||||
(float)fmod(id.y * 4096, imageSize[1])
|
||||
}
|
||||
};
|
||||
|
||||
useProgram(patternShader->program);
|
||||
patternShader->setMatrix(matrix);
|
||||
patternShader->setOffset(offset);
|
||||
patternShader->setPatternSize(imageSize);
|
||||
patternShader->setPatternTopLeft({{ imagePos.tl.x, imagePos.tl.y }});
|
||||
patternShader->setPatternBottomRight({{ imagePos.br.x, imagePos.br.y }});
|
||||
patternShader->setColor(fill_color);
|
||||
patternShader->setMix(mix);
|
||||
sprite->raster.bind(true);
|
||||
|
||||
// Draw the actual triangles into the color & stencil buffer.
|
||||
glDepthRange(strata + strata_epsilon, 1.0f);
|
||||
bucket.drawElements(*patternShader);
|
||||
} else {
|
||||
// Only draw the fill when it's either opaque and we're drawing opaque
|
||||
// fragments or when it's translucent and we're drawing translucent
|
||||
// fragments
|
||||
// Draw filling rectangle.
|
||||
useProgram(plainShader->program);
|
||||
plainShader->setMatrix(matrix);
|
||||
plainShader->setColor(fill_color);
|
||||
|
||||
// Draw the actual triangles into the color & stencil buffer.
|
||||
glDepthRange(strata + strata_epsilon, 1.0f);
|
||||
bucket.drawElements(*plainShader);
|
||||
}
|
||||
}
|
||||
|
||||
// Because we're drawing top-to-bottom, and we update the stencil mask
|
||||
// below, we have to draw the outline first (!)
|
||||
if (fringeline && pass == Translucent) {
|
||||
useProgram(outlineShader->program);
|
||||
outlineShader->setMatrix(matrix);
|
||||
lineWidth(2.0f); // This is always fixed and does not depend on the pixelRatio!
|
||||
|
||||
outlineShader->setColor(fill_color);
|
||||
|
||||
// Draw the entire line
|
||||
outlineShader->setWorld({{
|
||||
static_cast<float>(map.getState().getFramebufferWidth()),
|
||||
static_cast<float>(map.getState().getFramebufferHeight())
|
||||
}});
|
||||
|
||||
glDepthRange(strata + strata_epsilon, 1.0f);
|
||||
bucket.drawVertices(*outlineShader);
|
||||
}
|
||||
|
||||
translateLayer(properties.translate, true);
|
||||
}
|
||||
92
src/renderer/painter_line.cpp
Normal file
92
src/renderer/painter_line.cpp
Normal file
@@ -0,0 +1,92 @@
|
||||
#include <llmr/renderer/painter.hpp>
|
||||
#include <llmr/renderer/line_bucket.hpp>
|
||||
#include <llmr/map/map.hpp>
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
void Painter::renderLine(LineBucket& bucket, const std::string& layer_name, const Tile::ID& /*id*/) {
|
||||
// Abort early.
|
||||
if (pass == Opaque) return;
|
||||
if (!bucket.hasData()) return;
|
||||
|
||||
auto line_properties = map.getStyle().computed.lines;
|
||||
auto line_properties_it = line_properties.find(layer_name);
|
||||
if (line_properties_it == line_properties.end()) return;
|
||||
|
||||
const LineProperties& properties = line_properties_it->second;
|
||||
if (!properties.enabled) return;
|
||||
|
||||
float width = properties.width;
|
||||
float offset = properties.offset / 2;
|
||||
|
||||
// These are the radii of the line. We are limiting it to 16, which will result
|
||||
// in a point size of 64 on retina.
|
||||
float inset = fmin((fmax(-1, offset - width / 2 - 0.5) + 1), 16.0f);
|
||||
float outset = fmin(offset + width / 2 + 0.5, 16.0f);
|
||||
|
||||
Color color = properties.color;
|
||||
color[0] *= properties.opacity;
|
||||
color[1] *= properties.opacity;
|
||||
color[2] *= properties.opacity;
|
||||
color[3] *= properties.opacity;
|
||||
|
||||
float dash_length = properties.dash_array[0];
|
||||
float dash_gap = properties.dash_array[1];
|
||||
|
||||
translateLayer(properties.translate);
|
||||
|
||||
glDepthRange(strata, 1.0f);
|
||||
|
||||
// We're only drawing end caps + round line joins if the line is > 2px. Otherwise, they aren't visible anyway.
|
||||
if (bucket.hasPoints() && outset > 1.0f) {
|
||||
useProgram(linejoinShader->program);
|
||||
linejoinShader->setMatrix(matrix);
|
||||
linejoinShader->setColor(color);
|
||||
linejoinShader->setWorld({{
|
||||
map.getState().getFramebufferWidth() * 0.5f,
|
||||
map.getState().getFramebufferHeight() * 0.5f
|
||||
}
|
||||
});
|
||||
linejoinShader->setLineWidth({{
|
||||
((outset - 0.25f) * map.getState().getPixelRatio()),
|
||||
((inset - 0.25f) * map.getState().getPixelRatio())
|
||||
}
|
||||
});
|
||||
|
||||
float pointSize = ceil(map.getState().getPixelRatio() * outset * 2.0);
|
||||
#if defined(GL_ES_VERSION_2_0)
|
||||
linejoinShader->setSize(pointSize);
|
||||
#else
|
||||
glPointSize(pointSize);
|
||||
#endif
|
||||
bucket.drawPoints(*linejoinShader);
|
||||
}
|
||||
|
||||
// var imagePos = properties.image && imageSprite.getPosition(properties.image);
|
||||
bool imagePos = false;
|
||||
if (imagePos) {
|
||||
// var factor = 8 / Math.pow(2, painter.transform.zoom - params.z);
|
||||
|
||||
// imageSprite.bind(gl, true);
|
||||
|
||||
// //factor = Math.pow(2, 4 - painter.transform.zoom + params.z);
|
||||
// gl.switchShader(painter.linepatternShader, painter.translatedMatrix || painter.posMatrix, painter.extrudeMatrix);
|
||||
// shader = painter.linepatternShader;
|
||||
// glUniform2fv(painter.linepatternShader.u_pattern_size, [imagePos.size[0] * factor, imagePos.size[1] ]);
|
||||
// glUniform2fv(painter.linepatternShader.u_pattern_tl, imagePos.tl);
|
||||
// glUniform2fv(painter.linepatternShader.u_pattern_br, imagePos.br);
|
||||
// glUniform1f(painter.linepatternShader.u_fade, painter.transform.z % 1.0);
|
||||
|
||||
} else {
|
||||
useProgram(lineShader->program);
|
||||
lineShader->setMatrix(matrix);
|
||||
lineShader->setExtrudeMatrix(extrudeMatrix);
|
||||
lineShader->setDashArray({{ dash_length, dash_gap }});
|
||||
lineShader->setLineWidth({{ outset, inset }});
|
||||
lineShader->setRatio(map.getState().getPixelRatio());
|
||||
lineShader->setColor(color);
|
||||
bucket.drawLines(*lineShader);
|
||||
}
|
||||
|
||||
translateLayer(properties.translate, true);
|
||||
}
|
||||
80
src/renderer/painter_point.cpp
Normal file
80
src/renderer/painter_point.cpp
Normal file
@@ -0,0 +1,80 @@
|
||||
#include <llmr/renderer/painter.hpp>
|
||||
#include <llmr/renderer/point_bucket.hpp>
|
||||
#include <llmr/map/map.hpp>
|
||||
#include <llmr/style/sprite.hpp>
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
void Painter::renderPoint(PointBucket& bucket, const std::string& layer_name, const Tile::ID& /*id&*/) {
|
||||
// Abort early.
|
||||
if (!bucket.hasData()) return;
|
||||
if (pass == Opaque) return;
|
||||
|
||||
auto point_properties = map.getStyle().computed.points;
|
||||
auto point_properties_it = point_properties.find(layer_name);
|
||||
if (point_properties_it == point_properties.end()) return;
|
||||
|
||||
const PointProperties& properties = point_properties_it->second;
|
||||
if (!properties.enabled) return;
|
||||
|
||||
translateLayer(properties.translate);
|
||||
|
||||
Color color = properties.color;
|
||||
color[0] *= properties.opacity;
|
||||
color[1] *= properties.opacity;
|
||||
color[2] *= properties.opacity;
|
||||
color[3] *= properties.opacity;
|
||||
|
||||
auto &sprite = map.getStyle().sprite;
|
||||
ImagePosition imagePos;
|
||||
|
||||
if (properties.image.length() && sprite && sprite->isLoaded()) {
|
||||
std::string sized_image = properties.image;
|
||||
if (properties.size) {
|
||||
sized_image.append("-");
|
||||
sized_image.append(std::to_string(static_cast<int>(std::round(properties.size))));
|
||||
}
|
||||
imagePos = sprite->getPosition(sized_image, false);
|
||||
}
|
||||
|
||||
if (!imagePos.size) {
|
||||
useProgram(dotShader->program);
|
||||
dotShader->setMatrix(matrix);
|
||||
dotShader->setColor(color);
|
||||
|
||||
const float pointSize = (properties.radius ? properties.radius * 2 : 8) * map.getState().getPixelRatio();
|
||||
#if defined(GL_ES_VERSION_2_0)
|
||||
dotShader->setSize(pointSize);
|
||||
#else
|
||||
glPointSize(pointSize);
|
||||
glEnable(GL_POINT_SPRITE);
|
||||
#endif
|
||||
dotShader->setBlur((properties.blur ? properties.blur : 1.5) / pointSize);
|
||||
|
||||
glDepthRange(strata, 1.0f);
|
||||
bucket.drawPoints(*dotShader);
|
||||
} else {
|
||||
useProgram(pointShader->program);
|
||||
pointShader->setMatrix(matrix);
|
||||
pointShader->setColor(color);
|
||||
|
||||
pointShader->setImage(0);
|
||||
pointShader->setPointTopLeft({{ imagePos.tl.x, imagePos.tl.y }});
|
||||
pointShader->setPointBottomRight({{ imagePos.br.x, imagePos.br.y }});
|
||||
|
||||
sprite->raster.bind(map.getState().isChanging());
|
||||
|
||||
const float pointSize = (properties.size ? properties.size : (imagePos.size.x / map.getState().getPixelRatio())) * 1.4142135623730951 * map.getState().getPixelRatio();
|
||||
#if defined(GL_ES_VERSION_2_0)
|
||||
pointShader->setSize(pointSize);
|
||||
#else
|
||||
glPointSize(pointSize);
|
||||
glEnable(GL_POINT_SPRITE);
|
||||
#endif
|
||||
|
||||
glDepthRange(strata, 1.0f);
|
||||
bucket.drawPoints(*pointShader);
|
||||
}
|
||||
|
||||
translateLayer(properties.translate, true);
|
||||
}
|
||||
28
src/renderer/painter_raster.cpp
Normal file
28
src/renderer/painter_raster.cpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#include <llmr/renderer/painter.hpp>
|
||||
#include <llmr/renderer/raster_bucket.hpp>
|
||||
#include <llmr/map/map.hpp>
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
void Painter::renderRaster(RasterBucket& bucket, const std::string& layer_name, const Tile::ID& /*id*/) {
|
||||
if (pass == Translucent) return;
|
||||
|
||||
auto raster_properties = map.getStyle().computed.rasters;
|
||||
auto raster_properties_it = raster_properties.find(layer_name);
|
||||
if (raster_properties_it == raster_properties.end()) return;
|
||||
|
||||
const RasterProperties& properties = raster_properties_it->second;
|
||||
if (!properties.enabled) return;
|
||||
|
||||
depthMask(false);
|
||||
|
||||
useProgram(rasterShader->program);
|
||||
rasterShader->setMatrix(matrix);
|
||||
rasterShader->setOpacity(1);
|
||||
// rasterShader->setOpacity(properties.opacity * tile_data->raster->opacity);
|
||||
|
||||
glDepthRange(strata + strata_epsilon, 1.0f);
|
||||
bucket.drawRaster(*rasterShader, tileStencilBuffer, coveringRasterArray);
|
||||
|
||||
depthMask(true);
|
||||
}
|
||||
114
src/renderer/painter_text.cpp
Normal file
114
src/renderer/painter_text.cpp
Normal file
@@ -0,0 +1,114 @@
|
||||
#include <llmr/renderer/painter.hpp>
|
||||
#include <llmr/renderer/text_bucket.hpp>
|
||||
#include <llmr/map/map.hpp>
|
||||
|
||||
using namespace llmr;
|
||||
|
||||
void Painter::renderText(TextBucket& bucket, const std::string& layer_name, const Tile::ID& /*id*/) {
|
||||
// Abort early.
|
||||
if (pass == Opaque) return;
|
||||
if (!bucket.hasData()) return;
|
||||
|
||||
auto text_properties = map.getStyle().computed.texts;
|
||||
auto text_properties_it = text_properties.find(layer_name);
|
||||
if (text_properties_it == text_properties.end()) return;
|
||||
|
||||
const TextProperties& properties = text_properties_it->second;
|
||||
if (!properties.enabled) return;
|
||||
|
||||
translateLayer(properties.translate);
|
||||
|
||||
mat4 exMatrix;
|
||||
matrix::copy(exMatrix, projMatrix);
|
||||
if (bucket.geom_desc.path == TextPathType::Curve) {
|
||||
matrix::rotate_z(exMatrix, exMatrix, map.getState().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);
|
||||
|
||||
map.getGlyphAtlas().bind();
|
||||
textShader->setTextureSize({{static_cast<float>(map.getGlyphAtlas().width),
|
||||
static_cast<float>(map.getGlyphAtlas().height)}});
|
||||
|
||||
textShader->setGamma(2.5f / fontSize / map.getState().getPixelRatio());
|
||||
|
||||
// Convert the -pi..pi to an int8 range.
|
||||
float angle = round((map.getState().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((map.getState().getNormalizedZoom() - zoomAdjust) * 10); // current zoom level
|
||||
|
||||
// Label fading
|
||||
const time duration = 300_milliseconds;
|
||||
const time currentTime = util::now();
|
||||
|
||||
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].timestamp + duration < currentTime) {
|
||||
history.pop_front();
|
||||
}
|
||||
|
||||
if (history[1].timestamp + 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.timestamp - history[1].timestamp;
|
||||
if (timeDiff > duration) timeDiff = 1;
|
||||
float fadedist = zoomDiff / (timeDiff / duration);
|
||||
|
||||
if (isnan(fadedist)) fprintf(stderr, "fadedist should never be NaN\n");
|
||||
|
||||
// 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.timestamp) / duration * fadedist;
|
||||
|
||||
textShader->setFadeDist(fadedist * 10);
|
||||
textShader->setMinFadeZoom(floor(lowZ * 10));
|
||||
textShader->setMaxFadeZoom(floor(highZ * 10));
|
||||
textShader->setFadeZoom((map.getState().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);
|
||||
|
||||
translateLayer(properties.translate, true);
|
||||
}
|
||||
@@ -4,7 +4,6 @@
|
||||
#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>
|
||||
|
||||
@@ -103,6 +103,7 @@ void Style::cascade(float z) {
|
||||
text.haloRadius = layer.haloRadius.evaluate<float>(z);
|
||||
text.rotate = layer.rotate.evaluate<float>(z);
|
||||
text.alwaysVisible = layer.alwaysVisible.evaluate<bool>(z);
|
||||
text.opacity = layer.opacity.evaluate<float>(z);
|
||||
}
|
||||
|
||||
// Cascade raster classes
|
||||
|
||||
Reference in New Issue
Block a user