diff --git a/docs/layers/icon-layer.md b/docs/layers/icon-layer.md index 93505f47671..70fab07c6f6 100644 --- a/docs/layers/icon-layer.md +++ b/docs/layers/icon-layer.md @@ -212,6 +212,12 @@ The maximum size in pixels. Whether the layer should be rendered in high-precision 64-bit mode. Note that since deck.gl v6.1, the default 32-bit projection uses a hybrid mode that matches 64-bit precision with significantly better performance. +##### `billboard` (Boolean, optional) + +- Default: `true` + +If on, the icon always faces camera. Otherwise the icon faces up (z) + ### Data Accessors ##### `getIcon` ([Function](/docs/developer-guide/using-layers.md#accessors), optional) diff --git a/docs/layers/text-layer.md b/docs/layers/text-layer.md index d34697a6cbd..8257a5787aa 100644 --- a/docs/layers/text-layer.md +++ b/docs/layers/text-layer.md @@ -114,6 +114,12 @@ The maximum size in pixels. Whether the layer should be rendered in high-precision 64-bit mode. Note that since deck.gl v6.1, the default 32-bit projection uses a hybrid mode that matches 64-bit precision with significantly better performance. +##### `billboard` (Boolean, optional) + +- Default: `true` + +If on, the text always faces camera. Otherwise the text faces up (z). + ##### `fontFamily` (String, optional) * Default: `'Monaco, monospace'` diff --git a/modules/core/src/shaderlib/project/project.glsl.js b/modules/core/src/shaderlib/project/project.glsl.js index 3b2726f4ec0..f5f8232edab 100644 --- a/modules/core/src/shaderlib/project/project.glsl.js +++ b/modules/core/src/shaderlib/project/project.glsl.js @@ -180,6 +180,9 @@ float project_size_to_pixel(float meters) { float project_pixel_size(float pixels) { return pixels; } +vec2 project_pixel_size(vec2 pixels) { + return pixels; +} // Deprecated, remove in v8 float project_scale(float meters) { diff --git a/modules/layers/src/icon-layer/icon-layer-vertex.glsl.js b/modules/layers/src/icon-layer/icon-layer-vertex.glsl.js index 93ff0160b72..89974daf59f 100644 --- a/modules/layers/src/icon-layer/icon-layer-vertex.glsl.js +++ b/modules/layers/src/icon-layer/icon-layer-vertex.glsl.js @@ -37,6 +37,7 @@ uniform float sizeScale; uniform vec2 iconsTextureDim; uniform float sizeMinPixels; uniform float sizeMaxPixels; +uniform bool billboard; varying float vColorMode; varying vec4 vColor; @@ -66,10 +67,16 @@ void main(void) { // scale and rotate vertex in "pixel" value and convert back to fraction in clipspace vec2 pixelOffset = positions / 2.0 * iconSize + instanceOffsets; pixelOffset = rotate_by_angle(pixelOffset, instanceAngles) * instanceScale; - pixelOffset.y *= -1.0; - gl_Position = project_position_to_clipspace(instancePositions, instancePositions64xyLow, vec3(0.0)); - gl_Position.xy += project_pixel_size_to_clipspace(pixelOffset); + if (billboard) { + pixelOffset.y *= -1.0; + gl_Position = project_position_to_clipspace(instancePositions, instancePositions64xyLow, vec3(0.0)); + gl_Position.xy += project_pixel_size_to_clipspace(pixelOffset); + + } else { + vec3 offset_common = vec3(project_pixel_size(pixelOffset.xy), 0.0); + gl_Position = project_position_to_clipspace(instancePositions, instancePositions64xyLow, offset_common); + } vTextureCoords = mix( instanceIconFrames.xy, diff --git a/modules/layers/src/icon-layer/icon-layer.js b/modules/layers/src/icon-layer/icon-layer.js index 1bd5d4d3344..22283e918c0 100644 --- a/modules/layers/src/icon-layer/icon-layer.js +++ b/modules/layers/src/icon-layer/icon-layer.js @@ -56,6 +56,7 @@ const defaultProps = { iconMapping: {type: 'object', value: {}, async: true}, sizeScale: {type: 'number', value: 1, min: 0}, fp64: false, + billboard: true, sizeUnits: 'pixels', sizeMinPixels: {type: 'number', min: 0, value: 0}, // min point radius in pixels sizeMaxPixels: {type: 'number', min: 0, value: Number.MAX_SAFE_INTEGER}, // max point radius in pixels @@ -174,7 +175,7 @@ export default class IconLayer extends Layer { /* eslint-enable max-statements, complexity */ draw({uniforms}) { - const {sizeScale, sizeMinPixels, sizeMaxPixels, sizeUnits} = this.props; + const {sizeScale, sizeMinPixels, sizeMaxPixels, sizeUnits, billboard} = this.props; const {iconManager} = this.state; const {viewport} = this.context; @@ -187,7 +188,8 @@ export default class IconLayer extends Layer { sizeScale: sizeScale * (sizeUnits === 'pixels' ? viewport.distanceScales.metersPerPixel[2] : 1), sizeMinPixels, - sizeMaxPixels + sizeMaxPixels, + billboard }) ); } diff --git a/modules/layers/src/text-layer/multi-icon-layer/multi-icon-layer-vertex.glsl.js b/modules/layers/src/text-layer/multi-icon-layer/multi-icon-layer-vertex.glsl.js index e107d2060fc..f2b62471900 100644 --- a/modules/layers/src/text-layer/multi-icon-layer/multi-icon-layer-vertex.glsl.js +++ b/modules/layers/src/text-layer/multi-icon-layer/multi-icon-layer-vertex.glsl.js @@ -42,6 +42,7 @@ uniform float sizeMaxPixels; uniform vec2 iconsTextureDim; uniform float gamma; uniform float opacity; +uniform bool billboard; varying float vColorMode; varying vec4 vColor; @@ -73,10 +74,16 @@ void main(void) { pixelOffset = rotate_by_angle(pixelOffset, instanceAngles) * instanceScale; pixelOffset += instancePixelOffset; - pixelOffset.y *= -1.0; - - gl_Position = project_position_to_clipspace(instancePositions, instancePositions64xyLow, vec3(0.0)); - gl_Position.xy += project_pixel_size_to_clipspace(pixelOffset); + + if (billboard) { + pixelOffset.y *= -1.0; + gl_Position = project_position_to_clipspace(instancePositions, instancePositions64xyLow, vec3(0.0)); + gl_Position.xy += project_pixel_size_to_clipspace(pixelOffset); + + } else { + vec3 offset_common = vec3(project_pixel_size(pixelOffset.xy), 0.0); + gl_Position = project_position_to_clipspace(instancePositions, instancePositions64xyLow, offset_common); + } vTextureCoords = mix( instanceIconFrames.xy, diff --git a/modules/layers/src/text-layer/text-layer.js b/modules/layers/src/text-layer/text-layer.js index c014184b510..4df3c57c508 100644 --- a/modules/layers/src/text-layer/text-layer.js +++ b/modules/layers/src/text-layer/text-layer.js @@ -57,6 +57,7 @@ const FONT_SETTINGS_PROPS = ['fontSize', 'buffer', 'sdf', 'radius', 'cutoff']; const defaultProps = { fp64: false, + billboard: true, sizeScale: 1, sizeUnits: 'pixels', sizeMinPixels: 0, @@ -247,6 +248,7 @@ export default class TextLayer extends CompositeLayer { getAlignmentBaseline, getPixelOffset, fp64, + billboard, sdf, sizeScale, sizeUnits, @@ -272,6 +274,7 @@ export default class TextLayer extends CompositeLayer { getAnchorY: this.getAnchorYFromAlignmentBaseline(getAlignmentBaseline), getPixelOffset: this._getAccessor(getPixelOffset), fp64, + billboard, sizeScale: sizeScale * scale, sizeUnits, sizeMinPixels: sizeMinPixels * scale, diff --git a/test/render/golden-images/icon-lnglat-facing-up.png b/test/render/golden-images/icon-lnglat-facing-up.png new file mode 100644 index 00000000000..729d04ce914 Binary files /dev/null and b/test/render/golden-images/icon-lnglat-facing-up.png differ diff --git a/test/render/test-cases.js b/test/render/test-cases.js index 4bd29ea4ffd..934ef8eb05a 100644 --- a/test/render/test-cases.js +++ b/test/render/test-cases.js @@ -508,6 +508,40 @@ export const TEST_CASES = [ }, goldenImage: './test/render/golden-images/icon-lnglat.png' }, + { + name: 'icon-lnglat-facing-up', + viewState: { + latitude: 37.751537058389985, + longitude: -122.42694203247012, + zoom: 11.5, + pitch: 60, + bearing: 0 + }, + // rendering times + renderingTimes: 2, + layers: [ + new IconLayer({ + id: 'icon-lnglat', + data: dataSamples.points, + iconAtlas: ICON_ATLAS, + billboard: false, + iconMapping: dataSamples.iconAtlas, + sizeScale: 12, + getPosition: d => d.COORDINATES, + getColor: d => [64, 64, 72], + getIcon: d => (d.PLACEMENT === 'SW' ? 'marker' : 'marker-warning'), + getSize: d => (d.RACKS > 2 ? 2 : 1), + opacity: 0.8, + pickable: true + }) + ], + onAfterRender: ({layers, done}) => { + if (layers[0].state.iconManager.getTexture()) { + done(); + } + }, + goldenImage: './test/render/golden-images/icon-lnglat-facing-up.png' + }, { name: 'icon-lnglat-64', viewState: {