diff --git a/examples/webgl-points-layer.html b/examples/webgl-points-layer.html
index edf8dd120e..0a9af85142 100644
--- a/examples/webgl-points-layer.html
+++ b/examples/webgl-points-layer.html
@@ -13,6 +13,8 @@ docs: >
Note: those will be taken from the attributes provided to the renderer
* `['var', 'varName']` fetches a value from the style variables, or 0 if undefined
* `['time']` returns the time in seconds since the creation of the layer
+ * `['zoom']` returns the current zoom level
+ * `['resolution']` returns the current resolution
* Math operators:
* `['*', value1, value2]` multiplies `value1` by `value2`
@@ -57,7 +59,6 @@ docs: >
* `['color', red, green, blue, alpha]` creates a `color` value from `number` values; the `alpha` parameter is
optional; if not specified, it will be set to 1.
Note: `red`, `green` and `blue` components must be values between 0 and 255; `alpha` between 0 and 1.
-
Values can either be literals or another operator, as they will be evaluated recursively.
Literal values can be of the following types:
* `boolean`
@@ -76,6 +77,7 @@ experimental: true
+
diff --git a/examples/webgl-points-layer.js b/examples/webgl-points-layer.js
index 3d3c980a41..1db76814c4 100644
--- a/examples/webgl-points-layer.js
+++ b/examples/webgl-points-layer.js
@@ -95,6 +95,35 @@ const predefinedStyles = {
offset: [0, 0],
opacity: 0.95
}
+ },
+ 'rotating-bars': {
+ symbol: {
+ symbolType: 'square',
+ rotation: ['*', [
+ 'time'
+ ], 0.1],
+ size: ['array', 4, [
+ 'interpolate',
+ ['linear'],
+ ['get', 'population'],
+ 20000, 4,
+ 300000, 28]
+ ],
+ color: [
+ 'interpolate',
+ ['linear'],
+ ['get', 'population'],
+ 20000, '#ffdc00',
+ 300000, '#ff5b19'
+ ],
+ offset: ['array', 0, [
+ 'interpolate',
+ ['linear'],
+ ['get', 'population'],
+ 20000, 2,
+ 300000, 14]
+ ]
+ }
}
};
@@ -167,3 +196,10 @@ function onSelectChange() {
}
onSelectChange();
select.addEventListener('change', onSelectChange);
+
+// animate the map
+function animate() {
+ map.render();
+ window.requestAnimationFrame(animate);
+}
+animate();
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/style/expressions.js b/src/ol/style/expressions.js
index 6527164bf2..454d80a2d5 100644
--- a/src/ol/style/expressions.js
+++ b/src/ol/style/expressions.js
@@ -16,6 +16,8 @@ import {asArray, isStringColor} from '../color.js';
* Note: those will be taken from the attributes provided to the renderer
* * `['var', 'varName']` fetches a value from the style variables, or 0 if undefined
* * `['time']` returns the time in seconds since the creation of the layer
+ * * `['zoom']` returns the current zoom level
+ * * `['resolution']` returns the current resolution
*
* * Math operators:
* * `['*', value1, value2]` multiplies `value1` by `value2`
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);
+
}`);
});
});