Skip to content

Commit

Permalink
Improve PathLayer precision (#3141)
Browse files Browse the repository at this point in the history
  • Loading branch information
Pessimistress committed May 28, 2019
1 parent 55abc32 commit a0f12e5
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 63 deletions.
15 changes: 9 additions & 6 deletions modules/layers/src/path-layer/path-layer-vertex-64.glsl.js
Expand Up @@ -27,8 +27,9 @@ attribute vec3 positions;
attribute vec3 instanceStartPositions;
attribute vec3 instanceEndPositions;
attribute vec4 instanceStartEndPositions64xyLow;
attribute vec3 instanceLeftDeltas;
attribute vec3 instanceRightDeltas;
attribute vec3 instanceLeftPositions;
attribute vec3 instanceRightPositions;
attribute vec4 instanceNeighborPositions64xyLow;
attribute float instanceStrokeWidths;
attribute vec4 instanceColors;
attribute vec3 instancePickingColors;
Expand Down Expand Up @@ -204,20 +205,22 @@ void main() {
// Calculate previous position
vec3 prevPosition = mix(-instanceLeftDeltas, vec3(0.0), isEnd) + instanceStartPositions;
vec3 prevPosition = mix(instanceLeftPositions, instanceStartPositions, isEnd);
vec2 prevPosition64xyLow = mix(instanceNeighborPositions64xyLow.xy, instanceStartEndPositions64xyLow.xy, isEnd);
// Calculate prev position 64bit
vec2 projected_prev_position[2];
project_position_fp64(prevPosition.xy, instanceStartEndPositions64xyLow.xy, projected_prev_position);
project_position_fp64(prevPosition.xy, prevPosition64xyLow, projected_prev_position);
// Calculate next positions
vec3 nextPosition = mix(vec3(0.0), instanceRightDeltas, isEnd) + instanceEndPositions;
vec3 nextPosition = mix(instanceEndPositions, instanceRightPositions, isEnd);
vec2 nextPosition64xyLow = mix(instanceStartEndPositions64xyLow.zw, instanceNeighborPositions64xyLow.zw, isEnd);
// Calculate next position 64bit
vec2 projected_next_position[2];
project_position_fp64(nextPosition.xy, instanceStartEndPositions64xyLow.zw, projected_next_position);
project_position_fp64(nextPosition.xy, nextPosition64xyLow, projected_next_position);
vec3 pos = lineJoin(projected_prev_position, projected_curr_position, projected_next_position);
vec2 vertex_pos_modelspace[4];
Expand Down
25 changes: 7 additions & 18 deletions modules/layers/src/path-layer/path-layer-vertex.glsl.js
Expand Up @@ -26,8 +26,9 @@ attribute vec3 positions;
attribute vec3 instanceStartPositions;
attribute vec3 instanceEndPositions;
attribute vec4 instanceStartEndPositions64xyLow;
attribute vec3 instanceLeftDeltas;
attribute vec3 instanceRightDeltas;
attribute vec3 instanceLeftPositions;
attribute vec3 instanceRightPositions;
attribute vec4 instanceNeighborPositions64xyLow;
attribute float instanceStrokeWidths;
attribute vec4 instanceColors;
attribute vec3 instancePickingColors;
Expand Down Expand Up @@ -213,26 +214,14 @@ void main() {
float isEnd = positions.x;
vec3 prevPosition = instanceStartPositions;
vec2 prevPosition64xyLow = instanceStartEndPositions64xyLow.xy;
if (project_uCoordinateSystem == COORDINATE_SYSTEM_LNGLAT_AUTO_OFFSET) {
// In auto offset mode, add delta to low part of the positions for better precision
prevPosition64xyLow += mix(-instanceLeftDeltas, vec3(0.0), isEnd).xy;
} else {
prevPosition += mix(-instanceLeftDeltas, vec3(0.0), isEnd);
}
vec3 prevPosition = mix(instanceLeftPositions, instanceStartPositions, isEnd);
vec2 prevPosition64xyLow = mix(instanceNeighborPositions64xyLow.xy, instanceStartEndPositions64xyLow.xy, isEnd);
vec3 currPosition = mix(instanceStartPositions, instanceEndPositions, isEnd);
vec2 currPosition64xyLow = mix(instanceStartEndPositions64xyLow.xy, instanceStartEndPositions64xyLow.zw, isEnd);
vec3 nextPosition = instanceEndPositions;
vec2 nextPosition64xyLow = instanceStartEndPositions64xyLow.zw;
if (project_uCoordinateSystem == COORDINATE_SYSTEM_LNGLAT_AUTO_OFFSET) {
// In auto offset mode, add delta to low part of the positions for better precision
nextPosition64xyLow += mix(vec3(0.0), instanceRightDeltas, isEnd).xy;
} else {
nextPosition += mix(vec3(0.0), instanceRightDeltas, isEnd);
}
vec3 nextPosition = mix(instanceEndPositions, instanceRightPositions, isEnd);
vec2 nextPosition64xyLow = mix(instanceStartEndPositions64xyLow.zw, instanceNeighborPositions64xyLow.zw, isEnd);
if (billboard) {
// Extrude in clipspace
Expand Down
40 changes: 34 additions & 6 deletions modules/layers/src/path-layer/path-layer.js
Expand Up @@ -84,8 +84,25 @@ export default class PathLayer extends Layer {
update: this.calculateInstanceStartEndPositions64xyLow,
noAlloc
},
instanceLeftDeltas: {size: 3, update: this.calculateLeftDeltas, noAlloc},
instanceRightDeltas: {size: 3, update: this.calculateRightDeltas, noAlloc},
instanceLeftPositions: {
size: 3,
transition: ATTRIBUTE_TRANSITION,
accessor: 'getPath',
update: this.calculateLeftPositions,
noAlloc
},
instanceRightPositions: {
size: 3,
transition: ATTRIBUTE_TRANSITION,
accessor: 'getPath',
update: this.calculateRightPositions,
noAlloc
},
instanceNeighborPositions64xyLow: {
size: 4,
update: this.calculateInstanceNeighborPositions64xyLow,
noAlloc
},
instanceStrokeWidths: {
size: 1,
accessor: 'getWidth',
Expand Down Expand Up @@ -285,14 +302,25 @@ export default class PathLayer extends Layer {
}
}

calculateLeftDeltas(attribute) {
calculateLeftPositions(attribute) {
const {pathTesselator} = this.state;
attribute.value = pathTesselator.get('leftDeltas');
attribute.value = pathTesselator.get('leftPositions');
}

calculateRightDeltas(attribute) {
calculateRightPositions(attribute) {
const {pathTesselator} = this.state;
attribute.value = pathTesselator.get('rightDeltas');
attribute.value = pathTesselator.get('rightPositions');
}

calculateInstanceNeighborPositions64xyLow(attribute) {
const isFP64 = this.use64bitPositions();
attribute.constant = !isFP64;

if (isFP64) {
attribute.value = this.state.pathTesselator.get('neighborPositions64XyLow');
} else {
attribute.value = new Float32Array(4);
}
}

clearPickingColor(color) {
Expand Down
33 changes: 23 additions & 10 deletions modules/layers/src/path-layer/path-tesselator.js
Expand Up @@ -34,9 +34,10 @@ export default class PathTesselator extends Tesselator {
attributes: {
startPositions: {size: 3},
endPositions: {size: 3},
leftDeltas: {size: 3},
rightDeltas: {size: 3},
startEndPositions64XyLow: {size: 4, fp64Only: true}
leftPositions: {size: 3},
rightPositions: {size: 3},
startEndPositions64XyLow: {size: 4, fp64Only: true},
neighborPositions64XyLow: {size: 4, fp64Only: true}
}
});
}
Expand All @@ -54,7 +55,14 @@ export default class PathTesselator extends Tesselator {
/* eslint-disable max-statements, complexity */
updateGeometryAttributes(path, context) {
const {
attributes: {startPositions, endPositions, leftDeltas, rightDeltas, startEndPositions64XyLow},
attributes: {
startPositions,
endPositions,
leftPositions,
rightPositions,
startEndPositions64XyLow,
neighborPositions64XyLow
},
fp64
} = this;

Expand Down Expand Up @@ -85,19 +93,24 @@ export default class PathTesselator extends Tesselator {
endPositions[i * 3 + 1] = endPoint[1];
endPositions[i * 3 + 2] = endPoint[2] || 0;

leftDeltas[i * 3] = startPoint[0] - prevPoint[0];
leftDeltas[i * 3 + 1] = startPoint[1] - prevPoint[1];
leftDeltas[i * 3 + 2] = startPoint[2] - prevPoint[2] || 0;
leftPositions[i * 3] = prevPoint[0];
leftPositions[i * 3 + 1] = prevPoint[1];
leftPositions[i * 3 + 2] = prevPoint[2] || 0;

rightDeltas[i * 3] = nextPoint[0] - endPoint[0];
rightDeltas[i * 3 + 1] = nextPoint[1] - endPoint[1];
rightDeltas[i * 3 + 2] = nextPoint[2] - endPoint[2] || 0;
rightPositions[i * 3] = nextPoint[0];
rightPositions[i * 3 + 1] = nextPoint[1];
rightPositions[i * 3 + 2] = nextPoint[2] || 0;

if (fp64) {
startEndPositions64XyLow[i * 4] = fp64LowPart(startPoint[0]);
startEndPositions64XyLow[i * 4 + 1] = fp64LowPart(startPoint[1]);
startEndPositions64XyLow[i * 4 + 2] = fp64LowPart(endPoint[0]);
startEndPositions64XyLow[i * 4 + 3] = fp64LowPart(endPoint[1]);

neighborPositions64XyLow[i * 4] = fp64LowPart(prevPoint[0]);
neighborPositions64XyLow[i * 4 + 1] = fp64LowPart(prevPoint[1]);
neighborPositions64XyLow[i * 4 + 2] = fp64LowPart(nextPoint[0]);
neighborPositions64XyLow[i * 4 + 3] = fp64LowPart(nextPoint[1]);
}

prevPoint = startPoint;
Expand Down
56 changes: 33 additions & 23 deletions test/modules/layers/path-tesselator.spec.js
Expand Up @@ -103,32 +103,42 @@ test('PathTesselator#constructor', t => {
'endPositions is handling loop correctly'
);

t.ok(ArrayBuffer.isView(tesselator.get('leftDeltas')), 'PathTesselator.get leftDeltas');
t.ok(tesselator.get('leftDeltas').every(Number.isFinite), 'Valid leftDeltas attribute');
t.ok(ArrayBuffer.isView(tesselator.get('leftPositions')), 'PathTesselator.get leftPositions');
t.ok(tesselator.get('leftPositions').every(Number.isFinite), 'Valid leftPositions attribute');
t.deepEquals(
tesselator.get('leftDeltas').slice(0, 6),
[0, 0, 0, 1, 1, 0],
'leftDeltas are filled'
tesselator.get('leftPositions').slice(0, 6),
[1, 1, 0, 1, 1, 0],
'leftPositions are filled'
);

t.ok(ArrayBuffer.isView(tesselator.get('rightDeltas')), 'PathTesselator.get rightDeltas');
t.ok(
ArrayBuffer.isView(tesselator.get('rightPositions')),
'PathTesselator.get rightPositions'
);
t.deepEquals(
tesselator.get('rightDeltas').slice(0, 6),
[1, 1, 0, 0, 0, 0],
'rightDeltas are filled'
tesselator.get('rightPositions').slice(0, 6),
[3, 3, 0, 3, 3, 0],
'rightPositions are filled'
);
t.ok(
tesselator.get('rightPositions').every(Number.isFinite),
'Valid rightPositions attribute'
);
t.ok(tesselator.get('rightDeltas').every(Number.isFinite), 'Valid rightDeltas attribute');
t.deepEquals(
tesselator.get('rightDeltas').slice(-3),
[1, 1, 0],
'rightDeltas is handling loop correctly'
tesselator.get('rightPositions').slice(-3),
[2, 2, 0],
'rightPositions is handling loop correctly'
);

if (testCase.params.fp64) {
t.ok(
ArrayBuffer.isView(tesselator.get('startEndPositions64XyLow')),
'PathTesselator.get startEndPositions64XyLow'
);
t.ok(
ArrayBuffer.isView(tesselator.get('neighborPositions64XyLow')),
'PathTesselator.get neighborPositions64XyLow'
);
}
});
});
Expand All @@ -153,45 +163,45 @@ test('PathTesselator#partial update', t => {
});

let startPositions = tesselator.get('startPositions').slice(0, 15);
let leftDeltas = tesselator.get('leftDeltas').slice(0, 15);
let leftPositions = tesselator.get('leftPositions').slice(0, 15);
t.is(tesselator.instanceCount, 5, 'Initial instance count');
t.deepEquals(startPositions, [1, 1, 0, 2, 2, 0, 1, 1, 0, 2, 2, 0, 3, 3, 0], 'startPositions');
t.deepEquals(leftDeltas, [0, 0, 0, 1, 1, 0, -2, -2, 0, 1, 1, 0, 1, 1, 0], 'leftDeltas');
t.deepEquals(leftPositions, [1, 1, 0, 1, 1, 0, 3, 3, 0, 1, 1, 0, 2, 2, 0], 'leftPositions');
t.deepEquals(Array.from(accessorCalled), ['A', 'B'], 'Accessor called on all data');

sampleData[2] = {path: [[4, 4], [5, 5], [6, 6]], id: 'C'};
accessorCalled.clear();
tesselator.updatePartialGeometry({startRow: 2});
startPositions = tesselator.get('startPositions').slice(0, 21);
leftDeltas = tesselator.get('leftDeltas').slice(0, 21);
leftPositions = tesselator.get('leftPositions').slice(0, 21);
t.is(tesselator.instanceCount, 7, 'Updated instance count');
t.deepEquals(
startPositions,
[1, 1, 0, 2, 2, 0, 1, 1, 0, 2, 2, 0, 3, 3, 0, 4, 4, 0, 5, 5, 0],
'startPositions'
);
t.deepEquals(
leftDeltas,
[0, 0, 0, 1, 1, 0, -2, -2, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0],
'leftDeltas'
leftPositions,
[1, 1, 0, 1, 1, 0, 3, 3, 0, 1, 1, 0, 2, 2, 0, 4, 4, 0, 4, 4, 0],
'leftPositions'
);
t.deepEquals(Array.from(accessorCalled), ['C'], 'Accessor called only on partial data');

sampleData[0] = {path: [[6, 6], [5, 5], [4, 4]], id: 'A'};
accessorCalled.clear();
tesselator.updatePartialGeometry({startRow: 0, endRow: 1});
startPositions = tesselator.get('startPositions').slice(0, 21);
leftDeltas = tesselator.get('leftDeltas').slice(0, 21);
leftPositions = tesselator.get('leftPositions').slice(0, 21);
t.is(tesselator.instanceCount, 7, 'Updated instance count');
t.deepEquals(
startPositions,
[6, 6, 0, 5, 5, 0, 1, 1, 0, 2, 2, 0, 3, 3, 0, 4, 4, 0, 5, 5, 0],
'startPositions'
);
t.deepEquals(
leftDeltas,
[0, 0, 0, -1, -1, 0, -2, -2, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0],
'leftDeltas'
leftPositions,
[6, 6, 0, 6, 6, 0, 3, 3, 0, 1, 1, 0, 2, 2, 0, 4, 4, 0, 4, 4, 0],
'leftPositions'
);
t.deepEquals(Array.from(accessorCalled), ['A'], 'Accessor called only on partial data');

Expand Down
Binary file added test/render/golden-images/path-billboard.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 22 additions & 0 deletions test/render/test-cases.js
Expand Up @@ -359,6 +359,28 @@ export const TEST_CASES = [
],
goldenImage: './test/render/golden-images/path-lnglat.png'
},
{
name: 'path-billboard',
viewState: {
latitude: 37.7518488,
longitude: -122.427699,
zoom: 16.5,
pitch: 55,
bearing: -20
},
layers: [
new PathLayer({
id: 'path-lnglat',
data: dataSamples.zigzag3D,
opacity: 0.6,
billboard: true,
getPath: f => f.path,
getColor: f => [128, 0, 0],
getWidth: f => 10
})
],
goldenImage: './test/render/golden-images/path-billboard.png'
},
{
name: 'scatterplot-lnglat',
viewState: {
Expand Down

0 comments on commit a0f12e5

Please sign in to comment.