Files
MapLibreParticleLab/src/renderer/symbol_bucket.cpp
2014-11-03 15:44:27 +00:00

403 lines
16 KiB
C++

#include <mbgl/renderer/symbol_bucket.hpp>
#include <mbgl/geometry/text_buffer.hpp>
#include <mbgl/geometry/icon_buffer.hpp>
#include <mbgl/geometry/glyph_atlas.hpp>
#include <mbgl/geometry/sprite_atlas.hpp>
#include <mbgl/geometry/geometry.hpp>
#include <mbgl/geometry/anchor.hpp>
#include <mbgl/geometry/resample.hpp>
#include <mbgl/renderer/painter.hpp>
#include <mbgl/text/glyph_store.hpp>
#include <mbgl/text/placement.hpp>
#include <mbgl/platform/log.hpp>
#include <mbgl/text/collision.hpp>
#include <mbgl/map/sprite.hpp>
#include <mbgl/util/utf.hpp>
#include <mbgl/util/token.hpp>
#include <mbgl/util/math.hpp>
namespace mbgl {
SymbolBucket::SymbolBucket(const StyleBucketSymbol &properties_, Collision &collision_)
: properties(properties_), collision(collision_) {}
void SymbolBucket::render(Painter &painter, util::ptr<StyleLayer> layer_desc,
const Tile::ID &id, const mat4 &matrix) {
painter.renderSymbol(*this, layer_desc, id, matrix);
}
bool SymbolBucket::hasData() const { return hasTextData() || hasIconData(); }
bool SymbolBucket::hasTextData() const { return !text.groups.empty(); }
bool SymbolBucket::hasIconData() const { return !icon.groups.empty(); }
void SymbolBucket::addGlyphsToAtlas(uint64_t tileid, const std::string stackname,
const std::u32string &text, const FontStack &fontStack,
GlyphAtlas &glyphAtlas, GlyphPositions &face) {
glyphAtlas.addGlyphs(tileid, text, stackname, fontStack,face);
}
std::vector<SymbolFeature> SymbolBucket::processFeatures(const VectorTileLayer &layer,
const FilterExpression &filter,
GlyphStore &glyphStore,
const Sprite &sprite) {
const bool has_text = properties.text.field.size();
const bool has_icon = properties.icon.image.size();
std::vector<SymbolFeature> features;
if (!has_text && !has_icon) {
return features;
}
// Determine and load glyph ranges
std::set<GlyphRange> ranges;
FilteredVectorTileLayer filtered_layer(layer, filter);
for (const pbf &feature_pbf : filtered_layer) {
const VectorTileFeature feature{feature_pbf, layer};
SymbolFeature ft;
if (has_text) {
std::string u8string = util::replaceTokens(properties.text.field, feature.properties);
if (properties.text.transform == TextTransformType::Uppercase) {
u8string = platform::uppercase(u8string);
} else if (properties.text.transform == TextTransformType::Lowercase) {
u8string = platform::lowercase(u8string);
}
ft.label = util::utf8_to_utf32::convert(u8string);
if (ft.label.size()) {
// Loop through all characters of this text and collect unique codepoints.
for (char32_t chr : ft.label) {
ranges.insert(getGlyphRange(chr));
}
}
}
if (has_icon) {
ft.sprite = util::replaceTokens(properties.icon.image, feature.properties);
}
if (ft.label.length() || ft.sprite.length()) {
ft.geometry = feature.geometry;
features.push_back(std::move(ft));
}
}
glyphStore.waitForGlyphRanges(properties.text.font, ranges);
sprite.waitUntilLoaded();
return features;
}
void SymbolBucket::addFeatures(const VectorTileLayer &layer, const FilterExpression &filter,
const Tile::ID &id, SpriteAtlas &spriteAtlas, Sprite &sprite,
GlyphAtlas & glyphAtlas, GlyphStore &glyphStore) {
const std::vector<SymbolFeature> features = processFeatures(layer, filter, glyphStore, sprite);
float horizontalAlign = 0.5;
if (properties.text.horizontal_align == TextHorizontalAlignType::Right)
horizontalAlign = 1;
else if (properties.text.horizontal_align == TextHorizontalAlignType::Left)
horizontalAlign = 0;
float verticalAlign = 0.5;
if (properties.text.vertical_align == TextVerticalAlignType::Bottom)
verticalAlign = 1;
else if (properties.text.vertical_align == TextVerticalAlignType::Top)
verticalAlign = 0;
float justify = 0.5;
if (properties.text.justify == TextJustifyType::Right) justify = 1;
else if (properties.text.justify == TextJustifyType::Left) justify = 0;
const FontStack &fontStack = glyphStore.getFontStack(properties.text.font);
for (const SymbolFeature &feature : features) {
Shaping shaping;
Rect<uint16_t> image;
GlyphPositions face;
// if feature has text, shape the text
if (feature.label.length()) {
shaping = fontStack.getShaping(
/* string */ feature.label,
/* maxWidth */ properties.text.max_width,
/* lineHeight */ properties.text.line_height,
/* horizontalAlign */ horizontalAlign,
/* verticalAlign */ verticalAlign,
/* justify */ justify,
/* spacing */ properties.text.letter_spacing,
/* translate */ properties.text.offset);
// Add the glyphs we need for this label to the glyph atlas.
if (shaping.size()) {
SymbolBucket::addGlyphsToAtlas(id.to_uint64(), properties.text.font, feature.label, fontStack,
glyphAtlas, face);
}
}
// if feature has icon, get sprite atlas position
if (feature.sprite.length()) {
sprite.waitUntilLoaded();
image = spriteAtlas.getImage(feature.sprite);
if (sprite.getSpritePosition(feature.sprite).sdf) {
sdfIcons = true;
}
}
// if either shaping or icon position is present, add the feature
if (shaping.size() || image) {
addFeature(feature.geometry, shaping, face, image);
}
}
}
void SymbolBucket::addFeature(const pbf &geom_pbf, const Shaping &shaping,
const GlyphPositions &face, const Rect<uint16_t> &image) {
// Decode all lines.
std::vector<Coordinate> line;
Geometry::command cmd;
Coordinate coord;
pbf geom(geom_pbf);
Geometry geometry(geom);
int32_t x, y;
while ((cmd = geometry.next(x, y)) != Geometry::end) {
if (cmd == Geometry::move_to) {
if (!line.empty()) {
addFeature(line, shaping, face, image);
line.clear();
}
}
line.emplace_back(x, y);
}
if (line.size()) {
addFeature(line, shaping, face, image);
}
}
bool byScale(const Anchor &a, const Anchor &b) { return a.scale < b.scale; }
const PlacementRange fullRange{{2 * M_PI, 0}};
void SymbolBucket::addFeature(const std::vector<Coordinate> &line, const Shaping &shaping,
const GlyphPositions &face, const Rect<uint16_t> &image) {
assert(line.size());
const float minScale = 0.5f;
const float glyphSize = 24.0f;
const bool horizontalText =
properties.text.rotation_alignment == RotationAlignmentType::Viewport;
const bool horizontalIcon =
properties.icon.rotation_alignment == RotationAlignmentType::Viewport;
const float fontScale = properties.text.max_size / glyphSize;
const float textBoxScale = collision.tilePixelRatio * fontScale;
const float iconBoxScale = collision.tilePixelRatio * properties.icon.max_size;
const bool iconWithoutText = properties.text.optional || !shaping.size();
const bool textWithoutIcon = properties.icon.optional || !image;
const bool avoidEdges = properties.avoid_edges && properties.placement != PlacementType::Line;
Anchors anchors;
if (properties.placement == PlacementType::Line) {
// Line labels
anchors = resample(line, properties.min_distance, minScale, collision.maxPlacementScale,
collision.tilePixelRatio);
// Sort anchors by segment so that we can start placement with the
// anchors that can be shown at the lowest zoom levels.
std::sort(anchors.begin(), anchors.end(), byScale);
} else {
// Point labels
anchors = {Anchor{float(line[0].x), float(line[0].y), 0, minScale}};
}
// TODO: figure out correct ascender height.
const vec2<float> origin = {0, -17};
for (Anchor &anchor : anchors) {
// Calculate the scales at which the text and icons can be first shown without overlap
Placement glyphPlacement;
Placement iconPlacement;
float glyphScale = 0;
float iconScale = 0;
const bool inside = !(anchor.x < 0 || anchor.x > 4096 || anchor.y < 0 || anchor.y > 4096);
if (avoidEdges && !inside) continue;
if (shaping.size()) {
glyphPlacement = Placement::getGlyphs(anchor, origin, shaping, face, textBoxScale,
horizontalText, line, properties);
glyphScale =
properties.text.allow_overlap
? glyphPlacement.minScale
: collision.getPlacementScale(glyphPlacement.boxes, glyphPlacement.minScale, avoidEdges);
if (!glyphScale && !iconWithoutText)
continue;
}
if (image) {
iconPlacement = Placement::getIcon(anchor, image, iconBoxScale, line, properties);
iconScale =
properties.icon.allow_overlap
? iconPlacement.minScale
: collision.getPlacementScale(iconPlacement.boxes, iconPlacement.minScale, avoidEdges);
if (!iconScale && !textWithoutIcon)
continue;
}
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);
}
// Get the rotation ranges it is safe to show the glyphs
PlacementRange glyphRange =
(!glyphScale || properties.text.allow_overlap)
? fullRange
: collision.getPlacementRange(glyphPlacement.boxes, glyphScale, horizontalText);
PlacementRange iconRange =
(!iconScale || properties.icon.allow_overlap)
? fullRange
: collision.getPlacementRange(iconPlacement.boxes, iconScale, horizontalIcon);
const PlacementRange maxRange = {{
util::min(iconRange[0], glyphRange[0]), util::max(iconRange[1], glyphRange[1]),
}};
if (!iconWithoutText && !textWithoutIcon) {
iconRange = glyphRange = maxRange;
} else if (!textWithoutIcon) {
glyphRange = maxRange;
} else if (!iconWithoutText) {
iconRange = maxRange;
}
// Insert final placement into collision tree and add glyphs/icons to buffers
if (glyphScale && std::isfinite(glyphScale)) {
if (!properties.text.ignore_placement) {
collision.insert(glyphPlacement.boxes, anchor, glyphScale, glyphRange,
horizontalText);
}
if (inside) addSymbols(text, glyphPlacement.shapes, glyphScale, glyphRange);
}
if (iconScale && std::isfinite(iconScale)) {
if (!properties.icon.ignore_placement) {
collision.insert(iconPlacement.boxes, anchor, iconScale, iconRange, horizontalIcon);
}
if (inside) addSymbols(icon, iconPlacement.shapes, iconScale, iconRange);
}
}
}
template <typename Buffer>
void SymbolBucket::addSymbols(Buffer &buffer, const PlacedGlyphs &symbols, float scale,
PlacementRange placementRange) {
const float zoom = collision.zoom;
const float placementZoom = std::log(scale) / std::log(2) + zoom;
for (const PlacedGlyph &symbol : symbols) {
const auto &tl = symbol.tl;
const auto &tr = symbol.tr;
const auto &bl = symbol.bl;
const auto &br = symbol.br;
const auto &tex = symbol.tex;
const auto &angle = symbol.angle;
float minZoom =
util::max(static_cast<float>(zoom + log(symbol.minScale) / log(2)), placementZoom);
float maxZoom = util::min(static_cast<float>(zoom + log(symbol.maxScale) / log(2)), 25.0f);
const auto &glyphAnchor = symbol.anchor;
if (maxZoom <= minZoom)
continue;
// Lower min zoom so that while fading out the label
// it can be shown outside of collision-free zoom levels
if (minZoom == placementZoom) {
minZoom = 0;
}
const int glyph_vertex_length = 4;
if (!buffer.groups.size() ||
(buffer.groups.back().vertex_length + glyph_vertex_length > 65535)) {
// Move to a new group because the old one can't hold the geometry.
buffer.groups.emplace_back();
}
// We're generating triangle fans, so we always start with the first
// coordinate in this polygon.
auto &triangleGroup = buffer.groups.back();
uint32_t triangleIndex = triangleGroup.vertex_length;
// coordinates (2 triangles)
buffer.vertices.add(glyphAnchor.x, glyphAnchor.y, tl.x, tl.y, tex.x, tex.y, angle, minZoom,
placementRange, maxZoom, placementZoom);
buffer.vertices.add(glyphAnchor.x, glyphAnchor.y, tr.x, tr.y, tex.x + tex.w, tex.y, angle,
minZoom, placementRange, maxZoom, placementZoom);
buffer.vertices.add(glyphAnchor.x, glyphAnchor.y, bl.x, bl.y, tex.x, tex.y + tex.h, angle,
minZoom, placementRange, maxZoom, placementZoom);
buffer.vertices.add(glyphAnchor.x, glyphAnchor.y, br.x, br.y, tex.x + tex.w, tex.y + tex.h,
angle, minZoom, placementRange, maxZoom, placementZoom);
// add the two triangles, referencing the four coordinates we just inserted.
buffer.triangles.add(triangleIndex + 0, triangleIndex + 1, triangleIndex + 2);
buffer.triangles.add(triangleIndex + 1, triangleIndex + 2, triangleIndex + 3);
triangleGroup.vertex_length += glyph_vertex_length;
triangleGroup.elements_length += 2;
}
}
void SymbolBucket::drawGlyphs(SDFShader &shader) {
char *vertex_index = BUFFER_OFFSET(0);
char *elements_index = BUFFER_OFFSET(0);
for (TextElementGroup &group : text.groups) {
group.array[0].bind(shader, text.vertices, text.triangles, vertex_index);
glDrawElements(GL_TRIANGLES, group.elements_length * 3, GL_UNSIGNED_SHORT, elements_index);
vertex_index += group.vertex_length * text.vertices.itemSize;
elements_index += group.elements_length * text.triangles.itemSize;
}
}
void SymbolBucket::drawIcons(SDFShader &shader) {
char *vertex_index = BUFFER_OFFSET(0);
char *elements_index = BUFFER_OFFSET(0);
for (IconElementGroup &group : icon.groups) {
group.array[0].bind(shader, icon.vertices, icon.triangles, vertex_index);
glDrawElements(GL_TRIANGLES, group.elements_length * 3, GL_UNSIGNED_SHORT, elements_index);
vertex_index += group.vertex_length * icon.vertices.itemSize;
elements_index += group.elements_length * icon.triangles.itemSize;
}
}
void SymbolBucket::drawIcons(IconShader &shader) {
char *vertex_index = BUFFER_OFFSET(0);
char *elements_index = BUFFER_OFFSET(0);
for (IconElementGroup &group : icon.groups) {
group.array[1].bind(shader, icon.vertices, icon.triangles, vertex_index);
glDrawElements(GL_TRIANGLES, group.elements_length * 3, GL_UNSIGNED_SHORT, elements_index);
vertex_index += group.vertex_length * icon.vertices.itemSize;
elements_index += group.elements_length * icon.triangles.itemSize;
}
}
}