Merge pull request #10439 from jahow/webgl-add-rotation

WebGL points layer / add support for symbol rotation
This commit is contained in:
Olivier Guyot
2019-12-23 10:32:12 +01:00
committed by GitHub
6 changed files with 200 additions and 16 deletions
+3 -1
View File
@@ -13,6 +13,8 @@ docs: >
Note: those will be taken from the attributes provided to the renderer 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 * `['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 * `['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: * Math operators:
* `['*', value1, value2]` multiplies `value1` by `value2` * `['*', 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 * `['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. 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. 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. Values can either be literals or another operator, as they will be evaluated recursively.
Literal values can be of the following types: Literal values can be of the following types:
* `boolean` * `boolean`
@@ -76,6 +77,7 @@ experimental: true
<option value="triangles-latitude">Triangles, color related to latitude</option> <option value="triangles-latitude">Triangles, color related to latitude</option>
<option value="circles">Circles, size related to population</option> <option value="circles">Circles, size related to population</option>
<option value="circles-zoom">Circles, size related to zoom</option> <option value="circles-zoom">Circles, size related to zoom</option>
<option value="rotating-bars">Rotating bars</option>
</select> </select>
<textarea style="width: 100%; height: 20rem; font-family: monospace; font-size: small;" id="style-editor"></textarea> <textarea style="width: 100%; height: 20rem; font-family: monospace; font-size: small;" id="style-editor"></textarea>
<small> <small>
+36
View File
@@ -95,6 +95,35 @@ const predefinedStyles = {
offset: [0, 0], offset: [0, 0],
opacity: 0.95 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(); onSelectChange();
select.addEventListener('change', onSelectChange); select.addEventListener('change', onSelectChange);
// animate the map
function animate() {
map.render();
window.requestAnimationFrame(animate);
}
animate();
+1
View File
@@ -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 {string} [src] Path to the image to be used for the symbol. Only required with `symbolType: 'image'`.
* @property {import("../color.js").Color|Array<ExpressionValue>|string} [color='#FFFFFF'] Color used for the representation (either fill, line or symbol). * @property {import("../color.js").Color|Array<ExpressionValue>|string} [color='#FFFFFF'] Color used for the representation (either fill, line or symbol).
* @property {ExpressionValue} [opacity=1] Opacity. * @property {ExpressionValue} [opacity=1] Opacity.
* @property {ExpressionValue} [rotation=0] Symbol rotation in radians.
* @property {Array<ExpressionValue, ExpressionValue>} [offset] Offset on X and Y axis for symbols. If not specified, the symbol will be centered. * @property {Array<ExpressionValue, ExpressionValue>} [offset] Offset on X and Y axis for symbols. If not specified, the symbol will be centered.
* @property {Array<ExpressionValue, ExpressionValue, ExpressionValue, ExpressionValue>} [textureCoord] Texture coordinates. If not specified, the whole texture will be used (range for 0 to 1 on both axes). * @property {Array<ExpressionValue, ExpressionValue, ExpressionValue, ExpressionValue>} [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. * @property {boolean} [rotateWithView=false] Specify whether the symbol must rotate with the view or stay upwards.
+2
View File
@@ -16,6 +16,8 @@ import {asArray, isStringColor} from '../color.js';
* Note: those will be taken from the attributes provided to the renderer * 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 * * `['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 * * `['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: * * Math operators:
* * `['*', value1, value2]` multiplies `value1` by `value2` * * `['*', value1, value2]` multiplies `value1` by `value2`
+38 -3
View File
@@ -56,6 +56,12 @@ export class ShaderBuilder {
*/ */
this.sizeExpression = 'vec2(1.0)'; this.sizeExpression = 'vec2(1.0)';
/**
* @type {string}
* @private
*/
this.rotationExpression = '0.0';
/** /**
* @type {string} * @type {string}
* @private * @private
@@ -138,6 +144,18 @@ export class ShaderBuilder {
return this; 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. * Sets an expression to compute the offset of the symbol from the point center.
* This expression can use all the uniforms and attributes available * This expression can use all the uniforms and attributes available
@@ -291,10 +309,24 @@ ${varyings.map(function(varying) {
}).join('\n')} }).join('\n')}
void main(void) { void main(void) {
mat4 offsetMatrix = ${offsetMatrix}; mat4 offsetMatrix = ${offsetMatrix};
vec2 size = ${this.sizeExpression}; vec2 halfSize = ${this.sizeExpression} * 0.5;
vec2 offset = ${this.offsetExpression}; 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 angle = ${this.rotationExpression};
float offsetY = a_index == 0.0 || a_index == 1.0 ? offset.y - size.y / 2.0 : offset.y + size.y / 2.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); vec4 offsets = offsetMatrix * vec4(offsetX, offsetY, 0.0, 0.0);
gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets; gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets;
vec4 texCoord = ${this.texCoordExpression}; vec4 texCoord = ${this.texCoordExpression};
@@ -381,6 +413,7 @@ export function parseLiteralStyle(style) {
const texCoord = symbStyle.textureCoord || [0, 0, 1, 1]; const texCoord = symbStyle.textureCoord || [0, 0, 1, 1];
const offset = symbStyle.offset || [0, 0]; const offset = symbStyle.offset || [0, 0];
const opacity = symbStyle.opacity !== undefined ? symbStyle.opacity : 1; const opacity = symbStyle.opacity !== undefined ? symbStyle.opacity : 1;
const rotation = symbStyle.rotation !== undefined ? symbStyle.rotation : 0;
/** /**
* @type {import("../style/expressions.js").ParsingContext} * @type {import("../style/expressions.js").ParsingContext}
@@ -406,6 +439,7 @@ export function parseLiteralStyle(style) {
}; };
const parsedColor = expressionToGlsl(fragContext, color, ValueTypes.COLOR); const parsedColor = expressionToGlsl(fragContext, color, ValueTypes.COLOR);
const parsedOpacity = expressionToGlsl(fragContext, opacity, ValueTypes.NUMBER); const parsedOpacity = expressionToGlsl(fragContext, opacity, ValueTypes.NUMBER);
const parsedRotation = expressionToGlsl(fragContext, rotation, ValueTypes.NUMBER);
let opacityFilter = '1.0'; let opacityFilter = '1.0';
const visibleSize = `vec2(${expressionToGlsl(fragContext, size, ValueTypes.NUMBER_ARRAY | ValueTypes.NUMBER)}).x`; const visibleSize = `vec2(${expressionToGlsl(fragContext, size, ValueTypes.NUMBER_ARRAY | ValueTypes.NUMBER)}).x`;
@@ -427,6 +461,7 @@ export function parseLiteralStyle(style) {
const builder = new ShaderBuilder() const builder = new ShaderBuilder()
.setSizeExpression(`vec2(${parsedSize})`) .setSizeExpression(`vec2(${parsedSize})`)
.setRotationExpression(parsedRotation)
.setSymbolOffsetExpression(parsedOffset) .setSymbolOffsetExpression(parsedOffset)
.setTextureCoordinateExpression(parsedTexCoord) .setTextureCoordinateExpression(parsedTexCoord)
.setSymbolRotateWithView(!!symbStyle.rotateWithView) .setSymbolRotateWithView(!!symbStyle.rotateWithView)
+120 -12
View File
@@ -30,10 +30,24 @@ varying float v_opacity;
varying vec3 v_test; varying vec3 v_test;
void main(void) { void main(void) {
mat4 offsetMatrix = u_offsetScaleMatrix; mat4 offsetMatrix = u_offsetScaleMatrix;
vec2 size = vec2(6.0); vec2 halfSize = vec2(6.0) * 0.5;
vec2 offset = vec2(5.0, -7.0); 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 angle = 0.0;
float offsetY = a_index == 0.0 || a_index == 1.0 ? offset.y - size.y / 2.0 : offset.y + size.y / 2.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); vec4 offsets = offsetMatrix * vec4(offsetX, offsetY, 0.0, 0.0);
gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets; gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets;
vec4 texCoord = vec4(0.0, 0.5, 0.5, 1.0); vec4 texCoord = vec4(0.0, 0.5, 0.5, 1.0);
@@ -72,10 +86,24 @@ varying vec2 v_quadCoord;
void main(void) { void main(void) {
mat4 offsetMatrix = u_offsetScaleMatrix; mat4 offsetMatrix = u_offsetScaleMatrix;
vec2 size = vec2(6.0); vec2 halfSize = vec2(6.0) * 0.5;
vec2 offset = vec2(5.0, -7.0); 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 angle = 0.0;
float offsetY = a_index == 0.0 || a_index == 1.0 ? offset.y - size.y / 2.0 : offset.y + size.y / 2.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); vec4 offsets = offsetMatrix * vec4(offsetX, offsetY, 0.0, 0.0);
gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets; gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets;
vec4 texCoord = vec4(0.0, 0.5, 0.5, 1.0); vec4 texCoord = vec4(0.0, 0.5, 0.5, 1.0);
@@ -112,10 +140,24 @@ varying vec2 v_quadCoord;
void main(void) { void main(void) {
mat4 offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix; 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); 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 angle = 0.0;
float offsetY = a_index == 0.0 || a_index == 1.0 ? offset.y - size.y / 2.0 : offset.y + size.y / 2.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); vec4 offsets = offsetMatrix * vec4(offsetX, offsetY, 0.0, 0.0);
gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets; gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets;
vec4 texCoord = vec4(0.0, 0.5, 0.5, 1.0); vec4 texCoord = vec4(0.0, 0.5, 0.5, 1.0);
@@ -147,10 +189,24 @@ varying vec2 v_quadCoord;
varying vec4 v_hitColor; varying vec4 v_hitColor;
void main(void) { void main(void) {
mat4 offsetMatrix = u_offsetScaleMatrix; mat4 offsetMatrix = u_offsetScaleMatrix;
vec2 size = vec2(1.0); vec2 halfSize = vec2(1.0) * 0.5;
vec2 offset = vec2(0.0); 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 angle = 0.0;
float offsetY = a_index == 0.0 || a_index == 1.0 ? offset.y - size.y / 2.0 : offset.y + size.y / 2.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); vec4 offsets = offsetMatrix * vec4(offsetX, offsetY, 0.0, 0.0);
gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets; gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets;
vec4 texCoord = vec4(0.0, 0.0, 1.0, 1.0); 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 = a_index == 2.0 || a_index == 3.0 ? 0.0 : 1.0;
v_quadCoord = vec2(u, v); v_quadCoord = vec2(u, v);
v_hitColor = a_hitColor; 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);
}`); }`);
}); });
}); });