Merge pull request #9994 from jahow/add-webgl-layer-type
Add a new layer type: WebGLPointsLayer
This commit is contained in:
@@ -240,3 +240,12 @@ Support for the `OES_element_index_uint` WebGL extension is mandatory for WebGL
|
|||||||
### 64
|
### 64
|
||||||
|
|
||||||
Layer opacity must be a number.
|
Layer opacity must be a number.
|
||||||
|
|
||||||
|
### 65
|
||||||
|
|
||||||
|
A symbol literal representation must be defined on the style supplied to a `WebGLPointsLayer` instance.
|
||||||
|
|
||||||
|
### 66
|
||||||
|
|
||||||
|
`forEachFeatureAtCoordinate` cannot be used on a WebGL layer if the hit detection logic has not been enabled.
|
||||||
|
This is done by providing adequate shaders using the `hitVertexShader` and `hitFragmentShader` properties of `WebGLPointsLayerRenderer`.
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -7,8 +7,9 @@ import VectorLayer from '../src/ol/layer/Vector.js';
|
|||||||
import {Vector} from '../src/ol/source.js';
|
import {Vector} from '../src/ol/source.js';
|
||||||
import {fromLonLat} from '../src/ol/proj.js';
|
import {fromLonLat} from '../src/ol/proj.js';
|
||||||
import WebGLPointsLayerRenderer from '../src/ol/renderer/webgl/PointsLayer.js';
|
import WebGLPointsLayerRenderer from '../src/ol/renderer/webgl/PointsLayer.js';
|
||||||
import {clamp, lerp} from '../src/ol/math.js';
|
import {clamp} from '../src/ol/math.js';
|
||||||
import Stamen from '../src/ol/source/Stamen.js';
|
import Stamen from '../src/ol/source/Stamen.js';
|
||||||
|
import {formatColor} from '../src/ol/webgl/ShaderBuilder.js';
|
||||||
|
|
||||||
const vectorSource = new Vector({
|
const vectorSource = new Vector({
|
||||||
attributions: 'NASA'
|
attributions: 'NASA'
|
||||||
@@ -36,36 +37,58 @@ updateStatusText();
|
|||||||
class WebglPointsLayer extends VectorLayer {
|
class WebglPointsLayer extends VectorLayer {
|
||||||
createRenderer() {
|
createRenderer() {
|
||||||
return new WebGLPointsLayerRenderer(this, {
|
return new WebGLPointsLayerRenderer(this, {
|
||||||
colorCallback: function(feature, color) {
|
attributes: [
|
||||||
// color is interpolated based on year
|
{
|
||||||
const ratio = clamp((feature.get('year') - 1800) / (2013 - 1800), 0, 1);
|
name: 'size',
|
||||||
|
callback: function(feature) {
|
||||||
|
return 18 * clamp(feature.get('mass') / 200000, 0, 1) + 8;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'year',
|
||||||
|
callback: function(feature) {
|
||||||
|
return feature.get('year');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
vertexShader: [
|
||||||
|
'precision mediump float;',
|
||||||
|
|
||||||
color[0] = lerp(oldColor[0], newColor[0], ratio) / 255;
|
'uniform mat4 u_projectionMatrix;',
|
||||||
color[1] = lerp(oldColor[1], newColor[1], ratio) / 255;
|
'uniform mat4 u_offsetScaleMatrix;',
|
||||||
color[2] = lerp(oldColor[2], newColor[2], ratio) / 255;
|
'uniform mat4 u_offsetRotateMatrix;',
|
||||||
color[3] = 1;
|
'attribute vec2 a_position;',
|
||||||
|
'attribute float a_index;',
|
||||||
|
'attribute float a_size;',
|
||||||
|
'attribute float a_year;',
|
||||||
|
'varying vec2 v_texCoord;',
|
||||||
|
'varying float v_year;',
|
||||||
|
|
||||||
return color;
|
'void main(void) {',
|
||||||
},
|
' mat4 offsetMatrix = u_offsetScaleMatrix;',
|
||||||
sizeCallback: function(feature) {
|
' float offsetX = a_index == 0.0 || a_index == 3.0 ? -a_size / 2.0 : a_size / 2.0;',
|
||||||
return 18 * clamp(feature.get('mass') / 200000, 0, 1) + 8;
|
' float offsetY = a_index == 0.0 || a_index == 1.0 ? -a_size / 2.0 : a_size / 2.0;',
|
||||||
},
|
' vec4 offsets = offsetMatrix * vec4(offsetX, offsetY, 0.0, 0.0);',
|
||||||
|
' gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets;',
|
||||||
|
' float u = a_index == 0.0 || a_index == 3.0 ? 0.0 : 1.0;',
|
||||||
|
' float v = a_index == 0.0 || a_index == 1.0 ? 0.0 : 1.0;',
|
||||||
|
' v_texCoord = vec2(u, v);',
|
||||||
|
' v_year = a_year;',
|
||||||
|
'}'
|
||||||
|
].join(' '),
|
||||||
fragmentShader: [
|
fragmentShader: [
|
||||||
'precision mediump float;',
|
'precision mediump float;',
|
||||||
|
|
||||||
'uniform float u_time;',
|
'uniform float u_time;',
|
||||||
'uniform float u_minYear;',
|
'uniform float u_minYear;',
|
||||||
'uniform float u_maxYear;',
|
'uniform float u_maxYear;',
|
||||||
|
|
||||||
'varying vec2 v_texCoord;',
|
'varying vec2 v_texCoord;',
|
||||||
'varying float v_opacity;',
|
'varying float v_year;',
|
||||||
'varying vec4 v_color;',
|
|
||||||
|
|
||||||
'void main(void) {',
|
'void main(void) {',
|
||||||
' float impactYear = v_opacity;',
|
|
||||||
|
|
||||||
// filter out pixels if the year is outside of the given range
|
// filter out pixels if the year is outside of the given range
|
||||||
' if (impactYear < u_minYear || v_opacity > u_maxYear) {',
|
' if (v_year < u_minYear || v_year > u_maxYear) {',
|
||||||
' discard;',
|
' discard;',
|
||||||
' }',
|
' }',
|
||||||
|
|
||||||
@@ -74,19 +97,19 @@ class WebglPointsLayer extends VectorLayer {
|
|||||||
' float value = 2.0 * (1.0 - sqRadius);',
|
' float value = 2.0 * (1.0 - sqRadius);',
|
||||||
' float alpha = smoothstep(0.0, 1.0, value);',
|
' float alpha = smoothstep(0.0, 1.0, value);',
|
||||||
|
|
||||||
' vec3 color = v_color.rgb;',
|
// color is interpolated based on year
|
||||||
' float period = 8.0;',
|
' float ratio = clamp((v_year - 1800.0) / (2013.0 - 1800.0), 0.0, 1.1);',
|
||||||
' color.g *= 2.0 * (1.0 - sqrt(mod(u_time + impactYear * 0.025, period) / period));',
|
' vec3 color = mix(vec3(' + formatColor(oldColor) + '),',
|
||||||
|
' vec3(' + formatColor(newColor) + '), ratio);',
|
||||||
|
|
||||||
' gl_FragColor = vec4(color, v_color.a);',
|
' float period = 8.0;',
|
||||||
|
' color.g *= 2.0 * (1.0 - sqrt(mod(u_time + v_year * 0.025, period) / period));',
|
||||||
|
|
||||||
|
' gl_FragColor = vec4(color, 1.0);',
|
||||||
' gl_FragColor.a *= alpha;',
|
' gl_FragColor.a *= alpha;',
|
||||||
' gl_FragColor.rgb *= gl_FragColor.a;',
|
' gl_FragColor.rgb *= gl_FragColor.a;',
|
||||||
'}'
|
'}'
|
||||||
].join(' '),
|
].join(' '),
|
||||||
opacityCallback: function(feature) {
|
|
||||||
// here the opacity channel of the vertices is used to store the year of impact
|
|
||||||
return feature.get('year');
|
|
||||||
},
|
|
||||||
uniforms: {
|
uniforms: {
|
||||||
u_time: function() {
|
u_time: function() {
|
||||||
return Date.now() * 0.001 - startTime;
|
return Date.now() * 0.001 - startTime;
|
||||||
|
|||||||
+137
-31
@@ -8,7 +8,7 @@ import VectorLayer from '../src/ol/layer/Vector.js';
|
|||||||
import {Vector} from '../src/ol/source.js';
|
import {Vector} from '../src/ol/source.js';
|
||||||
import {fromLonLat} from '../src/ol/proj.js';
|
import {fromLonLat} from '../src/ol/proj.js';
|
||||||
import WebGLPointsLayerRenderer from '../src/ol/renderer/webgl/PointsLayer.js';
|
import WebGLPointsLayerRenderer from '../src/ol/renderer/webgl/PointsLayer.js';
|
||||||
import {lerp} from '../src/ol/math.js';
|
import {formatColor, formatNumber} from '../src/ol/webgl/ShaderBuilder.js';
|
||||||
|
|
||||||
const key = 'pk.eyJ1IjoidHNjaGF1YiIsImEiOiJjaW5zYW5lNHkxMTNmdWttM3JyOHZtMmNtIn0.CDIBD8H-G2Gf-cPkIuWtRg';
|
const key = 'pk.eyJ1IjoidHNjaGF1YiIsImEiOiJjaW5zYW5lNHkxMTNmdWttM3JyOHZtMmNtIn0.CDIBD8H-G2Gf-cPkIuWtRg';
|
||||||
|
|
||||||
@@ -21,47 +21,153 @@ const texture = new Image();
|
|||||||
texture.src = 'data/ufo_shapes.png';
|
texture.src = 'data/ufo_shapes.png';
|
||||||
|
|
||||||
// This describes the content of the associated sprite sheet
|
// This describes the content of the associated sprite sheet
|
||||||
// coords are u0, v0, u1, v1 for a given shape
|
// coords are u0, v0 for a given shape (all icons have a size of 0.25 x 0.5)
|
||||||
const shapeTextureCoords = {
|
const shapeTextureCoords = {
|
||||||
'light': [0, 0.5, 0.25, 0],
|
'light': [0, 0],
|
||||||
'sphere': [0.25, 0.5, 0.5, 0],
|
'sphere': [0.25, 0],
|
||||||
'circle': [0.25, 0.5, 0.5, 0],
|
'circle': [0.25, 0],
|
||||||
'disc': [0.5, 0.5, 0.75, 0],
|
'disc': [0.5, 0],
|
||||||
'oval': [0.5, 0.5, 0.75, 0],
|
'oval': [0.5, 0],
|
||||||
'triangle': [0.75, 0.5, 1, 0],
|
'triangle': [0.75, 0],
|
||||||
'fireball': [0, 1, 0.25, 0.5],
|
'fireball': [0, 0.5],
|
||||||
'default': [0.75, 1, 1, 0.5]
|
'default': [0.75, 0.5]
|
||||||
};
|
};
|
||||||
|
|
||||||
const oldColor = [255, 160, 110];
|
const oldColor = [255, 160, 110];
|
||||||
const newColor = [180, 255, 200];
|
const newColor = [180, 255, 200];
|
||||||
|
const size = 16;
|
||||||
|
|
||||||
class WebglPointsLayer extends VectorLayer {
|
class WebglPointsLayer extends VectorLayer {
|
||||||
createRenderer() {
|
createRenderer() {
|
||||||
return new WebGLPointsLayerRenderer(this, {
|
return new WebGLPointsLayerRenderer(this, {
|
||||||
texture: texture,
|
attributes: [
|
||||||
colorCallback: function(feature, color) {
|
{
|
||||||
// color is interpolated based on year (min is 1910, max is 2013)
|
name: 'year',
|
||||||
// please note: most values are between 2000-2013
|
callback: function(feature) {
|
||||||
const ratio = (feature.get('year') - 1950) / (2013 - 1950);
|
return feature.get('year');
|
||||||
|
}
|
||||||
color[0] = lerp(oldColor[0], newColor[0], ratio * ratio) / 255;
|
},
|
||||||
color[1] = lerp(oldColor[1], newColor[1], ratio * ratio) / 255;
|
{
|
||||||
color[2] = lerp(oldColor[2], newColor[2], ratio * ratio) / 255;
|
name: 'texCoordU',
|
||||||
color[3] = 1;
|
callback: function(feature) {
|
||||||
|
let coords = shapeTextureCoords[feature.get('shape')];
|
||||||
return color;
|
if (!coords) {
|
||||||
},
|
coords = shapeTextureCoords['default'];
|
||||||
texCoordCallback: function(feature, component) {
|
}
|
||||||
let coords = shapeTextureCoords[feature.get('shape')];
|
return coords[0];
|
||||||
if (!coords) {
|
}
|
||||||
coords = shapeTextureCoords['default'];
|
},
|
||||||
|
{
|
||||||
|
name: 'texCoordV',
|
||||||
|
callback: function(feature) {
|
||||||
|
let coords = shapeTextureCoords[feature.get('shape')];
|
||||||
|
if (!coords) {
|
||||||
|
coords = shapeTextureCoords['default'];
|
||||||
|
}
|
||||||
|
return coords[1];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return coords[component];
|
],
|
||||||
|
uniforms: {
|
||||||
|
u_texture: texture
|
||||||
},
|
},
|
||||||
sizeCallback: function() {
|
vertexShader: [
|
||||||
return 16;
|
'precision mediump float;',
|
||||||
}
|
|
||||||
|
'uniform mat4 u_projectionMatrix;',
|
||||||
|
'uniform mat4 u_offsetScaleMatrix;',
|
||||||
|
'uniform mat4 u_offsetRotateMatrix;',
|
||||||
|
'attribute vec2 a_position;',
|
||||||
|
'attribute float a_index;',
|
||||||
|
'attribute float a_year;',
|
||||||
|
'attribute float a_texCoordU;',
|
||||||
|
'attribute float a_texCoordV;',
|
||||||
|
'varying vec2 v_texCoord;',
|
||||||
|
'varying float v_year;',
|
||||||
|
|
||||||
|
'void main(void) {',
|
||||||
|
' mat4 offsetMatrix = u_offsetScaleMatrix;',
|
||||||
|
' float offsetX = a_index == 0.0 || a_index == 3.0 ? ',
|
||||||
|
' ' + formatNumber(-size / 2) + ' : ' + formatNumber(size / 2) + ';',
|
||||||
|
' float offsetY = a_index == 0.0 || a_index == 1.0 ? ',
|
||||||
|
' ' + formatNumber(-size / 2) + ' : ' + formatNumber(size / 2) + ';',
|
||||||
|
' vec4 offsets = offsetMatrix * vec4(offsetX, offsetY, 0.0, 0.0);',
|
||||||
|
' gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets;',
|
||||||
|
' float u = a_index == 0.0 || a_index == 3.0 ? a_texCoordU : a_texCoordU + 0.25;',
|
||||||
|
' float v = a_index == 2.0 || a_index == 3.0 ? a_texCoordV : a_texCoordV + 0.5;',
|
||||||
|
' v_texCoord = vec2(u, v);',
|
||||||
|
' v_year = a_year;',
|
||||||
|
'}'
|
||||||
|
].join(' '),
|
||||||
|
fragmentShader: [
|
||||||
|
'precision mediump float;',
|
||||||
|
|
||||||
|
'uniform float u_time;',
|
||||||
|
'uniform float u_minYear;',
|
||||||
|
'uniform float u_maxYear;',
|
||||||
|
'uniform sampler2D u_texture;',
|
||||||
|
'varying vec2 v_texCoord;',
|
||||||
|
'varying float v_year;',
|
||||||
|
|
||||||
|
'void main(void) {',
|
||||||
|
' vec4 textureColor = texture2D(u_texture, v_texCoord);',
|
||||||
|
' if (textureColor.a < 0.1) {',
|
||||||
|
' discard;',
|
||||||
|
' }',
|
||||||
|
|
||||||
|
// color is interpolated based on year
|
||||||
|
' float ratio = clamp((v_year - 1950.0) / (2013.0 - 1950.0), 0.0, 1.1);',
|
||||||
|
' vec3 color = mix(vec3(' + formatColor(oldColor) + '),',
|
||||||
|
' vec3(' + formatColor(newColor) + '), ratio);',
|
||||||
|
|
||||||
|
' gl_FragColor = vec4(color, 1.0) * textureColor;',
|
||||||
|
' gl_FragColor.rgb *= gl_FragColor.a;',
|
||||||
|
'}'
|
||||||
|
].join(' '),
|
||||||
|
hitVertexShader: [
|
||||||
|
'precision mediump float;',
|
||||||
|
|
||||||
|
'uniform mat4 u_projectionMatrix;',
|
||||||
|
'uniform mat4 u_offsetScaleMatrix;',
|
||||||
|
'uniform mat4 u_offsetRotateMatrix;',
|
||||||
|
'attribute vec2 a_position;',
|
||||||
|
'attribute float a_index;',
|
||||||
|
'attribute vec4 a_hitColor;',
|
||||||
|
'attribute float a_texCoordU;',
|
||||||
|
'attribute float a_texCoordV;',
|
||||||
|
'varying vec2 v_texCoord;',
|
||||||
|
'varying vec4 v_hitColor;',
|
||||||
|
|
||||||
|
'void main(void) {',
|
||||||
|
' mat4 offsetMatrix = u_offsetScaleMatrix;',
|
||||||
|
' float offsetX = a_index == 0.0 || a_index == 3.0 ? ',
|
||||||
|
' ' + formatNumber(-size / 2) + ' : ' + formatNumber(size / 2) + ';',
|
||||||
|
' float offsetY = a_index == 0.0 || a_index == 1.0 ? ',
|
||||||
|
' ' + formatNumber(-size / 2) + ' : ' + formatNumber(size / 2) + ';',
|
||||||
|
' vec4 offsets = offsetMatrix * vec4(offsetX, offsetY, 0.0, 0.0);',
|
||||||
|
' gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets;',
|
||||||
|
' float u = a_index == 0.0 || a_index == 3.0 ? a_texCoordU : a_texCoordU + 0.25;',
|
||||||
|
' float v = a_index == 2.0 || a_index == 3.0 ? a_texCoordV : a_texCoordV + 0.5;',
|
||||||
|
' v_texCoord = vec2(u, v);',
|
||||||
|
' v_hitColor = a_hitColor;',
|
||||||
|
'}'
|
||||||
|
].join(' '),
|
||||||
|
hitFragmentShader: [
|
||||||
|
'precision mediump float;',
|
||||||
|
|
||||||
|
'uniform sampler2D u_texture;',
|
||||||
|
'varying vec2 v_texCoord;',
|
||||||
|
'varying vec4 v_hitColor;',
|
||||||
|
|
||||||
|
'void main(void) {',
|
||||||
|
' vec4 textureColor = texture2D(u_texture, v_texCoord);',
|
||||||
|
' if (textureColor.a < 0.1) {',
|
||||||
|
' discard;',
|
||||||
|
' }',
|
||||||
|
|
||||||
|
' gl_FragColor = v_hitColor;',
|
||||||
|
'}'
|
||||||
|
].join(' ')
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
layout: example.html
|
||||||
|
title: WebGL points layer
|
||||||
|
shortdesc: Using a WebGL-optimized layer to render a large quantities of points
|
||||||
|
docs: >
|
||||||
|
<p>This example shows how to use a `WebGLPointsLayer` to show a large amount of points on the map.</p>
|
||||||
|
|
||||||
|
tags: "webgl, point, layer, feature"
|
||||||
|
---
|
||||||
|
|
||||||
|
<div id="map" class="map"></div>
|
||||||
|
<label>Current style used for the layer</label>
|
||||||
|
<textarea style="width: 100%; height: 20rem; font-family: monospace; font-size: small;" id="style-editor"></textarea>
|
||||||
|
<small id="style-valid" style="display: none; color: forestgreen">
|
||||||
|
✓ style is valid
|
||||||
|
</small>
|
||||||
|
<small id="style-invalid" style="display: none; color: grey">
|
||||||
|
✗ style not yet valid...
|
||||||
|
</small>
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import Map from '../src/ol/Map.js';
|
||||||
|
import View from '../src/ol/View.js';
|
||||||
|
import TileLayer from '../src/ol/layer/Tile.js';
|
||||||
|
import WebGLPointsLayer from '../src/ol/layer/WebGLPoints.js';
|
||||||
|
import GeoJSON from '../src/ol/format/GeoJSON.js';
|
||||||
|
import Vector from '../src/ol/source/Vector.js';
|
||||||
|
import OSM from '../src/ol/source/OSM.js';
|
||||||
|
|
||||||
|
const vectorSource = new Vector({
|
||||||
|
url: 'data/geojson/world-cities.geojson',
|
||||||
|
format: new GeoJSON()
|
||||||
|
});
|
||||||
|
|
||||||
|
let literalStyle = {
|
||||||
|
symbol: {
|
||||||
|
size: 4,
|
||||||
|
color: '#3388FF',
|
||||||
|
rotateWithView: false,
|
||||||
|
offset: [0, 0],
|
||||||
|
opacity: 1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let pointsLayer;
|
||||||
|
|
||||||
|
const map = new Map({
|
||||||
|
layers: [
|
||||||
|
new TileLayer({
|
||||||
|
source: new OSM()
|
||||||
|
})
|
||||||
|
],
|
||||||
|
target: document.getElementById('map'),
|
||||||
|
view: new View({
|
||||||
|
center: [0, 0],
|
||||||
|
zoom: 2
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const editor = document.getElementById('style-editor');
|
||||||
|
|
||||||
|
function refreshLayer() {
|
||||||
|
if (pointsLayer) {
|
||||||
|
map.removeLayer(pointsLayer);
|
||||||
|
}
|
||||||
|
pointsLayer = new WebGLPointsLayer({
|
||||||
|
source: vectorSource,
|
||||||
|
style: literalStyle
|
||||||
|
});
|
||||||
|
map.addLayer(pointsLayer);
|
||||||
|
editor.value = JSON.stringify(literalStyle, null, ' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
function setStyleStatus(valid) {
|
||||||
|
document.getElementById('style-valid').style.display = valid ? 'initial' : 'none';
|
||||||
|
document.getElementById('style-invalid').style.display = !valid ? 'initial' : 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.addEventListener('input', function() {
|
||||||
|
try {
|
||||||
|
literalStyle = JSON.parse(editor.value);
|
||||||
|
refreshLayer();
|
||||||
|
setStyleStatus(true);
|
||||||
|
} catch (e) {
|
||||||
|
setStyleStatus(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
refreshLayer();
|
||||||
@@ -2,28 +2,23 @@ import Map from '../../../src/ol/Map.js';
|
|||||||
import View from '../../../src/ol/View.js';
|
import View from '../../../src/ol/View.js';
|
||||||
import TileLayer from '../../../src/ol/layer/Tile.js';
|
import TileLayer from '../../../src/ol/layer/Tile.js';
|
||||||
import XYZ from '../../../src/ol/source/XYZ.js';
|
import XYZ from '../../../src/ol/source/XYZ.js';
|
||||||
import {Vector as VectorLayer} from '../../../src/ol/layer.js';
|
|
||||||
import VectorSource from '../../../src/ol/source/Vector.js';
|
import VectorSource from '../../../src/ol/source/Vector.js';
|
||||||
import KML from '../../../src/ol/format/KML.js';
|
import KML from '../../../src/ol/format/KML.js';
|
||||||
import WebGLPointsLayerRenderer from '../../../src/ol/renderer/webgl/PointsLayer.js';
|
import WebGLPointsLayer from '../../../src/ol/layer/WebGLPoints.js';
|
||||||
|
|
||||||
class CustomLayer extends VectorLayer {
|
const vector = new WebGLPointsLayer({
|
||||||
createRenderer() {
|
|
||||||
return new WebGLPointsLayerRenderer(this, {
|
|
||||||
sizeCallback: function() {
|
|
||||||
return 4;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const vector = new CustomLayer({
|
|
||||||
source: new VectorSource({
|
source: new VectorSource({
|
||||||
url: '/data/2012_Earthquakes_Mag5.kml',
|
url: '/data/2012_Earthquakes_Mag5.kml',
|
||||||
format: new KML({
|
format: new KML({
|
||||||
extractStyles: false
|
extractStyles: false
|
||||||
})
|
})
|
||||||
})
|
}),
|
||||||
|
style: {
|
||||||
|
symbol: {
|
||||||
|
size: 4,
|
||||||
|
color: 'white'
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const raster = new TileLayer({
|
const raster = new TileLayer({
|
||||||
|
|||||||
+22
-14
@@ -177,38 +177,47 @@ class Heatmap extends VectorLayer {
|
|||||||
*/
|
*/
|
||||||
createRenderer() {
|
createRenderer() {
|
||||||
return new WebGLPointsLayerRenderer(this, {
|
return new WebGLPointsLayerRenderer(this, {
|
||||||
|
attributes: [
|
||||||
|
{
|
||||||
|
name: 'weight',
|
||||||
|
callback: this.weightFunction_
|
||||||
|
}
|
||||||
|
],
|
||||||
vertexShader: `
|
vertexShader: `
|
||||||
precision mediump float;
|
precision mediump float;
|
||||||
attribute vec2 a_position;
|
|
||||||
attribute vec2 a_texCoord;
|
|
||||||
attribute vec2 a_offsets;
|
|
||||||
attribute float a_opacity;
|
|
||||||
|
|
||||||
uniform mat4 u_projectionMatrix;
|
uniform mat4 u_projectionMatrix;
|
||||||
uniform mat4 u_offsetScaleMatrix;
|
uniform mat4 u_offsetScaleMatrix;
|
||||||
uniform float u_size;
|
uniform float u_size;
|
||||||
|
attribute vec2 a_position;
|
||||||
|
attribute float a_index;
|
||||||
|
attribute float a_weight;
|
||||||
|
|
||||||
varying vec2 v_texCoord;
|
varying vec2 v_texCoord;
|
||||||
varying float v_opacity;
|
varying float v_weight;
|
||||||
|
|
||||||
void main(void) {
|
void main(void) {
|
||||||
vec4 offsets = u_offsetScaleMatrix * vec4(a_offsets, 0.0, 0.0);
|
mat4 offsetMatrix = u_offsetScaleMatrix;
|
||||||
gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets * u_size;
|
float offsetX = a_index == 0.0 || a_index == 3.0 ? -u_size / 2.0 : u_size / 2.0;
|
||||||
v_texCoord = a_texCoord;
|
float offsetY = a_index == 0.0 || a_index == 1.0 ? -u_size / 2.0 : u_size / 2.0;
|
||||||
v_opacity = a_opacity;
|
vec4 offsets = offsetMatrix * vec4(offsetX, offsetY, 0.0, 0.0);
|
||||||
|
gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets;
|
||||||
|
float u = a_index == 0.0 || a_index == 3.0 ? 0.0 : 1.0;
|
||||||
|
float v = a_index == 0.0 || a_index == 1.0 ? 0.0 : 1.0;
|
||||||
|
v_texCoord = vec2(u, v);
|
||||||
|
v_weight = a_weight;
|
||||||
}`,
|
}`,
|
||||||
fragmentShader: `
|
fragmentShader: `
|
||||||
precision mediump float;
|
precision mediump float;
|
||||||
uniform float u_blurSlope;
|
uniform float u_blurSlope;
|
||||||
|
|
||||||
varying vec2 v_texCoord;
|
varying vec2 v_texCoord;
|
||||||
varying float v_opacity;
|
varying float v_weight;
|
||||||
|
|
||||||
void main(void) {
|
void main(void) {
|
||||||
vec2 texCoord = v_texCoord * 2.0 - vec2(1.0, 1.0);
|
vec2 texCoord = v_texCoord * 2.0 - vec2(1.0, 1.0);
|
||||||
float sqRadius = texCoord.x * texCoord.x + texCoord.y * texCoord.y;
|
float sqRadius = texCoord.x * texCoord.x + texCoord.y * texCoord.y;
|
||||||
float value = (1.0 - sqrt(sqRadius)) * u_blurSlope;
|
float value = (1.0 - sqrt(sqRadius)) * u_blurSlope;
|
||||||
float alpha = smoothstep(0.0, 1.0, value) * v_opacity;
|
float alpha = smoothstep(0.0, 1.0, value) * v_weight;
|
||||||
gl_FragColor = vec4(alpha, alpha, alpha, alpha);
|
gl_FragColor = vec4(alpha, alpha, alpha, alpha);
|
||||||
}`,
|
}`,
|
||||||
uniforms: {
|
uniforms: {
|
||||||
@@ -239,8 +248,7 @@ class Heatmap extends VectorLayer {
|
|||||||
u_gradientTexture: this.gradient_
|
u_gradientTexture: this.gradient_
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
opacityCallback: this.weightFunction_
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,133 @@
|
|||||||
|
/**
|
||||||
|
* @module ol/layer/WebGLPoints
|
||||||
|
*/
|
||||||
|
import {assign} from '../obj.js';
|
||||||
|
import WebGLPointsLayerRenderer from '../renderer/webgl/PointsLayer.js';
|
||||||
|
import {
|
||||||
|
formatArray,
|
||||||
|
formatColor,
|
||||||
|
formatNumber,
|
||||||
|
getSymbolFragmentShader,
|
||||||
|
getSymbolVertexShader
|
||||||
|
} from '../webgl/ShaderBuilder.js';
|
||||||
|
import {assert} from '../asserts.js';
|
||||||
|
import {asArray} from '../color.js';
|
||||||
|
import Layer from './Layer.js';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} Options
|
||||||
|
* @property {import('../style/LiteralStyle.js').LiteralStyle} style Literal style to apply to the layer features.
|
||||||
|
* @property {string} [className='ol-layer'] A CSS class name to set to the layer element.
|
||||||
|
* @property {number} [opacity=1] Opacity (0, 1).
|
||||||
|
* @property {boolean} [visible=true] Visibility.
|
||||||
|
* @property {import("../extent.js").Extent} [extent] The bounding extent for layer rendering. The layer will not be
|
||||||
|
* rendered outside of this extent.
|
||||||
|
* @property {number} [zIndex] The z-index for layer rendering. At rendering time, the layers
|
||||||
|
* will be ordered, first by Z-index and then by position. When `undefined`, a `zIndex` of 0 is assumed
|
||||||
|
* for layers that are added to the map's `layers` collection, or `Infinity` when the layer's `setMap()`
|
||||||
|
* method was used.
|
||||||
|
* @property {number} [minResolution] The minimum resolution (inclusive) at which this layer will be
|
||||||
|
* visible.
|
||||||
|
* @property {number} [maxResolution] The maximum resolution (exclusive) below which this layer will
|
||||||
|
* be visible.
|
||||||
|
* @property {import("../source/Vector.js").default} [source] Source.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @classdesc
|
||||||
|
* Layer optimized for rendering large point datasets. Takes a `style` property which
|
||||||
|
* is a serializable JSON object describing how the layer should be rendered.
|
||||||
|
*
|
||||||
|
* Here are a few samples of literal style objects:
|
||||||
|
* ```js
|
||||||
|
* const style = {
|
||||||
|
* symbol: {
|
||||||
|
* symbolType: 'circle',
|
||||||
|
* size: 8,
|
||||||
|
* color: '#33AAFF',
|
||||||
|
* opacity: 0.9
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* const style = {
|
||||||
|
* symbol: {
|
||||||
|
* symbolType: 'image',
|
||||||
|
* offset: [0, 12],
|
||||||
|
* size: [4, 8],
|
||||||
|
* src: '../static/exclamation-mark.png'
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Note that any property set in the options is set as a {@link module:ol/Object~BaseObject}
|
||||||
|
* property on the layer object; for example, setting `title: 'My Title'` in the
|
||||||
|
* options means that `title` is observable, and has get/set accessors.
|
||||||
|
*
|
||||||
|
* @fires import("../render/Event.js").RenderEvent
|
||||||
|
*/
|
||||||
|
class WebGLPointsLayer extends Layer {
|
||||||
|
/**
|
||||||
|
* @param {Options} options Options.
|
||||||
|
*/
|
||||||
|
constructor(options) {
|
||||||
|
const baseOptions = assign({}, options);
|
||||||
|
|
||||||
|
super(baseOptions);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {import('../style/LiteralStyle.js').LiteralStyle}
|
||||||
|
*/
|
||||||
|
this.style = options.style;
|
||||||
|
|
||||||
|
assert(this.style.symbol !== undefined, 65);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
createRenderer() {
|
||||||
|
const symbolStyle = this.style.symbol;
|
||||||
|
const size = Array.isArray(symbolStyle.size) ?
|
||||||
|
formatArray(symbolStyle.size) : formatNumber(symbolStyle.size);
|
||||||
|
const color = symbolStyle.color !== undefined ?
|
||||||
|
(typeof symbolStyle.color === 'string' ? asArray(symbolStyle.color) : symbolStyle.color) :
|
||||||
|
[255, 255, 255, 1];
|
||||||
|
const texCoord = symbolStyle.textureCoord || [0, 0, 1, 1];
|
||||||
|
const offset = symbolStyle.offset || [0, 0];
|
||||||
|
const opacity = symbolStyle.opacity !== undefined ? symbolStyle.opacity : 1;
|
||||||
|
|
||||||
|
/** @type {import('../webgl/ShaderBuilder.js').ShaderParameters} */
|
||||||
|
const params = {
|
||||||
|
uniforms: [],
|
||||||
|
colorExpression: 'vec4(' + formatColor(color) + ') * vec4(1.0, 1.0, 1.0, ' + formatNumber(opacity) + ')',
|
||||||
|
sizeExpression: 'vec2(' + size + ')',
|
||||||
|
offsetExpression: 'vec2(' + formatArray(offset) + ')',
|
||||||
|
texCoordExpression: 'vec4(' + formatArray(texCoord) + ')',
|
||||||
|
rotateWithView: !!symbolStyle.rotateWithView
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @type {Object.<string,import("../webgl/Helper").UniformValue>} */
|
||||||
|
const uniforms = {};
|
||||||
|
|
||||||
|
if (symbolStyle.symbolType === 'image' && symbolStyle.src) {
|
||||||
|
const texture = new Image();
|
||||||
|
texture.src = symbolStyle.src;
|
||||||
|
params.uniforms.push('sampler2D u_texture');
|
||||||
|
params.colorExpression = params.colorExpression +
|
||||||
|
' * texture2D(u_texture, v_texCoord);';
|
||||||
|
uniforms['u_texture'] = texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new WebGLPointsLayerRenderer(this, {
|
||||||
|
vertexShader: getSymbolVertexShader(params),
|
||||||
|
fragmentShader: getSymbolFragmentShader(params),
|
||||||
|
uniforms: uniforms
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WebGLPointsLayer;
|
||||||
@@ -71,6 +71,7 @@ class WebGLLayerRenderer extends LayerRenderer {
|
|||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
disposeInternal() {
|
disposeInternal() {
|
||||||
|
this.helper.disposeInternal();
|
||||||
super.disposeInternal();
|
super.disposeInternal();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,66 +86,13 @@ class WebGLLayerRenderer extends LayerRenderer {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Float32Array} instructions Instructons array in which to write.
|
|
||||||
* @param {number} elementIndex Index from which render instructions will be written.
|
|
||||||
* @param {number} x Point center X coordinate
|
|
||||||
* @param {number} y Point center Y coordinate
|
|
||||||
* @param {number} u0 Left texture coordinate
|
|
||||||
* @param {number} v0 Bottom texture coordinate
|
|
||||||
* @param {number} u1 Right texture coordinate
|
|
||||||
* @param {number} v1 Top texture coordinate
|
|
||||||
* @param {number} size Radius of the point
|
|
||||||
* @param {number} opacity Opacity
|
|
||||||
* @param {boolean} rotateWithView If true, the point will stay aligned with the view
|
|
||||||
* @param {Array<number>} color Array holding red, green, blue, alpha values
|
|
||||||
* @return {number} Index from which the next element should be written
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
export function writePointFeatureInstructions(instructions, elementIndex, x, y, u0, v0, u1, v1, size, opacity, rotateWithView, color) {
|
|
||||||
let i = elementIndex;
|
|
||||||
instructions[i++] = x;
|
|
||||||
instructions[i++] = y;
|
|
||||||
instructions[i++] = u0;
|
|
||||||
instructions[i++] = v0;
|
|
||||||
instructions[i++] = u1;
|
|
||||||
instructions[i++] = v1;
|
|
||||||
instructions[i++] = size;
|
|
||||||
instructions[i++] = opacity;
|
|
||||||
instructions[i++] = rotateWithView ? 1 : 0;
|
|
||||||
instructions[i++] = color[0];
|
|
||||||
instructions[i++] = color[1];
|
|
||||||
instructions[i++] = color[2];
|
|
||||||
instructions[i++] = color[3];
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tmpArray_ = [];
|
const tmpArray_ = [];
|
||||||
const bufferPositions_ = {vertexPosition: 0, indexPosition: 0};
|
const bufferPositions_ = {vertexPosition: 0, indexPosition: 0};
|
||||||
|
|
||||||
export const POINT_INSTRUCTIONS_COUNT = 13;
|
function writePointVertex(buffer, pos, x, y, index) {
|
||||||
export const POINT_VERTEX_STRIDE = 12;
|
|
||||||
|
|
||||||
function writePointVertex(buffer, pos, x, y, offsetX, offsetY, u, v, opacity, rotateWithView, red, green, blue, alpha) {
|
|
||||||
buffer[pos + 0] = x;
|
buffer[pos + 0] = x;
|
||||||
buffer[pos + 1] = y;
|
buffer[pos + 1] = y;
|
||||||
buffer[pos + 2] = offsetX;
|
buffer[pos + 2] = index;
|
||||||
buffer[pos + 3] = offsetY;
|
|
||||||
buffer[pos + 4] = u;
|
|
||||||
buffer[pos + 5] = v;
|
|
||||||
buffer[pos + 6] = opacity;
|
|
||||||
buffer[pos + 7] = rotateWithView;
|
|
||||||
buffer[pos + 8] = red;
|
|
||||||
buffer[pos + 9] = green;
|
|
||||||
buffer[pos + 10] = blue;
|
|
||||||
buffer[pos + 11] = alpha;
|
|
||||||
}
|
|
||||||
|
|
||||||
function writeCustomAttrs(buffer, pos, customAttrs) {
|
|
||||||
if (customAttrs.length) {
|
|
||||||
buffer.set(customAttrs, pos);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -160,42 +108,27 @@ function writeCustomAttrs(buffer, pos, customAttrs) {
|
|||||||
* @param {number} elementIndex Index from which render instructions will be read.
|
* @param {number} elementIndex Index from which render instructions will be read.
|
||||||
* @param {Float32Array} vertexBuffer Buffer in the form of a typed array.
|
* @param {Float32Array} vertexBuffer Buffer in the form of a typed array.
|
||||||
* @param {Uint32Array} indexBuffer Buffer in the form of a typed array.
|
* @param {Uint32Array} indexBuffer Buffer in the form of a typed array.
|
||||||
|
* @param {number} customAttributesCount Amount of custom attributes for each element.
|
||||||
* @param {BufferPositions} [bufferPositions] Buffer write positions; if not specified, positions will be set at 0.
|
* @param {BufferPositions} [bufferPositions] Buffer write positions; if not specified, positions will be set at 0.
|
||||||
* @param {number} [count] Amount of render instructions that will be read. Default value is POINT_INSTRUCTIONS_COUNT
|
|
||||||
* but a higher value can be provided; all values beyond the default count will be put in the vertices buffer as
|
|
||||||
* is, thus allowing specifying custom attributes. Please note: this value should not vary inside the same buffer or
|
|
||||||
* rendering will break.
|
|
||||||
* @return {BufferPositions} New buffer positions where to write next
|
* @return {BufferPositions} New buffer positions where to write next
|
||||||
* @property {number} vertexPosition New position in the vertex buffer where future writes should start.
|
* @property {number} vertexPosition New position in the vertex buffer where future writes should start.
|
||||||
* @property {number} indexPosition New position in the index buffer where future writes should start.
|
* @property {number} indexPosition New position in the index buffer where future writes should start.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
export function writePointFeatureToBuffers(instructions, elementIndex, vertexBuffer, indexBuffer, bufferPositions, count) {
|
export function writePointFeatureToBuffers(instructions, elementIndex, vertexBuffer, indexBuffer, customAttributesCount, bufferPositions) {
|
||||||
const count_ = count > POINT_INSTRUCTIONS_COUNT ? count : POINT_INSTRUCTIONS_COUNT;
|
// This is for x, y and index
|
||||||
|
const baseVertexAttrsCount = 3;
|
||||||
|
const baseInstructionsCount = 2;
|
||||||
|
const stride = baseVertexAttrsCount + customAttributesCount;
|
||||||
|
|
||||||
const x = instructions[elementIndex + 0];
|
const x = instructions[elementIndex + 0];
|
||||||
const y = instructions[elementIndex + 1];
|
const y = instructions[elementIndex + 1];
|
||||||
const u0 = instructions[elementIndex + 2];
|
|
||||||
const v0 = instructions[elementIndex + 3];
|
|
||||||
const u1 = instructions[elementIndex + 4];
|
|
||||||
const v1 = instructions[elementIndex + 5];
|
|
||||||
const size = instructions[elementIndex + 6];
|
|
||||||
const opacity = instructions[elementIndex + 7];
|
|
||||||
const rotateWithView = instructions[elementIndex + 8];
|
|
||||||
const red = instructions[elementIndex + 9];
|
|
||||||
const green = instructions[elementIndex + 10];
|
|
||||||
const blue = instructions[elementIndex + 11];
|
|
||||||
const alpha = instructions[elementIndex + 12];
|
|
||||||
|
|
||||||
// the default vertex buffer stride is 12, plus additional custom values if any
|
|
||||||
const baseStride = POINT_VERTEX_STRIDE;
|
|
||||||
const stride = baseStride + count_ - POINT_INSTRUCTIONS_COUNT;
|
|
||||||
|
|
||||||
// read custom numerical attributes on the feature
|
// read custom numerical attributes on the feature
|
||||||
const customAttrs = tmpArray_;
|
const customAttrs = tmpArray_;
|
||||||
customAttrs.length = count_ - POINT_INSTRUCTIONS_COUNT;
|
customAttrs.length = customAttributesCount;
|
||||||
for (let i = 0; i < customAttrs.length; i++) {
|
for (let i = 0; i < customAttrs.length; i++) {
|
||||||
customAttrs[i] = instructions[elementIndex + POINT_INSTRUCTIONS_COUNT + i];
|
customAttrs[i] = instructions[elementIndex + baseInstructionsCount + i];
|
||||||
}
|
}
|
||||||
|
|
||||||
let vPos = bufferPositions ? bufferPositions.vertexPosition : 0;
|
let vPos = bufferPositions ? bufferPositions.vertexPosition : 0;
|
||||||
@@ -203,20 +136,20 @@ export function writePointFeatureToBuffers(instructions, elementIndex, vertexBuf
|
|||||||
const baseIndex = vPos / stride;
|
const baseIndex = vPos / stride;
|
||||||
|
|
||||||
// push vertices for each of the four quad corners (first standard then custom attributes)
|
// push vertices for each of the four quad corners (first standard then custom attributes)
|
||||||
writePointVertex(vertexBuffer, vPos, x, y, -size / 2, -size / 2, u0, v0, opacity, rotateWithView, red, green, blue, alpha);
|
writePointVertex(vertexBuffer, vPos, x, y, 0);
|
||||||
writeCustomAttrs(vertexBuffer, vPos + baseStride, customAttrs);
|
customAttrs.length && vertexBuffer.set(customAttrs, vPos + baseVertexAttrsCount);
|
||||||
vPos += stride;
|
vPos += stride;
|
||||||
|
|
||||||
writePointVertex(vertexBuffer, vPos, x, y, +size / 2, -size / 2, u1, v0, opacity, rotateWithView, red, green, blue, alpha);
|
writePointVertex(vertexBuffer, vPos, x, y, 1);
|
||||||
writeCustomAttrs(vertexBuffer, vPos + baseStride, customAttrs);
|
customAttrs.length && vertexBuffer.set(customAttrs, vPos + baseVertexAttrsCount);
|
||||||
vPos += stride;
|
vPos += stride;
|
||||||
|
|
||||||
writePointVertex(vertexBuffer, vPos, x, y, +size / 2, +size / 2, u1, v1, opacity, rotateWithView, red, green, blue, alpha);
|
writePointVertex(vertexBuffer, vPos, x, y, 2);
|
||||||
writeCustomAttrs(vertexBuffer, vPos + baseStride, customAttrs);
|
customAttrs.length && vertexBuffer.set(customAttrs, vPos + baseVertexAttrsCount);
|
||||||
vPos += stride;
|
vPos += stride;
|
||||||
|
|
||||||
writePointVertex(vertexBuffer, vPos, x, y, -size / 2, +size / 2, u0, v1, opacity, rotateWithView, red, green, blue, alpha);
|
writePointVertex(vertexBuffer, vPos, x, y, 3);
|
||||||
writeCustomAttrs(vertexBuffer, vPos + baseStride, customAttrs);
|
customAttrs.length && vertexBuffer.set(customAttrs, vPos + baseVertexAttrsCount);
|
||||||
vPos += stride;
|
vPos += stride;
|
||||||
|
|
||||||
indexBuffer[iPos++] = baseIndex; indexBuffer[iPos++] = baseIndex + 1; indexBuffer[iPos++] = baseIndex + 3;
|
indexBuffer[iPos++] = baseIndex; indexBuffer[iPos++] = baseIndex + 1; indexBuffer[iPos++] = baseIndex + 3;
|
||||||
|
|||||||
@@ -2,121 +2,42 @@
|
|||||||
* @module ol/renderer/webgl/PointsLayer
|
* @module ol/renderer/webgl/PointsLayer
|
||||||
*/
|
*/
|
||||||
import WebGLArrayBuffer from '../../webgl/Buffer.js';
|
import WebGLArrayBuffer from '../../webgl/Buffer.js';
|
||||||
import {DYNAMIC_DRAW, ARRAY_BUFFER, ELEMENT_ARRAY_BUFFER, FLOAT} from '../../webgl.js';
|
import {ARRAY_BUFFER, DYNAMIC_DRAW, ELEMENT_ARRAY_BUFFER} from '../../webgl.js';
|
||||||
import {DefaultAttrib, DefaultUniform} from '../../webgl/Helper.js';
|
import {AttributeType, DefaultUniform} from '../../webgl/Helper.js';
|
||||||
import GeometryType from '../../geom/GeometryType.js';
|
import GeometryType from '../../geom/GeometryType.js';
|
||||||
import WebGLLayerRenderer, {
|
import WebGLLayerRenderer, {colorDecodeId, colorEncodeId, WebGLWorkerMessageType} from './Layer.js';
|
||||||
colorDecodeId,
|
|
||||||
colorEncodeId,
|
|
||||||
getBlankImageData,
|
|
||||||
POINT_INSTRUCTIONS_COUNT, POINT_VERTEX_STRIDE, WebGLWorkerMessageType,
|
|
||||||
writePointFeatureInstructions
|
|
||||||
} from './Layer.js';
|
|
||||||
import ViewHint from '../../ViewHint.js';
|
import ViewHint from '../../ViewHint.js';
|
||||||
import {createEmpty, equals} from '../../extent.js';
|
import {createEmpty, equals} from '../../extent.js';
|
||||||
import {
|
import {
|
||||||
|
apply as applyTransform,
|
||||||
create as createTransform,
|
create as createTransform,
|
||||||
makeInverse as makeInverseTransform,
|
makeInverse as makeInverseTransform,
|
||||||
multiply as multiplyTransform,
|
multiply as multiplyTransform
|
||||||
apply as applyTransform
|
|
||||||
} from '../../transform.js';
|
} from '../../transform.js';
|
||||||
import {create as createWebGLWorker} from '../../worker/webgl.js';
|
import {create as createWebGLWorker} from '../../worker/webgl.js';
|
||||||
import {getUid} from '../../util.js';
|
import {getUid} from '../../util.js';
|
||||||
import WebGLRenderTarget from '../../webgl/RenderTarget.js';
|
import WebGLRenderTarget from '../../webgl/RenderTarget.js';
|
||||||
|
import {assert} from '../../asserts.js';
|
||||||
|
|
||||||
const VERTEX_SHADER = `
|
/**
|
||||||
precision mediump float;
|
* @typedef {Object} CustomAttribute A description of a custom attribute to be passed on to the GPU, with a value different
|
||||||
attribute vec2 a_position;
|
* for each feature.
|
||||||
attribute vec2 a_texCoord;
|
* @property {string} name Attribute name.
|
||||||
attribute float a_rotateWithView;
|
* @property {function(import("../../Feature").default):number} callback This callback computes the numerical value of the
|
||||||
attribute vec2 a_offsets;
|
* attribute for a given feature.
|
||||||
attribute float a_opacity;
|
*/
|
||||||
attribute vec4 a_color;
|
|
||||||
|
|
||||||
uniform mat4 u_projectionMatrix;
|
|
||||||
uniform mat4 u_offsetScaleMatrix;
|
|
||||||
uniform mat4 u_offsetRotateMatrix;
|
|
||||||
|
|
||||||
varying vec2 v_texCoord;
|
|
||||||
varying float v_opacity;
|
|
||||||
varying vec4 v_color;
|
|
||||||
|
|
||||||
void main(void) {
|
|
||||||
mat4 offsetMatrix = u_offsetScaleMatrix;
|
|
||||||
if (a_rotateWithView == 1.0) {
|
|
||||||
offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;
|
|
||||||
}
|
|
||||||
vec4 offsets = offsetMatrix * vec4(a_offsets, 0.0, 0.0);
|
|
||||||
gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets;
|
|
||||||
v_texCoord = a_texCoord;
|
|
||||||
v_opacity = a_opacity;
|
|
||||||
v_color = a_color;
|
|
||||||
}`;
|
|
||||||
|
|
||||||
const FRAGMENT_SHADER = `
|
|
||||||
precision mediump float;
|
|
||||||
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
|
|
||||||
varying vec2 v_texCoord;
|
|
||||||
varying float v_opacity;
|
|
||||||
varying vec4 v_color;
|
|
||||||
|
|
||||||
void main(void) {
|
|
||||||
if (v_opacity == 0.0) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
vec4 textureColor = texture2D(u_texture, v_texCoord);
|
|
||||||
gl_FragColor = v_color * textureColor;
|
|
||||||
gl_FragColor.a *= v_opacity;
|
|
||||||
gl_FragColor.rgb *= gl_FragColor.a;
|
|
||||||
}`;
|
|
||||||
|
|
||||||
const HIT_FRAGMENT_SHADER = `
|
|
||||||
precision mediump float;
|
|
||||||
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
|
|
||||||
varying vec2 v_texCoord;
|
|
||||||
varying float v_opacity;
|
|
||||||
varying vec4 v_color;
|
|
||||||
|
|
||||||
void main(void) {
|
|
||||||
if (v_opacity == 0.0) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
vec4 textureColor = texture2D(u_texture, v_texCoord);
|
|
||||||
if (textureColor.a < 0.1) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
gl_FragColor = v_color;
|
|
||||||
}`;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} Options
|
* @typedef {Object} Options
|
||||||
* @property {function(import("../../Feature").default):number} [sizeCallback] Will be called on every feature in the
|
* @property {Array<CustomAttribute>} [attributes] These attributes will be read from the features in the source and then
|
||||||
* source to compute the size of the quad on screen (in pixels). This is only done on source change.
|
* passed to the GPU. The `name` property of each attribute will serve as its identifier:
|
||||||
* @property {function(import("../../Feature").default, number):number} [coordCallback] Will be called on every feature in the
|
* * In the vertex shader as an `attribute` by prefixing it with `a_`
|
||||||
* source to compute the coordinate of the quad center on screen (in pixels). This is only done on source change.
|
* * In the fragment shader as a `varying` by prefixing it with `v_`
|
||||||
* The second argument is 0 for `x` component and 1 for `y`.
|
* Please note that these can only be numerical values.
|
||||||
* @property {function(import("../../Feature").default, number):number} [texCoordCallback] Will be called on every feature in the
|
* @property {string} vertexShader Vertex shader source, mandatory.
|
||||||
* source to compute the texture coordinates of each corner of the quad (without effect if no `texture` option defined). This is only done on source change.
|
* @property {string} fragmentShader Fragment shader source, mandatory.
|
||||||
* The second argument is 0 for `u0` component, 1 for `v0`, 2 for `u1`, and 3 for `v1`.
|
* @property {string} [hitVertexShader] Vertex shader source for hit detection rendering.
|
||||||
* @property {function(import("../../Feature").default, Array<number>=):Array<number>} [colorCallback] Will be called on every feature in the
|
* @property {string} [hitFragmentShader] Fragment shader source for hit detection rendering.
|
||||||
* source to compute the color for use in the fragment shader (available as the `v_color` varying). This is only done on source change.
|
|
||||||
* The return value should be between an array of R, G, B, A values between 0 and 1. To reduce unnecessary
|
|
||||||
* allocation, the function is called with a reusable array that can serve as the return value after updating
|
|
||||||
* the R, G, B, and A values.
|
|
||||||
* @property {function(import("../../Feature").default):number} [opacityCallback] Will be called on every feature in the
|
|
||||||
* source to compute the opacity of the quad on screen (from 0 to 1). This is only done on source change.
|
|
||||||
* Note: this is multiplied with the color of the point which can also have an alpha value < 1.
|
|
||||||
* @property {function(import("../../Feature").default):boolean} [rotateWithViewCallback] Will be called on every feature in the
|
|
||||||
* source to compute whether the quad on screen must stay upwards (`false`) or follow the view rotation (`true`). Default is `false`.
|
|
||||||
* This is only done on source change.
|
|
||||||
* @property {HTMLCanvasElement|HTMLImageElement|ImageData} [texture] Texture to use on points. `texCoordCallback` and `sizeCallback`
|
|
||||||
* must be defined for this to have any effect.
|
|
||||||
* @property {string} [vertexShader] Vertex shader source
|
|
||||||
* @property {string} [fragmentShader] Fragment shader source
|
|
||||||
* @property {Object.<string,import("../../webgl/Helper").UniformValue>} [uniforms] Uniform definitions for the post process steps
|
* @property {Object.<string,import("../../webgl/Helper").UniformValue>} [uniforms] Uniform definitions for the post process steps
|
||||||
* Please note that `u_texture` is reserved for the main texture slot.
|
* Please note that `u_texture` is reserved for the main texture slot.
|
||||||
* @property {Array<import("./Layer").PostProcessesOptions>} [postProcesses] Post-processes definitions
|
* @property {Array<import("./Layer").PostProcessesOptions>} [postProcesses] Post-processes definitions
|
||||||
@@ -128,13 +49,38 @@ const HIT_FRAGMENT_SHADER = `
|
|||||||
* All features will be rendered as quads (two triangles forming a square). New data will be flushed to the GPU
|
* All features will be rendered as quads (two triangles forming a square). New data will be flushed to the GPU
|
||||||
* every time the vector source changes.
|
* every time the vector source changes.
|
||||||
*
|
*
|
||||||
* Use shaders to customize the final output. The following attributes are available:
|
* You need to provide vertex and fragment shaders for rendering. This can be done using
|
||||||
* * `vec2 a_position`
|
* {@link module:ol/webgl/ShaderBuilder} utilities. These shaders shall expect a `a_position` attribute
|
||||||
* * `vec2 a_texCoord`
|
* containing the screen-space projected center of the quad, as well as a `a_index` attribute
|
||||||
* * `vec2 a_offsets`
|
* whose value (0, 1, 2 or 3) indicates which quad vertex is currently getting processed (see structure below).
|
||||||
* * `float a_rotateWithView`
|
*
|
||||||
* * `float a_opacity`
|
* To include variable attributes in the shaders, you need to declare them using the `attributes` property of
|
||||||
* * `float a_color`
|
* the options object like so:
|
||||||
|
* ```js
|
||||||
|
* new WebGLPointsLayerRenderer(layer, {
|
||||||
|
* attributes: [
|
||||||
|
* {
|
||||||
|
* name: 'size',
|
||||||
|
* callback: function(feature) {
|
||||||
|
* // compute something with the feature
|
||||||
|
* }
|
||||||
|
* },
|
||||||
|
* {
|
||||||
|
* name: 'weight',
|
||||||
|
* callback: function(feature) {
|
||||||
|
* // compute something with the feature
|
||||||
|
* }
|
||||||
|
* },
|
||||||
|
* ],
|
||||||
|
* vertexShader:
|
||||||
|
* // shader using attribute a_weight and a_size
|
||||||
|
* fragmentShader:
|
||||||
|
* // shader using varying v_weight and v_size
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* To enable hit detection, you must as well provide dedicated shaders using the `hitVertexShader`
|
||||||
|
* and `hitFragmentShader` properties. These shall expect the `a_hitColor` attribute to contain
|
||||||
|
* the final color that will have to be output for hit detection to work.
|
||||||
*
|
*
|
||||||
* The following uniform is used for the main texture: `u_texture`.
|
* The following uniform is used for the main texture: `u_texture`.
|
||||||
*
|
*
|
||||||
@@ -157,74 +103,16 @@ const HIT_FRAGMENT_SHADER = `
|
|||||||
*
|
*
|
||||||
* This uses {@link module:ol/webgl/Helper~WebGLHelper} internally.
|
* This uses {@link module:ol/webgl/Helper~WebGLHelper} internally.
|
||||||
*
|
*
|
||||||
* Default shaders are shown hereafter:
|
|
||||||
*
|
|
||||||
* * Vertex shader:
|
|
||||||
* ```
|
|
||||||
* precision mediump float;
|
|
||||||
*
|
|
||||||
* attribute vec2 a_position;
|
|
||||||
* attribute vec2 a_texCoord;
|
|
||||||
* attribute float a_rotateWithView;
|
|
||||||
* attribute vec2 a_offsets;
|
|
||||||
* attribute float a_opacity;
|
|
||||||
* attribute vec4 a_color;
|
|
||||||
*
|
|
||||||
* uniform mat4 u_projectionMatrix;
|
|
||||||
* uniform mat4 u_offsetScaleMatrix;
|
|
||||||
* uniform mat4 u_offsetRotateMatrix;
|
|
||||||
*
|
|
||||||
* varying vec2 v_texCoord;
|
|
||||||
* varying float v_opacity;
|
|
||||||
* varying vec4 v_color;
|
|
||||||
*
|
|
||||||
* void main(void) {
|
|
||||||
* mat4 offsetMatrix = u_offsetScaleMatrix;
|
|
||||||
* if (a_rotateWithView == 1.0) {
|
|
||||||
* offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;
|
|
||||||
* }
|
|
||||||
* vec4 offsets = offsetMatrix * vec4(a_offsets, 0.0, 0.0);
|
|
||||||
* gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets;
|
|
||||||
* v_texCoord = a_texCoord;
|
|
||||||
* v_opacity = a_opacity;
|
|
||||||
* v_color = a_color;
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* * Fragment shader:
|
|
||||||
* ```
|
|
||||||
* precision mediump float;
|
|
||||||
*
|
|
||||||
* uniform sampler2D u_texture;
|
|
||||||
*
|
|
||||||
* varying vec2 v_texCoord;
|
|
||||||
* varying float v_opacity;
|
|
||||||
* varying vec4 v_color;
|
|
||||||
*
|
|
||||||
* void main(void) {
|
|
||||||
* if (v_opacity == 0.0) {
|
|
||||||
* discard;
|
|
||||||
* }
|
|
||||||
* vec4 textureColor = texture2D(u_texture, v_texCoord);
|
|
||||||
* gl_FragColor = v_color * textureColor;
|
|
||||||
* gl_FragColor.a *= v_opacity;
|
|
||||||
* gl_FragColor.rgb *= gl_FragColor.a;
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* @api
|
* @api
|
||||||
*/
|
*/
|
||||||
class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
|
class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import("../../layer/Vector.js").default} vectorLayer Vector layer.
|
* @param {import("../../layer/Vector.js").default} vectorLayer Vector layer.
|
||||||
* @param {Options=} [opt_options] Options.
|
* @param {Options=} options Options.
|
||||||
*/
|
*/
|
||||||
constructor(vectorLayer, opt_options) {
|
constructor(vectorLayer, options) {
|
||||||
const options = opt_options || {};
|
|
||||||
|
|
||||||
const uniforms = options.uniforms || {};
|
const uniforms = options.uniforms || {};
|
||||||
uniforms.u_texture = options.texture || getBlankImageData();
|
|
||||||
const projectionMatrixTransform = createTransform();
|
const projectionMatrixTransform = createTransform();
|
||||||
uniforms[DefaultUniform.PROJECTION_MATRIX] = projectionMatrixTransform;
|
uniforms[DefaultUniform.PROJECTION_MATRIX] = projectionMatrixTransform;
|
||||||
|
|
||||||
@@ -240,36 +128,68 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
|
|||||||
this.indicesBuffer_ = new WebGLArrayBuffer(ELEMENT_ARRAY_BUFFER, DYNAMIC_DRAW);
|
this.indicesBuffer_ = new WebGLArrayBuffer(ELEMENT_ARRAY_BUFFER, DYNAMIC_DRAW);
|
||||||
|
|
||||||
this.program_ = this.helper.getProgram(
|
this.program_ = this.helper.getProgram(
|
||||||
options.fragmentShader || FRAGMENT_SHADER,
|
options.fragmentShader,
|
||||||
options.vertexShader || VERTEX_SHADER
|
options.vertexShader
|
||||||
);
|
|
||||||
this.hitProgram_ = this.helper.getProgram(
|
|
||||||
HIT_FRAGMENT_SHADER,
|
|
||||||
VERTEX_SHADER
|
|
||||||
);
|
);
|
||||||
|
|
||||||
this.sizeCallback_ = options.sizeCallback || function() {
|
/**
|
||||||
return 1;
|
* @type {boolean}
|
||||||
};
|
* @private
|
||||||
this.coordCallback_ = options.coordCallback || function(feature, index) {
|
*/
|
||||||
const geom = feature.getGeometry();
|
this.hitDetectionEnabled_ = options.hitFragmentShader && options.hitVertexShader ? true : false;
|
||||||
return geom.getCoordinates()[index];
|
|
||||||
};
|
|
||||||
this.opacityCallback_ = options.opacityCallback || function() {
|
|
||||||
return 1;
|
|
||||||
};
|
|
||||||
this.texCoordCallback_ = options.texCoordCallback || function(feature, index) {
|
|
||||||
return index < 2 ? 0 : 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
this.colorArray_ = [1, 1, 1, 1];
|
this.hitProgram_ = this.hitDetectionEnabled_ && this.helper.getProgram(
|
||||||
this.colorCallback_ = options.colorCallback || function(feature, color) {
|
options.hitFragmentShader,
|
||||||
return this.colorArray_;
|
options.hitVertexShader
|
||||||
};
|
);
|
||||||
|
|
||||||
this.rotateWithViewCallback_ = options.rotateWithViewCallback || function() {
|
const customAttributes = options.attributes ?
|
||||||
return false;
|
options.attributes.map(function(attribute) {
|
||||||
};
|
return {
|
||||||
|
name: 'a_' + attribute.name,
|
||||||
|
size: 1,
|
||||||
|
type: AttributeType.FLOAT
|
||||||
|
};
|
||||||
|
}) : [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of attributes used by the renderer. By default only the position and
|
||||||
|
* index of the vertex (0 to 3) are required.
|
||||||
|
* @type {Array<import('../../webgl/Helper.js').AttributeDescription>}
|
||||||
|
*/
|
||||||
|
this.attributes = [{
|
||||||
|
name: 'a_position',
|
||||||
|
size: 2,
|
||||||
|
type: AttributeType.FLOAT
|
||||||
|
}, {
|
||||||
|
name: 'a_index',
|
||||||
|
size: 1,
|
||||||
|
type: AttributeType.FLOAT
|
||||||
|
}].concat(customAttributes);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of attributes used for hit detection.
|
||||||
|
* @type {Array<import('../../webgl/Helper.js').AttributeDescription>}
|
||||||
|
*/
|
||||||
|
this.hitDetectionAttributes = [{
|
||||||
|
name: 'a_position',
|
||||||
|
size: 2,
|
||||||
|
type: AttributeType.FLOAT
|
||||||
|
}, {
|
||||||
|
name: 'a_index',
|
||||||
|
size: 1,
|
||||||
|
type: AttributeType.FLOAT
|
||||||
|
}, {
|
||||||
|
name: 'a_hitColor',
|
||||||
|
size: 4,
|
||||||
|
type: AttributeType.FLOAT
|
||||||
|
}, {
|
||||||
|
name: 'a_featureUid',
|
||||||
|
size: 1,
|
||||||
|
type: AttributeType.FLOAT
|
||||||
|
}].concat(customAttributes);
|
||||||
|
|
||||||
|
this.customAttributes = options.attributes ? options.attributes : [];
|
||||||
|
|
||||||
this.previousExtent_ = createEmpty();
|
this.previousExtent_ = createEmpty();
|
||||||
|
|
||||||
@@ -312,7 +232,7 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
|
|||||||
* @type {WebGLRenderTarget}
|
* @type {WebGLRenderTarget}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
this.hitRenderTarget_ = new WebGLRenderTarget(this.helper);
|
this.hitRenderTarget_ = this.hitDetectionEnabled_ && new WebGLRenderTarget(this.helper);
|
||||||
|
|
||||||
this.worker_ = createWebGLWorker();
|
this.worker_ = createWebGLWorker();
|
||||||
this.worker_.addEventListener('message', function(event) {
|
this.worker_.addEventListener('message', function(event) {
|
||||||
@@ -357,8 +277,10 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
|
|||||||
canvas.style.opacity = opacity;
|
canvas.style.opacity = opacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.renderHitDetection(frameState);
|
if (this.hitDetectionEnabled_) {
|
||||||
this.hitRenderTarget_.clearCachedData();
|
this.renderHitDetection(frameState);
|
||||||
|
this.hitRenderTarget_.clearCachedData();
|
||||||
|
}
|
||||||
|
|
||||||
return canvas;
|
return canvas;
|
||||||
}
|
}
|
||||||
@@ -371,8 +293,6 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
|
|||||||
const vectorSource = vectorLayer.getSource();
|
const vectorSource = vectorLayer.getSource();
|
||||||
const viewState = frameState.viewState;
|
const viewState = frameState.viewState;
|
||||||
|
|
||||||
const stride = POINT_VERTEX_STRIDE;
|
|
||||||
|
|
||||||
// the source has changed: clear the feature cache & reload features
|
// the source has changed: clear the feature cache & reload features
|
||||||
const sourceChanged = this.sourceRevision_ < vectorSource.getRevision();
|
const sourceChanged = this.sourceRevision_ < vectorSource.getRevision();
|
||||||
if (sourceChanged) {
|
if (sourceChanged) {
|
||||||
@@ -401,13 +321,7 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
|
|||||||
this.helper.bindBuffer(this.verticesBuffer_);
|
this.helper.bindBuffer(this.verticesBuffer_);
|
||||||
this.helper.bindBuffer(this.indicesBuffer_);
|
this.helper.bindBuffer(this.indicesBuffer_);
|
||||||
|
|
||||||
const bytesPerFloat = Float32Array.BYTES_PER_ELEMENT;
|
this.helper.enableAttributes(this.attributes);
|
||||||
this.helper.enableAttributeArray(DefaultAttrib.POSITION, 2, FLOAT, bytesPerFloat * stride, 0);
|
|
||||||
this.helper.enableAttributeArray(DefaultAttrib.OFFSETS, 2, FLOAT, bytesPerFloat * stride, bytesPerFloat * 2);
|
|
||||||
this.helper.enableAttributeArray(DefaultAttrib.TEX_COORD, 2, FLOAT, bytesPerFloat * stride, bytesPerFloat * 4);
|
|
||||||
this.helper.enableAttributeArray(DefaultAttrib.OPACITY, 1, FLOAT, bytesPerFloat * stride, bytesPerFloat * 6);
|
|
||||||
this.helper.enableAttributeArray(DefaultAttrib.ROTATE_WITH_VIEW, 1, FLOAT, bytesPerFloat * stride, bytesPerFloat * 7);
|
|
||||||
this.helper.enableAttributeArray(DefaultAttrib.COLOR, 4, FLOAT, bytesPerFloat * stride, bytesPerFloat * 8);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -426,76 +340,72 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
|
|||||||
this.helper.makeProjectionTransform(frameState, projectionTransform);
|
this.helper.makeProjectionTransform(frameState, projectionTransform);
|
||||||
|
|
||||||
const features = vectorSource.getFeatures();
|
const features = vectorSource.getFeatures();
|
||||||
const totalInstructionsCount = POINT_INSTRUCTIONS_COUNT * features.length;
|
|
||||||
|
// here we anticipate the amount of render instructions that we well generate
|
||||||
|
// this can be done since we know that for normal render we only have x, y as base instructions,
|
||||||
|
// and x, y, r, g, b, a and featureUid for hit render instructions
|
||||||
|
// and we also know the amount of custom attributes to append to these
|
||||||
|
const totalInstructionsCount = (2 + this.customAttributes.length) * features.length;
|
||||||
if (!this.renderInstructions_ || this.renderInstructions_.length !== totalInstructionsCount) {
|
if (!this.renderInstructions_ || this.renderInstructions_.length !== totalInstructionsCount) {
|
||||||
this.renderInstructions_ = new Float32Array(totalInstructionsCount);
|
this.renderInstructions_ = new Float32Array(totalInstructionsCount);
|
||||||
}
|
}
|
||||||
if (!this.hitRenderInstructions_ || this.hitRenderInstructions_.length !== totalInstructionsCount) {
|
if (this.hitDetectionEnabled_) {
|
||||||
this.hitRenderInstructions_ = new Float32Array(totalInstructionsCount);
|
const totalHitInstructionsCount = (7 + this.customAttributes.length) * features.length;
|
||||||
|
if (!this.hitRenderInstructions_ || this.hitRenderInstructions_.length !== totalHitInstructionsCount) {
|
||||||
|
this.hitRenderInstructions_ = new Float32Array(totalHitInstructionsCount);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// loop on features to fill the buffer
|
// loop on features to fill the buffer
|
||||||
let feature;
|
let feature;
|
||||||
const tmpCoords = [];
|
const tmpCoords = [];
|
||||||
const tmpColor = [];
|
const tmpColor = [];
|
||||||
let elementIndex = 0;
|
let renderIndex = 0;
|
||||||
let u0, v0, u1, v1, size, opacity, rotateWithView, color;
|
let hitIndex = 0;
|
||||||
|
let hitColor;
|
||||||
for (let i = 0; i < features.length; i++) {
|
for (let i = 0; i < features.length; i++) {
|
||||||
feature = features[i];
|
feature = features[i];
|
||||||
if (!feature.getGeometry() || feature.getGeometry().getType() !== GeometryType.POINT) {
|
if (!feature.getGeometry() || feature.getGeometry().getType() !== GeometryType.POINT) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpCoords[0] = this.coordCallback_(feature, 0);
|
tmpCoords[0] = feature.getGeometry().getFlatCoordinates()[0];
|
||||||
tmpCoords[1] = this.coordCallback_(feature, 1);
|
tmpCoords[1] = feature.getGeometry().getFlatCoordinates()[1];
|
||||||
applyTransform(projectionTransform, tmpCoords);
|
applyTransform(projectionTransform, tmpCoords);
|
||||||
|
|
||||||
u0 = this.texCoordCallback_(feature, 0);
|
hitColor = colorEncodeId(hitIndex + 6, tmpColor);
|
||||||
v0 = this.texCoordCallback_(feature, 1);
|
|
||||||
u1 = this.texCoordCallback_(feature, 2);
|
|
||||||
v1 = this.texCoordCallback_(feature, 3);
|
|
||||||
size = this.sizeCallback_(feature);
|
|
||||||
opacity = this.opacityCallback_(feature);
|
|
||||||
rotateWithView = this.rotateWithViewCallback_(feature);
|
|
||||||
color = this.colorCallback_(feature, this.colorArray_);
|
|
||||||
|
|
||||||
writePointFeatureInstructions(
|
this.renderInstructions_[renderIndex++] = tmpCoords[0];
|
||||||
this.renderInstructions_,
|
this.renderInstructions_[renderIndex++] = tmpCoords[1];
|
||||||
elementIndex,
|
|
||||||
tmpCoords[0],
|
|
||||||
tmpCoords[1],
|
|
||||||
u0,
|
|
||||||
v0,
|
|
||||||
u1,
|
|
||||||
v1,
|
|
||||||
size,
|
|
||||||
opacity,
|
|
||||||
rotateWithView,
|
|
||||||
color
|
|
||||||
);
|
|
||||||
|
|
||||||
// for hit detection, the feature uid is saved in the opacity value
|
// for hit detection, the feature uid is saved in the opacity value
|
||||||
// and the index of the opacity value is encoded in the color values
|
// and the index of the opacity value is encoded in the color values
|
||||||
elementIndex = writePointFeatureInstructions(
|
if (this.hitDetectionEnabled_) {
|
||||||
this.hitRenderInstructions_,
|
this.hitRenderInstructions_[hitIndex++] = tmpCoords[0];
|
||||||
elementIndex,
|
this.hitRenderInstructions_[hitIndex++] = tmpCoords[1];
|
||||||
tmpCoords[0],
|
this.hitRenderInstructions_[hitIndex++] = hitColor[0];
|
||||||
tmpCoords[1],
|
this.hitRenderInstructions_[hitIndex++] = hitColor[1];
|
||||||
u0,
|
this.hitRenderInstructions_[hitIndex++] = hitColor[2];
|
||||||
v0,
|
this.hitRenderInstructions_[hitIndex++] = hitColor[3];
|
||||||
u1,
|
this.hitRenderInstructions_[hitIndex++] = Number(getUid(feature));
|
||||||
v1,
|
}
|
||||||
size,
|
|
||||||
opacity > 0 ? Number(getUid(feature)) : 0,
|
// pushing custom attributes
|
||||||
rotateWithView,
|
let value;
|
||||||
colorEncodeId(elementIndex + 7, tmpColor)
|
for (let j = 0; j < this.customAttributes.length; j++) {
|
||||||
);
|
value = this.customAttributes[j].callback(feature);
|
||||||
|
this.renderInstructions_[renderIndex++] = value;
|
||||||
|
if (this.hitDetectionEnabled_) {
|
||||||
|
this.hitRenderInstructions_[hitIndex++] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @type import('./Layer').WebGLWorkerGenerateBuffersMessage */
|
/** @type import('./Layer').WebGLWorkerGenerateBuffersMessage */
|
||||||
const message = {
|
const message = {
|
||||||
type: WebGLWorkerMessageType.GENERATE_BUFFERS,
|
type: WebGLWorkerMessageType.GENERATE_BUFFERS,
|
||||||
renderInstructions: this.renderInstructions_.buffer
|
renderInstructions: this.renderInstructions_.buffer,
|
||||||
|
customAttributesCount: this.customAttributes.length
|
||||||
};
|
};
|
||||||
// additional properties will be sent back as-is by the worker
|
// additional properties will be sent back as-is by the worker
|
||||||
message['projectionTransform'] = projectionTransform;
|
message['projectionTransform'] = projectionTransform;
|
||||||
@@ -503,20 +413,24 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
|
|||||||
this.renderInstructions_ = null;
|
this.renderInstructions_ = null;
|
||||||
|
|
||||||
/** @type import('./Layer').WebGLWorkerGenerateBuffersMessage */
|
/** @type import('./Layer').WebGLWorkerGenerateBuffersMessage */
|
||||||
const hitMessage = {
|
if (this.hitDetectionEnabled_) {
|
||||||
type: WebGLWorkerMessageType.GENERATE_BUFFERS,
|
const hitMessage = {
|
||||||
renderInstructions: this.hitRenderInstructions_.buffer
|
type: WebGLWorkerMessageType.GENERATE_BUFFERS,
|
||||||
};
|
renderInstructions: this.hitRenderInstructions_.buffer,
|
||||||
hitMessage['projectionTransform'] = projectionTransform;
|
customAttributesCount: 5 + this.customAttributes.length
|
||||||
hitMessage['hitDetection'] = true;
|
};
|
||||||
this.worker_.postMessage(hitMessage, [this.hitRenderInstructions_.buffer]);
|
hitMessage['projectionTransform'] = projectionTransform;
|
||||||
this.hitRenderInstructions_ = null;
|
hitMessage['hitDetection'] = true;
|
||||||
|
this.worker_.postMessage(hitMessage, [this.hitRenderInstructions_.buffer]);
|
||||||
|
this.hitRenderInstructions_ = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
forEachFeatureAtCoordinate(coordinate, frameState, hitTolerance, callback, declutteredFeatures) {
|
forEachFeatureAtCoordinate(coordinate, frameState, hitTolerance, callback, declutteredFeatures) {
|
||||||
|
assert(this.hitDetectionEnabled_, 66);
|
||||||
if (!this.hitRenderInstructions_) {
|
if (!this.hitRenderInstructions_) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -546,9 +460,8 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
|
|||||||
* @param {import("../../PluggableMap.js").FrameState} frameState current frame state
|
* @param {import("../../PluggableMap.js").FrameState} frameState current frame state
|
||||||
*/
|
*/
|
||||||
renderHitDetection(frameState) {
|
renderHitDetection(frameState) {
|
||||||
// skip render entirely if vertices buffers for display & hit detection have different sizes
|
// skip render entirely if vertex buffers not ready/generated yet
|
||||||
// this typically means both buffers are temporarily out of sync
|
if (!this.hitVerticesBuffer_.getSize()) {
|
||||||
if (this.hitVerticesBuffer_.getSize() !== this.verticesBuffer_.getSize()) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -560,18 +473,19 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
|
|||||||
this.helper.bindBuffer(this.hitVerticesBuffer_);
|
this.helper.bindBuffer(this.hitVerticesBuffer_);
|
||||||
this.helper.bindBuffer(this.indicesBuffer_);
|
this.helper.bindBuffer(this.indicesBuffer_);
|
||||||
|
|
||||||
const stride = POINT_VERTEX_STRIDE;
|
this.helper.enableAttributes(this.hitDetectionAttributes);
|
||||||
const bytesPerFloat = Float32Array.BYTES_PER_ELEMENT;
|
|
||||||
this.helper.enableAttributeArray(DefaultAttrib.POSITION, 2, FLOAT, bytesPerFloat * stride, 0);
|
|
||||||
this.helper.enableAttributeArray(DefaultAttrib.OFFSETS, 2, FLOAT, bytesPerFloat * stride, bytesPerFloat * 2);
|
|
||||||
this.helper.enableAttributeArray(DefaultAttrib.TEX_COORD, 2, FLOAT, bytesPerFloat * stride, bytesPerFloat * 4);
|
|
||||||
this.helper.enableAttributeArray(DefaultAttrib.OPACITY, 1, FLOAT, bytesPerFloat * stride, bytesPerFloat * 6);
|
|
||||||
this.helper.enableAttributeArray(DefaultAttrib.ROTATE_WITH_VIEW, 1, FLOAT, bytesPerFloat * stride, bytesPerFloat * 7);
|
|
||||||
this.helper.enableAttributeArray(DefaultAttrib.COLOR, 4, FLOAT, bytesPerFloat * stride, bytesPerFloat * 8);
|
|
||||||
|
|
||||||
const renderCount = this.indicesBuffer_.getSize();
|
const renderCount = this.indicesBuffer_.getSize();
|
||||||
this.helper.drawElements(0, renderCount);
|
this.helper.drawElements(0, renderCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
disposeInternal() {
|
||||||
|
this.worker_.terminate();
|
||||||
|
super.disposeInternal();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default WebGLPointsLayerRenderer;
|
export default WebGLPointsLayerRenderer;
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* Literal Style objects differ from standard styles in that they cannot
|
||||||
|
* be functions and are made up of simple objects instead of classes.
|
||||||
|
* @module ol/style/LiteralStyle
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} LiteralStyle
|
||||||
|
* @property {LiteralSymbolStyle} [symbol] Symbol representation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
export const SymbolType = {
|
||||||
|
CIRCLE: 'circle',
|
||||||
|
SQUARE: 'square',
|
||||||
|
TRIANGLE: 'triangle',
|
||||||
|
IMAGE: 'image'
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} LiteralSymbolStyle
|
||||||
|
* @property {number|Array.<number, number>} size Size, mandatory.
|
||||||
|
* @property {SymbolType} symbolType Symbol type to use, either a regular shape or an image.
|
||||||
|
* @property {string} [src] Path to the image to be used for the symbol. Only required with `symbolType: 'image'`.
|
||||||
|
* @property {import("../color.js").Color|string} [color='#FFFFFF'] Color used for the representation (either fill, line or symbol).
|
||||||
|
* @property {number} [opacity=1] Opacity.
|
||||||
|
* @property {Array.<number, number>} [offset] Offset on X and Y axis for symbols. If not specified, the symbol will be centered.
|
||||||
|
* @property {Array.<number, number, number, number>} [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.
|
||||||
|
*/
|
||||||
+98
-31
@@ -14,7 +14,7 @@ import {
|
|||||||
} from '../transform.js';
|
} from '../transform.js';
|
||||||
import {create, fromTransform} from '../vec/mat4.js';
|
import {create, fromTransform} from '../vec/mat4.js';
|
||||||
import WebGLPostProcessingPass from './PostProcessingPass.js';
|
import WebGLPostProcessingPass from './PostProcessingPass.js';
|
||||||
import {getContext, getSupportedExtensions} from '../webgl.js';
|
import {FLOAT, getContext, getSupportedExtensions, UNSIGNED_BYTE, UNSIGNED_INT, UNSIGNED_SHORT} from '../webgl.js';
|
||||||
import {includes} from '../array.js';
|
import {includes} from '../array.js';
|
||||||
import {assert} from '../asserts.js';
|
import {assert} from '../asserts.js';
|
||||||
|
|
||||||
@@ -46,19 +46,27 @@ export const DefaultUniform = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attribute names used in the default shaders: `POSITION`, `TEX_COORD`, `OPACITY`,
|
* Attribute types, either `UNSIGNED_BYTE`, `UNSIGNED_SHORT`, `UNSIGNED_INT` or `FLOAT`
|
||||||
* `ROTATE_WITH_VIEW`, `OFFSETS` and `COLOR`
|
* Note: an attribute stored in a `Float32Array` should be of type `FLOAT`.
|
||||||
* @enum {string}
|
* @enum {number}
|
||||||
*/
|
*/
|
||||||
export const DefaultAttrib = {
|
export const AttributeType = {
|
||||||
POSITION: 'a_position',
|
UNSIGNED_BYTE: UNSIGNED_BYTE,
|
||||||
TEX_COORD: 'a_texCoord',
|
UNSIGNED_SHORT: UNSIGNED_SHORT,
|
||||||
OPACITY: 'a_opacity',
|
UNSIGNED_INT: UNSIGNED_INT,
|
||||||
ROTATE_WITH_VIEW: 'a_rotateWithView',
|
FLOAT: FLOAT
|
||||||
OFFSETS: 'a_offsets',
|
|
||||||
COLOR: 'a_color'
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Description of an attribute in a buffer
|
||||||
|
* @typedef {Object} AttributeDescription
|
||||||
|
* @property {string} name Attribute name to use in shaders
|
||||||
|
* @property {number} size Number of components per attributes
|
||||||
|
* @property {AttributeType} [type] Attribute type, i.e. number of bytes used to store the value. This is
|
||||||
|
* determined by the class of typed array which the buffer uses (eg. `Float32Array` for a `FLOAT` attribute).
|
||||||
|
* Default is `FLOAT`.
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {number|Array<number>|HTMLCanvasElement|HTMLImageElement|ImageData|import("../transform").Transform} UniformLiteralValue
|
* @typedef {number|Array<number>|HTMLCanvasElement|HTMLImageElement|ImageData|import("../transform").Transform} UniformLiteralValue
|
||||||
*/
|
*/
|
||||||
@@ -122,14 +130,14 @@ export const DefaultAttrib = {
|
|||||||
* // for subsequent rendering calls
|
* // for subsequent rendering calls
|
||||||
* const vertexShader = new WebGLVertex(VERTEX_SHADER);
|
* const vertexShader = new WebGLVertex(VERTEX_SHADER);
|
||||||
* const fragmentShader = new WebGLFragment(FRAGMENT_SHADER);
|
* const fragmentShader = new WebGLFragment(FRAGMENT_SHADER);
|
||||||
* this.program = this.context.getProgram(fragmentShader, vertexShader);
|
* const program = this.context.getProgram(fragmentShader, vertexShader);
|
||||||
* this.context.useProgram(this.program);
|
* helper.useProgram(this.program);
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* Uniforms are defined using the `uniforms` option and can either be explicit values or callbacks taking the frame state as argument.
|
* Uniforms are defined using the `uniforms` option and can either be explicit values or callbacks taking the frame state as argument.
|
||||||
* You can also change their value along the way like so:
|
* You can also change their value along the way like so:
|
||||||
* ```js
|
* ```js
|
||||||
* this.context.setUniformFloatValue('u_value', valueAsNumber);
|
* helper.setUniformFloatValue('u_value', valueAsNumber);
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* ### Defining post processing passes
|
* ### Defining post processing passes
|
||||||
@@ -162,32 +170,42 @@ export const DefaultAttrib = {
|
|||||||
* Examples below:
|
* Examples below:
|
||||||
* ```js
|
* ```js
|
||||||
* // at initialization phase
|
* // at initialization phase
|
||||||
* this.verticesBuffer = new WebGLArrayBuffer([], DYNAMIC_DRAW);
|
* const verticesBuffer = new WebGLArrayBuffer([], DYNAMIC_DRAW);
|
||||||
* this.indicesBuffer = new WebGLArrayBuffer([], DYNAMIC_DRAW);
|
* const indicesBuffer = new WebGLArrayBuffer([], DYNAMIC_DRAW);
|
||||||
*
|
*
|
||||||
* // when array values have changed
|
* // when array values have changed
|
||||||
* this.context.flushBufferData(ARRAY_BUFFER, this.verticesBuffer);
|
* helper.flushBufferData(ARRAY_BUFFER, this.verticesBuffer);
|
||||||
* this.context.flushBufferData(ELEMENT_ARRAY_BUFFER, this.indicesBuffer);
|
* helper.flushBufferData(ELEMENT_ARRAY_BUFFER, this.indicesBuffer);
|
||||||
*
|
*
|
||||||
* // at rendering phase
|
* // at rendering phase
|
||||||
* this.context.bindBuffer(ARRAY_BUFFER, this.verticesBuffer);
|
* helper.bindBuffer(ARRAY_BUFFER, this.verticesBuffer);
|
||||||
* this.context.bindBuffer(ELEMENT_ARRAY_BUFFER, this.indicesBuffer);
|
* helper.bindBuffer(ELEMENT_ARRAY_BUFFER, this.indicesBuffer);
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* ### Specifying attributes
|
* ### Specifying attributes
|
||||||
*
|
*
|
||||||
* The GPU only receives the data as arrays of numbers. These numbers must be handled differently depending on what it describes (position, texture coordinate...).
|
* The GPU only receives the data as arrays of numbers. These numbers must be handled differently depending on what it describes (position, texture coordinate...).
|
||||||
* Attributes are used to specify these uses. Use {@link enableAttributeArray} and either
|
* Attributes are used to specify these uses. Use {@link enableAttributeArray_} and either
|
||||||
* the default attribute names in {@link module:ol/webgl/Helper.DefaultAttrib} or custom ones.
|
* the default attribute names in {@link module:ol/webgl/Helper.DefaultAttrib} or custom ones.
|
||||||
*
|
*
|
||||||
* Please note that you will have to specify the type and offset of the attributes in the data array. You can refer to the documentation of [WebGLRenderingContext.vertexAttribPointer](https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/vertexAttribPointer) for more explanation.
|
* Please note that you will have to specify the type and offset of the attributes in the data array. You can refer to the documentation of [WebGLRenderingContext.vertexAttribPointer](https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/vertexAttribPointer) for more explanation.
|
||||||
* ```js
|
* ```js
|
||||||
* // here we indicate that the data array has the following structure:
|
* // here we indicate that the data array has the following structure:
|
||||||
* // [posX, posY, offsetX, offsetY, texCoordU, texCoordV, posX, posY, ...]
|
* // [posX, posY, offsetX, offsetY, texCoordU, texCoordV, posX, posY, ...]
|
||||||
* let bytesPerFloat = Float32Array.BYTES_PER_ELEMENT;
|
* helper.enableAttributes([
|
||||||
* this.context.enableAttributeArray(DefaultAttrib.POSITION, 2, FLOAT, bytesPerFloat * 6, 0);
|
* {
|
||||||
* this.context.enableAttributeArray(DefaultAttrib.OFFSETS, 2, FLOAT, bytesPerFloat * 6, bytesPerFloat * 2);
|
* name: 'a_position',
|
||||||
* this.context.enableAttributeArray(DefaultAttrib.TEX_COORD, 2, FLOAT, bytesPerFloat * 6, bytesPerFloat * 4);
|
* size: 2
|
||||||
|
* },
|
||||||
|
* {
|
||||||
|
* name: 'a_offset',
|
||||||
|
* size: 2
|
||||||
|
* },
|
||||||
|
* {
|
||||||
|
* name: 'a_texCoord',
|
||||||
|
* size: 2
|
||||||
|
* }
|
||||||
|
* ])
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* ### Rendering primitives
|
* ### Rendering primitives
|
||||||
@@ -195,13 +213,13 @@ export const DefaultAttrib = {
|
|||||||
* Once all the steps above have been achieved, rendering primitives to the screen is done using {@link prepareDraw}, {@link drawElements} and {@link finalizeDraw}.
|
* Once all the steps above have been achieved, rendering primitives to the screen is done using {@link prepareDraw}, {@link drawElements} and {@link finalizeDraw}.
|
||||||
* ```js
|
* ```js
|
||||||
* // frame preparation step
|
* // frame preparation step
|
||||||
* this.context.prepareDraw(frameState);
|
* helper.prepareDraw(frameState);
|
||||||
*
|
*
|
||||||
* // call this for every data array that has to be rendered on screen
|
* // call this for every data array that has to be rendered on screen
|
||||||
* this.context.drawElements(0, this.indicesBuffer.getArray().length);
|
* helper.drawElements(0, this.indicesBuffer.getArray().length);
|
||||||
*
|
*
|
||||||
* // finalize the rendering by applying post processes
|
* // finalize the rendering by applying post processes
|
||||||
* this.context.finalizeDraw(frameState);
|
* helper.finalizeDraw(frameState);
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* For an example usage of this class, refer to {@link module:ol/renderer/webgl/PointsLayer~WebGLPointsLayerRenderer}.
|
* For an example usage of this class, refer to {@link module:ol/renderer/webgl/PointsLayer~WebGLPointsLayerRenderer}.
|
||||||
@@ -543,6 +561,7 @@ class WebGLHelper extends Disposable {
|
|||||||
let textureSlot = 0;
|
let textureSlot = 0;
|
||||||
this.uniforms_.forEach(function(uniform) {
|
this.uniforms_.forEach(function(uniform) {
|
||||||
value = typeof uniform.value === 'function' ? uniform.value(frameState) : uniform.value;
|
value = typeof uniform.value === 'function' ? uniform.value(frameState) : uniform.value;
|
||||||
|
|
||||||
// apply value based on type
|
// apply value based on type
|
||||||
if (value instanceof HTMLCanvasElement || value instanceof HTMLImageElement || value instanceof ImageData) {
|
if (value instanceof HTMLCanvasElement || value instanceof HTMLImageElement || value instanceof ImageData) {
|
||||||
// create a texture & put data
|
// create a texture & put data
|
||||||
@@ -734,15 +753,16 @@ class WebGLHelper extends Disposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will set the currently bound buffer to an attribute of the shader program
|
* Will set the currently bound buffer to an attribute of the shader program. Used by `#enableAttributes`
|
||||||
|
* internally.
|
||||||
* @param {string} attribName Attribute name
|
* @param {string} attribName Attribute name
|
||||||
* @param {number} size Number of components per attributes
|
* @param {number} size Number of components per attributes
|
||||||
* @param {number} type UNSIGNED_INT, UNSIGNED_BYTE, UNSIGNED_SHORT or FLOAT
|
* @param {number} type UNSIGNED_INT, UNSIGNED_BYTE, UNSIGNED_SHORT or FLOAT
|
||||||
* @param {number} stride Stride in bytes (0 means attribs are packed)
|
* @param {number} stride Stride in bytes (0 means attribs are packed)
|
||||||
* @param {number} offset Offset in bytes
|
* @param {number} offset Offset in bytes
|
||||||
* @api
|
* @private
|
||||||
*/
|
*/
|
||||||
enableAttributeArray(attribName, size, type, stride, offset) {
|
enableAttributeArray_(attribName, size, type, stride, offset) {
|
||||||
const location = this.getAttributeLocation(attribName);
|
const location = this.getAttributeLocation(attribName);
|
||||||
// the attribute has not been found in the shaders; do not enable it
|
// the attribute has not been found in the shaders; do not enable it
|
||||||
if (location < 0) {
|
if (location < 0) {
|
||||||
@@ -753,6 +773,23 @@ class WebGLHelper extends Disposable {
|
|||||||
false, stride, offset);
|
false, stride, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will enable the following attributes to be read from the currently bound buffer,
|
||||||
|
* i.e. tell the GPU where to read the different attributes in the buffer. An error in the
|
||||||
|
* size/type/order of attributes will most likely break the rendering and throw a WebGL exception.
|
||||||
|
* @param {Array<AttributeDescription>} attributes Ordered list of attributes to read from the buffer
|
||||||
|
* @api
|
||||||
|
*/
|
||||||
|
enableAttributes(attributes) {
|
||||||
|
const stride = computeAttributesStride(attributes);
|
||||||
|
let offset = 0;
|
||||||
|
for (let i = 0; i < attributes.length; i++) {
|
||||||
|
const attr = attributes[i];
|
||||||
|
this.enableAttributeArray_(attr.name, attr.size, attr.type || FLOAT, stride, offset);
|
||||||
|
offset += attr.size * getByteSizeFromType(attr.type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* WebGL context was lost
|
* WebGL context was lost
|
||||||
* @private
|
* @private
|
||||||
@@ -808,4 +845,34 @@ class WebGLHelper extends Disposable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute a stride in bytes based on a list of attributes
|
||||||
|
* @param {Array<AttributeDescription>} attributes Ordered list of attributes
|
||||||
|
* @returns {number} Stride, ie amount of values for each vertex in the vertex buffer
|
||||||
|
* @api
|
||||||
|
*/
|
||||||
|
export function computeAttributesStride(attributes) {
|
||||||
|
let stride = 0;
|
||||||
|
for (let i = 0; i < attributes.length; i++) {
|
||||||
|
const attr = attributes[i];
|
||||||
|
stride += attr.size * getByteSizeFromType(attr.type);
|
||||||
|
}
|
||||||
|
return stride;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes the size in byte of an attribute type.
|
||||||
|
* @param {AttributeType} type Attribute type
|
||||||
|
* @returns {number} The size in bytes
|
||||||
|
*/
|
||||||
|
function getByteSizeFromType(type) {
|
||||||
|
switch (type) {
|
||||||
|
case AttributeType.UNSIGNED_BYTE: return Uint8Array.BYTES_PER_ELEMENT;
|
||||||
|
case AttributeType.UNSIGNED_SHORT: return Uint16Array.BYTES_PER_ELEMENT;
|
||||||
|
case AttributeType.UNSIGNED_INT: return Uint32Array.BYTES_PER_ELEMENT;
|
||||||
|
case AttributeType.FLOAT:
|
||||||
|
default: return Float32Array.BYTES_PER_ELEMENT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default WebGLHelper;
|
export default WebGLHelper;
|
||||||
|
|||||||
@@ -0,0 +1,143 @@
|
|||||||
|
/**
|
||||||
|
* Utilities for generating shaders from literal style objects
|
||||||
|
* @module ol/webgl/ShaderBuilder
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will return the number as a float with a dot separator, which is required by GLSL.
|
||||||
|
* @param {number} v Numerical value.
|
||||||
|
* @returns {string} The value as string.
|
||||||
|
*/
|
||||||
|
export function formatNumber(v) {
|
||||||
|
const s = v.toString();
|
||||||
|
return s.indexOf('.') === -1 ? s + '.0' : s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will return the number array as a float with a dot separator, concatenated with ', '.
|
||||||
|
* @param {Array<number>} array Numerical values array.
|
||||||
|
* @returns {string} The array as string, e. g.: `1.0, 2.0, 3.0`.
|
||||||
|
*/
|
||||||
|
export function formatArray(array) {
|
||||||
|
return array.map(formatNumber).join(', ');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will normalize and converts to string a color array compatible with GLSL.
|
||||||
|
* @param {Array<number>} colorArray Color in [r, g, b, a] array form, with RGB components in the
|
||||||
|
* 0..255 range and the alpha component in the 0..1 range. Note that if the A component is
|
||||||
|
* missing, only 3 values will be output.
|
||||||
|
* @returns {string} The color components concatenated in `1.0, 1.0, 1.0, 1.0` form.
|
||||||
|
*/
|
||||||
|
export function formatColor(colorArray) {
|
||||||
|
return colorArray.map(function(c, i) {
|
||||||
|
return i < 3 ? c / 255 : c;
|
||||||
|
}).map(formatNumber).join(', ');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} VaryingDescription
|
||||||
|
* @property {string} name Varying name, as will be declared in the header.
|
||||||
|
* @property {string} type Varying type, either `float`, `vec2`, `vec4`...
|
||||||
|
* @property {string} expression Expression which will be assigned to the varying in the vertex shader, and
|
||||||
|
* passed on to the fragment shader.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} ShaderParameters
|
||||||
|
* @property {Array<string>} [uniforms] Uniforms; these will be declared in the header (should include the type).
|
||||||
|
* @property {Array<string>} [attributes] Attributes; these will be declared in the header (should include the type).
|
||||||
|
* @property {Array<VaryingDescription>} [varyings] Varyings with a name, a type and an expression.
|
||||||
|
* @property {string} sizeExpression This will be assigned to a `vec2 size` variable.
|
||||||
|
* @property {string} offsetExpression This will be assigned to a `vec2 offset` variable.
|
||||||
|
* @property {string} colorExpression This will be the value assigned to gl_FragColor
|
||||||
|
* @property {string} texCoordExpression This will be the value assigned to the `vec4 v_texCoord` varying.
|
||||||
|
* @property {boolean} [rotateWithView=false] Whether symbols should rotate with view
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a symbol vertex shader from a set of parameters,
|
||||||
|
* intended to be used on point geometries.
|
||||||
|
*
|
||||||
|
* Three uniforms are hardcoded in all shaders: `u_projectionMatrix`, `u_offsetScaleMatrix` and
|
||||||
|
* `u_offsetRotateMatrix`.
|
||||||
|
*
|
||||||
|
* The following attributes are hardcoded and expected to be present in the vertex buffers:
|
||||||
|
* `vec2 a_position`, `float a_index` (being the index of the vertex in the quad, 0 to 3).
|
||||||
|
*
|
||||||
|
* @param {ShaderParameters} parameters Parameters for the shader.
|
||||||
|
* @returns {string} The full shader as a string.
|
||||||
|
*/
|
||||||
|
export function getSymbolVertexShader(parameters) {
|
||||||
|
const offsetMatrix = parameters.rotateWithView ?
|
||||||
|
'u_offsetScaleMatrix * u_offsetRotateMatrix' :
|
||||||
|
'u_offsetScaleMatrix';
|
||||||
|
|
||||||
|
const uniforms = parameters.uniforms || [];
|
||||||
|
const attributes = parameters.attributes || [];
|
||||||
|
const varyings = parameters.varyings || [];
|
||||||
|
|
||||||
|
const body = `precision mediump float;
|
||||||
|
uniform mat4 u_projectionMatrix;
|
||||||
|
uniform mat4 u_offsetScaleMatrix;
|
||||||
|
uniform mat4 u_offsetRotateMatrix;
|
||||||
|
${uniforms.map(function(uniform) {
|
||||||
|
return 'uniform ' + uniform + ';';
|
||||||
|
}).join('\n')}
|
||||||
|
attribute vec2 a_position;
|
||||||
|
attribute float a_index;
|
||||||
|
${attributes.map(function(attribute) {
|
||||||
|
return 'attribute ' + attribute + ';';
|
||||||
|
}).join('\n')}
|
||||||
|
varying vec2 v_texCoord;
|
||||||
|
${varyings.map(function(varying) {
|
||||||
|
return 'varying ' + varying.type + ' ' + varying.name + ';';
|
||||||
|
}).join('\n')}
|
||||||
|
void main(void) {
|
||||||
|
mat4 offsetMatrix = ${offsetMatrix};
|
||||||
|
vec2 size = ${parameters.sizeExpression};
|
||||||
|
vec2 offset = ${parameters.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;
|
||||||
|
vec4 offsets = offsetMatrix * vec4(offsetX, offsetY, 0.0, 0.0);
|
||||||
|
gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets;
|
||||||
|
vec4 texCoord = ${parameters.texCoordExpression};
|
||||||
|
float u = a_index == 0.0 || a_index == 3.0 ? texCoord.s : texCoord.q;
|
||||||
|
float v = a_index == 2.0 || a_index == 3.0 ? texCoord.t : texCoord.p;
|
||||||
|
v_texCoord = vec2(u, v);
|
||||||
|
${varyings.map(function(varying) {
|
||||||
|
return ' ' + varying.name + ' = ' + varying.expression + ';';
|
||||||
|
}).join('\n')}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a symbol fragment shader intended to be used on point geometries.
|
||||||
|
*
|
||||||
|
* Expected the following varyings to be transmitted by the vertex shader:
|
||||||
|
* `vec2 v_texCoord`
|
||||||
|
*
|
||||||
|
* @param {ShaderParameters} parameters Parameters for the shader.
|
||||||
|
* @returns {string} The full shader as a string.
|
||||||
|
*/
|
||||||
|
export function getSymbolFragmentShader(parameters) {
|
||||||
|
const uniforms = parameters.uniforms || [];
|
||||||
|
const varyings = parameters.varyings || [];
|
||||||
|
|
||||||
|
const body = `precision mediump float;
|
||||||
|
${uniforms.map(function(uniform) {
|
||||||
|
return 'uniform ' + uniform + ';';
|
||||||
|
}).join('\n')}
|
||||||
|
varying vec2 v_texCoord;
|
||||||
|
${varyings.map(function(varying) {
|
||||||
|
return 'varying ' + varying.type + ' ' + varying.name + ';';
|
||||||
|
}).join('\n')}
|
||||||
|
void main(void) {
|
||||||
|
gl_FragColor = ${parameters.colorExpression};
|
||||||
|
gl_FragColor.rgb *= gl_FragColor.a;
|
||||||
|
}`;
|
||||||
|
|
||||||
|
return body;
|
||||||
|
}
|
||||||
@@ -3,8 +3,6 @@
|
|||||||
* @module ol/worker/webgl
|
* @module ol/worker/webgl
|
||||||
*/
|
*/
|
||||||
import {
|
import {
|
||||||
POINT_INSTRUCTIONS_COUNT,
|
|
||||||
POINT_VERTEX_STRIDE,
|
|
||||||
WebGLWorkerMessageType,
|
WebGLWorkerMessageType,
|
||||||
writePointFeatureToBuffers
|
writePointFeatureToBuffers
|
||||||
} from '../renderer/webgl/Layer.js';
|
} from '../renderer/webgl/Layer.js';
|
||||||
@@ -16,13 +14,17 @@ const worker = self;
|
|||||||
worker.onmessage = event => {
|
worker.onmessage = event => {
|
||||||
const received = event.data;
|
const received = event.data;
|
||||||
if (received.type === WebGLWorkerMessageType.GENERATE_BUFFERS) {
|
if (received.type === WebGLWorkerMessageType.GENERATE_BUFFERS) {
|
||||||
|
// This is specific to point features (x, y, index)
|
||||||
|
const baseVertexAttrsCount = 3;
|
||||||
|
const baseInstructionsCount = 2;
|
||||||
|
|
||||||
|
const customAttrsCount = received.customAttributesCount;
|
||||||
|
const instructionsCount = baseInstructionsCount + customAttrsCount;
|
||||||
const renderInstructions = new Float32Array(received.renderInstructions);
|
const renderInstructions = new Float32Array(received.renderInstructions);
|
||||||
const customAttributesCount = received.customAttributesCount || 0;
|
|
||||||
const instructionsCount = POINT_INSTRUCTIONS_COUNT + customAttributesCount;
|
|
||||||
|
|
||||||
const elementsCount = renderInstructions.length / instructionsCount;
|
const elementsCount = renderInstructions.length / instructionsCount;
|
||||||
const indicesCount = elementsCount * 6;
|
const indicesCount = elementsCount * 6;
|
||||||
const verticesCount = elementsCount * 4 * (POINT_VERTEX_STRIDE + customAttributesCount);
|
const verticesCount = elementsCount * 4 * (customAttrsCount + baseVertexAttrsCount);
|
||||||
const indexBuffer = new Uint32Array(indicesCount);
|
const indexBuffer = new Uint32Array(indicesCount);
|
||||||
const vertexBuffer = new Float32Array(verticesCount);
|
const vertexBuffer = new Float32Array(verticesCount);
|
||||||
|
|
||||||
@@ -33,8 +35,8 @@ worker.onmessage = event => {
|
|||||||
i,
|
i,
|
||||||
vertexBuffer,
|
vertexBuffer,
|
||||||
indexBuffer,
|
indexBuffer,
|
||||||
bufferPositions,
|
customAttrsCount,
|
||||||
instructionsCount);
|
bufferPositions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @type {import('../renderer/webgl/Layer').WebGLWorkerGenerateBuffersMessage} */
|
/** @type {import('../renderer/webgl/Layer').WebGLWorkerGenerateBuffersMessage} */
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import WebGLLayerRenderer, {
|
import WebGLLayerRenderer, {
|
||||||
colorDecodeId,
|
colorDecodeId,
|
||||||
colorEncodeId,
|
colorEncodeId,
|
||||||
getBlankImageData, POINT_INSTRUCTIONS_COUNT, POINT_VERTEX_STRIDE,
|
getBlankImageData,
|
||||||
writePointFeatureInstructions, writePointFeatureToBuffers
|
writePointFeatureToBuffers
|
||||||
} from '../../../../../src/ol/renderer/webgl/Layer.js';
|
} from '../../../../../src/ol/renderer/webgl/Layer.js';
|
||||||
import Layer from '../../../../../src/ol/layer/Layer.js';
|
import Layer from '../../../../../src/ol/layer/Layer.js';
|
||||||
|
|
||||||
@@ -32,111 +32,36 @@ describe('ol.renderer.webgl.Layer', function() {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('writePointFeatureInstructions', function() {
|
|
||||||
let instructions;
|
|
||||||
|
|
||||||
beforeEach(function() {
|
|
||||||
instructions = new Float32Array(100);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('writes instructions corresponding to the given parameters', function() {
|
|
||||||
const baseIndex = 17;
|
|
||||||
writePointFeatureInstructions(instructions, baseIndex,
|
|
||||||
1, 2, 3, 4, 5, 6,
|
|
||||||
7, 8, true, [10, 11, 12, 13]);
|
|
||||||
expect(instructions[baseIndex + 0]).to.eql(1);
|
|
||||||
expect(instructions[baseIndex + 1]).to.eql(2);
|
|
||||||
expect(instructions[baseIndex + 2]).to.eql(3);
|
|
||||||
expect(instructions[baseIndex + 3]).to.eql(4);
|
|
||||||
expect(instructions[baseIndex + 4]).to.eql(5);
|
|
||||||
expect(instructions[baseIndex + 5]).to.eql(6);
|
|
||||||
expect(instructions[baseIndex + 6]).to.eql(7);
|
|
||||||
expect(instructions[baseIndex + 7]).to.eql(8);
|
|
||||||
expect(instructions[baseIndex + 8]).to.eql(1);
|
|
||||||
expect(instructions[baseIndex + 9]).to.eql(10);
|
|
||||||
expect(instructions[baseIndex + 10]).to.eql(11);
|
|
||||||
expect(instructions[baseIndex + 11]).to.eql(12);
|
|
||||||
expect(instructions[baseIndex + 12]).to.eql(13);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('correctly chains writes', function() {
|
|
||||||
let baseIndex = 0;
|
|
||||||
baseIndex = writePointFeatureInstructions(instructions, baseIndex,
|
|
||||||
1, 2, 3, 4, 5, 6,
|
|
||||||
7, 8, true, [10, 11, 12, 13]);
|
|
||||||
baseIndex = writePointFeatureInstructions(instructions, baseIndex,
|
|
||||||
1, 2, 3, 4, 5, 6,
|
|
||||||
7, 8, true, [10, 11, 12, 13]);
|
|
||||||
writePointFeatureInstructions(instructions, baseIndex,
|
|
||||||
1, 2, 3, 4, 5, 6,
|
|
||||||
7, 8, true, [10, 11, 12, 13]);
|
|
||||||
expect(instructions[baseIndex + 0]).to.eql(1);
|
|
||||||
expect(instructions[baseIndex + 1]).to.eql(2);
|
|
||||||
expect(instructions[baseIndex + 2]).to.eql(3);
|
|
||||||
expect(instructions[baseIndex + 3]).to.eql(4);
|
|
||||||
expect(instructions[baseIndex + 4]).to.eql(5);
|
|
||||||
expect(instructions[baseIndex + 5]).to.eql(6);
|
|
||||||
expect(instructions[baseIndex + 6]).to.eql(7);
|
|
||||||
expect(instructions[baseIndex + 7]).to.eql(8);
|
|
||||||
expect(instructions[baseIndex + 8]).to.eql(1);
|
|
||||||
expect(instructions[baseIndex + 9]).to.eql(10);
|
|
||||||
expect(instructions[baseIndex + 10]).to.eql(11);
|
|
||||||
expect(instructions[baseIndex + 11]).to.eql(12);
|
|
||||||
expect(instructions[baseIndex + 12]).to.eql(13);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('writePointFeatureToBuffers', function() {
|
describe('writePointFeatureToBuffers', function() {
|
||||||
let vertexBuffer, indexBuffer, instructions, elementIndex;
|
let vertexBuffer, indexBuffer, instructions;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
vertexBuffer = new Float32Array(100);
|
vertexBuffer = new Float32Array(100);
|
||||||
indexBuffer = new Uint32Array(100);
|
indexBuffer = new Uint32Array(100);
|
||||||
instructions = new Float32Array(100);
|
instructions = new Float32Array(100);
|
||||||
elementIndex = 3;
|
|
||||||
|
|
||||||
writePointFeatureInstructions(instructions, elementIndex,
|
instructions.set([0, 0, 0, 0, 10, 11]);
|
||||||
1, 2, 3, 4, 5, 6,
|
|
||||||
7, 8, true, [10, 11, 12, 13]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('writes correctly to the buffers (without custom attributes)', function() {
|
it('writes correctly to the buffers (without custom attributes)', function() {
|
||||||
const stride = POINT_VERTEX_STRIDE;
|
const stride = 3;
|
||||||
const positions = writePointFeatureToBuffers(instructions, elementIndex, vertexBuffer, indexBuffer);
|
const positions = writePointFeatureToBuffers(instructions, 4, vertexBuffer, indexBuffer, 0);
|
||||||
|
|
||||||
expect(vertexBuffer[0]).to.eql(1);
|
expect(vertexBuffer[0]).to.eql(10);
|
||||||
expect(vertexBuffer[1]).to.eql(2);
|
expect(vertexBuffer[1]).to.eql(11);
|
||||||
expect(vertexBuffer[2]).to.eql(-3.5);
|
expect(vertexBuffer[2]).to.eql(0);
|
||||||
expect(vertexBuffer[3]).to.eql(-3.5);
|
|
||||||
expect(vertexBuffer[4]).to.eql(3);
|
|
||||||
expect(vertexBuffer[5]).to.eql(4);
|
|
||||||
expect(vertexBuffer[6]).to.eql(8);
|
|
||||||
expect(vertexBuffer[7]).to.eql(1);
|
|
||||||
expect(vertexBuffer[8]).to.eql(10);
|
|
||||||
expect(vertexBuffer[9]).to.eql(11);
|
|
||||||
expect(vertexBuffer[10]).to.eql(12);
|
|
||||||
expect(vertexBuffer[11]).to.eql(13);
|
|
||||||
|
|
||||||
expect(vertexBuffer[stride + 0]).to.eql(1);
|
expect(vertexBuffer[stride + 0]).to.eql(10);
|
||||||
expect(vertexBuffer[stride + 1]).to.eql(2);
|
expect(vertexBuffer[stride + 1]).to.eql(11);
|
||||||
expect(vertexBuffer[stride + 2]).to.eql(+3.5);
|
expect(vertexBuffer[stride + 2]).to.eql(1);
|
||||||
expect(vertexBuffer[stride + 3]).to.eql(-3.5);
|
|
||||||
expect(vertexBuffer[stride + 4]).to.eql(5);
|
|
||||||
expect(vertexBuffer[stride + 5]).to.eql(4);
|
|
||||||
|
|
||||||
expect(vertexBuffer[stride * 2 + 0]).to.eql(1);
|
expect(vertexBuffer[stride * 2 + 0]).to.eql(10);
|
||||||
expect(vertexBuffer[stride * 2 + 1]).to.eql(2);
|
expect(vertexBuffer[stride * 2 + 1]).to.eql(11);
|
||||||
expect(vertexBuffer[stride * 2 + 2]).to.eql(+3.5);
|
expect(vertexBuffer[stride * 2 + 2]).to.eql(2);
|
||||||
expect(vertexBuffer[stride * 2 + 3]).to.eql(+3.5);
|
|
||||||
expect(vertexBuffer[stride * 2 + 4]).to.eql(5);
|
|
||||||
expect(vertexBuffer[stride * 2 + 5]).to.eql(6);
|
|
||||||
|
|
||||||
expect(vertexBuffer[stride * 3 + 0]).to.eql(1);
|
expect(vertexBuffer[stride * 3 + 0]).to.eql(10);
|
||||||
expect(vertexBuffer[stride * 3 + 1]).to.eql(2);
|
expect(vertexBuffer[stride * 3 + 1]).to.eql(11);
|
||||||
expect(vertexBuffer[stride * 3 + 2]).to.eql(-3.5);
|
expect(vertexBuffer[stride * 3 + 2]).to.eql(3);
|
||||||
expect(vertexBuffer[stride * 3 + 3]).to.eql(+3.5);
|
|
||||||
expect(vertexBuffer[stride * 3 + 4]).to.eql(3);
|
|
||||||
expect(vertexBuffer[stride * 3 + 5]).to.eql(6);
|
|
||||||
|
|
||||||
expect(indexBuffer[0]).to.eql(0);
|
expect(indexBuffer[0]).to.eql(0);
|
||||||
expect(indexBuffer[1]).to.eql(1);
|
expect(indexBuffer[1]).to.eql(1);
|
||||||
@@ -149,43 +74,34 @@ describe('ol.renderer.webgl.Layer', function() {
|
|||||||
expect(positions.vertexPosition).to.eql(stride * 4);
|
expect(positions.vertexPosition).to.eql(stride * 4);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('writes correctly to the buffers (with custom attributes)', function() {
|
it('writes correctly to the buffers (with 2 custom attributes)', function() {
|
||||||
instructions[elementIndex + POINT_INSTRUCTIONS_COUNT] = 101;
|
instructions.set([0, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12, 13]);
|
||||||
instructions[elementIndex + POINT_INSTRUCTIONS_COUNT + 1] = 102;
|
const stride = 5;
|
||||||
instructions[elementIndex + POINT_INSTRUCTIONS_COUNT + 2] = 103;
|
const positions = writePointFeatureToBuffers(instructions, 8, vertexBuffer, indexBuffer, 2);
|
||||||
|
|
||||||
const stride = POINT_VERTEX_STRIDE + 3;
|
expect(vertexBuffer[0]).to.eql(10);
|
||||||
const positions = writePointFeatureToBuffers(instructions, elementIndex, vertexBuffer, indexBuffer,
|
expect(vertexBuffer[1]).to.eql(11);
|
||||||
undefined, POINT_INSTRUCTIONS_COUNT + 3);
|
expect(vertexBuffer[2]).to.eql(0);
|
||||||
|
expect(vertexBuffer[3]).to.eql(12);
|
||||||
|
expect(vertexBuffer[4]).to.eql(13);
|
||||||
|
|
||||||
expect(vertexBuffer[0]).to.eql(1);
|
expect(vertexBuffer[stride + 0]).to.eql(10);
|
||||||
expect(vertexBuffer[1]).to.eql(2);
|
expect(vertexBuffer[stride + 1]).to.eql(11);
|
||||||
expect(vertexBuffer[2]).to.eql(-3.5);
|
expect(vertexBuffer[stride + 2]).to.eql(1);
|
||||||
expect(vertexBuffer[3]).to.eql(-3.5);
|
expect(vertexBuffer[stride + 3]).to.eql(12);
|
||||||
expect(vertexBuffer[4]).to.eql(3);
|
expect(vertexBuffer[stride + 4]).to.eql(13);
|
||||||
expect(vertexBuffer[5]).to.eql(4);
|
|
||||||
expect(vertexBuffer[6]).to.eql(8);
|
|
||||||
expect(vertexBuffer[7]).to.eql(1);
|
|
||||||
expect(vertexBuffer[8]).to.eql(10);
|
|
||||||
expect(vertexBuffer[9]).to.eql(11);
|
|
||||||
expect(vertexBuffer[10]).to.eql(12);
|
|
||||||
expect(vertexBuffer[11]).to.eql(13);
|
|
||||||
|
|
||||||
expect(vertexBuffer[12]).to.eql(101);
|
expect(vertexBuffer[stride * 2 + 0]).to.eql(10);
|
||||||
expect(vertexBuffer[13]).to.eql(102);
|
expect(vertexBuffer[stride * 2 + 1]).to.eql(11);
|
||||||
expect(vertexBuffer[14]).to.eql(103);
|
expect(vertexBuffer[stride * 2 + 2]).to.eql(2);
|
||||||
|
expect(vertexBuffer[stride * 2 + 3]).to.eql(12);
|
||||||
|
expect(vertexBuffer[stride * 2 + 4]).to.eql(13);
|
||||||
|
|
||||||
expect(vertexBuffer[stride + 12]).to.eql(101);
|
expect(vertexBuffer[stride * 3 + 0]).to.eql(10);
|
||||||
expect(vertexBuffer[stride + 13]).to.eql(102);
|
expect(vertexBuffer[stride * 3 + 1]).to.eql(11);
|
||||||
expect(vertexBuffer[stride + 14]).to.eql(103);
|
expect(vertexBuffer[stride * 3 + 2]).to.eql(3);
|
||||||
|
expect(vertexBuffer[stride * 3 + 3]).to.eql(12);
|
||||||
expect(vertexBuffer[stride * 2 + 12]).to.eql(101);
|
expect(vertexBuffer[stride * 3 + 4]).to.eql(13);
|
||||||
expect(vertexBuffer[stride * 2 + 13]).to.eql(102);
|
|
||||||
expect(vertexBuffer[stride * 2 + 14]).to.eql(103);
|
|
||||||
|
|
||||||
expect(vertexBuffer[stride * 3 + 12]).to.eql(101);
|
|
||||||
expect(vertexBuffer[stride * 3 + 13]).to.eql(102);
|
|
||||||
expect(vertexBuffer[stride * 3 + 14]).to.eql(103);
|
|
||||||
|
|
||||||
expect(indexBuffer[0]).to.eql(0);
|
expect(indexBuffer[0]).to.eql(0);
|
||||||
expect(indexBuffer[1]).to.eql(1);
|
expect(indexBuffer[1]).to.eql(1);
|
||||||
@@ -199,25 +115,23 @@ describe('ol.renderer.webgl.Layer', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('correctly chains buffer writes', function() {
|
it('correctly chains buffer writes', function() {
|
||||||
const stride = POINT_VERTEX_STRIDE;
|
instructions.set([10, 11, 20, 21, 30, 31]);
|
||||||
let positions = writePointFeatureToBuffers(instructions, elementIndex, vertexBuffer, indexBuffer);
|
const stride = 3;
|
||||||
positions = writePointFeatureToBuffers(instructions, elementIndex, vertexBuffer, indexBuffer, positions);
|
let positions = writePointFeatureToBuffers(instructions, 0, vertexBuffer, indexBuffer, 0);
|
||||||
positions = writePointFeatureToBuffers(instructions, elementIndex, vertexBuffer, indexBuffer, positions);
|
positions = writePointFeatureToBuffers(instructions, 2, vertexBuffer, indexBuffer, 0, positions);
|
||||||
|
positions = writePointFeatureToBuffers(instructions, 4, vertexBuffer, indexBuffer, 0, positions);
|
||||||
|
|
||||||
expect(vertexBuffer[0]).to.eql(1);
|
expect(vertexBuffer[0]).to.eql(10);
|
||||||
expect(vertexBuffer[1]).to.eql(2);
|
expect(vertexBuffer[1]).to.eql(11);
|
||||||
expect(vertexBuffer[2]).to.eql(-3.5);
|
expect(vertexBuffer[2]).to.eql(0);
|
||||||
expect(vertexBuffer[3]).to.eql(-3.5);
|
|
||||||
|
|
||||||
expect(vertexBuffer[stride * 4 + 0]).to.eql(1);
|
expect(vertexBuffer[stride * 4 + 0]).to.eql(20);
|
||||||
expect(vertexBuffer[stride * 4 + 1]).to.eql(2);
|
expect(vertexBuffer[stride * 4 + 1]).to.eql(21);
|
||||||
expect(vertexBuffer[stride * 4 + 2]).to.eql(-3.5);
|
expect(vertexBuffer[stride * 4 + 2]).to.eql(0);
|
||||||
expect(vertexBuffer[stride * 4 + 3]).to.eql(-3.5);
|
|
||||||
|
|
||||||
expect(vertexBuffer[stride * 8 + 0]).to.eql(1);
|
expect(vertexBuffer[stride * 8 + 0]).to.eql(30);
|
||||||
expect(vertexBuffer[stride * 8 + 1]).to.eql(2);
|
expect(vertexBuffer[stride * 8 + 1]).to.eql(31);
|
||||||
expect(vertexBuffer[stride * 8 + 2]).to.eql(-3.5);
|
expect(vertexBuffer[stride * 8 + 2]).to.eql(0);
|
||||||
expect(vertexBuffer[stride * 8 + 3]).to.eql(-3.5);
|
|
||||||
|
|
||||||
expect(indexBuffer[6 + 0]).to.eql(4);
|
expect(indexBuffer[6 + 0]).to.eql(4);
|
||||||
expect(indexBuffer[6 + 1]).to.eql(5);
|
expect(indexBuffer[6 + 1]).to.eql(5);
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import VectorSource from '../../../../../src/ol/source/Vector.js';
|
|||||||
import WebGLPointsLayerRenderer from '../../../../../src/ol/renderer/webgl/PointsLayer.js';
|
import WebGLPointsLayerRenderer from '../../../../../src/ol/renderer/webgl/PointsLayer.js';
|
||||||
import {get as getProjection} from '../../../../../src/ol/proj.js';
|
import {get as getProjection} from '../../../../../src/ol/proj.js';
|
||||||
import ViewHint from '../../../../../src/ol/ViewHint.js';
|
import ViewHint from '../../../../../src/ol/ViewHint.js';
|
||||||
import {POINT_VERTEX_STRIDE, WebGLWorkerMessageType} from '../../../../../src/ol/renderer/webgl/Layer.js';
|
import {WebGLWorkerMessageType} from '../../../../../src/ol/renderer/webgl/Layer.js';
|
||||||
import {create as createTransform, compose as composeTransform} from '../../../../../src/ol/transform.js';
|
import {compose as composeTransform, create as createTransform} from '../../../../../src/ol/transform.js';
|
||||||
|
|
||||||
const baseFrameState = {
|
const baseFrameState = {
|
||||||
viewHints: [],
|
viewHints: [],
|
||||||
@@ -21,6 +21,54 @@ const baseFrameState = {
|
|||||||
pixelRatio: 1
|
pixelRatio: 1
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const simpleVertexShader = `
|
||||||
|
precision mediump float;
|
||||||
|
uniform mat4 u_projectionMatrix;
|
||||||
|
uniform mat4 u_offsetScaleMatrix;
|
||||||
|
attribute vec2 a_position;
|
||||||
|
attribute float a_index;
|
||||||
|
|
||||||
|
void main(void) {
|
||||||
|
mat4 offsetMatrix = u_offsetScaleMatrix;
|
||||||
|
float offsetX = a_index == 0.0 || a_index == 3.0 ? -2.0 : 2.0;
|
||||||
|
float offsetY = a_index == 0.0 || a_index == 1.0 ? -2.0 : 2.0;
|
||||||
|
vec4 offsets = offsetMatrix * vec4(offsetX, offsetY, 0.0, 0.0);
|
||||||
|
gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets;
|
||||||
|
}`;
|
||||||
|
const simpleFragmentShader = `
|
||||||
|
precision mediump float;
|
||||||
|
|
||||||
|
void main(void) {
|
||||||
|
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
|
||||||
|
}`;
|
||||||
|
|
||||||
|
// these shaders support hit detection
|
||||||
|
// they have a built-in size value of 4
|
||||||
|
const hitVertexShader = `
|
||||||
|
precision mediump float;
|
||||||
|
uniform mat4 u_projectionMatrix;
|
||||||
|
uniform mat4 u_offsetScaleMatrix;
|
||||||
|
attribute vec2 a_position;
|
||||||
|
attribute float a_index;
|
||||||
|
attribute vec4 a_hitColor;
|
||||||
|
varying vec4 v_hitColor;
|
||||||
|
|
||||||
|
void main(void) {
|
||||||
|
mat4 offsetMatrix = u_offsetScaleMatrix;
|
||||||
|
float offsetX = a_index == 0.0 || a_index == 3.0 ? -2.0 : 2.0;
|
||||||
|
float offsetY = a_index == 0.0 || a_index == 1.0 ? -2.0 : 2.0;
|
||||||
|
vec4 offsets = offsetMatrix * vec4(offsetX, offsetY, 0.0, 0.0);
|
||||||
|
gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets;
|
||||||
|
v_hitColor = a_hitColor;
|
||||||
|
}`;
|
||||||
|
const hitFragmentShader = `
|
||||||
|
precision mediump float;
|
||||||
|
varying vec4 v_hitColor;
|
||||||
|
|
||||||
|
void main(void) {
|
||||||
|
gl_FragColor = v_hitColor;
|
||||||
|
}`;
|
||||||
|
|
||||||
describe('ol.renderer.webgl.PointsLayer', function() {
|
describe('ol.renderer.webgl.PointsLayer', function() {
|
||||||
|
|
||||||
describe('constructor', function() {
|
describe('constructor', function() {
|
||||||
@@ -42,7 +90,10 @@ describe('ol.renderer.webgl.PointsLayer', function() {
|
|||||||
const layer = new VectorLayer({
|
const layer = new VectorLayer({
|
||||||
source: new VectorSource()
|
source: new VectorSource()
|
||||||
});
|
});
|
||||||
const renderer = new WebGLPointsLayerRenderer(layer);
|
const renderer = new WebGLPointsLayerRenderer(layer, {
|
||||||
|
vertexShader: simpleVertexShader,
|
||||||
|
fragmentShader: simpleFragmentShader
|
||||||
|
});
|
||||||
expect(renderer).to.be.a(WebGLPointsLayerRenderer);
|
expect(renderer).to.be.a(WebGLPointsLayerRenderer);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -55,7 +106,12 @@ describe('ol.renderer.webgl.PointsLayer', function() {
|
|||||||
layer = new VectorLayer({
|
layer = new VectorLayer({
|
||||||
source: new VectorSource()
|
source: new VectorSource()
|
||||||
});
|
});
|
||||||
renderer = new WebGLPointsLayerRenderer(layer);
|
renderer = new WebGLPointsLayerRenderer(layer, {
|
||||||
|
vertexShader: simpleVertexShader,
|
||||||
|
fragmentShader: simpleFragmentShader,
|
||||||
|
hitVertexShader: hitVertexShader,
|
||||||
|
hitFragmentShader: hitFragmentShader
|
||||||
|
});
|
||||||
frameState = Object.assign({
|
frameState = Object.assign({
|
||||||
size: [2, 2],
|
size: [2, 2],
|
||||||
extent: [-100, -100, 100, 100]
|
extent: [-100, -100, 100, 100]
|
||||||
@@ -77,7 +133,7 @@ describe('ol.renderer.webgl.PointsLayer', function() {
|
|||||||
}));
|
}));
|
||||||
renderer.prepareFrame(frameState);
|
renderer.prepareFrame(frameState);
|
||||||
|
|
||||||
const attributePerVertex = POINT_VERTEX_STRIDE;
|
const attributePerVertex = 3;
|
||||||
|
|
||||||
renderer.worker_.addEventListener('message', function(event) {
|
renderer.worker_.addEventListener('message', function(event) {
|
||||||
if (event.data.type !== WebGLWorkerMessageType.GENERATE_BUFFERS) {
|
if (event.data.type !== WebGLWorkerMessageType.GENERATE_BUFFERS) {
|
||||||
@@ -94,6 +150,35 @@ describe('ol.renderer.webgl.PointsLayer', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('fills up the hit render buffer with 2 triangles per point', function(done) {
|
||||||
|
layer.getSource().addFeature(new Feature({
|
||||||
|
geometry: new Point([10, 20])
|
||||||
|
}));
|
||||||
|
layer.getSource().addFeature(new Feature({
|
||||||
|
geometry: new Point([30, 40])
|
||||||
|
}));
|
||||||
|
renderer.prepareFrame(frameState);
|
||||||
|
|
||||||
|
const attributePerVertex = 8;
|
||||||
|
|
||||||
|
renderer.worker_.addEventListener('message', function(event) {
|
||||||
|
if (event.data.type !== WebGLWorkerMessageType.GENERATE_BUFFERS) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!renderer.hitVerticesBuffer_.getArray()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
expect(renderer.hitVerticesBuffer_.getArray().length).to.eql(2 * 4 * attributePerVertex);
|
||||||
|
expect(renderer.indicesBuffer_.getArray().length).to.eql(2 * 6);
|
||||||
|
|
||||||
|
expect(renderer.hitVerticesBuffer_.getArray()[0]).to.eql(10);
|
||||||
|
expect(renderer.hitVerticesBuffer_.getArray()[1]).to.eql(20);
|
||||||
|
expect(renderer.hitVerticesBuffer_.getArray()[4 * attributePerVertex + 0]).to.eql(30);
|
||||||
|
expect(renderer.hitVerticesBuffer_.getArray()[4 * attributePerVertex + 1]).to.eql(40);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('clears the buffers when the features are gone', function(done) {
|
it('clears the buffers when the features are gone', function(done) {
|
||||||
const source = layer.getSource();
|
const source = layer.getSource();
|
||||||
source.addFeature(new Feature({
|
source.addFeature(new Feature({
|
||||||
@@ -109,7 +194,7 @@ describe('ol.renderer.webgl.PointsLayer', function() {
|
|||||||
if (event.data.type !== WebGLWorkerMessageType.GENERATE_BUFFERS) {
|
if (event.data.type !== WebGLWorkerMessageType.GENERATE_BUFFERS) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const attributePerVertex = 12;
|
const attributePerVertex = 3;
|
||||||
expect(renderer.verticesBuffer_.getArray().length).to.eql(4 * attributePerVertex);
|
expect(renderer.verticesBuffer_.getArray().length).to.eql(4 * attributePerVertex);
|
||||||
expect(renderer.indicesBuffer_.getArray().length).to.eql(6);
|
expect(renderer.indicesBuffer_.getArray().length).to.eql(6);
|
||||||
done();
|
done();
|
||||||
@@ -162,9 +247,10 @@ describe('ol.renderer.webgl.PointsLayer', function() {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
renderer = new WebGLPointsLayerRenderer(layer, {
|
renderer = new WebGLPointsLayerRenderer(layer, {
|
||||||
sizeCallback: function() {
|
vertexShader: simpleVertexShader,
|
||||||
return 4;
|
fragmentShader: simpleFragmentShader,
|
||||||
}
|
hitVertexShader: hitVertexShader,
|
||||||
|
hitFragmentShader: hitFragmentShader
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -254,4 +340,22 @@ describe('ol.renderer.webgl.PointsLayer', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#disposeInternal', function() {
|
||||||
|
it('terminates the worker and calls dispose on the helper', function() {
|
||||||
|
const layer = new VectorLayer({
|
||||||
|
source: new VectorSource()
|
||||||
|
});
|
||||||
|
const renderer = new WebGLPointsLayerRenderer(layer, {
|
||||||
|
vertexShader: simpleVertexShader,
|
||||||
|
fragmentShader: simpleFragmentShader
|
||||||
|
});
|
||||||
|
|
||||||
|
const spyHelper = sinon.spy(renderer.helper, 'disposeInternal');
|
||||||
|
const spyWorker = sinon.spy(renderer.worker_, 'terminate');
|
||||||
|
renderer.disposeInternal();
|
||||||
|
expect(spyHelper.called).to.be(true);
|
||||||
|
expect(spyWorker.called).to.be(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ import WebGLHelper from '../../../../src/ol/webgl/Helper.js';
|
|||||||
import {
|
import {
|
||||||
create as createTransform,
|
create as createTransform,
|
||||||
rotate as rotateTransform,
|
rotate as rotateTransform,
|
||||||
scale as scaleTransform, translate as translateTransform
|
scale as scaleTransform,
|
||||||
|
translate as translateTransform
|
||||||
} from '../../../../src/ol/transform.js';
|
} from '../../../../src/ol/transform.js';
|
||||||
|
import {FLOAT} from '../../../../src/ol/webgl.js';
|
||||||
|
|
||||||
|
|
||||||
const VERTEX_SHADER = `
|
const VERTEX_SHADER = `
|
||||||
@@ -290,4 +292,64 @@ describe('ol.webgl.WebGLHelper', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#enableAttributes', function() {
|
||||||
|
let baseAttrs, h;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
h = new WebGLHelper();
|
||||||
|
baseAttrs = [
|
||||||
|
{
|
||||||
|
name: 'attr1',
|
||||||
|
size: 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'attr2',
|
||||||
|
size: 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'attr3',
|
||||||
|
size: 1
|
||||||
|
}
|
||||||
|
];
|
||||||
|
h.useProgram(h.getProgram(FRAGMENT_SHADER, `
|
||||||
|
precision mediump float;
|
||||||
|
|
||||||
|
uniform mat4 u_projectionMatrix;
|
||||||
|
uniform mat4 u_offsetScaleMatrix;
|
||||||
|
uniform mat4 u_offsetRotateMatrix;
|
||||||
|
|
||||||
|
attribute vec3 attr1;
|
||||||
|
attribute vec2 attr2;
|
||||||
|
attribute float attr3;
|
||||||
|
uniform float u_test;
|
||||||
|
|
||||||
|
void main(void) {
|
||||||
|
gl_Position = vec4(u_test, a_test, 0.0, 1.0);
|
||||||
|
}`));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('enables attributes based on the given array (FLOAT)', function() {
|
||||||
|
const spy = sinon.spy(h, 'enableAttributeArray_');
|
||||||
|
h.enableAttributes(baseAttrs);
|
||||||
|
const bytesPerFloat = Float32Array.BYTES_PER_ELEMENT;
|
||||||
|
|
||||||
|
expect(spy.callCount).to.eql(3);
|
||||||
|
expect(spy.getCall(0).args[0]).to.eql('attr1');
|
||||||
|
expect(spy.getCall(0).args[1]).to.eql(3);
|
||||||
|
expect(spy.getCall(0).args[2]).to.eql(FLOAT);
|
||||||
|
expect(spy.getCall(0).args[3]).to.eql(6 * bytesPerFloat);
|
||||||
|
expect(spy.getCall(0).args[4]).to.eql(0);
|
||||||
|
expect(spy.getCall(1).args[0]).to.eql('attr2');
|
||||||
|
expect(spy.getCall(1).args[1]).to.eql(2);
|
||||||
|
expect(spy.getCall(1).args[2]).to.eql(FLOAT);
|
||||||
|
expect(spy.getCall(1).args[3]).to.eql(6 * bytesPerFloat);
|
||||||
|
expect(spy.getCall(1).args[4]).to.eql(3 * bytesPerFloat);
|
||||||
|
expect(spy.getCall(2).args[0]).to.eql('attr3');
|
||||||
|
expect(spy.getCall(2).args[1]).to.eql(1);
|
||||||
|
expect(spy.getCall(2).args[2]).to.eql(FLOAT);
|
||||||
|
expect(spy.getCall(2).args[3]).to.eql(6 * bytesPerFloat);
|
||||||
|
expect(spy.getCall(2).args[4]).to.eql(5 * bytesPerFloat);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,199 @@
|
|||||||
|
import {
|
||||||
|
getSymbolVertexShader,
|
||||||
|
formatNumber,
|
||||||
|
getSymbolFragmentShader,
|
||||||
|
formatColor, formatArray
|
||||||
|
} from '../../../../src/ol/webgl/ShaderBuilder.js';
|
||||||
|
|
||||||
|
describe('ol.webgl.ShaderBuilder', function() {
|
||||||
|
|
||||||
|
describe('formatNumber', function() {
|
||||||
|
it('does a simple transform when a fraction is present', function() {
|
||||||
|
expect(formatNumber(1.3456)).to.eql('1.3456');
|
||||||
|
});
|
||||||
|
it('adds a fraction separator when missing', function() {
|
||||||
|
expect(formatNumber(1)).to.eql('1.0');
|
||||||
|
expect(formatNumber(2.0)).to.eql('2.0');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('formatArray', function() {
|
||||||
|
it('outputs numbers with dot separators', function() {
|
||||||
|
expect(formatArray([1, 0, 3.45, 0.8888])).to.eql('1.0, 0.0, 3.45, 0.8888');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('formatColor', function() {
|
||||||
|
it('normalizes color and outputs numbers with dot separators', function() {
|
||||||
|
expect(formatColor([100, 0, 255, 1])).to.eql('0.39215686274509803, 0.0, 1.0, 1.0');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getSymbolVertexShader', function() {
|
||||||
|
it('generates a symbol vertex shader (with varying)', function() {
|
||||||
|
const parameters = {
|
||||||
|
varyings: [{
|
||||||
|
name: 'v_opacity',
|
||||||
|
type: 'float',
|
||||||
|
expression: formatNumber(0.4)
|
||||||
|
}, {
|
||||||
|
name: 'v_test',
|
||||||
|
type: 'vec3',
|
||||||
|
expression: 'vec3(' + formatArray([1, 2, 3]) + ')'
|
||||||
|
}],
|
||||||
|
sizeExpression: 'vec2(' + formatNumber(6) + ')',
|
||||||
|
offsetExpression: 'vec2(' + formatArray([5, -7]) + ')',
|
||||||
|
colorExpression: 'vec4(' + formatColor([80, 0, 255, 1]) + ')',
|
||||||
|
texCoordExpression: 'vec4(' + formatArray([0, 0.5, 0.5, 1]) + ')',
|
||||||
|
rotateWithView: false
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(getSymbolVertexShader(parameters)).to.eql(`precision mediump float;
|
||||||
|
uniform mat4 u_projectionMatrix;
|
||||||
|
uniform mat4 u_offsetScaleMatrix;
|
||||||
|
uniform mat4 u_offsetRotateMatrix;
|
||||||
|
|
||||||
|
attribute vec2 a_position;
|
||||||
|
attribute float a_index;
|
||||||
|
|
||||||
|
varying vec2 v_texCoord;
|
||||||
|
varying float v_opacity;
|
||||||
|
varying vec3 v_test;
|
||||||
|
void main(void) {
|
||||||
|
mat4 offsetMatrix = u_offsetScaleMatrix;
|
||||||
|
vec2 size = vec2(6.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 offsetY = a_index == 0.0 || a_index == 1.0 ? offset.y - size.y / 2.0 : offset.y + size.y / 2.0;
|
||||||
|
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);
|
||||||
|
float u = a_index == 0.0 || a_index == 3.0 ? texCoord.s : texCoord.q;
|
||||||
|
float v = a_index == 2.0 || a_index == 3.0 ? texCoord.t : texCoord.p;
|
||||||
|
v_texCoord = vec2(u, v);
|
||||||
|
v_opacity = 0.4;
|
||||||
|
v_test = vec3(1.0, 2.0, 3.0);
|
||||||
|
}`);
|
||||||
|
});
|
||||||
|
it('generates a symbol vertex shader (with uniforms and attributes)', function() {
|
||||||
|
const parameters = {
|
||||||
|
uniforms: ['float u_myUniform'],
|
||||||
|
attributes: ['vec2 a_myAttr'],
|
||||||
|
sizeExpression: 'vec2(' + formatNumber(6) + ')',
|
||||||
|
offsetExpression: 'vec2(' + formatArray([5, -7]) + ')',
|
||||||
|
colorExpression: 'vec4(' + formatColor([80, 0, 255, 1]) + ')',
|
||||||
|
texCoordExpression: 'vec4(' + formatArray([0, 0.5, 0.5, 1]) + ')'
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(getSymbolVertexShader(parameters)).to.eql(`precision mediump float;
|
||||||
|
uniform mat4 u_projectionMatrix;
|
||||||
|
uniform mat4 u_offsetScaleMatrix;
|
||||||
|
uniform mat4 u_offsetRotateMatrix;
|
||||||
|
uniform float u_myUniform;
|
||||||
|
attribute vec2 a_position;
|
||||||
|
attribute float a_index;
|
||||||
|
attribute vec2 a_myAttr;
|
||||||
|
varying vec2 v_texCoord;
|
||||||
|
|
||||||
|
void main(void) {
|
||||||
|
mat4 offsetMatrix = u_offsetScaleMatrix;
|
||||||
|
vec2 size = vec2(6.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 offsetY = a_index == 0.0 || a_index == 1.0 ? offset.y - size.y / 2.0 : offset.y + size.y / 2.0;
|
||||||
|
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);
|
||||||
|
float u = a_index == 0.0 || a_index == 3.0 ? texCoord.s : texCoord.q;
|
||||||
|
float v = a_index == 2.0 || a_index == 3.0 ? texCoord.t : texCoord.p;
|
||||||
|
v_texCoord = vec2(u, v);
|
||||||
|
|
||||||
|
}`);
|
||||||
|
});
|
||||||
|
it('generates a symbol vertex shader (with rotateWithView)', function() {
|
||||||
|
const parameters = {
|
||||||
|
sizeExpression: 'vec2(' + formatNumber(6) + ')',
|
||||||
|
offsetExpression: 'vec2(' + formatArray([5, -7]) + ')',
|
||||||
|
colorExpression: 'vec4(' + formatColor([80, 0, 255, 1]) + ')',
|
||||||
|
texCoordExpression: 'vec4(' + formatArray([0, 0.5, 0.5, 1]) + ')',
|
||||||
|
rotateWithView: true
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(getSymbolVertexShader(parameters)).to.eql(`precision mediump float;
|
||||||
|
uniform mat4 u_projectionMatrix;
|
||||||
|
uniform mat4 u_offsetScaleMatrix;
|
||||||
|
uniform mat4 u_offsetRotateMatrix;
|
||||||
|
|
||||||
|
attribute vec2 a_position;
|
||||||
|
attribute float a_index;
|
||||||
|
|
||||||
|
varying vec2 v_texCoord;
|
||||||
|
|
||||||
|
void main(void) {
|
||||||
|
mat4 offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;
|
||||||
|
vec2 size = vec2(6.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 offsetY = a_index == 0.0 || a_index == 1.0 ? offset.y - size.y / 2.0 : offset.y + size.y / 2.0;
|
||||||
|
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);
|
||||||
|
float u = a_index == 0.0 || a_index == 3.0 ? texCoord.s : texCoord.q;
|
||||||
|
float v = a_index == 2.0 || a_index == 3.0 ? texCoord.t : texCoord.p;
|
||||||
|
v_texCoord = vec2(u, v);
|
||||||
|
|
||||||
|
}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getSymbolFragmentShader', function() {
|
||||||
|
it('generates a symbol fragment shader (with varying)', function() {
|
||||||
|
const parameters = {
|
||||||
|
varyings: [{
|
||||||
|
name: 'v_opacity',
|
||||||
|
type: 'float',
|
||||||
|
expression: formatNumber(0.4)
|
||||||
|
}, {
|
||||||
|
name: 'v_test',
|
||||||
|
type: 'vec3',
|
||||||
|
expression: 'vec3(' + formatArray([1, 2, 3]) + ')'
|
||||||
|
}],
|
||||||
|
sizeExpression: 'vec2(' + formatNumber(6) + ')',
|
||||||
|
offsetExpression: 'vec2(' + formatArray([5, -7]) + ')',
|
||||||
|
colorExpression: 'vec4(' + formatColor([80, 0, 255]) + ', v_opacity)',
|
||||||
|
texCoordExpression: 'vec4(' + formatArray([0, 0.5, 0.5, 1]) + ')',
|
||||||
|
rotateWithView: false
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(getSymbolFragmentShader(parameters)).to.eql(`precision mediump float;
|
||||||
|
|
||||||
|
varying vec2 v_texCoord;
|
||||||
|
varying float v_opacity;
|
||||||
|
varying vec3 v_test;
|
||||||
|
void main(void) {
|
||||||
|
gl_FragColor = vec4(0.3137254901960784, 0.0, 1.0, v_opacity);
|
||||||
|
gl_FragColor.rgb *= gl_FragColor.a;
|
||||||
|
}`);
|
||||||
|
});
|
||||||
|
it('generates a symbol fragment shader (with uniforms)', function() {
|
||||||
|
const parameters = {
|
||||||
|
uniforms: ['float u_myUniform', 'vec2 u_myUniform2'],
|
||||||
|
sizeExpression: 'vec2(' + formatNumber(6) + ')',
|
||||||
|
offsetExpression: 'vec2(' + formatArray([5, -7]) + ')',
|
||||||
|
colorExpression: 'vec4(' + formatColor([255, 255, 255, 1]) + ')',
|
||||||
|
texCoordExpression: 'vec4(' + formatArray([0, 0.5, 0.5, 1]) + ')'
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(getSymbolFragmentShader(parameters)).to.eql(`precision mediump float;
|
||||||
|
uniform float u_myUniform;
|
||||||
|
uniform vec2 u_myUniform2;
|
||||||
|
varying vec2 v_texCoord;
|
||||||
|
|
||||||
|
void main(void) {
|
||||||
|
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
|
||||||
|
gl_FragColor.rgb *= gl_FragColor.a;
|
||||||
|
}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import {create} from '../../../../src/ol/worker/webgl.js';
|
import {create} from '../../../../src/ol/worker/webgl.js';
|
||||||
import {
|
import {
|
||||||
POINT_INSTRUCTIONS_COUNT,
|
|
||||||
WebGLWorkerMessageType
|
WebGLWorkerMessageType
|
||||||
} from '../../../../src/ol/renderer/webgl/Layer.js';
|
} from '../../../../src/ol/renderer/webgl/Layer.js';
|
||||||
|
|
||||||
@@ -22,7 +21,9 @@ describe('ol/worker/webgl', function() {
|
|||||||
describe('messaging', function() {
|
describe('messaging', function() {
|
||||||
describe('GENERATE_BUFFERS', function() {
|
describe('GENERATE_BUFFERS', function() {
|
||||||
it('responds with buffer data', function(done) {
|
it('responds with buffer data', function(done) {
|
||||||
worker.addEventListener('error', done);
|
worker.addEventListener('error', function(error) {
|
||||||
|
expect().fail(error.message);
|
||||||
|
});
|
||||||
|
|
||||||
worker.addEventListener('message', function(event) {
|
worker.addEventListener('message', function(event) {
|
||||||
expect(event.data.type).to.eql(WebGLWorkerMessageType.GENERATE_BUFFERS);
|
expect(event.data.type).to.eql(WebGLWorkerMessageType.GENERATE_BUFFERS);
|
||||||
@@ -34,11 +35,12 @@ describe('ol/worker/webgl', function() {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
const instructions = new Float32Array(POINT_INSTRUCTIONS_COUNT);
|
const instructions = new Float32Array(10);
|
||||||
|
|
||||||
const message = {
|
const message = {
|
||||||
type: WebGLWorkerMessageType.GENERATE_BUFFERS,
|
type: WebGLWorkerMessageType.GENERATE_BUFFERS,
|
||||||
renderInstructions: instructions,
|
renderInstructions: instructions,
|
||||||
|
customAttributesCount: 0,
|
||||||
testInt: 101,
|
testInt: 101,
|
||||||
testString: 'abcd'
|
testString: 'abcd'
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user