[core] Switch from background to foreground placement

- Background placement code now just generates static symbol buffers
 - Don't render GeometryTiles until their symbols are loaded. This is necessary for the CrossTileSymbolIndex to successfully prevent flicker.
 - SymbolInstances are transferred to SymbolBucket for use on foreground during collision detection
 - Symbols are sorted on foreground by sorting their index buffer but leaving vertex buffers intact (only works within one segment)
 - Vertical glyphs are generated at same time as horizontal glyphs. `reprojectLineLabels` chooses which one to use at render time and hides the other.
 - Icons are now always represented with a single collision box, even if they're placed along a line (this means their rotation alignment may be wrong, but the approach of representing them with multiple collision boxes wasn't very accurate either).
 - Generate vertices for new debug collision boxes and collision circles
 - Only add symbols within tile boundaries (reduces work, avoids double-draw)
 - Update symbol_projection.cpp to support line label projection calls from CollisionIndex.
This commit is contained in:
Chris Loer
2017-11-09 13:24:43 -08:00
committed by Chris Loer
parent 9a5b2fdfc3
commit c0cb210ddc
21 changed files with 696 additions and 430 deletions

View File

@@ -11,7 +11,6 @@ SymbolInstance::SymbolInstance(Anchor& anchor_,
optional<PositionedIcon> shapedIcon,
const SymbolLayoutProperties::Evaluated& layout,
const float layoutTextSize,
const bool addToBuffers,
const uint32_t index_,
const float textBoxScale,
const float textPadding,
@@ -19,11 +18,12 @@ SymbolInstance::SymbolInstance(Anchor& anchor_,
const std::array<float, 2> textOffset_,
const float iconBoxScale,
const float iconPadding,
const SymbolPlacementType iconPlacement,
const std::array<float, 2> iconOffset_,
const GlyphPositionMap& positions,
const IndexedSubfeature& indexedFeature,
const std::size_t featureIndex_) :
const std::size_t featureIndex_,
const std::u16string& key_,
const float overscaling) :
anchor(anchor_),
line(line_),
index(index_),
@@ -31,25 +31,22 @@ SymbolInstance::SymbolInstance(Anchor& anchor_,
hasIcon(shapedIcon),
// Create the collision features that will be used to check whether this symbol instance can be placed
textCollisionFeature(line_, anchor, shapedTextOrientations.second ?: shapedTextOrientations.first, textBoxScale, textPadding, textPlacement, indexedFeature),
iconCollisionFeature(line_, anchor, shapedIcon, iconBoxScale, iconPadding, iconPlacement, indexedFeature),
textCollisionFeature(line_, anchor, shapedTextOrientations.first, textBoxScale, textPadding, textPlacement, indexedFeature, overscaling),
iconCollisionFeature(line_, anchor, shapedIcon, iconBoxScale, iconPadding, SymbolPlacementType::Point, indexedFeature),
featureIndex(featureIndex_),
textOffset(textOffset_),
iconOffset(iconOffset_) {
iconOffset(iconOffset_),
key(key_) {
// Create the quads used for rendering the icon and glyphs.
if (addToBuffers) {
if (shapedIcon) {
iconQuad = getIconQuad(*shapedIcon, layout, layoutTextSize, shapedTextOrientations.first);
}
if (shapedTextOrientations.first) {
auto quads = getGlyphQuads(shapedTextOrientations.first, layout, textPlacement, positions);
glyphQuads.insert(glyphQuads.end(), quads.begin(), quads.end());
}
if (shapedTextOrientations.second) {
auto quads = getGlyphQuads(shapedTextOrientations.second, layout, textPlacement, positions);
glyphQuads.insert(glyphQuads.end(), quads.begin(), quads.end());
}
if (shapedIcon) {
iconQuad = getIconQuad(*shapedIcon, layout, layoutTextSize, shapedTextOrientations.first);
}
if (shapedTextOrientations.first) {
horizontalGlyphQuads = getGlyphQuads(shapedTextOrientations.first, layout, textPlacement, positions);
}
if (shapedTextOrientations.second) {
verticalGlyphQuads = getGlyphQuads(shapedTextOrientations.second, layout, textPlacement, positions);
}
if (shapedTextOrientations.first && shapedTextOrientations.second) {

View File

@@ -5,6 +5,7 @@
#include <mbgl/text/collision_feature.hpp>
#include <mbgl/style/layers/symbol_layer_properties.hpp>
namespace mbgl {
class Anchor;
@@ -18,7 +19,6 @@ public:
optional<PositionedIcon> shapedIcon,
const style::SymbolLayoutProperties::Evaluated&,
const float layoutTextSize,
const bool inside,
const uint32_t index,
const float textBoxScale,
const float textPadding,
@@ -26,18 +26,20 @@ public:
const std::array<float, 2> textOffset,
const float iconBoxScale,
const float iconPadding,
style::SymbolPlacementType iconPlacement,
const std::array<float, 2> iconOffset,
const GlyphPositionMap&,
const IndexedSubfeature&,
const std::size_t featureIndex);
const std::size_t featureIndex,
const std::u16string& key,
const float overscaling);
Anchor anchor;
GeometryCoordinates line;
uint32_t index;
bool hasText;
bool hasIcon;
SymbolQuads glyphQuads;
SymbolQuads horizontalGlyphQuads;
SymbolQuads verticalGlyphQuads;
optional<SymbolQuad> iconQuad;
CollisionFeature textCollisionFeature;
CollisionFeature iconCollisionFeature;
@@ -45,6 +47,12 @@ public:
std::size_t featureIndex;
std::array<float, 2> textOffset;
std::array<float, 2> iconOffset;
std::u16string key;
bool isDuplicate;
optional<size_t> placedTextIndex;
optional<size_t> placedVerticalTextIndex;
optional<size_t> placedIconIndex;
uint32_t crossTileID = 0;
};
} // namespace mbgl

View File

@@ -8,7 +8,6 @@
#include <mbgl/renderer/image_atlas.hpp>
#include <mbgl/style/layers/symbol_layer_impl.hpp>
#include <mbgl/text/get_anchors.hpp>
#include <mbgl/text/collision_tile.hpp>
#include <mbgl/text/shaping.hpp>
#include <mbgl/util/constants.hpp>
#include <mbgl/util/utf.hpp>
@@ -43,8 +42,8 @@ SymbolLayout::SymbolLayout(const BucketParameters& parameters,
std::unique_ptr<GeometryTileLayer> sourceLayer_,
ImageDependencies& imageDependencies,
GlyphDependencies& glyphDependencies)
: sourceLayer(std::move(sourceLayer_)),
bucketName(layers.at(0)->getID()),
: bucketName(layers.at(0)->getID()),
sourceLayer(std::move(sourceLayer_)),
overscaling(parameters.tileID.overscaleFactor()),
zoom(parameters.tileID.overscaledZ),
mode(parameters.mode),
@@ -179,7 +178,8 @@ bool SymbolLayout::hasSymbolInstances() const {
}
void SymbolLayout::prepare(const GlyphMap& glyphMap, const GlyphPositions& glyphPositions,
const ImageMap& imageMap, const ImagePositions& imagePositions) {
const ImageMap& imageMap, const ImagePositions& imagePositions,
const OverscaledTileID& tileID, const std::string& sourceID) {
const bool textAlongLine = layout.get<TextRotationAlignment>() == AlignmentType::Map &&
layout.get<SymbolPlacement>() == SymbolPlacementType::Line;
@@ -248,7 +248,7 @@ void SymbolLayout::prepare(const GlyphMap& glyphMap, const GlyphPositions& glyph
// if either shapedText or icon position is present, add the feature
if (shapedTextOrientations.first || shapedIcon) {
addFeature(std::distance(features.begin(), it), feature, shapedTextOrientations, shapedIcon, glyphPositionMap);
addFeature(std::distance(features.begin(), it), feature, shapedTextOrientations, shapedIcon, glyphPositionMap, tileID, sourceID);
}
feature.geometry.clear();
@@ -261,7 +261,9 @@ void SymbolLayout::addFeature(const std::size_t index,
const SymbolFeature& feature,
const std::pair<Shaping, Shaping>& shapedTextOrientations,
optional<PositionedIcon> shapedIcon,
const GlyphPositionMap& glyphPositionMap) {
const GlyphPositionMap& glyphPositionMap,
const OverscaledTileID& tileID,
const std::string& sourceID) {
const float minScale = 0.5f;
const float glyphSize = 24.0f;
@@ -288,12 +290,10 @@ void SymbolLayout::addFeature(const std::size_t index,
const SymbolPlacementType textPlacement = layout.get<TextRotationAlignment>() != AlignmentType::Map
? SymbolPlacementType::Point
: layout.get<SymbolPlacement>();
const SymbolPlacementType iconPlacement = layout.get<IconRotationAlignment>() != AlignmentType::Map
? SymbolPlacementType::Point
: layout.get<SymbolPlacement>();
const float textRepeatDistance = symbolSpacing / 2;
IndexedSubfeature indexedFeature = { feature.index, sourceLayer->getName(), bucketName,
symbolInstances.size() };
IndexedSubfeature indexedFeature(feature.index, sourceLayer->getName(), bucketName, symbolInstances.size(),
sourceID, tileID.canonical.z, tileID.canonical.x, tileID.canonical.y);
auto addSymbolInstance = [&] (const GeometryCoordinates& line, Anchor& anchor) {
// https://github.com/mapbox/vector-tile-spec/tree/master/2.1#41-layers
@@ -314,14 +314,16 @@ void SymbolLayout::addFeature(const std::size_t index,
if (avoidEdges && !inside) return;
const bool addToBuffers = mode == MapMode::Still || withinPlus0;
symbolInstances.emplace_back(anchor, line, shapedTextOrientations, shapedIcon,
layout.evaluate(zoom, feature), layoutTextSize,
addToBuffers, symbolInstances.size(),
textBoxScale, textPadding, textPlacement, textOffset,
iconBoxScale, iconPadding, iconPlacement, iconOffset,
glyphPositionMap, indexedFeature, index);
// TODO set this to make api-gl work
const bool singleTileMode = false && mode == MapMode::Still;
if (singleTileMode || withinPlus0) {
symbolInstances.emplace_back(anchor, line, shapedTextOrientations, shapedIcon,
layout.evaluate(zoom, feature), layoutTextSize,
symbolInstances.size(),
textBoxScale, textPadding, textPlacement, textOffset,
iconBoxScale, iconPadding, iconOffset,
glyphPositionMap, indexedFeature, index, feature.text ? *feature.text : std::u16string{}, overscaling);
}
};
const auto& type = feature.getType();
@@ -392,108 +394,93 @@ bool SymbolLayout::anchorIsTooClose(const std::u16string& text, const float repe
return false;
}
std::unique_ptr<SymbolBucket> SymbolLayout::place(CollisionTile& collisionTile) {
auto bucket = std::make_unique<SymbolBucket>(layout, layerPaintProperties, textSize, iconSize, zoom, sdfIcons, iconsNeedLinear);
// Calculate which labels can be shown and when they can be shown and
// create the bufers used for rendering.
const SymbolPlacementType textPlacement = layout.get<TextRotationAlignment>() != AlignmentType::Map
? SymbolPlacementType::Point
: layout.get<SymbolPlacement>();
const SymbolPlacementType iconPlacement = layout.get<IconRotationAlignment>() != AlignmentType::Map
? SymbolPlacementType::Point
: layout.get<SymbolPlacement>();
// Analog of `addToLineVertexArray` in JS. This version doesn't need to build up a line array like the
// JS version does, but it uses the same logic to calculate tile distances.
std::vector<float> CalculateTileDistances(const GeometryCoordinates& line, const Anchor& anchor) {
std::vector<float> tileDistances(line.size());
if (anchor.segment != -1) {
auto sumForwardLength = util::dist<float>(anchor.point, line[anchor.segment + 1]);
auto sumBackwardLength = util::dist<float>(anchor.point, line[anchor.segment]);
for (size_t i = anchor.segment + 1; i < line.size(); i++) {
tileDistances[i] = sumForwardLength;
if (i < line.size() - 1) {
sumForwardLength += util::dist<float>(line[i + 1], line[i]);
}
}
for (auto i = anchor.segment; i >= 0; i--) {
tileDistances[i] = sumBackwardLength;
if (i > 0) {
sumBackwardLength += util::dist<float>(line[i - 1], line[i]);
}
}
}
return tileDistances;
}
std::unique_ptr<SymbolBucket> SymbolLayout::place(const bool showCollisionBoxes) {
const bool mayOverlap = layout.get<TextAllowOverlap>() || layout.get<IconAllowOverlap>() ||
layout.get<TextIgnorePlacement>() || layout.get<IconIgnorePlacement>();
auto bucket = std::make_unique<SymbolBucket>(layout, layerPaintProperties, textSize, iconSize, zoom, sdfIcons, iconsNeedLinear, mayOverlap, std::move(symbolInstances));
const bool keepUpright = layout.get<TextKeepUpright>();
// Sort symbols by their y position on the canvas so that they lower symbols
// are drawn on top of higher symbols.
// Don't sort symbols that won't overlap because it isn't necessary and
// because it causes more labels to pop in and out when rotating.
if (mayOverlap) {
const float sin = std::sin(collisionTile.config.angle);
const float cos = std::cos(collisionTile.config.angle);
std::sort(symbolInstances.begin(), symbolInstances.end(), [sin, cos](SymbolInstance &a, SymbolInstance &b) {
const int32_t aRotated = sin * a.anchor.point.x + cos * a.anchor.point.y;
const int32_t bRotated = sin * b.anchor.point.x + cos * b.anchor.point.y;
return aRotated != bRotated ?
aRotated < bRotated :
a.index > b.index;
});
}
for (SymbolInstance &symbolInstance : symbolInstances) {
for (SymbolInstance &symbolInstance : bucket->symbolInstances) {
const bool hasText = symbolInstance.hasText;
const bool hasIcon = symbolInstance.hasIcon;
const bool iconWithoutText = layout.get<TextOptional>() || !hasText;
const bool textWithoutIcon = layout.get<IconOptional>() || !hasIcon;
// Calculate the scales at which the text and icon can be placed without collision.
float glyphScale = hasText ?
collisionTile.placeFeature(symbolInstance.textCollisionFeature,
layout.get<TextAllowOverlap>(), layout.get<SymbolAvoidEdges>()) :
collisionTile.minScale;
float iconScale = hasIcon ?
collisionTile.placeFeature(symbolInstance.iconCollisionFeature,
layout.get<IconAllowOverlap>(), layout.get<SymbolAvoidEdges>()) :
collisionTile.minScale;
// Combine the scales for icons and text.
if (!iconWithoutText && !textWithoutIcon) {
iconScale = glyphScale = util::max(iconScale, glyphScale);
} else if (!textWithoutIcon && glyphScale) {
glyphScale = util::max(iconScale, glyphScale);
} else if (!iconWithoutText && iconScale) {
iconScale = util::max(iconScale, glyphScale);
}
const auto& feature = features.at(symbolInstance.featureIndex);
// Insert final placement into collision tree and add glyphs/icons to buffers
if (hasText) {
const float placementZoom = util::max(util::log2(glyphScale) + zoom, 0.0f);
collisionTile.insertFeature(symbolInstance.textCollisionFeature, glyphScale, layout.get<TextIgnorePlacement>());
if (glyphScale < collisionTile.maxScale) {
const Range<float> sizeData = bucket->textSizeBinder->getVertexSizeData(feature);
bucket->text.placedSymbols.emplace_back(symbolInstance.anchor.point, symbolInstance.anchor.segment, sizeData.min, sizeData.max,
symbolInstance.textOffset, symbolInstance.writingModes, symbolInstance.line, CalculateTileDistances(symbolInstance.line, symbolInstance.anchor));
symbolInstance.placedTextIndex = bucket->text.placedSymbols.size() - 1;
PlacedSymbol& horizontalSymbol = bucket->text.placedSymbols.back();
const float labelAngle = std::fmod((symbolInstance.anchor.angle + collisionTile.config.angle) + 2 * M_PI, 2 * M_PI);
const bool inVerticalRange = (
(labelAngle > M_PI * 1.0 / 4.0 && labelAngle <= M_PI * 3.0 / 4) ||
(labelAngle > M_PI * 5.0 / 4.0 && labelAngle <= M_PI * 7.0 / 4));
const bool useVerticalMode = symbolInstance.writingModes & WritingModeType::Vertical && inVerticalRange;
const Range<float> sizeData = bucket->textSizeBinder->getVertexSizeData(feature);
bool firstHorizontal = true;
for (const auto& symbol : symbolInstance.horizontalGlyphQuads) {
size_t index = addSymbol(
bucket->text, sizeData, symbol,
symbolInstance.anchor, horizontalSymbol);
if (firstHorizontal) {
horizontalSymbol.vertexStartIndex = index;
firstHorizontal = false;
}
}
if (symbolInstance.writingModes & WritingModeType::Vertical) {
bucket->text.placedSymbols.emplace_back(symbolInstance.anchor.point, symbolInstance.anchor.segment, sizeData.min, sizeData.max,
symbolInstance.textOffset, placementZoom, useVerticalMode, symbolInstance.line);
for (const auto& symbol : symbolInstance.glyphQuads) {
addSymbol(
bucket->text, sizeData, symbol, placementZoom,
keepUpright, textPlacement, symbolInstance.anchor, bucket->text.placedSymbols.back());
symbolInstance.textOffset, WritingModeType::Vertical, symbolInstance.line, CalculateTileDistances(symbolInstance.line, symbolInstance.anchor));
symbolInstance.placedVerticalTextIndex = bucket->text.placedSymbols.size() - 1;
PlacedSymbol& verticalSymbol = bucket->text.placedSymbols.back();
bool firstVertical = true;
for (const auto& symbol : symbolInstance.verticalGlyphQuads) {
size_t index = addSymbol(
bucket->text, sizeData, symbol,
symbolInstance.anchor, verticalSymbol);
if (firstVertical) {
verticalSymbol.vertexStartIndex = index;
firstVertical = false;
}
}
}
}
if (hasIcon) {
const float placementZoom = util::max(util::log2(iconScale) + zoom, 0.0f);
collisionTile.insertFeature(symbolInstance.iconCollisionFeature, iconScale, layout.get<IconIgnorePlacement>());
if (iconScale < collisionTile.maxScale && symbolInstance.iconQuad) {
if (symbolInstance.iconQuad) {
const Range<float> sizeData = bucket->iconSizeBinder->getVertexSizeData(feature);
bucket->icon.placedSymbols.emplace_back(symbolInstance.anchor.point, symbolInstance.anchor.segment, sizeData.min, sizeData.max,
symbolInstance.iconOffset, placementZoom, false, symbolInstance.line);
addSymbol(
bucket->icon, sizeData, *symbolInstance.iconQuad, placementZoom,
keepUpright, iconPlacement, symbolInstance.anchor, bucket->icon.placedSymbols.back());
symbolInstance.iconOffset, WritingModeType::None, symbolInstance.line, std::vector<float>());
symbolInstance.placedIconIndex = bucket->icon.placedSymbols.size() - 1;
PlacedSymbol& iconSymbol = bucket->icon.placedSymbols.back();
iconSymbol.vertexStartIndex = addSymbol(
bucket->icon, sizeData, *symbolInstance.iconQuad,
symbolInstance.anchor, iconSymbol);
}
}
@@ -503,20 +490,17 @@ std::unique_ptr<SymbolBucket> SymbolLayout::place(CollisionTile& collisionTile)
}
}
if (collisionTile.config.debug) {
addToDebugBuffers(collisionTile, *bucket);
if (showCollisionBoxes) {
addToDebugBuffers(*bucket);
}
return bucket;
}
template <typename Buffer>
void SymbolLayout::addSymbol(Buffer& buffer,
size_t SymbolLayout::addSymbol(Buffer& buffer,
const Range<float> sizeData,
const SymbolQuad& symbol,
const float placementZoom,
const bool keepUpright,
const style::SymbolPlacementType placement,
const Anchor& labelAnchor,
PlacedSymbol& placedSymbol) {
constexpr const uint16_t vertexLength = 4;
@@ -527,11 +511,6 @@ void SymbolLayout::addSymbol(Buffer& buffer,
const auto &br = symbol.br;
const auto &tex = symbol.tex;
if (placement == style::SymbolPlacementType::Line && keepUpright) {
// drop incorrectly oriented glyphs
if ((symbol.writingMode == WritingModeType::Vertical) != placedSymbol.useVerticalMode) return;
}
if (buffer.segments.empty() || buffer.segments.back().vertexLength + vertexLength > std::numeric_limits<uint16_t>::max()) {
buffer.segments.emplace_back(buffer.vertices.vertexSize(), buffer.triangles.indexSize());
}
@@ -548,11 +527,19 @@ void SymbolLayout::addSymbol(Buffer& buffer,
buffer.vertices.emplace_back(SymbolLayoutAttributes::vertex(labelAnchor.point, bl, symbol.glyphOffset.y, tex.x, tex.y + tex.h, sizeData));
buffer.vertices.emplace_back(SymbolLayoutAttributes::vertex(labelAnchor.point, br, symbol.glyphOffset.y, tex.x + tex.w, tex.y + tex.h, sizeData));
auto dynamicVertex = SymbolDynamicLayoutAttributes::vertex(labelAnchor.point, 0, placementZoom);
// Dynamic/Opacity vertices are initialized so that the vertex count always agrees with
// the layout vertex buffer, but they will always be updated before rendering happens
auto dynamicVertex = SymbolDynamicLayoutAttributes::vertex(labelAnchor.point, 0);
buffer.dynamicVertices.emplace_back(dynamicVertex);
buffer.dynamicVertices.emplace_back(dynamicVertex);
buffer.dynamicVertices.emplace_back(dynamicVertex);
buffer.dynamicVertices.emplace_back(dynamicVertex);
auto opacityVertex = SymbolOpacityAttributes::vertex(1.0, 1.0);
buffer.opacityVertices.emplace_back(opacityVertex);
buffer.opacityVertices.emplace_back(opacityVertex);
buffer.opacityVertices.emplace_back(opacityVertex);
buffer.opacityVertices.emplace_back(opacityVertex);
// add the two triangles, referencing the four coordinates we just inserted.
buffer.triangles.emplace_back(index + 0, index + 1, index + 2);
@@ -562,54 +549,62 @@ void SymbolLayout::addSymbol(Buffer& buffer,
segment.indexLength += 6;
placedSymbol.glyphOffsets.push_back(symbol.glyphOffset.x);
return index;
}
void SymbolLayout::addToDebugBuffers(CollisionTile& collisionTile, SymbolBucket& bucket) {
void SymbolLayout::addToDebugBuffers(SymbolBucket& bucket) {
if (!hasSymbolInstances()) {
return;
}
const float yStretch = collisionTile.yStretch;
auto& collisionBox = bucket.collisionBox;
for (const SymbolInstance &symbolInstance : symbolInstances) {
auto populateCollisionBox = [&](const auto& feature) {
SymbolBucket::CollisionBuffer& collisionBuffer = feature.alongLine ?
static_cast<SymbolBucket::CollisionBuffer&>(bucket.collisionCircle) :
static_cast<SymbolBucket::CollisionBuffer&>(bucket.collisionBox);
for (const CollisionBox &box : feature.boxes) {
auto& anchor = box.anchor;
Point<float> tl{box.x1, box.y1 * yStretch};
Point<float> tr{box.x2, box.y1 * yStretch};
Point<float> bl{box.x1, box.y2 * yStretch};
Point<float> br{box.x2, box.y2 * yStretch};
tl = util::matrixMultiply(collisionTile.reverseRotationMatrix, tl);
tr = util::matrixMultiply(collisionTile.reverseRotationMatrix, tr);
bl = util::matrixMultiply(collisionTile.reverseRotationMatrix, bl);
br = util::matrixMultiply(collisionTile.reverseRotationMatrix, br);
const float maxZoom = util::clamp(zoom + util::log2(box.maxScale), util::MIN_ZOOM_F, util::MAX_ZOOM_F);
const float placementZoom = util::clamp(zoom + util::log2(box.placementScale), util::MIN_ZOOM_F, util::MAX_ZOOM_F);
Point<float> tl{box.x1, box.y1};
Point<float> tr{box.x2, box.y1};
Point<float> bl{box.x1, box.y2};
Point<float> br{box.x2, box.y2};
static constexpr std::size_t vertexLength = 4;
static constexpr std::size_t indexLength = 8;
const std::size_t indexLength = feature.alongLine ? 6 : 8;
if (collisionBox.segments.empty() || collisionBox.segments.back().vertexLength + vertexLength > std::numeric_limits<uint16_t>::max()) {
collisionBox.segments.emplace_back(collisionBox.vertices.vertexSize(), collisionBox.lines.indexSize());
if (collisionBuffer.segments.empty() || collisionBuffer.segments.back().vertexLength + vertexLength > std::numeric_limits<uint16_t>::max()) {
collisionBuffer.segments.emplace_back(collisionBuffer.vertices.vertexSize(),
feature.alongLine? bucket.collisionCircle.triangles.indexSize() : bucket.collisionBox.lines.indexSize());
}
auto& segment = collisionBox.segments.back();
auto& segment = collisionBuffer.segments.back();
uint16_t index = segment.vertexLength;
collisionBox.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.anchor.point, tl, maxZoom, placementZoom));
collisionBox.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.anchor.point, tr, maxZoom, placementZoom));
collisionBox.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.anchor.point, br, maxZoom, placementZoom));
collisionBox.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.anchor.point, bl, maxZoom, placementZoom));
collisionBuffer.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.anchor.point, tl));
collisionBuffer.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.anchor.point, tr));
collisionBuffer.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.anchor.point, br));
collisionBuffer.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.anchor.point, bl));
collisionBox.lines.emplace_back(index + 0, index + 1);
collisionBox.lines.emplace_back(index + 1, index + 2);
collisionBox.lines.emplace_back(index + 2, index + 3);
collisionBox.lines.emplace_back(index + 3, index + 0);
// Dynamic vertices are initialized so that the vertex count always agrees with
// the layout vertex buffer, but they will always be updated before rendering happens
auto dynamicVertex = CollisionBoxDynamicAttributes::vertex(false, false);
collisionBuffer.dynamicVertices.emplace_back(dynamicVertex);
collisionBuffer.dynamicVertices.emplace_back(dynamicVertex);
collisionBuffer.dynamicVertices.emplace_back(dynamicVertex);
collisionBuffer.dynamicVertices.emplace_back(dynamicVertex);
if (feature.alongLine) {
bucket.collisionCircle.triangles.emplace_back(index, index + 1, index + 2);
bucket.collisionCircle.triangles.emplace_back(index, index + 2, index + 3);
} else {
bucket.collisionBox.lines.emplace_back(index + 0, index + 1);
bucket.collisionBox.lines.emplace_back(index + 1, index + 2);
bucket.collisionBox.lines.emplace_back(index + 2, index + 3);
bucket.collisionBox.lines.emplace_back(index + 3, index + 0);
}
segment.vertexLength += vertexLength;
segment.indexLength += indexLength;

View File

@@ -16,7 +16,6 @@
namespace mbgl {
class BucketParameters;
class CollisionTile;
class SymbolBucket;
class Anchor;
class RenderLayer;
@@ -35,42 +34,44 @@ public:
GlyphDependencies&);
void prepare(const GlyphMap&, const GlyphPositions&,
const ImageMap&, const ImagePositions&);
const ImageMap&, const ImagePositions&,
const OverscaledTileID&, const std::string&);
std::unique_ptr<SymbolBucket> place(CollisionTile&);
std::unique_ptr<SymbolBucket> place(const bool showCollisionBoxes);
bool hasSymbolInstances() const;
std::map<std::string,
std::pair<style::IconPaintProperties::PossiblyEvaluated, style::TextPaintProperties::PossiblyEvaluated>> layerPaintProperties;
const std::string bucketName;
std::vector<SymbolInstance> symbolInstances;
private:
void addFeature(const size_t,
const SymbolFeature&,
const std::pair<Shaping, Shaping>& shapedTextOrientations,
optional<PositionedIcon> shapedIcon,
const GlyphPositionMap&);
const GlyphPositionMap&,
const OverscaledTileID&,
const std::string&);
bool anchorIsTooClose(const std::u16string& text, const float repeatDistance, const Anchor&);
std::map<std::u16string, std::vector<Anchor>> compareText;
void addToDebugBuffers(CollisionTile&, SymbolBucket&);
void addToDebugBuffers(SymbolBucket&);
// Adds placed items to the buffer.
template <typename Buffer>
void addSymbol(Buffer&,
size_t addSymbol(Buffer&,
const Range<float> sizeData,
const SymbolQuad&,
float scale,
const bool keepUpright,
const style::SymbolPlacementType,
const Anchor& labelAnchor,
PlacedSymbol& placedSymbol);
// Stores the layer so that we can hold on to GeometryTileFeature instances in SymbolFeature,
// which may reference data from this object.
const std::unique_ptr<GeometryTileLayer> sourceLayer;
const std::string bucketName;
const float overscaling;
const float zoom;
const MapMode mode;
@@ -87,7 +88,6 @@ private:
style::TextSize::UnevaluatedType textSize;
style::IconSize::UnevaluatedType iconSize;
std::vector<SymbolInstance> symbolInstances;
std::vector<SymbolFeature> features;
BiDi bidi; // Consider moving this up to geometry tile worker to reduce reinstantiation costs; use of BiDi/ubiditransform object must be constrained to one thread

View File

@@ -3,7 +3,6 @@
#include <mbgl/renderer/render_tile.hpp>
#include <mbgl/renderer/buckets/symbol_bucket.hpp>
#include <mbgl/renderer/layers/render_symbol_layer.hpp>
#include <mbgl/renderer/frame_history.hpp>
#include <mbgl/util/optional.hpp>
#include <mbgl/util/math.hpp>
@@ -93,9 +92,6 @@ namespace mbgl {
return m;
}
typedef std::pair<Point<float>,float> PointAndCameraDistance;
PointAndCameraDistance project(const Point<float>& point, const mat4& matrix) {
vec4 pos = {{ point.x, point.y, 0, 1 }};
matrix::transformMat4(pos, pos, matrix);
@@ -114,7 +110,7 @@ namespace mbgl {
}
}
bool isVisible(const vec4& anchorPos, const float placementZoom, const std::array<double, 2>& clippingBuffer, const FrameHistory& frameHistory) {
bool isVisible(const vec4& anchorPos, const std::array<double, 2>& clippingBuffer) {
const float x = anchorPos[0] / anchorPos[3];
const float y = anchorPos[1] / anchorPos[3];
const bool inPaddedViewport = (
@@ -122,12 +118,12 @@ namespace mbgl {
x <= clippingBuffer[0] &&
y >= -clippingBuffer[1] &&
y <= clippingBuffer[1]);
return inPaddedViewport && frameHistory.isVisible(placementZoom);
return inPaddedViewport;
}
void addDynamicAttributes(const Point<float>& anchorPoint, const float angle, const float placementZoom,
void addDynamicAttributes(const Point<float>& anchorPoint, const float angle,
gl::VertexVector<SymbolDynamicLayoutAttributes::Vertex>& dynamicVertexArray) {
auto dynamicVertex = SymbolDynamicLayoutAttributes::vertex(anchorPoint, angle, placementZoom);
auto dynamicVertex = SymbolDynamicLayoutAttributes::vertex(anchorPoint, angle);
dynamicVertexArray.emplace_back(dynamicVertex);
dynamicVertexArray.emplace_back(dynamicVertex);
dynamicVertexArray.emplace_back(dynamicVertex);
@@ -137,20 +133,15 @@ namespace mbgl {
void hideGlyphs(size_t numGlyphs, gl::VertexVector<SymbolDynamicLayoutAttributes::Vertex>& dynamicVertexArray) {
const Point<float> offscreenPoint = { -INFINITY, -INFINITY };
for (size_t i = 0; i < numGlyphs; i++) {
addDynamicAttributes(offscreenPoint, 0, 25, dynamicVertexArray);
addDynamicAttributes(offscreenPoint, 0, dynamicVertexArray);
}
}
struct PlacedGlyph {
PlacedGlyph(Point<float> point_, float angle_) : point(point_), angle(angle_) {}
Point<float> point;
float angle;
};
enum PlacementResult {
OK,
NotEnoughRoom,
NeedsFlipping
NeedsFlipping,
UseVertical
};
Point<float> projectTruncatedLineSegment(const Point<float>& previousTilePoint, const Point<float>& currentTilePoint, const Point<float>& previousProjectedPoint, const float minimumLength, const mat4& projectionMatrix) {
@@ -165,7 +156,7 @@ namespace mbgl {
}
optional<PlacedGlyph> placeGlyphAlongLine(const float offsetX, const float lineOffsetX, const float lineOffsetY, const bool flip,
const Point<float>& projectedAnchorPoint, const Point<float>& tileAnchorPoint, const uint16_t anchorSegment, const GeometryCoordinates& line, const mat4& labelPlaneMatrix) {
const Point<float>& projectedAnchorPoint, const Point<float>& tileAnchorPoint, const uint16_t anchorSegment, const GeometryCoordinates& line, const std::vector<float>& tileDistances, const mat4& labelPlaneMatrix, const bool returnTileDistance) {
const float combinedOffsetX = flip ?
offsetX - lineOffsetX :
@@ -185,6 +176,7 @@ namespace mbgl {
int32_t currentIndex = dir > 0 ? anchorSegment : anchorSegment + 1;
const int32_t initialIndex = currentIndex;
Point<float> current = projectedAnchorPoint;
Point<float> prev = projectedAnchorPoint;
float distanceToPrev = 0.0;
@@ -195,7 +187,9 @@ namespace mbgl {
currentIndex += dir;
// offset does not fit on the projected line
if (currentIndex < 0 || currentIndex >= static_cast<int32_t>(line.size())) return {};
if (currentIndex < 0 || currentIndex >= static_cast<int32_t>(line.size())) {
return {};
}
prev = current;
PointAndCameraDistance projection = project(convertPoint<float>(line.at(currentIndex)), labelPlaneMatrix);
@@ -225,7 +219,62 @@ namespace mbgl {
const float segmentAngle = angle + std::atan2(current.y - prev.y, current.x - prev.x);
return {{ p, segmentAngle }};
return {{
p,
segmentAngle,
returnTileDistance ?
TileDistance(
(currentIndex - dir) == initialIndex ? 0 : tileDistances[currentIndex - dir],
absOffsetX - distanceToPrev
) :
optional<TileDistance>()
}};
}
optional<std::pair<PlacedGlyph, PlacedGlyph>> placeFirstAndLastGlyph(const float fontScale,
const float lineOffsetX,
const float lineOffsetY,
const bool flip,
const Point<float>& anchorPoint,
const Point<float>& tileAnchorPoint,
const PlacedSymbol& symbol,
const mat4& labelPlaneMatrix,
const bool returnTileDistance) {
const float firstGlyphOffset = symbol.glyphOffsets.front();
const float lastGlyphOffset = symbol.glyphOffsets.back();;
optional<PlacedGlyph> firstPlacedGlyph = placeGlyphAlongLine(fontScale * firstGlyphOffset, lineOffsetX, lineOffsetY, flip, anchorPoint, tileAnchorPoint, symbol.segment, symbol.line, symbol.tileDistances, labelPlaneMatrix, returnTileDistance);
if (!firstPlacedGlyph)
return optional<std::pair<PlacedGlyph, PlacedGlyph>>();
optional<PlacedGlyph> lastPlacedGlyph = placeGlyphAlongLine(fontScale * lastGlyphOffset, lineOffsetX, lineOffsetY, flip, anchorPoint, tileAnchorPoint, symbol.segment, symbol.line, symbol.tileDistances, labelPlaneMatrix, returnTileDistance);
if (!lastPlacedGlyph)
return optional<std::pair<PlacedGlyph, PlacedGlyph>>();
return std::make_pair(*firstPlacedGlyph, *lastPlacedGlyph);
}
optional<PlacementResult> requiresOrientationChange(const WritingModeType writingModes, const Point<float>& firstPoint, const Point<float>& lastPoint, const float aspectRatio) {
if (writingModes == (WritingModeType::Horizontal | WritingModeType::Vertical)) {
// On top of choosing whether to flip, choose whether to render this version of the glyphs or the alternate
// vertical glyphs. We can't just filter out vertical glyphs in the horizontal range because the horizontal
// and vertical versions can have slightly different projections which could lead to angles where both or
// neither showed.
auto rise = std::abs(lastPoint.y - firstPoint.y);
auto run = std::abs(lastPoint.x - firstPoint.x) * aspectRatio;
if (rise > run) {
return PlacementResult::UseVertical;
}
}
if ((writingModes == WritingModeType::Vertical) ?
(firstPoint.y < lastPoint.y) :
(firstPoint.x > lastPoint.x)) {
// Includes "horizontalOnly" case for labels without vertical glyphs
return PlacementResult::NeedsFlipping;
}
return {};
}
PlacementResult placeGlyphsAlongLine(const PlacedSymbol& symbol,
@@ -236,7 +285,8 @@ namespace mbgl {
const mat4& labelPlaneMatrix,
const mat4& glCoordMatrix,
gl::VertexVector<SymbolDynamicLayoutAttributes::Vertex>& dynamicVertexArray,
const Point<float>& projectedAnchorPoint) {
const Point<float>& projectedAnchorPoint,
const float aspectRatio) {
const float fontScale = fontSize / 24.0;
const float lineOffsetX = symbol.lineOffset[0] * fontSize;
const float lineOffsetY = symbol.lineOffset[1] * fontSize;
@@ -244,33 +294,30 @@ namespace mbgl {
std::vector<PlacedGlyph> placedGlyphs;
if (symbol.glyphOffsets.size() > 1) {
const float firstGlyphOffset = symbol.glyphOffsets.front();
const float lastGlyphOffset = symbol.glyphOffsets.back();
optional<PlacedGlyph> firstPlacedGlyph = placeGlyphAlongLine(fontScale * firstGlyphOffset, lineOffsetX, lineOffsetY, flip, projectedAnchorPoint, symbol.anchorPoint, symbol.segment, symbol.line, labelPlaneMatrix);
if (!firstPlacedGlyph)
const optional<std::pair<PlacedGlyph,PlacedGlyph>> firstAndLastGlyph =
placeFirstAndLastGlyph(fontScale, lineOffsetX, lineOffsetY, flip, projectedAnchorPoint, symbol.anchorPoint, symbol, labelPlaneMatrix, false);
if (!firstAndLastGlyph) {
return PlacementResult::NotEnoughRoom;
optional<PlacedGlyph> lastPlacedGlyph = placeGlyphAlongLine(fontScale * lastGlyphOffset, lineOffsetX, lineOffsetY, flip, projectedAnchorPoint, symbol.anchorPoint, symbol.segment, symbol.line, labelPlaneMatrix);
if (!lastPlacedGlyph)
return PlacementResult::NotEnoughRoom;
const Point<float> firstPoint = project(firstPlacedGlyph->point, glCoordMatrix).first;
const Point<float> lastPoint = project(lastPlacedGlyph->point, glCoordMatrix).first;
if (keepUpright && !flip &&
(symbol.useVerticalMode ? firstPoint.y < lastPoint.y : firstPoint.x > lastPoint.x)) {
return PlacementResult::NeedsFlipping;
}
placedGlyphs.push_back(*firstPlacedGlyph);
const Point<float> firstPoint = project(firstAndLastGlyph->first.point, glCoordMatrix).first;
const Point<float> lastPoint = project(firstAndLastGlyph->second.point, glCoordMatrix).first;
if (keepUpright && !flip) {
auto orientationChange = requiresOrientationChange(symbol.writingModes, firstPoint, lastPoint, aspectRatio);
if (orientationChange) {
return *orientationChange;
}
}
placedGlyphs.push_back(firstAndLastGlyph->first);
for (size_t glyphIndex = 1; glyphIndex < symbol.glyphOffsets.size() - 1; glyphIndex++) {
const float glyphOffsetX = symbol.glyphOffsets[glyphIndex];
// Since first and last glyph fit on the line, we're sure that the rest of the glyphs can be placed
auto placedGlyph = placeGlyphAlongLine(glyphOffsetX * fontScale, lineOffsetX, lineOffsetY, flip, projectedAnchorPoint, symbol.anchorPoint, symbol.segment, symbol.line, labelPlaneMatrix);
auto placedGlyph = placeGlyphAlongLine(glyphOffsetX * fontScale, lineOffsetX, lineOffsetY, flip, projectedAnchorPoint, symbol.anchorPoint, symbol.segment, symbol.line, symbol.tileDistances, labelPlaneMatrix, false);
placedGlyphs.push_back(*placedGlyph);
}
placedGlyphs.push_back(*lastPlacedGlyph);
placedGlyphs.push_back(firstAndLastGlyph->second);
} else {
// Only a single glyph to place
// So, determine whether to flip based on projected angle of the line segment it's on
@@ -285,14 +332,15 @@ namespace mbgl {
projectedVertex.first :
projectTruncatedLineSegment(symbol.anchorPoint,tileSegmentEnd, a, 1, posMatrix);
if (symbol.useVerticalMode ? b.y > a.y : b.x < a.x) {
return PlacementResult::NeedsFlipping;
auto orientationChange = requiresOrientationChange(symbol.writingModes, a, b, aspectRatio);
if (orientationChange) {
return *orientationChange;
}
}
assert(symbol.glyphOffsets.size() == 1); // We are relying on SymbolInstance.hasText filtering out symbols without any glyphs at all
const float glyphOffsetX = symbol.glyphOffsets.front();
optional<PlacedGlyph> singleGlyph = placeGlyphAlongLine(fontScale * glyphOffsetX, lineOffsetX, lineOffsetY, flip, projectedAnchorPoint, symbol.anchorPoint, symbol.segment,
symbol.line, labelPlaneMatrix);
symbol.line, symbol.tileDistances, labelPlaneMatrix, false);
if (!singleGlyph)
return PlacementResult::NotEnoughRoom;
@@ -300,7 +348,7 @@ namespace mbgl {
}
for (auto& placedGlyph : placedGlyphs) {
addDynamicAttributes(placedGlyph.point, placedGlyph.angle, symbol.placementZoom, dynamicVertexArray);
addDynamicAttributes(placedGlyph.point, placedGlyph.angle, dynamicVertexArray);
}
return PlacementResult::OK;
@@ -309,7 +357,7 @@ namespace mbgl {
void reprojectLineLabels(gl::VertexVector<SymbolDynamicLayoutAttributes::Vertex>& dynamicVertexArray, const std::vector<PlacedSymbol>& placedSymbols,
const mat4& posMatrix, const style::SymbolPropertyValues& values,
const RenderTile& tile, const SymbolSizeBinder& sizeBinder, const TransformState& state, const FrameHistory& frameHistory) {
const RenderTile& tile, const SymbolSizeBinder& sizeBinder, const TransformState& state) {
const ZoomEvaluatedSize partiallyEvaluatedSize = sizeBinder.evaluateForZoom(state.getZoom());
@@ -325,19 +373,31 @@ namespace mbgl {
const mat4 glCoordMatrix = getGlCoordMatrix(posMatrix, pitchWithMap, rotateWithMap, state, pixelsToTileUnits);
dynamicVertexArray.clear();
bool useVertical = false;
for (auto& placedSymbol : placedSymbols) {
// Don't do calculations for vertical glyphs unless the previous symbol was horizontal
// and we determined that vertical glyphs were necessary.
// Also don't do calculations for symbols that are collided and fully faded out
if (placedSymbol.hidden || (placedSymbol.writingModes == WritingModeType::Vertical && !useVertical)) {
hideGlyphs(placedSymbol.glyphOffsets.size(), dynamicVertexArray);
continue;
}
// Awkward... but we're counting on the paired "vertical" symbol coming immediately after its horizontal counterpart
useVertical = false;
vec4 anchorPos = {{ placedSymbol.anchorPoint.x, placedSymbol.anchorPoint.y, 0, 1 }};
matrix::transformMat4(anchorPos, anchorPos, posMatrix);
// Don't bother calculating the correct point for invisible labels.
if (!isVisible(anchorPos, placedSymbol.placementZoom, clippingBuffer, frameHistory)) {
if (!isVisible(anchorPos, clippingBuffer)) {
hideGlyphs(placedSymbol.glyphOffsets.size(), dynamicVertexArray);
continue;
}
const float cameraToAnchorDistance = anchorPos[3];
const float perspectiveRatio = 1 + 0.5 * ((cameraToAnchorDistance / state.getCameraToCenterDistance()) - 1.0);
const float perspectiveRatio = 0.5 + 0.5 * (cameraToAnchorDistance / state.getCameraToCenterDistance());
const float fontSize = evaluateSizeForFeature(partiallyEvaluatedSize, placedSymbol);
const float pitchScaledFontSize = values.pitchAlignment == style::AlignmentType::Map ?
@@ -346,11 +406,13 @@ namespace mbgl {
const Point<float> anchorPoint = project(placedSymbol.anchorPoint, labelPlaneMatrix).first;
PlacementResult placeUnflipped = placeGlyphsAlongLine(placedSymbol, pitchScaledFontSize, false /*unflipped*/, values.keepUpright, posMatrix, labelPlaneMatrix, glCoordMatrix, dynamicVertexArray, anchorPoint);
PlacementResult placeUnflipped = placeGlyphsAlongLine(placedSymbol, pitchScaledFontSize, false /*unflipped*/, values.keepUpright, posMatrix, labelPlaneMatrix, glCoordMatrix, dynamicVertexArray, anchorPoint, state.getSize().aspectRatio());
useVertical = placeUnflipped == PlacementResult::UseVertical;
if (placeUnflipped == PlacementResult::NotEnoughRoom ||
if (placeUnflipped == PlacementResult::NotEnoughRoom || useVertical ||
(placeUnflipped == PlacementResult::NeedsFlipping &&
placeGlyphsAlongLine(placedSymbol, pitchScaledFontSize, true /*flipped*/, values.keepUpright, posMatrix, labelPlaneMatrix, glCoordMatrix, dynamicVertexArray, anchorPoint) == PlacementResult::NotEnoughRoom)) {
placeGlyphsAlongLine(placedSymbol, pitchScaledFontSize, true /*flipped*/, values.keepUpright, posMatrix, labelPlaneMatrix, glCoordMatrix, dynamicVertexArray, anchorPoint, state.getSize().aspectRatio()) == PlacementResult::NotEnoughRoom)) {
hideGlyphs(placedSymbol.glyphOffsets.size(), dynamicVertexArray);
}
}

View File

@@ -8,18 +8,48 @@ namespace mbgl {
class TransformState;
class RenderTile;
class FrameHistory;
class SymbolSizeBinder;
class PlacedSymbol;
namespace style {
class SymbolPropertyValues;
} // end namespace style
struct TileDistance {
TileDistance(float prevTileDistance_, float lastSegmentViewportDistance_)
: prevTileDistance(prevTileDistance_), lastSegmentViewportDistance(lastSegmentViewportDistance_)
{}
float prevTileDistance;
float lastSegmentViewportDistance;
};
struct PlacedGlyph {
PlacedGlyph(Point<float> point_, float angle_, optional<TileDistance> tileDistance_)
: point(point_), angle(angle_), tileDistance(std::move(tileDistance_))
{}
Point<float> point;
float angle;
optional<TileDistance> tileDistance;
};
float evaluateSizeForFeature(const ZoomEvaluatedSize& zoomEvaluatedSize, const PlacedSymbol& placedSymbol);
mat4 getLabelPlaneMatrix(const mat4& posMatrix, const bool pitchWithMap, const bool rotateWithMap, const TransformState& state, const float pixelsToTileUnits);
mat4 getGlCoordMatrix(const mat4& posMatrix, const bool pitchWithMap, const bool rotateWithMap, const TransformState& state, const float pixelsToTileUnits);
using PointAndCameraDistance = std::pair<Point<float>,float>;
PointAndCameraDistance project(const Point<float>& point, const mat4& matrix);
void reprojectLineLabels(gl::VertexVector<SymbolDynamicLayoutAttributes::Vertex>&, const std::vector<PlacedSymbol>&,
const mat4& posMatrix, const style::SymbolPropertyValues&,
const RenderTile&, const SymbolSizeBinder& sizeBinder, const TransformState&, const FrameHistory& frameHistory);
const RenderTile&, const SymbolSizeBinder& sizeBinder, const TransformState&);
optional<std::pair<PlacedGlyph, PlacedGlyph>> placeFirstAndLastGlyph(const float fontScale,
const float lineOffsetX,
const float lineOffsetY,
const bool flip,
const Point<float>& anchorPoint,
const Point<float>& tileAnchorPoint,
const PlacedSymbol& symbol,
const mat4& labelPlaneMatrix,
const bool returnTileDistance);
} // end namespace mbgl

View File

@@ -16,10 +16,14 @@ SymbolBucket::SymbolBucket(style::SymbolLayoutProperties::PossiblyEvaluated layo
const style::DataDrivenPropertyValue<float>& iconSize,
float zoom,
bool sdfIcons_,
bool iconsNeedLinear_)
bool iconsNeedLinear_,
bool sortFeaturesByY_,
const std::vector<SymbolInstance>&& symbolInstances_)
: layout(std::move(layout_)),
sdfIcons(sdfIcons_),
iconsNeedLinear(iconsNeedLinear_ || iconSize.isDataDriven() || !iconSize.isZoomConstant()),
sortFeaturesByY(sortFeaturesByY_),
symbolInstances(std::move(symbolInstances_)),
textSizeBinder(SymbolSizeBinder::create(zoom, textSize, TextSize::defaultValue())),
iconSizeBinder(SymbolSizeBinder::create(zoom, iconSize, IconSize::defaultValue())) {
@@ -36,28 +40,84 @@ SymbolBucket::SymbolBucket(style::SymbolLayoutProperties::PossiblyEvaluated layo
void SymbolBucket::upload(gl::Context& context) {
if (hasTextData()) {
text.vertexBuffer = context.createVertexBuffer(std::move(text.vertices));
text.dynamicVertexBuffer = context.createVertexBuffer(std::move(text.dynamicVertices), gl::BufferUsage::StreamDraw);
text.indexBuffer = context.createIndexBuffer(std::move(text.triangles));
if (!staticUploaded) {
text.indexBuffer = context.createIndexBuffer(std::move(text.triangles), sortFeaturesByY ? gl::BufferUsage::StreamDraw : gl::BufferUsage::StaticDraw);
text.vertexBuffer = context.createVertexBuffer(std::move(text.vertices));
} else if (!sortUploaded) {
context.updateIndexBuffer(*text.indexBuffer, std::move(text.triangles));
}
if (!dynamicUploaded) {
text.dynamicVertexBuffer = context.createVertexBuffer(std::move(text.dynamicVertices), gl::BufferUsage::StreamDraw);
}
if (!placementChangesUploaded) {
if (!text.opacityVertexBuffer) {
text.opacityVertexBuffer = context.createVertexBuffer(std::move(text.opacityVertices), gl::BufferUsage::StreamDraw);
} else {
context.updateVertexBuffer(*text.opacityVertexBuffer, std::move(text.opacityVertices));
}
}
}
if (hasIconData()) {
icon.vertexBuffer = context.createVertexBuffer(std::move(icon.vertices));
icon.dynamicVertexBuffer = context.createVertexBuffer(std::move(icon.dynamicVertices), gl::BufferUsage::StreamDraw);
icon.indexBuffer = context.createIndexBuffer(std::move(icon.triangles));
if (!staticUploaded) {
icon.indexBuffer = context.createIndexBuffer(std::move(icon.triangles), sortFeaturesByY ? gl::BufferUsage::StreamDraw : gl::BufferUsage::StaticDraw);
icon.vertexBuffer = context.createVertexBuffer(std::move(icon.vertices));
} else if (!sortUploaded) {
context.updateIndexBuffer(*icon.indexBuffer, std::move(icon.triangles));
}
if (!dynamicUploaded) {
icon.dynamicVertexBuffer = context.createVertexBuffer(std::move(icon.dynamicVertices), gl::BufferUsage::StreamDraw);
}
if (!placementChangesUploaded) {
if (!icon.opacityVertexBuffer) {
icon.opacityVertexBuffer = context.createVertexBuffer(std::move(icon.opacityVertices), gl::BufferUsage::StreamDraw);
} else {
context.updateVertexBuffer(*icon.opacityVertexBuffer, std::move(icon.opacityVertices));
}
}
}
if (!collisionBox.vertices.empty()) {
collisionBox.vertexBuffer = context.createVertexBuffer(std::move(collisionBox.vertices));
collisionBox.indexBuffer = context.createIndexBuffer(std::move(collisionBox.lines));
if (hasCollisionBoxData()) {
if (!staticUploaded) {
collisionBox.indexBuffer = context.createIndexBuffer(std::move(collisionBox.lines));
collisionBox.vertexBuffer = context.createVertexBuffer(std::move(collisionBox.vertices));
}
if (!placementChangesUploaded) {
if (!collisionBox.dynamicVertexBuffer) {
collisionBox.dynamicVertexBuffer = context.createVertexBuffer(std::move(collisionBox.dynamicVertices), gl::BufferUsage::StreamDraw);
} else {
context.updateVertexBuffer(*collisionBox.dynamicVertexBuffer, std::move(collisionBox.dynamicVertices));
}
}
}
if (hasCollisionCircleData()) {
if (!staticUploaded) {
collisionCircle.indexBuffer = context.createIndexBuffer(std::move(collisionCircle.triangles));
collisionCircle.vertexBuffer = context.createVertexBuffer(std::move(collisionCircle.vertices));
}
if (!placementChangesUploaded) {
if (!collisionCircle.dynamicVertexBuffer) {
collisionCircle.dynamicVertexBuffer = context.createVertexBuffer(std::move(collisionCircle.dynamicVertices), gl::BufferUsage::StreamDraw);
} else {
context.updateVertexBuffer(*collisionCircle.dynamicVertexBuffer, std::move(collisionCircle.dynamicVertices));
}
}
}
for (auto& pair : paintPropertyBinders) {
pair.second.first.upload(context);
pair.second.second.upload(context);
if (!staticUploaded) {
for (auto& pair : paintPropertyBinders) {
pair.second.first.upload(context);
pair.second.second.upload(context);
}
}
uploaded = true;
staticUploaded = true;
placementChangesUploaded = true;
dynamicUploaded = true;
sortUploaded = true;
}
bool SymbolBucket::hasData() const {
@@ -76,4 +136,82 @@ bool SymbolBucket::hasCollisionBoxData() const {
return !collisionBox.segments.empty();
}
bool SymbolBucket::hasCollisionCircleData() const {
return !collisionCircle.segments.empty();
}
void SymbolBucket::updateOpacity() {
placementChangesUploaded = false;
uploaded = false;
}
void addPlacedSymbol(gl::IndexVector<gl::Triangles>& triangles, const PlacedSymbol& placedSymbol) {
auto endIndex = placedSymbol.vertexStartIndex + placedSymbol.glyphOffsets.size() * 4;
for (auto vertexIndex = placedSymbol.vertexStartIndex; vertexIndex < endIndex; vertexIndex += 4) {
triangles.emplace_back(vertexIndex + 0, vertexIndex + 1, vertexIndex + 2);
triangles.emplace_back(vertexIndex + 1, vertexIndex + 2, vertexIndex + 3);
}
}
void SymbolBucket::sortFeatures(const float angle) {
if (!sortFeaturesByY) {
return;
}
if (sortedAngle && *sortedAngle == angle) {
return;
}
sortedAngle = angle;
// The current approach to sorting doesn't sort across segments so don't try.
// Sorting within segments separately seemed not to be worth the complexity.
if (text.segments.size() > 1 || icon.segments.size() > 1) {
return;
}
sortUploaded = false;
uploaded = false;
// If the symbols are allowed to overlap sort them by their vertical screen position.
// The index array buffer is rewritten to reference the (unchanged) vertices in the
// sorted order.
// To avoid sorting the actual symbolInstance array we sort an array of indexes.
std::vector<size_t> symbolInstanceIndexes;
for (size_t i = 0; i < symbolInstances.size(); i++) {
symbolInstanceIndexes.push_back(i);
}
const float sin = std::sin(angle);
const float cos = std::cos(angle);
std::sort(symbolInstanceIndexes.begin(), symbolInstanceIndexes.end(), [sin, cos, this](size_t &aIndex, size_t &bIndex) {
const SymbolInstance& a = symbolInstances[aIndex];
const SymbolInstance& b = symbolInstances[bIndex];
const int32_t aRotated = sin * a.anchor.point.x + cos * a.anchor.point.y;
const int32_t bRotated = sin * b.anchor.point.x + cos * b.anchor.point.y;
return aRotated != bRotated ?
aRotated < bRotated :
a.index > b.index;
});
text.triangles.clear();
icon.triangles.clear();
for (auto i : symbolInstanceIndexes) {
const SymbolInstance& symbolInstance = symbolInstances[i];
if (symbolInstance.placedTextIndex) {
addPlacedSymbol(text.triangles, text.placedSymbols[*symbolInstance.placedTextIndex]);
}
if (symbolInstance.placedVerticalTextIndex) {
addPlacedSymbol(text.triangles, text.placedSymbols[*symbolInstance.placedVerticalTextIndex]);
}
if (symbolInstance.placedIconIndex) {
addPlacedSymbol(icon.triangles, icon.placedSymbols[*symbolInstance.placedIconIndex]);
}
}
}
} // namespace mbgl

View File

@@ -10,6 +10,7 @@
#include <mbgl/text/glyph_range.hpp>
#include <mbgl/style/layers/symbol_layer_properties.hpp>
#include <mbgl/layout/symbol_feature.hpp>
#include <mbgl/layout/symbol_instance.hpp>
#include <vector>
@@ -18,18 +19,22 @@ namespace mbgl {
class PlacedSymbol {
public:
PlacedSymbol(Point<float> anchorPoint_, uint16_t segment_, float lowerSize_, float upperSize_,
std::array<float, 2> lineOffset_, float placementZoom_, bool useVerticalMode_, GeometryCoordinates line_) :
std::array<float, 2> lineOffset_, WritingModeType writingModes_, GeometryCoordinates line_, std::vector<float> tileDistances_) :
anchorPoint(anchorPoint_), segment(segment_), lowerSize(lowerSize_), upperSize(upperSize_),
lineOffset(lineOffset_), placementZoom(placementZoom_), useVerticalMode(useVerticalMode_), line(std::move(line_)) {}
lineOffset(lineOffset_), writingModes(writingModes_), line(std::move(line_)), tileDistances(std::move(tileDistances_)), hidden(false), vertexStartIndex(0)
{
}
Point<float> anchorPoint;
uint16_t segment;
float lowerSize;
float upperSize;
std::array<float, 2> lineOffset;
float placementZoom;
bool useVerticalMode;
WritingModeType writingModes;
GeometryCoordinates line;
std::vector<float> tileDistances;
std::vector<float> glyphOffsets;
bool hidden;
size_t vertexStartIndex;
};
class SymbolBucket : public Bucket {
@@ -40,17 +45,33 @@ public:
const style::DataDrivenPropertyValue<float>& iconSize,
float zoom,
bool sdfIcons,
bool iconsNeedLinear);
bool iconsNeedLinear,
bool sortFeaturesByY,
const std::vector<SymbolInstance>&&);
void upload(gl::Context&) override;
bool hasData() const override;
bool hasTextData() const;
bool hasIconData() const;
bool hasCollisionBoxData() const;
bool hasCollisionCircleData() const;
void updateOpacity();
void sortFeatures(const float angle);
const style::SymbolLayoutProperties::PossiblyEvaluated layout;
const bool sdfIcons;
const bool iconsNeedLinear;
const bool sortFeaturesByY;
optional<float> sortedAngle;
bool staticUploaded = false;
bool placementChangesUploaded = false;
bool dynamicUploaded = false;
bool sortUploaded = false;
std::vector<SymbolInstance> symbolInstances;
std::map<std::string, std::pair<
SymbolIconProgram::PaintPropertyBinders,
@@ -61,12 +82,14 @@ public:
struct TextBuffer {
gl::VertexVector<SymbolLayoutVertex> vertices;
gl::VertexVector<SymbolDynamicLayoutAttributes::Vertex> dynamicVertices;
gl::VertexVector<SymbolOpacityAttributes::Vertex> opacityVertices;
gl::IndexVector<gl::Triangles> triangles;
SegmentVector<SymbolTextAttributes> segments;
std::vector<PlacedSymbol> placedSymbols;
optional<gl::VertexBuffer<SymbolLayoutVertex>> vertexBuffer;
optional<gl::VertexBuffer<SymbolDynamicLayoutAttributes::Vertex>> dynamicVertexBuffer;
optional<gl::VertexBuffer<SymbolOpacityAttributes::Vertex>> opacityVertexBuffer;
optional<gl::IndexBuffer<gl::Triangles>> indexBuffer;
} text;
@@ -75,6 +98,7 @@ public:
struct IconBuffer {
gl::VertexVector<SymbolLayoutVertex> vertices;
gl::VertexVector<SymbolDynamicLayoutAttributes::Vertex> dynamicVertices;
gl::VertexVector<SymbolOpacityAttributes::Vertex> opacityVertices;
gl::IndexVector<gl::Triangles> triangles;
SegmentVector<SymbolIconAttributes> segments;
std::vector<PlacedSymbol> placedSymbols;
@@ -82,18 +106,30 @@ public:
optional<gl::VertexBuffer<SymbolLayoutVertex>> vertexBuffer;
optional<gl::VertexBuffer<SymbolDynamicLayoutAttributes::Vertex>> dynamicVertexBuffer;
optional<gl::VertexBuffer<SymbolOpacityAttributes::Vertex>> opacityVertexBuffer;
optional<gl::IndexBuffer<gl::Triangles>> indexBuffer;
} icon;
struct CollisionBoxBuffer {
gl::VertexVector<CollisionBoxVertex> vertices;
gl::IndexVector<gl::Lines> lines;
SegmentVector<CollisionBoxAttributes> segments;
struct CollisionBuffer {
gl::VertexVector<CollisionBoxLayoutAttributes::Vertex> vertices;
gl::VertexVector<CollisionBoxDynamicAttributes::Vertex> dynamicVertices;
SegmentVector<CollisionBoxProgram::Attributes> segments;
optional<gl::VertexBuffer<CollisionBoxVertex>> vertexBuffer;
optional<gl::VertexBuffer<SymbolDynamicLayoutAttributes::Vertex>> dynamicVertexBuffer;
optional<gl::VertexBuffer<CollisionBoxLayoutAttributes::Vertex>> vertexBuffer;
optional<gl::VertexBuffer<CollisionBoxDynamicAttributes::Vertex>> dynamicVertexBuffer;
};
struct CollisionBoxBuffer : public CollisionBuffer {
gl::IndexVector<gl::Lines> lines;
optional<gl::IndexBuffer<gl::Lines>> indexBuffer;
} collisionBox;
struct CollisionCircleBuffer : public CollisionBuffer {
gl::IndexVector<gl::Triangles> triangles;
optional<gl::IndexBuffer<gl::Triangles>> indexBuffer;
} collisionCircle;
uint32_t bucketInstanceId = 0;
};
} // namespace mbgl

View File

@@ -74,6 +74,8 @@ public:
uint32_t currentLayer;
float depthRangeSize;
const float depthEpsilon = 1.0f / (1 << 16);
float symbolFadeChange;
};
} // namespace mbgl

View File

@@ -81,14 +81,13 @@ public:
friend std::string layoutKey(const RenderLayer&);
// Stores current set of tiles to be rendered for this layer.
std::vector<std::reference_wrapper<RenderTile>> renderTiles;
protected:
// Stores what render passes this layer is currently enabled for. This depends on the
// evaluated StyleProperties object and is updated accordingly.
RenderPass passes = RenderPass::None;
//Stores current set of tiles to be rendered for this layer.
std::vector<std::reference_wrapper<RenderTile>> renderTiles;
};
} // namespace mbgl

View File

@@ -23,6 +23,7 @@
#include <mbgl/style/source_impl.hpp>
#include <mbgl/style/transition_options.hpp>
#include <mbgl/text/glyph_manager.hpp>
#include <mbgl/text/cross_tile_symbol_index.hpp>
#include <mbgl/tile/tile.hpp>
#include <mbgl/util/math.hpp>
#include <mbgl/util/string.hpp>
@@ -56,7 +57,9 @@ Renderer::Impl::Impl(RendererBackend& backend_,
, imageImpls(makeMutable<std::vector<Immutable<style::Image::Impl>>>())
, sourceImpls(makeMutable<std::vector<Immutable<style::Source::Impl>>>())
, layerImpls(makeMutable<std::vector<Immutable<style::Layer::Impl>>>())
, renderLight(makeMutable<Light::Impl>()) {
, renderLight(makeMutable<Light::Impl>())
, crossTileSymbolIndex(std::make_unique<CrossTileSymbolIndex>())
, placement(std::make_unique<Placement>(TransformState{}, MapMode::Still)) {
glyphManager->setObserver(this);
}
@@ -348,26 +351,6 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) {
continue;
}
// We're not clipping symbol layers, so when we have both parents and children of symbol
// layers, we drop all children in favor of their parent to avoid duplicate labels.
// See https://github.com/mapbox/mapbox-gl-native/issues/2482
if (symbolLayer) {
bool skip = false;
// Look back through the buckets we decided to render to find out whether there is
// already a bucket from this layer that is a parent of this tile. Tiles are ordered
// by zoom level when we obtain them from getTiles().
for (auto it = sortedTilesForInsertion.rbegin();
it != sortedTilesForInsertion.rend(); ++it) {
if (tile.tile.id.isChildOf(it->get().tile.id)) {
skip = true;
break;
}
}
if (skip) {
continue;
}
}
auto bucket = tile.tile.getBucket(*layer->baseImpl);
if (bucket) {
sortedTilesForInsertion.emplace_back(tile);
@@ -388,6 +371,45 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) {
frameHistory.record(parameters.timePoint,
parameters.state.getZoom(),
parameters.mapMode == MapMode::Continuous ? util::DEFAULT_TRANSITION_DURATION : Milliseconds(0));
bool symbolBucketsChanged = false;
if (parameters.mapMode == MapMode::Still) {
// TODO: Think about right way for symbol index to handle still rendering
crossTileSymbolIndex->reset();
}
for (auto it = order.rbegin(); it != order.rend(); ++it) {
if (it->layer.is<RenderSymbolLayer>()) {
if (crossTileSymbolIndex->addLayer(*it->layer.as<RenderSymbolLayer>())) symbolBucketsChanged = true;
}
}
bool placementChanged = false;
if (!placement->stillRecent(parameters.timePoint)) {
auto newPlacement = std::make_unique<Placement>(parameters.state, parameters.mapMode);
for (auto it = order.rbegin(); it != order.rend(); ++it) {
if (it->layer.is<RenderSymbolLayer>()) {
newPlacement->placeLayer(*it->layer.as<RenderSymbolLayer>(), parameters.projMatrix, parameters.debugOptions & MapDebugOptions::Collision);
}
}
placementChanged = newPlacement->commit(*placement, parameters.timePoint);
if (placementChanged || symbolBucketsChanged) {
placement = std::move(newPlacement);
}
placement->setRecent(parameters.timePoint);
} else {
placement->setStale();
}
parameters.symbolFadeChange = placement->symbolFadeChange(parameters.timePoint);
if (placementChanged || symbolBucketsChanged) {
for (auto it = order.rbegin(); it != order.rend(); ++it) {
if (it->layer.is<RenderSymbolLayer>()) {
placement->updateLayerOpacities(*it->layer.as<RenderSymbolLayer>());
}
}
}
// - UPLOAD PASS -------------------------------------------------------------------------------
// Uploads all required buffers and images before we do any actual rendering.
@@ -607,7 +629,7 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) {
observer->onDidFinishRenderingFrame(
loaded ? RendererObserver::RenderMode::Full : RendererObserver::RenderMode::Partial,
updateParameters.mode == MapMode::Continuous && (hasTransitions() || frameHistory.needsAnimation(util::DEFAULT_TRANSITION_DURATION))
updateParameters.mode == MapMode::Continuous && (hasTransitions(parameters.timePoint))
);
if (!loaded) {
@@ -647,7 +669,7 @@ std::vector<Feature> Renderer::Impl::queryRenderedFeatures(const ScreenLineStrin
std::unordered_map<std::string, std::vector<Feature>> resultsByLayer;
for (const auto& sourceID : sourceIDs) {
if (RenderSource* renderSource = getRenderSource(sourceID)) {
auto sourceResults = renderSource->queryRenderedFeatures(geometry, transformState, layers, options);
auto sourceResults = renderSource->queryRenderedFeatures(geometry, transformState, layers, options, placement->getCollisionIndex());
std::move(sourceResults.begin(), sourceResults.end(), std::inserter(resultsByLayer, resultsByLayer.begin()));
}
}
@@ -727,7 +749,7 @@ RenderSource* Renderer::Impl::getRenderSource(const std::string& id) const {
return it != renderSources.end() ? it->second.get() : nullptr;
}
bool Renderer::Impl::hasTransitions() const {
bool Renderer::Impl::hasTransitions(TimePoint timePoint) const {
if (renderLight.hasTransition()) {
return true;
}
@@ -738,6 +760,10 @@ bool Renderer::Impl::hasTransitions() const {
}
}
if (placement->hasTransitions(timePoint)) {
return true;
}
return false;
}

View File

@@ -11,6 +11,7 @@
#include <mbgl/map/transform_state.hpp>
#include <mbgl/map/zoom_history.hpp>
#include <mbgl/text/glyph_manager_observer.hpp>
#include <mbgl/text/placement.hpp>
#include <memory>
#include <string>
@@ -31,6 +32,7 @@ class Scheduler;
class GlyphManager;
class ImageManager;
class LineAtlas;
class CrossTileSymbolIndex;
class Renderer::Impl : public GlyphManagerObserver,
public RenderSourceObserver{
@@ -56,7 +58,7 @@ public:
private:
bool isLoaded() const;
bool hasTransitions() const;
bool hasTransitions(TimePoint) const;
RenderSource* getRenderSource(const std::string& id) const;
@@ -108,6 +110,9 @@ private:
std::unordered_map<std::string, std::unique_ptr<RenderLayer>> renderLayers;
RenderLight renderLight;
std::unique_ptr<CrossTileSymbolIndex> crossTileSymbolIndex;
std::unique_ptr<Placement> placement;
bool contextLost = false;
};

View File

@@ -5,7 +5,6 @@
#include <mbgl/renderer/tile_parameters.hpp>
#include <mbgl/renderer/query.hpp>
#include <mbgl/map/transform.hpp>
#include <mbgl/text/placement_config.hpp>
#include <mbgl/math/clamp.hpp>
#include <mbgl/util/tile_cover.hpp>
#include <mbgl/util/enum.hpp>
@@ -120,6 +119,7 @@ void TilePyramid::update(const std::vector<Immutable<style::Layer::Impl>>& layer
// use because they're still loading. In addition to that, we also need to retain all tiles that
// we're actively using, e.g. as a replacement for tile that aren't loaded yet.
std::set<OverscaledTileID> retain;
std::set<UnwrappedTileID> rendered;
auto retainTileFn = [&](Tile& tile, TileNecessity necessity) -> void {
if (retain.emplace(tile.id).second) {
@@ -150,6 +150,7 @@ void TilePyramid::update(const std::vector<Immutable<style::Layer::Impl>>& layer
};
auto renderTileFn = [&](const UnwrappedTileID& tileID, Tile& tile) {
renderTiles.emplace_back(tileID, tile);
rendered.emplace(tileID);
};
renderTiles.clear();
@@ -177,6 +178,13 @@ void TilePyramid::update(const std::vector<Immutable<style::Layer::Impl>>& layer
auto tilesIt = tiles.begin();
auto retainIt = retain.begin();
while (tilesIt != tiles.end()) {
auto renderedIt = rendered.find(tilesIt->first.toUnwrapped());
if (renderedIt == rendered.end()) {
// Since this tile isn't in the render set, crossTileIDs won't be kept
// updated by CrossTileSymbolIndex. We need to reset the stored crossTileIDs
// so they're not reused if/when this tile is re-added to the render set
tilesIt->second->resetCrossTileIDs();
}
if (retainIt == retain.end() || tilesIt->first < *retainIt) {
if (!needsRelayout) {
tilesIt->second->setNecessity(TileNecessity::Optional);
@@ -193,13 +201,7 @@ void TilePyramid::update(const std::vector<Immutable<style::Layer::Impl>>& layer
}
for (auto& pair : tiles) {
const PlacementConfig config { parameters.transformState.getAngle(),
parameters.transformState.getPitch(),
parameters.transformState.getCameraToCenterDistance(),
parameters.transformState.getCameraToTileDistance(pair.first.toUnwrapped()),
parameters.debugOptions & MapDebugOptions::Collision };
pair.second->setPlacementConfig(config);
pair.second->setShowCollisionBoxes(parameters.debugOptions & MapDebugOptions::Collision);
}
}

View File

@@ -13,8 +13,9 @@ CollisionFeature::CollisionFeature(const GeometryCoordinates& line,
const float padding,
const style::SymbolPlacementType placement,
IndexedSubfeature indexedFeature_,
const AlignmentType alignment)
: indexedFeature(std::move(indexedFeature_)) {
const float overscaling)
: indexedFeature(std::move(indexedFeature_))
, alongLine(placement == style::SymbolPlacementType::Line) {
if (top == 0 && bottom == 0 && left == 0 && right == 0) return;
const float y1 = top * boxScale - padding;
@@ -22,7 +23,7 @@ CollisionFeature::CollisionFeature(const GeometryCoordinates& line,
const float x1 = left * boxScale - padding;
const float x2 = right * boxScale + padding;
if (placement == style::SymbolPlacementType::Line) {
if (alongLine) {
float height = y2 - y1;
const double length = x2 - x1;
@@ -31,29 +32,26 @@ CollisionFeature::CollisionFeature(const GeometryCoordinates& line,
height = std::max(10.0f * boxScale, height);
GeometryCoordinate anchorPoint = convertPoint<int16_t>(anchor.point);
if (alignment == AlignmentType::Straight) {
// used for icon labels that are aligned with the line, but don't curve along it
const GeometryCoordinate vector = convertPoint<int16_t>(util::unit(convertPoint<double>(line[anchor.segment + 1] - line[anchor.segment])) * length);
const GeometryCoordinates newLine({ anchorPoint - vector, anchorPoint + vector });
bboxifyLabel(newLine, anchorPoint, 0, length, height);
} else {
// used for text labels that curve along a line
bboxifyLabel(line, anchorPoint, anchor.segment, length, height);
}
bboxifyLabel(line, anchorPoint, anchor.segment, length, height, overscaling);
} else {
boxes.emplace_back(anchor.point, Point<float>{ 0, 0 }, x1, y1, x2, y2, std::numeric_limits<float>::infinity());
boxes.emplace_back(anchor.point, Point<float>{ 0, 0 }, x1, y1, x2, y2);
}
}
void CollisionFeature::bboxifyLabel(const GeometryCoordinates& line, GeometryCoordinate& anchorPoint,
const int segment, const float labelLength, const float boxSize) {
const int segment, const float labelLength, const float boxSize, const float overscaling) {
const float step = boxSize / 2;
const int nBoxes = std::floor(labelLength / step);
// We calculate line collision boxes out to 300% of what would normally be our
// We calculate line collision circles out to 300% of what would normally be our
// max size, to allow collision detection to work on labels that expand as
// they move into the distance
const int nPitchPaddingBoxes = std::floor(nBoxes / 2);
// Vertically oriented labels in the distant field can extend past this padding
// This is a noticeable problem in overscaled tiles where the pitch 0-based
// symbol spacing will put labels very close together in a pitched map.
// To reduce the cost of adding extra collision circles, we slowly increase
// them for overscaled tiles.
const float overscalingPaddingFactor = 1 + .4 * std::log(overscaling) / std::log(2);
const int nPitchPaddingBoxes = std::floor(nBoxes * overscalingPaddingFactor / 2);
// offset the center of the first box by half a box so that the edge of the
// box is at the edge of the label.
@@ -124,47 +122,18 @@ void CollisionFeature::bboxifyLabel(const GeometryCoordinates& line, GeometryCoo
p0.x + segmentBoxDistance / segmentLength * (p1.x - p0.x),
p0.y + segmentBoxDistance / segmentLength * (p1.y - p0.y)
};
// If the box is within boxSize of the anchor, force the box to be used
// (so even 0-width labels use at least one box)
// Otherwise, the .8 multiplication gives us a little bit of conservative
// padding in choosing which boxes to use (see CollisionIndex#placedCollisionCircles)
const float paddedAnchorDistance = std::abs(boxDistanceToAnchor - firstBoxOffset) < step ?
0 :
(boxDistanceToAnchor - firstBoxOffset) * 0.8;
// Distance from label anchor point to inner (towards center) edge of this box
// The tricky thing here is that box positioning doesn't change with scale,
// but box size does change with scale.
// Technically, distanceToInnerEdge should be:
// Math.max(Math.abs(boxDistanceToAnchor - firstBoxOffset) - (step / scale), 0);
// But using that formula would make solving for maxScale more difficult, so we
// approximate with scale=2.
// This makes our calculation spot-on at scale=2, and on the conservative side for
// lower scales
const float distanceToInnerEdge = std::max(std::fabs(boxDistanceToAnchor - firstBoxOffset) - step / 2, 0.0f);
float maxScale = util::division(labelLength / 2, distanceToInnerEdge, std::numeric_limits<float>::infinity());
// The box maxScale calculations are designed to be conservative on collisions in the scale range
// [1,2]. At scale=1, each box has 50% overlap, and at scale=2, the boxes are lined up edge
// to edge (beyond scale 2, gaps start to appear, which could potentially allow missed collisions).
// We add "pitch padding" boxes to the left and right to handle effective underzooming
// (scale < 1) when labels are in the distance. The overlap approximation could cause us to use
// these boxes when the scale is greater than 1, but we prevent that because we know
// they're only necessary for scales less than one.
// This preserves the pre-pitch-padding behavior for unpitched maps.
if (i < 0 || i >= nBoxes) {
maxScale = std::min(maxScale, 0.99f);
}
boxes.emplace_back(boxAnchor, boxAnchor - convertPoint<float>(anchorPoint), -boxSize / 2, -boxSize / 2, boxSize / 2, boxSize / 2, maxScale);
boxes.emplace_back(boxAnchor, boxAnchor - convertPoint<float>(anchorPoint), -boxSize / 2, -boxSize / 2, boxSize / 2, boxSize / 2, paddedAnchorDistance, boxSize / 2);
}
}
float CollisionBox::adjustedMaxScale(const std::array<float, 4>& rotationMatrix, const float yStretch) const {
// When the map is pitched the distance covered by a line changes.
// Adjust the max scale by (approximatePitchedLength / approximateRegularLength)
// to compensate for this.
const Point<float> rotatedOffset = util::matrixMultiply(rotationMatrix, offset);
const float xSqr = rotatedOffset.x * rotatedOffset.x;
const float ySqr = rotatedOffset.y * rotatedOffset.y;
const float yStretchSqr = ySqr * yStretch * yStretch;
const float adjustmentFactor = xSqr + ySqr != 0 ?
std::sqrt((xSqr + yStretchSqr) / (xSqr + ySqr)) :
1.0f;
return maxScale * adjustmentFactor;
}
} // namespace mbgl

View File

@@ -11,10 +11,8 @@ namespace mbgl {
class CollisionBox {
public:
CollisionBox(Point<float> _anchor, Point<float> _offset, float _x1, float _y1, float _x2, float _y2, float _maxScale) :
anchor(std::move(_anchor)), offset(_offset), x1(_x1), y1(_y1), x2(_x2), y2(_y2), maxScale(_maxScale) {}
float adjustedMaxScale(const std::array<float, 4>& rotationMatrix, const float yStretch) const;
CollisionBox(Point<float> _anchor, Point<float> _offset, float _x1, float _y1, float _x2, float _y2, float _signedDistanceFromAnchor = 0, float _radius = 0) :
anchor(std::move(_anchor)), offset(_offset), x1(_x1), y1(_y1), x2(_x2), y2(_y2), used(true), signedDistanceFromAnchor(_signedDistanceFromAnchor), radius(_radius) {}
// the box is centered around the anchor point
Point<float> anchor;
@@ -28,20 +26,23 @@ public:
float x2;
float y2;
// the box is only valid for scales < maxScale.
// The box does not block other boxes at scales >= maxScale;
float maxScale;
// Projected box geometry: generated/updated at placement time
float px1;
float py1;
float px2;
float py2;
// Projected circle geometry: generated/updated at placement time
float px;
float py;
bool used;
// the scale at which the label can first be shown
float placementScale = 0.0f;
float signedDistanceFromAnchor;
float radius;
};
class CollisionFeature {
public:
enum class AlignmentType : bool {
Straight = false,
Curved
};
// for text
CollisionFeature(const GeometryCoordinates& line,
@@ -50,8 +51,9 @@ public:
const float boxScale,
const float padding,
const style::SymbolPlacementType placement,
const IndexedSubfeature& indexedFeature_)
: CollisionFeature(line, anchor, shapedText.top, shapedText.bottom, shapedText.left, shapedText.right, boxScale, padding, placement, indexedFeature_, AlignmentType::Curved) {}
const IndexedSubfeature& indexedFeature_,
const float overscaling)
: CollisionFeature(line, anchor, shapedText.top, shapedText.bottom, shapedText.left, shapedText.right, boxScale, padding, placement, indexedFeature_, overscaling) {}
// for icons
CollisionFeature(const GeometryCoordinates& line,
@@ -66,7 +68,7 @@ public:
(shapedIcon ? shapedIcon->bottom() : 0),
(shapedIcon ? shapedIcon->left() : 0),
(shapedIcon ? shapedIcon->right() : 0),
boxScale, padding, placement, indexedFeature_, AlignmentType::Straight) {}
boxScale, padding, placement, indexedFeature_, 1) {}
CollisionFeature(const GeometryCoordinates& line,
const Anchor&,
@@ -78,14 +80,15 @@ public:
const float padding,
const style::SymbolPlacementType,
IndexedSubfeature,
const AlignmentType);
const float overscaling);
std::vector<CollisionBox> boxes;
IndexedSubfeature indexedFeature;
bool alongLine;
private:
void bboxifyLabel(const GeometryCoordinates& line, GeometryCoordinate& anchorPoint,
const int segment, const float length, const float height);
const int segment, const float length, const float height, const float overscaling);
};
} // namespace mbgl

View File

@@ -62,13 +62,13 @@ void Placement::placeLayer(RenderSymbolLayer& symbolLayer, const mat4& projMatri
state.matrixFor(posMatrix, renderTile.id);
matrix::multiply(posMatrix, projMatrix, posMatrix);
mat4 textLabelPlaneMatrix = getLabelPlaneMatrix(renderTile.matrix,
mat4 textLabelPlaneMatrix = getLabelPlaneMatrix(posMatrix,
layout.get<TextPitchAlignment>() == style::AlignmentType::Map,
layout.get<TextRotationAlignment>() == style::AlignmentType::Map,
state,
pixelsToTileUnits);
mat4 iconLabelPlaneMatrix = getLabelPlaneMatrix(renderTile.matrix,
mat4 iconLabelPlaneMatrix = getLabelPlaneMatrix(posMatrix,
layout.get<IconPitchAlignment>() == style::AlignmentType::Map,
layout.get<IconRotationAlignment>() == style::AlignmentType::Map,
state,

View File

@@ -15,7 +15,6 @@
#include <mbgl/renderer/image_atlas.hpp>
#include <mbgl/storage/file_source.hpp>
#include <mbgl/geometry/feature_index.hpp>
#include <mbgl/text/collision_tile.hpp>
#include <mbgl/map/transform_state.hpp>
#include <mbgl/style/filter_evaluator.hpp>
#include <mbgl/util/logging.hpp>
@@ -33,7 +32,7 @@ using namespace style;
GeometryTile's 'correlationID' is used for ensuring the tile will be flagged
as non-pending only when the placement coming from the last operation (as in
'setData', 'setLayers', 'setPlacementConfig') occurs. This is important for
'setData', 'setLayers', 'setShowCollisionBoxes') occurs. This is important for
still mode rendering as we want to render only when all layout and placement
operations are completed.
@@ -52,13 +51,15 @@ GeometryTile::GeometryTile(const OverscaledTileID& id_,
worker(parameters.workerScheduler,
ActorRef<GeometryTile>(*this, mailbox),
id_,
sourceID,
obsolete,
parameters.mode,
parameters.pixelRatio),
parameters.pixelRatio,
parameters.debugOptions & MapDebugOptions::Collision),
glyphManager(parameters.glyphManager),
imageManager(parameters.imageManager),
lastYStretch(1.0f),
mode(parameters.mode) {
mode(parameters.mode),
showCollisionBoxes(parameters.debugOptions & MapDebugOptions::Collision) {
}
GeometryTile::~GeometryTile() {
@@ -89,25 +90,6 @@ void GeometryTile::setData(std::unique_ptr<const GeometryTileData> data_) {
worker.invoke(&GeometryTileWorker::setData, std::move(data_), correlationID);
}
void GeometryTile::setPlacementConfig(const PlacementConfig& desiredConfig) {
if (requestedConfig == desiredConfig) {
return;
}
// Mark the tile as pending again if it was complete before to prevent signaling a complete
// state despite pending parse operations.
pending = true;
++correlationID;
requestedConfig = desiredConfig;
invokePlacement();
}
void GeometryTile::invokePlacement() {
if (requestedConfig) {
worker.invoke(&GeometryTileWorker::setPlacementConfig, *requestedConfig, correlationID);
}
}
void GeometryTile::setLayers(const std::vector<Immutable<Layer::Impl>>& layers) {
// Mark the tile as pending again if it was complete before to prevent signaling a complete
@@ -134,14 +116,22 @@ void GeometryTile::setLayers(const std::vector<Immutable<Layer::Impl>>& layers)
worker.invoke(&GeometryTileWorker::setLayers, std::move(impls), correlationID);
}
void GeometryTile::setShowCollisionBoxes(const bool showCollisionBoxes_) {
if (showCollisionBoxes != showCollisionBoxes_) {
showCollisionBoxes = showCollisionBoxes_;
++correlationID;
worker.invoke(&GeometryTileWorker::setShowCollisionBoxes, showCollisionBoxes, correlationID);
}
}
void GeometryTile::onLayout(LayoutResult result, const uint64_t resultCorrelationID) {
loaded = true;
renderable = true;
// Don't mark ourselves loaded or renderable until the first successful placement
// TODO: Ideally we'd render this tile without symbols as long as this tile wasn't
// replacing a tile at a different zoom that _did_ have symbols.
(void)resultCorrelationID;
nonSymbolBuckets = std::move(result.nonSymbolBuckets);
featureIndex = std::move(result.featureIndex);
data = std::move(result.tileData);
collisionTile.reset();
observer->onTileChanged(*this);
}
@@ -152,16 +142,13 @@ void GeometryTile::onPlacement(PlacementResult result, const uint64_t resultCorr
pending = false;
}
symbolBuckets = std::move(result.symbolBuckets);
collisionTile = std::move(result.collisionTile);
if (result.glyphAtlasImage) {
glyphAtlasImage = std::move(*result.glyphAtlasImage);
}
if (result.iconAtlasImage) {
iconAtlasImage = std::move(*result.iconAtlasImage);
}
if (collisionTile.get()) {
lastYStretch = collisionTile->yStretch;
}
observer->onTileChanged(*this);
}
@@ -231,7 +218,8 @@ void GeometryTile::queryRenderedFeatures(
const GeometryCoordinates& queryGeometry,
const TransformState& transformState,
const std::vector<const RenderLayer*>& layers,
const RenderedQueryOptions& options) {
const RenderedQueryOptions& options,
const CollisionIndex& collisionIndex) {
if (!featureIndex || !data) return;
@@ -251,9 +239,10 @@ void GeometryTile::queryRenderedFeatures(
std::pow(2, transformState.getZoom() - id.overscaledZ),
options,
*data,
id.canonical,
id.toUnwrapped(),
sourceID,
layers,
collisionTile.get(),
collisionIndex,
additionalRadius);
}
@@ -293,11 +282,16 @@ void GeometryTile::querySourceFeatures(
}
}
float GeometryTile::yStretch() const {
// collisionTile gets reset in onLayout but we don't clear the symbolBuckets
// until a new placement result comes along, so keep the yStretch value in
// case we need to render them.
return lastYStretch;
void GeometryTile::resetCrossTileIDs() {
for (auto& bucket : symbolBuckets) {
auto symbolBucket = dynamic_cast<SymbolBucket*>(bucket.second.get());
if (symbolBucket && symbolBucket->bucketInstanceId) {
symbolBucket->bucketInstanceId = 0;
for (auto& symbolInstance : symbolBucket->symbolInstances) {
symbolInstance.crossTileID = 0;
}
}
}
}
} // namespace mbgl

View File

@@ -4,12 +4,11 @@
#include <mbgl/tile/geometry_tile_worker.hpp>
#include <mbgl/renderer/image_manager.hpp>
#include <mbgl/text/glyph_manager.hpp>
#include <mbgl/text/placement_config.hpp>
#include <mbgl/text/collision_tile.hpp>
#include <mbgl/util/feature.hpp>
#include <mbgl/util/throttler.hpp>
#include <mbgl/actor/actor.hpp>
#include <mbgl/geometry/feature_index.hpp>
#include <mbgl/layout/symbol_layout.hpp>
#include <atomic>
#include <memory>
@@ -36,9 +35,9 @@ public:
void setError(std::exception_ptr);
void setData(std::unique_ptr<const GeometryTileData>);
void setPlacementConfig(const PlacementConfig&) override;
void setLayers(const std::vector<Immutable<style::Layer::Impl>>&) override;
void setShowCollisionBoxes(const bool showCollisionBoxes) override;
void onGlyphsAvailable(GlyphMap) override;
void onImagesAvailable(ImageMap, uint64_t imageCorrelationID) override;
@@ -56,7 +55,8 @@ public:
const GeometryCoordinates& queryGeometry,
const TransformState&,
const std::vector<const RenderLayer*>& layers,
const RenderedQueryOptions& options) override;
const RenderedQueryOptions& options,
const CollisionIndex& collisionIndex) override;
void querySourceFeatures(
std::vector<Feature>& result,
@@ -82,16 +82,13 @@ public:
class PlacementResult {
public:
std::unordered_map<std::string, std::shared_ptr<Bucket>> symbolBuckets;
std::unique_ptr<CollisionTile> collisionTile;
optional<AlphaImage> glyphAtlasImage;
optional<PremultipliedImage> iconAtlasImage;
PlacementResult(std::unordered_map<std::string, std::shared_ptr<Bucket>> symbolBuckets_,
std::unique_ptr<CollisionTile> collisionTile_,
optional<AlphaImage> glyphAtlasImage_,
optional<PremultipliedImage> iconAtlasImage_)
: symbolBuckets(std::move(symbolBuckets_)),
collisionTile(std::move(collisionTile_)),
glyphAtlasImage(std::move(glyphAtlasImage_)),
iconAtlasImage(std::move(iconAtlasImage_)) {}
};
@@ -99,7 +96,7 @@ public:
void onError(std::exception_ptr, uint64_t correlationID);
float yStretch() const override;
void resetCrossTileIDs() override;
protected:
const GeometryTileData* getData() {
@@ -108,7 +105,6 @@ protected:
private:
void markObsolete();
void invokePlacement();
const std::string sourceID;
@@ -122,7 +118,6 @@ private:
ImageManager& imageManager;
uint64_t correlationID = 0;
optional<PlacementConfig> requestedConfig;
std::unordered_map<std::string, std::shared_ptr<Bucket>> nonSymbolBuckets;
std::unique_ptr<FeatureIndex> featureIndex;
@@ -132,10 +127,11 @@ private:
optional<PremultipliedImage> iconAtlasImage;
std::unordered_map<std::string, std::shared_ptr<Bucket>> symbolBuckets;
std::unique_ptr<CollisionTile> collisionTile;
std::unordered_map<std::string, std::unique_ptr<SymbolLayout>> symbolLayouts;
float lastYStretch;
const MapMode mode;
bool showCollisionBoxes;
public:
optional<gl::Texture> glyphAtlasTexture;

View File

@@ -1,7 +1,6 @@
#include <mbgl/tile/geometry_tile_worker.hpp>
#include <mbgl/tile/geometry_tile_data.hpp>
#include <mbgl/tile/geometry_tile.hpp>
#include <mbgl/text/collision_tile.hpp>
#include <mbgl/layout/symbol_layout.hpp>
#include <mbgl/renderer/bucket_parameters.hpp>
#include <mbgl/renderer/group_by_layout.hpp>
@@ -24,15 +23,19 @@ using namespace style;
GeometryTileWorker::GeometryTileWorker(ActorRef<GeometryTileWorker> self_,
ActorRef<GeometryTile> parent_,
OverscaledTileID id_,
const std::string& sourceID_,
const std::atomic<bool>& obsolete_,
const MapMode mode_,
const float pixelRatio_)
const float pixelRatio_,
const bool showCollisionBoxes_)
: self(std::move(self_)),
parent(std::move(parent_)),
id(std::move(id_)),
sourceID(sourceID_),
obsolete(obsolete_),
mode(mode_),
pixelRatio(pixelRatio_) {
pixelRatio(pixelRatio_),
showCollisionBoxes(showCollisionBoxes_) {
}
GeometryTileWorker::~GeometryTileWorker() = default;
@@ -116,9 +119,9 @@ void GeometryTileWorker::setLayers(std::vector<Immutable<Layer::Impl>> layers_,
}
}
void GeometryTileWorker::setPlacementConfig(PlacementConfig placementConfig_, uint64_t correlationID_) {
void GeometryTileWorker::setShowCollisionBoxes(bool showCollisionBoxes_, uint64_t correlationID_) {
try {
placementConfig = std::move(placementConfig_);
showCollisionBoxes = showCollisionBoxes_;
correlationID = correlationID_;
switch (state) {
@@ -372,7 +375,7 @@ bool GeometryTileWorker::hasPendingSymbolDependencies() const {
}
void GeometryTileWorker::attemptPlacement() {
if (!data || !layers || !placementConfig || hasPendingSymbolDependencies()) {
if (!data || !layers || hasPendingSymbolDependencies()) {
return;
}
@@ -392,13 +395,13 @@ void GeometryTileWorker::attemptPlacement() {
}
symbolLayout->prepare(glyphMap, glyphAtlas.positions,
imageMap, imageAtlas.positions);
imageMap, imageAtlas.positions,
id, sourceID);
}
symbolLayoutsNeedPreparation = false;
}
auto collisionTile = std::make_unique<CollisionTile>(*placementConfig);
std::unordered_map<std::string, std::shared_ptr<Bucket>> buckets;
for (auto& symbolLayout : symbolLayouts) {
@@ -410,7 +413,7 @@ void GeometryTileWorker::attemptPlacement() {
continue;
}
std::shared_ptr<Bucket> bucket = symbolLayout->place(*collisionTile);
std::shared_ptr<Bucket> bucket = symbolLayout->place(showCollisionBoxes);
for (const auto& pair : symbolLayout->layerPaintProperties) {
buckets.emplace(pair.first, bucket);
}
@@ -418,7 +421,6 @@ void GeometryTileWorker::attemptPlacement() {
parent.invoke(&GeometryTile::onPlacement, GeometryTile::PlacementResult {
std::move(buckets),
std::move(collisionTile),
std::move(glyphAtlasImage),
std::move(iconAtlasImage),
}, correlationID);

View File

@@ -4,7 +4,6 @@
#include <mbgl/tile/tile_id.hpp>
#include <mbgl/style/image_impl.hpp>
#include <mbgl/text/glyph.hpp>
#include <mbgl/text/placement_config.hpp>
#include <mbgl/actor/actor_ref.hpp>
#include <mbgl/util/optional.hpp>
#include <mbgl/util/immutable.hpp>
@@ -28,14 +27,16 @@ public:
GeometryTileWorker(ActorRef<GeometryTileWorker> self,
ActorRef<GeometryTile> parent,
OverscaledTileID,
const std::string&,
const std::atomic<bool>&,
const MapMode,
const float pixelRatio);
const float pixelRatio,
const bool showCollisionBoxes_);
~GeometryTileWorker();
void setLayers(std::vector<Immutable<style::Layer::Impl>>, uint64_t correlationID);
void setData(std::unique_ptr<const GeometryTileData>, uint64_t correlationID);
void setPlacementConfig(PlacementConfig, uint64_t correlationID);
void setShowCollisionBoxes(bool showCollisionBoxes_, uint64_t correlationID_);
void onGlyphsAvailable(GlyphMap glyphs);
void onImagesAvailable(ImageMap images, uint64_t imageCorrelationID);
@@ -57,6 +58,7 @@ private:
ActorRef<GeometryTile> parent;
const OverscaledTileID id;
const std::string sourceID;
const std::atomic<bool>& obsolete;
const MapMode mode;
const float pixelRatio;
@@ -75,7 +77,6 @@ private:
// Outer optional indicates whether we've received it or not.
optional<std::vector<Immutable<style::Layer::Impl>>> layers;
optional<std::unique_ptr<const GeometryTileData>> data;
optional<PlacementConfig> placementConfig;
bool symbolLayoutsNeedPreparation = false;
std::vector<std::unique_ptr<SymbolLayout>> symbolLayouts;
@@ -83,6 +84,8 @@ private:
ImageDependencies pendingImageDependencies;
GlyphMap glyphMap;
ImageMap imageMap;
bool showCollisionBoxes;
};
} // namespace mbgl

View File

@@ -23,7 +23,6 @@ namespace mbgl {
class DebugBucket;
class TransformState;
class TileObserver;
class PlacementConfig;
class RenderLayer;
class RenderedQueryOptions;
class SourceQueryOptions;
@@ -47,7 +46,7 @@ public:
virtual void upload(gl::Context&) = 0;
virtual Bucket* getBucket(const style::Layer::Impl&) const = 0;
virtual void setPlacementConfig(const PlacementConfig&) {}
virtual void setShowCollisionBoxes(const bool) {}
virtual void setLayers(const std::vector<Immutable<style::Layer::Impl>>&) {}
virtual void setMask(TileMask&&) {}
@@ -92,6 +91,8 @@ public:
bool isComplete() const {
return loaded && !pending;
}
virtual void resetCrossTileIDs() {};
void dumpDebugLogs() const;
@@ -101,8 +102,6 @@ public:
// Contains the tile ID string for painting debug information.
std::unique_ptr<DebugBucket> debugBucket;
virtual float yStretch() const { return 1.0f; }
protected:
bool triedOptional = false;