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