1 #include <mbgl/text/collision_feature.hpp>
2 #include <mbgl/util/math.hpp>
3 #include <mbgl/math/log2.hpp>
4 
5 namespace mbgl {
6 
CollisionFeature(const GeometryCoordinates & line,const Anchor & anchor,const float top,const float bottom,const float left,const float right,const float boxScale,const float padding,const style::SymbolPlacementType placement,IndexedSubfeature indexedFeature_,const float overscaling)7 CollisionFeature::CollisionFeature(const GeometryCoordinates& line,
8                                    const Anchor& anchor,
9                                    const float top,
10                                    const float bottom,
11                                    const float left,
12                                    const float right,
13                                    const float boxScale,
14                                    const float padding,
15                                    const style::SymbolPlacementType placement,
16                                    IndexedSubfeature indexedFeature_,
17                                    const float overscaling)
18         : indexedFeature(std::move(indexedFeature_))
19         , alongLine(placement != style::SymbolPlacementType::Point) {
20     if (top == 0 && bottom == 0 && left == 0 && right == 0) return;
21 
22     const float y1 = top * boxScale - padding;
23     const float y2 = bottom * boxScale + padding;
24     const float x1 = left * boxScale - padding;
25     const float x2 = right * boxScale + padding;
26 
27     if (alongLine) {
28         float height = y2 - y1;
29         const double length = x2 - x1;
30 
31         if (height <= 0.0f) return;
32 
33         height = std::max(10.0f * boxScale, height);
34 
35         GeometryCoordinate anchorPoint = convertPoint<int16_t>(anchor.point);
36         bboxifyLabel(line, anchorPoint, anchor.segment, length, height, overscaling);
37     } else {
38         boxes.emplace_back(anchor.point, Point<float>{ 0, 0 }, x1, y1, x2, y2);
39     }
40 }
41 
bboxifyLabel(const GeometryCoordinates & line,GeometryCoordinate & anchorPoint,const int segment,const float labelLength,const float boxSize,const float overscaling)42 void CollisionFeature::bboxifyLabel(const GeometryCoordinates& line, GeometryCoordinate& anchorPoint,
43                                     const int segment, const float labelLength, const float boxSize, const float overscaling) {
44     const float step = boxSize / 2;
45     const int nBoxes = std::max(static_cast<int>(std::floor(labelLength / step)), 1);
46 
47     // We calculate line collision circles out to 300% of what would normally be our
48     // max size, to allow collision detection to work on labels that expand as
49     // they move into the distance
50     // Vertically oriented labels in the distant field can extend past this padding
51     // This is a noticeable problem in overscaled tiles where the pitch 0-based
52     // symbol spacing will put labels very close together in a pitched map.
53     // To reduce the cost of adding extra collision circles, we slowly increase
54     // them for overscaled tiles.
55     const float overscalingPaddingFactor = 1 + .4 * ::log2(static_cast<double>(overscaling));
56     const int nPitchPaddingBoxes = std::floor(nBoxes * overscalingPaddingFactor / 2);
57 
58     // offset the center of the first box by half a box so that the edge of the
59     // box is at the edge of the label.
60     const float firstBoxOffset = -boxSize / 2;
61 
62     GeometryCoordinate &p = anchorPoint;
63     int index = segment + 1;
64     float anchorDistance = firstBoxOffset;
65     const float labelStartDistance = -labelLength / 2;
66     const float paddingStartDistance = labelStartDistance - labelLength / 8;
67 
68     // move backwards along the line to the first segment the label appears on
69     do {
70         index--;
71 
72         if (index < 0) {
73             if (anchorDistance > labelStartDistance) {
74                 // there isn't enough room for the label after the beginning of the line
75                 // checkMaxAngle should have already caught this
76                 return;
77             } else {
78                 // The line doesn't extend far enough back for all of our padding,
79                 // but we got far enough to show the label under most conditions.
80                 index = 0;
81                 break;
82             }
83         }
84 
85         anchorDistance -= util::dist<float>(line[index], p);
86         p = line[index];
87     } while (anchorDistance > paddingStartDistance);
88 
89     auto segmentLength = util::dist<float>(line[index], line[index + 1]);
90 
91     for (int i = -nPitchPaddingBoxes; i < nBoxes + nPitchPaddingBoxes; i++) {
92         // the distance the box will be from the anchor
93         const float boxOffset = i * step;
94         float boxDistanceToAnchor = labelStartDistance + boxOffset;
95 
96          // make the distance between pitch padding boxes bigger
97          if (boxOffset < 0) boxDistanceToAnchor += boxOffset;
98          if (boxOffset > labelLength) boxDistanceToAnchor += boxOffset - labelLength;
99 
100         if (boxDistanceToAnchor < anchorDistance) {
101             // The line doesn't extend far enough back for this box, skip it
102             // (This could allow for line collisions on distant tiles)
103             continue;
104         }
105 
106         // the box is not on the current segment. Move to the next segment.
107         while (anchorDistance + segmentLength < boxDistanceToAnchor) {
108             anchorDistance += segmentLength;
109             index++;
110 
111             // There isn't enough room before the end of the line.
112             if (index + 1 >= (int)line.size()) return;
113 
114             segmentLength = util::dist<float>(line[index], line[index + 1]);
115         }
116 
117         // the distance the box will be from the beginning of the segment
118         const float segmentBoxDistance = boxDistanceToAnchor - anchorDistance;
119 
120         const auto& p0 = line[index];
121         const auto& p1 = line[index + 1];
122 
123         Point<float> boxAnchor = {
124             p0.x + segmentBoxDistance / segmentLength * (p1.x - p0.x),
125             p0.y + segmentBoxDistance / segmentLength * (p1.y - p0.y)
126         };
127 
128         // If the box is within boxSize of the anchor, force the box to be used
129         // (so even 0-width labels use at least one box)
130         // Otherwise, the .8 multiplication gives us a little bit of conservative
131         // padding in choosing which boxes to use (see CollisionIndex#placedCollisionCircles)
132         const float paddedAnchorDistance = std::abs(boxDistanceToAnchor - firstBoxOffset) < step ?
133             0 :
134             (boxDistanceToAnchor - firstBoxOffset) * 0.8;
135 
136         boxes.emplace_back(boxAnchor, boxAnchor - convertPoint<float>(anchorPoint), -boxSize / 2, -boxSize / 2, boxSize / 2, boxSize / 2, paddedAnchorDistance, boxSize / 2);
137     }
138 }
139 
140 
141 } // namespace mbgl
142