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

Commit

Permalink
Port line-dasharray fix from gl-js
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
karimnaaji committed Feb 12, 2020
1 parent d3535f1 commit 14ab34f
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 57 deletions.
165 changes: 108 additions & 57 deletions src/mbgl/geometry/line_atlas.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
#include <mbgl/util/logging.hpp>
#include <mbgl/util/platform.hpp>

#include <cmath>

namespace mbgl {
namespace {

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

std::vector<DashRange> getDashRanges(const std::vector<float>& dasharray, float stretch) {
// If dasharray has an odd length, both the first and last parts
// are dashes and should be joined seamlessly.
const bool oddDashArray = dasharray.size() % 2 == 1;

float left = oddDashArray ? -dasharray.back() * stretch : 0.0f;
float right = dasharray.front() * stretch;
bool isDash = true;

std::vector<DashRange> ranges;
ranges.push_back({left, right, isDash, dasharray.front() == 0.0f});

float currentDashLength = dasharray.front();
for (size_t i = 1; i < dasharray.size(); ++i) {
isDash = !isDash;

const float dashLength = dasharray[i];
left = currentDashLength * stretch;
currentDashLength += dashLength;
right = currentDashLength * stretch;

ranges.push_back({left, right, isDash, dashLength == 0.0f});
}

return ranges;
}

void addRoundDash(const std::vector<DashRange>& ranges, const int32_t yOffset, float stretch, const int n, AlphaImage& image) {
const float halfStretch = stretch * 0.5f;

for (int y = -n; y <= n; y++) {
int row = yOffset + n + y;
int index = image.size.width * row;
int currIndex = 0;
DashRange range = ranges[currIndex];

for (uint32_t x = 0; x < image.size.width; x++) {
if (x / range.right > 1.0f) { range = ranges[++currIndex]; }

float distLeft = std::fabsf(x - range.left);
float distRight = std::fabsf(x - range.right);
float minDist = std::fminf(distLeft, distRight);
float signedDistance;

float distMiddle = static_cast<float>(y) / n * (halfStretch + 1.0f);
if (range.isDash) {
float distEdge = halfStretch - std::fabsf(distMiddle);
signedDistance = std::sqrtf(minDist * minDist + distEdge * distEdge);
} else {
signedDistance = halfStretch - std::sqrtf(minDist * minDist + distMiddle * distMiddle);
}

image.data[index + x] = static_cast<uint8_t>(std::fmaxf(0.0f, std::fminf(255.0f, signedDistance + 128.0f)));
}
}
}

void addRegularDash(std::vector<DashRange>& ranges, const int32_t yOffset, AlphaImage& image) {
// Collapse any zero-length range
for (auto it = ranges.begin(); it != ranges.end();) {
if (it->isZeroLength) {
ranges.erase(it);
} else { ++it; }
}

if (ranges.size() >= 2) {
// Collapse neighbouring same-type parts into a single part
for (auto curr = ranges.begin(), next = ranges.begin() + 1; curr != ranges.end() && next != ranges.end();) {
if (next->isDash == curr->isDash) {
next->left = curr->left;
ranges.erase(curr);
} else {
++curr;
++next;
}
}
}

DashRange& first = ranges.front();
DashRange& last = ranges.back();
if (first.isDash == last.isDash) {
first.left = last.left - image.size.width;
last.right = first.right + image.size.width;
}

int index = image.size.width * yOffset;
int currIndex = 0;
DashRange range = ranges[currIndex];

for (uint32_t x = 0; x < image.size.width; ++x) {
if (x / range.right > 1.0f) { range = ranges[++currIndex]; }

float distLeft = std::fabsf(x - range.left);
float distRight = std::fabsf(x - range.right);
float minDist = std::fminf(distLeft, distRight);
float signedDistance = range.isDash ? minDist : -minDist;

image.data[index + x] = static_cast<uint8_t>(std::fmaxf(0.0f, std::fminf(255.0f, signedDistance + 128.0f)));
}
}

LinePatternPos addDashPattern(AlphaImage& image,
const int32_t yOffset,
const std::vector<float>& dasharray,
const LinePatternCap patternCap) {
const uint8_t n = patternCap == LinePatternCap::Round ? 7 : 0;
constexpr const uint8_t offset = 128;

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

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

for (int y = -n; y <= n; y++) {
int row = yOffset + n + y;
int index = image.size.width * row;

float left = 0;
float right = dasharray[0];
unsigned int partIndex = 1;

if (oddLength) {
left -= dasharray.back();
}

for (uint32_t x = 0; x < image.size.width; x++) {
while (right < x / stretch) {
left = right;
if (partIndex >= dasharray.size()) {
return LinePatternPos();
}
right = right + dasharray[partIndex];

if (oddLength && partIndex == dasharray.size() - 1) {
right += dasharray.front();
}

partIndex++;
}

float distLeft = fabs(x - left * stretch);
float distRight = fabs(x - right * stretch);
float dist = fmin(distLeft, distRight);
bool inside = (partIndex % 2) == 1;
int signedDistance;

if (patternCap == LinePatternCap::Round) {
float distMiddle = n ? (float)y / n * (halfWidth + 1) : 0;
if (inside) {
float distEdge = halfWidth - fabs(distMiddle);
signedDistance = sqrt(dist * dist + distEdge * distEdge);
} else {
signedDistance = halfWidth - sqrt(dist * dist + distMiddle * distMiddle);
}

} else {
signedDistance = int((inside ? 1 : -1) * dist);
}

image.data[index + x] = fmax(0, fmin(255, signedDistance + offset));
}
if (patternCap == LinePatternCap::Round) {
addRoundDash(ranges, yOffset, stretch, n, image);
} else {
addRegularDash(ranges, yOffset, image);
}

LinePatternPos position;
position.y = (0.5 + yOffset + n) / image.size.height;
position.height = (2.0 * n + 1) / image.size.height;
position.y = (0.5f + yOffset + n) / image.size.height;
position.height = (2.0f * n + 1) / image.size.height;
position.width = length;

return position;
Expand Down
7 changes: 7 additions & 0 deletions src/mbgl/geometry/line_atlas.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ enum class LinePatternCap : bool {
Round = true,
};

struct DashRange {
float left;
float right;
bool isDash;
bool isZeroLength;
};

class DashPatternTexture {
public:
DashPatternTexture(const std::vector<float>& from, const std::vector<float>& to, LinePatternCap);
Expand Down

0 comments on commit 14ab34f

Please sign in to comment.