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