Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix visual artifact for line-dasharray #9246

Merged
merged 2 commits into from
Feb 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 107 additions & 53 deletions src/render/line_atlas.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class LineAtlas {
nextRow: number;
bytes: number;
data: Uint8Array;
positions: {[string]: any};
dashEntry: {[string]: any};
dirty: boolean;
texture: WebGLTexture;

Expand All @@ -30,7 +30,7 @@ class LineAtlas {

this.data = new Uint8Array(this.width * this.height);

this.positions = {};
this.dashEntry = {};
}

/**
Expand All @@ -44,80 +44,134 @@ class LineAtlas {
getDash(dasharray: Array<number>, round: boolean) {
const key = dasharray.join(",") + String(round);

if (!this.positions[key]) {
this.positions[key] = this.addDash(dasharray, round);
if (!this.dashEntry[key]) {
this.dashEntry[key] = this.addDash(dasharray, round);
}
return this.positions[key];
return this.dashEntry[key];
}

addDash(dasharray: Array<number>, round: boolean) {
getDashRanges(dasharray: Array<number>, lineAtlasWidth: number, stretch: number) {
// If dasharray has an odd length, both the first and last parts
// are dashes and should be joined seamlessly.
const oddDashArray = dasharray.length % 2 === 1;

const n = round ? 7 : 0;
const height = 2 * n + 1;
const offset = 128;
const ranges = [];

if (this.nextRow + height > this.height) {
warnOnce('LineAtlas out of space');
return null;
}
let left = oddDashArray ? -dasharray[dasharray.length - 1] * stretch : 0;
let right = dasharray[0] * stretch;
let isDash = true;

let length = 0;
for (let i = 0; i < dasharray.length; i++) {
length += dasharray[i];
ranges.push({left, right, isDash, zeroLength: dasharray[0] === 0});

let currentDashLength = dasharray[0];
for (let i = 1; i < dasharray.length; i++) {
isDash = !isDash;

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

ranges.push({left, right, isDash, zeroLength: dashLength === 0});
}

const stretch = this.width / length;
const halfWidth = stretch / 2;
return ranges;
}

// If dasharray has an odd length, both the first and last parts
// are dashes and should be joined seamlessly.
const oddLength = dasharray.length % 2 === 1;
addRoundDash(ranges: Object, stretch: number, n: number) {
const halfStretch = stretch / 2;

for (let y = -n; y <= n; y++) {
const row = this.nextRow + n + y;
const index = this.width * row;

let left = oddLength ? -dasharray[dasharray.length - 1] : 0;
let right = dasharray[0];
let partIndex = 1;
let currIndex = 0;
let range = ranges[currIndex];

for (let x = 0; x < this.width; x++) {
if (x / range.right > 1) { range = ranges[++currIndex]; }

while (right < x / stretch) {
left = right;
right = right + dasharray[partIndex];

if (oddLength && partIndex === dasharray.length - 1) {
right += dasharray[0];
}

partIndex++;
}

const distLeft = Math.abs(x - left * stretch);
const distRight = Math.abs(x - right * stretch);
const dist = Math.min(distLeft, distRight);
const inside = (partIndex % 2) === 1;
const distLeft = Math.abs(x - range.left);
const distRight = Math.abs(x - range.right);
const minDist = Math.min(distLeft, distRight);
let signedDistance;

if (round) {
// Add circle caps
const distMiddle = n ? y / n * (halfWidth + 1) : 0;
if (inside) {
const distEdge = halfWidth - Math.abs(distMiddle);
signedDistance = Math.sqrt(dist * dist + distEdge * distEdge);
} else {
signedDistance = halfWidth - Math.sqrt(dist * dist + distMiddle * distMiddle);
}
const distMiddle = y / n * (halfStretch + 1);
if (range.isDash) {
const distEdge = halfStretch - Math.abs(distMiddle);
signedDistance = Math.sqrt(minDist * minDist + distEdge * distEdge);
} else {
signedDistance = (inside ? 1 : -1) * dist;
signedDistance = halfStretch - Math.sqrt(minDist * minDist + distMiddle * distMiddle);
}

this.data[index + x] = Math.max(0, Math.min(255, signedDistance + offset));
this.data[index + x] = Math.max(0, Math.min(255, signedDistance + 128));
}
}
}

addRegularDash(ranges: Object) {

// Collapse any zero-length range
// Collapse neighbouring same-type parts into a single part
for (let i = ranges.length - 1; i >= 0; --i) {
const part = ranges[i];
const next = ranges[i + 1];
if (part.zeroLength) {
ranges.splice(i, 1);
} else if (next && next.isDash === part.isDash) {
next.left = part.left;
ranges.splice(i, 1);
}
}

const pos = {
// Combine the first and last parts if possible
const first = ranges[0];
const last = ranges[ranges.length - 1];
if (first.isDash === last.isDash) {
first.left = last.left - this.width;
last.right = first.right + this.width;
}

const index = this.width * this.nextRow;
let currIndex = 0;
let range = ranges[currIndex];

for (let x = 0; x < this.width; x++) {
if (x / range.right > 1) {
range = ranges[++currIndex];
}

const distLeft = Math.abs(x - range.left);
const distRight = Math.abs(x - range.right);

const minDist = Math.min(distLeft, distRight);
const signedDistance = range.isDash ? minDist : -minDist;

this.data[index + x] = Math.max(0, Math.min(255, signedDistance + 128));
}
}

addDash(dasharray: Array<number>, round: boolean) {
const n = round ? 7 : 0;
const height = 2 * n + 1;

if (this.nextRow + height > this.height) {
warnOnce('LineAtlas out of space');
return null;
}

let length = 0;
for (let i = 0; i < dasharray.length; i++) { length += dasharray[i]; }

const stretch = this.width / length;
const ranges = this.getDashRanges(dasharray, this.width, stretch);

if (round) {
this.addRoundDash(ranges, stretch, n);
} else {
this.addRegularDash(ranges);
}

const dashEntry = {
y: (this.nextRow + n + 0.5) / this.height,
height: 2 * n / this.height,
width: length
Expand All @@ -126,7 +180,7 @@ class LineAtlas {
this.nextRow += height;
this.dirty = true;

return pos;
return dashEntry;
}

bind(context: Context) {
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
{
"version": 8,
"metadata": {
"test": {
"width": 128,
"height": 132
}
},
"zoom": 0,
"sources": {
"geojson": {
"type": "geojson",
"data": {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"width": 2,
"offset": 0
},
"geometry": {
"type": "LineString",
"coordinates": [ [ -40, -18 ], [ 40, -18 ] ]
}
},
{
"type": "Feature",
"properties": {
"width": 4,
"offset": 0
},
"geometry": {
"type": "LineString",
"coordinates": [ [ -40, -10 ], [ 40, -10 ] ]
}
},
{
"type": "Feature",
"properties": {
"width": 6,
"offset": 0
},
"geometry": {
"type": "LineString",
"coordinates": [ [ -40, -2 ], [ 40, -2 ] ]
}
}
]
}
}
},
"layers": [
{
"id": "road0",
"type": "line",
"source": "geojson",
"paint": {
"line-width": ["get", "width"],
"line-offset": ["+", ["get", "offset"], 32],
"line-dasharray": [1, 0, 1, 1],
"line-color": "red"
}
},
{
"id": "road1",
"type": "line",
"source": "geojson",
"paint": {
"line-width": ["get", "width"],
"line-offset": ["-", ["get", "offset"], 32],
"line-dasharray": [1, 1, 0],
"line-color": "green"
}
},
{
"id": "road2",
"type": "line",
"source": "geojson",
"paint": {
"line-width": ["get", "width"],
"line-offset": ["-", ["get", "offset"], 64],
"line-dasharray": [1, 0, 1],
"line-color": "blue"
}
},
{
"id": "road3",
"type": "line",
"source": "geojson",
"paint": {
"line-width": ["get", "width"],
"line-dasharray": [1, 0],
"line-color": "yellow"
}
},
{
"id": "road4",
"type": "line",
"source": "geojson",
"paint": {
"line-width": ["get", "width"],
"line-dasharray": [0, 1],
"line-color": "black"
}
}
]
}