From 6c46eb1dd00d7127cb07a9a723f44a24aa18c47a Mon Sep 17 00:00:00 2001 From: Olivier Guyot Date: Fri, 20 Dec 2019 17:14:06 +0100 Subject: [PATCH] Webgl / add support for a rotation parameter in LiteralStyle The ShaderBuilder can now take a rotation expression. --- src/ol/style/LiteralStyle.js | 1 + src/ol/webgl/ShaderBuilder.js | 41 ++++++- test/spec/ol/webgl/shaderbuilder.test.js | 132 ++++++++++++++++++++--- 3 files changed, 159 insertions(+), 15 deletions(-) diff --git a/src/ol/style/LiteralStyle.js b/src/ol/style/LiteralStyle.js index f4fd388464..1e320ca9c2 100644 --- a/src/ol/style/LiteralStyle.js +++ b/src/ol/style/LiteralStyle.js @@ -35,6 +35,7 @@ export const SymbolType = { * @property {string} [src] Path to the image to be used for the symbol. Only required with `symbolType: 'image'`. * @property {import("../color.js").Color|Array|string} [color='#FFFFFF'] Color used for the representation (either fill, line or symbol). * @property {ExpressionValue} [opacity=1] Opacity. + * @property {ExpressionValue} [rotation=0] Symbol rotation in radians. * @property {Array} [offset] Offset on X and Y axis for symbols. If not specified, the symbol will be centered. * @property {Array} [textureCoord] Texture coordinates. If not specified, the whole texture will be used (range for 0 to 1 on both axes). * @property {boolean} [rotateWithView=false] Specify whether the symbol must rotate with the view or stay upwards. diff --git a/src/ol/webgl/ShaderBuilder.js b/src/ol/webgl/ShaderBuilder.js index 1ccd598583..a2b21298c4 100644 --- a/src/ol/webgl/ShaderBuilder.js +++ b/src/ol/webgl/ShaderBuilder.js @@ -56,6 +56,12 @@ export class ShaderBuilder { */ this.sizeExpression = 'vec2(1.0)'; + /** + * @type {string} + * @private + */ + this.rotationExpression = '0.0'; + /** * @type {string} * @private @@ -138,6 +144,18 @@ export class ShaderBuilder { return this; } + /** + * Sets an expression to compute the rotation of the shape. + * This expression can use all the uniforms and attributes available + * in the vertex shader, and should evaluate to a `float` value in radians. + * @param {string} expression Size expression + * @return {ShaderBuilder} the builder object + */ + setRotationExpression(expression) { + this.rotationExpression = expression; + return this; + } + /** * Sets an expression to compute the offset of the symbol from the point center. * This expression can use all the uniforms and attributes available @@ -291,10 +309,24 @@ ${varyings.map(function(varying) { }).join('\n')} void main(void) { mat4 offsetMatrix = ${offsetMatrix}; - vec2 size = ${this.sizeExpression}; + vec2 halfSize = ${this.sizeExpression} * 0.5; vec2 offset = ${this.offsetExpression}; - float offsetX = a_index == 0.0 || a_index == 3.0 ? offset.x - size.x / 2.0 : offset.x + size.x / 2.0; - float offsetY = a_index == 0.0 || a_index == 1.0 ? offset.y - size.y / 2.0 : offset.y + size.y / 2.0; + float angle = ${this.rotationExpression}; + float offsetX; + float offsetY; + if (a_index == 0.0) { + offsetX = (offset.x - halfSize.x) * cos(angle) + (offset.y - halfSize.y) * sin(angle); + offsetY = (offset.y - halfSize.y) * cos(angle) - (offset.x - halfSize.x) * sin(angle); + } else if (a_index == 1.0) { + offsetX = (offset.x + halfSize.x) * cos(angle) + (offset.y - halfSize.y) * sin(angle); + offsetY = (offset.y - halfSize.y) * cos(angle) - (offset.x + halfSize.x) * sin(angle); + } else if (a_index == 2.0) { + offsetX = (offset.x + halfSize.x) * cos(angle) + (offset.y + halfSize.y) * sin(angle); + offsetY = (offset.y + halfSize.y) * cos(angle) - (offset.x + halfSize.x) * sin(angle); + } else { + offsetX = (offset.x - halfSize.x) * cos(angle) + (offset.y + halfSize.y) * sin(angle); + offsetY = (offset.y + halfSize.y) * cos(angle) - (offset.x - halfSize.x) * sin(angle); + } vec4 offsets = offsetMatrix * vec4(offsetX, offsetY, 0.0, 0.0); gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets; vec4 texCoord = ${this.texCoordExpression}; @@ -381,6 +413,7 @@ export function parseLiteralStyle(style) { const texCoord = symbStyle.textureCoord || [0, 0, 1, 1]; const offset = symbStyle.offset || [0, 0]; const opacity = symbStyle.opacity !== undefined ? symbStyle.opacity : 1; + const rotation = symbStyle.rotation !== undefined ? symbStyle.rotation : 0; /** * @type {import("../style/expressions.js").ParsingContext} @@ -406,6 +439,7 @@ export function parseLiteralStyle(style) { }; const parsedColor = expressionToGlsl(fragContext, color, ValueTypes.COLOR); const parsedOpacity = expressionToGlsl(fragContext, opacity, ValueTypes.NUMBER); + const parsedRotation = expressionToGlsl(fragContext, rotation, ValueTypes.NUMBER); let opacityFilter = '1.0'; const visibleSize = `vec2(${expressionToGlsl(fragContext, size, ValueTypes.NUMBER_ARRAY | ValueTypes.NUMBER)}).x`; @@ -427,6 +461,7 @@ export function parseLiteralStyle(style) { const builder = new ShaderBuilder() .setSizeExpression(`vec2(${parsedSize})`) + .setRotationExpression(parsedRotation) .setSymbolOffsetExpression(parsedOffset) .setTextureCoordinateExpression(parsedTexCoord) .setSymbolRotateWithView(!!symbStyle.rotateWithView) diff --git a/test/spec/ol/webgl/shaderbuilder.test.js b/test/spec/ol/webgl/shaderbuilder.test.js index 4b08699ebb..78ac4390e9 100644 --- a/test/spec/ol/webgl/shaderbuilder.test.js +++ b/test/spec/ol/webgl/shaderbuilder.test.js @@ -30,10 +30,24 @@ varying float v_opacity; varying vec3 v_test; void main(void) { mat4 offsetMatrix = u_offsetScaleMatrix; - vec2 size = vec2(6.0); + vec2 halfSize = vec2(6.0) * 0.5; vec2 offset = vec2(5.0, -7.0); - float offsetX = a_index == 0.0 || a_index == 3.0 ? offset.x - size.x / 2.0 : offset.x + size.x / 2.0; - float offsetY = a_index == 0.0 || a_index == 1.0 ? offset.y - size.y / 2.0 : offset.y + size.y / 2.0; + float angle = 0.0; + float offsetX; + float offsetY; + if (a_index == 0.0) { + offsetX = (offset.x - halfSize.x) * cos(angle) + (offset.y - halfSize.y) * sin(angle); + offsetY = (offset.y - halfSize.y) * cos(angle) - (offset.x - halfSize.x) * sin(angle); + } else if (a_index == 1.0) { + offsetX = (offset.x + halfSize.x) * cos(angle) + (offset.y - halfSize.y) * sin(angle); + offsetY = (offset.y - halfSize.y) * cos(angle) - (offset.x + halfSize.x) * sin(angle); + } else if (a_index == 2.0) { + offsetX = (offset.x + halfSize.x) * cos(angle) + (offset.y + halfSize.y) * sin(angle); + offsetY = (offset.y + halfSize.y) * cos(angle) - (offset.x + halfSize.x) * sin(angle); + } else { + offsetX = (offset.x - halfSize.x) * cos(angle) + (offset.y + halfSize.y) * sin(angle); + offsetY = (offset.y + halfSize.y) * cos(angle) - (offset.x - halfSize.x) * sin(angle); + } vec4 offsets = offsetMatrix * vec4(offsetX, offsetY, 0.0, 0.0); gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets; vec4 texCoord = vec4(0.0, 0.5, 0.5, 1.0); @@ -72,10 +86,24 @@ varying vec2 v_quadCoord; void main(void) { mat4 offsetMatrix = u_offsetScaleMatrix; - vec2 size = vec2(6.0); + vec2 halfSize = vec2(6.0) * 0.5; vec2 offset = vec2(5.0, -7.0); - float offsetX = a_index == 0.0 || a_index == 3.0 ? offset.x - size.x / 2.0 : offset.x + size.x / 2.0; - float offsetY = a_index == 0.0 || a_index == 1.0 ? offset.y - size.y / 2.0 : offset.y + size.y / 2.0; + float angle = 0.0; + float offsetX; + float offsetY; + if (a_index == 0.0) { + offsetX = (offset.x - halfSize.x) * cos(angle) + (offset.y - halfSize.y) * sin(angle); + offsetY = (offset.y - halfSize.y) * cos(angle) - (offset.x - halfSize.x) * sin(angle); + } else if (a_index == 1.0) { + offsetX = (offset.x + halfSize.x) * cos(angle) + (offset.y - halfSize.y) * sin(angle); + offsetY = (offset.y - halfSize.y) * cos(angle) - (offset.x + halfSize.x) * sin(angle); + } else if (a_index == 2.0) { + offsetX = (offset.x + halfSize.x) * cos(angle) + (offset.y + halfSize.y) * sin(angle); + offsetY = (offset.y + halfSize.y) * cos(angle) - (offset.x + halfSize.x) * sin(angle); + } else { + offsetX = (offset.x - halfSize.x) * cos(angle) + (offset.y + halfSize.y) * sin(angle); + offsetY = (offset.y + halfSize.y) * cos(angle) - (offset.x - halfSize.x) * sin(angle); + } vec4 offsets = offsetMatrix * vec4(offsetX, offsetY, 0.0, 0.0); gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets; vec4 texCoord = vec4(0.0, 0.5, 0.5, 1.0); @@ -112,10 +140,24 @@ varying vec2 v_quadCoord; void main(void) { mat4 offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix; - vec2 size = vec2(6.0); + vec2 halfSize = vec2(6.0) * 0.5; vec2 offset = vec2(5.0, -7.0); - float offsetX = a_index == 0.0 || a_index == 3.0 ? offset.x - size.x / 2.0 : offset.x + size.x / 2.0; - float offsetY = a_index == 0.0 || a_index == 1.0 ? offset.y - size.y / 2.0 : offset.y + size.y / 2.0; + float angle = 0.0; + float offsetX; + float offsetY; + if (a_index == 0.0) { + offsetX = (offset.x - halfSize.x) * cos(angle) + (offset.y - halfSize.y) * sin(angle); + offsetY = (offset.y - halfSize.y) * cos(angle) - (offset.x - halfSize.x) * sin(angle); + } else if (a_index == 1.0) { + offsetX = (offset.x + halfSize.x) * cos(angle) + (offset.y - halfSize.y) * sin(angle); + offsetY = (offset.y - halfSize.y) * cos(angle) - (offset.x + halfSize.x) * sin(angle); + } else if (a_index == 2.0) { + offsetX = (offset.x + halfSize.x) * cos(angle) + (offset.y + halfSize.y) * sin(angle); + offsetY = (offset.y + halfSize.y) * cos(angle) - (offset.x + halfSize.x) * sin(angle); + } else { + offsetX = (offset.x - halfSize.x) * cos(angle) + (offset.y + halfSize.y) * sin(angle); + offsetY = (offset.y + halfSize.y) * cos(angle) - (offset.x - halfSize.x) * sin(angle); + } vec4 offsets = offsetMatrix * vec4(offsetX, offsetY, 0.0, 0.0); gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets; vec4 texCoord = vec4(0.0, 0.5, 0.5, 1.0); @@ -147,10 +189,24 @@ varying vec2 v_quadCoord; varying vec4 v_hitColor; void main(void) { mat4 offsetMatrix = u_offsetScaleMatrix; - vec2 size = vec2(1.0); + vec2 halfSize = vec2(1.0) * 0.5; vec2 offset = vec2(0.0); - float offsetX = a_index == 0.0 || a_index == 3.0 ? offset.x - size.x / 2.0 : offset.x + size.x / 2.0; - float offsetY = a_index == 0.0 || a_index == 1.0 ? offset.y - size.y / 2.0 : offset.y + size.y / 2.0; + float angle = 0.0; + float offsetX; + float offsetY; + if (a_index == 0.0) { + offsetX = (offset.x - halfSize.x) * cos(angle) + (offset.y - halfSize.y) * sin(angle); + offsetY = (offset.y - halfSize.y) * cos(angle) - (offset.x - halfSize.x) * sin(angle); + } else if (a_index == 1.0) { + offsetX = (offset.x + halfSize.x) * cos(angle) + (offset.y - halfSize.y) * sin(angle); + offsetY = (offset.y - halfSize.y) * cos(angle) - (offset.x + halfSize.x) * sin(angle); + } else if (a_index == 2.0) { + offsetX = (offset.x + halfSize.x) * cos(angle) + (offset.y + halfSize.y) * sin(angle); + offsetY = (offset.y + halfSize.y) * cos(angle) - (offset.x + halfSize.x) * sin(angle); + } else { + offsetX = (offset.x - halfSize.x) * cos(angle) + (offset.y + halfSize.y) * sin(angle); + offsetY = (offset.y + halfSize.y) * cos(angle) - (offset.x - halfSize.x) * sin(angle); + } vec4 offsets = offsetMatrix * vec4(offsetX, offsetY, 0.0, 0.0); gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets; vec4 texCoord = vec4(0.0, 0.0, 1.0, 1.0); @@ -161,6 +217,58 @@ void main(void) { v = a_index == 2.0 || a_index == 3.0 ? 0.0 : 1.0; v_quadCoord = vec2(u, v); v_hitColor = a_hitColor; +}`); + }); + it('generates a symbol vertex shader (with a rotation expression)', function() { + const builder = new ShaderBuilder(); + builder.setSizeExpression(`vec2(${numberToGlsl(6)})`); + builder.setSymbolOffsetExpression(arrayToGlsl([5, -7])); + builder.setRotationExpression('u_time * 0.2'); + + expect(builder.getSymbolVertexShader()).to.eql(`precision mediump float; +uniform mat4 u_projectionMatrix; +uniform mat4 u_offsetScaleMatrix; +uniform mat4 u_offsetRotateMatrix; +uniform float u_time; +uniform float u_zoom; +uniform float u_resolution; + +attribute vec2 a_position; +attribute float a_index; + +varying vec2 v_texCoord; +varying vec2 v_quadCoord; + +void main(void) { + mat4 offsetMatrix = u_offsetScaleMatrix; + vec2 halfSize = vec2(6.0) * 0.5; + vec2 offset = vec2(5.0, -7.0); + float angle = u_time * 0.2; + float offsetX; + float offsetY; + if (a_index == 0.0) { + offsetX = (offset.x - halfSize.x) * cos(angle) + (offset.y - halfSize.y) * sin(angle); + offsetY = (offset.y - halfSize.y) * cos(angle) - (offset.x - halfSize.x) * sin(angle); + } else if (a_index == 1.0) { + offsetX = (offset.x + halfSize.x) * cos(angle) + (offset.y - halfSize.y) * sin(angle); + offsetY = (offset.y - halfSize.y) * cos(angle) - (offset.x + halfSize.x) * sin(angle); + } else if (a_index == 2.0) { + offsetX = (offset.x + halfSize.x) * cos(angle) + (offset.y + halfSize.y) * sin(angle); + offsetY = (offset.y + halfSize.y) * cos(angle) - (offset.x + halfSize.x) * sin(angle); + } else { + offsetX = (offset.x - halfSize.x) * cos(angle) + (offset.y + halfSize.y) * sin(angle); + offsetY = (offset.y + halfSize.y) * cos(angle) - (offset.x - halfSize.x) * sin(angle); + } + vec4 offsets = offsetMatrix * vec4(offsetX, offsetY, 0.0, 0.0); + gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets; + vec4 texCoord = vec4(0.0, 0.0, 1.0, 1.0); + float u = a_index == 0.0 || a_index == 3.0 ? texCoord.s : texCoord.p; + float v = a_index == 2.0 || a_index == 3.0 ? texCoord.t : texCoord.q; + v_texCoord = vec2(u, v); + u = a_index == 0.0 || a_index == 3.0 ? 0.0 : 1.0; + v = a_index == 2.0 || a_index == 3.0 ? 0.0 : 1.0; + v_quadCoord = vec2(u, v); + }`); }); });