Files
openlayers/test/spec/ol/renderer/webgl/pointslayer.test.js
2019-09-25 17:22:36 +02:00

362 lines
12 KiB
JavaScript

import Feature from '../../../../../src/ol/Feature.js';
import Point from '../../../../../src/ol/geom/Point.js';
import VectorLayer from '../../../../../src/ol/layer/Vector.js';
import VectorSource from '../../../../../src/ol/source/Vector.js';
import WebGLPointsLayerRenderer from '../../../../../src/ol/renderer/webgl/PointsLayer.js';
import {get as getProjection} from '../../../../../src/ol/proj.js';
import ViewHint from '../../../../../src/ol/ViewHint.js';
import {WebGLWorkerMessageType} from '../../../../../src/ol/renderer/webgl/Layer.js';
import {compose as composeTransform, create as createTransform} from '../../../../../src/ol/transform.js';
const baseFrameState = {
viewHints: [],
viewState: {
projection: getProjection('EPSG:3857'),
resolution: 1,
rotation: 0,
center: [0, 0]
},
layerStatesArray: [{}],
layerIndex: 0,
pixelRatio: 1
};
const simpleVertexShader = `
precision mediump float;
uniform mat4 u_projectionMatrix;
uniform mat4 u_offsetScaleMatrix;
attribute vec2 a_position;
attribute float a_index;
void main(void) {
mat4 offsetMatrix = u_offsetScaleMatrix;
float offsetX = a_index == 0.0 || a_index == 3.0 ? -2.0 : 2.0;
float offsetY = a_index == 0.0 || a_index == 1.0 ? -2.0 : 2.0;
vec4 offsets = offsetMatrix * vec4(offsetX, offsetY, 0.0, 0.0);
gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets;
}`;
const simpleFragmentShader = `
precision mediump float;
void main(void) {
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}`;
// these shaders support hit detection
// they have a built-in size value of 4
const hitVertexShader = `
precision mediump float;
uniform mat4 u_projectionMatrix;
uniform mat4 u_offsetScaleMatrix;
attribute vec2 a_position;
attribute float a_index;
attribute vec4 a_hitColor;
varying vec4 v_hitColor;
void main(void) {
mat4 offsetMatrix = u_offsetScaleMatrix;
float offsetX = a_index == 0.0 || a_index == 3.0 ? -2.0 : 2.0;
float offsetY = a_index == 0.0 || a_index == 1.0 ? -2.0 : 2.0;
vec4 offsets = offsetMatrix * vec4(offsetX, offsetY, 0.0, 0.0);
gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets;
v_hitColor = a_hitColor;
}`;
const hitFragmentShader = `
precision mediump float;
varying vec4 v_hitColor;
void main(void) {
gl_FragColor = v_hitColor;
}`;
describe('ol.renderer.webgl.PointsLayer', function() {
describe('constructor', function() {
let target;
beforeEach(function() {
target = document.createElement('div');
target.style.width = '256px';
target.style.height = '256px';
document.body.appendChild(target);
});
afterEach(function() {
document.body.removeChild(target);
});
it('creates a new instance', function() {
const layer = new VectorLayer({
source: new VectorSource()
});
const renderer = new WebGLPointsLayerRenderer(layer, {
vertexShader: simpleVertexShader,
fragmentShader: simpleFragmentShader
});
expect(renderer).to.be.a(WebGLPointsLayerRenderer);
});
});
describe('#prepareFrame', function() {
let layer, renderer, frameState;
beforeEach(function() {
layer = new VectorLayer({
source: new VectorSource()
});
renderer = new WebGLPointsLayerRenderer(layer, {
vertexShader: simpleVertexShader,
fragmentShader: simpleFragmentShader,
hitVertexShader: hitVertexShader,
hitFragmentShader: hitFragmentShader
});
frameState = Object.assign({
size: [2, 2],
extent: [-100, -100, 100, 100]
}, baseFrameState);
});
it('calls WebGlHelper#prepareDraw', function() {
const spy = sinon.spy(renderer.helper, 'prepareDraw');
renderer.prepareFrame(frameState);
expect(spy.called).to.be(true);
});
it('fills up a buffer with 2 triangles per point', function(done) {
layer.getSource().addFeature(new Feature({
geometry: new Point([10, 20])
}));
layer.getSource().addFeature(new Feature({
geometry: new Point([30, 40])
}));
renderer.prepareFrame(frameState);
const attributePerVertex = 3;
renderer.worker_.addEventListener('message', function(event) {
if (event.data.type !== WebGLWorkerMessageType.GENERATE_BUFFERS) {
return;
}
expect(renderer.verticesBuffer_.getArray().length).to.eql(2 * 4 * attributePerVertex);
expect(renderer.indicesBuffer_.getArray().length).to.eql(2 * 6);
expect(renderer.verticesBuffer_.getArray()[0]).to.eql(10);
expect(renderer.verticesBuffer_.getArray()[1]).to.eql(20);
expect(renderer.verticesBuffer_.getArray()[4 * attributePerVertex + 0]).to.eql(30);
expect(renderer.verticesBuffer_.getArray()[4 * attributePerVertex + 1]).to.eql(40);
done();
});
});
it('fills up the hit render buffer with 2 triangles per point', function(done) {
layer.getSource().addFeature(new Feature({
geometry: new Point([10, 20])
}));
layer.getSource().addFeature(new Feature({
geometry: new Point([30, 40])
}));
renderer.prepareFrame(frameState);
const attributePerVertex = 8;
renderer.worker_.addEventListener('message', function(event) {
if (event.data.type !== WebGLWorkerMessageType.GENERATE_BUFFERS) {
return;
}
if (!renderer.hitVerticesBuffer_.getArray()) {
return;
}
expect(renderer.hitVerticesBuffer_.getArray().length).to.eql(2 * 4 * attributePerVertex);
expect(renderer.indicesBuffer_.getArray().length).to.eql(2 * 6);
expect(renderer.hitVerticesBuffer_.getArray()[0]).to.eql(10);
expect(renderer.hitVerticesBuffer_.getArray()[1]).to.eql(20);
expect(renderer.hitVerticesBuffer_.getArray()[4 * attributePerVertex + 0]).to.eql(30);
expect(renderer.hitVerticesBuffer_.getArray()[4 * attributePerVertex + 1]).to.eql(40);
done();
});
});
it('clears the buffers when the features are gone', function(done) {
const source = layer.getSource();
source.addFeature(new Feature({
geometry: new Point([10, 20])
}));
source.removeFeature(source.getFeatures()[0]);
source.addFeature(new Feature({
geometry: new Point([10, 20])
}));
renderer.prepareFrame(frameState);
renderer.worker_.addEventListener('message', function(event) {
if (event.data.type !== WebGLWorkerMessageType.GENERATE_BUFFERS) {
return;
}
const attributePerVertex = 3;
expect(renderer.verticesBuffer_.getArray().length).to.eql(4 * attributePerVertex);
expect(renderer.indicesBuffer_.getArray().length).to.eql(6);
done();
});
});
it('rebuilds the buffers only when not interacting or animating', function() {
const spy = sinon.spy(renderer, 'rebuildBuffers_');
frameState.viewHints[ViewHint.INTERACTING] = 1;
frameState.viewHints[ViewHint.ANIMATING] = 0;
renderer.prepareFrame(frameState);
expect(spy.called).to.be(false);
frameState.viewHints[ViewHint.INTERACTING] = 0;
frameState.viewHints[ViewHint.ANIMATING] = 1;
renderer.prepareFrame(frameState);
expect(spy.called).to.be(false);
frameState.viewHints[ViewHint.INTERACTING] = 0;
frameState.viewHints[ViewHint.ANIMATING] = 0;
renderer.prepareFrame(frameState);
expect(spy.called).to.be(true);
});
it('rebuilds the buffers only when the frame extent changed', function() {
const spy = sinon.spy(renderer, 'rebuildBuffers_');
renderer.prepareFrame(frameState);
expect(spy.callCount).to.be(1);
renderer.prepareFrame(frameState);
expect(spy.callCount).to.be(1);
frameState.extent = [10, 20, 30, 40];
renderer.prepareFrame(frameState);
expect(spy.callCount).to.be(2);
});
});
describe('#forEachFeatureAtCoordinate', function() {
let layer, renderer, feature, feature2;
beforeEach(function() {
feature = new Feature({geometry: new Point([0, 0]), id: 1});
feature2 = new Feature({geometry: new Point([14, 14]), id: 2});
layer = new VectorLayer({
source: new VectorSource({
features: [feature, feature2]
})
});
renderer = new WebGLPointsLayerRenderer(layer, {
vertexShader: simpleVertexShader,
fragmentShader: simpleFragmentShader,
hitVertexShader: hitVertexShader,
hitFragmentShader: hitFragmentShader
});
});
it('correctly hit detects a feature', function(done) {
const transform = composeTransform(createTransform(), 20, 20, 1, -1, 0, 0, 0);
const frameState = Object.assign({
extent: [-20, -20, 20, 20],
size: [40, 40],
coordinateToPixelTransform: transform
}, baseFrameState);
let found;
const cb = function(feature) {
found = feature;
};
renderer.prepareFrame(frameState);
renderer.worker_.addEventListener('message', function() {
if (!renderer.hitRenderInstructions_) {
return;
}
renderer.prepareFrame(frameState);
renderer.renderFrame(frameState);
function checkHit(x, y, expected) {
found = null;
renderer.forEachFeatureAtCoordinate([x, y], frameState, 0, cb, null);
expect(found).to.be(expected);
}
checkHit(0, 0, feature);
checkHit(1, -1, feature);
checkHit(-2, 2, feature);
checkHit(2, 0, null);
checkHit(1, -3, null);
checkHit(14, 14, feature2);
checkHit(15, 13, feature2);
checkHit(12, 16, feature2);
checkHit(16, 14, null);
checkHit(13, 11, null);
done();
});
});
it('correctly hit detects with pixelratio != 1', function(done) {
const transform = composeTransform(createTransform(), 20, 20, 1, -1, 0, 0, 0);
const frameState = Object.assign({
pixelRatio: 3,
extent: [-20, -20, 20, 20],
size: [40, 40],
coordinateToPixelTransform: transform
}, baseFrameState);
let found;
const cb = function(feature) {
found = feature;
};
renderer.prepareFrame(frameState);
renderer.worker_.addEventListener('message', function() {
if (!renderer.hitRenderInstructions_) {
return;
}
renderer.prepareFrame(frameState);
renderer.renderFrame(frameState);
function checkHit(x, y, expected) {
found = null;
renderer.forEachFeatureAtCoordinate([x, y], frameState, 0, cb, null);
expect(found).to.be(expected);
}
checkHit(0, 0, feature);
checkHit(1, -1, feature);
checkHit(-2, 2, feature);
checkHit(2, 0, null);
checkHit(1, -3, null);
checkHit(14, 14, feature2);
checkHit(15, 13, feature2);
checkHit(12, 16, feature2);
checkHit(16, 14, null);
checkHit(13, 11, null);
done();
});
});
});
describe('#disposeInternal', function() {
it('terminates the worker and calls dispose on the helper', function() {
const layer = new VectorLayer({
source: new VectorSource()
});
const renderer = new WebGLPointsLayerRenderer(layer, {
vertexShader: simpleVertexShader,
fragmentShader: simpleFragmentShader
});
const spyHelper = sinon.spy(renderer.helper, 'disposeInternal');
const spyWorker = sinon.spy(renderer.worker_, 'terminate');
renderer.disposeInternal();
expect(spyHelper.called).to.be(true);
expect(spyWorker.called).to.be(true);
});
});
});