Merge pull request #13461 from jahow/webgl-shape-renderer
WebGL vector renderer for polygons, lines and points
This commit is contained in:
10
examples/webgl-vector-layer.html
Normal file
10
examples/webgl-vector-layer.html
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
layout: example.html
|
||||||
|
title: WebGL Vector Layer
|
||||||
|
shortdesc: Example of a vector layer rendered using WebGL
|
||||||
|
docs: >
|
||||||
|
The ecoregions are loaded from a GeoJSON file.
|
||||||
|
tags: "vector, geojson, webgl"
|
||||||
|
experimental: true
|
||||||
|
---
|
||||||
|
<div id="map" class="map"></div>
|
||||||
64
examples/webgl-vector-layer.js
Normal file
64
examples/webgl-vector-layer.js
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import GeoJSON from '../src/ol/format/GeoJSON.js';
|
||||||
|
import Layer from '../src/ol/layer/Layer.js';
|
||||||
|
import Map from '../src/ol/Map.js';
|
||||||
|
import OSM from '../src/ol/source/OSM.js';
|
||||||
|
import TileLayer from '../src/ol/layer/WebGLTile.js';
|
||||||
|
import VectorSource from '../src/ol/source/Vector.js';
|
||||||
|
import View from '../src/ol/View.js';
|
||||||
|
import WebGLVectorLayerRenderer from '../src/ol/renderer/webgl/VectorLayer.js';
|
||||||
|
import {asArray} from '../src/ol/color.js';
|
||||||
|
import {packColor} from '../src/ol/renderer/webgl/shaders.js';
|
||||||
|
|
||||||
|
class WebGLLayer extends Layer {
|
||||||
|
createRenderer() {
|
||||||
|
return new WebGLVectorLayerRenderer(this, {
|
||||||
|
fill: {
|
||||||
|
attributes: {
|
||||||
|
color: function (feature) {
|
||||||
|
const color = asArray(feature.get('COLOR') || '#eee');
|
||||||
|
color[3] = 0.85;
|
||||||
|
return packColor(color);
|
||||||
|
},
|
||||||
|
opacity: function () {
|
||||||
|
return 0.6;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
stroke: {
|
||||||
|
attributes: {
|
||||||
|
color: function (feature) {
|
||||||
|
const color = [...asArray(feature.get('COLOR') || '#eee')];
|
||||||
|
color.forEach((_, i) => (color[i] = Math.round(color[i] * 0.75))); // darken slightly
|
||||||
|
return packColor(color);
|
||||||
|
},
|
||||||
|
width: function () {
|
||||||
|
return 1.5;
|
||||||
|
},
|
||||||
|
opacity: function () {
|
||||||
|
return 1;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const osm = new TileLayer({
|
||||||
|
source: new OSM(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const vectorLayer = new WebGLLayer({
|
||||||
|
source: new VectorSource({
|
||||||
|
url: 'https://openlayers.org/data/vector/ecoregions.json',
|
||||||
|
format: new GeoJSON(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const map = new Map({
|
||||||
|
layers: [osm, vectorLayer],
|
||||||
|
target: 'map',
|
||||||
|
view: new View({
|
||||||
|
center: [0, 0],
|
||||||
|
zoom: 1,
|
||||||
|
}),
|
||||||
|
});
|
||||||
11
package-lock.json
generated
11
package-lock.json
generated
@@ -9,6 +9,7 @@
|
|||||||
"version": "7.0.0-dev",
|
"version": "7.0.0-dev",
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"earcut": "^2.2.3",
|
||||||
"geotiff": "2.0.4",
|
"geotiff": "2.0.4",
|
||||||
"ol-mapbox-style": "^8.0.5",
|
"ol-mapbox-style": "^8.0.5",
|
||||||
"pbf": "3.2.1",
|
"pbf": "3.2.1",
|
||||||
@@ -3838,6 +3839,11 @@
|
|||||||
"void-elements": "^2.0.0"
|
"void-elements": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/earcut": {
|
||||||
|
"version": "2.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.3.tgz",
|
||||||
|
"integrity": "sha512-iRDI1QeCQIhMCZk48DRDMVgQSSBDmbzzNhnxIo+pwx3swkfjMh6vh0nWLq1NdvGHLKH6wIrAM3vQWeTj6qeoug=="
|
||||||
|
},
|
||||||
"node_modules/ee-first": {
|
"node_modules/ee-first": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||||
@@ -13182,6 +13188,11 @@
|
|||||||
"void-elements": "^2.0.0"
|
"void-elements": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"earcut": {
|
||||||
|
"version": "2.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.3.tgz",
|
||||||
|
"integrity": "sha512-iRDI1QeCQIhMCZk48DRDMVgQSSBDmbzzNhnxIo+pwx3swkfjMh6vh0nWLq1NdvGHLKH6wIrAM3vQWeTj6qeoug=="
|
||||||
|
},
|
||||||
"ee-first": {
|
"ee-first": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||||
|
|||||||
@@ -45,6 +45,7 @@
|
|||||||
"url": "https://opencollective.com/openlayers"
|
"url": "https://opencollective.com/openlayers"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"earcut": "^2.2.3",
|
||||||
"geotiff": "2.0.4",
|
"geotiff": "2.0.4",
|
||||||
"ol-mapbox-style": "^8.0.5",
|
"ol-mapbox-style": "^8.0.5",
|
||||||
"pbf": "3.2.1",
|
"pbf": "3.2.1",
|
||||||
|
|||||||
201
src/ol/render/webgl/BatchRenderer.js
Normal file
201
src/ol/render/webgl/BatchRenderer.js
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
/**
|
||||||
|
* @module ol/render/webgl/BatchRenderer
|
||||||
|
*/
|
||||||
|
import {WebGLWorkerMessageType} from './constants.js';
|
||||||
|
import {abstract} from '../../util.js';
|
||||||
|
import {
|
||||||
|
create as createTransform,
|
||||||
|
makeInverse as makeInverseTransform,
|
||||||
|
multiply as multiplyTransform,
|
||||||
|
translate as translateTransform,
|
||||||
|
} from '../../transform.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} CustomAttribute A description of a custom attribute to be passed on to the GPU, with a value different
|
||||||
|
* for each feature.
|
||||||
|
* @property {string} name Attribute name.
|
||||||
|
* @property {function(import("../../Feature").default):number} callback This callback computes the numerical value of the
|
||||||
|
* attribute for a given feature.
|
||||||
|
*/
|
||||||
|
|
||||||
|
let workerMessageCounter = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @classdesc Abstract class for batch renderers.
|
||||||
|
* Batch renderers are meant to render the geometries contained in a {@link module:ol/render/webgl/GeometryBatch}
|
||||||
|
* instance. They are responsible for generating render instructions and transforming them into WebGL buffers.
|
||||||
|
*/
|
||||||
|
class AbstractBatchRenderer {
|
||||||
|
/**
|
||||||
|
* @param {import("../../webgl/Helper.js").default} helper WebGL helper instance
|
||||||
|
* @param {Worker} worker WebGL worker instance
|
||||||
|
* @param {string} vertexShader Vertex shader
|
||||||
|
* @param {string} fragmentShader Fragment shader
|
||||||
|
* @param {Array<CustomAttribute>} customAttributes List of custom attributes
|
||||||
|
*/
|
||||||
|
constructor(helper, worker, vertexShader, fragmentShader, customAttributes) {
|
||||||
|
/**
|
||||||
|
* @type {import("../../webgl/Helper.js").default}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.helper_ = helper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Worker}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.worker_ = worker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {WebGLProgram}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.program_ = this.helper_.getProgram(fragmentShader, vertexShader);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of attributes used by the renderer.
|
||||||
|
* @type {Array<import('../../webgl/Helper.js').AttributeDescription>}
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
this.attributes = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Array<CustomAttribute>}
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
this.customAttributes = customAttributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rebuild rendering instructions and webgl buffers based on the provided frame state
|
||||||
|
* Note: this is a costly operation.
|
||||||
|
* @param {import("./MixedGeometryBatch.js").GeometryBatch} batch Geometry batch
|
||||||
|
* @param {import("../../PluggableMap").FrameState} frameState Frame state.
|
||||||
|
* @param {import("../../geom/Geometry.js").Type} geometryType Geometry type
|
||||||
|
* @param {function(): void} callback Function called once the render buffers are updated
|
||||||
|
*/
|
||||||
|
rebuild(batch, frameState, geometryType, callback) {
|
||||||
|
// store transform for rendering instructions
|
||||||
|
batch.renderInstructionsTransform = this.helper_.makeProjectionTransform(
|
||||||
|
frameState,
|
||||||
|
createTransform()
|
||||||
|
);
|
||||||
|
this.generateRenderInstructions(batch);
|
||||||
|
this.generateBuffers_(batch, geometryType, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the geometries in the batch. This will also update the current transform used for rendering according to
|
||||||
|
* the invert transform of the webgl buffers
|
||||||
|
* @param {import("./MixedGeometryBatch.js").GeometryBatch} batch Geometry batch
|
||||||
|
* @param {import("../../transform.js").Transform} currentTransform Transform
|
||||||
|
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
|
||||||
|
* @param {number} offsetX X offset
|
||||||
|
*/
|
||||||
|
render(batch, currentTransform, frameState, offsetX) {
|
||||||
|
// multiply the current projection transform with the invert of the one used to fill buffers
|
||||||
|
this.helper_.makeProjectionTransform(frameState, currentTransform);
|
||||||
|
translateTransform(currentTransform, offsetX, 0);
|
||||||
|
multiplyTransform(currentTransform, batch.invertVerticesBufferTransform);
|
||||||
|
|
||||||
|
// enable program, buffers and attributes
|
||||||
|
this.helper_.useProgram(this.program_, frameState);
|
||||||
|
this.helper_.bindBuffer(batch.verticesBuffer);
|
||||||
|
this.helper_.bindBuffer(batch.indicesBuffer);
|
||||||
|
this.helper_.enableAttributes(this.attributes);
|
||||||
|
|
||||||
|
const renderCount = batch.indicesBuffer.getSize();
|
||||||
|
this.helper_.drawElements(0, renderCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rebuild rendering instructions based on the provided frame state
|
||||||
|
* This is specific to the geometry type and has to be implemented by subclasses.
|
||||||
|
* @param {import("./MixedGeometryBatch.js").GeometryBatch} batch Geometry batch
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
generateRenderInstructions(batch) {
|
||||||
|
abstract();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rebuild internal webgl buffers for rendering based on the current rendering instructions;
|
||||||
|
* This is asynchronous: webgl buffers wil _not_ be updated right away
|
||||||
|
* @param {import("./MixedGeometryBatch.js").GeometryBatch} batch Geometry batch
|
||||||
|
* @param {import("../../geom/Geometry.js").Type} geometryType Geometry type
|
||||||
|
* @param {function(): void} callback Function called once the render buffers are updated
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
generateBuffers_(batch, geometryType, callback) {
|
||||||
|
const messageId = workerMessageCounter++;
|
||||||
|
|
||||||
|
let messageType;
|
||||||
|
switch (geometryType) {
|
||||||
|
case 'Polygon':
|
||||||
|
messageType = WebGLWorkerMessageType.GENERATE_POLYGON_BUFFERS;
|
||||||
|
break;
|
||||||
|
case 'Point':
|
||||||
|
messageType = WebGLWorkerMessageType.GENERATE_POINT_BUFFERS;
|
||||||
|
break;
|
||||||
|
case 'LineString':
|
||||||
|
messageType = WebGLWorkerMessageType.GENERATE_LINE_STRING_BUFFERS;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {import('./constants.js').WebGLWorkerGenerateBuffersMessage} */
|
||||||
|
const message = {
|
||||||
|
id: messageId,
|
||||||
|
type: messageType,
|
||||||
|
renderInstructions: batch.renderInstructions.buffer,
|
||||||
|
renderInstructionsTransform: batch.renderInstructionsTransform,
|
||||||
|
customAttributesCount: this.customAttributes.length,
|
||||||
|
};
|
||||||
|
this.worker_.postMessage(message, [batch.renderInstructions.buffer]);
|
||||||
|
|
||||||
|
// leave ownership of render instructions
|
||||||
|
batch.renderInstructions = null;
|
||||||
|
|
||||||
|
const handleMessage =
|
||||||
|
/**
|
||||||
|
* @param {*} event Event.
|
||||||
|
* @this {AbstractBatchRenderer}
|
||||||
|
*/
|
||||||
|
function (event) {
|
||||||
|
const received = event.data;
|
||||||
|
|
||||||
|
// this is not the response to our request: skip
|
||||||
|
if (received.id !== messageId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we've received our response: stop listening
|
||||||
|
this.worker_.removeEventListener('message', handleMessage);
|
||||||
|
|
||||||
|
// store transform & invert transform for webgl buffers
|
||||||
|
batch.verticesBufferTransform = received.renderInstructionsTransform;
|
||||||
|
makeInverseTransform(
|
||||||
|
batch.invertVerticesBufferTransform,
|
||||||
|
batch.verticesBufferTransform
|
||||||
|
);
|
||||||
|
|
||||||
|
// copy & flush received buffers to GPU
|
||||||
|
batch.verticesBuffer.fromArrayBuffer(received.vertexBuffer);
|
||||||
|
this.helper_.flushBufferData(batch.verticesBuffer);
|
||||||
|
batch.indicesBuffer.fromArrayBuffer(received.indexBuffer);
|
||||||
|
this.helper_.flushBufferData(batch.indicesBuffer);
|
||||||
|
|
||||||
|
// take back ownership of the render instructions for further use
|
||||||
|
batch.renderInstructions = new Float32Array(
|
||||||
|
received.renderInstructions
|
||||||
|
);
|
||||||
|
|
||||||
|
callback();
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
|
this.worker_.addEventListener('message', handleMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AbstractBatchRenderer;
|
||||||
116
src/ol/render/webgl/LineStringBatchRenderer.js
Normal file
116
src/ol/render/webgl/LineStringBatchRenderer.js
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
/**
|
||||||
|
* @module ol/render/webgl/LineStringBatchRenderer
|
||||||
|
*/
|
||||||
|
import AbstractBatchRenderer from './BatchRenderer.js';
|
||||||
|
import {AttributeType} from '../../webgl/Helper.js';
|
||||||
|
import {transform2D} from '../../geom/flat/transform.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Names of attributes made available to the vertex shader.
|
||||||
|
* Please note: changing these *will* break custom shaders!
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
export const Attributes = {
|
||||||
|
SEGMENT_START: 'a_segmentStart',
|
||||||
|
SEGMENT_END: 'a_segmentEnd',
|
||||||
|
PARAMETERS: 'a_parameters',
|
||||||
|
};
|
||||||
|
|
||||||
|
class LineStringBatchRenderer extends AbstractBatchRenderer {
|
||||||
|
/**
|
||||||
|
* @param {import("../../webgl/Helper.js").default} helper WebGL helper instance
|
||||||
|
* @param {Worker} worker WebGL worker instance
|
||||||
|
* @param {string} vertexShader Vertex shader
|
||||||
|
* @param {string} fragmentShader Fragment shader
|
||||||
|
* @param {Array<import('./BatchRenderer.js').CustomAttribute>} customAttributes List of custom attributes
|
||||||
|
*/
|
||||||
|
constructor(helper, worker, vertexShader, fragmentShader, customAttributes) {
|
||||||
|
super(helper, worker, vertexShader, fragmentShader, customAttributes);
|
||||||
|
|
||||||
|
// vertices for lines must hold both a position (x,y) and an offset (dx,dy)
|
||||||
|
this.attributes = [
|
||||||
|
{
|
||||||
|
name: Attributes.SEGMENT_START,
|
||||||
|
size: 2,
|
||||||
|
type: AttributeType.FLOAT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: Attributes.SEGMENT_END,
|
||||||
|
size: 2,
|
||||||
|
type: AttributeType.FLOAT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: Attributes.PARAMETERS,
|
||||||
|
size: 1,
|
||||||
|
type: AttributeType.FLOAT,
|
||||||
|
},
|
||||||
|
].concat(
|
||||||
|
customAttributes.map(function (attribute) {
|
||||||
|
return {
|
||||||
|
name: 'a_' + attribute.name,
|
||||||
|
size: 1,
|
||||||
|
type: AttributeType.FLOAT,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render instructions for lines are structured like so:
|
||||||
|
* [ customAttr0, ... , customAttrN, numberOfVertices0, x0, y0, ... , xN, yN, numberOfVertices1, ... ]
|
||||||
|
* @param {import("./MixedGeometryBatch.js").LineStringGeometryBatch} batch Linestring geometry batch
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
generateRenderInstructions(batch) {
|
||||||
|
// here we anticipate the amount of render instructions for lines:
|
||||||
|
// 2 instructions per vertex for position (x and y)
|
||||||
|
// + 1 instruction per line per custom attributes
|
||||||
|
// + 1 instruction per line (for vertices count)
|
||||||
|
const totalInstructionsCount =
|
||||||
|
2 * batch.verticesCount +
|
||||||
|
(1 + this.customAttributes.length) * batch.geometriesCount;
|
||||||
|
if (
|
||||||
|
!batch.renderInstructions ||
|
||||||
|
batch.renderInstructions.length !== totalInstructionsCount
|
||||||
|
) {
|
||||||
|
batch.renderInstructions = new Float32Array(totalInstructionsCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// loop on features to fill the render instructions
|
||||||
|
let batchEntry;
|
||||||
|
const flatCoords = [];
|
||||||
|
let renderIndex = 0;
|
||||||
|
let value;
|
||||||
|
for (const featureUid in batch.entries) {
|
||||||
|
batchEntry = batch.entries[featureUid];
|
||||||
|
for (let i = 0, ii = batchEntry.flatCoordss.length; i < ii; i++) {
|
||||||
|
flatCoords.length = batchEntry.flatCoordss[i].length;
|
||||||
|
transform2D(
|
||||||
|
batchEntry.flatCoordss[i],
|
||||||
|
0,
|
||||||
|
flatCoords.length,
|
||||||
|
2,
|
||||||
|
batch.renderInstructionsTransform,
|
||||||
|
flatCoords
|
||||||
|
);
|
||||||
|
|
||||||
|
// custom attributes
|
||||||
|
for (let k = 0, kk = this.customAttributes.length; k < kk; k++) {
|
||||||
|
value = this.customAttributes[k].callback(batchEntry.feature);
|
||||||
|
batch.renderInstructions[renderIndex++] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// vertices count
|
||||||
|
batch.renderInstructions[renderIndex++] = flatCoords.length / 2;
|
||||||
|
|
||||||
|
// looping on points for positions
|
||||||
|
for (let j = 0, jj = flatCoords.length; j < jj; j += 2) {
|
||||||
|
batch.renderInstructions[renderIndex++] = flatCoords[j];
|
||||||
|
batch.renderInstructions[renderIndex++] = flatCoords[j + 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LineStringBatchRenderer;
|
||||||
364
src/ol/render/webgl/MixedGeometryBatch.js
Normal file
364
src/ol/render/webgl/MixedGeometryBatch.js
Normal file
@@ -0,0 +1,364 @@
|
|||||||
|
/**
|
||||||
|
* @module ol/render/webgl/MixedGeometryBatch
|
||||||
|
*/
|
||||||
|
import WebGLArrayBuffer from '../../webgl/Buffer.js';
|
||||||
|
import {ARRAY_BUFFER, DYNAMIC_DRAW, ELEMENT_ARRAY_BUFFER} from '../../webgl.js';
|
||||||
|
import {create as createTransform} from '../../transform.js';
|
||||||
|
import {getUid} from '../../util.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} GeometryBatchItem Object that holds a reference to a feature as well as the raw coordinates of its various geometries
|
||||||
|
* @property {import("../../Feature").default} feature Feature
|
||||||
|
* @property {Array<Array<number>>} flatCoordss Array of flat coordinates arrays, one for each geometry related to the feature
|
||||||
|
* @property {number} [verticesCount] Only defined for linestring and polygon batches
|
||||||
|
* @property {number} [ringsCount] Only defined for polygon batches
|
||||||
|
* @property {Array<Array<number>>} [ringsVerticesCounts] Array of vertices counts in each ring for each geometry; only defined for polygons batches
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {PointGeometryBatch|LineStringGeometryBatch|PolygonGeometryBatch} GeometryBatch
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} PolygonGeometryBatch A geometry batch specific to polygons
|
||||||
|
* @property {Object<string, GeometryBatchItem>} entries Dictionary of all entries in the batch with associated computed values.
|
||||||
|
* One entry corresponds to one feature. Key is feature uid.
|
||||||
|
* @property {number} geometriesCount Amount of geometries in the batch.
|
||||||
|
* @property {Float32Array} renderInstructions Render instructions for polygons are structured like so:
|
||||||
|
* [ numberOfRings, numberOfVerticesInRing0, ..., numberOfVerticesInRingN, x0, y0, customAttr0, ..., xN, yN, customAttrN, numberOfRings,... ]
|
||||||
|
* @property {WebGLArrayBuffer} verticesBuffer Vertices WebGL buffer
|
||||||
|
* @property {WebGLArrayBuffer} indicesBuffer Indices WebGL buffer
|
||||||
|
* @property {import("../../transform.js").Transform} renderInstructionsTransform Converts world space coordinates to screen space; applies to the rendering instructions
|
||||||
|
* @property {import("../../transform.js").Transform} verticesBufferTransform Converts world space coordinates to screen space; applies to the webgl vertices buffer
|
||||||
|
* @property {import("../../transform.js").Transform} invertVerticesBufferTransform Screen space to world space; applies to the webgl vertices buffer
|
||||||
|
* @property {number} verticesCount Amount of vertices from geometries in the batch.
|
||||||
|
* @property {number} ringsCount How many outer and inner rings in this batch.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} LineStringGeometryBatch A geometry batch specific to lines
|
||||||
|
* @property {Object<string, GeometryBatchItem>} entries Dictionary of all entries in the batch with associated computed values.
|
||||||
|
* One entry corresponds to one feature. Key is feature uid.
|
||||||
|
* @property {number} geometriesCount Amount of geometries in the batch.
|
||||||
|
* @property {Float32Array} renderInstructions Render instructions for polygons are structured like so:
|
||||||
|
* [ numberOfRings, numberOfVerticesInRing0, ..., numberOfVerticesInRingN, x0, y0, customAttr0, ..., xN, yN, customAttrN, numberOfRings,... ]
|
||||||
|
* @property {WebGLArrayBuffer} verticesBuffer Vertices WebGL buffer
|
||||||
|
* @property {WebGLArrayBuffer} indicesBuffer Indices WebGL buffer
|
||||||
|
* @property {import("../../transform.js").Transform} renderInstructionsTransform Converts world space coordinates to screen space; applies to the rendering instructions
|
||||||
|
* @property {import("../../transform.js").Transform} verticesBufferTransform Converts world space coordinates to screen space; applies to the webgl vertices buffer
|
||||||
|
* @property {import("../../transform.js").Transform} invertVerticesBufferTransform Screen space to world space; applies to the webgl vertices buffer
|
||||||
|
* @property {number} verticesCount Amount of vertices from geometries in the batch.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} PointGeometryBatch A geometry batch specific to points
|
||||||
|
* @property {Object<string, GeometryBatchItem>} entries Dictionary of all entries in the batch with associated computed values.
|
||||||
|
* One entry corresponds to one feature. Key is feature uid.
|
||||||
|
* @property {number} geometriesCount Amount of geometries in the batch.
|
||||||
|
* @property {Float32Array} renderInstructions Render instructions for polygons are structured like so:
|
||||||
|
* [ numberOfRings, numberOfVerticesInRing0, ..., numberOfVerticesInRingN, x0, y0, customAttr0, ..., xN, yN, customAttrN, numberOfRings,... ]
|
||||||
|
* @property {WebGLArrayBuffer} verticesBuffer Vertices WebGL buffer
|
||||||
|
* @property {WebGLArrayBuffer} indicesBuffer Indices WebGL buffer
|
||||||
|
* @property {import("../../transform.js").Transform} renderInstructionsTransform Converts world space coordinates to screen space; applies to the rendering instructions
|
||||||
|
* @property {import("../../transform.js").Transform} verticesBufferTransform Converts world space coordinates to screen space; applies to the webgl vertices buffer
|
||||||
|
* @property {import("../../transform.js").Transform} invertVerticesBufferTransform Screen space to world space; applies to the webgl vertices buffer
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @classdesc This class is used to group several geometries of various types together for faster rendering.
|
||||||
|
* Three inner batches are maintained for polygons, lines and points. Each time a feature is added, changed or removed
|
||||||
|
* from the batch, these inner batches are modified accordingly in order to keep them up-to-date.
|
||||||
|
*
|
||||||
|
* A feature can be present in several inner batches, for example a polygon geometry will be present in the polygon batch
|
||||||
|
* and its linar rings will be present in the line batch. Multi geometries are also broken down into individual geometries
|
||||||
|
* and added to the corresponding batches in a recursive manner.
|
||||||
|
*
|
||||||
|
* Corresponding {@link module:ol/render/webgl/BatchRenderer} instances are then used to generate the render instructions
|
||||||
|
* and WebGL buffers (vertices and indices) for each inner batches; render instructions are stored on the inner batches,
|
||||||
|
* alongside the transform used to convert world coords to screen coords at the time these instructions were generated.
|
||||||
|
* The resulting WebGL buffers are stored on the batches as well.
|
||||||
|
*
|
||||||
|
* An important aspect of geometry batches is that there is no guarantee that render instructions and WebGL buffers
|
||||||
|
* are synchronized, i.e. render instructions can describe a new state while WebGL buffers might not have been written yet.
|
||||||
|
* This is why two world-to-screen transforms are stored on each batch: one for the render instructions and one for
|
||||||
|
* the WebGL buffers.
|
||||||
|
*/
|
||||||
|
class MixedGeometryBatch {
|
||||||
|
constructor() {
|
||||||
|
/**
|
||||||
|
* @type {PolygonGeometryBatch}
|
||||||
|
*/
|
||||||
|
this.polygonBatch = {
|
||||||
|
entries: {},
|
||||||
|
geometriesCount: 0,
|
||||||
|
verticesCount: 0,
|
||||||
|
ringsCount: 0,
|
||||||
|
renderInstructions: new Float32Array(0),
|
||||||
|
verticesBuffer: new WebGLArrayBuffer(ARRAY_BUFFER, DYNAMIC_DRAW),
|
||||||
|
indicesBuffer: new WebGLArrayBuffer(ELEMENT_ARRAY_BUFFER, DYNAMIC_DRAW),
|
||||||
|
renderInstructionsTransform: createTransform(),
|
||||||
|
verticesBufferTransform: createTransform(),
|
||||||
|
invertVerticesBufferTransform: createTransform(),
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {PointGeometryBatch}
|
||||||
|
*/
|
||||||
|
this.pointBatch = {
|
||||||
|
entries: {},
|
||||||
|
geometriesCount: 0,
|
||||||
|
renderInstructions: new Float32Array(0),
|
||||||
|
verticesBuffer: new WebGLArrayBuffer(ARRAY_BUFFER, DYNAMIC_DRAW),
|
||||||
|
indicesBuffer: new WebGLArrayBuffer(ELEMENT_ARRAY_BUFFER, DYNAMIC_DRAW),
|
||||||
|
renderInstructionsTransform: createTransform(),
|
||||||
|
verticesBufferTransform: createTransform(),
|
||||||
|
invertVerticesBufferTransform: createTransform(),
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {LineStringGeometryBatch}
|
||||||
|
*/
|
||||||
|
this.lineStringBatch = {
|
||||||
|
entries: {},
|
||||||
|
geometriesCount: 0,
|
||||||
|
verticesCount: 0,
|
||||||
|
renderInstructions: new Float32Array(0),
|
||||||
|
verticesBuffer: new WebGLArrayBuffer(ARRAY_BUFFER, DYNAMIC_DRAW),
|
||||||
|
indicesBuffer: new WebGLArrayBuffer(ELEMENT_ARRAY_BUFFER, DYNAMIC_DRAW),
|
||||||
|
renderInstructionsTransform: createTransform(),
|
||||||
|
verticesBufferTransform: createTransform(),
|
||||||
|
invertVerticesBufferTransform: createTransform(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Array<import("../../Feature").default>} features Array of features to add to the batch
|
||||||
|
*/
|
||||||
|
addFeatures(features) {
|
||||||
|
for (let i = 0; i < features.length; i++) {
|
||||||
|
this.addFeature(features[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("../../Feature").default} feature Feature to add to the batch
|
||||||
|
*/
|
||||||
|
addFeature(feature) {
|
||||||
|
const geometry = feature.getGeometry();
|
||||||
|
if (!geometry) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.addGeometry_(geometry, feature);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("../../Feature").default} feature Feature
|
||||||
|
* @return {GeometryBatchItem} Batch item added (or existing one)
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
addFeatureEntryInPointBatch_(feature) {
|
||||||
|
const uid = getUid(feature);
|
||||||
|
if (!(uid in this.pointBatch.entries)) {
|
||||||
|
this.pointBatch.entries[uid] = {
|
||||||
|
feature: feature,
|
||||||
|
flatCoordss: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return this.pointBatch.entries[uid];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("../../Feature").default} feature Feature
|
||||||
|
* @return {GeometryBatchItem} Batch item added (or existing one)
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
addFeatureEntryInLineStringBatch_(feature) {
|
||||||
|
const uid = getUid(feature);
|
||||||
|
if (!(uid in this.lineStringBatch.entries)) {
|
||||||
|
this.lineStringBatch.entries[uid] = {
|
||||||
|
feature: feature,
|
||||||
|
flatCoordss: [],
|
||||||
|
verticesCount: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return this.lineStringBatch.entries[uid];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("../../Feature").default} feature Feature
|
||||||
|
* @return {GeometryBatchItem} Batch item added (or existing one)
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
addFeatureEntryInPolygonBatch_(feature) {
|
||||||
|
const uid = getUid(feature);
|
||||||
|
if (!(uid in this.polygonBatch.entries)) {
|
||||||
|
this.polygonBatch.entries[uid] = {
|
||||||
|
feature: feature,
|
||||||
|
flatCoordss: [],
|
||||||
|
verticesCount: 0,
|
||||||
|
ringsCount: 0,
|
||||||
|
ringsVerticesCounts: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return this.polygonBatch.entries[uid];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("../../Feature").default} feature Feature
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
clearFeatureEntryInPointBatch_(feature) {
|
||||||
|
const entry = this.pointBatch.entries[getUid(feature)];
|
||||||
|
if (!entry) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.pointBatch.geometriesCount -= entry.flatCoordss.length;
|
||||||
|
delete this.pointBatch.entries[getUid(feature)];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("../../Feature").default} feature Feature
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
clearFeatureEntryInLineStringBatch_(feature) {
|
||||||
|
const entry = this.lineStringBatch.entries[getUid(feature)];
|
||||||
|
if (!entry) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.lineStringBatch.verticesCount -= entry.verticesCount;
|
||||||
|
this.lineStringBatch.geometriesCount -= entry.flatCoordss.length;
|
||||||
|
delete this.lineStringBatch.entries[getUid(feature)];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("../../Feature").default} feature Feature
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
clearFeatureEntryInPolygonBatch_(feature) {
|
||||||
|
const entry = this.polygonBatch.entries[getUid(feature)];
|
||||||
|
if (!entry) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.polygonBatch.verticesCount -= entry.verticesCount;
|
||||||
|
this.polygonBatch.ringsCount -= entry.ringsCount;
|
||||||
|
this.polygonBatch.geometriesCount -= entry.flatCoordss.length;
|
||||||
|
delete this.polygonBatch.entries[getUid(feature)];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("../../geom").Geometry} geometry Geometry
|
||||||
|
* @param {import("../../Feature").default} feature Feature
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
addGeometry_(geometry, feature) {
|
||||||
|
const type = geometry.getType();
|
||||||
|
let flatCoords;
|
||||||
|
let verticesCount;
|
||||||
|
let batchEntry;
|
||||||
|
switch (type) {
|
||||||
|
case 'GeometryCollection':
|
||||||
|
/** @type {import("../../geom").GeometryCollection} */ (geometry)
|
||||||
|
.getGeometries()
|
||||||
|
.map((geom) => this.addGeometry_(geom, feature));
|
||||||
|
break;
|
||||||
|
case 'MultiPolygon':
|
||||||
|
/** @type {import("../../geom").MultiPolygon} */ (geometry)
|
||||||
|
.getPolygons()
|
||||||
|
.map((polygon) => this.addGeometry_(polygon, feature));
|
||||||
|
break;
|
||||||
|
case 'MultiLineString':
|
||||||
|
/** @type {import("../../geom").MultiLineString} */ (geometry)
|
||||||
|
.getLineStrings()
|
||||||
|
.map((line) => this.addGeometry_(line, feature));
|
||||||
|
break;
|
||||||
|
case 'MultiPoint':
|
||||||
|
/** @type {import("../../geom").MultiPoint} */ (geometry)
|
||||||
|
.getPoints()
|
||||||
|
.map((point) => this.addGeometry_(point, feature));
|
||||||
|
break;
|
||||||
|
case 'Polygon':
|
||||||
|
const polygonGeom = /** @type {import("../../geom").Polygon} */ (
|
||||||
|
geometry
|
||||||
|
);
|
||||||
|
batchEntry = this.addFeatureEntryInPolygonBatch_(feature);
|
||||||
|
flatCoords = polygonGeom.getFlatCoordinates();
|
||||||
|
verticesCount = flatCoords.length / 2;
|
||||||
|
const ringsCount = polygonGeom.getLinearRingCount();
|
||||||
|
const ringsVerticesCount = polygonGeom
|
||||||
|
.getEnds()
|
||||||
|
.map((end, ind, arr) =>
|
||||||
|
ind > 0 ? (end - arr[ind - 1]) / 2 : end / 2
|
||||||
|
);
|
||||||
|
this.polygonBatch.verticesCount += verticesCount;
|
||||||
|
this.polygonBatch.ringsCount += ringsCount;
|
||||||
|
this.polygonBatch.geometriesCount++;
|
||||||
|
batchEntry.flatCoordss.push(flatCoords);
|
||||||
|
batchEntry.ringsVerticesCounts.push(ringsVerticesCount);
|
||||||
|
batchEntry.verticesCount += verticesCount;
|
||||||
|
batchEntry.ringsCount += ringsCount;
|
||||||
|
polygonGeom
|
||||||
|
.getLinearRings()
|
||||||
|
.map((ring) => this.addGeometry_(ring, feature));
|
||||||
|
break;
|
||||||
|
case 'Point':
|
||||||
|
const pointGeom = /** @type {import("../../geom").Point} */ (geometry);
|
||||||
|
batchEntry = this.addFeatureEntryInPointBatch_(feature);
|
||||||
|
flatCoords = pointGeom.getFlatCoordinates();
|
||||||
|
this.pointBatch.geometriesCount++;
|
||||||
|
batchEntry.flatCoordss.push(flatCoords);
|
||||||
|
break;
|
||||||
|
case 'LineString':
|
||||||
|
case 'LinearRing':
|
||||||
|
const lineGeom = /** @type {import("../../geom").LineString} */ (
|
||||||
|
geometry
|
||||||
|
);
|
||||||
|
batchEntry = this.addFeatureEntryInLineStringBatch_(feature);
|
||||||
|
flatCoords = lineGeom.getFlatCoordinates();
|
||||||
|
verticesCount = flatCoords.length / 2;
|
||||||
|
this.lineStringBatch.verticesCount += verticesCount;
|
||||||
|
this.lineStringBatch.geometriesCount++;
|
||||||
|
batchEntry.flatCoordss.push(flatCoords);
|
||||||
|
batchEntry.verticesCount += verticesCount;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("../../Feature").default} feature Feature
|
||||||
|
*/
|
||||||
|
changeFeature(feature) {
|
||||||
|
this.clearFeatureEntryInPointBatch_(feature);
|
||||||
|
this.clearFeatureEntryInPolygonBatch_(feature);
|
||||||
|
this.clearFeatureEntryInLineStringBatch_(feature);
|
||||||
|
const geometry = feature.getGeometry();
|
||||||
|
if (!geometry) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.addGeometry_(geometry, feature);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("../../Feature").default} feature Feature
|
||||||
|
*/
|
||||||
|
removeFeature(feature) {
|
||||||
|
this.clearFeatureEntryInPointBatch_(feature);
|
||||||
|
this.clearFeatureEntryInPolygonBatch_(feature);
|
||||||
|
this.clearFeatureEntryInLineStringBatch_(feature);
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this.polygonBatch.entries = {};
|
||||||
|
this.polygonBatch.geometriesCount = 0;
|
||||||
|
this.polygonBatch.verticesCount = 0;
|
||||||
|
this.polygonBatch.ringsCount = 0;
|
||||||
|
this.lineStringBatch.entries = {};
|
||||||
|
this.lineStringBatch.geometriesCount = 0;
|
||||||
|
this.lineStringBatch.verticesCount = 0;
|
||||||
|
this.pointBatch.entries = {};
|
||||||
|
this.pointBatch.geometriesCount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MixedGeometryBatch;
|
||||||
97
src/ol/render/webgl/PointBatchRenderer.js
Normal file
97
src/ol/render/webgl/PointBatchRenderer.js
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
/**
|
||||||
|
* @module ol/render/webgl/PointBatchRenderer
|
||||||
|
*/
|
||||||
|
|
||||||
|
import AbstractBatchRenderer from './BatchRenderer.js';
|
||||||
|
import {AttributeType} from '../../webgl/Helper.js';
|
||||||
|
import {apply as applyTransform} from '../../transform.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Names of attributes made available to the vertex shader.
|
||||||
|
* Please note: changing these *will* break custom shaders!
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
export const Attributes = {
|
||||||
|
POSITION: 'a_position',
|
||||||
|
INDEX: 'a_index',
|
||||||
|
};
|
||||||
|
|
||||||
|
class PointBatchRenderer extends AbstractBatchRenderer {
|
||||||
|
/**
|
||||||
|
* @param {import("../../webgl/Helper.js").default} helper WebGL helper instance
|
||||||
|
* @param {Worker} worker WebGL worker instance
|
||||||
|
* @param {string} vertexShader Vertex shader
|
||||||
|
* @param {string} fragmentShader Fragment shader
|
||||||
|
* @param {Array<import('./BatchRenderer.js').CustomAttribute>} customAttributes List of custom attributes
|
||||||
|
*/
|
||||||
|
constructor(helper, worker, vertexShader, fragmentShader, customAttributes) {
|
||||||
|
super(helper, worker, vertexShader, fragmentShader, customAttributes);
|
||||||
|
|
||||||
|
// vertices for point must hold both a position (x,y) and an index (their position in the quad)
|
||||||
|
this.attributes = [
|
||||||
|
{
|
||||||
|
name: Attributes.POSITION,
|
||||||
|
size: 2,
|
||||||
|
type: AttributeType.FLOAT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: Attributes.INDEX,
|
||||||
|
size: 1,
|
||||||
|
type: AttributeType.FLOAT,
|
||||||
|
},
|
||||||
|
].concat(
|
||||||
|
customAttributes.map(function (attribute) {
|
||||||
|
return {
|
||||||
|
name: 'a_' + attribute.name,
|
||||||
|
size: 1,
|
||||||
|
type: AttributeType.FLOAT,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render instructions for lines are structured like so:
|
||||||
|
* [ x0, y0, customAttr0, ... , xN, yN, customAttrN ]
|
||||||
|
* @param {import("./MixedGeometryBatch.js").PointGeometryBatch} batch Point geometry batch
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
generateRenderInstructions(batch) {
|
||||||
|
// here we anticipate the amount of render instructions for points:
|
||||||
|
// 2 instructions per vertex for position (x and y)
|
||||||
|
// + 1 instruction per vertex per custom attributes
|
||||||
|
const totalInstructionsCount =
|
||||||
|
(2 + this.customAttributes.length) * batch.geometriesCount;
|
||||||
|
if (
|
||||||
|
!batch.renderInstructions ||
|
||||||
|
batch.renderInstructions.length !== totalInstructionsCount
|
||||||
|
) {
|
||||||
|
batch.renderInstructions = new Float32Array(totalInstructionsCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// loop on features to fill the render instructions
|
||||||
|
let batchEntry;
|
||||||
|
const tmpCoords = [];
|
||||||
|
let renderIndex = 0;
|
||||||
|
let value;
|
||||||
|
for (const featureUid in batch.entries) {
|
||||||
|
batchEntry = batch.entries[featureUid];
|
||||||
|
for (let i = 0, ii = batchEntry.flatCoordss.length; i < ii; i++) {
|
||||||
|
tmpCoords[0] = batchEntry.flatCoordss[i][0];
|
||||||
|
tmpCoords[1] = batchEntry.flatCoordss[i][1];
|
||||||
|
applyTransform(batch.renderInstructionsTransform, tmpCoords);
|
||||||
|
|
||||||
|
batch.renderInstructions[renderIndex++] = tmpCoords[0];
|
||||||
|
batch.renderInstructions[renderIndex++] = tmpCoords[1];
|
||||||
|
|
||||||
|
// pushing custom attributes
|
||||||
|
for (let j = 0, jj = this.customAttributes.length; j < jj; j++) {
|
||||||
|
value = this.customAttributes[j].callback(batchEntry.feature);
|
||||||
|
batch.renderInstructions[renderIndex++] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PointBatchRenderer;
|
||||||
117
src/ol/render/webgl/PolygonBatchRenderer.js
Normal file
117
src/ol/render/webgl/PolygonBatchRenderer.js
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
/**
|
||||||
|
* @module ol/render/webgl/PolygonBatchRenderer
|
||||||
|
*/
|
||||||
|
import AbstractBatchRenderer from './BatchRenderer.js';
|
||||||
|
import {AttributeType} from '../../webgl/Helper.js';
|
||||||
|
import {transform2D} from '../../geom/flat/transform.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Names of attributes made available to the vertex shader.
|
||||||
|
* Please note: changing these *will* break custom shaders!
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
export const Attributes = {
|
||||||
|
POSITION: 'a_position',
|
||||||
|
};
|
||||||
|
|
||||||
|
class PolygonBatchRenderer extends AbstractBatchRenderer {
|
||||||
|
/**
|
||||||
|
* @param {import("../../webgl/Helper.js").default} helper WebGL helper instance
|
||||||
|
* @param {Worker} worker WebGL worker instance
|
||||||
|
* @param {string} vertexShader Vertex shader
|
||||||
|
* @param {string} fragmentShader Fragment shader
|
||||||
|
* @param {Array<import('./BatchRenderer.js').CustomAttribute>} customAttributes List of custom attributes
|
||||||
|
*/
|
||||||
|
constructor(helper, worker, vertexShader, fragmentShader, customAttributes) {
|
||||||
|
super(helper, worker, vertexShader, fragmentShader, customAttributes);
|
||||||
|
|
||||||
|
// By default only a position attribute is required to render polygons
|
||||||
|
this.attributes = [
|
||||||
|
{
|
||||||
|
name: Attributes.POSITION,
|
||||||
|
size: 2,
|
||||||
|
type: AttributeType.FLOAT,
|
||||||
|
},
|
||||||
|
].concat(
|
||||||
|
customAttributes.map(function (attribute) {
|
||||||
|
return {
|
||||||
|
name: 'a_' + attribute.name,
|
||||||
|
size: 1,
|
||||||
|
type: AttributeType.FLOAT,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render instructions for polygons are structured like so:
|
||||||
|
* [ customAttr0, ..., customAttrN, numberOfRings, numberOfVerticesInRing0, ..., numberOfVerticesInRingN, x0, y0, ..., xN, yN, numberOfRings,... ]
|
||||||
|
* @param {import("./MixedGeometryBatch.js").PolygonGeometryBatch} batch Polygon geometry batch
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
generateRenderInstructions(batch) {
|
||||||
|
// here we anticipate the amount of render instructions for polygons:
|
||||||
|
// 2 instructions per vertex for position (x and y)
|
||||||
|
// + 1 instruction per polygon per custom attributes
|
||||||
|
// + 1 instruction per polygon (for vertices count in polygon)
|
||||||
|
// + 1 instruction per ring (for vertices count in ring)
|
||||||
|
const totalInstructionsCount =
|
||||||
|
2 * batch.verticesCount +
|
||||||
|
(1 + this.customAttributes.length) * batch.geometriesCount +
|
||||||
|
batch.ringsCount;
|
||||||
|
if (
|
||||||
|
!batch.renderInstructions ||
|
||||||
|
batch.renderInstructions.length !== totalInstructionsCount
|
||||||
|
) {
|
||||||
|
batch.renderInstructions = new Float32Array(totalInstructionsCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// loop on features to fill the render instructions
|
||||||
|
let batchEntry;
|
||||||
|
const flatCoords = [];
|
||||||
|
let renderIndex = 0;
|
||||||
|
let value;
|
||||||
|
for (const featureUid in batch.entries) {
|
||||||
|
batchEntry = batch.entries[featureUid];
|
||||||
|
for (let i = 0, ii = batchEntry.flatCoordss.length; i < ii; i++) {
|
||||||
|
flatCoords.length = batchEntry.flatCoordss[i].length;
|
||||||
|
transform2D(
|
||||||
|
batchEntry.flatCoordss[i],
|
||||||
|
0,
|
||||||
|
flatCoords.length,
|
||||||
|
2,
|
||||||
|
batch.renderInstructionsTransform,
|
||||||
|
flatCoords
|
||||||
|
);
|
||||||
|
|
||||||
|
// custom attributes
|
||||||
|
for (let k = 0, kk = this.customAttributes.length; k < kk; k++) {
|
||||||
|
value = this.customAttributes[k].callback(batchEntry.feature);
|
||||||
|
batch.renderInstructions[renderIndex++] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ring count
|
||||||
|
batch.renderInstructions[renderIndex++] =
|
||||||
|
batchEntry.ringsVerticesCounts[i].length;
|
||||||
|
|
||||||
|
// vertices count in each ring
|
||||||
|
for (
|
||||||
|
let j = 0, jj = batchEntry.ringsVerticesCounts[i].length;
|
||||||
|
j < jj;
|
||||||
|
j++
|
||||||
|
) {
|
||||||
|
batch.renderInstructions[renderIndex++] =
|
||||||
|
batchEntry.ringsVerticesCounts[i][j];
|
||||||
|
}
|
||||||
|
|
||||||
|
// looping on points for positions
|
||||||
|
for (let j = 0, jj = flatCoords.length; j < jj; j += 2) {
|
||||||
|
batch.renderInstructions[renderIndex++] = flatCoords[j];
|
||||||
|
batch.renderInstructions[renderIndex++] = flatCoords[j + 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PolygonBatchRenderer;
|
||||||
27
src/ol/render/webgl/constants.js
Normal file
27
src/ol/render/webgl/constants.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* @module ol/render/webgl/constants
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
export const WebGLWorkerMessageType = {
|
||||||
|
GENERATE_POLYGON_BUFFERS: 'GENERATE_POLYGON_BUFFERS',
|
||||||
|
GENERATE_POINT_BUFFERS: 'GENERATE_POINT_BUFFERS',
|
||||||
|
GENERATE_LINE_STRING_BUFFERS: 'GENERATE_LINE_STRING_BUFFERS',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} WebGLWorkerGenerateBuffersMessage
|
||||||
|
* This message will trigger the generation of a vertex and an index buffer based on the given render instructions.
|
||||||
|
* When the buffers are generated, the worked will send a message of the same type to the main thread, with
|
||||||
|
* the generated buffers in it.
|
||||||
|
* Note that any addition properties present in the message *will* be sent back to the main thread.
|
||||||
|
* @property {number} id Message id; will be used both in request and response as a means of identification
|
||||||
|
* @property {WebGLWorkerMessageType} type Message type
|
||||||
|
* @property {ArrayBuffer} renderInstructions Polygon render instructions raw binary buffer.
|
||||||
|
* @property {number} [customAttributesCount] Amount of custom attributes count in the polygon render instructions.
|
||||||
|
* @property {ArrayBuffer} [vertexBuffer] Vertices array raw binary buffer (sent by the worker).
|
||||||
|
* @property {ArrayBuffer} [indexBuffer] Indices array raw binary buffer (sent by the worker).
|
||||||
|
* @property {import("../../transform").Transform} [renderInstructionsTransform] Transformation matrix used to project the instructions coordinates
|
||||||
|
*/
|
||||||
351
src/ol/render/webgl/utils.js
Normal file
351
src/ol/render/webgl/utils.js
Normal file
@@ -0,0 +1,351 @@
|
|||||||
|
/**
|
||||||
|
* @module ol/render/webgl/utils
|
||||||
|
*/
|
||||||
|
import earcut from 'earcut';
|
||||||
|
import {apply as applyTransform} from '../../transform.js';
|
||||||
|
import {clamp} from '../../math.js';
|
||||||
|
|
||||||
|
const tmpArray_ = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object holding positions both in an index and a vertex buffer.
|
||||||
|
* @typedef {Object} BufferPositions
|
||||||
|
* @property {number} vertexPosition Position in the vertex buffer
|
||||||
|
* @property {number} indexPosition Position in the index buffer
|
||||||
|
*/
|
||||||
|
const bufferPositions_ = {vertexPosition: 0, indexPosition: 0};
|
||||||
|
|
||||||
|
function writePointVertex(buffer, pos, x, y, index) {
|
||||||
|
buffer[pos + 0] = x;
|
||||||
|
buffer[pos + 1] = y;
|
||||||
|
buffer[pos + 2] = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pushes a quad (two triangles) based on a point geometry
|
||||||
|
* @param {Float32Array} instructions Array of render instructions for points.
|
||||||
|
* @param {number} elementIndex Index from which render instructions will be read.
|
||||||
|
* @param {Float32Array} vertexBuffer 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.
|
||||||
|
* @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} indexPosition New position in the index buffer where future writes should start.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
export function writePointFeatureToBuffers(
|
||||||
|
instructions,
|
||||||
|
elementIndex,
|
||||||
|
vertexBuffer,
|
||||||
|
indexBuffer,
|
||||||
|
customAttributesCount,
|
||||||
|
bufferPositions
|
||||||
|
) {
|
||||||
|
// This is for x, y and index
|
||||||
|
const baseVertexAttrsCount = 3;
|
||||||
|
const baseInstructionsCount = 2;
|
||||||
|
const stride = baseVertexAttrsCount + customAttributesCount;
|
||||||
|
|
||||||
|
const x = instructions[elementIndex + 0];
|
||||||
|
const y = instructions[elementIndex + 1];
|
||||||
|
|
||||||
|
// read custom numerical attributes on the feature
|
||||||
|
const customAttrs = tmpArray_;
|
||||||
|
customAttrs.length = customAttributesCount;
|
||||||
|
for (let i = 0; i < customAttrs.length; i++) {
|
||||||
|
customAttrs[i] = instructions[elementIndex + baseInstructionsCount + i];
|
||||||
|
}
|
||||||
|
|
||||||
|
let vPos = bufferPositions ? bufferPositions.vertexPosition : 0;
|
||||||
|
let iPos = bufferPositions ? bufferPositions.indexPosition : 0;
|
||||||
|
const baseIndex = vPos / stride;
|
||||||
|
|
||||||
|
// push vertices for each of the four quad corners (first standard then custom attributes)
|
||||||
|
writePointVertex(vertexBuffer, vPos, x, y, 0);
|
||||||
|
customAttrs.length &&
|
||||||
|
vertexBuffer.set(customAttrs, vPos + baseVertexAttrsCount);
|
||||||
|
vPos += stride;
|
||||||
|
|
||||||
|
writePointVertex(vertexBuffer, vPos, x, y, 1);
|
||||||
|
customAttrs.length &&
|
||||||
|
vertexBuffer.set(customAttrs, vPos + baseVertexAttrsCount);
|
||||||
|
vPos += stride;
|
||||||
|
|
||||||
|
writePointVertex(vertexBuffer, vPos, x, y, 2);
|
||||||
|
customAttrs.length &&
|
||||||
|
vertexBuffer.set(customAttrs, vPos + baseVertexAttrsCount);
|
||||||
|
vPos += stride;
|
||||||
|
|
||||||
|
writePointVertex(vertexBuffer, vPos, x, y, 3);
|
||||||
|
customAttrs.length &&
|
||||||
|
vertexBuffer.set(customAttrs, vPos + baseVertexAttrsCount);
|
||||||
|
vPos += stride;
|
||||||
|
|
||||||
|
indexBuffer[iPos++] = baseIndex;
|
||||||
|
indexBuffer[iPos++] = baseIndex + 1;
|
||||||
|
indexBuffer[iPos++] = baseIndex + 3;
|
||||||
|
indexBuffer[iPos++] = baseIndex + 1;
|
||||||
|
indexBuffer[iPos++] = baseIndex + 2;
|
||||||
|
indexBuffer[iPos++] = baseIndex + 3;
|
||||||
|
|
||||||
|
bufferPositions_.vertexPosition = vPos;
|
||||||
|
bufferPositions_.indexPosition = iPos;
|
||||||
|
|
||||||
|
return bufferPositions_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pushes a single quad to form a line segment; also includes a computation for the join angles with previous and next
|
||||||
|
* segment, in order to be able to offset the vertices correctly in the shader
|
||||||
|
* @param {Float32Array} instructions Array of render instructions for lines.
|
||||||
|
* @param {number} segmentStartIndex Index of the segment start point from which render instructions will be read.
|
||||||
|
* @param {number} segmentEndIndex Index of the segment start point from which render instructions will be read.
|
||||||
|
* @param {number|null} beforeSegmentIndex Index of the point right before the segment (null if none, e.g this is a line start)
|
||||||
|
* @param {number|null} afterSegmentIndex Index of the point right after the segment (null if none, e.g this is a line end)
|
||||||
|
* @param {Array<number>} vertexArray Array containing vertices.
|
||||||
|
* @param {Array<number>} indexArray Array containing indices.
|
||||||
|
* @param {Array<number>} customAttributes Array of custom attributes value
|
||||||
|
* @param {import('../../transform.js').Transform} instructionsTransform Transform matrix used to project coordinates in instructions
|
||||||
|
* @param {import('../../transform.js').Transform} invertInstructionsTransform Transform matrix used to project coordinates in instructions
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
export function writeLineSegmentToBuffers(
|
||||||
|
instructions,
|
||||||
|
segmentStartIndex,
|
||||||
|
segmentEndIndex,
|
||||||
|
beforeSegmentIndex,
|
||||||
|
afterSegmentIndex,
|
||||||
|
vertexArray,
|
||||||
|
indexArray,
|
||||||
|
customAttributes,
|
||||||
|
instructionsTransform,
|
||||||
|
invertInstructionsTransform
|
||||||
|
) {
|
||||||
|
// compute the stride to determine how many vertices were already pushed
|
||||||
|
const baseVertexAttrsCount = 5; // base attributes: x0, y0, x1, y1, params (vertex number [0-3], join angle 1, join angle 2)
|
||||||
|
const stride = baseVertexAttrsCount + customAttributes.length;
|
||||||
|
const baseIndex = vertexArray.length / stride;
|
||||||
|
|
||||||
|
// The segment is composed of two positions called P0[x0, y0] and P1[x1, y1]
|
||||||
|
// Depending on whether there are points before and after the segment, its final shape
|
||||||
|
// will be different
|
||||||
|
const p0 = [
|
||||||
|
instructions[segmentStartIndex + 0],
|
||||||
|
instructions[segmentStartIndex + 1],
|
||||||
|
];
|
||||||
|
const p1 = [instructions[segmentEndIndex], instructions[segmentEndIndex + 1]];
|
||||||
|
|
||||||
|
// to compute offsets from the line center we need to reproject
|
||||||
|
// coordinates back in world units and compute the length of the segment
|
||||||
|
const p0world = applyTransform(invertInstructionsTransform, [...p0]);
|
||||||
|
const p1world = applyTransform(invertInstructionsTransform, [...p1]);
|
||||||
|
|
||||||
|
function computeVertexParameters(vertexNumber, joinAngle1, joinAngle2) {
|
||||||
|
const shift = 10000;
|
||||||
|
const anglePrecision = 1500;
|
||||||
|
return (
|
||||||
|
Math.round(joinAngle1 * anglePrecision) +
|
||||||
|
Math.round(joinAngle2 * anglePrecision) * shift +
|
||||||
|
vertexNumber * shift * shift
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// compute the angle between p0pA and p0pB
|
||||||
|
// returns a value in [0, 2PI]
|
||||||
|
function angleBetween(p0, pA, pB) {
|
||||||
|
const lenA = Math.sqrt(
|
||||||
|
(pA[0] - p0[0]) * (pA[0] - p0[0]) + (pA[1] - p0[1]) * (pA[1] - p0[1])
|
||||||
|
);
|
||||||
|
const tangentA = [(pA[0] - p0[0]) / lenA, (pA[1] - p0[1]) / lenA];
|
||||||
|
const orthoA = [-tangentA[1], tangentA[0]];
|
||||||
|
const lenB = Math.sqrt(
|
||||||
|
(pB[0] - p0[0]) * (pB[0] - p0[0]) + (pB[1] - p0[1]) * (pB[1] - p0[1])
|
||||||
|
);
|
||||||
|
const tangentB = [(pB[0] - p0[0]) / lenB, (pB[1] - p0[1]) / lenB];
|
||||||
|
|
||||||
|
// this angle can be clockwise or anticlockwise; hence the computation afterwards
|
||||||
|
const angle =
|
||||||
|
lenA === 0 || lenB === 0
|
||||||
|
? 0
|
||||||
|
: Math.acos(
|
||||||
|
clamp(tangentB[0] * tangentA[0] + tangentB[1] * tangentA[1], -1, 1)
|
||||||
|
);
|
||||||
|
const isClockwise = tangentB[0] * orthoA[0] + tangentB[1] * orthoA[1] > 0;
|
||||||
|
return !isClockwise ? Math.PI * 2 - angle : angle;
|
||||||
|
}
|
||||||
|
|
||||||
|
const joinBefore = beforeSegmentIndex !== null;
|
||||||
|
const joinAfter = afterSegmentIndex !== null;
|
||||||
|
|
||||||
|
let angle0 = 0;
|
||||||
|
let angle1 = 0;
|
||||||
|
|
||||||
|
// add vertices and adapt offsets for P0 in case of join
|
||||||
|
if (joinBefore) {
|
||||||
|
// B for before
|
||||||
|
const pB = [
|
||||||
|
instructions[beforeSegmentIndex],
|
||||||
|
instructions[beforeSegmentIndex + 1],
|
||||||
|
];
|
||||||
|
const pBworld = applyTransform(invertInstructionsTransform, [...pB]);
|
||||||
|
angle0 = angleBetween(p0world, p1world, pBworld);
|
||||||
|
}
|
||||||
|
// adapt offsets for P1 in case of join
|
||||||
|
if (joinAfter) {
|
||||||
|
// A for after
|
||||||
|
const pA = [
|
||||||
|
instructions[afterSegmentIndex],
|
||||||
|
instructions[afterSegmentIndex + 1],
|
||||||
|
];
|
||||||
|
const pAworld = applyTransform(invertInstructionsTransform, [...pA]);
|
||||||
|
angle1 = angleBetween(p1world, p0world, pAworld);
|
||||||
|
}
|
||||||
|
|
||||||
|
// add main segment triangles
|
||||||
|
vertexArray.push(
|
||||||
|
p0[0],
|
||||||
|
p0[1],
|
||||||
|
p1[0],
|
||||||
|
p1[1],
|
||||||
|
computeVertexParameters(0, angle0, angle1)
|
||||||
|
);
|
||||||
|
vertexArray.push(...customAttributes);
|
||||||
|
|
||||||
|
vertexArray.push(
|
||||||
|
p0[0],
|
||||||
|
p0[1],
|
||||||
|
p1[0],
|
||||||
|
p1[1],
|
||||||
|
computeVertexParameters(1, angle0, angle1)
|
||||||
|
);
|
||||||
|
vertexArray.push(...customAttributes);
|
||||||
|
|
||||||
|
vertexArray.push(
|
||||||
|
p0[0],
|
||||||
|
p0[1],
|
||||||
|
p1[0],
|
||||||
|
p1[1],
|
||||||
|
computeVertexParameters(2, angle0, angle1)
|
||||||
|
);
|
||||||
|
vertexArray.push(...customAttributes);
|
||||||
|
|
||||||
|
vertexArray.push(
|
||||||
|
p0[0],
|
||||||
|
p0[1],
|
||||||
|
p1[0],
|
||||||
|
p1[1],
|
||||||
|
computeVertexParameters(3, angle0, angle1)
|
||||||
|
);
|
||||||
|
vertexArray.push(...customAttributes);
|
||||||
|
|
||||||
|
indexArray.push(
|
||||||
|
baseIndex,
|
||||||
|
baseIndex + 1,
|
||||||
|
baseIndex + 2,
|
||||||
|
baseIndex + 1,
|
||||||
|
baseIndex + 3,
|
||||||
|
baseIndex + 2
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pushes several triangles to form a polygon, including holes
|
||||||
|
* @param {Float32Array} instructions Array of render instructions for lines.
|
||||||
|
* @param {number} polygonStartIndex Index of the polygon start point from which render instructions will be read.
|
||||||
|
* @param {Array<number>} vertexArray Array containing vertices.
|
||||||
|
* @param {Array<number>} indexArray Array containing indices.
|
||||||
|
* @param {number} customAttributesCount Amount of custom attributes for each element.
|
||||||
|
* @return {number} Next polygon instructions index
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
export function writePolygonTrianglesToBuffers(
|
||||||
|
instructions,
|
||||||
|
polygonStartIndex,
|
||||||
|
vertexArray,
|
||||||
|
indexArray,
|
||||||
|
customAttributesCount
|
||||||
|
) {
|
||||||
|
const instructionsPerVertex = 2; // x, y
|
||||||
|
const attributesPerVertex = 2 + customAttributesCount;
|
||||||
|
let instructionsIndex = polygonStartIndex;
|
||||||
|
const customAttributes = instructions.slice(
|
||||||
|
instructionsIndex,
|
||||||
|
instructionsIndex + customAttributesCount
|
||||||
|
);
|
||||||
|
instructionsIndex += customAttributesCount;
|
||||||
|
const ringsCount = instructions[instructionsIndex++];
|
||||||
|
let verticesCount = 0;
|
||||||
|
const holes = new Array(ringsCount - 1);
|
||||||
|
for (let i = 0; i < ringsCount; i++) {
|
||||||
|
verticesCount += instructions[instructionsIndex++];
|
||||||
|
if (i < ringsCount - 1) {
|
||||||
|
holes[i] = verticesCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const flatCoords = instructions.slice(
|
||||||
|
instructionsIndex,
|
||||||
|
instructionsIndex + verticesCount * instructionsPerVertex
|
||||||
|
);
|
||||||
|
|
||||||
|
// pushing to vertices and indices!! this is where the magic happens
|
||||||
|
const result = earcut(flatCoords, holes, instructionsPerVertex);
|
||||||
|
for (let i = 0; i < result.length; i++) {
|
||||||
|
indexArray.push(result[i] + vertexArray.length / attributesPerVertex);
|
||||||
|
}
|
||||||
|
for (let i = 0; i < flatCoords.length; i += 2) {
|
||||||
|
vertexArray.push(flatCoords[i], flatCoords[i + 1], ...customAttributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
return instructionsIndex + verticesCount * instructionsPerVertex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a texture of 1x1 pixel, white
|
||||||
|
* @private
|
||||||
|
* @return {ImageData} Image data.
|
||||||
|
*/
|
||||||
|
export function getBlankImageData() {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const image = canvas.getContext('2d').createImageData(1, 1);
|
||||||
|
image.data[0] = 255;
|
||||||
|
image.data[1] = 255;
|
||||||
|
image.data[2] = 255;
|
||||||
|
image.data[3] = 255;
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a color array based on a numerical id
|
||||||
|
* Note: the range for each component is 0 to 1 with 256 steps
|
||||||
|
* @param {number} id Id
|
||||||
|
* @param {Array<number>} [opt_array] Reusable array
|
||||||
|
* @return {Array<number>} Color array containing the encoded id
|
||||||
|
*/
|
||||||
|
export function colorEncodeId(id, opt_array) {
|
||||||
|
const array = opt_array || [];
|
||||||
|
const radix = 256;
|
||||||
|
const divide = radix - 1;
|
||||||
|
array[0] = Math.floor(id / radix / radix / radix) / divide;
|
||||||
|
array[1] = (Math.floor(id / radix / radix) % radix) / divide;
|
||||||
|
array[2] = (Math.floor(id / radix) % radix) / divide;
|
||||||
|
array[3] = (id % radix) / divide;
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads an id from a color-encoded array
|
||||||
|
* Note: the expected range for each component is 0 to 1 with 256 steps.
|
||||||
|
* @param {Array<number>} color Color array containing the encoded id
|
||||||
|
* @return {number} Decoded id
|
||||||
|
*/
|
||||||
|
export function colorDecodeId(color) {
|
||||||
|
let id = 0;
|
||||||
|
const radix = 256;
|
||||||
|
const mult = radix - 1;
|
||||||
|
id += Math.round(color[0] * radix * radix * radix * mult);
|
||||||
|
id += Math.round(color[1] * radix * radix * mult);
|
||||||
|
id += Math.round(color[2] * radix * mult);
|
||||||
|
id += Math.round(color[3] * mult);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
@@ -13,26 +13,6 @@ import {
|
|||||||
} from '../../transform.js';
|
} from '../../transform.js';
|
||||||
import {containsCoordinate} from '../../extent.js';
|
import {containsCoordinate} from '../../extent.js';
|
||||||
|
|
||||||
/**
|
|
||||||
* @enum {string}
|
|
||||||
*/
|
|
||||||
export const WebGLWorkerMessageType = {
|
|
||||||
GENERATE_BUFFERS: 'GENERATE_BUFFERS',
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {Object} WebGLWorkerGenerateBuffersMessage
|
|
||||||
* This message will trigger the generation of a vertex and an index buffer based on the given render instructions.
|
|
||||||
* When the buffers are generated, the worked will send a message of the same type to the main thread, with
|
|
||||||
* the generated buffers in it.
|
|
||||||
* Note that any addition properties present in the message *will* be sent back to the main thread.
|
|
||||||
* @property {WebGLWorkerMessageType} type Message type
|
|
||||||
* @property {ArrayBuffer} renderInstructions Render instructions raw binary buffer.
|
|
||||||
* @property {ArrayBuffer} [vertexBuffer] Vertices array raw binary buffer (sent by the worker).
|
|
||||||
* @property {ArrayBuffer} [indexBuffer] Indices array raw binary buffer (sent by the worker).
|
|
||||||
* @property {number} [customAttributesCount] Amount of custom attributes count in the render instructions.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} PostProcessesOptions
|
* @typedef {Object} PostProcessesOptions
|
||||||
* @property {number} [scaleRatio] Scale ratio; if < 1, the post process will render to a texture smaller than
|
* @property {number} [scaleRatio] Scale ratio; if < 1, the post process will render to a texture smaller than
|
||||||
@@ -343,144 +323,4 @@ class WebGLLayerRenderer extends LayerRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const tmpArray_ = [];
|
|
||||||
const bufferPositions_ = {vertexPosition: 0, indexPosition: 0};
|
|
||||||
|
|
||||||
function writePointVertex(buffer, pos, x, y, index) {
|
|
||||||
buffer[pos + 0] = x;
|
|
||||||
buffer[pos + 1] = y;
|
|
||||||
buffer[pos + 2] = index;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An object holding positions both in an index and a vertex buffer.
|
|
||||||
* @typedef {Object} BufferPositions
|
|
||||||
* @property {number} vertexPosition Position in the vertex buffer
|
|
||||||
* @property {number} indexPosition Position in the index buffer
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pushes a quad (two triangles) based on a point geometry
|
|
||||||
* @param {Float32Array} instructions Array of render instructions for points.
|
|
||||||
* @param {number} elementIndex Index from which render instructions will be read.
|
|
||||||
* @param {Float32Array} vertexBuffer 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.
|
|
||||||
* @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} indexPosition New position in the index buffer where future writes should start.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
export function writePointFeatureToBuffers(
|
|
||||||
instructions,
|
|
||||||
elementIndex,
|
|
||||||
vertexBuffer,
|
|
||||||
indexBuffer,
|
|
||||||
customAttributesCount,
|
|
||||||
bufferPositions
|
|
||||||
) {
|
|
||||||
// This is for x, y and index
|
|
||||||
const baseVertexAttrsCount = 3;
|
|
||||||
const baseInstructionsCount = 2;
|
|
||||||
const stride = baseVertexAttrsCount + customAttributesCount;
|
|
||||||
|
|
||||||
const x = instructions[elementIndex + 0];
|
|
||||||
const y = instructions[elementIndex + 1];
|
|
||||||
|
|
||||||
// read custom numerical attributes on the feature
|
|
||||||
const customAttrs = tmpArray_;
|
|
||||||
customAttrs.length = customAttributesCount;
|
|
||||||
for (let i = 0; i < customAttrs.length; i++) {
|
|
||||||
customAttrs[i] = instructions[elementIndex + baseInstructionsCount + i];
|
|
||||||
}
|
|
||||||
|
|
||||||
let vPos = bufferPositions ? bufferPositions.vertexPosition : 0;
|
|
||||||
let iPos = bufferPositions ? bufferPositions.indexPosition : 0;
|
|
||||||
const baseIndex = vPos / stride;
|
|
||||||
|
|
||||||
// push vertices for each of the four quad corners (first standard then custom attributes)
|
|
||||||
writePointVertex(vertexBuffer, vPos, x, y, 0);
|
|
||||||
customAttrs.length &&
|
|
||||||
vertexBuffer.set(customAttrs, vPos + baseVertexAttrsCount);
|
|
||||||
vPos += stride;
|
|
||||||
|
|
||||||
writePointVertex(vertexBuffer, vPos, x, y, 1);
|
|
||||||
customAttrs.length &&
|
|
||||||
vertexBuffer.set(customAttrs, vPos + baseVertexAttrsCount);
|
|
||||||
vPos += stride;
|
|
||||||
|
|
||||||
writePointVertex(vertexBuffer, vPos, x, y, 2);
|
|
||||||
customAttrs.length &&
|
|
||||||
vertexBuffer.set(customAttrs, vPos + baseVertexAttrsCount);
|
|
||||||
vPos += stride;
|
|
||||||
|
|
||||||
writePointVertex(vertexBuffer, vPos, x, y, 3);
|
|
||||||
customAttrs.length &&
|
|
||||||
vertexBuffer.set(customAttrs, vPos + baseVertexAttrsCount);
|
|
||||||
vPos += stride;
|
|
||||||
|
|
||||||
indexBuffer[iPos++] = baseIndex;
|
|
||||||
indexBuffer[iPos++] = baseIndex + 1;
|
|
||||||
indexBuffer[iPos++] = baseIndex + 3;
|
|
||||||
indexBuffer[iPos++] = baseIndex + 1;
|
|
||||||
indexBuffer[iPos++] = baseIndex + 2;
|
|
||||||
indexBuffer[iPos++] = baseIndex + 3;
|
|
||||||
|
|
||||||
bufferPositions_.vertexPosition = vPos;
|
|
||||||
bufferPositions_.indexPosition = iPos;
|
|
||||||
|
|
||||||
return bufferPositions_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a texture of 1x1 pixel, white
|
|
||||||
* @private
|
|
||||||
* @return {ImageData} Image data.
|
|
||||||
*/
|
|
||||||
export function getBlankImageData() {
|
|
||||||
const canvas = document.createElement('canvas');
|
|
||||||
const image = canvas.getContext('2d').createImageData(1, 1);
|
|
||||||
image.data[0] = 255;
|
|
||||||
image.data[1] = 255;
|
|
||||||
image.data[2] = 255;
|
|
||||||
image.data[3] = 255;
|
|
||||||
return image;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a color array based on a numerical id
|
|
||||||
* Note: the range for each component is 0 to 1 with 256 steps
|
|
||||||
* @param {number} id Id
|
|
||||||
* @param {Array<number>} [opt_array] Reusable array
|
|
||||||
* @return {Array<number>} Color array containing the encoded id
|
|
||||||
*/
|
|
||||||
export function colorEncodeId(id, opt_array) {
|
|
||||||
const array = opt_array || [];
|
|
||||||
const radix = 256;
|
|
||||||
const divide = radix - 1;
|
|
||||||
array[0] = Math.floor(id / radix / radix / radix) / divide;
|
|
||||||
array[1] = (Math.floor(id / radix / radix) % radix) / divide;
|
|
||||||
array[2] = (Math.floor(id / radix) % radix) / divide;
|
|
||||||
array[3] = (id % radix) / divide;
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads an id from a color-encoded array
|
|
||||||
* Note: the expected range for each component is 0 to 1 with 256 steps.
|
|
||||||
* @param {Array<number>} color Color array containing the encoded id
|
|
||||||
* @return {number} Decoded id
|
|
||||||
*/
|
|
||||||
export function colorDecodeId(color) {
|
|
||||||
let id = 0;
|
|
||||||
const radix = 256;
|
|
||||||
const mult = radix - 1;
|
|
||||||
id += Math.round(color[0] * radix * radix * radix * mult);
|
|
||||||
id += Math.round(color[1] * radix * radix * mult);
|
|
||||||
id += Math.round(color[2] * radix * mult);
|
|
||||||
id += Math.round(color[3] * mult);
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default WebGLLayerRenderer;
|
export default WebGLLayerRenderer;
|
||||||
|
|||||||
@@ -5,14 +5,11 @@ import BaseVector from '../../layer/BaseVector.js';
|
|||||||
import VectorEventType from '../../source/VectorEventType.js';
|
import VectorEventType from '../../source/VectorEventType.js';
|
||||||
import ViewHint from '../../ViewHint.js';
|
import ViewHint from '../../ViewHint.js';
|
||||||
import WebGLArrayBuffer from '../../webgl/Buffer.js';
|
import WebGLArrayBuffer from '../../webgl/Buffer.js';
|
||||||
import WebGLLayerRenderer, {
|
import WebGLLayerRenderer from './Layer.js';
|
||||||
WebGLWorkerMessageType,
|
|
||||||
colorDecodeId,
|
|
||||||
colorEncodeId,
|
|
||||||
} from './Layer.js';
|
|
||||||
import WebGLRenderTarget from '../../webgl/RenderTarget.js';
|
import WebGLRenderTarget from '../../webgl/RenderTarget.js';
|
||||||
import {ARRAY_BUFFER, DYNAMIC_DRAW, ELEMENT_ARRAY_BUFFER} from '../../webgl.js';
|
import {ARRAY_BUFFER, DYNAMIC_DRAW, ELEMENT_ARRAY_BUFFER} from '../../webgl.js';
|
||||||
import {AttributeType, DefaultUniform} from '../../webgl/Helper.js';
|
import {AttributeType, DefaultUniform} from '../../webgl/Helper.js';
|
||||||
|
import {WebGLWorkerMessageType} from '../../render/webgl/constants.js';
|
||||||
import {
|
import {
|
||||||
apply as applyTransform,
|
apply as applyTransform,
|
||||||
create as createTransform,
|
create as createTransform,
|
||||||
@@ -22,6 +19,7 @@ import {
|
|||||||
} from '../../transform.js';
|
} from '../../transform.js';
|
||||||
import {assert} from '../../asserts.js';
|
import {assert} from '../../asserts.js';
|
||||||
import {buffer, createEmpty, equals, getWidth} from '../../extent.js';
|
import {buffer, createEmpty, equals, getWidth} from '../../extent.js';
|
||||||
|
import {colorDecodeId, colorEncodeId} from '../../render/webgl/utils.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 {listen, unlistenByKey} from '../../events.js';
|
import {listen, unlistenByKey} from '../../events.js';
|
||||||
@@ -294,7 +292,11 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
|
|||||||
*/
|
*/
|
||||||
this.generateBuffersRun_ = 0;
|
this.generateBuffersRun_ = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
this.worker_ = createWebGLWorker();
|
this.worker_ = createWebGLWorker();
|
||||||
|
|
||||||
this.worker_.addEventListener(
|
this.worker_.addEventListener(
|
||||||
'message',
|
'message',
|
||||||
/**
|
/**
|
||||||
@@ -303,7 +305,7 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
|
|||||||
*/
|
*/
|
||||||
function (event) {
|
function (event) {
|
||||||
const received = event.data;
|
const received = event.data;
|
||||||
if (received.type === WebGLWorkerMessageType.GENERATE_BUFFERS) {
|
if (received.type === WebGLWorkerMessageType.GENERATE_POINT_BUFFERS) {
|
||||||
const projectionTransform = received.projectionTransform;
|
const projectionTransform = received.projectionTransform;
|
||||||
if (received.hitDetection) {
|
if (received.hitDetection) {
|
||||||
this.hitVerticesBuffer_.fromArrayBuffer(received.vertexBuffer);
|
this.hitVerticesBuffer_.fromArrayBuffer(received.vertexBuffer);
|
||||||
@@ -540,7 +542,7 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
|
|||||||
this.previousExtent_ = frameState.extent.slice();
|
this.previousExtent_ = frameState.extent.slice();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.helper.useProgram(this.program_);
|
this.helper.useProgram(this.program_, frameState);
|
||||||
this.helper.prepareDraw(frameState);
|
this.helper.prepareDraw(frameState);
|
||||||
|
|
||||||
// write new data
|
// write new data
|
||||||
@@ -637,9 +639,10 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @type {import('./Layer').WebGLWorkerGenerateBuffersMessage} */
|
/** @type {import('../../render/webgl/constants.js').WebGLWorkerGenerateBuffersMessage} */
|
||||||
const message = {
|
const message = {
|
||||||
type: WebGLWorkerMessageType.GENERATE_BUFFERS,
|
id: 0,
|
||||||
|
type: WebGLWorkerMessageType.GENERATE_POINT_BUFFERS,
|
||||||
renderInstructions: this.renderInstructions_.buffer,
|
renderInstructions: this.renderInstructions_.buffer,
|
||||||
customAttributesCount: this.customAttributes.length,
|
customAttributesCount: this.customAttributes.length,
|
||||||
};
|
};
|
||||||
@@ -650,10 +653,11 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
|
|||||||
this.worker_.postMessage(message, [this.renderInstructions_.buffer]);
|
this.worker_.postMessage(message, [this.renderInstructions_.buffer]);
|
||||||
this.renderInstructions_ = null;
|
this.renderInstructions_ = null;
|
||||||
|
|
||||||
/** @type {import('./Layer').WebGLWorkerGenerateBuffersMessage} */
|
/** @type {import('../../render/webgl/constants.js').WebGLWorkerGenerateBuffersMessage} */
|
||||||
if (this.hitDetectionEnabled_) {
|
if (this.hitDetectionEnabled_) {
|
||||||
const hitMessage = {
|
const hitMessage = {
|
||||||
type: WebGLWorkerMessageType.GENERATE_BUFFERS,
|
id: 0,
|
||||||
|
type: WebGLWorkerMessageType.GENERATE_POINT_BUFFERS,
|
||||||
renderInstructions: this.hitRenderInstructions_.buffer,
|
renderInstructions: this.hitRenderInstructions_.buffer,
|
||||||
customAttributesCount: 5 + this.customAttributes.length,
|
customAttributesCount: 5 + this.customAttributes.length,
|
||||||
};
|
};
|
||||||
@@ -726,7 +730,7 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
|
|||||||
Math.floor(frameState.size[1] / 2),
|
Math.floor(frameState.size[1] / 2),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
this.helper.useProgram(this.hitProgram_);
|
this.helper.useProgram(this.hitProgram_, frameState);
|
||||||
this.helper.prepareDrawToRenderTarget(
|
this.helper.prepareDrawToRenderTarget(
|
||||||
frameState,
|
frameState,
|
||||||
this.hitRenderTarget_,
|
this.hitRenderTarget_,
|
||||||
|
|||||||
@@ -508,7 +508,7 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.helper.useProgram(this.program_);
|
this.helper.useProgram(this.program_, frameState);
|
||||||
this.helper.prepareDraw(frameState, !blend);
|
this.helper.prepareDraw(frameState, !blend);
|
||||||
|
|
||||||
const zs = Object.keys(tileTexturesByZ)
|
const zs = Object.keys(tileTexturesByZ)
|
||||||
|
|||||||
409
src/ol/renderer/webgl/VectorLayer.js
Normal file
409
src/ol/renderer/webgl/VectorLayer.js
Normal file
@@ -0,0 +1,409 @@
|
|||||||
|
/**
|
||||||
|
* @module ol/renderer/webgl/VectorLayer
|
||||||
|
*/
|
||||||
|
import BaseVector from '../../layer/BaseVector.js';
|
||||||
|
import LineStringBatchRenderer from '../../render/webgl/LineStringBatchRenderer.js';
|
||||||
|
import MixedGeometryBatch from '../../render/webgl/MixedGeometryBatch.js';
|
||||||
|
import PointBatchRenderer from '../../render/webgl/PointBatchRenderer.js';
|
||||||
|
import PolygonBatchRenderer from '../../render/webgl/PolygonBatchRenderer.js';
|
||||||
|
import VectorEventType from '../../source/VectorEventType.js';
|
||||||
|
import ViewHint from '../../ViewHint.js';
|
||||||
|
import WebGLLayerRenderer from './Layer.js';
|
||||||
|
import {DefaultUniform} from '../../webgl/Helper.js';
|
||||||
|
import {
|
||||||
|
FILL_FRAGMENT_SHADER,
|
||||||
|
FILL_VERTEX_SHADER,
|
||||||
|
POINT_FRAGMENT_SHADER,
|
||||||
|
POINT_VERTEX_SHADER,
|
||||||
|
STROKE_FRAGMENT_SHADER,
|
||||||
|
STROKE_VERTEX_SHADER,
|
||||||
|
packColor,
|
||||||
|
} from './shaders.js';
|
||||||
|
import {buffer, createEmpty, equals, getWidth} from '../../extent.js';
|
||||||
|
import {create as createTransform} from '../../transform.js';
|
||||||
|
import {create as createWebGLWorker} from '../../worker/webgl.js';
|
||||||
|
import {listen, unlistenByKey} from '../../events.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {function(import("../../Feature").default, Object<string, *>):number} CustomAttributeCallback A callback computing
|
||||||
|
* the value of a custom attribute (different for each feature) to be passed on to the GPU.
|
||||||
|
* Properties are available as 2nd arg for quicker access.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} ShaderProgram An object containing both shaders (vertex and fragment) as well as the required attributes
|
||||||
|
* @property {string} [vertexShader] Vertex shader source (using the default one if unspecified).
|
||||||
|
* @property {string} [fragmentShader] Fragment shader source (using the default one if unspecified).
|
||||||
|
* @property {Object<import("./shaders.js").DefaultAttributes,CustomAttributeCallback>} attributes Custom attributes made available in the vertex shader.
|
||||||
|
* Keys are the names of the attributes which are then accessible in the vertex shader using the `a_` prefix, e.g.: `a_opacity`.
|
||||||
|
* Default shaders rely on the attributes in {@link module:ol/render/webgl/shaders~DefaultAttributes}.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} Options
|
||||||
|
* @property {string} [className='ol-layer'] A CSS class name to set to the canvas element.
|
||||||
|
* @property {ShaderProgram} [fill] Attributes and shaders for filling polygons.
|
||||||
|
* @property {ShaderProgram} [stroke] Attributes and shaders for line strings and polygon strokes.
|
||||||
|
* @property {ShaderProgram} [point] Attributes and shaders for points.
|
||||||
|
* @property {Object<string,import("../../webgl/Helper").UniformValue>} [uniforms] Uniform definitions.
|
||||||
|
* @property {Array<import("./Layer").PostProcessesOptions>} [postProcesses] Post-processes definitions
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Object<import("./shaders.js").DefaultAttributes,CustomAttributeCallback>} obj Lookup of attribute getters.
|
||||||
|
* @return {Array<import("../../render/webgl/BatchRenderer").CustomAttribute>} An array of attribute descriptors.
|
||||||
|
*/
|
||||||
|
function toAttributesArray(obj) {
|
||||||
|
return Object.keys(obj).map((key) => ({name: key, callback: obj[key]}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @classdesc
|
||||||
|
* Experimental WebGL vector renderer. Supports polygons, lines and points:
|
||||||
|
* * Polygons are broken down into triangles
|
||||||
|
* * Lines are rendered as strips of quads
|
||||||
|
* * Points are rendered as quads
|
||||||
|
*
|
||||||
|
* You need to provide vertex and fragment shaders as well as custom attributes for each type of geometry. All shaders
|
||||||
|
* can access the uniforms in the {@link module:ol/webgl/Helper~DefaultUniform} enum.
|
||||||
|
* The vertex shaders can access the following attributes depending on the geometry type:
|
||||||
|
* * For polygons: {@link module:ol/render/webgl/PolygonBatchRenderer~Attributes}
|
||||||
|
* * For line strings: {@link module:ol/render/webgl/LineStringBatchRenderer~Attributes}
|
||||||
|
* * For points: {@link module:ol/render/webgl/PointBatchRenderer~Attributes}
|
||||||
|
*
|
||||||
|
* Please note that the fragment shaders output should have premultiplied alpha, otherwise visual anomalies may occur.
|
||||||
|
*
|
||||||
|
* Note: this uses {@link module:ol/webgl/Helper~WebGLHelper} internally.
|
||||||
|
*/
|
||||||
|
class WebGLVectorLayerRenderer extends WebGLLayerRenderer {
|
||||||
|
/**
|
||||||
|
* @param {import("../../layer/Layer.js").default} layer Layer.
|
||||||
|
* @param {Options} options Options.
|
||||||
|
*/
|
||||||
|
constructor(layer, options) {
|
||||||
|
const uniforms = options.uniforms || {};
|
||||||
|
const projectionMatrixTransform = createTransform();
|
||||||
|
uniforms[DefaultUniform.PROJECTION_MATRIX] = projectionMatrixTransform;
|
||||||
|
|
||||||
|
super(layer, {
|
||||||
|
uniforms: uniforms,
|
||||||
|
postProcesses: options.postProcesses,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.sourceRevision_ = -1;
|
||||||
|
|
||||||
|
this.previousExtent_ = createEmpty();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This transform is updated on every frame and is the composition of:
|
||||||
|
* - invert of the world->screen transform that was used when rebuilding buffers (see `this.renderTransform_`)
|
||||||
|
* - current world->screen transform
|
||||||
|
* @type {import("../../transform.js").Transform}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.currentTransform_ = projectionMatrixTransform;
|
||||||
|
|
||||||
|
const fillAttributes = {
|
||||||
|
color: function () {
|
||||||
|
return packColor('#ddd');
|
||||||
|
},
|
||||||
|
opacity: function () {
|
||||||
|
return 1;
|
||||||
|
},
|
||||||
|
...(options.fill && options.fill.attributes),
|
||||||
|
};
|
||||||
|
|
||||||
|
const strokeAttributes = {
|
||||||
|
color: function () {
|
||||||
|
return packColor('#eee');
|
||||||
|
},
|
||||||
|
opacity: function () {
|
||||||
|
return 1;
|
||||||
|
},
|
||||||
|
width: function () {
|
||||||
|
return 1.5;
|
||||||
|
},
|
||||||
|
...(options.stroke && options.stroke.attributes),
|
||||||
|
};
|
||||||
|
|
||||||
|
const pointAttributes = {
|
||||||
|
color: function () {
|
||||||
|
return packColor('#eee');
|
||||||
|
},
|
||||||
|
opacity: function () {
|
||||||
|
return 1;
|
||||||
|
},
|
||||||
|
...(options.point && options.point.attributes),
|
||||||
|
};
|
||||||
|
|
||||||
|
this.fillVertexShader_ =
|
||||||
|
(options.fill && options.fill.vertexShader) || FILL_VERTEX_SHADER;
|
||||||
|
this.fillFragmentShader_ =
|
||||||
|
(options.fill && options.fill.fragmentShader) || FILL_FRAGMENT_SHADER;
|
||||||
|
this.fillAttributes_ = toAttributesArray(fillAttributes);
|
||||||
|
|
||||||
|
this.strokeVertexShader_ =
|
||||||
|
(options.stroke && options.stroke.vertexShader) || STROKE_VERTEX_SHADER;
|
||||||
|
this.strokeFragmentShader_ =
|
||||||
|
(options.stroke && options.stroke.fragmentShader) ||
|
||||||
|
STROKE_FRAGMENT_SHADER;
|
||||||
|
this.strokeAttributes_ = toAttributesArray(strokeAttributes);
|
||||||
|
|
||||||
|
this.pointVertexShader_ =
|
||||||
|
(options.point && options.point.vertexShader) || POINT_VERTEX_SHADER;
|
||||||
|
this.pointFragmentShader_ =
|
||||||
|
(options.point && options.point.fragmentShader) || POINT_FRAGMENT_SHADER;
|
||||||
|
this.pointAttributes_ = toAttributesArray(pointAttributes);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.worker_ = createWebGLWorker();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
this.batch_ = new MixedGeometryBatch();
|
||||||
|
|
||||||
|
const source = this.getLayer().getSource();
|
||||||
|
this.batch_.addFeatures(source.getFeatures());
|
||||||
|
this.sourceListenKeys_ = [
|
||||||
|
listen(
|
||||||
|
source,
|
||||||
|
VectorEventType.ADDFEATURE,
|
||||||
|
this.handleSourceFeatureAdded_,
|
||||||
|
this
|
||||||
|
),
|
||||||
|
listen(
|
||||||
|
source,
|
||||||
|
VectorEventType.CHANGEFEATURE,
|
||||||
|
this.handleSourceFeatureChanged_,
|
||||||
|
this
|
||||||
|
),
|
||||||
|
listen(
|
||||||
|
source,
|
||||||
|
VectorEventType.REMOVEFEATURE,
|
||||||
|
this.handleSourceFeatureDelete_,
|
||||||
|
this
|
||||||
|
),
|
||||||
|
listen(
|
||||||
|
source,
|
||||||
|
VectorEventType.CLEAR,
|
||||||
|
this.handleSourceFeatureClear_,
|
||||||
|
this
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
afterHelperCreated() {
|
||||||
|
this.polygonRenderer_ = new PolygonBatchRenderer(
|
||||||
|
this.helper,
|
||||||
|
this.worker_,
|
||||||
|
this.fillVertexShader_,
|
||||||
|
this.fillFragmentShader_,
|
||||||
|
this.fillAttributes_
|
||||||
|
);
|
||||||
|
this.pointRenderer_ = new PointBatchRenderer(
|
||||||
|
this.helper,
|
||||||
|
this.worker_,
|
||||||
|
this.pointVertexShader_,
|
||||||
|
this.pointFragmentShader_,
|
||||||
|
this.pointAttributes_
|
||||||
|
);
|
||||||
|
this.lineStringRenderer_ = new LineStringBatchRenderer(
|
||||||
|
this.helper,
|
||||||
|
this.worker_,
|
||||||
|
this.strokeVertexShader_,
|
||||||
|
this.strokeFragmentShader_,
|
||||||
|
this.strokeAttributes_
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("../../source/Vector.js").VectorSourceEvent} event Event.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
handleSourceFeatureAdded_(event) {
|
||||||
|
const feature = event.feature;
|
||||||
|
this.batch_.addFeature(feature);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("../../source/Vector.js").VectorSourceEvent} event Event.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
handleSourceFeatureChanged_(event) {
|
||||||
|
const feature = event.feature;
|
||||||
|
this.batch_.changeFeature(feature);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("../../source/Vector.js").VectorSourceEvent} event Event.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
handleSourceFeatureDelete_(event) {
|
||||||
|
const feature = event.feature;
|
||||||
|
this.batch_.removeFeature(feature);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
handleSourceFeatureClear_() {
|
||||||
|
this.batch_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the layer.
|
||||||
|
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
|
||||||
|
* @return {HTMLElement} The rendered element.
|
||||||
|
*/
|
||||||
|
renderFrame(frameState) {
|
||||||
|
const gl = this.helper.getGL();
|
||||||
|
this.preRender(gl, frameState);
|
||||||
|
|
||||||
|
const layer = this.getLayer();
|
||||||
|
const vectorSource = layer.getSource();
|
||||||
|
const projection = frameState.viewState.projection;
|
||||||
|
const multiWorld = vectorSource.getWrapX() && projection.canWrapX();
|
||||||
|
const projectionExtent = projection.getExtent();
|
||||||
|
const extent = frameState.extent;
|
||||||
|
const worldWidth = multiWorld ? getWidth(projectionExtent) : null;
|
||||||
|
const endWorld = multiWorld
|
||||||
|
? Math.ceil((extent[2] - projectionExtent[2]) / worldWidth) + 1
|
||||||
|
: 1;
|
||||||
|
let world = multiWorld
|
||||||
|
? Math.floor((extent[0] - projectionExtent[0]) / worldWidth)
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
this.polygonRenderer_.render(
|
||||||
|
this.batch_.polygonBatch,
|
||||||
|
this.currentTransform_,
|
||||||
|
frameState,
|
||||||
|
world * worldWidth
|
||||||
|
);
|
||||||
|
this.lineStringRenderer_.render(
|
||||||
|
this.batch_.lineStringBatch,
|
||||||
|
this.currentTransform_,
|
||||||
|
frameState,
|
||||||
|
world * worldWidth
|
||||||
|
);
|
||||||
|
this.pointRenderer_.render(
|
||||||
|
this.batch_.pointBatch,
|
||||||
|
this.currentTransform_,
|
||||||
|
frameState,
|
||||||
|
world * worldWidth
|
||||||
|
);
|
||||||
|
} while (++world < endWorld);
|
||||||
|
|
||||||
|
this.helper.finalizeDraw(frameState);
|
||||||
|
|
||||||
|
const canvas = this.helper.getCanvas();
|
||||||
|
const layerState = frameState.layerStatesArray[frameState.layerIndex];
|
||||||
|
const opacity = layerState.opacity;
|
||||||
|
if (opacity !== parseFloat(canvas.style.opacity)) {
|
||||||
|
canvas.style.opacity = String(opacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.postRender(gl, frameState);
|
||||||
|
return canvas;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether renderFrame should be called.
|
||||||
|
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
|
||||||
|
* @return {boolean} Layer is ready to be rendered.
|
||||||
|
*/
|
||||||
|
prepareFrameInternal(frameState) {
|
||||||
|
const layer = this.getLayer();
|
||||||
|
const vectorSource = layer.getSource();
|
||||||
|
const viewState = frameState.viewState;
|
||||||
|
const viewNotMoving =
|
||||||
|
!frameState.viewHints[ViewHint.ANIMATING] &&
|
||||||
|
!frameState.viewHints[ViewHint.INTERACTING];
|
||||||
|
const extentChanged = !equals(this.previousExtent_, frameState.extent);
|
||||||
|
const sourceChanged = this.sourceRevision_ < vectorSource.getRevision();
|
||||||
|
|
||||||
|
if (sourceChanged) {
|
||||||
|
this.sourceRevision_ = vectorSource.getRevision();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (viewNotMoving && (extentChanged || sourceChanged)) {
|
||||||
|
const projection = viewState.projection;
|
||||||
|
const resolution = viewState.resolution;
|
||||||
|
|
||||||
|
const renderBuffer =
|
||||||
|
layer instanceof BaseVector ? layer.getRenderBuffer() : 0;
|
||||||
|
const extent = buffer(frameState.extent, renderBuffer * resolution);
|
||||||
|
vectorSource.loadFeatures(extent, resolution, projection);
|
||||||
|
|
||||||
|
this.ready = false;
|
||||||
|
let remaining = 3;
|
||||||
|
const rebuildCb = () => {
|
||||||
|
remaining--;
|
||||||
|
this.ready = remaining <= 0;
|
||||||
|
this.getLayer().changed();
|
||||||
|
};
|
||||||
|
|
||||||
|
this.polygonRenderer_.rebuild(
|
||||||
|
this.batch_.polygonBatch,
|
||||||
|
frameState,
|
||||||
|
'Polygon',
|
||||||
|
rebuildCb
|
||||||
|
);
|
||||||
|
this.lineStringRenderer_.rebuild(
|
||||||
|
this.batch_.lineStringBatch,
|
||||||
|
frameState,
|
||||||
|
'LineString',
|
||||||
|
rebuildCb
|
||||||
|
);
|
||||||
|
this.pointRenderer_.rebuild(
|
||||||
|
this.batch_.pointBatch,
|
||||||
|
frameState,
|
||||||
|
'Point',
|
||||||
|
rebuildCb
|
||||||
|
);
|
||||||
|
this.previousExtent_ = frameState.extent.slice();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.helper.makeProjectionTransform(frameState, this.currentTransform_);
|
||||||
|
this.helper.prepareDraw(frameState);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("../../coordinate.js").Coordinate} coordinate Coordinate.
|
||||||
|
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
|
||||||
|
* @param {number} hitTolerance Hit tolerance in pixels.
|
||||||
|
* @param {import("../vector.js").FeatureCallback<T>} callback Feature callback.
|
||||||
|
* @param {Array<import("../Map.js").HitMatch<T>>} matches The hit detected matches with tolerance.
|
||||||
|
* @return {T|undefined} Callback result.
|
||||||
|
* @template T
|
||||||
|
*/
|
||||||
|
forEachFeatureAtCoordinate(
|
||||||
|
coordinate,
|
||||||
|
frameState,
|
||||||
|
hitTolerance,
|
||||||
|
callback,
|
||||||
|
matches
|
||||||
|
) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up.
|
||||||
|
*/
|
||||||
|
disposeInternal() {
|
||||||
|
this.worker_.terminate();
|
||||||
|
this.layer_ = null;
|
||||||
|
this.sourceListenKeys_.forEach(function (key) {
|
||||||
|
unlistenByKey(key);
|
||||||
|
});
|
||||||
|
this.sourceListenKeys_ = null;
|
||||||
|
super.disposeInternal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WebGLVectorLayerRenderer;
|
||||||
198
src/ol/renderer/webgl/shaders.js
Normal file
198
src/ol/renderer/webgl/shaders.js
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
/**
|
||||||
|
* @module ol/renderer/webgl/shaders
|
||||||
|
*/
|
||||||
|
import {asArray} from '../../color.js';
|
||||||
|
|
||||||
|
/** @typedef {'color'|'opacity'|'width'} DefaultAttributes */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Packs red/green/blue channels of a color into a single float value; alpha is ignored.
|
||||||
|
* This is how the color is expected to be computed.
|
||||||
|
* @param {import("../../color.js").Color|string} color Color as array of numbers or string
|
||||||
|
* @return {number} Float value containing the color
|
||||||
|
*/
|
||||||
|
export function packColor(color) {
|
||||||
|
const array = asArray(color);
|
||||||
|
const r = array[0] * 256 * 256;
|
||||||
|
const g = array[1] * 256;
|
||||||
|
const b = array[2];
|
||||||
|
return r + g + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DECODE_COLOR_EXPRESSION = `vec3(
|
||||||
|
fract(floor(a_color / 256.0 / 256.0) / 256.0),
|
||||||
|
fract(floor(a_color / 256.0) / 256.0),
|
||||||
|
fract(a_color / 256.0)
|
||||||
|
);`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default polygon vertex shader.
|
||||||
|
* Relies on the color and opacity attributes.
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
export const FILL_VERTEX_SHADER = `
|
||||||
|
precision mediump float;
|
||||||
|
uniform mat4 u_projectionMatrix;
|
||||||
|
attribute vec2 a_position;
|
||||||
|
attribute float a_color;
|
||||||
|
attribute float a_opacity;
|
||||||
|
varying vec3 v_color;
|
||||||
|
varying float v_opacity;
|
||||||
|
|
||||||
|
void main(void) {
|
||||||
|
gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0);
|
||||||
|
v_color = ${DECODE_COLOR_EXPRESSION}
|
||||||
|
v_opacity = a_opacity;
|
||||||
|
}`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default polygon fragment shader.
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
export const FILL_FRAGMENT_SHADER = `
|
||||||
|
precision mediump float;
|
||||||
|
varying vec3 v_color;
|
||||||
|
varying float v_opacity;
|
||||||
|
|
||||||
|
void main(void) {
|
||||||
|
gl_FragColor = vec4(v_color, 1.0) * v_opacity;
|
||||||
|
}`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default linestring vertex shader.
|
||||||
|
* Relies on color, opacity and width attributes.
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
export const STROKE_VERTEX_SHADER = `
|
||||||
|
precision mediump float;
|
||||||
|
uniform mat4 u_projectionMatrix;
|
||||||
|
uniform vec2 u_sizePx;
|
||||||
|
attribute vec2 a_segmentStart;
|
||||||
|
attribute vec2 a_segmentEnd;
|
||||||
|
attribute float a_parameters;
|
||||||
|
attribute float a_color;
|
||||||
|
attribute float a_opacity;
|
||||||
|
attribute float a_width;
|
||||||
|
varying vec2 v_segmentStart;
|
||||||
|
varying vec2 v_segmentEnd;
|
||||||
|
varying float v_angleStart;
|
||||||
|
varying float v_angleEnd;
|
||||||
|
varying vec3 v_color;
|
||||||
|
varying float v_opacity;
|
||||||
|
varying float v_width;
|
||||||
|
|
||||||
|
vec2 worldToPx(vec2 worldPos) {
|
||||||
|
vec4 screenPos = u_projectionMatrix * vec4(worldPos, 0.0, 1.0);
|
||||||
|
return (0.5 * screenPos.xy + 0.5) * u_sizePx;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 pxToScreen(vec2 pxPos) {
|
||||||
|
vec2 screenPos = pxPos * 4.0 / u_sizePx;
|
||||||
|
return vec4(screenPos.xy, 0.0, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 getOffsetDirection(vec2 normalPx, vec2 tangentPx, float joinAngle) {
|
||||||
|
if (cos(joinAngle) > 0.93) return normalPx - tangentPx;
|
||||||
|
float halfAngle = joinAngle / 2.0;
|
||||||
|
vec2 angleBisectorNormal = vec2(
|
||||||
|
sin(halfAngle) * normalPx.x + cos(halfAngle) * normalPx.y,
|
||||||
|
-cos(halfAngle) * normalPx.x + sin(halfAngle) * normalPx.y
|
||||||
|
);
|
||||||
|
float length = 1.0 / sin(halfAngle);
|
||||||
|
return angleBisectorNormal * length;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main(void) {
|
||||||
|
float anglePrecision = 1500.0;
|
||||||
|
float paramShift = 10000.0;
|
||||||
|
v_angleStart = fract(a_parameters / paramShift) * paramShift / anglePrecision;
|
||||||
|
v_angleEnd = fract(floor(a_parameters / paramShift + 0.5) / paramShift) * paramShift / anglePrecision;
|
||||||
|
float vertexNumber = floor(a_parameters / paramShift / paramShift + 0.0001);
|
||||||
|
vec2 tangentPx = worldToPx(a_segmentEnd) - worldToPx(a_segmentStart);
|
||||||
|
tangentPx = normalize(tangentPx);
|
||||||
|
vec2 normalPx = vec2(-tangentPx.y, tangentPx.x);
|
||||||
|
float normalDir = vertexNumber < 0.5 || (vertexNumber > 1.5 && vertexNumber < 2.5) ? 1.0 : -1.0;
|
||||||
|
float tangentDir = vertexNumber < 1.5 ? 1.0 : -1.0;
|
||||||
|
float angle = vertexNumber < 1.5 ? v_angleStart : v_angleEnd;
|
||||||
|
vec2 offsetPx = getOffsetDirection(normalPx * normalDir, tangentDir * tangentPx, angle) * a_width * 0.5;
|
||||||
|
vec2 position = vertexNumber < 1.5 ? a_segmentStart : a_segmentEnd;
|
||||||
|
gl_Position = u_projectionMatrix * vec4(position, 0.0, 1.0) + pxToScreen(offsetPx);
|
||||||
|
v_segmentStart = worldToPx(a_segmentStart);
|
||||||
|
v_segmentEnd = worldToPx(a_segmentEnd);
|
||||||
|
v_color = ${DECODE_COLOR_EXPRESSION}
|
||||||
|
v_opacity = a_opacity;
|
||||||
|
v_width = a_width;
|
||||||
|
}`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default linestring fragment shader.
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
export const STROKE_FRAGMENT_SHADER = `
|
||||||
|
precision mediump float;
|
||||||
|
uniform float u_pixelRatio;
|
||||||
|
varying vec2 v_segmentStart;
|
||||||
|
varying vec2 v_segmentEnd;
|
||||||
|
varying float v_angleStart;
|
||||||
|
varying float v_angleEnd;
|
||||||
|
varying vec3 v_color;
|
||||||
|
varying float v_opacity;
|
||||||
|
varying float v_width;
|
||||||
|
|
||||||
|
float segmentDistanceField(vec2 point, vec2 start, vec2 end, float radius) {
|
||||||
|
vec2 startToPoint = point - start;
|
||||||
|
vec2 startToEnd = end - start;
|
||||||
|
float ratio = clamp(dot(startToPoint, startToEnd) / dot(startToEnd, startToEnd), 0.0, 1.0);
|
||||||
|
float dist = length(startToPoint - ratio * startToEnd);
|
||||||
|
return 1.0 - smoothstep(radius - 1.0, radius, dist);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main(void) {
|
||||||
|
vec2 v_currentPoint = gl_FragCoord.xy / u_pixelRatio;
|
||||||
|
gl_FragColor = vec4(v_color, 1.0) * v_opacity;
|
||||||
|
gl_FragColor *= segmentDistanceField(v_currentPoint, v_segmentStart, v_segmentEnd, v_width);
|
||||||
|
}`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default point vertex shader.
|
||||||
|
* Relies on color and opacity attributes.
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
export const POINT_VERTEX_SHADER = `
|
||||||
|
precision mediump float;
|
||||||
|
uniform mat4 u_projectionMatrix;
|
||||||
|
uniform mat4 u_offsetScaleMatrix;
|
||||||
|
attribute vec2 a_position;
|
||||||
|
attribute float a_index;
|
||||||
|
attribute float a_color;
|
||||||
|
attribute float a_opacity;
|
||||||
|
varying vec2 v_texCoord;
|
||||||
|
varying vec3 v_color;
|
||||||
|
varying float v_opacity;
|
||||||
|
|
||||||
|
void main(void) {
|
||||||
|
mat4 offsetMatrix = u_offsetScaleMatrix;
|
||||||
|
float size = 6.0;
|
||||||
|
float offsetX = a_index == 0.0 || a_index == 3.0 ? -size / 2.0 : size / 2.0;
|
||||||
|
float offsetY = a_index == 0.0 || a_index == 1.0 ? -size / 2.0 : 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_color = ${DECODE_COLOR_EXPRESSION}
|
||||||
|
v_opacity = a_opacity;
|
||||||
|
}`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default point fragment shader.
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
export const POINT_FRAGMENT_SHADER = `
|
||||||
|
precision mediump float;
|
||||||
|
varying vec3 v_color;
|
||||||
|
varying float v_opacity;
|
||||||
|
|
||||||
|
void main(void) {
|
||||||
|
gl_FragColor = vec4(v_color, 1.0) * v_opacity;
|
||||||
|
}`;
|
||||||
@@ -38,8 +38,8 @@ export const ShaderType = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uniform names used in the default shaders: `PROJECTION_MATRIX`, `OFFSET_SCALE_MATRIX`.
|
* Names of uniforms made available to all shaders.
|
||||||
* and `OFFSET_ROTATION_MATRIX`.
|
* Please note: changing these *will* break custom shaders!
|
||||||
* @enum {string}
|
* @enum {string}
|
||||||
*/
|
*/
|
||||||
export const DefaultUniform = {
|
export const DefaultUniform = {
|
||||||
@@ -49,6 +49,8 @@ export const DefaultUniform = {
|
|||||||
TIME: 'u_time',
|
TIME: 'u_time',
|
||||||
ZOOM: 'u_zoom',
|
ZOOM: 'u_zoom',
|
||||||
RESOLUTION: 'u_resolution',
|
RESOLUTION: 'u_resolution',
|
||||||
|
SIZE_PX: 'u_sizePx',
|
||||||
|
PIXEL_RATIO: 'u_pixelRatio',
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -206,11 +208,12 @@ function releaseCanvas(key) {
|
|||||||
* Shaders must be compiled and assembled into a program like so:
|
* Shaders must be compiled and assembled into a program like so:
|
||||||
* ```js
|
* ```js
|
||||||
* // here we simply create two shaders and assemble them in a program which is then used
|
* // here we simply create two shaders and assemble them in a program which is then used
|
||||||
* // for subsequent rendering calls
|
* // for subsequent rendering calls; note how a frameState is required to set up a program,
|
||||||
|
* // as several default uniforms are computed from it (projection matrix, zoom level, etc.)
|
||||||
* const vertexShader = new WebGLVertex(VERTEX_SHADER);
|
* const vertexShader = new WebGLVertex(VERTEX_SHADER);
|
||||||
* const fragmentShader = new WebGLFragment(FRAGMENT_SHADER);
|
* const fragmentShader = new WebGLFragment(FRAGMENT_SHADER);
|
||||||
* const program = this.context.getProgram(fragmentShader, vertexShader);
|
* const program = this.context.getProgram(fragmentShader, vertexShader);
|
||||||
* helper.useProgram(this.program);
|
* helper.useProgram(this.program, frameState);
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* 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.
|
||||||
@@ -302,8 +305,6 @@ function releaseCanvas(key) {
|
|||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* 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}.
|
||||||
*
|
|
||||||
* @api
|
|
||||||
*/
|
*/
|
||||||
class WebGLHelper extends Disposable {
|
class WebGLHelper extends Disposable {
|
||||||
/**
|
/**
|
||||||
@@ -484,7 +485,6 @@ class WebGLHelper extends Disposable {
|
|||||||
* the WebGL buffer, bind it, populate it, and add an entry to
|
* the WebGL buffer, bind it, populate it, and add an entry to
|
||||||
* the cache.
|
* the cache.
|
||||||
* @param {import("./Buffer").default} buffer Buffer.
|
* @param {import("./Buffer").default} buffer Buffer.
|
||||||
* @api
|
|
||||||
*/
|
*/
|
||||||
bindBuffer(buffer) {
|
bindBuffer(buffer) {
|
||||||
const gl = this.getGL();
|
const gl = this.getGL();
|
||||||
@@ -505,7 +505,6 @@ class WebGLHelper extends Disposable {
|
|||||||
* Update the data contained in the buffer array; this is required for the
|
* Update the data contained in the buffer array; this is required for the
|
||||||
* new data to be rendered
|
* new data to be rendered
|
||||||
* @param {import("./Buffer").default} buffer Buffer.
|
* @param {import("./Buffer").default} buffer Buffer.
|
||||||
* @api
|
|
||||||
*/
|
*/
|
||||||
flushBufferData(buffer) {
|
flushBufferData(buffer) {
|
||||||
const gl = this.getGL();
|
const gl = this.getGL();
|
||||||
@@ -551,7 +550,6 @@ class WebGLHelper extends Disposable {
|
|||||||
* subsequent draw calls.
|
* subsequent draw calls.
|
||||||
* @param {import("../PluggableMap.js").FrameState} frameState current frame state
|
* @param {import("../PluggableMap.js").FrameState} frameState current frame state
|
||||||
* @param {boolean} [opt_disableAlphaBlend] If true, no alpha blending will happen.
|
* @param {boolean} [opt_disableAlphaBlend] If true, no alpha blending will happen.
|
||||||
* @api
|
|
||||||
*/
|
*/
|
||||||
prepareDraw(frameState, opt_disableAlphaBlend) {
|
prepareDraw(frameState, opt_disableAlphaBlend) {
|
||||||
const gl = this.getGL();
|
const gl = this.getGL();
|
||||||
@@ -564,8 +562,6 @@ class WebGLHelper extends Disposable {
|
|||||||
canvas.style.width = size[0] + 'px';
|
canvas.style.width = size[0] + 'px';
|
||||||
canvas.style.height = size[1] + 'px';
|
canvas.style.height = size[1] + 'px';
|
||||||
|
|
||||||
gl.useProgram(this.currentProgram_);
|
|
||||||
|
|
||||||
// loop backwards in post processes list
|
// loop backwards in post processes list
|
||||||
for (let i = this.postProcessPasses_.length - 1; i >= 0; i--) {
|
for (let i = this.postProcessPasses_.length - 1; i >= 0; i--) {
|
||||||
this.postProcessPasses_[i].init(frameState);
|
this.postProcessPasses_[i].init(frameState);
|
||||||
@@ -581,10 +577,6 @@ class WebGLHelper extends Disposable {
|
|||||||
gl.ONE,
|
gl.ONE,
|
||||||
opt_disableAlphaBlend ? gl.ZERO : gl.ONE_MINUS_SRC_ALPHA
|
opt_disableAlphaBlend ? gl.ZERO : gl.ONE_MINUS_SRC_ALPHA
|
||||||
);
|
);
|
||||||
|
|
||||||
gl.useProgram(this.currentProgram_);
|
|
||||||
this.applyFrameState(frameState);
|
|
||||||
this.applyUniforms(frameState);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -609,17 +601,12 @@ class WebGLHelper extends Disposable {
|
|||||||
gl.ONE,
|
gl.ONE,
|
||||||
opt_disableAlphaBlend ? gl.ZERO : gl.ONE_MINUS_SRC_ALPHA
|
opt_disableAlphaBlend ? gl.ZERO : gl.ONE_MINUS_SRC_ALPHA
|
||||||
);
|
);
|
||||||
|
|
||||||
gl.useProgram(this.currentProgram_);
|
|
||||||
this.applyFrameState(frameState);
|
|
||||||
this.applyUniforms(frameState);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute a draw call based on the currently bound program, texture, buffers, attributes.
|
* Execute a draw call based on the currently bound program, texture, buffers, attributes.
|
||||||
* @param {number} start Start index.
|
* @param {number} start Start index.
|
||||||
* @param {number} end End index.
|
* @param {number} end End index.
|
||||||
* @api
|
|
||||||
*/
|
*/
|
||||||
drawElements(start, end) {
|
drawElements(start, end) {
|
||||||
const gl = this.getGL();
|
const gl = this.getGL();
|
||||||
@@ -660,7 +647,6 @@ class WebGLHelper extends Disposable {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {HTMLCanvasElement} Canvas.
|
* @return {HTMLCanvasElement} Canvas.
|
||||||
* @api
|
|
||||||
*/
|
*/
|
||||||
getCanvas() {
|
getCanvas() {
|
||||||
return this.canvas_;
|
return this.canvas_;
|
||||||
@@ -669,7 +655,6 @@ class WebGLHelper extends Disposable {
|
|||||||
/**
|
/**
|
||||||
* Get the WebGL rendering context
|
* Get the WebGL rendering context
|
||||||
* @return {WebGLRenderingContext} The rendering context.
|
* @return {WebGLRenderingContext} The rendering context.
|
||||||
* @api
|
|
||||||
*/
|
*/
|
||||||
getGL() {
|
getGL() {
|
||||||
return this.gl_;
|
return this.gl_;
|
||||||
@@ -682,6 +667,7 @@ class WebGLHelper extends Disposable {
|
|||||||
applyFrameState(frameState) {
|
applyFrameState(frameState) {
|
||||||
const size = frameState.size;
|
const size = frameState.size;
|
||||||
const rotation = frameState.viewState.rotation;
|
const rotation = frameState.viewState.rotation;
|
||||||
|
const pixelRatio = frameState.pixelRatio;
|
||||||
|
|
||||||
const offsetScaleMatrix = resetTransform(this.offsetScaleMatrix_);
|
const offsetScaleMatrix = resetTransform(this.offsetScaleMatrix_);
|
||||||
scaleTransform(offsetScaleMatrix, 2 / size[0], 2 / size[1]);
|
scaleTransform(offsetScaleMatrix, 2 / size[0], 2 / size[1]);
|
||||||
@@ -709,6 +695,8 @@ class WebGLHelper extends Disposable {
|
|||||||
DefaultUniform.RESOLUTION,
|
DefaultUniform.RESOLUTION,
|
||||||
frameState.viewState.resolution
|
frameState.viewState.resolution
|
||||||
);
|
);
|
||||||
|
this.setUniformFloatValue(DefaultUniform.PIXEL_RATIO, pixelRatio);
|
||||||
|
this.setUniformFloatVec2(DefaultUniform.SIZE_PX, [size[0], size[1]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -803,22 +791,19 @@ class WebGLHelper extends Disposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use a program. If the program is already in use, this will return `false`.
|
* Set up a program for use. The program will be set as the current one. Then, the uniforms used
|
||||||
|
* in the program will be set based on the current frame state and the helper configuration.
|
||||||
* @param {WebGLProgram} program Program.
|
* @param {WebGLProgram} program Program.
|
||||||
* @return {boolean} Changed.
|
* @param {import("../PluggableMap.js").FrameState} frameState Frame state.
|
||||||
* @api
|
|
||||||
*/
|
*/
|
||||||
useProgram(program) {
|
useProgram(program, frameState) {
|
||||||
if (program == this.currentProgram_) {
|
const gl = this.getGL();
|
||||||
return false;
|
gl.useProgram(program);
|
||||||
} else {
|
this.currentProgram_ = program;
|
||||||
const gl = this.getGL();
|
this.uniformLocations_ = {};
|
||||||
gl.useProgram(program);
|
this.attribLocations_ = {};
|
||||||
this.currentProgram_ = program;
|
this.applyFrameState(frameState);
|
||||||
this.uniformLocations_ = {};
|
this.applyUniforms(frameState);
|
||||||
this.attribLocations_ = {};
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -843,7 +828,6 @@ class WebGLHelper extends Disposable {
|
|||||||
* @param {string} fragmentShaderSource Fragment shader source.
|
* @param {string} fragmentShaderSource Fragment shader source.
|
||||||
* @param {string} vertexShaderSource Vertex shader source.
|
* @param {string} vertexShaderSource Vertex shader source.
|
||||||
* @return {WebGLProgram} Program
|
* @return {WebGLProgram} Program
|
||||||
* @api
|
|
||||||
*/
|
*/
|
||||||
getProgram(fragmentShaderSource, vertexShaderSource) {
|
getProgram(fragmentShaderSource, vertexShaderSource) {
|
||||||
const gl = this.getGL();
|
const gl = this.getGL();
|
||||||
@@ -893,7 +877,6 @@ class WebGLHelper extends Disposable {
|
|||||||
* Will get the location from the shader or the cache
|
* Will get the location from the shader or the cache
|
||||||
* @param {string} name Uniform name
|
* @param {string} name Uniform name
|
||||||
* @return {WebGLUniformLocation} uniformLocation
|
* @return {WebGLUniformLocation} uniformLocation
|
||||||
* @api
|
|
||||||
*/
|
*/
|
||||||
getUniformLocation(name) {
|
getUniformLocation(name) {
|
||||||
if (this.uniformLocations_[name] === undefined) {
|
if (this.uniformLocations_[name] === undefined) {
|
||||||
@@ -909,7 +892,6 @@ class WebGLHelper extends Disposable {
|
|||||||
* Will get the location from the shader or the cache
|
* Will get the location from the shader or the cache
|
||||||
* @param {string} name Attribute name
|
* @param {string} name Attribute name
|
||||||
* @return {number} attribLocation
|
* @return {number} attribLocation
|
||||||
* @api
|
|
||||||
*/
|
*/
|
||||||
getAttributeLocation(name) {
|
getAttributeLocation(name) {
|
||||||
if (this.attribLocations_[name] === undefined) {
|
if (this.attribLocations_[name] === undefined) {
|
||||||
@@ -927,7 +909,6 @@ class WebGLHelper extends Disposable {
|
|||||||
* @param {import("../PluggableMap.js").FrameState} frameState Frame state.
|
* @param {import("../PluggableMap.js").FrameState} frameState Frame state.
|
||||||
* @param {import("../transform").Transform} transform Transform to update.
|
* @param {import("../transform").Transform} transform Transform to update.
|
||||||
* @return {import("../transform").Transform} The updated transform object.
|
* @return {import("../transform").Transform} The updated transform object.
|
||||||
* @api
|
|
||||||
*/
|
*/
|
||||||
makeProjectionTransform(frameState, transform) {
|
makeProjectionTransform(frameState, transform) {
|
||||||
const size = frameState.size;
|
const size = frameState.size;
|
||||||
@@ -953,12 +934,20 @@ class WebGLHelper extends Disposable {
|
|||||||
* Give a value for a standard float uniform
|
* Give a value for a standard float uniform
|
||||||
* @param {string} uniform Uniform name
|
* @param {string} uniform Uniform name
|
||||||
* @param {number} value Value
|
* @param {number} value Value
|
||||||
* @api
|
|
||||||
*/
|
*/
|
||||||
setUniformFloatValue(uniform, value) {
|
setUniformFloatValue(uniform, value) {
|
||||||
this.getGL().uniform1f(this.getUniformLocation(uniform), value);
|
this.getGL().uniform1f(this.getUniformLocation(uniform), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Give a value for a vec2 uniform
|
||||||
|
* @param {string} uniform Uniform name
|
||||||
|
* @param {Array<number>} value Array of length 4.
|
||||||
|
*/
|
||||||
|
setUniformFloatVec2(uniform, value) {
|
||||||
|
this.getGL().uniform2fv(this.getUniformLocation(uniform), value);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Give a value for a vec4 uniform
|
* Give a value for a vec4 uniform
|
||||||
* @param {string} uniform Uniform name
|
* @param {string} uniform Uniform name
|
||||||
@@ -972,7 +961,6 @@ class WebGLHelper extends Disposable {
|
|||||||
* Give a value for a standard matrix4 uniform
|
* Give a value for a standard matrix4 uniform
|
||||||
* @param {string} uniform Uniform name
|
* @param {string} uniform Uniform name
|
||||||
* @param {Array<number>} value Matrix value
|
* @param {Array<number>} value Matrix value
|
||||||
* @api
|
|
||||||
*/
|
*/
|
||||||
setUniformMatrixValue(uniform, value) {
|
setUniformMatrixValue(uniform, value) {
|
||||||
this.getGL().uniformMatrix4fv(
|
this.getGL().uniformMatrix4fv(
|
||||||
@@ -1014,7 +1002,6 @@ class WebGLHelper extends Disposable {
|
|||||||
* i.e. tell the GPU where to read the different attributes in the buffer. An error in the
|
* 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.
|
* 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
|
* @param {Array<AttributeDescription>} attributes Ordered list of attributes to read from the buffer
|
||||||
* @api
|
|
||||||
*/
|
*/
|
||||||
enableAttributes(attributes) {
|
enableAttributes(attributes) {
|
||||||
const stride = computeAttributesStride(attributes);
|
const stride = computeAttributesStride(attributes);
|
||||||
@@ -1056,7 +1043,6 @@ class WebGLHelper extends Disposable {
|
|||||||
* @param {ImageData|HTMLImageElement|HTMLCanvasElement} [opt_data] Image data/object to bind to the texture
|
* @param {ImageData|HTMLImageElement|HTMLCanvasElement} [opt_data] Image data/object to bind to the texture
|
||||||
* @param {WebGLTexture} [opt_texture] Existing texture to reuse
|
* @param {WebGLTexture} [opt_texture] Existing texture to reuse
|
||||||
* @return {WebGLTexture} The generated texture
|
* @return {WebGLTexture} The generated texture
|
||||||
* @api
|
|
||||||
*/
|
*/
|
||||||
createTexture(size, opt_data, opt_texture) {
|
createTexture(size, opt_data, opt_texture) {
|
||||||
const gl = this.getGL();
|
const gl = this.getGL();
|
||||||
@@ -1103,7 +1089,6 @@ class WebGLHelper extends Disposable {
|
|||||||
* Compute a stride in bytes based on a list of attributes
|
* Compute a stride in bytes based on a list of attributes
|
||||||
* @param {Array<AttributeDescription>} attributes Ordered list of attributes
|
* @param {Array<AttributeDescription>} attributes Ordered list of attributes
|
||||||
* @return {number} Stride, ie amount of values for each vertex in the vertex buffer
|
* @return {number} Stride, ie amount of values for each vertex in the vertex buffer
|
||||||
* @api
|
|
||||||
*/
|
*/
|
||||||
export function computeAttributesStride(attributes) {
|
export function computeAttributesStride(attributes) {
|
||||||
let stride = 0;
|
let stride = 0;
|
||||||
|
|||||||
@@ -2,60 +2,176 @@
|
|||||||
* A worker that does cpu-heavy tasks related to webgl rendering.
|
* A worker that does cpu-heavy tasks related to webgl rendering.
|
||||||
* @module ol/worker/webgl
|
* @module ol/worker/webgl
|
||||||
*/
|
*/
|
||||||
import {
|
import {WebGLWorkerMessageType} from '../render/webgl/constants.js';
|
||||||
WebGLWorkerMessageType,
|
|
||||||
writePointFeatureToBuffers,
|
|
||||||
} from '../renderer/webgl/Layer.js';
|
|
||||||
import {assign} from '../obj.js';
|
import {assign} from '../obj.js';
|
||||||
|
import {
|
||||||
|
create as createTransform,
|
||||||
|
makeInverse as makeInverseTransform,
|
||||||
|
} from '../transform.js';
|
||||||
|
import {
|
||||||
|
writeLineSegmentToBuffers,
|
||||||
|
writePointFeatureToBuffers,
|
||||||
|
writePolygonTrianglesToBuffers,
|
||||||
|
} from '../render/webgl/utils.js';
|
||||||
|
|
||||||
/** @type {any} */
|
/** @type {any} */
|
||||||
const worker = self;
|
const worker = self;
|
||||||
|
|
||||||
worker.onmessage = (event) => {
|
worker.onmessage = (event) => {
|
||||||
const received = event.data;
|
const received = event.data;
|
||||||
if (received.type === WebGLWorkerMessageType.GENERATE_BUFFERS) {
|
switch (received.type) {
|
||||||
// This is specific to point features (x, y, index)
|
case WebGLWorkerMessageType.GENERATE_POINT_BUFFERS: {
|
||||||
const baseVertexAttrsCount = 3;
|
// This is specific to point features (x, y, index)
|
||||||
const baseInstructionsCount = 2;
|
const baseVertexAttrsCount = 3;
|
||||||
|
const baseInstructionsCount = 2;
|
||||||
|
|
||||||
const customAttrsCount = received.customAttributesCount;
|
const customAttrsCount = received.customAttributesCount;
|
||||||
const instructionsCount = baseInstructionsCount + customAttrsCount;
|
const instructionsCount = baseInstructionsCount + customAttrsCount;
|
||||||
const renderInstructions = new Float32Array(received.renderInstructions);
|
const renderInstructions = new Float32Array(received.renderInstructions);
|
||||||
|
|
||||||
const elementsCount = renderInstructions.length / instructionsCount;
|
const elementsCount = renderInstructions.length / instructionsCount;
|
||||||
const indicesCount = elementsCount * 6;
|
const indicesCount = elementsCount * 6;
|
||||||
const verticesCount =
|
const verticesCount =
|
||||||
elementsCount * 4 * (customAttrsCount + baseVertexAttrsCount);
|
elementsCount * 4 * (customAttrsCount + baseVertexAttrsCount);
|
||||||
const indexBuffer = new Uint32Array(indicesCount);
|
const indexBuffer = new Uint32Array(indicesCount);
|
||||||
const vertexBuffer = new Float32Array(verticesCount);
|
const vertexBuffer = new Float32Array(verticesCount);
|
||||||
|
|
||||||
let bufferPositions;
|
let bufferPositions;
|
||||||
for (let i = 0; i < renderInstructions.length; i += instructionsCount) {
|
for (let i = 0; i < renderInstructions.length; i += instructionsCount) {
|
||||||
bufferPositions = writePointFeatureToBuffers(
|
bufferPositions = writePointFeatureToBuffers(
|
||||||
renderInstructions,
|
renderInstructions,
|
||||||
i,
|
i,
|
||||||
vertexBuffer,
|
vertexBuffer,
|
||||||
indexBuffer,
|
indexBuffer,
|
||||||
customAttrsCount,
|
customAttrsCount,
|
||||||
bufferPositions
|
bufferPositions
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {import('../render/webgl/constants.js').WebGLWorkerGenerateBuffersMessage} */
|
||||||
|
const message = assign(
|
||||||
|
{
|
||||||
|
vertexBuffer: vertexBuffer.buffer,
|
||||||
|
indexBuffer: indexBuffer.buffer,
|
||||||
|
renderInstructions: renderInstructions.buffer,
|
||||||
|
},
|
||||||
|
received
|
||||||
);
|
);
|
||||||
|
|
||||||
|
worker.postMessage(message, [
|
||||||
|
vertexBuffer.buffer,
|
||||||
|
indexBuffer.buffer,
|
||||||
|
renderInstructions.buffer,
|
||||||
|
]);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
case WebGLWorkerMessageType.GENERATE_LINE_STRING_BUFFERS: {
|
||||||
|
const vertices = [];
|
||||||
|
const indices = [];
|
||||||
|
|
||||||
/** @type {import('../renderer/webgl/Layer').WebGLWorkerGenerateBuffersMessage} */
|
const customAttrsCount = received.customAttributesCount;
|
||||||
const message = assign(
|
const instructionsPerVertex = 2;
|
||||||
{
|
|
||||||
vertexBuffer: vertexBuffer.buffer,
|
|
||||||
indexBuffer: indexBuffer.buffer,
|
|
||||||
renderInstructions: renderInstructions.buffer,
|
|
||||||
},
|
|
||||||
received
|
|
||||||
);
|
|
||||||
|
|
||||||
worker.postMessage(message, [
|
const renderInstructions = new Float32Array(received.renderInstructions);
|
||||||
vertexBuffer.buffer,
|
let currentInstructionsIndex = 0;
|
||||||
indexBuffer.buffer,
|
|
||||||
renderInstructions.buffer,
|
const transform = received.renderInstructionsTransform;
|
||||||
]);
|
const invertTransform = createTransform();
|
||||||
|
makeInverseTransform(invertTransform, transform);
|
||||||
|
|
||||||
|
let verticesCount, customAttributes;
|
||||||
|
while (currentInstructionsIndex < renderInstructions.length) {
|
||||||
|
customAttributes = Array.from(
|
||||||
|
renderInstructions.slice(
|
||||||
|
currentInstructionsIndex,
|
||||||
|
currentInstructionsIndex + customAttrsCount
|
||||||
|
)
|
||||||
|
);
|
||||||
|
currentInstructionsIndex += customAttrsCount;
|
||||||
|
verticesCount = renderInstructions[currentInstructionsIndex++];
|
||||||
|
|
||||||
|
// last point is only a segment end, do not loop over it
|
||||||
|
for (let i = 0; i < verticesCount - 1; i++) {
|
||||||
|
writeLineSegmentToBuffers(
|
||||||
|
renderInstructions,
|
||||||
|
currentInstructionsIndex + i * instructionsPerVertex,
|
||||||
|
currentInstructionsIndex + (i + 1) * instructionsPerVertex,
|
||||||
|
i > 0
|
||||||
|
? currentInstructionsIndex + (i - 1) * instructionsPerVertex
|
||||||
|
: null,
|
||||||
|
i < verticesCount - 2
|
||||||
|
? currentInstructionsIndex + (i + 2) * instructionsPerVertex
|
||||||
|
: null,
|
||||||
|
vertices,
|
||||||
|
indices,
|
||||||
|
customAttributes,
|
||||||
|
transform,
|
||||||
|
invertTransform
|
||||||
|
);
|
||||||
|
}
|
||||||
|
currentInstructionsIndex += verticesCount * instructionsPerVertex;
|
||||||
|
}
|
||||||
|
|
||||||
|
const indexBuffer = Uint32Array.from(indices);
|
||||||
|
const vertexBuffer = Float32Array.from(vertices);
|
||||||
|
|
||||||
|
/** @type {import('../render/webgl/constants.js').WebGLWorkerGenerateBuffersMessage} */
|
||||||
|
const message = assign(
|
||||||
|
{
|
||||||
|
vertexBuffer: vertexBuffer.buffer,
|
||||||
|
indexBuffer: indexBuffer.buffer,
|
||||||
|
renderInstructions: renderInstructions.buffer,
|
||||||
|
},
|
||||||
|
received
|
||||||
|
);
|
||||||
|
|
||||||
|
worker.postMessage(message, [
|
||||||
|
vertexBuffer.buffer,
|
||||||
|
indexBuffer.buffer,
|
||||||
|
renderInstructions.buffer,
|
||||||
|
]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case WebGLWorkerMessageType.GENERATE_POLYGON_BUFFERS: {
|
||||||
|
const vertices = [];
|
||||||
|
const indices = [];
|
||||||
|
|
||||||
|
const customAttrsCount = received.customAttributesCount;
|
||||||
|
const renderInstructions = new Float32Array(received.renderInstructions);
|
||||||
|
|
||||||
|
let currentInstructionsIndex = 0;
|
||||||
|
while (currentInstructionsIndex < renderInstructions.length) {
|
||||||
|
currentInstructionsIndex = writePolygonTrianglesToBuffers(
|
||||||
|
renderInstructions,
|
||||||
|
currentInstructionsIndex,
|
||||||
|
vertices,
|
||||||
|
indices,
|
||||||
|
customAttrsCount
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const indexBuffer = Uint32Array.from(indices);
|
||||||
|
const vertexBuffer = Float32Array.from(vertices);
|
||||||
|
|
||||||
|
/** @type {import('../render/webgl/constants.js').WebGLWorkerGenerateBuffersMessage} */
|
||||||
|
const message = assign(
|
||||||
|
{
|
||||||
|
vertexBuffer: vertexBuffer.buffer,
|
||||||
|
indexBuffer: indexBuffer.buffer,
|
||||||
|
renderInstructions: renderInstructions.buffer,
|
||||||
|
},
|
||||||
|
received
|
||||||
|
);
|
||||||
|
|
||||||
|
worker.postMessage(message, [
|
||||||
|
vertexBuffer.buffer,
|
||||||
|
indexBuffer.buffer,
|
||||||
|
renderInstructions.buffer,
|
||||||
|
]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// pass
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
307
test/browser/spec/ol/render/webgl/BatchRenderer.test.js
Normal file
307
test/browser/spec/ol/render/webgl/BatchRenderer.test.js
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
import Feature from '../../../../../../src/ol/Feature.js';
|
||||||
|
import LineString from '../../../../../../src/ol/geom/LineString.js';
|
||||||
|
import LineStringBatchRenderer from '../../../../../../src/ol/render/webgl/LineStringBatchRenderer.js';
|
||||||
|
import MixedGeometryBatch from '../../../../../../src/ol/render/webgl/MixedGeometryBatch.js';
|
||||||
|
import Point from '../../../../../../src/ol/geom/Point.js';
|
||||||
|
import PointBatchRenderer from '../../../../../../src/ol/render/webgl/PointBatchRenderer.js';
|
||||||
|
import Polygon from '../../../../../../src/ol/geom/Polygon.js';
|
||||||
|
import PolygonBatchRenderer from '../../../../../../src/ol/render/webgl/PolygonBatchRenderer.js';
|
||||||
|
import WebGLHelper from '../../../../../../src/ol/webgl/Helper.js';
|
||||||
|
import {FLOAT} from '../../../../../../src/ol/webgl.js';
|
||||||
|
import {WebGLWorkerMessageType} from '../../../../../../src/ol/render/webgl/constants.js';
|
||||||
|
import {
|
||||||
|
create as createTransform,
|
||||||
|
translate as translateTransform,
|
||||||
|
} from '../../../../../../src/ol/transform.js';
|
||||||
|
import {create as createWebGLWorker} from '../../../../../../src/ol/worker/webgl.js';
|
||||||
|
|
||||||
|
const POINT_VERTEX_SHADER = `precision mediump float;
|
||||||
|
void main(void) {}`;
|
||||||
|
const POINT_FRAGMENT_SHADER = `precision mediump float;
|
||||||
|
void main(void) {}`;
|
||||||
|
|
||||||
|
const SAMPLE_FRAMESTATE = {
|
||||||
|
viewState: {
|
||||||
|
center: [0, 10],
|
||||||
|
resolution: 1,
|
||||||
|
rotation: 0,
|
||||||
|
},
|
||||||
|
size: [10, 10],
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Batch renderers', function () {
|
||||||
|
let batchRenderer, helper, mixedBatch, worker, attributes;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
helper = new WebGLHelper();
|
||||||
|
worker = createWebGLWorker();
|
||||||
|
attributes = [
|
||||||
|
{
|
||||||
|
name: 'test',
|
||||||
|
callback: function (feature, properties) {
|
||||||
|
return feature.get('test');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
mixedBatch = new MixedGeometryBatch();
|
||||||
|
mixedBatch.addFeatures([
|
||||||
|
new Feature({
|
||||||
|
test: 1000,
|
||||||
|
geometry: new Point([10, 20]),
|
||||||
|
}),
|
||||||
|
new Feature({
|
||||||
|
test: 2000,
|
||||||
|
geometry: new Point([30, 40]),
|
||||||
|
}),
|
||||||
|
new Feature({
|
||||||
|
test: 3000,
|
||||||
|
geometry: new Polygon([
|
||||||
|
[
|
||||||
|
[10, 10],
|
||||||
|
[20, 10],
|
||||||
|
[30, 20],
|
||||||
|
[20, 40],
|
||||||
|
[10, 10],
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
new Feature({
|
||||||
|
test: 4000,
|
||||||
|
geometry: new LineString([
|
||||||
|
[100, 200],
|
||||||
|
[300, 400],
|
||||||
|
[500, 600],
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('PointBatchRenderer', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
batchRenderer = new PointBatchRenderer(
|
||||||
|
helper,
|
||||||
|
worker,
|
||||||
|
POINT_VERTEX_SHADER,
|
||||||
|
POINT_FRAGMENT_SHADER,
|
||||||
|
attributes
|
||||||
|
);
|
||||||
|
});
|
||||||
|
describe('constructor', function () {
|
||||||
|
it('generates the attributes list', function () {
|
||||||
|
expect(batchRenderer.attributes).to.eql([
|
||||||
|
{name: 'a_position', size: 2, type: FLOAT},
|
||||||
|
{name: 'a_index', size: 1, type: FLOAT},
|
||||||
|
{name: 'a_test', size: 1, type: FLOAT},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('#rebuild', function () {
|
||||||
|
let rebuildCb;
|
||||||
|
beforeEach(function (done) {
|
||||||
|
sinon.spy(helper, 'flushBufferData');
|
||||||
|
rebuildCb = sinon.spy();
|
||||||
|
batchRenderer.rebuild(
|
||||||
|
mixedBatch.pointBatch,
|
||||||
|
SAMPLE_FRAMESTATE,
|
||||||
|
'Point',
|
||||||
|
rebuildCb
|
||||||
|
);
|
||||||
|
// wait for worker response for our specific message
|
||||||
|
worker.addEventListener('message', function (event) {
|
||||||
|
if (
|
||||||
|
event.data.type === WebGLWorkerMessageType.GENERATE_POINT_BUFFERS &&
|
||||||
|
event.data.renderInstructions.byteLength > 0
|
||||||
|
) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('generates render instructions and updates buffers from the worker response', function () {
|
||||||
|
expect(Array.from(mixedBatch.pointBatch.renderInstructions)).to.eql([
|
||||||
|
2, 2, 1000, 6, 6, 2000,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
it('updates buffers', function () {
|
||||||
|
expect(
|
||||||
|
mixedBatch.pointBatch.verticesBuffer.getArray().length
|
||||||
|
).to.be.greaterThan(0);
|
||||||
|
expect(
|
||||||
|
mixedBatch.pointBatch.indicesBuffer.getArray().length
|
||||||
|
).to.be.greaterThan(0);
|
||||||
|
expect(helper.flushBufferData.calledTwice).to.be(true);
|
||||||
|
});
|
||||||
|
it('updates the instructions transform', function () {
|
||||||
|
expect(mixedBatch.pointBatch.renderInstructionsTransform).to.eql([
|
||||||
|
0.2, 0, 0, 0.2, 0, -2,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
it('calls the provided callback', function () {
|
||||||
|
expect(rebuildCb.calledOnce).to.be(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('#render (from parent)', function () {
|
||||||
|
let transform;
|
||||||
|
const offsetX = 12;
|
||||||
|
beforeEach(function () {
|
||||||
|
sinon.spy(helper, 'makeProjectionTransform');
|
||||||
|
sinon.spy(helper, 'useProgram');
|
||||||
|
sinon.spy(helper, 'bindBuffer');
|
||||||
|
sinon.spy(helper, 'enableAttributes');
|
||||||
|
sinon.spy(helper, 'drawElements');
|
||||||
|
|
||||||
|
transform = createTransform();
|
||||||
|
batchRenderer.render(
|
||||||
|
mixedBatch.pointBatch,
|
||||||
|
transform,
|
||||||
|
SAMPLE_FRAMESTATE,
|
||||||
|
offsetX
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('computes current transform', function () {
|
||||||
|
expect(helper.makeProjectionTransform.calledOnce).to.be(true);
|
||||||
|
});
|
||||||
|
it('includes the X offset in the transform used for rendering', function () {
|
||||||
|
const expected = helper.makeProjectionTransform(
|
||||||
|
SAMPLE_FRAMESTATE,
|
||||||
|
createTransform()
|
||||||
|
);
|
||||||
|
translateTransform(expected, offsetX, 0);
|
||||||
|
expect(transform).to.eql(expected);
|
||||||
|
});
|
||||||
|
it('computes sets up render parameters', function () {
|
||||||
|
expect(helper.useProgram.calledOnce).to.be(true);
|
||||||
|
expect(helper.enableAttributes.calledOnce).to.be(true);
|
||||||
|
expect(helper.bindBuffer.calledTwice).to.be(true);
|
||||||
|
});
|
||||||
|
it('renders elements', function () {
|
||||||
|
expect(helper.drawElements.calledOnce).to.be(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('LineStringBatchRenderer', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
batchRenderer = new LineStringBatchRenderer(
|
||||||
|
helper,
|
||||||
|
worker,
|
||||||
|
POINT_VERTEX_SHADER,
|
||||||
|
POINT_FRAGMENT_SHADER,
|
||||||
|
attributes
|
||||||
|
);
|
||||||
|
});
|
||||||
|
describe('constructor', function () {
|
||||||
|
it('generates the attributes list', function () {
|
||||||
|
expect(batchRenderer.attributes).to.eql([
|
||||||
|
{name: 'a_segmentStart', size: 2, type: FLOAT},
|
||||||
|
{name: 'a_segmentEnd', size: 2, type: FLOAT},
|
||||||
|
{name: 'a_parameters', size: 1, type: FLOAT},
|
||||||
|
{name: 'a_test', size: 1, type: FLOAT},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('#rebuild', function () {
|
||||||
|
let rebuildCb;
|
||||||
|
beforeEach(function (done) {
|
||||||
|
sinon.spy(helper, 'flushBufferData');
|
||||||
|
rebuildCb = sinon.spy();
|
||||||
|
batchRenderer.rebuild(
|
||||||
|
mixedBatch.lineStringBatch,
|
||||||
|
SAMPLE_FRAMESTATE,
|
||||||
|
'LineString',
|
||||||
|
rebuildCb
|
||||||
|
);
|
||||||
|
// wait for worker response for our specific message
|
||||||
|
worker.addEventListener('message', function (event) {
|
||||||
|
if (
|
||||||
|
event.data.type ===
|
||||||
|
WebGLWorkerMessageType.GENERATE_LINE_STRING_BUFFERS &&
|
||||||
|
event.data.renderInstructions.byteLength > 0
|
||||||
|
) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('generates render instructions and updates buffers from the worker response', function () {
|
||||||
|
expect(
|
||||||
|
Array.from(mixedBatch.lineStringBatch.renderInstructions)
|
||||||
|
).to.eql([
|
||||||
|
3000, 5, 2, 0, 4, 0, 6, 2, 4, 6, 2, 0, 4000, 3, 20, 38, 60, 78, 100,
|
||||||
|
118,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
it('updates buffers', function () {
|
||||||
|
expect(
|
||||||
|
mixedBatch.lineStringBatch.verticesBuffer.getArray().length
|
||||||
|
).to.be.greaterThan(0);
|
||||||
|
expect(
|
||||||
|
mixedBatch.lineStringBatch.indicesBuffer.getArray().length
|
||||||
|
).to.be.greaterThan(0);
|
||||||
|
expect(helper.flushBufferData.calledTwice).to.be(true);
|
||||||
|
});
|
||||||
|
it('calls the provided callback', function () {
|
||||||
|
expect(rebuildCb.calledOnce).to.be(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('PolygonBatchRenderer', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
batchRenderer = new PolygonBatchRenderer(
|
||||||
|
helper,
|
||||||
|
worker,
|
||||||
|
POINT_VERTEX_SHADER,
|
||||||
|
POINT_FRAGMENT_SHADER,
|
||||||
|
attributes
|
||||||
|
);
|
||||||
|
});
|
||||||
|
describe('constructor', function () {
|
||||||
|
it('generates the attributes list', function () {
|
||||||
|
expect(batchRenderer.attributes).to.eql([
|
||||||
|
{name: 'a_position', size: 2, type: FLOAT},
|
||||||
|
{name: 'a_test', size: 1, type: FLOAT},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('#rebuild', function () {
|
||||||
|
let rebuildCb;
|
||||||
|
beforeEach(function (done) {
|
||||||
|
sinon.spy(helper, 'flushBufferData');
|
||||||
|
rebuildCb = sinon.spy();
|
||||||
|
batchRenderer.rebuild(
|
||||||
|
mixedBatch.polygonBatch,
|
||||||
|
SAMPLE_FRAMESTATE,
|
||||||
|
'Polygon',
|
||||||
|
rebuildCb
|
||||||
|
);
|
||||||
|
// wait for worker response for our specific message
|
||||||
|
worker.addEventListener('message', function (event) {
|
||||||
|
if (
|
||||||
|
event.data.type ===
|
||||||
|
WebGLWorkerMessageType.GENERATE_POLYGON_BUFFERS &&
|
||||||
|
event.data.renderInstructions.byteLength > 0
|
||||||
|
) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('generates render instructions and updates buffers from the worker response', function () {
|
||||||
|
expect(Array.from(mixedBatch.polygonBatch.renderInstructions)).to.eql([
|
||||||
|
3000, 1, 5, 2, 0, 4, 0, 6, 2, 4, 6, 2, 0,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
it('updates buffers', function () {
|
||||||
|
expect(
|
||||||
|
mixedBatch.polygonBatch.verticesBuffer.getArray().length
|
||||||
|
).to.be.greaterThan(0);
|
||||||
|
expect(
|
||||||
|
mixedBatch.polygonBatch.indicesBuffer.getArray().length
|
||||||
|
).to.be.greaterThan(0);
|
||||||
|
expect(helper.flushBufferData.calledTwice).to.be(true);
|
||||||
|
});
|
||||||
|
it('calls the provided callback', function () {
|
||||||
|
expect(rebuildCb.calledOnce).to.be(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
763
test/browser/spec/ol/render/webgl/MixedGeometryBatch.test.js
Normal file
763
test/browser/spec/ol/render/webgl/MixedGeometryBatch.test.js
Normal file
@@ -0,0 +1,763 @@
|
|||||||
|
import Feature from '../../../../../../src/ol/Feature.js';
|
||||||
|
import GeometryCollection from '../../../../../../src/ol/geom/GeometryCollection.js';
|
||||||
|
import LineString from '../../../../../../src/ol/geom/LineString.js';
|
||||||
|
import LinearRing from '../../../../../../src/ol/geom/LinearRing.js';
|
||||||
|
import MixedGeometryBatch from '../../../../../../src/ol/render/webgl/MixedGeometryBatch.js';
|
||||||
|
import MultiLineString from '../../../../../../src/ol/geom/MultiLineString.js';
|
||||||
|
import MultiPoint from '../../../../../../src/ol/geom/MultiPoint.js';
|
||||||
|
import MultiPolygon from '../../../../../../src/ol/geom/MultiPolygon.js';
|
||||||
|
import Point from '../../../../../../src/ol/geom/Point.js';
|
||||||
|
import Polygon from '../../../../../../src/ol/geom/Polygon.js';
|
||||||
|
import {getUid} from '../../../../../../src/ol/index.js';
|
||||||
|
|
||||||
|
describe('MixedGeometryBatch', function () {
|
||||||
|
let mixedBatch;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mixedBatch = new MixedGeometryBatch();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#addFeatures', () => {
|
||||||
|
let features, spy;
|
||||||
|
beforeEach(() => {
|
||||||
|
features = [new Feature(), new Feature(), new Feature()];
|
||||||
|
spy = sinon.spy(mixedBatch, 'addFeature');
|
||||||
|
mixedBatch.addFeatures(features);
|
||||||
|
});
|
||||||
|
it('calls addFeature for each feature', () => {
|
||||||
|
expect(spy.callCount).to.be(3);
|
||||||
|
expect(spy.args[0][0]).to.be(features[0]);
|
||||||
|
expect(spy.args[1][0]).to.be(features[1]);
|
||||||
|
expect(spy.args[2][0]).to.be(features[2]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('features with Point geometries', () => {
|
||||||
|
let geometry1, feature1, geometry2, feature2;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
geometry1 = new Point([0, 1]);
|
||||||
|
feature1 = new Feature({
|
||||||
|
geometry: geometry1,
|
||||||
|
prop1: 'abcd',
|
||||||
|
prop2: 'efgh',
|
||||||
|
});
|
||||||
|
geometry2 = new Point([2, 3]);
|
||||||
|
feature2 = new Feature({
|
||||||
|
geometry: geometry2,
|
||||||
|
prop3: '1234',
|
||||||
|
prop4: '5678',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#addFeature', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mixedBatch.addFeature(feature1);
|
||||||
|
mixedBatch.addFeature(feature2);
|
||||||
|
});
|
||||||
|
it('puts the geometries in the point batch', () => {
|
||||||
|
const keys = Object.keys(mixedBatch.pointBatch.entries);
|
||||||
|
const uid1 = getUid(feature1);
|
||||||
|
const uid2 = getUid(feature2);
|
||||||
|
expect(keys).to.eql([uid1, uid2]);
|
||||||
|
expect(mixedBatch.pointBatch.entries[uid1]).to.eql({
|
||||||
|
feature: feature1,
|
||||||
|
flatCoordss: [[0, 1]],
|
||||||
|
});
|
||||||
|
expect(mixedBatch.pointBatch.entries[uid2]).to.eql({
|
||||||
|
feature: feature2,
|
||||||
|
flatCoordss: [[2, 3]],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('computes the geometries count', () => {
|
||||||
|
expect(mixedBatch.pointBatch.geometriesCount).to.be(2);
|
||||||
|
});
|
||||||
|
it('leaves other batches untouched', () => {
|
||||||
|
expect(Object.keys(mixedBatch.polygonBatch.entries)).to.have.length(0);
|
||||||
|
expect(Object.keys(mixedBatch.lineStringBatch.entries)).to.have.length(
|
||||||
|
0
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#changeFeature', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mixedBatch.addFeature(feature1);
|
||||||
|
mixedBatch.addFeature(feature2);
|
||||||
|
});
|
||||||
|
describe('modifying geometry and props', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
feature1.set('prop1', 'changed');
|
||||||
|
geometry1.setCoordinates([100, 101]);
|
||||||
|
mixedBatch.changeFeature(feature1);
|
||||||
|
});
|
||||||
|
it('updates the modified properties and geometry in the point batch', () => {
|
||||||
|
const entry = mixedBatch.pointBatch.entries[getUid(feature1)];
|
||||||
|
expect(entry.feature.get('prop1')).to.eql('changed');
|
||||||
|
});
|
||||||
|
it('keeps geometry count the same', () => {
|
||||||
|
expect(mixedBatch.pointBatch.geometriesCount).to.be(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('changing the geometry', () => {
|
||||||
|
let newGeom;
|
||||||
|
beforeEach(() => {
|
||||||
|
newGeom = new Point([40, 41]);
|
||||||
|
feature1.setGeometry(newGeom);
|
||||||
|
mixedBatch.changeFeature(feature1);
|
||||||
|
});
|
||||||
|
it('updates the geometry in the point batch', () => {
|
||||||
|
const entry = mixedBatch.pointBatch.entries[getUid(feature1)];
|
||||||
|
expect(entry.flatCoordss).to.eql([[40, 41]]);
|
||||||
|
});
|
||||||
|
it('keeps geometry count the same', () => {
|
||||||
|
expect(mixedBatch.pointBatch.geometriesCount).to.be(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#removeFeature', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mixedBatch.addFeature(feature1);
|
||||||
|
mixedBatch.addFeature(feature2);
|
||||||
|
mixedBatch.removeFeature(feature1);
|
||||||
|
});
|
||||||
|
it('clears the entry related to this feature', () => {
|
||||||
|
const keys = Object.keys(mixedBatch.pointBatch.entries);
|
||||||
|
expect(keys).to.not.contain(getUid(feature1));
|
||||||
|
});
|
||||||
|
it('recompute geometry count', () => {
|
||||||
|
expect(mixedBatch.pointBatch.geometriesCount).to.be(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('features with LineString geometries', () => {
|
||||||
|
let geometry1, feature1, geometry2, feature2;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
geometry1 = new LineString([
|
||||||
|
[0, 1],
|
||||||
|
[2, 3],
|
||||||
|
[4, 5],
|
||||||
|
[6, 7],
|
||||||
|
]);
|
||||||
|
feature1 = new Feature({
|
||||||
|
geometry: geometry1,
|
||||||
|
prop1: 'abcd',
|
||||||
|
prop2: 'efgh',
|
||||||
|
});
|
||||||
|
geometry2 = new LineString([
|
||||||
|
[8, 9],
|
||||||
|
[10, 11],
|
||||||
|
[12, 13],
|
||||||
|
]);
|
||||||
|
feature2 = new Feature({
|
||||||
|
geometry: geometry2,
|
||||||
|
prop3: '1234',
|
||||||
|
prop4: '5678',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#addFeature', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mixedBatch.addFeature(feature1);
|
||||||
|
mixedBatch.addFeature(feature2);
|
||||||
|
});
|
||||||
|
it('puts the geometries in the linestring batch', () => {
|
||||||
|
const keys = Object.keys(mixedBatch.lineStringBatch.entries);
|
||||||
|
const uid1 = getUid(feature1);
|
||||||
|
const uid2 = getUid(feature2);
|
||||||
|
expect(keys).to.eql([uid1, uid2]);
|
||||||
|
expect(mixedBatch.lineStringBatch.entries[uid1]).to.eql({
|
||||||
|
feature: feature1,
|
||||||
|
flatCoordss: [[0, 1, 2, 3, 4, 5, 6, 7]],
|
||||||
|
verticesCount: 4,
|
||||||
|
});
|
||||||
|
expect(mixedBatch.lineStringBatch.entries[uid2]).to.eql({
|
||||||
|
feature: feature2,
|
||||||
|
flatCoordss: [[8, 9, 10, 11, 12, 13]],
|
||||||
|
verticesCount: 3,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('computes the aggregated metrics on all geoms', () => {
|
||||||
|
expect(mixedBatch.lineStringBatch.verticesCount).to.be(7);
|
||||||
|
expect(mixedBatch.lineStringBatch.geometriesCount).to.be(2);
|
||||||
|
});
|
||||||
|
it('leaves other batches untouched', () => {
|
||||||
|
expect(Object.keys(mixedBatch.polygonBatch.entries)).to.have.length(0);
|
||||||
|
expect(Object.keys(mixedBatch.pointBatch.entries)).to.have.length(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#changeFeature', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mixedBatch.addFeature(feature1);
|
||||||
|
mixedBatch.addFeature(feature2);
|
||||||
|
});
|
||||||
|
describe('modifying geometry and props', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
feature1.set('prop1', 'changed');
|
||||||
|
geometry1.appendCoordinate([100, 101]);
|
||||||
|
geometry1.appendCoordinate([102, 103]);
|
||||||
|
mixedBatch.changeFeature(feature1);
|
||||||
|
});
|
||||||
|
it('updates the modified properties and geometry in the linestring batch', () => {
|
||||||
|
const entry = mixedBatch.lineStringBatch.entries[getUid(feature1)];
|
||||||
|
expect(entry.feature.get('prop1')).to.eql('changed');
|
||||||
|
expect(entry.verticesCount).to.eql(6);
|
||||||
|
expect(entry.flatCoordss).to.eql([
|
||||||
|
[0, 1, 2, 3, 4, 5, 6, 7, 100, 101, 102, 103],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
it('updates the aggregated metrics on all geoms', () => {
|
||||||
|
expect(mixedBatch.lineStringBatch.verticesCount).to.be(9);
|
||||||
|
expect(mixedBatch.lineStringBatch.geometriesCount).to.be(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('changing the geometry', () => {
|
||||||
|
let newGeom;
|
||||||
|
beforeEach(() => {
|
||||||
|
newGeom = new LineString([
|
||||||
|
[40, 41],
|
||||||
|
[42, 43],
|
||||||
|
]);
|
||||||
|
feature1.setGeometry(newGeom);
|
||||||
|
mixedBatch.changeFeature(feature1);
|
||||||
|
});
|
||||||
|
it('updates the geometry in the linestring batch', () => {
|
||||||
|
const entry = mixedBatch.lineStringBatch.entries[getUid(feature1)];
|
||||||
|
expect(entry.flatCoordss).to.eql([[40, 41, 42, 43]]);
|
||||||
|
});
|
||||||
|
it('updates the aggregated metrics on all geoms', () => {
|
||||||
|
expect(mixedBatch.lineStringBatch.verticesCount).to.be(5);
|
||||||
|
expect(mixedBatch.lineStringBatch.geometriesCount).to.be(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#removeFeature', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mixedBatch.addFeature(feature1);
|
||||||
|
mixedBatch.addFeature(feature2);
|
||||||
|
mixedBatch.removeFeature(feature1);
|
||||||
|
});
|
||||||
|
it('clears the entry related to this feature', () => {
|
||||||
|
const keys = Object.keys(mixedBatch.lineStringBatch.entries);
|
||||||
|
expect(keys).to.not.contain(getUid(feature1));
|
||||||
|
});
|
||||||
|
it('updates the aggregated metrics on all geoms', () => {
|
||||||
|
expect(mixedBatch.lineStringBatch.verticesCount).to.be(3);
|
||||||
|
expect(mixedBatch.lineStringBatch.geometriesCount).to.be(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('features with Polygon geometries', () => {
|
||||||
|
let geometry1, feature1, geometry2, feature2;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
geometry1 = new Polygon([
|
||||||
|
[
|
||||||
|
[0, 1],
|
||||||
|
[2, 3],
|
||||||
|
[4, 5],
|
||||||
|
[6, 7],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[20, 21],
|
||||||
|
[22, 23],
|
||||||
|
[24, 25],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
feature1 = new Feature({
|
||||||
|
geometry: geometry1,
|
||||||
|
prop1: 'abcd',
|
||||||
|
prop2: 'efgh',
|
||||||
|
});
|
||||||
|
geometry2 = new Polygon([
|
||||||
|
[
|
||||||
|
[8, 9],
|
||||||
|
[10, 11],
|
||||||
|
[12, 13],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[30, 31],
|
||||||
|
[32, 33],
|
||||||
|
[34, 35],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[40, 41],
|
||||||
|
[42, 43],
|
||||||
|
[44, 45],
|
||||||
|
[46, 47],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
feature2 = new Feature({
|
||||||
|
geometry: geometry2,
|
||||||
|
prop3: '1234',
|
||||||
|
prop4: '5678',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#addFeature', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mixedBatch.addFeature(feature1);
|
||||||
|
mixedBatch.addFeature(feature2);
|
||||||
|
});
|
||||||
|
it('puts the polygons in the polygon batch', () => {
|
||||||
|
const keys = Object.keys(mixedBatch.polygonBatch.entries);
|
||||||
|
const uid1 = getUid(feature1);
|
||||||
|
const uid2 = getUid(feature2);
|
||||||
|
expect(keys).to.eql([uid1, uid2]);
|
||||||
|
expect(mixedBatch.polygonBatch.entries[uid1]).to.eql({
|
||||||
|
feature: feature1,
|
||||||
|
flatCoordss: [[0, 1, 2, 3, 4, 5, 6, 7, 20, 21, 22, 23, 24, 25]],
|
||||||
|
verticesCount: 7,
|
||||||
|
ringsCount: 2,
|
||||||
|
ringsVerticesCounts: [[4, 3]],
|
||||||
|
});
|
||||||
|
expect(mixedBatch.polygonBatch.entries[uid2]).to.eql({
|
||||||
|
feature: feature2,
|
||||||
|
flatCoordss: [
|
||||||
|
[
|
||||||
|
8, 9, 10, 11, 12, 13, 30, 31, 32, 33, 34, 35, 40, 41, 42, 43, 44,
|
||||||
|
45, 46, 47,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
verticesCount: 10,
|
||||||
|
ringsCount: 3,
|
||||||
|
ringsVerticesCounts: [[3, 3, 4]],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('computes the aggregated metrics on all polygons', () => {
|
||||||
|
expect(mixedBatch.polygonBatch.verticesCount).to.be(17);
|
||||||
|
expect(mixedBatch.polygonBatch.geometriesCount).to.be(2);
|
||||||
|
expect(mixedBatch.polygonBatch.ringsCount).to.be(5);
|
||||||
|
});
|
||||||
|
it('puts the linear rings in the linestring batch', () => {
|
||||||
|
const keys = Object.keys(mixedBatch.lineStringBatch.entries);
|
||||||
|
expect(keys).to.eql([getUid(feature1), getUid(feature2)]);
|
||||||
|
expect(mixedBatch.lineStringBatch.entries[getUid(feature1)]).to.eql({
|
||||||
|
feature: feature1,
|
||||||
|
flatCoordss: [
|
||||||
|
[0, 1, 2, 3, 4, 5, 6, 7],
|
||||||
|
[20, 21, 22, 23, 24, 25],
|
||||||
|
],
|
||||||
|
verticesCount: 7,
|
||||||
|
});
|
||||||
|
expect(mixedBatch.lineStringBatch.entries[getUid(feature2)]).to.eql({
|
||||||
|
feature: feature2,
|
||||||
|
flatCoordss: [
|
||||||
|
[8, 9, 10, 11, 12, 13],
|
||||||
|
[30, 31, 32, 33, 34, 35],
|
||||||
|
[40, 41, 42, 43, 44, 45, 46, 47],
|
||||||
|
],
|
||||||
|
verticesCount: 10,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('computes the aggregated metrics on all linestrings', () => {
|
||||||
|
expect(mixedBatch.lineStringBatch.verticesCount).to.be(17);
|
||||||
|
expect(mixedBatch.lineStringBatch.geometriesCount).to.be(5);
|
||||||
|
});
|
||||||
|
it('leaves point batch untouched', () => {
|
||||||
|
expect(Object.keys(mixedBatch.pointBatch.entries)).to.have.length(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#changeFeature', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mixedBatch.addFeature(feature1);
|
||||||
|
mixedBatch.addFeature(feature2);
|
||||||
|
});
|
||||||
|
describe('modifying geometry and props', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
feature1.set('prop1', 'changed');
|
||||||
|
geometry1.appendLinearRing(
|
||||||
|
new LinearRing([
|
||||||
|
[201, 202],
|
||||||
|
[203, 204],
|
||||||
|
[205, 206],
|
||||||
|
[207, 208],
|
||||||
|
])
|
||||||
|
);
|
||||||
|
mixedBatch.changeFeature(feature1);
|
||||||
|
});
|
||||||
|
it('updates the modified properties and geometry in the polygon batch', () => {
|
||||||
|
const entry = mixedBatch.polygonBatch.entries[getUid(feature1)];
|
||||||
|
expect(entry.feature.get('prop1')).to.eql('changed');
|
||||||
|
expect(entry.verticesCount).to.eql(11);
|
||||||
|
expect(entry.ringsCount).to.eql(3);
|
||||||
|
expect(entry.ringsVerticesCounts).to.eql([[4, 3, 4]]);
|
||||||
|
});
|
||||||
|
it('updates the aggregated metrics on all geoms', () => {
|
||||||
|
expect(mixedBatch.polygonBatch.verticesCount).to.be(21);
|
||||||
|
expect(mixedBatch.polygonBatch.geometriesCount).to.be(2);
|
||||||
|
expect(mixedBatch.polygonBatch.ringsCount).to.be(6);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('changing the geometry', () => {
|
||||||
|
let newGeom;
|
||||||
|
beforeEach(() => {
|
||||||
|
newGeom = new Polygon([
|
||||||
|
[
|
||||||
|
[201, 202],
|
||||||
|
[203, 204],
|
||||||
|
[205, 206],
|
||||||
|
[207, 208],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
feature1.setGeometry(newGeom);
|
||||||
|
mixedBatch.changeFeature(feature1);
|
||||||
|
});
|
||||||
|
it('updates the geometry in the polygon batch', () => {
|
||||||
|
const entry = mixedBatch.polygonBatch.entries[getUid(feature1)];
|
||||||
|
expect(entry.feature).to.be(feature1);
|
||||||
|
expect(entry.verticesCount).to.eql(4);
|
||||||
|
expect(entry.ringsCount).to.eql(1);
|
||||||
|
expect(entry.ringsVerticesCounts).to.eql([[4]]);
|
||||||
|
expect(entry.flatCoordss).to.eql([
|
||||||
|
[201, 202, 203, 204, 205, 206, 207, 208],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
it('updates the aggregated metrics on all geoms', () => {
|
||||||
|
expect(mixedBatch.polygonBatch.verticesCount).to.be(14);
|
||||||
|
expect(mixedBatch.polygonBatch.geometriesCount).to.be(2);
|
||||||
|
expect(mixedBatch.polygonBatch.ringsCount).to.be(4);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#removeFeature', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mixedBatch.addFeature(feature1);
|
||||||
|
mixedBatch.addFeature(feature2);
|
||||||
|
mixedBatch.removeFeature(feature1);
|
||||||
|
});
|
||||||
|
it('clears the entry related to this feature', () => {
|
||||||
|
const keys = Object.keys(mixedBatch.polygonBatch.entries);
|
||||||
|
expect(keys).to.not.contain(getUid(feature1));
|
||||||
|
});
|
||||||
|
it('updates the aggregated metrics on all geoms', () => {
|
||||||
|
expect(mixedBatch.polygonBatch.verticesCount).to.be(10);
|
||||||
|
expect(mixedBatch.polygonBatch.geometriesCount).to.be(1);
|
||||||
|
expect(mixedBatch.polygonBatch.ringsCount).to.be(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('feature with nested geometries (collection, multi)', () => {
|
||||||
|
let feature, geomCollection, multiPolygon, multiPoint, multiLine;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
multiPoint = new MultiPoint([
|
||||||
|
[101, 102],
|
||||||
|
[201, 202],
|
||||||
|
[301, 302],
|
||||||
|
]);
|
||||||
|
multiLine = new MultiLineString([
|
||||||
|
[
|
||||||
|
[0, 1],
|
||||||
|
[2, 3],
|
||||||
|
[4, 5],
|
||||||
|
[6, 7],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[8, 9],
|
||||||
|
[10, 11],
|
||||||
|
[12, 13],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
multiPolygon = new MultiPolygon([
|
||||||
|
[
|
||||||
|
[
|
||||||
|
[0, 1],
|
||||||
|
[2, 3],
|
||||||
|
[4, 5],
|
||||||
|
[6, 7],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[20, 21],
|
||||||
|
[22, 23],
|
||||||
|
[24, 25],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[
|
||||||
|
[8, 9],
|
||||||
|
[10, 11],
|
||||||
|
[12, 13],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[30, 31],
|
||||||
|
[32, 33],
|
||||||
|
[34, 35],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[40, 41],
|
||||||
|
[42, 43],
|
||||||
|
[44, 45],
|
||||||
|
[46, 47],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
geomCollection = new GeometryCollection([
|
||||||
|
multiPolygon,
|
||||||
|
multiLine,
|
||||||
|
multiPoint,
|
||||||
|
]);
|
||||||
|
feature = new Feature({
|
||||||
|
geometry: geomCollection,
|
||||||
|
prop3: '1234',
|
||||||
|
prop4: '5678',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#addFeature', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mixedBatch.addFeature(feature);
|
||||||
|
});
|
||||||
|
it('puts the polygons in the polygon batch', () => {
|
||||||
|
const uid = getUid(feature);
|
||||||
|
expect(mixedBatch.polygonBatch.entries[uid]).to.eql({
|
||||||
|
feature: feature,
|
||||||
|
flatCoordss: [
|
||||||
|
[0, 1, 2, 3, 4, 5, 6, 7, 20, 21, 22, 23, 24, 25],
|
||||||
|
[
|
||||||
|
8, 9, 10, 11, 12, 13, 30, 31, 32, 33, 34, 35, 40, 41, 42, 43, 44,
|
||||||
|
45, 46, 47,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
verticesCount: 17,
|
||||||
|
ringsCount: 5,
|
||||||
|
ringsVerticesCounts: [
|
||||||
|
[4, 3],
|
||||||
|
[3, 3, 4],
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('puts the polygon rings and linestrings in the linestring batch', () => {
|
||||||
|
const uid = getUid(feature);
|
||||||
|
expect(mixedBatch.lineStringBatch.entries[uid]).to.eql({
|
||||||
|
feature: feature,
|
||||||
|
flatCoordss: [
|
||||||
|
[0, 1, 2, 3, 4, 5, 6, 7],
|
||||||
|
[20, 21, 22, 23, 24, 25],
|
||||||
|
[8, 9, 10, 11, 12, 13],
|
||||||
|
[30, 31, 32, 33, 34, 35],
|
||||||
|
[40, 41, 42, 43, 44, 45, 46, 47],
|
||||||
|
[0, 1, 2, 3, 4, 5, 6, 7],
|
||||||
|
[8, 9, 10, 11, 12, 13],
|
||||||
|
],
|
||||||
|
verticesCount: 24,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('puts the points in the linestring batch', () => {
|
||||||
|
const uid = getUid(feature);
|
||||||
|
expect(mixedBatch.pointBatch.entries[uid]).to.eql({
|
||||||
|
feature: feature,
|
||||||
|
flatCoordss: [
|
||||||
|
[101, 102],
|
||||||
|
[201, 202],
|
||||||
|
[301, 302],
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('computes the aggregated metrics on all polygons', () => {
|
||||||
|
expect(mixedBatch.polygonBatch.verticesCount).to.be(17);
|
||||||
|
expect(mixedBatch.polygonBatch.geometriesCount).to.be(2);
|
||||||
|
expect(mixedBatch.polygonBatch.ringsCount).to.be(5);
|
||||||
|
});
|
||||||
|
it('computes the aggregated metrics on all linestring', () => {
|
||||||
|
expect(mixedBatch.lineStringBatch.verticesCount).to.be(24);
|
||||||
|
expect(mixedBatch.lineStringBatch.geometriesCount).to.be(7);
|
||||||
|
});
|
||||||
|
it('computes the aggregated metrics on all points', () => {
|
||||||
|
expect(mixedBatch.pointBatch.geometriesCount).to.be(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#changeFeature', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mixedBatch.addFeature(feature);
|
||||||
|
});
|
||||||
|
describe('modifying geometry', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
multiLine.appendLineString(
|
||||||
|
new LineString([
|
||||||
|
[500, 501],
|
||||||
|
[502, 503],
|
||||||
|
[504, 505],
|
||||||
|
[506, 507],
|
||||||
|
])
|
||||||
|
);
|
||||||
|
multiPolygon.appendPolygon(
|
||||||
|
new Polygon([
|
||||||
|
[
|
||||||
|
[201, 202],
|
||||||
|
[203, 204],
|
||||||
|
[205, 206],
|
||||||
|
[207, 208],
|
||||||
|
[209, 210],
|
||||||
|
],
|
||||||
|
])
|
||||||
|
);
|
||||||
|
mixedBatch.changeFeature(feature);
|
||||||
|
});
|
||||||
|
it('updates the geometries in the polygon batch', () => {
|
||||||
|
const entry = mixedBatch.polygonBatch.entries[getUid(feature)];
|
||||||
|
expect(entry).to.eql({
|
||||||
|
feature: feature,
|
||||||
|
flatCoordss: [
|
||||||
|
[0, 1, 2, 3, 4, 5, 6, 7, 20, 21, 22, 23, 24, 25],
|
||||||
|
[
|
||||||
|
8, 9, 10, 11, 12, 13, 30, 31, 32, 33, 34, 35, 40, 41, 42, 43,
|
||||||
|
44, 45, 46, 47,
|
||||||
|
],
|
||||||
|
[201, 202, 203, 204, 205, 206, 207, 208, 209, 210],
|
||||||
|
],
|
||||||
|
verticesCount: 22,
|
||||||
|
ringsCount: 6,
|
||||||
|
ringsVerticesCounts: [[4, 3], [3, 3, 4], [5]],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('updates the geometries in the linestring batch', () => {
|
||||||
|
const entry = mixedBatch.lineStringBatch.entries[getUid(feature)];
|
||||||
|
expect(entry).to.eql({
|
||||||
|
feature: feature,
|
||||||
|
flatCoordss: [
|
||||||
|
[0, 1, 2, 3, 4, 5, 6, 7],
|
||||||
|
[20, 21, 22, 23, 24, 25],
|
||||||
|
[8, 9, 10, 11, 12, 13],
|
||||||
|
[30, 31, 32, 33, 34, 35],
|
||||||
|
[40, 41, 42, 43, 44, 45, 46, 47],
|
||||||
|
[201, 202, 203, 204, 205, 206, 207, 208, 209, 210],
|
||||||
|
[0, 1, 2, 3, 4, 5, 6, 7],
|
||||||
|
[8, 9, 10, 11, 12, 13],
|
||||||
|
[500, 501, 502, 503, 504, 505, 506, 507],
|
||||||
|
],
|
||||||
|
verticesCount: 33,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('updates the aggregated metrics on the polygon batch', () => {
|
||||||
|
expect(mixedBatch.polygonBatch.verticesCount).to.be(22);
|
||||||
|
expect(mixedBatch.polygonBatch.geometriesCount).to.be(3);
|
||||||
|
expect(mixedBatch.polygonBatch.ringsCount).to.be(6);
|
||||||
|
});
|
||||||
|
it('updates the aggregated metrics on the linestring batch', () => {
|
||||||
|
expect(mixedBatch.lineStringBatch.verticesCount).to.be(33);
|
||||||
|
expect(mixedBatch.lineStringBatch.geometriesCount).to.be(9);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('changing the geometry', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
feature.setGeometry(
|
||||||
|
new Polygon([
|
||||||
|
[
|
||||||
|
[201, 202],
|
||||||
|
[203, 204],
|
||||||
|
[205, 206],
|
||||||
|
[207, 208],
|
||||||
|
],
|
||||||
|
])
|
||||||
|
);
|
||||||
|
mixedBatch.changeFeature(feature);
|
||||||
|
});
|
||||||
|
it('updates the geometries in the polygon batch', () => {
|
||||||
|
const entry = mixedBatch.polygonBatch.entries[getUid(feature)];
|
||||||
|
expect(entry).to.eql({
|
||||||
|
feature: feature,
|
||||||
|
flatCoordss: [[201, 202, 203, 204, 205, 206, 207, 208]],
|
||||||
|
verticesCount: 4,
|
||||||
|
ringsCount: 1,
|
||||||
|
ringsVerticesCounts: [[4]],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('updates the geometries in the linestring batch', () => {
|
||||||
|
const entry = mixedBatch.lineStringBatch.entries[getUid(feature)];
|
||||||
|
expect(entry).to.eql({
|
||||||
|
feature: feature,
|
||||||
|
flatCoordss: [[201, 202, 203, 204, 205, 206, 207, 208]],
|
||||||
|
verticesCount: 4,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('updates the aggregated metrics on the polygon batch', () => {
|
||||||
|
expect(mixedBatch.polygonBatch.verticesCount).to.be(4);
|
||||||
|
expect(mixedBatch.polygonBatch.geometriesCount).to.be(1);
|
||||||
|
expect(mixedBatch.polygonBatch.ringsCount).to.be(1);
|
||||||
|
});
|
||||||
|
it('updates the aggregated metrics on the linestring batch', () => {
|
||||||
|
expect(mixedBatch.lineStringBatch.verticesCount).to.be(4);
|
||||||
|
expect(mixedBatch.lineStringBatch.geometriesCount).to.be(1);
|
||||||
|
});
|
||||||
|
it('updates the aggregated metrics on the point batch', () => {
|
||||||
|
const keys = Object.keys(mixedBatch.pointBatch.entries);
|
||||||
|
expect(keys).to.not.contain(getUid(feature));
|
||||||
|
expect(mixedBatch.pointBatch.geometriesCount).to.be(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#removeFeature', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mixedBatch.addFeature(feature);
|
||||||
|
mixedBatch.removeFeature(feature);
|
||||||
|
});
|
||||||
|
it('clears all entries in the polygon batch', () => {
|
||||||
|
const keys = Object.keys(mixedBatch.polygonBatch.entries);
|
||||||
|
expect(keys).to.have.length(0);
|
||||||
|
expect(mixedBatch.polygonBatch.verticesCount).to.be(0);
|
||||||
|
expect(mixedBatch.polygonBatch.geometriesCount).to.be(0);
|
||||||
|
expect(mixedBatch.polygonBatch.ringsCount).to.be(0);
|
||||||
|
});
|
||||||
|
it('clears all entries in the linestring batch', () => {
|
||||||
|
const keys = Object.keys(mixedBatch.lineStringBatch.entries);
|
||||||
|
expect(keys).to.have.length(0);
|
||||||
|
expect(mixedBatch.lineStringBatch.verticesCount).to.be(0);
|
||||||
|
expect(mixedBatch.lineStringBatch.geometriesCount).to.be(0);
|
||||||
|
});
|
||||||
|
it('clears all entries in the point batch', () => {
|
||||||
|
const keys = Object.keys(mixedBatch.pointBatch.entries);
|
||||||
|
expect(keys).to.have.length(0);
|
||||||
|
expect(mixedBatch.pointBatch.geometriesCount).to.be(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#clear', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const feature1 = new Feature(
|
||||||
|
new Polygon([
|
||||||
|
[
|
||||||
|
[201, 202],
|
||||||
|
[203, 204],
|
||||||
|
[205, 206],
|
||||||
|
[207, 208],
|
||||||
|
],
|
||||||
|
])
|
||||||
|
);
|
||||||
|
const feature2 = new Feature(new Point([201, 202]));
|
||||||
|
mixedBatch.addFeature(feature1);
|
||||||
|
mixedBatch.addFeature(feature2);
|
||||||
|
mixedBatch.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('clears polygon batch', () => {
|
||||||
|
expect(Object.keys(mixedBatch.polygonBatch.entries)).to.have.length(0);
|
||||||
|
expect(mixedBatch.polygonBatch.geometriesCount).to.be(0);
|
||||||
|
expect(mixedBatch.polygonBatch.verticesCount).to.be(0);
|
||||||
|
expect(mixedBatch.polygonBatch.ringsCount).to.be(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('clears linestring batch', () => {
|
||||||
|
expect(Object.keys(mixedBatch.lineStringBatch.entries)).to.have.length(0);
|
||||||
|
expect(mixedBatch.lineStringBatch.geometriesCount).to.be(0);
|
||||||
|
expect(mixedBatch.lineStringBatch.verticesCount).to.be(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('clears point batch', () => {
|
||||||
|
expect(Object.keys(mixedBatch.pointBatch.entries)).to.have.length(0);
|
||||||
|
expect(mixedBatch.pointBatch.geometriesCount).to.be(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
477
test/browser/spec/ol/render/webgl/utils.test.js
Normal file
477
test/browser/spec/ol/render/webgl/utils.test.js
Normal file
@@ -0,0 +1,477 @@
|
|||||||
|
import {
|
||||||
|
colorDecodeId,
|
||||||
|
colorEncodeId,
|
||||||
|
getBlankImageData,
|
||||||
|
writeLineSegmentToBuffers,
|
||||||
|
writePointFeatureToBuffers,
|
||||||
|
writePolygonTrianglesToBuffers,
|
||||||
|
} from '../../../../../../src/ol/render/webgl/utils.js';
|
||||||
|
import {
|
||||||
|
compose as composeTransform,
|
||||||
|
create as createTransform,
|
||||||
|
makeInverse as makeInverseTransform,
|
||||||
|
} from '../../../../../../src/ol/transform.js';
|
||||||
|
|
||||||
|
describe('webgl render utils', function () {
|
||||||
|
describe('writePointFeatureToBuffers', function () {
|
||||||
|
let vertexBuffer, indexBuffer, instructions;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
vertexBuffer = new Float32Array(100);
|
||||||
|
indexBuffer = new Uint32Array(100);
|
||||||
|
instructions = new Float32Array(100);
|
||||||
|
|
||||||
|
instructions.set([0, 0, 0, 0, 10, 11]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('writes correctly to the buffers (without custom attributes)', function () {
|
||||||
|
const stride = 3;
|
||||||
|
const positions = writePointFeatureToBuffers(
|
||||||
|
instructions,
|
||||||
|
4,
|
||||||
|
vertexBuffer,
|
||||||
|
indexBuffer,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(vertexBuffer[0]).to.eql(10);
|
||||||
|
expect(vertexBuffer[1]).to.eql(11);
|
||||||
|
expect(vertexBuffer[2]).to.eql(0);
|
||||||
|
|
||||||
|
expect(vertexBuffer[stride + 0]).to.eql(10);
|
||||||
|
expect(vertexBuffer[stride + 1]).to.eql(11);
|
||||||
|
expect(vertexBuffer[stride + 2]).to.eql(1);
|
||||||
|
|
||||||
|
expect(vertexBuffer[stride * 2 + 0]).to.eql(10);
|
||||||
|
expect(vertexBuffer[stride * 2 + 1]).to.eql(11);
|
||||||
|
expect(vertexBuffer[stride * 2 + 2]).to.eql(2);
|
||||||
|
|
||||||
|
expect(vertexBuffer[stride * 3 + 0]).to.eql(10);
|
||||||
|
expect(vertexBuffer[stride * 3 + 1]).to.eql(11);
|
||||||
|
expect(vertexBuffer[stride * 3 + 2]).to.eql(3);
|
||||||
|
|
||||||
|
expect(indexBuffer[0]).to.eql(0);
|
||||||
|
expect(indexBuffer[1]).to.eql(1);
|
||||||
|
expect(indexBuffer[2]).to.eql(3);
|
||||||
|
expect(indexBuffer[3]).to.eql(1);
|
||||||
|
expect(indexBuffer[4]).to.eql(2);
|
||||||
|
expect(indexBuffer[5]).to.eql(3);
|
||||||
|
|
||||||
|
expect(positions.indexPosition).to.eql(6);
|
||||||
|
expect(positions.vertexPosition).to.eql(stride * 4);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('writes correctly to the buffers (with 2 custom attributes)', function () {
|
||||||
|
instructions.set([0, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12, 13]);
|
||||||
|
const stride = 5;
|
||||||
|
const positions = writePointFeatureToBuffers(
|
||||||
|
instructions,
|
||||||
|
8,
|
||||||
|
vertexBuffer,
|
||||||
|
indexBuffer,
|
||||||
|
2
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(vertexBuffer[0]).to.eql(10);
|
||||||
|
expect(vertexBuffer[1]).to.eql(11);
|
||||||
|
expect(vertexBuffer[2]).to.eql(0);
|
||||||
|
expect(vertexBuffer[3]).to.eql(12);
|
||||||
|
expect(vertexBuffer[4]).to.eql(13);
|
||||||
|
|
||||||
|
expect(vertexBuffer[stride + 0]).to.eql(10);
|
||||||
|
expect(vertexBuffer[stride + 1]).to.eql(11);
|
||||||
|
expect(vertexBuffer[stride + 2]).to.eql(1);
|
||||||
|
expect(vertexBuffer[stride + 3]).to.eql(12);
|
||||||
|
expect(vertexBuffer[stride + 4]).to.eql(13);
|
||||||
|
|
||||||
|
expect(vertexBuffer[stride * 2 + 0]).to.eql(10);
|
||||||
|
expect(vertexBuffer[stride * 2 + 1]).to.eql(11);
|
||||||
|
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 * 3 + 0]).to.eql(10);
|
||||||
|
expect(vertexBuffer[stride * 3 + 1]).to.eql(11);
|
||||||
|
expect(vertexBuffer[stride * 3 + 2]).to.eql(3);
|
||||||
|
expect(vertexBuffer[stride * 3 + 3]).to.eql(12);
|
||||||
|
expect(vertexBuffer[stride * 3 + 4]).to.eql(13);
|
||||||
|
|
||||||
|
expect(indexBuffer[0]).to.eql(0);
|
||||||
|
expect(indexBuffer[1]).to.eql(1);
|
||||||
|
expect(indexBuffer[2]).to.eql(3);
|
||||||
|
expect(indexBuffer[3]).to.eql(1);
|
||||||
|
expect(indexBuffer[4]).to.eql(2);
|
||||||
|
expect(indexBuffer[5]).to.eql(3);
|
||||||
|
|
||||||
|
expect(positions.indexPosition).to.eql(6);
|
||||||
|
expect(positions.vertexPosition).to.eql(stride * 4);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('correctly chains buffer writes', function () {
|
||||||
|
instructions.set([10, 11, 20, 21, 30, 31]);
|
||||||
|
const stride = 3;
|
||||||
|
let positions = writePointFeatureToBuffers(
|
||||||
|
instructions,
|
||||||
|
0,
|
||||||
|
vertexBuffer,
|
||||||
|
indexBuffer,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
positions = writePointFeatureToBuffers(
|
||||||
|
instructions,
|
||||||
|
2,
|
||||||
|
vertexBuffer,
|
||||||
|
indexBuffer,
|
||||||
|
0,
|
||||||
|
positions
|
||||||
|
);
|
||||||
|
positions = writePointFeatureToBuffers(
|
||||||
|
instructions,
|
||||||
|
4,
|
||||||
|
vertexBuffer,
|
||||||
|
indexBuffer,
|
||||||
|
0,
|
||||||
|
positions
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(vertexBuffer[0]).to.eql(10);
|
||||||
|
expect(vertexBuffer[1]).to.eql(11);
|
||||||
|
expect(vertexBuffer[2]).to.eql(0);
|
||||||
|
|
||||||
|
expect(vertexBuffer[stride * 4 + 0]).to.eql(20);
|
||||||
|
expect(vertexBuffer[stride * 4 + 1]).to.eql(21);
|
||||||
|
expect(vertexBuffer[stride * 4 + 2]).to.eql(0);
|
||||||
|
|
||||||
|
expect(vertexBuffer[stride * 8 + 0]).to.eql(30);
|
||||||
|
expect(vertexBuffer[stride * 8 + 1]).to.eql(31);
|
||||||
|
expect(vertexBuffer[stride * 8 + 2]).to.eql(0);
|
||||||
|
|
||||||
|
expect(indexBuffer[6 + 0]).to.eql(4);
|
||||||
|
expect(indexBuffer[6 + 1]).to.eql(5);
|
||||||
|
expect(indexBuffer[6 + 2]).to.eql(7);
|
||||||
|
expect(indexBuffer[6 + 3]).to.eql(5);
|
||||||
|
expect(indexBuffer[6 + 4]).to.eql(6);
|
||||||
|
expect(indexBuffer[6 + 5]).to.eql(7);
|
||||||
|
|
||||||
|
expect(indexBuffer[6 * 2 + 0]).to.eql(8);
|
||||||
|
expect(indexBuffer[6 * 2 + 1]).to.eql(9);
|
||||||
|
expect(indexBuffer[6 * 2 + 2]).to.eql(11);
|
||||||
|
expect(indexBuffer[6 * 2 + 3]).to.eql(9);
|
||||||
|
expect(indexBuffer[6 * 2 + 4]).to.eql(10);
|
||||||
|
expect(indexBuffer[6 * 2 + 5]).to.eql(11);
|
||||||
|
|
||||||
|
expect(positions.indexPosition).to.eql(6 * 3);
|
||||||
|
expect(positions.vertexPosition).to.eql(stride * 4 * 3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('writeLineSegmentToBuffers', function () {
|
||||||
|
let vertexArray, indexArray, instructions;
|
||||||
|
let instructionsTransform, invertInstructionsTransform;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
vertexArray = [];
|
||||||
|
indexArray = [];
|
||||||
|
|
||||||
|
instructions = new Float32Array(100);
|
||||||
|
|
||||||
|
instructionsTransform = createTransform();
|
||||||
|
invertInstructionsTransform = createTransform();
|
||||||
|
composeTransform(instructionsTransform, 0, 0, 10, 20, 0, -50, 200);
|
||||||
|
makeInverseTransform(invertInstructionsTransform, instructionsTransform);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isolated segment', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
instructions.set([0, 0, 0, 2, 5, 5, 25, 5]);
|
||||||
|
writeLineSegmentToBuffers(
|
||||||
|
instructions,
|
||||||
|
4,
|
||||||
|
6,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
vertexArray,
|
||||||
|
indexArray,
|
||||||
|
[],
|
||||||
|
instructionsTransform,
|
||||||
|
invertInstructionsTransform
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('generates a quad for the segment', function () {
|
||||||
|
expect(vertexArray).to.have.length(20);
|
||||||
|
expect(vertexArray).to.eql([
|
||||||
|
5, 5, 25, 5, 0, 5, 5, 25, 5, 100000000, 5, 5, 25, 5, 200000000, 5, 5,
|
||||||
|
25, 5, 300000000,
|
||||||
|
]);
|
||||||
|
expect(indexArray).to.have.length(6);
|
||||||
|
expect(indexArray).to.eql([0, 1, 2, 1, 3, 2]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isolated segment with custom attributes', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
instructions.set([888, 999, 2, 5, 5, 25, 5]);
|
||||||
|
writeLineSegmentToBuffers(
|
||||||
|
instructions,
|
||||||
|
3,
|
||||||
|
5,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
vertexArray,
|
||||||
|
indexArray,
|
||||||
|
[888, 999],
|
||||||
|
instructionsTransform,
|
||||||
|
invertInstructionsTransform
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('adds custom attributes in the vertices buffer', function () {
|
||||||
|
expect(vertexArray).to.have.length(28);
|
||||||
|
expect(vertexArray).to.eql([
|
||||||
|
5, 5, 25, 5, 0, 888, 999, 5, 5, 25, 5, 100000000, 888, 999, 5, 5, 25,
|
||||||
|
5, 200000000, 888, 999, 5, 5, 25, 5, 300000000, 888, 999,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
it('does not impact indices array', function () {
|
||||||
|
expect(indexArray).to.have.length(6);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('segment with a point coming before it, join angle < PI', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
instructions.set([2, 5, 5, 25, 5, 5, 20]);
|
||||||
|
writeLineSegmentToBuffers(
|
||||||
|
instructions,
|
||||||
|
1,
|
||||||
|
3,
|
||||||
|
5,
|
||||||
|
null,
|
||||||
|
vertexArray,
|
||||||
|
indexArray,
|
||||||
|
[],
|
||||||
|
instructionsTransform,
|
||||||
|
invertInstructionsTransform
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('generate the correct amount of vertices', () => {
|
||||||
|
expect(vertexArray).to.have.length(20);
|
||||||
|
});
|
||||||
|
it('correctly encodes the join angle', () => {
|
||||||
|
expect(vertexArray[4]).to.eql(2356);
|
||||||
|
expect(vertexArray[9]).to.eql(100002356);
|
||||||
|
expect(vertexArray[14]).to.eql(200002356);
|
||||||
|
expect(vertexArray[19]).to.eql(300002356);
|
||||||
|
});
|
||||||
|
it('does not impact indices array', function () {
|
||||||
|
expect(indexArray).to.have.length(6);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('segment with a point coming before it, join angle > PI', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
instructions.set([2, 5, 5, 25, 5, 5, -10]);
|
||||||
|
writeLineSegmentToBuffers(
|
||||||
|
instructions,
|
||||||
|
1,
|
||||||
|
3,
|
||||||
|
5,
|
||||||
|
null,
|
||||||
|
vertexArray,
|
||||||
|
indexArray,
|
||||||
|
[],
|
||||||
|
instructionsTransform,
|
||||||
|
invertInstructionsTransform
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('generate the correct amount of vertices', () => {
|
||||||
|
expect(vertexArray).to.have.length(20);
|
||||||
|
});
|
||||||
|
it('correctly encodes the join angle', () => {
|
||||||
|
expect(vertexArray[4]).to.eql(7069);
|
||||||
|
expect(vertexArray[9]).to.eql(100007069);
|
||||||
|
expect(vertexArray[14]).to.eql(200007069);
|
||||||
|
expect(vertexArray[19]).to.eql(300007069);
|
||||||
|
});
|
||||||
|
it('does not impact indices array', function () {
|
||||||
|
expect(indexArray).to.have.length(6);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('segment with a point coming after it, join angle < PI', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
instructions.set([2, 5, 5, 25, 5, 5, 20]);
|
||||||
|
writeLineSegmentToBuffers(
|
||||||
|
instructions,
|
||||||
|
1,
|
||||||
|
3,
|
||||||
|
null,
|
||||||
|
5,
|
||||||
|
vertexArray,
|
||||||
|
indexArray,
|
||||||
|
[],
|
||||||
|
instructionsTransform,
|
||||||
|
invertInstructionsTransform
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('generate the correct amount of vertices', () => {
|
||||||
|
expect(vertexArray).to.have.length(20);
|
||||||
|
});
|
||||||
|
it('correctly encodes the join angle', () => {
|
||||||
|
expect(vertexArray[4]).to.eql(88870000);
|
||||||
|
expect(vertexArray[9]).to.eql(188870000);
|
||||||
|
expect(vertexArray[14]).to.eql(288870000);
|
||||||
|
expect(vertexArray[19]).to.eql(388870000);
|
||||||
|
});
|
||||||
|
it('does not impact indices array', function () {
|
||||||
|
expect(indexArray).to.have.length(6);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('segment with a point coming after it, join angle > PI', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
instructions.set([2, 5, 5, 25, 5, 25, -10]);
|
||||||
|
writeLineSegmentToBuffers(
|
||||||
|
instructions,
|
||||||
|
1,
|
||||||
|
3,
|
||||||
|
null,
|
||||||
|
5,
|
||||||
|
vertexArray,
|
||||||
|
indexArray,
|
||||||
|
[],
|
||||||
|
instructionsTransform,
|
||||||
|
invertInstructionsTransform
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('generate the correct amount of vertices', () => {
|
||||||
|
expect(vertexArray).to.have.length(20);
|
||||||
|
});
|
||||||
|
it('correctly encodes join angles', () => {
|
||||||
|
expect(vertexArray[4]).to.eql(23560000);
|
||||||
|
expect(vertexArray[9]).to.eql(123560000);
|
||||||
|
expect(vertexArray[14]).to.eql(223560000);
|
||||||
|
expect(vertexArray[19]).to.eql(323560000);
|
||||||
|
});
|
||||||
|
it('does not impact indices array', function () {
|
||||||
|
expect(indexArray).to.have.length(6);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('writePolygonTrianglesToBuffers', function () {
|
||||||
|
let vertexArray, indexArray, instructions, newIndex;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
vertexArray = [];
|
||||||
|
indexArray = [];
|
||||||
|
instructions = new Float32Array(100);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('polygon with a hole', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
instructions.set([
|
||||||
|
0, 0, 0, 2, 6, 5, 0, 0, 10, 0, 15, 6, 10, 12, 0, 12, 0, 0, 3, 3, 5, 1,
|
||||||
|
7, 3, 5, 5, 3, 3,
|
||||||
|
]);
|
||||||
|
newIndex = writePolygonTrianglesToBuffers(
|
||||||
|
instructions,
|
||||||
|
3,
|
||||||
|
vertexArray,
|
||||||
|
indexArray,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('generates triangles correctly', function () {
|
||||||
|
expect(vertexArray).to.have.length(22);
|
||||||
|
expect(vertexArray).to.eql([
|
||||||
|
0, 0, 10, 0, 15, 6, 10, 12, 0, 12, 0, 0, 3, 3, 5, 1, 7, 3, 5, 5, 3, 3,
|
||||||
|
]);
|
||||||
|
expect(indexArray).to.have.length(24);
|
||||||
|
expect(indexArray).to.eql([
|
||||||
|
4, 0, 9, 7, 10, 0, 1, 2, 3, 3, 4, 9, 7, 0, 1, 3, 9, 8, 8, 7, 1, 1, 3,
|
||||||
|
8,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
it('correctly returns the new index', function () {
|
||||||
|
expect(newIndex).to.eql(28);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('polygon with a hole and custom attributes', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
instructions.set([
|
||||||
|
0, 0, 0, 1234, 2, 6, 5, 0, 0, 10, 0, 15, 6, 10, 12, 0, 12, 0, 0, 3, 3,
|
||||||
|
5, 1, 7, 3, 5, 5, 3, 3,
|
||||||
|
]);
|
||||||
|
newIndex = writePolygonTrianglesToBuffers(
|
||||||
|
instructions,
|
||||||
|
3,
|
||||||
|
vertexArray,
|
||||||
|
indexArray,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('generates triangles correctly', function () {
|
||||||
|
expect(vertexArray).to.have.length(33);
|
||||||
|
expect(vertexArray).to.eql([
|
||||||
|
0, 0, 1234, 10, 0, 1234, 15, 6, 1234, 10, 12, 1234, 0, 12, 1234, 0, 0,
|
||||||
|
1234, 3, 3, 1234, 5, 1, 1234, 7, 3, 1234, 5, 5, 1234, 3, 3, 1234,
|
||||||
|
]);
|
||||||
|
expect(indexArray).to.have.length(24);
|
||||||
|
expect(indexArray).to.eql([
|
||||||
|
4, 0, 9, 7, 10, 0, 1, 2, 3, 3, 4, 9, 7, 0, 1, 3, 9, 8, 8, 7, 1, 1, 3,
|
||||||
|
8,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
it('correctly returns the new index', function () {
|
||||||
|
expect(newIndex).to.eql(29);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getBlankImageData', function () {
|
||||||
|
it('creates a 1x1 white texture', function () {
|
||||||
|
const texture = getBlankImageData();
|
||||||
|
expect(texture.height).to.eql(1);
|
||||||
|
expect(texture.width).to.eql(1);
|
||||||
|
expect(texture.data[0]).to.eql(255);
|
||||||
|
expect(texture.data[1]).to.eql(255);
|
||||||
|
expect(texture.data[2]).to.eql(255);
|
||||||
|
expect(texture.data[3]).to.eql(255);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('colorEncodeId and colorDecodeId', function () {
|
||||||
|
it('correctly encodes and decodes ids', function () {
|
||||||
|
expect(colorDecodeId(colorEncodeId(0))).to.eql(0);
|
||||||
|
expect(colorDecodeId(colorEncodeId(1))).to.eql(1);
|
||||||
|
expect(colorDecodeId(colorEncodeId(123))).to.eql(123);
|
||||||
|
expect(colorDecodeId(colorEncodeId(12345))).to.eql(12345);
|
||||||
|
expect(colorDecodeId(colorEncodeId(123456))).to.eql(123456);
|
||||||
|
expect(colorDecodeId(colorEncodeId(91612))).to.eql(91612);
|
||||||
|
expect(colorDecodeId(colorEncodeId(1234567890))).to.eql(1234567890);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('correctly reuses array', function () {
|
||||||
|
const arr = [];
|
||||||
|
expect(colorEncodeId(123, arr)).to.be(arr);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is compatible with Uint8Array storage', function () {
|
||||||
|
const encoded = colorEncodeId(91612);
|
||||||
|
const typed = Uint8Array.of(
|
||||||
|
encoded[0] * 255,
|
||||||
|
encoded[1] * 255,
|
||||||
|
encoded[2] * 255,
|
||||||
|
encoded[3] * 255
|
||||||
|
);
|
||||||
|
const arr = [
|
||||||
|
typed[0] / 255,
|
||||||
|
typed[1] / 255,
|
||||||
|
typed[2] / 255,
|
||||||
|
typed[3] / 255,
|
||||||
|
];
|
||||||
|
const decoded = colorDecodeId(arr);
|
||||||
|
expect(decoded).to.eql(91612);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -6,12 +6,7 @@ import TileLayer from '../../../../../../src/ol/layer/WebGLTile.js';
|
|||||||
import VectorLayer from '../../../../../../src/ol/layer/Vector.js';
|
import VectorLayer from '../../../../../../src/ol/layer/Vector.js';
|
||||||
import VectorSource from '../../../../../../src/ol/source/Vector.js';
|
import VectorSource from '../../../../../../src/ol/source/Vector.js';
|
||||||
import View from '../../../../../../src/ol/View.js';
|
import View from '../../../../../../src/ol/View.js';
|
||||||
import WebGLLayerRenderer, {
|
import WebGLLayerRenderer from '../../../../../../src/ol/renderer/webgl/Layer.js';
|
||||||
colorDecodeId,
|
|
||||||
colorEncodeId,
|
|
||||||
getBlankImageData,
|
|
||||||
writePointFeatureToBuffers,
|
|
||||||
} from '../../../../../../src/ol/renderer/webgl/Layer.js';
|
|
||||||
import {getUid} from '../../../../../../src/ol/util.js';
|
import {getUid} from '../../../../../../src/ol/util.js';
|
||||||
|
|
||||||
describe('ol/renderer/webgl/Layer', function () {
|
describe('ol/renderer/webgl/Layer', function () {
|
||||||
@@ -36,205 +31,6 @@ describe('ol/renderer/webgl/Layer', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('writePointFeatureToBuffers', function () {
|
|
||||||
let vertexBuffer, indexBuffer, instructions;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
vertexBuffer = new Float32Array(100);
|
|
||||||
indexBuffer = new Uint32Array(100);
|
|
||||||
instructions = new Float32Array(100);
|
|
||||||
|
|
||||||
instructions.set([0, 0, 0, 0, 10, 11]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('writes correctly to the buffers (without custom attributes)', function () {
|
|
||||||
const stride = 3;
|
|
||||||
const positions = writePointFeatureToBuffers(
|
|
||||||
instructions,
|
|
||||||
4,
|
|
||||||
vertexBuffer,
|
|
||||||
indexBuffer,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(vertexBuffer[0]).to.eql(10);
|
|
||||||
expect(vertexBuffer[1]).to.eql(11);
|
|
||||||
expect(vertexBuffer[2]).to.eql(0);
|
|
||||||
|
|
||||||
expect(vertexBuffer[stride + 0]).to.eql(10);
|
|
||||||
expect(vertexBuffer[stride + 1]).to.eql(11);
|
|
||||||
expect(vertexBuffer[stride + 2]).to.eql(1);
|
|
||||||
|
|
||||||
expect(vertexBuffer[stride * 2 + 0]).to.eql(10);
|
|
||||||
expect(vertexBuffer[stride * 2 + 1]).to.eql(11);
|
|
||||||
expect(vertexBuffer[stride * 2 + 2]).to.eql(2);
|
|
||||||
|
|
||||||
expect(vertexBuffer[stride * 3 + 0]).to.eql(10);
|
|
||||||
expect(vertexBuffer[stride * 3 + 1]).to.eql(11);
|
|
||||||
expect(vertexBuffer[stride * 3 + 2]).to.eql(3);
|
|
||||||
|
|
||||||
expect(indexBuffer[0]).to.eql(0);
|
|
||||||
expect(indexBuffer[1]).to.eql(1);
|
|
||||||
expect(indexBuffer[2]).to.eql(3);
|
|
||||||
expect(indexBuffer[3]).to.eql(1);
|
|
||||||
expect(indexBuffer[4]).to.eql(2);
|
|
||||||
expect(indexBuffer[5]).to.eql(3);
|
|
||||||
|
|
||||||
expect(positions.indexPosition).to.eql(6);
|
|
||||||
expect(positions.vertexPosition).to.eql(stride * 4);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('writes correctly to the buffers (with 2 custom attributes)', function () {
|
|
||||||
instructions.set([0, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12, 13]);
|
|
||||||
const stride = 5;
|
|
||||||
const positions = writePointFeatureToBuffers(
|
|
||||||
instructions,
|
|
||||||
8,
|
|
||||||
vertexBuffer,
|
|
||||||
indexBuffer,
|
|
||||||
2
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(vertexBuffer[0]).to.eql(10);
|
|
||||||
expect(vertexBuffer[1]).to.eql(11);
|
|
||||||
expect(vertexBuffer[2]).to.eql(0);
|
|
||||||
expect(vertexBuffer[3]).to.eql(12);
|
|
||||||
expect(vertexBuffer[4]).to.eql(13);
|
|
||||||
|
|
||||||
expect(vertexBuffer[stride + 0]).to.eql(10);
|
|
||||||
expect(vertexBuffer[stride + 1]).to.eql(11);
|
|
||||||
expect(vertexBuffer[stride + 2]).to.eql(1);
|
|
||||||
expect(vertexBuffer[stride + 3]).to.eql(12);
|
|
||||||
expect(vertexBuffer[stride + 4]).to.eql(13);
|
|
||||||
|
|
||||||
expect(vertexBuffer[stride * 2 + 0]).to.eql(10);
|
|
||||||
expect(vertexBuffer[stride * 2 + 1]).to.eql(11);
|
|
||||||
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 * 3 + 0]).to.eql(10);
|
|
||||||
expect(vertexBuffer[stride * 3 + 1]).to.eql(11);
|
|
||||||
expect(vertexBuffer[stride * 3 + 2]).to.eql(3);
|
|
||||||
expect(vertexBuffer[stride * 3 + 3]).to.eql(12);
|
|
||||||
expect(vertexBuffer[stride * 3 + 4]).to.eql(13);
|
|
||||||
|
|
||||||
expect(indexBuffer[0]).to.eql(0);
|
|
||||||
expect(indexBuffer[1]).to.eql(1);
|
|
||||||
expect(indexBuffer[2]).to.eql(3);
|
|
||||||
expect(indexBuffer[3]).to.eql(1);
|
|
||||||
expect(indexBuffer[4]).to.eql(2);
|
|
||||||
expect(indexBuffer[5]).to.eql(3);
|
|
||||||
|
|
||||||
expect(positions.indexPosition).to.eql(6);
|
|
||||||
expect(positions.vertexPosition).to.eql(stride * 4);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('correctly chains buffer writes', function () {
|
|
||||||
instructions.set([10, 11, 20, 21, 30, 31]);
|
|
||||||
const stride = 3;
|
|
||||||
let positions = writePointFeatureToBuffers(
|
|
||||||
instructions,
|
|
||||||
0,
|
|
||||||
vertexBuffer,
|
|
||||||
indexBuffer,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
positions = writePointFeatureToBuffers(
|
|
||||||
instructions,
|
|
||||||
2,
|
|
||||||
vertexBuffer,
|
|
||||||
indexBuffer,
|
|
||||||
0,
|
|
||||||
positions
|
|
||||||
);
|
|
||||||
positions = writePointFeatureToBuffers(
|
|
||||||
instructions,
|
|
||||||
4,
|
|
||||||
vertexBuffer,
|
|
||||||
indexBuffer,
|
|
||||||
0,
|
|
||||||
positions
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(vertexBuffer[0]).to.eql(10);
|
|
||||||
expect(vertexBuffer[1]).to.eql(11);
|
|
||||||
expect(vertexBuffer[2]).to.eql(0);
|
|
||||||
|
|
||||||
expect(vertexBuffer[stride * 4 + 0]).to.eql(20);
|
|
||||||
expect(vertexBuffer[stride * 4 + 1]).to.eql(21);
|
|
||||||
expect(vertexBuffer[stride * 4 + 2]).to.eql(0);
|
|
||||||
|
|
||||||
expect(vertexBuffer[stride * 8 + 0]).to.eql(30);
|
|
||||||
expect(vertexBuffer[stride * 8 + 1]).to.eql(31);
|
|
||||||
expect(vertexBuffer[stride * 8 + 2]).to.eql(0);
|
|
||||||
|
|
||||||
expect(indexBuffer[6 + 0]).to.eql(4);
|
|
||||||
expect(indexBuffer[6 + 1]).to.eql(5);
|
|
||||||
expect(indexBuffer[6 + 2]).to.eql(7);
|
|
||||||
expect(indexBuffer[6 + 3]).to.eql(5);
|
|
||||||
expect(indexBuffer[6 + 4]).to.eql(6);
|
|
||||||
expect(indexBuffer[6 + 5]).to.eql(7);
|
|
||||||
|
|
||||||
expect(indexBuffer[6 * 2 + 0]).to.eql(8);
|
|
||||||
expect(indexBuffer[6 * 2 + 1]).to.eql(9);
|
|
||||||
expect(indexBuffer[6 * 2 + 2]).to.eql(11);
|
|
||||||
expect(indexBuffer[6 * 2 + 3]).to.eql(9);
|
|
||||||
expect(indexBuffer[6 * 2 + 4]).to.eql(10);
|
|
||||||
expect(indexBuffer[6 * 2 + 5]).to.eql(11);
|
|
||||||
|
|
||||||
expect(positions.indexPosition).to.eql(6 * 3);
|
|
||||||
expect(positions.vertexPosition).to.eql(stride * 4 * 3);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getBlankImageData', function () {
|
|
||||||
it('creates a 1x1 white texture', function () {
|
|
||||||
const texture = getBlankImageData();
|
|
||||||
expect(texture.height).to.eql(1);
|
|
||||||
expect(texture.width).to.eql(1);
|
|
||||||
expect(texture.data[0]).to.eql(255);
|
|
||||||
expect(texture.data[1]).to.eql(255);
|
|
||||||
expect(texture.data[2]).to.eql(255);
|
|
||||||
expect(texture.data[3]).to.eql(255);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('colorEncodeId and colorDecodeId', function () {
|
|
||||||
it('correctly encodes and decodes ids', function () {
|
|
||||||
expect(colorDecodeId(colorEncodeId(0))).to.eql(0);
|
|
||||||
expect(colorDecodeId(colorEncodeId(1))).to.eql(1);
|
|
||||||
expect(colorDecodeId(colorEncodeId(123))).to.eql(123);
|
|
||||||
expect(colorDecodeId(colorEncodeId(12345))).to.eql(12345);
|
|
||||||
expect(colorDecodeId(colorEncodeId(123456))).to.eql(123456);
|
|
||||||
expect(colorDecodeId(colorEncodeId(91612))).to.eql(91612);
|
|
||||||
expect(colorDecodeId(colorEncodeId(1234567890))).to.eql(1234567890);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('correctly reuses array', function () {
|
|
||||||
const arr = [];
|
|
||||||
expect(colorEncodeId(123, arr)).to.be(arr);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('is compatible with Uint8Array storage', function () {
|
|
||||||
const encoded = colorEncodeId(91612);
|
|
||||||
const typed = Uint8Array.of(
|
|
||||||
encoded[0] * 255,
|
|
||||||
encoded[1] * 255,
|
|
||||||
encoded[2] * 255,
|
|
||||||
encoded[3] * 255
|
|
||||||
);
|
|
||||||
const arr = [
|
|
||||||
typed[0] / 255,
|
|
||||||
typed[1] / 255,
|
|
||||||
typed[2] / 255,
|
|
||||||
typed[3] / 255,
|
|
||||||
];
|
|
||||||
const decoded = colorDecodeId(arr);
|
|
||||||
expect(decoded).to.eql(91612);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('context sharing', () => {
|
describe('context sharing', () => {
|
||||||
let target;
|
let target;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import View from '../../../../../../src/ol/View.js';
|
|||||||
import ViewHint from '../../../../../../src/ol/ViewHint.js';
|
import ViewHint from '../../../../../../src/ol/ViewHint.js';
|
||||||
import WebGLPointsLayer from '../../../../../../src/ol/layer/WebGLPoints.js';
|
import WebGLPointsLayer from '../../../../../../src/ol/layer/WebGLPoints.js';
|
||||||
import WebGLPointsLayerRenderer from '../../../../../../src/ol/renderer/webgl/PointsLayer.js';
|
import WebGLPointsLayerRenderer from '../../../../../../src/ol/renderer/webgl/PointsLayer.js';
|
||||||
import {WebGLWorkerMessageType} from '../../../../../../src/ol/renderer/webgl/Layer.js';
|
import {WebGLWorkerMessageType} from '../../../../../../src/ol/render/webgl/constants.js';
|
||||||
import {
|
import {
|
||||||
compose as composeTransform,
|
compose as composeTransform,
|
||||||
create as createTransform,
|
create as createTransform,
|
||||||
@@ -156,7 +156,7 @@ describe('ol/renderer/webgl/PointsLayer', function () {
|
|||||||
const attributePerVertex = 3;
|
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_POINT_BUFFERS) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
expect(renderer.verticesBuffer_.getArray().length).to.eql(
|
expect(renderer.verticesBuffer_.getArray().length).to.eql(
|
||||||
@@ -192,7 +192,7 @@ describe('ol/renderer/webgl/PointsLayer', function () {
|
|||||||
const attributePerVertex = 8;
|
const attributePerVertex = 8;
|
||||||
|
|
||||||
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_POINT_BUFFERS) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!renderer.hitVerticesBuffer_.getArray()) {
|
if (!renderer.hitVerticesBuffer_.getArray()) {
|
||||||
@@ -231,7 +231,7 @@ describe('ol/renderer/webgl/PointsLayer', function () {
|
|||||||
renderer.prepareFrame(frameState);
|
renderer.prepareFrame(frameState);
|
||||||
|
|
||||||
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_POINT_BUFFERS) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const attributePerVertex = 3;
|
const attributePerVertex = 3;
|
||||||
@@ -627,14 +627,14 @@ describe('ol/renderer/webgl/PointsLayer', function () {
|
|||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
source = new VectorSource({
|
source = new VectorSource({
|
||||||
features: new GeoJSON().readFeatures({
|
features: new GeoJSON().readFeatures({
|
||||||
'type': 'FeatureCollection',
|
type: 'FeatureCollection',
|
||||||
'features': [
|
features: [
|
||||||
{
|
{
|
||||||
'type': 'Feature',
|
type: 'Feature',
|
||||||
'properties': {},
|
properties: {},
|
||||||
'geometry': {
|
geometry: {
|
||||||
'type': 'Point',
|
type: 'Point',
|
||||||
'coordinates': [13, 52],
|
coordinates: [13, 52],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -57,6 +57,16 @@ const INVALID_FRAGMENT_SHADER = `
|
|||||||
gl_FragColor = vec4(oops, 1.0, 1.0, 1.0);
|
gl_FragColor = vec4(oops, 1.0, 1.0, 1.0);
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
|
const SAMPLE_FRAMESTATE = {
|
||||||
|
size: [100, 150],
|
||||||
|
viewState: {
|
||||||
|
rotation: 0.4,
|
||||||
|
resolution: 2,
|
||||||
|
center: [10, 20],
|
||||||
|
zoom: 3,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
describe('ol/webgl/WebGLHelper', function () {
|
describe('ol/webgl/WebGLHelper', function () {
|
||||||
let h;
|
let h;
|
||||||
afterEach(function () {
|
afterEach(function () {
|
||||||
@@ -117,7 +127,10 @@ describe('ol/webgl/WebGLHelper', function () {
|
|||||||
u_test4: createTransform(),
|
u_test4: createTransform(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
h.useProgram(h.getProgram(FRAGMENT_SHADER, VERTEX_SHADER));
|
h.useProgram(
|
||||||
|
h.getProgram(FRAGMENT_SHADER, VERTEX_SHADER),
|
||||||
|
SAMPLE_FRAMESTATE
|
||||||
|
);
|
||||||
h.prepareDraw({
|
h.prepareDraw({
|
||||||
pixelRatio: 2,
|
pixelRatio: 2,
|
||||||
size: [50, 80],
|
size: [50, 80],
|
||||||
@@ -142,6 +155,16 @@ describe('ol/webgl/WebGLHelper', function () {
|
|||||||
h.uniformLocations_[DefaultUniform.OFFSET_SCALE_MATRIX]
|
h.uniformLocations_[DefaultUniform.OFFSET_SCALE_MATRIX]
|
||||||
).not.to.eql(undefined);
|
).not.to.eql(undefined);
|
||||||
expect(h.uniformLocations_[DefaultUniform.TIME]).not.to.eql(undefined);
|
expect(h.uniformLocations_[DefaultUniform.TIME]).not.to.eql(undefined);
|
||||||
|
expect(h.uniformLocations_[DefaultUniform.ZOOM]).not.to.eql(undefined);
|
||||||
|
expect(h.uniformLocations_[DefaultUniform.RESOLUTION]).not.to.eql(
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
expect(h.uniformLocations_[DefaultUniform.SIZE_PX]).not.to.eql(
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
expect(h.uniformLocations_[DefaultUniform.PIXEL_RATIO]).not.to.eql(
|
||||||
|
undefined
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('has processed uniforms', function () {
|
it('has processed uniforms', function () {
|
||||||
@@ -164,7 +187,7 @@ describe('ol/webgl/WebGLHelper', function () {
|
|||||||
h = new WebGLHelper();
|
h = new WebGLHelper();
|
||||||
|
|
||||||
p = h.getProgram(FRAGMENT_SHADER, VERTEX_SHADER);
|
p = h.getProgram(FRAGMENT_SHADER, VERTEX_SHADER);
|
||||||
h.useProgram(p);
|
h.useProgram(p, SAMPLE_FRAMESTATE);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('has saved the program', function () {
|
it('has saved the program', function () {
|
||||||
@@ -209,34 +232,30 @@ describe('ol/webgl/WebGLHelper', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('#makeProjectionTransform', function () {
|
describe('#makeProjectionTransform', function () {
|
||||||
let frameState;
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
h = new WebGLHelper();
|
h = new WebGLHelper();
|
||||||
|
|
||||||
frameState = {
|
|
||||||
size: [100, 150],
|
|
||||||
viewState: {
|
|
||||||
rotation: 0.4,
|
|
||||||
resolution: 2,
|
|
||||||
center: [10, 20],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('gives out the correct transform', function () {
|
it('gives out the correct transform', function () {
|
||||||
const scaleX = 2 / frameState.size[0] / frameState.viewState.resolution;
|
const scaleX =
|
||||||
const scaleY = 2 / frameState.size[1] / frameState.viewState.resolution;
|
2 /
|
||||||
|
SAMPLE_FRAMESTATE.size[0] /
|
||||||
|
SAMPLE_FRAMESTATE.viewState.resolution;
|
||||||
|
const scaleY =
|
||||||
|
2 /
|
||||||
|
SAMPLE_FRAMESTATE.size[1] /
|
||||||
|
SAMPLE_FRAMESTATE.viewState.resolution;
|
||||||
const given = createTransform();
|
const given = createTransform();
|
||||||
const expected = createTransform();
|
const expected = createTransform();
|
||||||
scaleTransform(expected, scaleX, scaleY);
|
scaleTransform(expected, scaleX, scaleY);
|
||||||
rotateTransform(expected, -frameState.viewState.rotation);
|
rotateTransform(expected, -SAMPLE_FRAMESTATE.viewState.rotation);
|
||||||
translateTransform(
|
translateTransform(
|
||||||
expected,
|
expected,
|
||||||
-frameState.viewState.center[0],
|
-SAMPLE_FRAMESTATE.viewState.center[0],
|
||||||
-frameState.viewState.center[1]
|
-SAMPLE_FRAMESTATE.viewState.center[1]
|
||||||
);
|
);
|
||||||
|
|
||||||
h.makeProjectionTransform(frameState, given);
|
h.makeProjectionTransform(SAMPLE_FRAMESTATE, given);
|
||||||
|
|
||||||
expect(given.map((val) => val.toFixed(15))).to.eql(
|
expect(given.map((val) => val.toFixed(15))).to.eql(
|
||||||
expected.map((val) => val.toFixed(15))
|
expected.map((val) => val.toFixed(15))
|
||||||
@@ -377,7 +396,8 @@ describe('ol/webgl/WebGLHelper', function () {
|
|||||||
void main(void) {
|
void main(void) {
|
||||||
gl_Position = vec4(u_test, attr3, 0.0, 1.0);
|
gl_Position = vec4(u_test, attr3, 0.0, 1.0);
|
||||||
}`
|
}`
|
||||||
)
|
),
|
||||||
|
SAMPLE_FRAMESTATE
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -404,4 +424,50 @@ describe('ol/webgl/WebGLHelper', function () {
|
|||||||
expect(spy.getCall(2).args[4]).to.eql(5 * bytesPerFloat);
|
expect(spy.getCall(2).args[4]).to.eql(5 * bytesPerFloat);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#applyFrameState', function () {
|
||||||
|
let stubMatrix, stubFloat, stubVec2, stubTime;
|
||||||
|
beforeEach(function () {
|
||||||
|
stubTime = sinon.stub(Date, 'now');
|
||||||
|
stubTime.returns(1000);
|
||||||
|
h = new WebGLHelper();
|
||||||
|
stubMatrix = sinon.stub(h, 'setUniformMatrixValue');
|
||||||
|
stubFloat = sinon.stub(h, 'setUniformFloatValue');
|
||||||
|
stubVec2 = sinon.stub(h, 'setUniformFloatVec2');
|
||||||
|
|
||||||
|
stubTime.returns(2000);
|
||||||
|
h.applyFrameState({...SAMPLE_FRAMESTATE, pixelRatio: 2});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
stubTime.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets the default uniforms according the frame state', function () {
|
||||||
|
expect(stubMatrix.getCall(0).args).to.eql([
|
||||||
|
DefaultUniform.OFFSET_SCALE_MATRIX,
|
||||||
|
[
|
||||||
|
0.9210609940028851, -0.3894183423086505, 0, 0, 0.3894183423086505,
|
||||||
|
0.9210609940028851, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
expect(stubMatrix.getCall(1).args).to.eql([
|
||||||
|
DefaultUniform.OFFSET_ROTATION_MATRIX,
|
||||||
|
[
|
||||||
|
0.9210609940028851, -0.3894183423086505, 0, 0, 0.3894183423086505,
|
||||||
|
0.9210609940028851, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(stubFloat.getCall(0).args).to.eql([DefaultUniform.TIME, 1]);
|
||||||
|
expect(stubFloat.getCall(1).args).to.eql([DefaultUniform.ZOOM, 3]);
|
||||||
|
expect(stubFloat.getCall(2).args).to.eql([DefaultUniform.RESOLUTION, 2]);
|
||||||
|
expect(stubFloat.getCall(3).args).to.eql([DefaultUniform.PIXEL_RATIO, 2]);
|
||||||
|
|
||||||
|
expect(stubVec2.getCall(0).args).to.eql([
|
||||||
|
DefaultUniform.SIZE_PX,
|
||||||
|
[100, 150],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
import {WebGLWorkerMessageType} from '../../../../../src/ol/renderer/webgl/Layer.js';
|
import {WebGLWorkerMessageType} from '../../../../../src/ol/render/webgl/constants.js';
|
||||||
import {create} from '../../../../../src/ol/worker/webgl.js';
|
import {create} from '../../../../../src/ol/worker/webgl.js';
|
||||||
|
import {create as createTransform} from '../../../../../src/ol/transform.js';
|
||||||
|
|
||||||
describe('ol/worker/webgl', function () {
|
describe('ol/worker/webgl', function () {
|
||||||
let worker;
|
let worker;
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
worker = create();
|
worker = create();
|
||||||
|
worker.addEventListener('error', function (error) {
|
||||||
|
expect().fail(error.message);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function () {
|
||||||
@@ -15,35 +19,145 @@ describe('ol/worker/webgl', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('messaging', function () {
|
describe('messaging', function () {
|
||||||
describe('GENERATE_BUFFERS', function () {
|
describe('GENERATE_POINT_BUFFERS', function () {
|
||||||
it('responds with buffer data', function (done) {
|
let responseData;
|
||||||
worker.addEventListener('error', function (error) {
|
beforeEach(function (done) {
|
||||||
expect().fail(error.message);
|
const renderInstructions = Float32Array.from([0, 10, 111, 20, 30, 222]);
|
||||||
});
|
const id = Math.floor(Math.random() * 10000);
|
||||||
|
|
||||||
worker.addEventListener('message', function (event) {
|
|
||||||
expect(event.data.type).to.eql(
|
|
||||||
WebGLWorkerMessageType.GENERATE_BUFFERS
|
|
||||||
);
|
|
||||||
expect(event.data.renderInstructions.byteLength).to.greaterThan(0);
|
|
||||||
expect(event.data.indexBuffer.byteLength).to.greaterThan(0);
|
|
||||||
expect(event.data.vertexBuffer.byteLength).to.greaterThan(0);
|
|
||||||
expect(event.data.testInt).to.be(101);
|
|
||||||
expect(event.data.testString).to.be('abcd');
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
const instructions = new Float32Array(10);
|
|
||||||
|
|
||||||
const message = {
|
const message = {
|
||||||
type: WebGLWorkerMessageType.GENERATE_BUFFERS,
|
type: WebGLWorkerMessageType.GENERATE_POINT_BUFFERS,
|
||||||
renderInstructions: instructions,
|
renderInstructions,
|
||||||
customAttributesCount: 0,
|
customAttributesCount: 1,
|
||||||
testInt: 101,
|
testInt: 101,
|
||||||
testString: 'abcd',
|
testString: 'abcd',
|
||||||
|
id,
|
||||||
};
|
};
|
||||||
|
responseData = null;
|
||||||
worker.postMessage(message);
|
worker.postMessage(message);
|
||||||
|
|
||||||
|
worker.addEventListener('message', function (event) {
|
||||||
|
if (event.data.id === id) {
|
||||||
|
responseData = event.data;
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('responds with info passed in the message', function () {
|
||||||
|
expect(responseData.type).to.eql(
|
||||||
|
WebGLWorkerMessageType.GENERATE_POINT_BUFFERS
|
||||||
|
);
|
||||||
|
expect(responseData.renderInstructions.byteLength).to.greaterThan(0);
|
||||||
|
expect(responseData.testInt).to.be(101);
|
||||||
|
expect(responseData.testString).to.be('abcd');
|
||||||
|
});
|
||||||
|
it('responds with buffer data', function () {
|
||||||
|
const indices = Array.from(new Uint32Array(responseData.indexBuffer));
|
||||||
|
const vertices = Array.from(
|
||||||
|
new Float32Array(responseData.vertexBuffer)
|
||||||
|
);
|
||||||
|
expect(indices).to.eql([0, 1, 3, 1, 2, 3, 4, 5, 7, 5, 6, 7]);
|
||||||
|
expect(vertices).to.eql([
|
||||||
|
0, 10, 0, 111, 0, 10, 1, 111, 0, 10, 2, 111, 0, 10, 3, 111, 20, 30, 0,
|
||||||
|
222, 20, 30, 1, 222, 20, 30, 2, 222, 20, 30, 3, 222,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GENERATE_LINE_STRING_BUFFERS', function () {
|
||||||
|
let responseData;
|
||||||
|
beforeEach(function (done) {
|
||||||
|
const renderInstructions = Float32Array.from([
|
||||||
|
111, 4, 20, 30, 40, 50, 6, 7, 80, 90,
|
||||||
|
]);
|
||||||
|
const id = Math.floor(Math.random() * 10000);
|
||||||
|
const renderInstructionsTransform = createTransform();
|
||||||
|
const message = {
|
||||||
|
type: WebGLWorkerMessageType.GENERATE_LINE_STRING_BUFFERS,
|
||||||
|
renderInstructions,
|
||||||
|
customAttributesCount: 1,
|
||||||
|
testInt: 101,
|
||||||
|
testString: 'abcd',
|
||||||
|
id,
|
||||||
|
renderInstructionsTransform,
|
||||||
|
};
|
||||||
|
responseData = null;
|
||||||
|
worker.postMessage(message);
|
||||||
|
|
||||||
|
worker.addEventListener('message', function (event) {
|
||||||
|
if (event.data.id === id) {
|
||||||
|
responseData = event.data;
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('responds with info passed in the message', function () {
|
||||||
|
expect(responseData.type).to.eql(
|
||||||
|
WebGLWorkerMessageType.GENERATE_LINE_STRING_BUFFERS
|
||||||
|
);
|
||||||
|
expect(responseData.renderInstructions.byteLength).to.greaterThan(0);
|
||||||
|
expect(responseData.testInt).to.be(101);
|
||||||
|
expect(responseData.testString).to.be('abcd');
|
||||||
|
});
|
||||||
|
it('responds with buffer data', function () {
|
||||||
|
const indices = Array.from(new Uint32Array(responseData.indexBuffer));
|
||||||
|
const vertices = Array.from(
|
||||||
|
new Float32Array(responseData.vertexBuffer)
|
||||||
|
);
|
||||||
|
expect(indices).to.eql([
|
||||||
|
0, 1, 2, 1, 3, 2, 4, 5, 6, 5, 7, 6, 8, 9, 10, 9, 11, 10,
|
||||||
|
]);
|
||||||
|
expect(vertices).to.eql([
|
||||||
|
20, 30, 40, 50, 1750000, 111, 20, 30, 40, 50, 101750000, 111, 20, 30,
|
||||||
|
40, 50, 201750000, 111, 20, 30, 40, 50, 301750016, 111, 40, 50, 6, 7,
|
||||||
|
93369248, 111, 40, 50, 6, 7, 193369248, 111, 40, 50, 6, 7, 293369248,
|
||||||
|
111, 40, 50, 6, 7, 393369248, 111, 6, 7, 80, 90, 89, 111, 6, 7, 80,
|
||||||
|
90, 100000088, 111, 6, 7, 80, 90, 200000096, 111, 6, 7, 80, 90,
|
||||||
|
300000096, 111,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GENERATE_POLYGON_BUFFERS', function () {
|
||||||
|
let responseData;
|
||||||
|
beforeEach(function (done) {
|
||||||
|
const renderInstructions = Float32Array.from([
|
||||||
|
1234, 2, 6, 5, 0, 0, 10, 0, 15, 6, 10, 12, 0, 12, 0, 0, 3, 3, 5, 1, 7,
|
||||||
|
3, 5, 5, 3, 3,
|
||||||
|
]);
|
||||||
|
const id = Math.floor(Math.random() * 10000);
|
||||||
|
const message = {
|
||||||
|
type: WebGLWorkerMessageType.GENERATE_POLYGON_BUFFERS,
|
||||||
|
renderInstructions,
|
||||||
|
customAttributesCount: 1,
|
||||||
|
testInt: 101,
|
||||||
|
testString: 'abcd',
|
||||||
|
id,
|
||||||
|
};
|
||||||
|
responseData = null;
|
||||||
|
worker.postMessage(message);
|
||||||
|
|
||||||
|
worker.addEventListener('message', function (event) {
|
||||||
|
if (event.data.id === id) {
|
||||||
|
responseData = event.data;
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('responds with info passed in the message', function () {
|
||||||
|
expect(responseData.type).to.eql(
|
||||||
|
WebGLWorkerMessageType.GENERATE_POLYGON_BUFFERS
|
||||||
|
);
|
||||||
|
expect(responseData.renderInstructions.byteLength).to.greaterThan(0);
|
||||||
|
expect(responseData.testInt).to.be(101);
|
||||||
|
expect(responseData.testString).to.be('abcd');
|
||||||
|
});
|
||||||
|
it('responds with buffer data', function () {
|
||||||
|
const indices = Array.from(new Uint32Array(responseData.indexBuffer));
|
||||||
|
const vertices = Array.from(
|
||||||
|
new Float32Array(responseData.vertexBuffer)
|
||||||
|
);
|
||||||
|
expect(indices).to.have.length(24);
|
||||||
|
expect(vertices).to.have.length(33);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
BIN
test/rendering/cases/webgl-vector/expected.png
Normal file
BIN
test/rendering/cases/webgl-vector/expected.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
44
test/rendering/cases/webgl-vector/main.js
Normal file
44
test/rendering/cases/webgl-vector/main.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import GeoJSON from '../../../../src/ol/format/GeoJSON.js';
|
||||||
|
import Layer from '../../../../src/ol/layer/Layer.js';
|
||||||
|
import Map from '../../../../src/ol/Map.js';
|
||||||
|
import TileLayer from '../../../../src/ol/layer/Tile.js';
|
||||||
|
import VectorSource from '../../../../src/ol/source/Vector.js';
|
||||||
|
import View from '../../../../src/ol/View.js';
|
||||||
|
import WebGLVectorLayerRenderer from '../../../../src/ol/renderer/webgl/VectorLayer.js';
|
||||||
|
import XYZ from '../../../../src/ol/source/XYZ.js';
|
||||||
|
|
||||||
|
class WebGLLayer extends Layer {
|
||||||
|
createRenderer() {
|
||||||
|
return new WebGLVectorLayerRenderer(this, {
|
||||||
|
className: this.getClassName(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const vector = new WebGLLayer({
|
||||||
|
source: new VectorSource({
|
||||||
|
url: '/data/countries.json',
|
||||||
|
format: new GeoJSON(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const raster = new TileLayer({
|
||||||
|
source: new XYZ({
|
||||||
|
url: '/data/tiles/satellite/{z}/{x}/{y}.jpg',
|
||||||
|
transition: 0,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
new Map({
|
||||||
|
layers: [raster, vector],
|
||||||
|
target: 'map',
|
||||||
|
view: new View({
|
||||||
|
center: [0, 0],
|
||||||
|
zoom: 1,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
render({
|
||||||
|
message:
|
||||||
|
'Countries are rendered as grey polygons using webgl and default shaders',
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user