1 #include <mbgl/text/placement.hpp>
2 #include <mbgl/renderer/render_layer.hpp>
3 #include <mbgl/renderer/layers/render_symbol_layer.hpp>
4 #include <mbgl/renderer/render_tile.hpp>
5 #include <mbgl/tile/geometry_tile.hpp>
6 #include <mbgl/renderer/buckets/symbol_bucket.hpp>
7 #include <mbgl/renderer/bucket.hpp>
8 
9 namespace mbgl {
10 
OpacityState(bool placed_,bool skipFade)11 OpacityState::OpacityState(bool placed_, bool skipFade)
12     : opacity((skipFade && placed_) ? 1 : 0)
13     , placed(placed_)
14 {
15 }
16 
OpacityState(const OpacityState & prevState,float increment,bool placed_)17 OpacityState::OpacityState(const OpacityState& prevState, float increment, bool placed_) :
18     opacity(::fmax(0, ::fmin(1, prevState.opacity + (prevState.placed ? increment : -increment)))),
19     placed(placed_) {}
20 
isHidden() const21 bool OpacityState::isHidden() const {
22     return opacity == 0 && !placed;
23 }
24 
JointOpacityState(bool placedText,bool placedIcon,bool skipFade)25 JointOpacityState::JointOpacityState(bool placedText, bool placedIcon, bool skipFade) :
26     icon(OpacityState(placedIcon, skipFade)),
27     text(OpacityState(placedText, skipFade)) {}
28 
JointOpacityState(const JointOpacityState & prevOpacityState,float increment,bool placedText,bool placedIcon)29 JointOpacityState::JointOpacityState(const JointOpacityState& prevOpacityState, float increment, bool placedText, bool placedIcon) :
30     icon(OpacityState(prevOpacityState.icon, increment, placedIcon)),
31     text(OpacityState(prevOpacityState.text, increment, placedText)) {}
32 
isHidden() const33 bool JointOpacityState::isHidden() const {
34     return icon.isHidden() && text.isHidden();
35 }
36 
Placement(const TransformState & state_,MapMode mapMode_)37 Placement::Placement(const TransformState& state_, MapMode mapMode_)
38     : collisionIndex(state_)
39     , state(state_)
40     , mapMode(mapMode_)
41 {}
42 
placeLayer(RenderSymbolLayer & symbolLayer,const mat4 & projMatrix,bool showCollisionBoxes)43 void Placement::placeLayer(RenderSymbolLayer& symbolLayer, const mat4& projMatrix, bool showCollisionBoxes) {
44 
45     std::unordered_set<uint32_t> seenCrossTileIDs;
46 
47     for (RenderTile& renderTile : symbolLayer.renderTiles) {
48         if (!renderTile.tile.isRenderable()) {
49             continue;
50         }
51         assert(dynamic_cast<GeometryTile*>(&renderTile.tile));
52         GeometryTile& geometryTile = static_cast<GeometryTile&>(renderTile.tile);
53 
54         auto bucket = renderTile.tile.getBucket<SymbolBucket>(*symbolLayer.baseImpl);
55         if (!bucket) {
56             continue;
57         }
58         SymbolBucket& symbolBucket = *bucket;
59 
60         if (symbolBucket.bucketLeaderID != symbolLayer.getID()) {
61             // Only place this layer if it's the "group leader" for the bucket
62             continue;
63         }
64 
65         auto& layout = symbolBucket.layout;
66 
67         const float pixelsToTileUnits = renderTile.id.pixelsToTileUnits(1, state.getZoom());
68 
69         const float scale = std::pow(2, state.getZoom() - geometryTile.id.overscaledZ);
70         const float textPixelRatio = (util::tileSize * geometryTile.id.overscaleFactor()) / util::EXTENT;
71 
72         mat4 posMatrix;
73         state.matrixFor(posMatrix, renderTile.id);
74         matrix::multiply(posMatrix, projMatrix, posMatrix);
75 
76         mat4 textLabelPlaneMatrix = getLabelPlaneMatrix(posMatrix,
77                 layout.get<style::TextPitchAlignment>() == style::AlignmentType::Map,
78                 layout.get<style::TextRotationAlignment>() == style::AlignmentType::Map,
79                 state,
80                 pixelsToTileUnits);
81 
82         mat4 iconLabelPlaneMatrix = getLabelPlaneMatrix(posMatrix,
83                 layout.get<style::IconPitchAlignment>() == style::AlignmentType::Map,
84                 layout.get<style::IconRotationAlignment>() == style::AlignmentType::Map,
85                 state,
86                 pixelsToTileUnits);
87 
88 
89         // As long as this placement lives, we have to hold onto this bucket's
90         // matching FeatureIndex/data for querying purposes
91         retainedQueryData.emplace(std::piecewise_construct,
92                                   std::forward_as_tuple(symbolBucket.bucketInstanceId),
93                                   std::forward_as_tuple(symbolBucket.bucketInstanceId, geometryTile.getFeatureIndex(), geometryTile.id));
94 
95         placeLayerBucket(symbolBucket, posMatrix, textLabelPlaneMatrix, iconLabelPlaneMatrix, scale, textPixelRatio, showCollisionBoxes, seenCrossTileIDs, renderTile.tile.holdForFade());
96     }
97 }
98 
placeLayerBucket(SymbolBucket & bucket,const mat4 & posMatrix,const mat4 & textLabelPlaneMatrix,const mat4 & iconLabelPlaneMatrix,const float scale,const float textPixelRatio,const bool showCollisionBoxes,std::unordered_set<uint32_t> & seenCrossTileIDs,const bool holdingForFade)99 void Placement::placeLayerBucket(
100         SymbolBucket& bucket,
101         const mat4& posMatrix,
102         const mat4& textLabelPlaneMatrix,
103         const mat4& iconLabelPlaneMatrix,
104         const float scale,
105         const float textPixelRatio,
106         const bool showCollisionBoxes,
107         std::unordered_set<uint32_t>& seenCrossTileIDs,
108         const bool holdingForFade) {
109 
110     auto partiallyEvaluatedTextSize = bucket.textSizeBinder->evaluateForZoom(state.getZoom());
111     auto partiallyEvaluatedIconSize = bucket.iconSizeBinder->evaluateForZoom(state.getZoom());
112 
113     for (auto& symbolInstance : bucket.symbolInstances) {
114 
115         if (seenCrossTileIDs.count(symbolInstance.crossTileID) == 0) {
116             if (holdingForFade) {
117                 // Mark all symbols from this tile as "not placed", but don't add to seenCrossTileIDs, because we don't
118                 // know yet if we have a duplicate in a parent tile that _should_ be placed.
119                 placements.emplace(symbolInstance.crossTileID, JointPlacement(false, false, false));
120                 continue;
121             }
122 
123             bool placeText = false;
124             bool placeIcon = false;
125             bool offscreen = true;
126 
127             if (symbolInstance.placedTextIndex) {
128                 PlacedSymbol& placedSymbol = bucket.text.placedSymbols.at(*symbolInstance.placedTextIndex);
129                 const float fontSize = evaluateSizeForFeature(partiallyEvaluatedTextSize, placedSymbol);
130 
131                 auto placed = collisionIndex.placeFeature(symbolInstance.textCollisionFeature,
132                         posMatrix, textLabelPlaneMatrix, textPixelRatio,
133                         placedSymbol, scale, fontSize,
134                         bucket.layout.get<style::TextAllowOverlap>(),
135                         bucket.layout.get<style::TextPitchAlignment>() == style::AlignmentType::Map,
136                         showCollisionBoxes);
137                 placeText = placed.first;
138                 offscreen &= placed.second;
139             }
140 
141             if (symbolInstance.placedIconIndex) {
142                 PlacedSymbol& placedSymbol = bucket.icon.placedSymbols.at(*symbolInstance.placedIconIndex);
143                 const float fontSize = evaluateSizeForFeature(partiallyEvaluatedIconSize, placedSymbol);
144 
145                 auto placed = collisionIndex.placeFeature(symbolInstance.iconCollisionFeature,
146                         posMatrix, iconLabelPlaneMatrix, textPixelRatio,
147                         placedSymbol, scale, fontSize,
148                         bucket.layout.get<style::IconAllowOverlap>(),
149                         bucket.layout.get<style::IconPitchAlignment>() == style::AlignmentType::Map,
150                         showCollisionBoxes);
151                 placeIcon = placed.first;
152                 offscreen &= placed.second;
153             }
154 
155             const bool iconWithoutText = !symbolInstance.hasText || bucket.layout.get<style::TextOptional>();
156             const bool textWithoutIcon = !symbolInstance.hasIcon || bucket.layout.get<style::IconOptional>();
157 
158             // combine placements for icon and text
159             if (!iconWithoutText && !textWithoutIcon) {
160                 placeText = placeIcon = placeText && placeIcon;
161             } else if (!textWithoutIcon) {
162                 placeText = placeText && placeIcon;
163             } else if (!iconWithoutText) {
164                 placeIcon = placeText && placeIcon;
165             }
166 
167             if (placeText) {
168                 collisionIndex.insertFeature(symbolInstance.textCollisionFeature, bucket.layout.get<style::TextIgnorePlacement>(), bucket.bucketInstanceId);
169             }
170 
171             if (placeIcon) {
172                 collisionIndex.insertFeature(symbolInstance.iconCollisionFeature, bucket.layout.get<style::IconIgnorePlacement>(), bucket.bucketInstanceId);
173             }
174 
175             assert(symbolInstance.crossTileID != 0);
176 
177             if (placements.find(symbolInstance.crossTileID) != placements.end()) {
178                 // If there's a previous placement with this ID, it comes from a tile that's fading out
179                 // Erase it so that the placement result from the non-fading tile supersedes it
180                 placements.erase(symbolInstance.crossTileID);
181             }
182 
183             placements.emplace(symbolInstance.crossTileID, JointPlacement(placeText, placeIcon, offscreen || bucket.justReloaded));
184             seenCrossTileIDs.insert(symbolInstance.crossTileID);
185         }
186     }
187 
188     bucket.justReloaded = false;
189 }
190 
commit(const Placement & prevPlacement,TimePoint now)191 void Placement::commit(const Placement& prevPlacement, TimePoint now) {
192     commitTime = now;
193 
194     bool placementChanged = false;
195 
196     float increment = mapMode == MapMode::Continuous ?
197         std::chrono::duration<float>(commitTime - prevPlacement.commitTime) / Duration(std::chrono::milliseconds(300)) :
198         1.0;
199 
200     // add the opacities from the current placement, and copy their current values from the previous placement
201     for (auto& jointPlacement : placements) {
202         auto prevOpacity = prevPlacement.opacities.find(jointPlacement.first);
203         if (prevOpacity != prevPlacement.opacities.end()) {
204             opacities.emplace(jointPlacement.first, JointOpacityState(prevOpacity->second, increment, jointPlacement.second.text, jointPlacement.second.icon));
205             placementChanged = placementChanged ||
206                 jointPlacement.second.icon != prevOpacity->second.icon.placed ||
207                 jointPlacement.second.text != prevOpacity->second.text.placed;
208         } else {
209             opacities.emplace(jointPlacement.first, JointOpacityState(jointPlacement.second.text, jointPlacement.second.icon, jointPlacement.second.skipFade));
210             placementChanged = placementChanged || jointPlacement.second.icon || jointPlacement.second.text;
211         }
212     }
213 
214     // copy and update values from the previous placement that aren't in the current placement but haven't finished fading
215     for (auto& prevOpacity : prevPlacement.opacities) {
216         if (opacities.find(prevOpacity.first) == opacities.end()) {
217             JointOpacityState jointOpacity(prevOpacity.second, increment, false, false);
218             if (!jointOpacity.isHidden()) {
219                 opacities.emplace(prevOpacity.first, jointOpacity);
220                 placementChanged = placementChanged || prevOpacity.second.icon.placed || prevOpacity.second.text.placed;
221             }
222         }
223     }
224 
225     fadeStartTime = placementChanged ? commitTime : prevPlacement.fadeStartTime;
226 }
227 
updateLayerOpacities(RenderSymbolLayer & symbolLayer)228 void Placement::updateLayerOpacities(RenderSymbolLayer& symbolLayer) {
229     std::set<uint32_t> seenCrossTileIDs;
230     for (RenderTile& renderTile : symbolLayer.renderTiles) {
231         if (!renderTile.tile.isRenderable()) {
232             continue;
233         }
234 
235         auto bucket = renderTile.tile.getBucket<SymbolBucket>(*symbolLayer.baseImpl);
236         if (!bucket) {
237             continue;
238         }
239         SymbolBucket& symbolBucket = *bucket;
240 
241         if (symbolBucket.bucketLeaderID != symbolLayer.getID()) {
242             // Only update opacities this layer if it's the "group leader" for the bucket
243             continue;
244         }
245         updateBucketOpacities(symbolBucket, seenCrossTileIDs);
246     }
247 }
248 
updateBucketOpacities(SymbolBucket & bucket,std::set<uint32_t> & seenCrossTileIDs)249 void Placement::updateBucketOpacities(SymbolBucket& bucket, std::set<uint32_t>& seenCrossTileIDs) {
250     if (bucket.hasTextData()) bucket.text.opacityVertices.clear();
251     if (bucket.hasIconData()) bucket.icon.opacityVertices.clear();
252     if (bucket.hasCollisionBoxData()) bucket.collisionBox.dynamicVertices.clear();
253     if (bucket.hasCollisionCircleData()) bucket.collisionCircle.dynamicVertices.clear();
254 
255     JointOpacityState duplicateOpacityState(false, false, true);
256 
257     JointOpacityState defaultOpacityState(
258             bucket.layout.get<style::TextAllowOverlap>(),
259             bucket.layout.get<style::IconAllowOverlap>(),
260             true);
261 
262     for (SymbolInstance& symbolInstance : bucket.symbolInstances) {
263         bool isDuplicate = seenCrossTileIDs.count(symbolInstance.crossTileID) > 0;
264 
265         auto it = opacities.find(symbolInstance.crossTileID);
266         auto opacityState = defaultOpacityState;
267         if (isDuplicate) {
268             opacityState = duplicateOpacityState;
269         } else if (it != opacities.end()) {
270             opacityState = it->second;
271         }
272 
273         if (it == opacities.end()) {
274             opacities.emplace(symbolInstance.crossTileID, defaultOpacityState);
275         }
276 
277         seenCrossTileIDs.insert(symbolInstance.crossTileID);
278 
279         if (symbolInstance.hasText) {
280             auto opacityVertex = SymbolOpacityAttributes::vertex(opacityState.text.placed, opacityState.text.opacity);
281             for (size_t i = 0; i < symbolInstance.horizontalGlyphQuads.size() * 4; i++) {
282                 bucket.text.opacityVertices.emplace_back(opacityVertex);
283             }
284             for (size_t i = 0; i < symbolInstance.verticalGlyphQuads.size() * 4; i++) {
285                 bucket.text.opacityVertices.emplace_back(opacityVertex);
286             }
287             if (symbolInstance.placedTextIndex) {
288                 bucket.text.placedSymbols[*symbolInstance.placedTextIndex].hidden = opacityState.isHidden();
289             }
290             if (symbolInstance.placedVerticalTextIndex) {
291                 bucket.text.placedSymbols[*symbolInstance.placedVerticalTextIndex].hidden = opacityState.isHidden();
292             }
293         }
294         if (symbolInstance.hasIcon) {
295             auto opacityVertex = SymbolOpacityAttributes::vertex(opacityState.icon.placed, opacityState.icon.opacity);
296             if (symbolInstance.iconQuad) {
297                 bucket.icon.opacityVertices.emplace_back(opacityVertex);
298                 bucket.icon.opacityVertices.emplace_back(opacityVertex);
299                 bucket.icon.opacityVertices.emplace_back(opacityVertex);
300                 bucket.icon.opacityVertices.emplace_back(opacityVertex);
301             }
302             if (symbolInstance.placedIconIndex) {
303                 bucket.icon.placedSymbols[*symbolInstance.placedIconIndex].hidden = opacityState.isHidden();
304             }
305         }
306 
307         auto updateCollisionBox = [&](const auto& feature, const bool placed) {
308             if (feature.alongLine) {
309                 return;
310             }
311             auto dynamicVertex = CollisionBoxDynamicAttributes::vertex(placed, false);
312             for (size_t i = 0; i < feature.boxes.size() * 4; i++) {
313                 bucket.collisionBox.dynamicVertices.emplace_back(dynamicVertex);
314             }
315         };
316 
317         auto updateCollisionCircles = [&](const auto& feature, const bool placed) {
318             if (!feature.alongLine) {
319                 return;
320             }
321             for (const CollisionBox& box : feature.boxes) {
322                 auto dynamicVertex = CollisionBoxDynamicAttributes::vertex(placed, !box.used);
323                 bucket.collisionCircle.dynamicVertices.emplace_back(dynamicVertex);
324                 bucket.collisionCircle.dynamicVertices.emplace_back(dynamicVertex);
325                 bucket.collisionCircle.dynamicVertices.emplace_back(dynamicVertex);
326                 bucket.collisionCircle.dynamicVertices.emplace_back(dynamicVertex);
327             }
328         };
329 
330         if (bucket.hasCollisionBoxData()) {
331             updateCollisionBox(symbolInstance.textCollisionFeature, opacityState.text.placed);
332             updateCollisionBox(symbolInstance.iconCollisionFeature, opacityState.icon.placed);
333         }
334         if (bucket.hasCollisionCircleData()) {
335             updateCollisionCircles(symbolInstance.textCollisionFeature, opacityState.text.placed);
336             updateCollisionCircles(symbolInstance.iconCollisionFeature, opacityState.icon.placed);
337         }
338     }
339 
340     bucket.updateOpacity();
341     bucket.sortFeatures(state.getAngle());
342     auto retainedData = retainedQueryData.find(bucket.bucketInstanceId);
343     if (retainedData != retainedQueryData.end()) {
344         retainedData->second.featureSortOrder = bucket.featureSortOrder;
345     }
346 }
347 
symbolFadeChange(TimePoint now) const348 float Placement::symbolFadeChange(TimePoint now) const {
349     if (mapMode == MapMode::Continuous) {
350         return std::chrono::duration<float>(now - commitTime) / Duration(std::chrono::milliseconds(300));
351     } else {
352         return 1.0;
353     }
354 }
355 
hasTransitions(TimePoint now) const356 bool Placement::hasTransitions(TimePoint now) const {
357     if (mapMode == MapMode::Continuous) {
358         return stale || std::chrono::duration<float>(now - fadeStartTime) < Duration(std::chrono::milliseconds(300));
359     } else {
360         return false;
361     }
362 }
363 
stillRecent(TimePoint now) const364 bool Placement::stillRecent(TimePoint now) const {
365     return mapMode == MapMode::Continuous && commitTime + Duration(std::chrono::milliseconds(300)) > now;
366 }
367 
setStale()368 void Placement::setStale() {
369     stale = true;
370 }
371 
getCollisionIndex() const372 const CollisionIndex& Placement::getCollisionIndex() const {
373     return collisionIndex;
374 }
375 
getQueryData(uint32_t bucketInstanceId) const376 const RetainedQueryData& Placement::getQueryData(uint32_t bucketInstanceId) const {
377     auto it = retainedQueryData.find(bucketInstanceId);
378     if (it == retainedQueryData.end()) {
379         throw std::runtime_error("Placement::getQueryData with unrecognized bucketInstanceId");
380     }
381     return it->second;
382 }
383 
384 } // namespace mbgl
385