Skip to content
This repository was archived by the owner on Aug 8, 2023. It is now read-only.

Commit 4249ce8

Browse files
committed
Port line-dasharray fix from gl-js
* Fix artifact for zero-lenghted dash array Fixes issue mapbox/mapbox-gl-js#9213 and add render test for coverage around 0-length dash arrays Update distance field generation for regular dashes as follows: 1. Compute the dash array ranges and stretch location along the distance field 2. Collapse any 0-length distance field range 3. Collapse neighbouring same-type parts into single part * combine consecutive dashes and parts mapbox/mapbox-gl-js#9246 #16181
1 parent d3535f1 commit 4249ce8

File tree

2 files changed

+115
-57
lines changed

2 files changed

+115
-57
lines changed

src/mbgl/geometry/line_atlas.cpp

+108-57
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
#include <mbgl/util/logging.hpp>
77
#include <mbgl/util/platform.hpp>
88

9-
#include <cmath>
10-
119
namespace mbgl {
1210
namespace {
1311

@@ -20,12 +18,112 @@ size_t getDashPatternHash(const std::vector<float>& dasharray, const LinePattern
2018
return key;
2119
}
2220

21+
std::vector<DashRange> getDashRanges(const std::vector<float>& dasharray, float stretch) {
22+
// If dasharray has an odd length, both the first and last parts
23+
// are dashes and should be joined seamlessly.
24+
const bool oddDashArray = dasharray.size() % 2 == 1;
25+
26+
float left = oddDashArray ? -dasharray.back() * stretch : 0.0f;
27+
float right = dasharray.front() * stretch;
28+
bool isDash = true;
29+
30+
std::vector<DashRange> ranges;
31+
ranges.push_back({left, right, isDash, dasharray.front() == 0.0f});
32+
33+
float currentDashLength = dasharray.front();
34+
for (size_t i = 1; i < dasharray.size(); ++i) {
35+
isDash = !isDash;
36+
37+
const float dashLength = dasharray[i];
38+
left = currentDashLength * stretch;
39+
currentDashLength += dashLength;
40+
right = currentDashLength * stretch;
41+
42+
ranges.push_back({left, right, isDash, dashLength == 0.0f});
43+
}
44+
45+
return ranges;
46+
}
47+
48+
void addRoundDash(const std::vector<DashRange>& ranges, const int32_t yOffset, float stretch, const int n, AlphaImage& image) {
49+
const float halfStretch = stretch * 0.5f;
50+
51+
for (int y = -n; y <= n; y++) {
52+
int row = yOffset + n + y;
53+
int index = image.size.width * row;
54+
int currIndex = 0;
55+
DashRange range = ranges[currIndex];
56+
57+
for (uint32_t x = 0; x < image.size.width; x++) {
58+
if (x / range.right > 1.0f) { range = ranges[++currIndex]; }
59+
60+
float distLeft = std::fabsf(x - range.left);
61+
float distRight = std::fabsf(x - range.right);
62+
float minDist = std::fminf(distLeft, distRight);
63+
float signedDistance;
64+
65+
float distMiddle = static_cast<float>(y) / n * (halfStretch + 1.0f);
66+
if (range.isDash) {
67+
float distEdge = halfStretch - std::fabsf(distMiddle);
68+
signedDistance = std::sqrtf(minDist * minDist + distEdge * distEdge);
69+
} else {
70+
signedDistance = halfStretch - std::sqrtf(minDist * minDist + distMiddle * distMiddle);
71+
}
72+
73+
image.data[index + x] = static_cast<uint8_t>(std::fmaxf(0.0f, std::fminf(255.0f, signedDistance + 128.0f)));
74+
}
75+
}
76+
}
77+
78+
void addRegularDash(std::vector<DashRange>& ranges, const int32_t yOffset, AlphaImage& image) {
79+
// Collapse any zero-length range
80+
for (auto it = ranges.begin(); it != ranges.end();) {
81+
if (it->isZeroLength) {
82+
ranges.erase(it);
83+
} else { ++it; }
84+
}
85+
86+
if (ranges.size() >= 2) {
87+
// Collapse neighbouring same-type parts into a single part
88+
for (auto curr = ranges.begin(), next = ranges.begin() + 1; curr != ranges.end() && next >= ranges.end();) {
89+
if (next->isDash == curr->isDash) {
90+
next->left = curr->left;
91+
ranges.erase(curr);
92+
} else {
93+
++curr;
94+
++next;
95+
}
96+
}
97+
}
98+
99+
DashRange& first = ranges.front();
100+
DashRange& last = ranges.back();
101+
if (first.isDash == last.isDash) {
102+
first.left = last.left - image.size.width;
103+
last.right = first.right + image.size.width;
104+
}
105+
106+
int index = image.size.width * yOffset;
107+
int currIndex = 0;
108+
DashRange range = ranges[currIndex];
109+
110+
for (uint32_t x = 0; x < image.size.width; ++x) {
111+
if (x / range.right > 1.0f) { range = ranges[++currIndex]; }
112+
113+
float distLeft = std::fabsf(x - range.left);
114+
float distRight = std::fabsf(x - range.right);
115+
float minDist = std::fminf(distLeft, distRight);
116+
float signedDistance = range.isDash ? minDist : -minDist;
117+
118+
image.data[index + x] = static_cast<uint8_t>(std::fmaxf(0.0f, std::fminf(255.0f, signedDistance + 128.0f)));
119+
}
120+
}
121+
23122
LinePatternPos addDashPattern(AlphaImage& image,
24123
const int32_t yOffset,
25124
const std::vector<float>& dasharray,
26125
const LinePatternCap patternCap) {
27126
const uint8_t n = patternCap == LinePatternCap::Round ? 7 : 0;
28-
constexpr const uint8_t offset = 128;
29127

30128
if (dasharray.size() < 2) {
31129
Log::Warning(Event::ParseStyle, "line dasharray requires at least two elements");
@@ -38,64 +136,17 @@ LinePatternPos addDashPattern(AlphaImage& image,
38136
}
39137

40138
float stretch = image.size.width / length;
41-
float halfWidth = stretch * 0.5;
42-
// If dasharray has an odd length, both the first and last parts
43-
// are dashes and should be joined seamlessly.
44-
bool oddLength = dasharray.size() % 2 == 1;
139+
std::vector<DashRange> ranges = getDashRanges(dasharray, stretch);
45140

46-
for (int y = -n; y <= n; y++) {
47-
int row = yOffset + n + y;
48-
int index = image.size.width * row;
49-
50-
float left = 0;
51-
float right = dasharray[0];
52-
unsigned int partIndex = 1;
53-
54-
if (oddLength) {
55-
left -= dasharray.back();
56-
}
57-
58-
for (uint32_t x = 0; x < image.size.width; x++) {
59-
while (right < x / stretch) {
60-
left = right;
61-
if (partIndex >= dasharray.size()) {
62-
return LinePatternPos();
63-
}
64-
right = right + dasharray[partIndex];
65-
66-
if (oddLength && partIndex == dasharray.size() - 1) {
67-
right += dasharray.front();
68-
}
69-
70-
partIndex++;
71-
}
72-
73-
float distLeft = fabs(x - left * stretch);
74-
float distRight = fabs(x - right * stretch);
75-
float dist = fmin(distLeft, distRight);
76-
bool inside = (partIndex % 2) == 1;
77-
int signedDistance;
78-
79-
if (patternCap == LinePatternCap::Round) {
80-
float distMiddle = n ? (float)y / n * (halfWidth + 1) : 0;
81-
if (inside) {
82-
float distEdge = halfWidth - fabs(distMiddle);
83-
signedDistance = sqrt(dist * dist + distEdge * distEdge);
84-
} else {
85-
signedDistance = halfWidth - sqrt(dist * dist + distMiddle * distMiddle);
86-
}
87-
88-
} else {
89-
signedDistance = int((inside ? 1 : -1) * dist);
90-
}
91-
92-
image.data[index + x] = fmax(0, fmin(255, signedDistance + offset));
93-
}
141+
if (patternCap == LinePatternCap::Round) {
142+
addRoundDash(ranges, yOffset, stretch, n, image);
143+
} else {
144+
addRegularDash(ranges, yOffset, image);
94145
}
95146

96147
LinePatternPos position;
97-
position.y = (0.5 + yOffset + n) / image.size.height;
98-
position.height = (2.0 * n + 1) / image.size.height;
148+
position.y = (0.5f + yOffset + n) / image.size.height;
149+
position.height = (2.0f * n + 1) / image.size.height;
99150
position.width = length;
100151

101152
return position;

src/mbgl/geometry/line_atlas.hpp

+7
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ enum class LinePatternCap : bool {
2727
Round = true,
2828
};
2929

30+
struct DashRange {
31+
float left;
32+
float right;
33+
bool isDash;
34+
bool isZeroLength;
35+
};
36+
3037
class DashPatternTexture {
3138
public:
3239
DashPatternTexture(const std::vector<float>& from, const std::vector<float>& to, LinePatternCap);

0 commit comments

Comments
 (0)