diff --git a/modules/layers/src/path-layer/path-layer-vertex-64.glsl.js b/modules/layers/src/path-layer/path-layer-vertex-64.glsl.js index 25fdd2731c0..46faa952b10 100644 --- a/modules/layers/src/path-layer/path-layer-vertex-64.glsl.js +++ b/modules/layers/src/path-layer/path-layer-vertex-64.glsl.js @@ -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; @@ -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]; diff --git a/modules/layers/src/path-layer/path-layer-vertex.glsl.js b/modules/layers/src/path-layer/path-layer-vertex.glsl.js index c04ddbfec0b..27b8ae2ca70 100644 --- a/modules/layers/src/path-layer/path-layer-vertex.glsl.js +++ b/modules/layers/src/path-layer/path-layer-vertex.glsl.js @@ -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; @@ -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 diff --git a/modules/layers/src/path-layer/path-layer.js b/modules/layers/src/path-layer/path-layer.js index 82e0294c2ba..e6d35b2185c 100644 --- a/modules/layers/src/path-layer/path-layer.js +++ b/modules/layers/src/path-layer/path-layer.js @@ -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', @@ -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) { diff --git a/modules/layers/src/path-layer/path-tesselator.js b/modules/layers/src/path-layer/path-tesselator.js index 834fd34969d..6f63026ca64 100644 --- a/modules/layers/src/path-layer/path-tesselator.js +++ b/modules/layers/src/path-layer/path-tesselator.js @@ -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} } }); } @@ -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; @@ -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; diff --git a/test/modules/layers/path-tesselator.spec.js b/test/modules/layers/path-tesselator.spec.js index 672d97f4f0b..c64a178f82f 100644 --- a/test/modules/layers/path-tesselator.spec.js +++ b/test/modules/layers/path-tesselator.spec.js @@ -103,25 +103,31 @@ 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) { @@ -129,6 +135,10 @@ test('PathTesselator#constructor', t => { ArrayBuffer.isView(tesselator.get('startEndPositions64XyLow')), 'PathTesselator.get startEndPositions64XyLow' ); + t.ok( + ArrayBuffer.isView(tesselator.get('neighborPositions64XyLow')), + 'PathTesselator.get neighborPositions64XyLow' + ); } }); }); @@ -153,17 +163,17 @@ 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, @@ -171,9 +181,9 @@ test('PathTesselator#partial update', t => { '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'); @@ -181,7 +191,7 @@ test('PathTesselator#partial update', t => { 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, @@ -189,9 +199,9 @@ test('PathTesselator#partial update', t => { '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'); diff --git a/test/render/golden-images/path-billboard.png b/test/render/golden-images/path-billboard.png new file mode 100644 index 00000000000..1393fbadf10 Binary files /dev/null and b/test/render/golden-images/path-billboard.png differ diff --git a/test/render/test-cases.js b/test/render/test-cases.js index 25ca966777c..81e96a25dca 100644 --- a/test/render/test-cases.js +++ b/test/render/test-cases.js @@ -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: {