1 #include <mbgl/renderer/buckets/line_bucket.hpp>
2 #include <mbgl/renderer/layers/render_line_layer.hpp>
3 #include <mbgl/renderer/bucket_parameters.hpp>
4 #include <mbgl/style/layers/line_layer_impl.hpp>
5 #include <mbgl/util/math.hpp>
6 #include <mbgl/util/constants.hpp>
7 
8 #include <cassert>
9 
10 namespace mbgl {
11 
12 using namespace style;
13 
LineBucket(const BucketParameters & parameters,const std::vector<const RenderLayer * > & layers,const style::LineLayoutProperties::Unevaluated & layout_)14 LineBucket::LineBucket(const BucketParameters& parameters,
15                        const std::vector<const RenderLayer*>& layers,
16                        const style::LineLayoutProperties::Unevaluated& layout_)
17     : Bucket(LayerType::Line),
18       layout(layout_.evaluate(PropertyEvaluationParameters(parameters.tileID.overscaledZ))),
19       overscaling(parameters.tileID.overscaleFactor()),
20       zoom(parameters.tileID.overscaledZ) {
21     for (const auto& layer : layers) {
22         paintPropertyBinders.emplace(
23             std::piecewise_construct,
24             std::forward_as_tuple(layer->getID()),
25             std::forward_as_tuple(
26                 layer->as<RenderLineLayer>()->evaluated,
27                 parameters.tileID.overscaledZ));
28     }
29 }
30 
addFeature(const GeometryTileFeature & feature,const GeometryCollection & geometryCollection)31 void LineBucket::addFeature(const GeometryTileFeature& feature,
32                             const GeometryCollection& geometryCollection) {
33     for (auto& line : geometryCollection) {
34         addGeometry(line, feature);
35     }
36 
37     for (auto& pair : paintPropertyBinders) {
38         pair.second.populateVertexVectors(feature, vertices.vertexSize());
39     }
40 }
41 
42 /*
43  * Sharp corners cause dashed lines to tilt because the distance along the line
44  * is the same at both the inner and outer corners. To improve the appearance of
45  * dashed lines we add extra points near sharp corners so that a smaller part
46  * of the line is tilted.
47  *
48  * COS_HALF_SHARP_CORNER controls how sharp a corner has to be for us to add an
49  * extra vertex. The default is 75 degrees.
50  *
51  * The newly created vertices are placed SHARP_CORNER_OFFSET pixels from the corner.
52  */
53 const float COS_HALF_SHARP_CORNER = std::cos(75.0 / 2.0 * (M_PI / 180.0));
54 const float SHARP_CORNER_OFFSET = 15.0f;
55 
56 // The number of bits that is used to store the line distance in the buffer.
57 const int LINE_DISTANCE_BUFFER_BITS = 14;
58 
59 // We don't have enough bits for the line distance as we'd like to have, so
60 // use this value to scale the line distance (in tile units) down to a smaller
61 // value. This lets us store longer distances while sacrificing precision.
62 const float LINE_DISTANCE_SCALE = 1.0 / 2.0;
63 
64 // The maximum line distance, in tile units, that fits in the buffer.
65 const float MAX_LINE_DISTANCE = std::pow(2, LINE_DISTANCE_BUFFER_BITS) / LINE_DISTANCE_SCALE;
66 
addGeometry(const GeometryCoordinates & coordinates,const GeometryTileFeature & feature)67 void LineBucket::addGeometry(const GeometryCoordinates& coordinates, const GeometryTileFeature& feature) {
68     const FeatureType type = feature.getType();
69     const std::size_t len = [&coordinates] {
70         std::size_t l = coordinates.size();
71         // If the line has duplicate vertices at the end, adjust length to remove them.
72         while (l >= 2 && coordinates[l - 1] == coordinates[l - 2]) {
73             l--;
74         }
75         return l;
76     }();
77 
78     const std::size_t first = [&coordinates, &len] {
79         std::size_t i = 0;
80         // If the line has duplicate vertices at the start, adjust index to remove them.
81         while (i < len - 1 && coordinates[i] == coordinates[i + 1]) {
82             i++;
83         }
84         return i;
85     }();
86 
87     // Ignore invalid geometry.
88     if (len < (type == FeatureType::Polygon ? 3 : 2)) {
89         return;
90     }
91 
92     const LineJoinType joinType = layout.evaluate<LineJoin>(zoom, feature);
93 
94     const float miterLimit = joinType == LineJoinType::Bevel ? 1.05f : float(layout.get<LineMiterLimit>());
95 
96     const double sharpCornerOffset = SHARP_CORNER_OFFSET * (float(util::EXTENT) / (util::tileSize * overscaling));
97 
98     const GeometryCoordinate firstCoordinate = coordinates[first];
99     const LineCapType beginCap = layout.get<LineCap>();
100     const LineCapType endCap = type == FeatureType::Polygon ? LineCapType::Butt : LineCapType(layout.get<LineCap>());
101 
102     double distance = 0;
103     bool startOfLine = true;
104     optional<GeometryCoordinate> currentCoordinate;
105     optional<GeometryCoordinate> prevCoordinate;
106     optional<GeometryCoordinate> nextCoordinate;
107     optional<Point<double>> prevNormal;
108     optional<Point<double>> nextNormal;
109 
110     // the last three vertices added
111     e1 = e2 = e3 = -1;
112 
113     if (type == FeatureType::Polygon) {
114         currentCoordinate = coordinates[len - 2];
115         nextNormal = util::perp(util::unit(convertPoint<double>(firstCoordinate - *currentCoordinate)));
116     }
117 
118     const std::size_t startVertex = vertices.vertexSize();
119     std::vector<TriangleElement> triangleStore;
120 
121     for (std::size_t i = first; i < len; ++i) {
122         if (type == FeatureType::Polygon && i == len - 1) {
123             // if the line is closed, we treat the last vertex like the first
124             nextCoordinate = coordinates[first + 1];
125         } else if (i + 1 < len) {
126             // just the next vertex
127             nextCoordinate = coordinates[i + 1];
128         } else {
129             // there is no next vertex
130             nextCoordinate = {};
131         }
132 
133         // if two consecutive vertices exist, skip the current one
134         if (nextCoordinate && coordinates[i] == *nextCoordinate) {
135             continue;
136         }
137 
138         if (nextNormal) {
139             prevNormal = *nextNormal;
140         }
141         if (currentCoordinate) {
142             prevCoordinate = *currentCoordinate;
143         }
144 
145         currentCoordinate = coordinates[i];
146 
147         // Calculate the normal towards the next vertex in this line. In case
148         // there is no next vertex, pretend that the line is continuing straight,
149         // meaning that we are just using the previous normal.
150         nextNormal = nextCoordinate ? util::perp(util::unit(convertPoint<double>(*nextCoordinate - *currentCoordinate)))
151                                 : prevNormal;
152 
153         // If we still don't have a previous normal, this is the beginning of a
154         // non-closed line, so we're doing a straight "join".
155         if (!prevNormal) {
156             prevNormal = *nextNormal;
157         }
158 
159         // Determine the normal of the join extrusion. It is the angle bisector
160         // of the segments between the previous line and the next line.
161         // In the case of 180° angles, the prev and next normals cancel each other out:
162         // prevNormal + nextNormal = (0, 0), its magnitude is 0, so the unit vector would be
163         // undefined. In that case, we're keeping the joinNormal at (0, 0), so that the cosHalfAngle
164         // below will also become 0 and miterLength will become Infinity.
165         Point<double> joinNormal = *prevNormal + *nextNormal;
166         if (joinNormal.x != 0 || joinNormal.y != 0) {
167             joinNormal = util::unit(joinNormal);
168         }
169 
170         /*  joinNormal     prevNormal
171          *             ↖      ↑
172          *                .________. prevVertex
173          *                |
174          * nextNormal  ←  |  currentVertex
175          *                |
176          *     nextVertex !
177          *
178          */
179 
180         // Calculate the length of the miter (the ratio of the miter to the width).
181         // Find the cosine of the angle between the next and join normals
182         // using dot product. The inverse of that is the miter length.
183         const double cosHalfAngle = joinNormal.x * nextNormal->x + joinNormal.y * nextNormal->y;
184         const double miterLength =
185             cosHalfAngle != 0 ? 1 / cosHalfAngle : std::numeric_limits<double>::infinity();
186 
187         const bool isSharpCorner = cosHalfAngle < COS_HALF_SHARP_CORNER && prevCoordinate && nextCoordinate;
188 
189         if (isSharpCorner && i > first) {
190             const auto prevSegmentLength = util::dist<double>(*currentCoordinate, *prevCoordinate);
191             if (prevSegmentLength > 2.0 * sharpCornerOffset) {
192                 GeometryCoordinate newPrevVertex = *currentCoordinate - convertPoint<int16_t>(util::round(convertPoint<double>(*currentCoordinate - *prevCoordinate) * (sharpCornerOffset / prevSegmentLength)));
193                 distance += util::dist<double>(newPrevVertex, *prevCoordinate);
194                 addCurrentVertex(newPrevVertex, distance, *prevNormal, 0, 0, false, startVertex, triangleStore);
195                 prevCoordinate = newPrevVertex;
196             }
197         }
198 
199         // The join if a middle vertex, otherwise the cap
200         const bool middleVertex = prevCoordinate && nextCoordinate;
201         LineJoinType currentJoin = joinType;
202         const LineCapType currentCap = nextCoordinate ? beginCap : endCap;
203 
204         if (middleVertex) {
205             if (currentJoin == LineJoinType::Round) {
206                 if (miterLength < layout.get<LineRoundLimit>()) {
207                     currentJoin = LineJoinType::Miter;
208                 } else if (miterLength <= 2) {
209                     currentJoin = LineJoinType::FakeRound;
210                 }
211             }
212 
213             if (currentJoin == LineJoinType::Miter && miterLength > miterLimit) {
214                 currentJoin = LineJoinType::Bevel;
215             }
216 
217             if (currentJoin == LineJoinType::Bevel) {
218                 // The maximum extrude length is 128 / 63 = 2 times the width of the line
219                 // so if miterLength >= 2 we need to draw a different type of bevel here.
220                 if (miterLength > 2) {
221                     currentJoin = LineJoinType::FlipBevel;
222                 }
223 
224                 // If the miterLength is really small and the line bevel wouldn't be visible,
225                 // just draw a miter join to save a triangle.
226                 if (miterLength < miterLimit) {
227                     currentJoin = LineJoinType::Miter;
228                 }
229             }
230         }
231 
232         // Calculate how far along the line the currentVertex is
233         if (prevCoordinate)
234             distance += util::dist<double>(*currentCoordinate, *prevCoordinate);
235 
236         if (middleVertex && currentJoin == LineJoinType::Miter) {
237             joinNormal = joinNormal * miterLength;
238             addCurrentVertex(*currentCoordinate, distance, joinNormal, 0, 0, false, startVertex,
239                              triangleStore);
240 
241         } else if (middleVertex && currentJoin == LineJoinType::FlipBevel) {
242             // miter is too big, flip the direction to make a beveled join
243 
244             if (miterLength > 100) {
245                 // Almost parallel lines
246                 joinNormal = *nextNormal * -1.0;
247             } else {
248                 const double direction = prevNormal->x * nextNormal->y - prevNormal->y * nextNormal->x > 0 ? -1 : 1;
249                 const double bevelLength = miterLength * util::mag(*prevNormal + *nextNormal) /
250                                           util::mag(*prevNormal - *nextNormal);
251                 joinNormal = util::perp(joinNormal) * bevelLength * direction;
252             }
253 
254             addCurrentVertex(*currentCoordinate, distance, joinNormal, 0, 0, false, startVertex,
255                              triangleStore);
256 
257             addCurrentVertex(*currentCoordinate, distance, joinNormal * -1.0, 0, 0, false, startVertex,
258                              triangleStore);
259         } else if (middleVertex && (currentJoin == LineJoinType::Bevel || currentJoin == LineJoinType::FakeRound)) {
260             const bool lineTurnsLeft = (prevNormal->x * nextNormal->y - prevNormal->y * nextNormal->x) > 0;
261             const float offset = -std::sqrt(miterLength * miterLength - 1);
262             float offsetA;
263             float offsetB;
264 
265             if (lineTurnsLeft) {
266                 offsetB = 0;
267                 offsetA = offset;
268             } else {
269                 offsetA = 0;
270                 offsetB = offset;
271             }
272 
273             // Close previous segement with bevel
274             if (!startOfLine) {
275                 addCurrentVertex(*currentCoordinate, distance, *prevNormal, offsetA, offsetB, false,
276                                  startVertex, triangleStore);
277             }
278 
279             if (currentJoin == LineJoinType::FakeRound) {
280                 // The join angle is sharp enough that a round join would be visible.
281                 // Bevel joins fill the gap between segments with a single pie slice triangle.
282                 // Create a round join by adding multiple pie slices. The join isn't actually round, but
283                 // it looks like it is at the sizes we render lines at.
284 
285                 // Add more triangles for sharper angles.
286                 // This math is just a good enough approximation. It isn't "correct".
287                 const int n = std::floor((0.5 - (cosHalfAngle - 0.5)) * 8);
288 
289                 for (int m = 0; m < n; m++) {
290                     auto approxFractionalJoinNormal = util::unit(*nextNormal * ((m + 1.0) / (n + 1.0)) + *prevNormal);
291                     addPieSliceVertex(*currentCoordinate, distance, approxFractionalJoinNormal, lineTurnsLeft, startVertex, triangleStore);
292                 }
293 
294                 addPieSliceVertex(*currentCoordinate, distance, joinNormal, lineTurnsLeft, startVertex, triangleStore);
295 
296                 for (int k = n - 1; k >= 0; k--) {
297                     auto approxFractionalJoinNormal = util::unit(*prevNormal * ((k + 1.0) / (n + 1.0)) + *nextNormal);
298                     addPieSliceVertex(*currentCoordinate, distance, approxFractionalJoinNormal, lineTurnsLeft, startVertex, triangleStore);
299                 }
300             }
301 
302             // Start next segment
303             if (nextCoordinate) {
304                 addCurrentVertex(*currentCoordinate, distance, *nextNormal, -offsetA, -offsetB,
305                                  false, startVertex, triangleStore);
306             }
307 
308         } else if (!middleVertex && currentCap == LineCapType::Butt) {
309             if (!startOfLine) {
310                 // Close previous segment with a butt
311                 addCurrentVertex(*currentCoordinate, distance, *prevNormal, 0, 0, false,
312                                  startVertex, triangleStore);
313             }
314 
315             // Start next segment with a butt
316             if (nextCoordinate) {
317                 addCurrentVertex(*currentCoordinate, distance, *nextNormal, 0, 0, false,
318                                  startVertex, triangleStore);
319             }
320 
321         } else if (!middleVertex && currentCap == LineCapType::Square) {
322             if (!startOfLine) {
323                 // Close previous segment with a square cap
324                 addCurrentVertex(*currentCoordinate, distance, *prevNormal, 1, 1, false,
325                                  startVertex, triangleStore);
326 
327                 // The segment is done. Unset vertices to disconnect segments.
328                 e1 = e2 = -1;
329             }
330 
331             // Start next segment
332             if (nextCoordinate) {
333                 addCurrentVertex(*currentCoordinate, distance, *nextNormal, -1, -1, false,
334                                  startVertex, triangleStore);
335             }
336 
337         } else if (middleVertex ? currentJoin == LineJoinType::Round : currentCap == LineCapType::Round) {
338             if (!startOfLine) {
339                 // Close previous segment with a butt
340                 addCurrentVertex(*currentCoordinate, distance, *prevNormal, 0, 0, false,
341                                  startVertex, triangleStore);
342 
343                 // Add round cap or linejoin at end of segment
344                 addCurrentVertex(*currentCoordinate, distance, *prevNormal, 1, 1, true, startVertex,
345                                  triangleStore);
346 
347                 // The segment is done. Unset vertices to disconnect segments.
348                 e1 = e2 = -1;
349             }
350 
351             // Start next segment with a butt
352             if (nextCoordinate) {
353                 // Add round cap before first segment
354                 addCurrentVertex(*currentCoordinate, distance, *nextNormal, -1, -1, true,
355                                  startVertex, triangleStore);
356 
357                 addCurrentVertex(*currentCoordinate, distance, *nextNormal, 0, 0, false,
358                                  startVertex, triangleStore);
359             }
360         }
361 
362         if (isSharpCorner && i < len - 1) {
363             const auto nextSegmentLength = util::dist<double>(*currentCoordinate, *nextCoordinate);
364             if (nextSegmentLength > 2 * sharpCornerOffset) {
365                 GeometryCoordinate newCurrentVertex = *currentCoordinate + convertPoint<int16_t>(util::round(convertPoint<double>(*nextCoordinate - *currentCoordinate) * (sharpCornerOffset / nextSegmentLength)));
366                 distance += util::dist<double>(newCurrentVertex, *currentCoordinate);
367                 addCurrentVertex(newCurrentVertex, distance, *nextNormal, 0, 0, false, startVertex, triangleStore);
368                 currentCoordinate = newCurrentVertex;
369             }
370         }
371 
372         startOfLine = false;
373     }
374 
375     const std::size_t endVertex = vertices.vertexSize();
376     const std::size_t vertexCount = endVertex - startVertex;
377 
378     if (segments.empty() || segments.back().vertexLength + vertexCount > std::numeric_limits<uint16_t>::max()) {
379         segments.emplace_back(startVertex, triangles.indexSize());
380     }
381 
382     auto& segment = segments.back();
383     assert(segment.vertexLength <= std::numeric_limits<uint16_t>::max());
384     uint16_t index = segment.vertexLength;
385 
386     for (const auto& triangle : triangleStore) {
387         triangles.emplace_back(index + triangle.a, index + triangle.b, index + triangle.c);
388     }
389 
390     segment.vertexLength += vertexCount;
391     segment.indexLength += triangleStore.size() * 3;
392 }
393 
addCurrentVertex(const GeometryCoordinate & currentCoordinate,double & distance,const Point<double> & normal,double endLeft,double endRight,bool round,std::size_t startVertex,std::vector<TriangleElement> & triangleStore)394 void LineBucket::addCurrentVertex(const GeometryCoordinate& currentCoordinate,
395                                   double &distance,
396                                   const Point<double>& normal,
397                                   double endLeft,
398                                   double endRight,
399                                   bool round,
400                                   std::size_t startVertex,
401                                   std::vector<TriangleElement>& triangleStore) {
402     Point<double> extrude = normal;
403     if (endLeft)
404         extrude = extrude - (util::perp(normal) * endLeft);
405     vertices.emplace_back(LineProgram::layoutVertex(currentCoordinate, extrude, round, false, endLeft, distance * LINE_DISTANCE_SCALE));
406     e3 = vertices.vertexSize() - 1 - startVertex;
407     if (e1 >= 0 && e2 >= 0) {
408         triangleStore.emplace_back(e1, e2, e3);
409     }
410     e1 = e2;
411     e2 = e3;
412 
413     extrude = normal * -1.0;
414     if (endRight)
415         extrude = extrude - (util::perp(normal) * endRight);
416     vertices.emplace_back(LineProgram::layoutVertex(currentCoordinate, extrude, round, true, -endRight, distance * LINE_DISTANCE_SCALE));
417     e3 = vertices.vertexSize() - 1 - startVertex;
418     if (e1 >= 0 && e2 >= 0) {
419         triangleStore.emplace_back(e1, e2, e3);
420     }
421     e1 = e2;
422     e2 = e3;
423 
424     // There is a maximum "distance along the line" that we can store in the buffers.
425     // When we get close to the distance, reset it to zero and add the vertex again with
426     // a distance of zero. The max distance is determined by the number of bits we allocate
427     // to `linesofar`.
428     if (distance > MAX_LINE_DISTANCE / 2.0f) {
429         distance = 0;
430         addCurrentVertex(currentCoordinate, distance, normal, endLeft, endRight, round, startVertex, triangleStore);
431     }
432 }
433 
addPieSliceVertex(const GeometryCoordinate & currentVertex,double distance,const Point<double> & extrude,bool lineTurnsLeft,std::size_t startVertex,std::vector<TriangleElement> & triangleStore)434 void LineBucket::addPieSliceVertex(const GeometryCoordinate& currentVertex,
435                                    double distance,
436                                    const Point<double>& extrude,
437                                    bool lineTurnsLeft,
438                                    std::size_t startVertex,
439                                    std::vector<TriangleElement>& triangleStore) {
440     Point<double> flippedExtrude = extrude * (lineTurnsLeft ? -1.0 : 1.0);
441     vertices.emplace_back(LineProgram::layoutVertex(currentVertex, flippedExtrude, false, lineTurnsLeft, 0, distance * LINE_DISTANCE_SCALE));
442     e3 = vertices.vertexSize() - 1 - startVertex;
443     if (e1 >= 0 && e2 >= 0) {
444         triangleStore.emplace_back(e1, e2, e3);
445     }
446 
447     if (lineTurnsLeft) {
448         e2 = e3;
449     } else {
450         e1 = e3;
451     }
452 }
453 
upload(gl::Context & context)454 void LineBucket::upload(gl::Context& context) {
455     vertexBuffer = context.createVertexBuffer(std::move(vertices));
456     indexBuffer = context.createIndexBuffer(std::move(triangles));
457 
458     for (auto& pair : paintPropertyBinders) {
459         pair.second.upload(context);
460     }
461 
462     uploaded = true;
463 }
464 
hasData() const465 bool LineBucket::hasData() const {
466     return !segments.empty();
467 }
468 
469 template <class Property>
get(const RenderLineLayer & layer,const std::map<std::string,LineProgram::PaintPropertyBinders> & paintPropertyBinders)470 static float get(const RenderLineLayer& layer, const std::map<std::string, LineProgram::PaintPropertyBinders>& paintPropertyBinders) {
471     auto it = paintPropertyBinders.find(layer.getID());
472     if (it == paintPropertyBinders.end() || !it->second.statistics<Property>().max()) {
473         return layer.evaluated.get<Property>().constantOr(Property::defaultValue());
474     } else {
475         return *it->second.statistics<Property>().max();
476     }
477 }
478 
getLineWidth(const RenderLineLayer & layer) const479 float LineBucket::getLineWidth(const RenderLineLayer& layer) const {
480     float lineWidth = get<LineWidth>(layer, paintPropertyBinders);
481     float gapWidth = get<LineGapWidth>(layer, paintPropertyBinders);
482 
483     if (gapWidth) {
484         return gapWidth + 2 * lineWidth;
485     } else {
486         return lineWidth;
487     }
488 }
489 
getQueryRadius(const RenderLayer & layer) const490 float LineBucket::getQueryRadius(const RenderLayer& layer) const {
491     if (!layer.is<RenderLineLayer>()) {
492         return 0;
493     }
494 
495     auto lineLayer = layer.as<RenderLineLayer>();
496 
497     const std::array<float, 2>& translate = lineLayer->evaluated.get<LineTranslate>();
498     float offset = get<LineOffset>(*lineLayer, paintPropertyBinders);
499     return getLineWidth(*lineLayer) / 2.0 + std::abs(offset) + util::length(translate[0], translate[1]);
500 }
501 
502 
503 } // namespace mbgl
504