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",
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"earcut": "^2.2.3",
|
||||
"geotiff": "2.0.4",
|
||||
"ol-mapbox-style": "^8.0.5",
|
||||
"pbf": "3.2.1",
|
||||
@@ -3838,6 +3839,11 @@
|
||||
"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": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
@@ -13182,6 +13188,11 @@
|
||||
"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": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
"url": "https://opencollective.com/openlayers"
|
||||
},
|
||||
"dependencies": {
|
||||
"earcut": "^2.2.3",
|
||||
"geotiff": "2.0.4",
|
||||
"ol-mapbox-style": "^8.0.5",
|
||||
"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';
|
||||
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
|
||||
* @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;
|
||||
|
||||
@@ -5,14 +5,11 @@ import BaseVector from '../../layer/BaseVector.js';
|
||||
import VectorEventType from '../../source/VectorEventType.js';
|
||||
import ViewHint from '../../ViewHint.js';
|
||||
import WebGLArrayBuffer from '../../webgl/Buffer.js';
|
||||
import WebGLLayerRenderer, {
|
||||
WebGLWorkerMessageType,
|
||||
colorDecodeId,
|
||||
colorEncodeId,
|
||||
} from './Layer.js';
|
||||
import WebGLLayerRenderer from './Layer.js';
|
||||
import WebGLRenderTarget from '../../webgl/RenderTarget.js';
|
||||
import {ARRAY_BUFFER, DYNAMIC_DRAW, ELEMENT_ARRAY_BUFFER} from '../../webgl.js';
|
||||
import {AttributeType, DefaultUniform} from '../../webgl/Helper.js';
|
||||
import {WebGLWorkerMessageType} from '../../render/webgl/constants.js';
|
||||
import {
|
||||
apply as applyTransform,
|
||||
create as createTransform,
|
||||
@@ -22,6 +19,7 @@ import {
|
||||
} from '../../transform.js';
|
||||
import {assert} from '../../asserts.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 {getUid} from '../../util.js';
|
||||
import {listen, unlistenByKey} from '../../events.js';
|
||||
@@ -294,7 +292,11 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
|
||||
*/
|
||||
this.generateBuffersRun_ = 0;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
this.worker_ = createWebGLWorker();
|
||||
|
||||
this.worker_.addEventListener(
|
||||
'message',
|
||||
/**
|
||||
@@ -303,7 +305,7 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
|
||||
*/
|
||||
function (event) {
|
||||
const received = event.data;
|
||||
if (received.type === WebGLWorkerMessageType.GENERATE_BUFFERS) {
|
||||
if (received.type === WebGLWorkerMessageType.GENERATE_POINT_BUFFERS) {
|
||||
const projectionTransform = received.projectionTransform;
|
||||
if (received.hitDetection) {
|
||||
this.hitVerticesBuffer_.fromArrayBuffer(received.vertexBuffer);
|
||||
@@ -540,7 +542,7 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
|
||||
this.previousExtent_ = frameState.extent.slice();
|
||||
}
|
||||
|
||||
this.helper.useProgram(this.program_);
|
||||
this.helper.useProgram(this.program_, frameState);
|
||||
this.helper.prepareDraw(frameState);
|
||||
|
||||
// write new data
|
||||
@@ -637,9 +639,10 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {import('./Layer').WebGLWorkerGenerateBuffersMessage} */
|
||||
/** @type {import('../../render/webgl/constants.js').WebGLWorkerGenerateBuffersMessage} */
|
||||
const message = {
|
||||
type: WebGLWorkerMessageType.GENERATE_BUFFERS,
|
||||
id: 0,
|
||||
type: WebGLWorkerMessageType.GENERATE_POINT_BUFFERS,
|
||||
renderInstructions: this.renderInstructions_.buffer,
|
||||
customAttributesCount: this.customAttributes.length,
|
||||
};
|
||||
@@ -650,10 +653,11 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
|
||||
this.worker_.postMessage(message, [this.renderInstructions_.buffer]);
|
||||
this.renderInstructions_ = null;
|
||||
|
||||
/** @type {import('./Layer').WebGLWorkerGenerateBuffersMessage} */
|
||||
/** @type {import('../../render/webgl/constants.js').WebGLWorkerGenerateBuffersMessage} */
|
||||
if (this.hitDetectionEnabled_) {
|
||||
const hitMessage = {
|
||||
type: WebGLWorkerMessageType.GENERATE_BUFFERS,
|
||||
id: 0,
|
||||
type: WebGLWorkerMessageType.GENERATE_POINT_BUFFERS,
|
||||
renderInstructions: this.hitRenderInstructions_.buffer,
|
||||
customAttributesCount: 5 + this.customAttributes.length,
|
||||
};
|
||||
@@ -726,7 +730,7 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
|
||||
Math.floor(frameState.size[1] / 2),
|
||||
]);
|
||||
|
||||
this.helper.useProgram(this.hitProgram_);
|
||||
this.helper.useProgram(this.hitProgram_, frameState);
|
||||
this.helper.prepareDrawToRenderTarget(
|
||||
frameState,
|
||||
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);
|
||||
|
||||
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`.
|
||||
* and `OFFSET_ROTATION_MATRIX`.
|
||||
* Names of uniforms made available to all shaders.
|
||||
* Please note: changing these *will* break custom shaders!
|
||||
* @enum {string}
|
||||
*/
|
||||
export const DefaultUniform = {
|
||||
@@ -49,6 +49,8 @@ export const DefaultUniform = {
|
||||
TIME: 'u_time',
|
||||
ZOOM: 'u_zoom',
|
||||
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:
|
||||
* ```js
|
||||
* // 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 fragmentShader = new WebGLFragment(FRAGMENT_SHADER);
|
||||
* 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.
|
||||
@@ -302,8 +305,6 @@ function releaseCanvas(key) {
|
||||
* ```
|
||||
*
|
||||
* For an example usage of this class, refer to {@link module:ol/renderer/webgl/PointsLayer~WebGLPointsLayerRenderer}.
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
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 cache.
|
||||
* @param {import("./Buffer").default} buffer Buffer.
|
||||
* @api
|
||||
*/
|
||||
bindBuffer(buffer) {
|
||||
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
|
||||
* new data to be rendered
|
||||
* @param {import("./Buffer").default} buffer Buffer.
|
||||
* @api
|
||||
*/
|
||||
flushBufferData(buffer) {
|
||||
const gl = this.getGL();
|
||||
@@ -551,7 +550,6 @@ class WebGLHelper extends Disposable {
|
||||
* subsequent draw calls.
|
||||
* @param {import("../PluggableMap.js").FrameState} frameState current frame state
|
||||
* @param {boolean} [opt_disableAlphaBlend] If true, no alpha blending will happen.
|
||||
* @api
|
||||
*/
|
||||
prepareDraw(frameState, opt_disableAlphaBlend) {
|
||||
const gl = this.getGL();
|
||||
@@ -564,8 +562,6 @@ class WebGLHelper extends Disposable {
|
||||
canvas.style.width = size[0] + 'px';
|
||||
canvas.style.height = size[1] + 'px';
|
||||
|
||||
gl.useProgram(this.currentProgram_);
|
||||
|
||||
// loop backwards in post processes list
|
||||
for (let i = this.postProcessPasses_.length - 1; i >= 0; i--) {
|
||||
this.postProcessPasses_[i].init(frameState);
|
||||
@@ -581,10 +577,6 @@ class WebGLHelper extends Disposable {
|
||||
gl.ONE,
|
||||
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,
|
||||
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.
|
||||
* @param {number} start Start index.
|
||||
* @param {number} end End index.
|
||||
* @api
|
||||
*/
|
||||
drawElements(start, end) {
|
||||
const gl = this.getGL();
|
||||
@@ -660,7 +647,6 @@ class WebGLHelper extends Disposable {
|
||||
|
||||
/**
|
||||
* @return {HTMLCanvasElement} Canvas.
|
||||
* @api
|
||||
*/
|
||||
getCanvas() {
|
||||
return this.canvas_;
|
||||
@@ -669,7 +655,6 @@ class WebGLHelper extends Disposable {
|
||||
/**
|
||||
* Get the WebGL rendering context
|
||||
* @return {WebGLRenderingContext} The rendering context.
|
||||
* @api
|
||||
*/
|
||||
getGL() {
|
||||
return this.gl_;
|
||||
@@ -682,6 +667,7 @@ class WebGLHelper extends Disposable {
|
||||
applyFrameState(frameState) {
|
||||
const size = frameState.size;
|
||||
const rotation = frameState.viewState.rotation;
|
||||
const pixelRatio = frameState.pixelRatio;
|
||||
|
||||
const offsetScaleMatrix = resetTransform(this.offsetScaleMatrix_);
|
||||
scaleTransform(offsetScaleMatrix, 2 / size[0], 2 / size[1]);
|
||||
@@ -709,6 +695,8 @@ class WebGLHelper extends Disposable {
|
||||
DefaultUniform.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.
|
||||
* @return {boolean} Changed.
|
||||
* @api
|
||||
* @param {import("../PluggableMap.js").FrameState} frameState Frame state.
|
||||
*/
|
||||
useProgram(program) {
|
||||
if (program == this.currentProgram_) {
|
||||
return false;
|
||||
} else {
|
||||
useProgram(program, frameState) {
|
||||
const gl = this.getGL();
|
||||
gl.useProgram(program);
|
||||
this.currentProgram_ = program;
|
||||
this.uniformLocations_ = {};
|
||||
this.attribLocations_ = {};
|
||||
return true;
|
||||
}
|
||||
this.applyFrameState(frameState);
|
||||
this.applyUniforms(frameState);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -843,7 +828,6 @@ class WebGLHelper extends Disposable {
|
||||
* @param {string} fragmentShaderSource Fragment shader source.
|
||||
* @param {string} vertexShaderSource Vertex shader source.
|
||||
* @return {WebGLProgram} Program
|
||||
* @api
|
||||
*/
|
||||
getProgram(fragmentShaderSource, vertexShaderSource) {
|
||||
const gl = this.getGL();
|
||||
@@ -893,7 +877,6 @@ class WebGLHelper extends Disposable {
|
||||
* Will get the location from the shader or the cache
|
||||
* @param {string} name Uniform name
|
||||
* @return {WebGLUniformLocation} uniformLocation
|
||||
* @api
|
||||
*/
|
||||
getUniformLocation(name) {
|
||||
if (this.uniformLocations_[name] === undefined) {
|
||||
@@ -909,7 +892,6 @@ class WebGLHelper extends Disposable {
|
||||
* Will get the location from the shader or the cache
|
||||
* @param {string} name Attribute name
|
||||
* @return {number} attribLocation
|
||||
* @api
|
||||
*/
|
||||
getAttributeLocation(name) {
|
||||
if (this.attribLocations_[name] === undefined) {
|
||||
@@ -927,7 +909,6 @@ class WebGLHelper extends Disposable {
|
||||
* @param {import("../PluggableMap.js").FrameState} frameState Frame state.
|
||||
* @param {import("../transform").Transform} transform Transform to update.
|
||||
* @return {import("../transform").Transform} The updated transform object.
|
||||
* @api
|
||||
*/
|
||||
makeProjectionTransform(frameState, transform) {
|
||||
const size = frameState.size;
|
||||
@@ -953,12 +934,20 @@ class WebGLHelper extends Disposable {
|
||||
* Give a value for a standard float uniform
|
||||
* @param {string} uniform Uniform name
|
||||
* @param {number} value Value
|
||||
* @api
|
||||
*/
|
||||
setUniformFloatValue(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
|
||||
* @param {string} uniform Uniform name
|
||||
@@ -972,7 +961,6 @@ class WebGLHelper extends Disposable {
|
||||
* Give a value for a standard matrix4 uniform
|
||||
* @param {string} uniform Uniform name
|
||||
* @param {Array<number>} value Matrix value
|
||||
* @api
|
||||
*/
|
||||
setUniformMatrixValue(uniform, value) {
|
||||
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
|
||||
* size/type/order of attributes will most likely break the rendering and throw a WebGL exception.
|
||||
* @param {Array<AttributeDescription>} attributes Ordered list of attributes to read from the buffer
|
||||
* @api
|
||||
*/
|
||||
enableAttributes(attributes) {
|
||||
const stride = computeAttributesStride(attributes);
|
||||
@@ -1056,7 +1043,6 @@ class WebGLHelper extends Disposable {
|
||||
* @param {ImageData|HTMLImageElement|HTMLCanvasElement} [opt_data] Image data/object to bind to the texture
|
||||
* @param {WebGLTexture} [opt_texture] Existing texture to reuse
|
||||
* @return {WebGLTexture} The generated texture
|
||||
* @api
|
||||
*/
|
||||
createTexture(size, opt_data, opt_texture) {
|
||||
const gl = this.getGL();
|
||||
@@ -1103,7 +1089,6 @@ class WebGLHelper extends Disposable {
|
||||
* Compute a stride in bytes based on a 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
|
||||
* @api
|
||||
*/
|
||||
export function computeAttributesStride(attributes) {
|
||||
let stride = 0;
|
||||
|
||||
@@ -2,18 +2,25 @@
|
||||
* A worker that does cpu-heavy tasks related to webgl rendering.
|
||||
* @module ol/worker/webgl
|
||||
*/
|
||||
import {
|
||||
WebGLWorkerMessageType,
|
||||
writePointFeatureToBuffers,
|
||||
} from '../renderer/webgl/Layer.js';
|
||||
import {WebGLWorkerMessageType} from '../render/webgl/constants.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} */
|
||||
const worker = self;
|
||||
|
||||
worker.onmessage = (event) => {
|
||||
const received = event.data;
|
||||
if (received.type === WebGLWorkerMessageType.GENERATE_BUFFERS) {
|
||||
switch (received.type) {
|
||||
case WebGLWorkerMessageType.GENERATE_POINT_BUFFERS: {
|
||||
// This is specific to point features (x, y, index)
|
||||
const baseVertexAttrsCount = 3;
|
||||
const baseInstructionsCount = 2;
|
||||
@@ -41,7 +48,7 @@ worker.onmessage = (event) => {
|
||||
);
|
||||
}
|
||||
|
||||
/** @type {import('../renderer/webgl/Layer').WebGLWorkerGenerateBuffersMessage} */
|
||||
/** @type {import('../render/webgl/constants.js').WebGLWorkerGenerateBuffersMessage} */
|
||||
const message = assign(
|
||||
{
|
||||
vertexBuffer: vertexBuffer.buffer,
|
||||
@@ -56,6 +63,115 @@ worker.onmessage = (event) => {
|
||||
indexBuffer.buffer,
|
||||
renderInstructions.buffer,
|
||||
]);
|
||||
break;
|
||||
}
|
||||
case WebGLWorkerMessageType.GENERATE_LINE_STRING_BUFFERS: {
|
||||
const vertices = [];
|
||||
const indices = [];
|
||||
|
||||
const customAttrsCount = received.customAttributesCount;
|
||||
const instructionsPerVertex = 2;
|
||||
|
||||
const renderInstructions = new Float32Array(received.renderInstructions);
|
||||
let currentInstructionsIndex = 0;
|
||||
|
||||
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 VectorSource from '../../../../../../src/ol/source/Vector.js';
|
||||
import View from '../../../../../../src/ol/View.js';
|
||||
import WebGLLayerRenderer, {
|
||||
colorDecodeId,
|
||||
colorEncodeId,
|
||||
getBlankImageData,
|
||||
writePointFeatureToBuffers,
|
||||
} from '../../../../../../src/ol/renderer/webgl/Layer.js';
|
||||
import WebGLLayerRenderer from '../../../../../../src/ol/renderer/webgl/Layer.js';
|
||||
import {getUid} from '../../../../../../src/ol/util.js';
|
||||
|
||||
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', () => {
|
||||
let target;
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -8,7 +8,7 @@ import View from '../../../../../../src/ol/View.js';
|
||||
import ViewHint from '../../../../../../src/ol/ViewHint.js';
|
||||
import WebGLPointsLayer from '../../../../../../src/ol/layer/WebGLPoints.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 {
|
||||
compose as composeTransform,
|
||||
create as createTransform,
|
||||
@@ -156,7 +156,7 @@ describe('ol/renderer/webgl/PointsLayer', function () {
|
||||
const attributePerVertex = 3;
|
||||
|
||||
renderer.worker_.addEventListener('message', function (event) {
|
||||
if (event.data.type !== WebGLWorkerMessageType.GENERATE_BUFFERS) {
|
||||
if (event.data.type !== WebGLWorkerMessageType.GENERATE_POINT_BUFFERS) {
|
||||
return;
|
||||
}
|
||||
expect(renderer.verticesBuffer_.getArray().length).to.eql(
|
||||
@@ -192,7 +192,7 @@ describe('ol/renderer/webgl/PointsLayer', function () {
|
||||
const attributePerVertex = 8;
|
||||
|
||||
renderer.worker_.addEventListener('message', function (event) {
|
||||
if (event.data.type !== WebGLWorkerMessageType.GENERATE_BUFFERS) {
|
||||
if (event.data.type !== WebGLWorkerMessageType.GENERATE_POINT_BUFFERS) {
|
||||
return;
|
||||
}
|
||||
if (!renderer.hitVerticesBuffer_.getArray()) {
|
||||
@@ -231,7 +231,7 @@ describe('ol/renderer/webgl/PointsLayer', function () {
|
||||
renderer.prepareFrame(frameState);
|
||||
|
||||
renderer.worker_.addEventListener('message', function (event) {
|
||||
if (event.data.type !== WebGLWorkerMessageType.GENERATE_BUFFERS) {
|
||||
if (event.data.type !== WebGLWorkerMessageType.GENERATE_POINT_BUFFERS) {
|
||||
return;
|
||||
}
|
||||
const attributePerVertex = 3;
|
||||
@@ -627,14 +627,14 @@ describe('ol/renderer/webgl/PointsLayer', function () {
|
||||
beforeEach(function () {
|
||||
source = new VectorSource({
|
||||
features: new GeoJSON().readFeatures({
|
||||
'type': 'FeatureCollection',
|
||||
'features': [
|
||||
type: 'FeatureCollection',
|
||||
features: [
|
||||
{
|
||||
'type': 'Feature',
|
||||
'properties': {},
|
||||
'geometry': {
|
||||
'type': 'Point',
|
||||
'coordinates': [13, 52],
|
||||
type: 'Feature',
|
||||
properties: {},
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
coordinates: [13, 52],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -57,6 +57,16 @@ const INVALID_FRAGMENT_SHADER = `
|
||||
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 () {
|
||||
let h;
|
||||
afterEach(function () {
|
||||
@@ -117,7 +127,10 @@ describe('ol/webgl/WebGLHelper', function () {
|
||||
u_test4: createTransform(),
|
||||
},
|
||||
});
|
||||
h.useProgram(h.getProgram(FRAGMENT_SHADER, VERTEX_SHADER));
|
||||
h.useProgram(
|
||||
h.getProgram(FRAGMENT_SHADER, VERTEX_SHADER),
|
||||
SAMPLE_FRAMESTATE
|
||||
);
|
||||
h.prepareDraw({
|
||||
pixelRatio: 2,
|
||||
size: [50, 80],
|
||||
@@ -142,6 +155,16 @@ describe('ol/webgl/WebGLHelper', function () {
|
||||
h.uniformLocations_[DefaultUniform.OFFSET_SCALE_MATRIX]
|
||||
).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 () {
|
||||
@@ -164,7 +187,7 @@ describe('ol/webgl/WebGLHelper', function () {
|
||||
h = new WebGLHelper();
|
||||
|
||||
p = h.getProgram(FRAGMENT_SHADER, VERTEX_SHADER);
|
||||
h.useProgram(p);
|
||||
h.useProgram(p, SAMPLE_FRAMESTATE);
|
||||
});
|
||||
|
||||
it('has saved the program', function () {
|
||||
@@ -209,34 +232,30 @@ describe('ol/webgl/WebGLHelper', function () {
|
||||
});
|
||||
|
||||
describe('#makeProjectionTransform', function () {
|
||||
let frameState;
|
||||
beforeEach(function () {
|
||||
h = new WebGLHelper();
|
||||
|
||||
frameState = {
|
||||
size: [100, 150],
|
||||
viewState: {
|
||||
rotation: 0.4,
|
||||
resolution: 2,
|
||||
center: [10, 20],
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it('gives out the correct transform', function () {
|
||||
const scaleX = 2 / frameState.size[0] / frameState.viewState.resolution;
|
||||
const scaleY = 2 / frameState.size[1] / frameState.viewState.resolution;
|
||||
const scaleX =
|
||||
2 /
|
||||
SAMPLE_FRAMESTATE.size[0] /
|
||||
SAMPLE_FRAMESTATE.viewState.resolution;
|
||||
const scaleY =
|
||||
2 /
|
||||
SAMPLE_FRAMESTATE.size[1] /
|
||||
SAMPLE_FRAMESTATE.viewState.resolution;
|
||||
const given = createTransform();
|
||||
const expected = createTransform();
|
||||
scaleTransform(expected, scaleX, scaleY);
|
||||
rotateTransform(expected, -frameState.viewState.rotation);
|
||||
rotateTransform(expected, -SAMPLE_FRAMESTATE.viewState.rotation);
|
||||
translateTransform(
|
||||
expected,
|
||||
-frameState.viewState.center[0],
|
||||
-frameState.viewState.center[1]
|
||||
-SAMPLE_FRAMESTATE.viewState.center[0],
|
||||
-SAMPLE_FRAMESTATE.viewState.center[1]
|
||||
);
|
||||
|
||||
h.makeProjectionTransform(frameState, given);
|
||||
h.makeProjectionTransform(SAMPLE_FRAMESTATE, given);
|
||||
|
||||
expect(given.map((val) => val.toFixed(15))).to.eql(
|
||||
expected.map((val) => val.toFixed(15))
|
||||
@@ -377,7 +396,8 @@ describe('ol/webgl/WebGLHelper', function () {
|
||||
void main(void) {
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
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 as createTransform} from '../../../../../src/ol/transform.js';
|
||||
|
||||
describe('ol/worker/webgl', function () {
|
||||
let worker;
|
||||
beforeEach(function () {
|
||||
worker = create();
|
||||
worker.addEventListener('error', function (error) {
|
||||
expect().fail(error.message);
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
@@ -15,35 +19,145 @@ describe('ol/worker/webgl', function () {
|
||||
});
|
||||
|
||||
describe('messaging', function () {
|
||||
describe('GENERATE_BUFFERS', function () {
|
||||
it('responds with buffer data', function (done) {
|
||||
worker.addEventListener('error', function (error) {
|
||||
expect().fail(error.message);
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
describe('GENERATE_POINT_BUFFERS', function () {
|
||||
let responseData;
|
||||
beforeEach(function (done) {
|
||||
const renderInstructions = Float32Array.from([0, 10, 111, 20, 30, 222]);
|
||||
const id = Math.floor(Math.random() * 10000);
|
||||
const message = {
|
||||
type: WebGLWorkerMessageType.GENERATE_BUFFERS,
|
||||
renderInstructions: instructions,
|
||||
customAttributesCount: 0,
|
||||
type: WebGLWorkerMessageType.GENERATE_POINT_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_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