WebGLPointsLayer wrapX support - partially addressing #11131

WebGLPointsLayer renderFrame and renderHitDetection will now draw
multiple worlds if the source and projection support wrapX.

Hit detection needs additional improvement. It stops working after
more than one wrap around the world. If 0 is the middle world, then
the hit detection works for worlds -1, 0, and -1, but not for worlds
> 2 or < -2.

The example has hit detection enabled, demonstrated with a colour
change on hover for the circle styles. When moving the mouse, the hit
detection is unreliable and flickers on/off. This needs improvement.

The webgl-points renderer test has been updated.
This commit is contained in:
Tomas Burleigh
2022-04-01 13:35:13 +13:00
parent 10fb55b9e6
commit d524d46969
4 changed files with 66 additions and 17 deletions

View File

@@ -9,6 +9,7 @@ import WebGLPointsLayer from '../src/ol/layer/WebGLPoints.js';
const vectorSource = new Vector({ const vectorSource = new Vector({
url: 'data/geojson/world-cities.geojson', url: 'data/geojson/world-cities.geojson',
format: new GeoJSON(), format: new GeoJSON(),
wrapX: true,
}); });
const predefinedStyles = { const predefinedStyles = {
@@ -79,7 +80,7 @@ const predefinedStyles = {
2000000, 2000000,
28, 28,
], ],
color: '#006688', color: ['match', ['get', 'hover'], 1, '#ff3f3f', '#006688'],
rotateWithView: false, rotateWithView: false,
offset: [0, 0], offset: [0, 0],
opacity: [ opacity: [
@@ -97,7 +98,7 @@ const predefinedStyles = {
symbol: { symbol: {
symbolType: 'circle', symbolType: 'circle',
size: ['interpolate', ['exponential', 2.5], ['zoom'], 2, 1, 14, 32], size: ['interpolate', ['exponential', 2.5], ['zoom'], 2, 1, 14, 32],
color: '#240572', color: ['match', ['get', 'hover'], 1, '#ff3f3f', '#006688'],
offset: [0, 0], offset: [0, 0],
opacity: 0.95, opacity: 0.95,
}, },
@@ -160,12 +161,27 @@ const map = new Map({
let literalStyle; let literalStyle;
let pointsLayer; let pointsLayer;
let selected = null;
map.on('pointermove', function (ev) {
if (selected !== null) {
selected.set('hover', 0);
selected = null;
}
map.forEachFeatureAtPixel(ev.pixel, function (feature) {
feature.set('hover', 1);
selected = feature;
return true;
});
});
function refreshLayer(newStyle) { function refreshLayer(newStyle) {
const previousLayer = pointsLayer; const previousLayer = pointsLayer;
pointsLayer = new WebGLPointsLayer({ pointsLayer = new WebGLPointsLayer({
source: vectorSource, source: vectorSource,
style: newStyle, style: newStyle,
disableHitDetection: true,
}); });
map.addLayer(pointsLayer); map.addLayer(pointsLayer);

View File

@@ -19,9 +19,10 @@ import {
create as createTransform, create as createTransform,
makeInverse as makeInverseTransform, makeInverse as makeInverseTransform,
multiply as multiplyTransform, multiply as multiplyTransform,
translate as translateTransform,
} from '../../transform.js'; } from '../../transform.js';
import {assert} from '../../asserts.js'; import {assert} from '../../asserts.js';
import {buffer, createEmpty, equals} from '../../extent.js'; import {buffer, createEmpty, equals, getWidth} from '../../extent.js';
import {create as createWebGLWorker} from '../../worker/webgl.js'; import {create as createWebGLWorker} from '../../worker/webgl.js';
import {getUid} from '../../util.js'; import {getUid} from '../../util.js';
import {listen, unlistenByKey} from '../../events.js'; import {listen, unlistenByKey} from '../../events.js';
@@ -461,8 +462,36 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
const gl = this.helper.getGL(); const gl = this.helper.getGL();
this.preRender(gl, frameState); this.preRender(gl, frameState);
const projection = frameState.viewState.projection;
const layer = this.getLayer();
const vectorSource = layer.getSource();
// FIXME fix hit detection isn't reliable when rendering multiple worlds
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;
const startWorld = multiWorld
? Math.floor((extent[0] - projectionExtent[0]) / worldWidth)
: 0;
let world = startWorld;
const renderCount = this.indicesBuffer_.getSize(); const renderCount = this.indicesBuffer_.getSize();
this.helper.drawElements(0, renderCount);
do {
// apply the current projection transform with the invert of the one used to fill buffers
this.helper.makeProjectionTransform(frameState, this.currentTransform_);
translateTransform(this.currentTransform_, world * worldWidth, 0);
multiplyTransform(this.currentTransform_, this.invertRenderTransform_);
this.helper.applyUniforms(frameState);
this.helper.drawElements(0, renderCount);
} while (++world < endWorld);
this.helper.finalizeDraw( this.helper.finalizeDraw(
frameState, frameState,
this.dispatchPreComposeEvent, this.dispatchPreComposeEvent,
@@ -471,7 +500,7 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
const canvas = this.helper.getCanvas(); const canvas = this.helper.getCanvas();
if (this.hitDetectionEnabled_) { if (this.hitDetectionEnabled_) {
this.renderHitDetection(frameState); this.renderHitDetection(frameState, startWorld, endWorld, worldWidth);
this.hitRenderTarget_.clearCachedData(); this.hitRenderTarget_.clearCachedData();
} }
@@ -512,17 +541,12 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
this.previousExtent_ = frameState.extent.slice(); this.previousExtent_ = frameState.extent.slice();
} }
// apply the current projection transform with the invert of the one used to fill buffers
this.helper.makeProjectionTransform(frameState, this.currentTransform_);
multiplyTransform(this.currentTransform_, this.invertRenderTransform_);
this.helper.useProgram(this.program_); this.helper.useProgram(this.program_);
this.helper.prepareDraw(frameState); this.helper.prepareDraw(frameState);
// write new data // write new data
this.helper.bindBuffer(this.verticesBuffer_); this.helper.bindBuffer(this.verticesBuffer_);
this.helper.bindBuffer(this.indicesBuffer_); this.helper.bindBuffer(this.indicesBuffer_);
this.helper.enableAttributes(this.attributes); this.helper.enableAttributes(this.attributes);
return true; return true;
@@ -686,13 +710,18 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
/** /**
* Render the hit detection data to the corresponding render target * Render the hit detection data to the corresponding render target
* @param {import("../../PluggableMap.js").FrameState} frameState current frame state * @param {import("../../PluggableMap.js").FrameState} frameState current frame state
* @param {number} startWorld the world to render in the first iteration
* @param {number} endWorld the last world to render
* @param {number} worldWidth the width of the worlds being rendered
*/ */
renderHitDetection(frameState) { renderHitDetection(frameState, startWorld, endWorld, worldWidth) {
// skip render entirely if vertex buffers not ready/generated yet // skip render entirely if vertex buffers not ready/generated yet
if (!this.hitVerticesBuffer_.getSize()) { if (!this.hitVerticesBuffer_.getSize()) {
return; return;
} }
let world = startWorld;
this.hitRenderTarget_.setSize([ this.hitRenderTarget_.setSize([
Math.floor(frameState.size[0] / 2), Math.floor(frameState.size[0] / 2),
Math.floor(frameState.size[1] / 2), Math.floor(frameState.size[1] / 2),
@@ -707,11 +736,17 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
this.helper.bindBuffer(this.hitVerticesBuffer_); this.helper.bindBuffer(this.hitVerticesBuffer_);
this.helper.bindBuffer(this.indicesBuffer_); this.helper.bindBuffer(this.indicesBuffer_);
this.helper.enableAttributes(this.hitDetectionAttributes); this.helper.enableAttributes(this.hitDetectionAttributes);
const renderCount = this.indicesBuffer_.getSize(); do {
this.helper.drawElements(0, renderCount); this.helper.makeProjectionTransform(frameState, this.currentTransform_);
translateTransform(this.currentTransform_, world * worldWidth, 0);
multiplyTransform(this.currentTransform_, this.invertRenderTransform_);
this.helper.applyUniforms(frameState);
const renderCount = this.indicesBuffer_.getSize();
this.helper.drawElements(0, renderCount);
} while (++world < endWorld);
} }
/** /**

View File

@@ -678,7 +678,6 @@ class WebGLHelper extends Disposable {
/** /**
* Sets the default matrix uniforms for a given frame state. This is called internally in `prepareDraw`. * Sets the default matrix uniforms for a given frame state. This is called internally in `prepareDraw`.
* @param {import("../PluggableMap.js").FrameState} frameState Frame state. * @param {import("../PluggableMap.js").FrameState} frameState Frame state.
* @private
*/ */
applyFrameState(frameState) { applyFrameState(frameState) {
const size = frameState.size; const size = frameState.size;
@@ -715,7 +714,6 @@ class WebGLHelper extends Disposable {
/** /**
* Sets the custom uniforms based on what was given in the constructor. This is called internally in `prepareDraw`. * Sets the custom uniforms based on what was given in the constructor. This is called internally in `prepareDraw`.
* @param {import("../PluggableMap.js").FrameState} frameState Frame state. * @param {import("../PluggableMap.js").FrameState} frameState Frame state.
* @private
*/ */
applyUniforms(frameState) { applyUniforms(frameState) {
const gl = this.getGL(); const gl = this.getGL();

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 98 KiB