1 #include <mbgl/layout/symbol_layout.hpp>
2 #include <mbgl/layout/merge_lines.hpp>
3 #include <mbgl/layout/clip_lines.hpp>
4 #include <mbgl/renderer/buckets/symbol_bucket.hpp>
5 #include <mbgl/renderer/bucket_parameters.hpp>
6 #include <mbgl/renderer/layers/render_symbol_layer.hpp>
7 #include <mbgl/renderer/image_atlas.hpp>
8 #include <mbgl/style/layers/symbol_layer_impl.hpp>
9 #include <mbgl/text/get_anchors.hpp>
10 #include <mbgl/text/shaping.hpp>
11 #include <mbgl/util/constants.hpp>
12 #include <mbgl/util/utf.hpp>
13 #include <mbgl/util/std.hpp>
14 #include <mbgl/util/constants.hpp>
15 #include <mbgl/util/string.hpp>
16 #include <mbgl/util/i18n.hpp>
17 #include <mbgl/math/clamp.hpp>
18 #include <mbgl/math/minmax.hpp>
19 #include <mbgl/math/log2.hpp>
20 #include <mbgl/util/platform.hpp>
21 #include <mbgl/util/logging.hpp>
22 #include <mbgl/tile/geometry_tile_data.hpp>
23
24 #include <mapbox/polylabel.hpp>
25
26 namespace mbgl {
27
28 using namespace style;
29
30 template <class Property>
has(const style::SymbolLayoutProperties::PossiblyEvaluated & layout)31 static bool has(const style::SymbolLayoutProperties::PossiblyEvaluated& layout) {
32 return layout.get<Property>().match(
33 [&] (const typename Property::Type& t) { return !t.empty(); },
34 [&] (const auto&) { return true; }
35 );
36 }
37
SymbolLayout(const BucketParameters & parameters,const std::vector<const RenderLayer * > & layers,std::unique_ptr<GeometryTileLayer> sourceLayer_,ImageDependencies & imageDependencies,GlyphDependencies & glyphDependencies)38 SymbolLayout::SymbolLayout(const BucketParameters& parameters,
39 const std::vector<const RenderLayer*>& layers,
40 std::unique_ptr<GeometryTileLayer> sourceLayer_,
41 ImageDependencies& imageDependencies,
42 GlyphDependencies& glyphDependencies)
43 : bucketLeaderID(layers.at(0)->getID()),
44 sourceLayer(std::move(sourceLayer_)),
45 overscaling(parameters.tileID.overscaleFactor()),
46 zoom(parameters.tileID.overscaledZ),
47 mode(parameters.mode),
48 pixelRatio(parameters.pixelRatio),
49 tileSize(util::tileSize * overscaling),
50 tilePixelRatio(float(util::EXTENT) / tileSize),
51 textSize(layers.at(0)->as<RenderSymbolLayer>()->impl().layout.get<TextSize>()),
52 iconSize(layers.at(0)->as<RenderSymbolLayer>()->impl().layout.get<IconSize>())
53 {
54
55 const SymbolLayer::Impl& leader = layers.at(0)->as<RenderSymbolLayer>()->impl();
56
57 layout = leader.layout.evaluate(PropertyEvaluationParameters(zoom));
58
59 if (layout.get<IconRotationAlignment>() == AlignmentType::Auto) {
60 if (layout.get<SymbolPlacement>() != SymbolPlacementType::Point) {
61 layout.get<IconRotationAlignment>() = AlignmentType::Map;
62 } else {
63 layout.get<IconRotationAlignment>() = AlignmentType::Viewport;
64 }
65 }
66
67 if (layout.get<TextRotationAlignment>() == AlignmentType::Auto) {
68 if (layout.get<SymbolPlacement>() != SymbolPlacementType::Point) {
69 layout.get<TextRotationAlignment>() = AlignmentType::Map;
70 } else {
71 layout.get<TextRotationAlignment>() = AlignmentType::Viewport;
72 }
73 }
74
75 // If unspecified `*-pitch-alignment` inherits `*-rotation-alignment`
76 if (layout.get<TextPitchAlignment>() == AlignmentType::Auto) {
77 layout.get<TextPitchAlignment>() = layout.get<TextRotationAlignment>();
78 }
79 if (layout.get<IconPitchAlignment>() == AlignmentType::Auto) {
80 layout.get<IconPitchAlignment>() = layout.get<IconRotationAlignment>();
81 }
82
83 const bool hasText = has<TextField>(layout) && has<TextFont>(layout);
84 const bool hasIcon = has<IconImage>(layout);
85
86 if (!hasText && !hasIcon) {
87 return;
88 }
89
90 for (const auto& layer : layers) {
91 layerPaintProperties.emplace(layer->getID(), std::make_pair(
92 layer->as<RenderSymbolLayer>()->iconPaintProperties(),
93 layer->as<RenderSymbolLayer>()->textPaintProperties()
94 ));
95 }
96
97 // Determine glyph dependencies
98 const size_t featureCount = sourceLayer->featureCount();
99 for (size_t i = 0; i < featureCount; ++i) {
100 auto feature = sourceLayer->getFeature(i);
101 if (!leader.filter(expression::EvaluationContext { this->zoom, feature.get() }))
102 continue;
103
104 SymbolFeature ft(std::move(feature));
105
106 ft.index = i;
107
108 if (hasText) {
109 std::string u8string = layout.evaluate<TextField>(zoom, ft);
110
111 auto textTransform = layout.evaluate<TextTransform>(zoom, ft);
112 if (textTransform == TextTransformType::Uppercase) {
113 u8string = platform::uppercase(u8string);
114 } else if (textTransform == TextTransformType::Lowercase) {
115 u8string = platform::lowercase(u8string);
116 }
117
118 ft.text = applyArabicShaping(util::utf8_to_utf16::convert(u8string));
119 const bool canVerticalizeText = layout.get<TextRotationAlignment>() == AlignmentType::Map
120 && layout.get<SymbolPlacement>() != SymbolPlacementType::Point
121 && util::i18n::allowsVerticalWritingMode(*ft.text);
122
123 FontStack fontStack = layout.evaluate<TextFont>(zoom, ft);
124 GlyphIDs& dependencies = glyphDependencies[fontStack];
125
126 // Loop through all characters of this text and collect unique codepoints.
127 for (char16_t chr : *ft.text) {
128 dependencies.insert(chr);
129 if (canVerticalizeText) {
130 if (char16_t verticalChr = util::i18n::verticalizePunctuation(chr)) {
131 dependencies.insert(verticalChr);
132 }
133 }
134 }
135 }
136
137 if (hasIcon) {
138 ft.icon = layout.evaluate<IconImage>(zoom, ft);
139 imageDependencies.insert(*ft.icon);
140 }
141
142 if (ft.text || ft.icon) {
143 features.push_back(std::move(ft));
144 }
145 }
146
147 if (layout.get<SymbolPlacement>() == SymbolPlacementType::Line) {
148 util::mergeLines(features);
149 }
150 }
151
hasSymbolInstances() const152 bool SymbolLayout::hasSymbolInstances() const {
153 return !symbolInstances.empty();
154 }
155
prepare(const GlyphMap & glyphMap,const GlyphPositions & glyphPositions,const ImageMap & imageMap,const ImagePositions & imagePositions)156 void SymbolLayout::prepare(const GlyphMap& glyphMap, const GlyphPositions& glyphPositions,
157 const ImageMap& imageMap, const ImagePositions& imagePositions) {
158 const bool textAlongLine = layout.get<TextRotationAlignment>() == AlignmentType::Map &&
159 layout.get<SymbolPlacement>() != SymbolPlacementType::Point;
160
161 for (auto it = features.begin(); it != features.end(); ++it) {
162 auto& feature = *it;
163 if (feature.geometry.empty()) continue;
164
165 FontStack fontStack = layout.evaluate<TextFont>(zoom, feature);
166
167 auto glyphMapIt = glyphMap.find(fontStack);
168 const Glyphs& glyphs = glyphMapIt != glyphMap.end()
169 ? glyphMapIt->second : Glyphs();
170
171 auto glyphPositionsIt = glyphPositions.find(fontStack);
172 const GlyphPositionMap& glyphPositionMap = glyphPositionsIt != glyphPositions.end()
173 ? glyphPositionsIt->second : GlyphPositionMap();
174
175 std::pair<Shaping, Shaping> shapedTextOrientations;
176 optional<PositionedIcon> shapedIcon;
177
178 // if feature has text, shape the text
179 if (feature.text) {
180 auto applyShaping = [&] (const std::u16string& text, WritingModeType writingMode) {
181 const float oneEm = 24.0f;
182 const Shaping result = getShaping(
183 /* string */ text,
184 /* maxWidth: ems */ layout.get<SymbolPlacement>() == SymbolPlacementType::Point ?
185 layout.evaluate<TextMaxWidth>(zoom, feature) * oneEm : 0,
186 /* lineHeight: ems */ layout.get<TextLineHeight>() * oneEm,
187 /* anchor */ layout.evaluate<TextAnchor>(zoom, feature),
188 /* justify */ layout.evaluate<TextJustify>(zoom, feature),
189 /* spacing: ems */ util::i18n::allowsLetterSpacing(*feature.text) ? layout.evaluate<TextLetterSpacing>(zoom, feature) * oneEm : 0.0f,
190 /* translate */ Point<float>(layout.evaluate<TextOffset>(zoom, feature)[0] * oneEm, layout.evaluate<TextOffset>(zoom, feature)[1] * oneEm),
191 /* verticalHeight */ oneEm,
192 /* writingMode */ writingMode,
193 /* bidirectional algorithm object */ bidi,
194 /* glyphs */ glyphs);
195
196 return result;
197 };
198
199 shapedTextOrientations.first = applyShaping(*feature.text, WritingModeType::Horizontal);
200
201 if (util::i18n::allowsVerticalWritingMode(*feature.text) && textAlongLine) {
202 shapedTextOrientations.second = applyShaping(util::i18n::verticalizePunctuation(*feature.text), WritingModeType::Vertical);
203 }
204 }
205
206 // if feature has icon, get sprite atlas position
207 if (feature.icon) {
208 auto image = imageMap.find(*feature.icon);
209 if (image != imageMap.end()) {
210 shapedIcon = PositionedIcon::shapeIcon(
211 imagePositions.at(*feature.icon),
212 layout.evaluate<IconOffset>(zoom, feature),
213 layout.evaluate<IconAnchor>(zoom, feature),
214 layout.evaluate<IconRotate>(zoom, feature) * util::DEG2RAD);
215 if (image->second->sdf) {
216 sdfIcons = true;
217 }
218 if (image->second->pixelRatio != pixelRatio) {
219 iconsNeedLinear = true;
220 } else if (layout.get<IconRotate>().constantOr(1) != 0) {
221 iconsNeedLinear = true;
222 }
223 }
224 }
225
226 // if either shapedText or icon position is present, add the feature
227 if (shapedTextOrientations.first || shapedIcon) {
228 addFeature(std::distance(features.begin(), it), feature, shapedTextOrientations, shapedIcon, glyphPositionMap);
229 }
230
231 feature.geometry.clear();
232 }
233
234 compareText.clear();
235 }
236
addFeature(const std::size_t layoutFeatureIndex,const SymbolFeature & feature,const std::pair<Shaping,Shaping> & shapedTextOrientations,optional<PositionedIcon> shapedIcon,const GlyphPositionMap & glyphPositionMap)237 void SymbolLayout::addFeature(const std::size_t layoutFeatureIndex,
238 const SymbolFeature& feature,
239 const std::pair<Shaping, Shaping>& shapedTextOrientations,
240 optional<PositionedIcon> shapedIcon,
241 const GlyphPositionMap& glyphPositionMap) {
242 const float minScale = 0.5f;
243 const float glyphSize = 24.0f;
244
245 const float layoutTextSize = layout.evaluate<TextSize>(zoom + 1, feature);
246 const float layoutIconSize = layout.evaluate<IconSize>(zoom + 1, feature);
247 const std::array<float, 2> textOffset = layout.evaluate<TextOffset>(zoom, feature);
248 const std::array<float, 2> iconOffset = layout.evaluate<IconOffset>(zoom, feature);
249
250 // To reduce the number of labels that jump around when zooming we need
251 // to use a text-size value that is the same for all zoom levels.
252 // This calculates text-size at a high zoom level so that all tiles can
253 // use the same value when calculating anchor positions.
254 const float textMaxSize = layout.evaluate<TextSize>(18, feature);
255
256 const float fontScale = layoutTextSize / glyphSize;
257 const float textBoxScale = tilePixelRatio * fontScale;
258 const float textMaxBoxScale = tilePixelRatio * textMaxSize / glyphSize;
259 const float iconBoxScale = tilePixelRatio * layoutIconSize;
260 const float symbolSpacing = tilePixelRatio * layout.get<SymbolSpacing>();
261 // CJL: I'm not sure why SymbolPlacementType::Line -> avoidEdges = false. It seems redundant since
262 // getAnchors will already avoid generating anchors outside the tile bounds.
263 // However, SymbolPlacementType::LineCenter allows anchors outside tile boundaries, so its behavior
264 // here should match SymbolPlacement::Point
265 const bool avoidEdges = layout.get<SymbolAvoidEdges>() && layout.get<SymbolPlacement>() != SymbolPlacementType::Line;
266 const float textPadding = layout.get<TextPadding>() * tilePixelRatio;
267 const float iconPadding = layout.get<IconPadding>() * tilePixelRatio;
268 const float textMaxAngle = layout.get<TextMaxAngle>() * util::DEG2RAD;
269 const SymbolPlacementType textPlacement = layout.get<TextRotationAlignment>() != AlignmentType::Map
270 ? SymbolPlacementType::Point
271 : layout.get<SymbolPlacement>();
272
273 const float textRepeatDistance = symbolSpacing / 2;
274 IndexedSubfeature indexedFeature(feature.index, sourceLayer->getName(), bucketLeaderID, symbolInstances.size());
275
276 auto addSymbolInstance = [&] (const GeometryCoordinates& line, Anchor& anchor) {
277 // https://github.com/mapbox/vector-tile-spec/tree/master/2.1#41-layers
278 // +-------------------+ Symbols with anchors located on tile edges
279 // |(0,0) || are duplicated on neighbor tiles.
280 // | ||
281 // | || In continuous mode, to avoid overdraw we
282 // | || skip symbols located on the extent edges.
283 // | Tile || In still mode, we include the features in
284 // | || the buffers for both tiles and clip them
285 // | || at draw time.
286 // | ||
287 // +-------------------| In this scenario, the inner bounding box
288 // +-------------------+ is called 'withinPlus0', and the outer
289 // (extent,extent) is called 'inside'.
290 const bool withinPlus0 = anchor.point.x >= 0 && anchor.point.x < util::EXTENT && anchor.point.y >= 0 && anchor.point.y < util::EXTENT;
291 const bool inside = withinPlus0 || anchor.point.x == util::EXTENT || anchor.point.y == util::EXTENT;
292
293 if (avoidEdges && !inside) return;
294
295 if (mode == MapMode::Tile || withinPlus0) {
296 symbolInstances.emplace_back(anchor, line, shapedTextOrientations, shapedIcon,
297 layout.evaluate(zoom, feature), layoutTextSize,
298 textBoxScale, textPadding, textPlacement, textOffset,
299 iconBoxScale, iconPadding, iconOffset,
300 glyphPositionMap, indexedFeature, layoutFeatureIndex, feature.index, feature.text.value_or(std::u16string()), overscaling);
301 }
302 };
303
304 const auto& type = feature.getType();
305
306 if (layout.get<SymbolPlacement>() == SymbolPlacementType::Line) {
307 auto clippedLines = util::clipLines(feature.geometry, 0, 0, util::EXTENT, util::EXTENT);
308 for (const auto& line : clippedLines) {
309 Anchors anchors = getAnchors(line,
310 symbolSpacing,
311 textMaxAngle,
312 (shapedTextOrientations.second ?: shapedTextOrientations.first).left,
313 (shapedTextOrientations.second ?: shapedTextOrientations.first).right,
314 (shapedIcon ? shapedIcon->left() : 0),
315 (shapedIcon ? shapedIcon->right() : 0),
316 glyphSize,
317 textMaxBoxScale,
318 overscaling);
319
320 for (auto& anchor : anchors) {
321 if (!feature.text || !anchorIsTooClose(*feature.text, textRepeatDistance, anchor)) {
322 addSymbolInstance(line, anchor);
323 }
324 }
325 }
326 } else if (layout.get<SymbolPlacement>() == SymbolPlacementType::LineCenter) {
327 // No clipping, multiple lines per feature are allowed
328 // "lines" with only one point are ignored as in clipLines
329 for (const auto& line : feature.geometry) {
330 if (line.size() > 1) {
331 optional<Anchor> anchor = getCenterAnchor(line,
332 textMaxAngle,
333 (shapedTextOrientations.second ?: shapedTextOrientations.first).left,
334 (shapedTextOrientations.second ?: shapedTextOrientations.first).right,
335 (shapedIcon ? shapedIcon->left() : 0),
336 (shapedIcon ? shapedIcon->right() : 0),
337 glyphSize,
338 textMaxBoxScale);
339 if (anchor) {
340 addSymbolInstance(line, *anchor);
341 }
342 }
343 }
344 } else if (type == FeatureType::Polygon) {
345 for (const auto& polygon : classifyRings(feature.geometry)) {
346 Polygon<double> poly;
347 for (const auto& ring : polygon) {
348 LinearRing<double> r;
349 for (const auto& p : ring) {
350 r.push_back(convertPoint<double>(p));
351 }
352 poly.push_back(r);
353 }
354
355 // 1 pixel worth of precision, in tile coordinates
356 auto poi = mapbox::polylabel(poly, double(util::EXTENT / util::tileSize));
357 Anchor anchor(poi.x, poi.y, 0, minScale);
358 addSymbolInstance(polygon[0], anchor);
359 }
360 } else if (type == FeatureType::LineString) {
361 for (const auto& line : feature.geometry) {
362 Anchor anchor(line[0].x, line[0].y, 0, minScale);
363 addSymbolInstance(line, anchor);
364 }
365 } else if (type == FeatureType::Point) {
366 for (const auto& points : feature.geometry) {
367 for (const auto& point : points) {
368 Anchor anchor(point.x, point.y, 0, minScale);
369 addSymbolInstance({point}, anchor);
370 }
371 }
372 }
373 }
374
anchorIsTooClose(const std::u16string & text,const float repeatDistance,const Anchor & anchor)375 bool SymbolLayout::anchorIsTooClose(const std::u16string& text, const float repeatDistance, const Anchor& anchor) {
376 if (compareText.find(text) == compareText.end()) {
377 compareText.emplace(text, Anchors());
378 } else {
379 auto otherAnchors = compareText.find(text)->second;
380 for (const Anchor& otherAnchor : otherAnchors) {
381 if (util::dist<float>(anchor.point, otherAnchor.point) < repeatDistance) {
382 return true;
383 }
384 }
385 }
386 compareText[text].push_back(anchor);
387 return false;
388 }
389
390 // Analog of `addToLineVertexArray` in JS. This version doesn't need to build up a line array like the
391 // JS version does, but it uses the same logic to calculate tile distances.
CalculateTileDistances(const GeometryCoordinates & line,const Anchor & anchor)392 std::vector<float> CalculateTileDistances(const GeometryCoordinates& line, const Anchor& anchor) {
393 std::vector<float> tileDistances(line.size());
394 if (anchor.segment != -1) {
395 auto sumForwardLength = util::dist<float>(anchor.point, line[anchor.segment + 1]);
396 auto sumBackwardLength = util::dist<float>(anchor.point, line[anchor.segment]);
397 for (size_t i = anchor.segment + 1; i < line.size(); i++) {
398 tileDistances[i] = sumForwardLength;
399 if (i < line.size() - 1) {
400 sumForwardLength += util::dist<float>(line[i + 1], line[i]);
401 }
402 }
403 for (auto i = anchor.segment; i >= 0; i--) {
404 tileDistances[i] = sumBackwardLength;
405 if (i > 0) {
406 sumBackwardLength += util::dist<float>(line[i - 1], line[i]);
407 }
408 }
409 }
410 return tileDistances;
411 }
412
place(const bool showCollisionBoxes)413 std::unique_ptr<SymbolBucket> SymbolLayout::place(const bool showCollisionBoxes) {
414 const bool mayOverlap = layout.get<TextAllowOverlap>() || layout.get<IconAllowOverlap>() ||
415 layout.get<TextIgnorePlacement>() || layout.get<IconIgnorePlacement>();
416
417 auto bucket = std::make_unique<SymbolBucket>(layout, layerPaintProperties, textSize, iconSize, zoom, sdfIcons, iconsNeedLinear, mayOverlap, bucketLeaderID, std::move(symbolInstances));
418
419 for (SymbolInstance &symbolInstance : bucket->symbolInstances) {
420
421 const bool hasText = symbolInstance.hasText;
422 const bool hasIcon = symbolInstance.hasIcon;
423
424 const auto& feature = features.at(symbolInstance.layoutFeatureIndex);
425
426 // Insert final placement into collision tree and add glyphs/icons to buffers
427
428 if (hasText) {
429 const Range<float> sizeData = bucket->textSizeBinder->getVertexSizeData(feature);
430 bucket->text.placedSymbols.emplace_back(symbolInstance.anchor.point, symbolInstance.anchor.segment, sizeData.min, sizeData.max,
431 symbolInstance.textOffset, symbolInstance.writingModes, symbolInstance.line, CalculateTileDistances(symbolInstance.line, symbolInstance.anchor));
432 symbolInstance.placedTextIndex = bucket->text.placedSymbols.size() - 1;
433 PlacedSymbol& horizontalSymbol = bucket->text.placedSymbols.back();
434
435 bool firstHorizontal = true;
436 for (const auto& symbol : symbolInstance.horizontalGlyphQuads) {
437 size_t index = addSymbol(
438 bucket->text, sizeData, symbol,
439 symbolInstance.anchor, horizontalSymbol);
440 if (firstHorizontal) {
441 horizontalSymbol.vertexStartIndex = index;
442 firstHorizontal = false;
443 }
444 }
445
446 if (symbolInstance.writingModes & WritingModeType::Vertical) {
447 bucket->text.placedSymbols.emplace_back(symbolInstance.anchor.point, symbolInstance.anchor.segment, sizeData.min, sizeData.max,
448 symbolInstance.textOffset, WritingModeType::Vertical, symbolInstance.line, CalculateTileDistances(symbolInstance.line, symbolInstance.anchor));
449 symbolInstance.placedVerticalTextIndex = bucket->text.placedSymbols.size() - 1;
450
451 PlacedSymbol& verticalSymbol = bucket->text.placedSymbols.back();
452 bool firstVertical = true;
453
454 for (const auto& symbol : symbolInstance.verticalGlyphQuads) {
455 size_t index = addSymbol(
456 bucket->text, sizeData, symbol,
457 symbolInstance.anchor, verticalSymbol);
458
459 if (firstVertical) {
460 verticalSymbol.vertexStartIndex = index;
461 firstVertical = false;
462 }
463 }
464 }
465 }
466
467 if (hasIcon) {
468 if (symbolInstance.iconQuad) {
469 const Range<float> sizeData = bucket->iconSizeBinder->getVertexSizeData(feature);
470 bucket->icon.placedSymbols.emplace_back(symbolInstance.anchor.point, symbolInstance.anchor.segment, sizeData.min, sizeData.max,
471 symbolInstance.iconOffset, WritingModeType::None, symbolInstance.line, std::vector<float>());
472 symbolInstance.placedIconIndex = bucket->icon.placedSymbols.size() - 1;
473 PlacedSymbol& iconSymbol = bucket->icon.placedSymbols.back();
474 iconSymbol.vertexStartIndex = addSymbol(
475 bucket->icon, sizeData, *symbolInstance.iconQuad,
476 symbolInstance.anchor, iconSymbol);
477 }
478 }
479
480 for (auto& pair : bucket->paintPropertyBinders) {
481 pair.second.first.populateVertexVectors(feature, bucket->icon.vertices.vertexSize());
482 pair.second.second.populateVertexVectors(feature, bucket->text.vertices.vertexSize());
483 }
484 }
485
486 if (showCollisionBoxes) {
487 addToDebugBuffers(*bucket);
488 }
489
490 return bucket;
491 }
492
493 template <typename Buffer>
addSymbol(Buffer & buffer,const Range<float> sizeData,const SymbolQuad & symbol,const Anchor & labelAnchor,PlacedSymbol & placedSymbol)494 size_t SymbolLayout::addSymbol(Buffer& buffer,
495 const Range<float> sizeData,
496 const SymbolQuad& symbol,
497 const Anchor& labelAnchor,
498 PlacedSymbol& placedSymbol) {
499 constexpr const uint16_t vertexLength = 4;
500
501 const auto &tl = symbol.tl;
502 const auto &tr = symbol.tr;
503 const auto &bl = symbol.bl;
504 const auto &br = symbol.br;
505 const auto &tex = symbol.tex;
506
507 if (buffer.segments.empty() || buffer.segments.back().vertexLength + vertexLength > std::numeric_limits<uint16_t>::max()) {
508 buffer.segments.emplace_back(buffer.vertices.vertexSize(), buffer.triangles.indexSize());
509 }
510
511 // We're generating triangle fans, so we always start with the first
512 // coordinate in this polygon.
513 auto& segment = buffer.segments.back();
514 assert(segment.vertexLength <= std::numeric_limits<uint16_t>::max());
515 uint16_t index = segment.vertexLength;
516
517 // coordinates (2 triangles)
518 buffer.vertices.emplace_back(SymbolLayoutAttributes::vertex(labelAnchor.point, tl, symbol.glyphOffset.y, tex.x, tex.y, sizeData));
519 buffer.vertices.emplace_back(SymbolLayoutAttributes::vertex(labelAnchor.point, tr, symbol.glyphOffset.y, tex.x + tex.w, tex.y, sizeData));
520 buffer.vertices.emplace_back(SymbolLayoutAttributes::vertex(labelAnchor.point, bl, symbol.glyphOffset.y, tex.x, tex.y + tex.h, sizeData));
521 buffer.vertices.emplace_back(SymbolLayoutAttributes::vertex(labelAnchor.point, br, symbol.glyphOffset.y, tex.x + tex.w, tex.y + tex.h, sizeData));
522
523 // Dynamic/Opacity vertices are initialized so that the vertex count always agrees with
524 // the layout vertex buffer, but they will always be updated before rendering happens
525 auto dynamicVertex = SymbolDynamicLayoutAttributes::vertex(labelAnchor.point, 0);
526 buffer.dynamicVertices.emplace_back(dynamicVertex);
527 buffer.dynamicVertices.emplace_back(dynamicVertex);
528 buffer.dynamicVertices.emplace_back(dynamicVertex);
529 buffer.dynamicVertices.emplace_back(dynamicVertex);
530
531 auto opacityVertex = SymbolOpacityAttributes::vertex(1.0, 1.0);
532 buffer.opacityVertices.emplace_back(opacityVertex);
533 buffer.opacityVertices.emplace_back(opacityVertex);
534 buffer.opacityVertices.emplace_back(opacityVertex);
535 buffer.opacityVertices.emplace_back(opacityVertex);
536
537 // add the two triangles, referencing the four coordinates we just inserted.
538 buffer.triangles.emplace_back(index + 0, index + 1, index + 2);
539 buffer.triangles.emplace_back(index + 1, index + 2, index + 3);
540
541 segment.vertexLength += vertexLength;
542 segment.indexLength += 6;
543
544 placedSymbol.glyphOffsets.push_back(symbol.glyphOffset.x);
545
546 return index;
547 }
548
addToDebugBuffers(SymbolBucket & bucket)549 void SymbolLayout::addToDebugBuffers(SymbolBucket& bucket) {
550
551 if (!hasSymbolInstances()) {
552 return;
553 }
554
555 for (const SymbolInstance &symbolInstance : symbolInstances) {
556 auto populateCollisionBox = [&](const auto& feature) {
557 SymbolBucket::CollisionBuffer& collisionBuffer = feature.alongLine ?
558 static_cast<SymbolBucket::CollisionBuffer&>(bucket.collisionCircle) :
559 static_cast<SymbolBucket::CollisionBuffer&>(bucket.collisionBox);
560 for (const CollisionBox &box : feature.boxes) {
561 auto& anchor = box.anchor;
562
563 Point<float> tl{box.x1, box.y1};
564 Point<float> tr{box.x2, box.y1};
565 Point<float> bl{box.x1, box.y2};
566 Point<float> br{box.x2, box.y2};
567
568 static constexpr std::size_t vertexLength = 4;
569 const std::size_t indexLength = feature.alongLine ? 6 : 8;
570
571 if (collisionBuffer.segments.empty() || collisionBuffer.segments.back().vertexLength + vertexLength > std::numeric_limits<uint16_t>::max()) {
572 collisionBuffer.segments.emplace_back(collisionBuffer.vertices.vertexSize(),
573 feature.alongLine? bucket.collisionCircle.triangles.indexSize() : bucket.collisionBox.lines.indexSize());
574 }
575
576 auto& segment = collisionBuffer.segments.back();
577 uint16_t index = segment.vertexLength;
578
579 collisionBuffer.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.anchor.point, tl));
580 collisionBuffer.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.anchor.point, tr));
581 collisionBuffer.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.anchor.point, br));
582 collisionBuffer.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.anchor.point, bl));
583
584 // Dynamic vertices are initialized so that the vertex count always agrees with
585 // the layout vertex buffer, but they will always be updated before rendering happens
586 auto dynamicVertex = CollisionBoxDynamicAttributes::vertex(false, false);
587 collisionBuffer.dynamicVertices.emplace_back(dynamicVertex);
588 collisionBuffer.dynamicVertices.emplace_back(dynamicVertex);
589 collisionBuffer.dynamicVertices.emplace_back(dynamicVertex);
590 collisionBuffer.dynamicVertices.emplace_back(dynamicVertex);
591
592 if (feature.alongLine) {
593 bucket.collisionCircle.triangles.emplace_back(index, index + 1, index + 2);
594 bucket.collisionCircle.triangles.emplace_back(index, index + 2, index + 3);
595 } else {
596 bucket.collisionBox.lines.emplace_back(index + 0, index + 1);
597 bucket.collisionBox.lines.emplace_back(index + 1, index + 2);
598 bucket.collisionBox.lines.emplace_back(index + 2, index + 3);
599 bucket.collisionBox.lines.emplace_back(index + 3, index + 0);
600 }
601
602 segment.vertexLength += vertexLength;
603 segment.indexLength += indexLength;
604 }
605 };
606 populateCollisionBox(symbolInstance.textCollisionFeature);
607 populateCollisionBox(symbolInstance.iconCollisionFeature);
608 }
609 }
610
611 } // namespace mbgl
612