1 #include <mbgl/layout/symbol_projection.hpp>
2 #include <mbgl/map/transform_state.hpp>
3 #include <mbgl/renderer/render_tile.hpp>
4 #include <mbgl/renderer/buckets/symbol_bucket.hpp>
5 #include <mbgl/renderer/layers/render_symbol_layer.hpp>
6 #include <mbgl/util/optional.hpp>
7 #include <mbgl/util/math.hpp>
8 
9 namespace mbgl {
10 
11 	/*
12 	 * # Overview of coordinate spaces
13 	 *
14 	 * ## Tile coordinate spaces
15 	 * Each label has an anchor. Some labels have corresponding line geometries.
16 	 * The points for both anchors and lines are stored in tile units. Each tile has it's own
17 	 * coordinate space going from (0, 0) at the top left to (EXTENT, EXTENT) at the bottom right.
18 	 *
19 	 * ## GL coordinate space
20 	 * At the end of everything, the vertex shader needs to produce a position in GL coordinate space,
21 	 * which is (-1, 1) at the top left and (1, -1) in the bottom right.
22 	 *
23 	 * ## Map pixel coordinate spaces
24 	 * Each tile has a pixel coordinate space. It's just the tile units scaled so that one unit is
25 	 * whatever counts as 1 pixel at the current zoom.
26 	 * This space is used for pitch-alignment=map, rotation-alignment=map
27 	 *
28 	 * ## Rotated map pixel coordinate spaces
29 	 * Like the above, but rotated so axis of the space are aligned with the viewport instead of the tile.
30 	 * This space is used for pitch-alignment=map, rotation-alignment=viewport
31 	 *
32 	 * ## Viewport pixel coordinate space
33 	 * (0, 0) is at the top left of the canvas and (pixelWidth, pixelHeight) is at the bottom right corner
34 	 * of the canvas. This space is used for pitch-alignment=viewport
35 	 *
36 	 *
37 	 * # Vertex projection
38 	 * It goes roughly like this:
39 	 * 1. project the anchor and line from tile units into the correct label coordinate space
40 	 *      - map pixel space           pitch-alignment=map         rotation-alignment=map
41 	 *      - rotated map pixel space   pitch-alignment=map         rotation-alignment=viewport
42 	 *      - viewport pixel space      pitch-alignment=viewport    rotation-alignment=*
43 	 * 2. if the label follows a line, find the point along the line that is the correct distance from the anchor.
44 	 * 3. add the glyph's corner offset to the point from step 3
45 	 * 4. convert from the label coordinate space to gl coordinates
46 	 *
47 	 * For horizontal labels we want to do step 1 in the shader for performance reasons (no cpu work).
48 	 *      This is what `u_label_plane_matrix` is used for.
49 	 * For labels aligned with lines we have to steps 1 and 2 on the cpu since we need access to the line geometry.
50 	 *      This is what `updateLineLabels(...)` does.
51 	 *      Since the conversion is handled on the cpu we just set `u_label_plane_matrix` to an identity matrix.
52 	 *
53 	 * Steps 3 and 4 are done in the shaders for all labels.
54 	 */
55 
56 	/*
57 	 * Returns a matrix for converting from tile units to the correct label coordinate space.
58 	 */
getLabelPlaneMatrix(const mat4 & posMatrix,const bool pitchWithMap,const bool rotateWithMap,const TransformState & state,const float pixelsToTileUnits)59     mat4 getLabelPlaneMatrix(const mat4& posMatrix, const bool pitchWithMap, const bool rotateWithMap, const TransformState& state, const float pixelsToTileUnits) {
60         mat4 m;
61         matrix::identity(m);
62         if (pitchWithMap) {
63             matrix::scale(m, m, 1 / pixelsToTileUnits, 1 / pixelsToTileUnits, 1);
64             if (!rotateWithMap) {
65                 matrix::rotate_z(m, m, state.getAngle());
66             }
67         } else {
68             matrix::scale(m, m, state.getSize().width / 2.0, -(state.getSize().height / 2.0), 1.0);
69             matrix::translate(m, m, 1, -1, 0);
70             matrix::multiply(m, m, posMatrix);
71         }
72         return m;
73     }
74 
75 	/*
76 	 * Returns a matrix for converting from the correct label coordinate space to gl coords.
77 	 */
getGlCoordMatrix(const mat4 & posMatrix,const bool pitchWithMap,const bool rotateWithMap,const TransformState & state,const float pixelsToTileUnits)78     mat4 getGlCoordMatrix(const mat4& posMatrix, const bool pitchWithMap, const bool rotateWithMap, const TransformState& state, const float pixelsToTileUnits) {
79         mat4 m;
80         matrix::identity(m);
81         if (pitchWithMap) {
82             matrix::multiply(m, m, posMatrix);
83             matrix::scale(m, m, pixelsToTileUnits, pixelsToTileUnits, 1);
84             if (!rotateWithMap) {
85                 matrix::rotate_z(m, m, -state.getAngle());
86             }
87         } else {
88             matrix::scale(m, m, 1, -1, 1);
89             matrix::translate(m, m, -1, -1, 0);
90             matrix::scale(m, m, 2.0 / state.getSize().width, 2.0 / state.getSize().height, 1.0);
91         }
92         return m;
93     }
94 
project(const Point<float> & point,const mat4 & matrix)95     PointAndCameraDistance project(const Point<float>& point, const mat4& matrix) {
96         vec4 pos = {{ point.x, point.y, 0, 1 }};
97         matrix::transformMat4(pos, pos, matrix);
98         return {{ static_cast<float>(pos[0] / pos[3]), static_cast<float>(pos[1] / pos[3]) }, pos[3] };
99     }
100 
evaluateSizeForFeature(const ZoomEvaluatedSize & zoomEvaluatedSize,const PlacedSymbol & placedSymbol)101     float evaluateSizeForFeature(const ZoomEvaluatedSize& zoomEvaluatedSize, const PlacedSymbol& placedSymbol) {
102         if (zoomEvaluatedSize.isFeatureConstant) {
103             return zoomEvaluatedSize.size;
104         } else {
105             if (zoomEvaluatedSize.isZoomConstant) {
106                 return placedSymbol.lowerSize;
107             } else {
108                 return placedSymbol.lowerSize + zoomEvaluatedSize.sizeT * (placedSymbol.upperSize - placedSymbol.lowerSize);
109             }
110         }
111     }
112 
isVisible(const vec4 & anchorPos,const std::array<double,2> & clippingBuffer)113     bool isVisible(const vec4& anchorPos, const std::array<double, 2>& clippingBuffer) {
114         const float x = anchorPos[0] / anchorPos[3];
115         const float y = anchorPos[1] / anchorPos[3];
116         const bool inPaddedViewport = (
117                 x >= -clippingBuffer[0] &&
118                 x <= clippingBuffer[0] &&
119                 y >= -clippingBuffer[1] &&
120                 y <= clippingBuffer[1]);
121         return inPaddedViewport;
122     }
123 
addDynamicAttributes(const Point<float> & anchorPoint,const float angle,gl::VertexVector<SymbolDynamicLayoutAttributes::Vertex> & dynamicVertexArray)124     void addDynamicAttributes(const Point<float>& anchorPoint, const float angle,
125             gl::VertexVector<SymbolDynamicLayoutAttributes::Vertex>& dynamicVertexArray) {
126         auto dynamicVertex = SymbolDynamicLayoutAttributes::vertex(anchorPoint, angle);
127         dynamicVertexArray.emplace_back(dynamicVertex);
128         dynamicVertexArray.emplace_back(dynamicVertex);
129         dynamicVertexArray.emplace_back(dynamicVertex);
130         dynamicVertexArray.emplace_back(dynamicVertex);
131     }
132 
hideGlyphs(size_t numGlyphs,gl::VertexVector<SymbolDynamicLayoutAttributes::Vertex> & dynamicVertexArray)133     void hideGlyphs(size_t numGlyphs, gl::VertexVector<SymbolDynamicLayoutAttributes::Vertex>& dynamicVertexArray) {
134         const Point<float> offscreenPoint = { -INFINITY, -INFINITY };
135         for (size_t i = 0; i < numGlyphs; i++) {
136             addDynamicAttributes(offscreenPoint, 0, dynamicVertexArray);
137         }
138     }
139 
140     enum PlacementResult {
141         OK,
142         NotEnoughRoom,
143         NeedsFlipping,
144         UseVertical
145     };
146 
projectTruncatedLineSegment(const Point<float> & previousTilePoint,const Point<float> & currentTilePoint,const Point<float> & previousProjectedPoint,const float minimumLength,const mat4 & projectionMatrix)147     Point<float> projectTruncatedLineSegment(const Point<float>& previousTilePoint, const Point<float>& currentTilePoint, const Point<float>& previousProjectedPoint, const float minimumLength, const mat4& projectionMatrix) {
148         // We are assuming "previousTilePoint" won't project to a point within one unit of the camera plane
149         // If it did, that would mean our label extended all the way out from within the viewport to a (very distant)
150         // point near the plane of the camera. We wouldn't be able to render the label anyway once it crossed the
151         // plane of the camera.
152         const Point<float> projectedUnitVertex = project(previousTilePoint + util::unit<float>(previousTilePoint - currentTilePoint), projectionMatrix).first;
153         const Point<float> projectedUnitSegment = previousProjectedPoint - projectedUnitVertex;
154 
155         return previousProjectedPoint + (projectedUnitSegment * (minimumLength / util::mag<float>(projectedUnitSegment)));
156     }
157 
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 std::vector<float> & tileDistances,const mat4 & labelPlaneMatrix,const bool returnTileDistance)158 	optional<PlacedGlyph> placeGlyphAlongLine(const float offsetX, const float lineOffsetX, const float lineOffsetY, const bool flip,
159             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) {
160 
161         const float combinedOffsetX = flip ?
162             offsetX - lineOffsetX :
163             offsetX + lineOffsetX;
164 
165         int16_t dir = combinedOffsetX > 0 ? 1 : -1;
166 
167         float angle = 0.0;
168         if (flip) {
169             // The label needs to be flipped to keep text upright.
170             // Iterate in the reverse direction.
171             dir *= -1;
172             angle = M_PI;
173         }
174 
175         if (dir < 0) angle += M_PI;
176 
177         int32_t currentIndex = dir > 0 ? anchorSegment : anchorSegment + 1;
178 
179         const int32_t initialIndex = currentIndex;
180         Point<float> current = projectedAnchorPoint;
181         Point<float> prev = projectedAnchorPoint;
182         float distanceToPrev = 0.0;
183         float currentSegmentDistance = 0.0;
184         const float absOffsetX = std::abs(combinedOffsetX);
185 
186         while (distanceToPrev + currentSegmentDistance <= absOffsetX) {
187             currentIndex += dir;
188 
189             // offset does not fit on the projected line
190             if (currentIndex < 0 || currentIndex >= static_cast<int32_t>(line.size())) {
191                 return {};
192             }
193 
194             prev = current;
195             PointAndCameraDistance projection = project(convertPoint<float>(line.at(currentIndex)), labelPlaneMatrix);
196             if (projection.second > 0) {
197                 current = projection.first;
198             } else {
199                 // The vertex is behind the plane of the camera, so we can't project it
200                 // Instead, we'll create a vertex along the line that's far enough to include the glyph
201                 const Point<float> previousTilePoint = distanceToPrev == 0 ?
202                     tileAnchorPoint :
203                     convertPoint<float>(line.at(currentIndex - dir));
204                 const Point<float> currentTilePoint = convertPoint<float>(line.at(currentIndex));
205                 current = projectTruncatedLineSegment(previousTilePoint, currentTilePoint, prev, absOffsetX - distanceToPrev + 1, labelPlaneMatrix);
206             }
207 
208             distanceToPrev += currentSegmentDistance;
209             currentSegmentDistance = util::dist<float>(prev, current);
210         }
211 
212         // The point is on the current segment. Interpolate to find it.
213         const float segmentInterpolationT = (absOffsetX - distanceToPrev) / currentSegmentDistance;
214         const Point<float> prevToCurrent = current - prev;
215         Point<float> p = (prevToCurrent * segmentInterpolationT) + prev;
216 
217         // offset the point from the line to text-offset and icon-offset
218         p += util::perp(prevToCurrent) * static_cast<float>(lineOffsetY * dir / util::mag(prevToCurrent));
219 
220         const float segmentAngle = angle + std::atan2(current.y - prev.y, current.x - prev.x);
221 
222         return {{
223             p,
224             segmentAngle,
225             returnTileDistance ?
226                 TileDistance(
227                     (currentIndex - dir) == initialIndex ? 0 : tileDistances[currentIndex - dir],
228                     absOffsetX - distanceToPrev
229                 ) :
230                 optional<TileDistance>()
231         }};
232     }
233 
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)234     optional<std::pair<PlacedGlyph, PlacedGlyph>> placeFirstAndLastGlyph(const float fontScale,
235                                                             const float lineOffsetX,
236                                                             const float lineOffsetY,
237                                                             const bool flip,
238                                                             const Point<float>& anchorPoint,
239                                                             const Point<float>& tileAnchorPoint,
240                                                             const PlacedSymbol& symbol,
241                                                             const mat4& labelPlaneMatrix,
242                                                             const bool returnTileDistance) {
243         if (symbol.glyphOffsets.empty()) {
244             assert(false);
245             return optional<std::pair<PlacedGlyph, PlacedGlyph>>();
246         }
247 
248         const float firstGlyphOffset = symbol.glyphOffsets.front();
249         const float lastGlyphOffset = symbol.glyphOffsets.back();;
250 
251         optional<PlacedGlyph> firstPlacedGlyph = placeGlyphAlongLine(fontScale * firstGlyphOffset, lineOffsetX, lineOffsetY, flip, anchorPoint, tileAnchorPoint, symbol.segment, symbol.line, symbol.tileDistances, labelPlaneMatrix,  returnTileDistance);
252         if (!firstPlacedGlyph)
253             return optional<std::pair<PlacedGlyph, PlacedGlyph>>();
254 
255         optional<PlacedGlyph> lastPlacedGlyph = placeGlyphAlongLine(fontScale * lastGlyphOffset, lineOffsetX, lineOffsetY, flip, anchorPoint, tileAnchorPoint, symbol.segment, symbol.line, symbol.tileDistances, labelPlaneMatrix, returnTileDistance);
256         if (!lastPlacedGlyph)
257             return optional<std::pair<PlacedGlyph, PlacedGlyph>>();
258 
259         return std::make_pair(*firstPlacedGlyph, *lastPlacedGlyph);
260     }
261 
requiresOrientationChange(const WritingModeType writingModes,const Point<float> & firstPoint,const Point<float> & lastPoint,const float aspectRatio)262     optional<PlacementResult> requiresOrientationChange(const WritingModeType writingModes, const Point<float>& firstPoint,  const Point<float>& lastPoint, const float aspectRatio) {
263         if (writingModes == (WritingModeType::Horizontal | WritingModeType::Vertical)) {
264             // On top of choosing whether to flip, choose whether to render this version of the glyphs or the alternate
265             // vertical glyphs. We can't just filter out vertical glyphs in the horizontal range because the horizontal
266             // and vertical versions can have slightly different projections which could lead to angles where both or
267             // neither showed.
268             auto rise = std::abs(lastPoint.y - firstPoint.y);
269             auto run = std::abs(lastPoint.x - firstPoint.x) * aspectRatio;
270             if (rise > run) {
271                 return PlacementResult::UseVertical;
272             }
273         }
274 
275         if ((writingModes == WritingModeType::Vertical) ?
276             (firstPoint.y < lastPoint.y) :
277             (firstPoint.x > lastPoint.x)) {
278             // Includes "horizontalOnly" case for labels without vertical glyphs
279             return PlacementResult::NeedsFlipping;
280         }
281         return {};
282     }
283 
placeGlyphsAlongLine(const PlacedSymbol & symbol,const float fontSize,const bool flip,const bool keepUpright,const mat4 & posMatrix,const mat4 & labelPlaneMatrix,const mat4 & glCoordMatrix,gl::VertexVector<SymbolDynamicLayoutAttributes::Vertex> & dynamicVertexArray,const Point<float> & projectedAnchorPoint,const float aspectRatio)284     PlacementResult placeGlyphsAlongLine(const PlacedSymbol& symbol,
285                               const float fontSize,
286                               const bool flip,
287                               const bool keepUpright,
288                               const mat4& posMatrix,
289                               const mat4& labelPlaneMatrix,
290                               const mat4& glCoordMatrix,
291                               gl::VertexVector<SymbolDynamicLayoutAttributes::Vertex>& dynamicVertexArray,
292                               const Point<float>& projectedAnchorPoint,
293                               const float aspectRatio) {
294         const float fontScale = fontSize / 24.0;
295         const float lineOffsetX = symbol.lineOffset[0] * fontSize;
296         const float lineOffsetY = symbol.lineOffset[1] * fontSize;
297 
298         std::vector<PlacedGlyph> placedGlyphs;
299         if (symbol.glyphOffsets.size() > 1) {
300 
301             const optional<std::pair<PlacedGlyph, PlacedGlyph>> firstAndLastGlyph =
302                 placeFirstAndLastGlyph(fontScale, lineOffsetX, lineOffsetY, flip, projectedAnchorPoint, symbol.anchorPoint, symbol, labelPlaneMatrix, false);
303             if (!firstAndLastGlyph) {
304                 return PlacementResult::NotEnoughRoom;
305             }
306 
307             const Point<float> firstPoint = project(firstAndLastGlyph->first.point, glCoordMatrix).first;
308             const Point<float> lastPoint = project(firstAndLastGlyph->second.point, glCoordMatrix).first;
309 
310             if (keepUpright && !flip) {
311                 auto orientationChange = requiresOrientationChange(symbol.writingModes, firstPoint, lastPoint, aspectRatio);
312                 if (orientationChange) {
313                     return *orientationChange;
314                 }
315             }
316 
317             placedGlyphs.push_back(firstAndLastGlyph->first);
318             for (size_t glyphIndex = 1; glyphIndex < symbol.glyphOffsets.size() - 1; glyphIndex++) {
319                 const float glyphOffsetX = symbol.glyphOffsets[glyphIndex];
320                 // Since first and last glyph fit on the line, we're sure that the rest of the glyphs can be placed
321                 auto placedGlyph = placeGlyphAlongLine(glyphOffsetX * fontScale, lineOffsetX, lineOffsetY, flip, projectedAnchorPoint, symbol.anchorPoint, symbol.segment, symbol.line, symbol.tileDistances, labelPlaneMatrix, false);
322                 placedGlyphs.push_back(*placedGlyph);
323             }
324             placedGlyphs.push_back(firstAndLastGlyph->second);
325         } else if (symbol.glyphOffsets.size() == 1) {
326             // Only a single glyph to place
327             // So, determine whether to flip based on projected angle of the line segment it's on
328             if (keepUpright && !flip) {
329                 const Point<float> a = project(symbol.anchorPoint, posMatrix).first;
330                 const Point<float> tileSegmentEnd = convertPoint<float>(symbol.line.at(symbol.segment + 1));
331                 const PointAndCameraDistance projectedVertex = project(tileSegmentEnd, posMatrix);
332                 // We know the anchor will be in the viewport, but the end of the line segment may be
333                 // behind the plane of the camera, in which case we can use a point at any arbitrary (closer)
334                 // point on the segment.
335                 const Point<float> b = (projectedVertex.second > 0) ?
336                     projectedVertex.first :
337                     projectTruncatedLineSegment(symbol.anchorPoint,tileSegmentEnd, a, 1, posMatrix);
338 
339                 auto orientationChange = requiresOrientationChange(symbol.writingModes, a, b, aspectRatio);
340                 if (orientationChange) {
341                     return *orientationChange;
342                 }
343             }
344             const float glyphOffsetX = symbol.glyphOffsets.front();
345             optional<PlacedGlyph> singleGlyph = placeGlyphAlongLine(fontScale * glyphOffsetX, lineOffsetX, lineOffsetY, flip, projectedAnchorPoint, symbol.anchorPoint, symbol.segment,
346                 symbol.line, symbol.tileDistances, labelPlaneMatrix, false);
347             if (!singleGlyph)
348                 return PlacementResult::NotEnoughRoom;
349 
350             placedGlyphs.push_back(*singleGlyph);
351         }
352 
353         // The number of placedGlyphs must equal the number of glyphOffsets, which must correspond to the number of glyph vertices
354         // There may be 0 glyphs here, if a label consists entirely of glyphs that have 0x0 dimensions
355         for (auto& placedGlyph : placedGlyphs) {
356             addDynamicAttributes(placedGlyph.point, placedGlyph.angle, dynamicVertexArray);
357         }
358 
359         return PlacementResult::OK;
360     }
361 
362 
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)363     void reprojectLineLabels(gl::VertexVector<SymbolDynamicLayoutAttributes::Vertex>& dynamicVertexArray, const std::vector<PlacedSymbol>& placedSymbols,
364 			const mat4& posMatrix, const style::SymbolPropertyValues& values,
365             const RenderTile& tile, const SymbolSizeBinder& sizeBinder, const TransformState& state) {
366 
367         const ZoomEvaluatedSize partiallyEvaluatedSize = sizeBinder.evaluateForZoom(state.getZoom());
368 
369         const std::array<double, 2> clippingBuffer = {{ 256.0 / state.getSize().width * 2.0 + 1.0, 256.0 / state.getSize().height * 2.0 + 1.0 }};
370 
371         const bool pitchWithMap = values.pitchAlignment == style::AlignmentType::Map;
372         const bool rotateWithMap = values.rotationAlignment == style::AlignmentType::Map;
373         const float pixelsToTileUnits = tile.id.pixelsToTileUnits(1, state.getZoom());
374 
375         const mat4 labelPlaneMatrix = getLabelPlaneMatrix(posMatrix, pitchWithMap,
376                 rotateWithMap, state, pixelsToTileUnits);
377 
378         const mat4 glCoordMatrix = getGlCoordMatrix(posMatrix, pitchWithMap, rotateWithMap, state, pixelsToTileUnits);
379 
380         dynamicVertexArray.clear();
381 
382         bool useVertical = false;
383 
384         for (auto& placedSymbol : placedSymbols) {
385             // Don't do calculations for vertical glyphs unless the previous symbol was horizontal
386             // and we determined that vertical glyphs were necessary.
387             // Also don't do calculations for symbols that are collided and fully faded out
388             if (placedSymbol.hidden || (placedSymbol.writingModes == WritingModeType::Vertical && !useVertical)) {
389                 hideGlyphs(placedSymbol.glyphOffsets.size(), dynamicVertexArray);
390                 continue;
391             }
392             // Awkward... but we're counting on the paired "vertical" symbol coming immediately after its horizontal counterpart
393             useVertical = false;
394 
395 			vec4 anchorPos = {{ placedSymbol.anchorPoint.x, placedSymbol.anchorPoint.y, 0, 1 }};
396             matrix::transformMat4(anchorPos, anchorPos, posMatrix);
397 
398             // Don't bother calculating the correct point for invisible labels.
399             if (!isVisible(anchorPos, clippingBuffer)) {
400                 hideGlyphs(placedSymbol.glyphOffsets.size(), dynamicVertexArray);
401                 continue;
402             }
403 
404             const float cameraToAnchorDistance = anchorPos[3];
405             const float perspectiveRatio = 0.5 + 0.5 * (cameraToAnchorDistance / state.getCameraToCenterDistance());
406 
407             const float fontSize = evaluateSizeForFeature(partiallyEvaluatedSize, placedSymbol);
408             const float pitchScaledFontSize = values.pitchAlignment == style::AlignmentType::Map ?
409                 fontSize * perspectiveRatio :
410                 fontSize / perspectiveRatio;
411 
412             const Point<float> anchorPoint = project(placedSymbol.anchorPoint, labelPlaneMatrix).first;
413 
414             PlacementResult placeUnflipped = placeGlyphsAlongLine(placedSymbol, pitchScaledFontSize, false /*unflipped*/, values.keepUpright, posMatrix, labelPlaneMatrix, glCoordMatrix, dynamicVertexArray, anchorPoint, state.getSize().aspectRatio());
415 
416             useVertical = placeUnflipped == PlacementResult::UseVertical;
417 
418             if (placeUnflipped == PlacementResult::NotEnoughRoom || useVertical ||
419                 (placeUnflipped == PlacementResult::NeedsFlipping &&
420                  placeGlyphsAlongLine(placedSymbol, pitchScaledFontSize, true /*flipped*/, values.keepUpright, posMatrix, labelPlaneMatrix, glCoordMatrix, dynamicVertexArray, anchorPoint, state.getSize().aspectRatio()) == PlacementResult::NotEnoughRoom)) {
421                 hideGlyphs(placedSymbol.glyphOffsets.size(), dynamicVertexArray);
422             }
423         }
424     }
425 } // end namespace mbgl
426