Skip to content

Commit

Permalink
Backport icon-image-cross-fade property and icon variant support (#505)
Browse files Browse the repository at this point in the history
* Add icon-image-cross-fade into style spec

* Parse secondary image variant name in image expressions

* Load and render secondary image variants

* Update test configuration

* Update examples

* Name primary image variant explicitly
  • Loading branch information
endanke authored Mar 28, 2023
1 parent aa8c806 commit 6a7126f
Show file tree
Hide file tree
Showing 30 changed files with 668 additions and 278 deletions.
2 changes: 2 additions & 0 deletions build/generate-struct-arrays.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ import {
symbolGlobeExtAttributes,
dynamicLayoutAttributes,
placementOpacityAttributes,
iconTransitioningAttributes,
collisionBox,
collisionBoxLayout,
collisionCircleLayout,
Expand All @@ -174,6 +175,7 @@ createStructArrayType(`symbol_layout`, symbolLayoutAttributes);
createStructArrayType(`symbol_globe_ext`, symbolGlobeExtAttributes);
createStructArrayType(`symbol_dynamic_layout`, dynamicLayoutAttributes);
createStructArrayType(`symbol_opacity`, placementOpacityAttributes);
createStructArrayType(`symbol_icon_transitioning`, iconTransitioningAttributes);
createStructArrayType('collision_box', collisionBox, true);
createStructArrayType(`collision_box_layout`, collisionBoxLayout);
createStructArrayType(`collision_circle_layout`, collisionCircleLayout);
Expand Down
3 changes: 2 additions & 1 deletion debug/bucket-stats.html
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@
bucket.dynamicLayoutVertexArray.arrayBuffer.byteLength +
bucket.layoutVertexArray.arrayBuffer.byteLength +
bucket.opacityVertexArray.arrayBuffer.byteLength +
bucket.placedSymbolArray.arrayBuffer.byteLength;
bucket.placedSymbolArray.arrayBuffer.byteLength +
bucket.iconTransitioningVertexArray.arrayBuffer.byteLength;
}

const statsEl = document.getElementById('stats');
Expand Down
53 changes: 47 additions & 6 deletions debug/measure-light.html
Original file line number Diff line number Diff line change
Expand Up @@ -92,27 +92,27 @@
updateLights(value);
});
var directionalLightAltitude = lights.add(demo3d, 'directionalLightAltitude', 0.0, 90.0);
directionalLightAltitude.onFinishChange((value) => {
directionalLightAltitude.onChange((value) => {
updateLights(demo3d.enable3dLights);
});
var directionalLightAzimuth = lights.add(demo3d, 'directionalLightAzimuth', 0.0, 360.0);
directionalLightAzimuth.onFinishChange((value) => {
directionalLightAzimuth.onChange((value) => {
updateLights(demo3d.enable3dLights);
});
var directionalLightIntensity = lights.add(demo3d, 'directionalLightIntensity', 0.0, 1.0);
directionalLightIntensity.onFinishChange((value) => {
directionalLightIntensity.onChange((value) => {
updateLights(demo3d.enable3dLights);
});
var directionalLightColor = lights.addColor(demo3d, 'directionalLightColor');
directionalLightColor.onFinishChange((value) => {
directionalLightColor.onChange((value) => {
updateLights(demo3d.enable3dLights);
});
var ambientLightIntensity = lights.add(demo3d, 'ambientLightIntensity', 0.0, 1.0);
ambientLightIntensity.onFinishChange((value) => {
ambientLightIntensity.onChange((value) => {
updateLights(demo3d.enable3dLights);
});
var ambientLightColor = lights.addColor(demo3d, 'ambientLightColor');
ambientLightColor.onFinishChange((value) => {
ambientLightColor.onChange((value) => {
updateLights(demo3d.enable3dLights);
});

Expand Down Expand Up @@ -297,6 +297,47 @@
]
}
});
map.addLayer({
"id": "icon-variant-label-demo",
"layout": {
"icon-image": [
"case",
[">", ["get", "sizerank"], 14],
[
"image",
"rectangle-yellow-2",
"rectangle-red-2"
],
"rectangle-green-2"
],
"text-field": [
"get",
"sizerank"
],
"text-size": 8
},
"paint": {
"icon-image-cross-fade": [
"interpolate",
["linear"],
["measure-light", "brightness"],
0.45,
0.0,
0.5,
1.0
],
"text-color": [
"case",
["<",
["measure-light", "brightness"], 0.5],
"black",
"white"
]
},
"source": "composite",
"source-layer": "poi_label",
"type": "symbol"
});
});
});

Expand Down
32 changes: 32 additions & 0 deletions src/data/array_types.js
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,36 @@ class StructArrayLayout1ul4 extends StructArray {
StructArrayLayout1ul4.prototype.bytesPerElement = 4;
register(StructArrayLayout1ul4, 'StructArrayLayout1ul4');

/**
* Implementation of the StructArray layout:
* [0]: Uint8[2]
*
* @private
*/
class StructArrayLayout2ub2 extends StructArray {
uint8: Uint8Array;

_refreshViews() {
this.uint8 = new Uint8Array(this.arrayBuffer);
}

emplaceBack(v0: number, v1: number): number {
const i = this.length;
this.resize(i + 1);
return this.emplace(i, v0, v1);
}

emplace(i: number, v0: number, v1: number): number {
const o1 = i * 2;
this.uint8[o1 + 0] = v0;
this.uint8[o1 + 1] = v1;
return i;
}
}

StructArrayLayout2ub2.prototype.bytesPerElement = 2;
register(StructArrayLayout2ub2, 'StructArrayLayout2ub2');

/**
* Implementation of the StructArray layout:
* [0]: Int16[5]
Expand Down Expand Up @@ -1287,6 +1317,7 @@ export {
StructArrayLayout4i4ui4i24,
StructArrayLayout3i3f20,
StructArrayLayout1ul4,
StructArrayLayout2ub2,
StructArrayLayout5i4f1i1ul2ui40,
StructArrayLayout3i2i2i16,
StructArrayLayout2f1f2i16,
Expand Down Expand Up @@ -1318,6 +1349,7 @@ export {
StructArrayLayout3i3f20 as SymbolGlobeExtArray,
StructArrayLayout4f16 as SymbolDynamicLayoutArray,
StructArrayLayout1ul4 as SymbolOpacityArray,
StructArrayLayout2ub2 as SymbolIconTransitioningArray,
StructArrayLayout3i2i2i16 as CollisionBoxLayoutArray,
StructArrayLayout2f1f2i16 as CollisionCircleLayoutArray,
StructArrayLayout2ub2f12 as CollisionVertexArray,
Expand Down
4 changes: 4 additions & 0 deletions src/data/bucket/symbol_attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ export const placementOpacityAttributes: StructArrayLayout = createLayout([
{name: 'a_fade_opacity', components: 1, type: 'Uint32'}
], 4);

export const iconTransitioningAttributes: StructArrayLayout = createLayout([
{name: 'a_texb', components: 2, type: 'Uint8'}
]);

export const collisionVertexAttributes: StructArrayLayout = createLayout([
{name: 'a_placed', components: 2, type: 'Uint8'},
{name: 'a_shift', components: 2, type: 'Float32'},
Expand Down
55 changes: 43 additions & 12 deletions src/data/bucket/symbol_bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import {symbolLayoutAttributes,
collisionVertexAttributes,
collisionVertexAttributesExt,
collisionBoxLayout,
dynamicLayoutAttributes
dynamicLayoutAttributes,
iconTransitioningAttributes
} from './symbol_attributes.js';

import {SymbolLayoutArray,
Expand All @@ -18,7 +19,8 @@ import {SymbolLayoutArray,
PlacedSymbolArray,
SymbolInstanceArray,
GlyphOffsetArray,
SymbolLineVertexArray
SymbolLineVertexArray,
SymbolIconTransitioningArray
} from '../array_types.js';

import ONE_EM from '../../symbol/one_em.js';
Expand Down Expand Up @@ -155,6 +157,10 @@ function addVertex(array, tileAnchorX, tileAnchorY, ox, oy, tx, ty, sizeVertex,
);
}

function addTransitioningVertex(array, tx, ty) {
array.emplaceBack(tx, ty);
}

function addGlobeVertex(array, projAnchorX, projAnchorY, projAnchorZ, normX, normY, normZ) {
array.emplaceBack(
// a_globe_anchor
Expand Down Expand Up @@ -209,8 +215,11 @@ export class SymbolBuffers {
opacityVertexArray: SymbolOpacityArray;
opacityVertexBuffer: VertexBuffer;

iconTransitioningVertexArray: SymbolIconTransitioningArray;
iconTransitioningVertexBuffer: ?VertexBuffer;

globeExtVertexArray: SymbolGlobeExtArray;
globeExtVertexBuffer: VertexBuffer;
globeExtVertexBuffer: ?VertexBuffer;

placedSymbolArray: PlacedSymbolArray;

Expand All @@ -222,14 +231,16 @@ export class SymbolBuffers {
this.dynamicLayoutVertexArray = new SymbolDynamicLayoutArray();
this.opacityVertexArray = new SymbolOpacityArray();
this.placedSymbolArray = new PlacedSymbolArray();
this.iconTransitioningVertexArray = new SymbolIconTransitioningArray();
this.globeExtVertexArray = new SymbolGlobeExtArray();
}

isEmpty(): boolean {
return this.layoutVertexArray.length === 0 &&
this.indexArray.length === 0 &&
this.dynamicLayoutVertexArray.length === 0 &&
this.opacityVertexArray.length === 0;
this.opacityVertexArray.length === 0 &&
this.iconTransitioningVertexArray.length === 0;
}

upload(context: Context, dynamicIndexBuffer: boolean, upload?: boolean, update?: boolean) {
Expand All @@ -242,6 +253,9 @@ export class SymbolBuffers {
this.indexBuffer = context.createIndexBuffer(this.indexArray, dynamicIndexBuffer);
this.dynamicLayoutVertexBuffer = context.createVertexBuffer(this.dynamicLayoutVertexArray, dynamicLayoutAttributes.members, true);
this.opacityVertexBuffer = context.createVertexBuffer(this.opacityVertexArray, shaderOpacityAttributes, true);
if (this.iconTransitioningVertexArray.length > 0) {
this.iconTransitioningVertexBuffer = context.createVertexBuffer(this.iconTransitioningVertexArray, iconTransitioningAttributes.members, true);
}
if (this.globeExtVertexArray.length > 0) {
this.globeExtVertexBuffer = context.createVertexBuffer(this.globeExtVertexArray, symbolGlobeExtAttributes.members, true);
}
Expand All @@ -262,6 +276,9 @@ export class SymbolBuffers {
this.segments.destroy();
this.dynamicLayoutVertexBuffer.destroy();
this.opacityVertexBuffer.destroy();
if (this.iconTransitioningVertexBuffer) {
this.iconTransitioningVertexBuffer.destroy();
}
if (this.globeExtVertexBuffer) {
this.globeExtVertexBuffer.destroy();
}
Expand Down Expand Up @@ -584,7 +601,10 @@ class SymbolBucket implements Bucket {
this.features.push(symbolFeature);

if (icon) {
icons[icon.name] = true;
icons[icon.namePrimary] = true;
if (icon.nameSecondary) {
icons[icon.nameSecondary] = true;
}
}

if (text) {
Expand All @@ -599,7 +619,7 @@ class SymbolBucket implements Bucket {
this.calculateGlyphDependencies(section.text, sectionStack, textAlongLine, this.allowVerticalPlacement, doesAllowVerticalWritingMode);
} else {
// Add section image to the list of dependencies.
icons[section.image.name] = true;
icons[section.image.namePrimary] = true;
}
}
}
Expand Down Expand Up @@ -693,7 +713,8 @@ class SymbolBucket implements Bucket {
associatedIconIndex: number,
availableImages: Array<string>,
canonical: CanonicalTileID,
brightness: ?number) {
brightness: ?number,
hasAnySecondaryIcon: boolean) {
const indexArray = arrays.indexArray;
const layoutVertexArray = arrays.layoutVertexArray;
const globeExtVertexArray = arrays.globeExtVertexArray;
Expand All @@ -707,14 +728,14 @@ class SymbolBucket implements Bucket {
const sections = feature.text && feature.text.sections;

for (let i = 0; i < quads.length; i++) {
const {tl, tr, bl, br, tex, pixelOffsetTL, pixelOffsetBR, minFontScaleX, minFontScaleY, glyphOffset, isSDF, sectionIndex} = quads[i];
const {tl, tr, bl, br, texPrimary, texSecondary, pixelOffsetTL, pixelOffsetBR, minFontScaleX, minFontScaleY, glyphOffset, isSDF, sectionIndex} = quads[i];
const index = segment.vertexLength;

const y = glyphOffset[1];
addVertex(layoutVertexArray, tileAnchor.x, tileAnchor.y, tl.x, y + tl.y, tex.x, tex.y, sizeVertex, isSDF, pixelOffsetTL.x, pixelOffsetTL.y, minFontScaleX, minFontScaleY);
addVertex(layoutVertexArray, tileAnchor.x, tileAnchor.y, tr.x, y + tr.y, tex.x + tex.w, tex.y, sizeVertex, isSDF, pixelOffsetBR.x, pixelOffsetTL.y, minFontScaleX, minFontScaleY);
addVertex(layoutVertexArray, tileAnchor.x, tileAnchor.y, bl.x, y + bl.y, tex.x, tex.y + tex.h, sizeVertex, isSDF, pixelOffsetTL.x, pixelOffsetBR.y, minFontScaleX, minFontScaleY);
addVertex(layoutVertexArray, tileAnchor.x, tileAnchor.y, br.x, y + br.y, tex.x + tex.w, tex.y + tex.h, sizeVertex, isSDF, pixelOffsetBR.x, pixelOffsetBR.y, minFontScaleX, minFontScaleY);
addVertex(layoutVertexArray, tileAnchor.x, tileAnchor.y, tl.x, y + tl.y, texPrimary.x, texPrimary.y, sizeVertex, isSDF, pixelOffsetTL.x, pixelOffsetTL.y, minFontScaleX, minFontScaleY);
addVertex(layoutVertexArray, tileAnchor.x, tileAnchor.y, tr.x, y + tr.y, texPrimary.x + texPrimary.w, texPrimary.y, sizeVertex, isSDF, pixelOffsetBR.x, pixelOffsetTL.y, minFontScaleX, minFontScaleY);
addVertex(layoutVertexArray, tileAnchor.x, tileAnchor.y, bl.x, y + bl.y, texPrimary.x, texPrimary.y + texPrimary.h, sizeVertex, isSDF, pixelOffsetTL.x, pixelOffsetBR.y, minFontScaleX, minFontScaleY);
addVertex(layoutVertexArray, tileAnchor.x, tileAnchor.y, br.x, y + br.y, texPrimary.x + texPrimary.w, texPrimary.y + texPrimary.h, sizeVertex, isSDF, pixelOffsetBR.x, pixelOffsetBR.y, minFontScaleX, minFontScaleY);

if (globe) {
const {x, y, z} = globe.anchor;
Expand All @@ -729,6 +750,16 @@ class SymbolBucket implements Bucket {
addDynamicAttributes(arrays.dynamicLayoutVertexArray, tileAnchor.x, tileAnchor.y, tileAnchor.z, angle);
}

// For data-driven cases if at least of one the icon has a transitionable variant
// we have to load the main variant in cases where the secondary image is not specified
if (hasAnySecondaryIcon) {
const tex = texSecondary ? texSecondary : texPrimary;
addTransitioningVertex(arrays.iconTransitioningVertexArray, tex.x, tex.y);
addTransitioningVertex(arrays.iconTransitioningVertexArray, tex.x + tex.w, tex.y);
addTransitioningVertex(arrays.iconTransitioningVertexArray, tex.x, tex.y + tex.h);
addTransitioningVertex(arrays.iconTransitioningVertexArray, tex.x + tex.w, tex.y + tex.h);
}

indexArray.emplaceBack(index, index + 1, index + 2);
indexArray.emplaceBack(index + 1, index + 2, index + 3);

Expand Down
8 changes: 6 additions & 2 deletions src/render/draw_symbol.js
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,7 @@ function drawLayerSymbols(painter, sourceCache, layer, coords, isText, translate
const uLabelPlaneMatrix = projectedPosOnLabelSpace ? identityMat4 : labelPlaneMatrixRendering;
const uglCoordMatrix = painter.translatePosMatrix(glCoordMatrix, tile, translate, translateAnchor, true);
const invMatrix = bucket.getProjection().createInversionMatrix(tr, coord.canonical);
const transitionProgress = layer.paint.get('icon-image-cross-fade').constantOr(0.0);

const baseDefines = ([]: any);
if (painter.terrainRenderModeElevated() && pitchWithMap) {
Expand All @@ -389,6 +390,9 @@ function drawLayerSymbols(painter, sourceCache, layer, coords, isText, translate
if (projectedPosOnLabelSpace) {
baseDefines.push('PROJECTED_POS_ON_VIEWPORT');
}
if (transitionProgress > 0.0) {
baseDefines.push('ICON_TRANSITION');
}

const hasHalo = isSDF && layer.paint.get(isText ? 'text-halo-width' : 'icon-halo-width').constantOr(1) !== 0;

Expand All @@ -403,7 +407,7 @@ function drawLayerSymbols(painter, sourceCache, layer, coords, isText, translate
}
} else {
uniformValues = symbolIconUniformValues(sizeData.kind, size, rotateInShader, pitchWithMap, painter, matrix,
uLabelPlaneMatrix, uglCoordMatrix, isText, texSize, coord, globeToMercator, mercatorCenter, invMatrix, cameraUpVector, bucket.getProjection());
uLabelPlaneMatrix, uglCoordMatrix, isText, texSize, coord, globeToMercator, mercatorCenter, invMatrix, cameraUpVector, bucket.getProjection(), transitionProgress);
}

const program = painter.useProgram(getSymbolProgramName(isSDF, isText, bucket), programConfiguration, baseDefines);
Expand Down Expand Up @@ -480,7 +484,7 @@ function drawLayerSymbols(painter, sourceCache, layer, coords, isText, translate
function drawSymbolElements(buffers, segments, layer, painter, program, depthMode, stencilMode, colorMode, uniformValues) {
const context = painter.context;
const gl = context.gl;
const dynamicBuffers = [buffers.dynamicLayoutVertexBuffer, buffers.opacityVertexBuffer, buffers.globeExtVertexBuffer];
const dynamicBuffers = [buffers.dynamicLayoutVertexBuffer, buffers.opacityVertexBuffer, buffers.iconTransitioningVertexBuffer, buffers.globeExtVertexBuffer];
program.draw(context, gl.TRIANGLES, depthMode, stencilMode, colorMode, CullFaceMode.disabled,
uniformValues, layer.id, buffers.layoutVertexBuffer,
buffers.indexBuffer, segments, layer.paint,
Expand Down
Loading

0 comments on commit 6a7126f

Please sign in to comment.