From bc71a362929a257cf8c40a1f90e03573c2fff39c Mon Sep 17 00:00:00 2001 From: tsauerwein Date: Fri, 19 Dec 2014 14:47:52 +0100 Subject: [PATCH] Implement hasFeatureAtPixel for webgl --- src/ol/render/webgl/webglreplay.js | 115 ++++++++++++++++-- src/ol/renderer/webgl/webglmaprenderer.js | 47 +++++++ .../webgl/webglvectorlayerrenderer.js | 22 ++++ 3 files changed, 176 insertions(+), 8 deletions(-) diff --git a/src/ol/render/webgl/webglreplay.js b/src/ol/render/webgl/webglreplay.js index 36ff9183f5..83ad70c590 100644 --- a/src/ol/render/webgl/webglreplay.js +++ b/src/ol/render/webgl/webglreplay.js @@ -557,6 +557,7 @@ ol.render.webgl.ImageReplay.prototype.createTextures_ = * @param {number} saturation Global saturation. * @param {Object} skippedFeaturesHash Ids of features to skip. * @param {function(ol.Feature): T|undefined} featureCallback Feature callback. + * @param {boolean} oneByOne Draw features one-by-one for the hit-detecion. * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting * this extent are checked. * @return {T|undefined} Callback result. @@ -565,7 +566,7 @@ ol.render.webgl.ImageReplay.prototype.createTextures_ = ol.render.webgl.ImageReplay.prototype.replay = function(context, center, resolution, rotation, size, pixelRatio, opacity, brightness, contrast, hue, saturation, skippedFeaturesHash, - featureCallback, opt_hitExtent) { + featureCallback, oneByOne, opt_hitExtent) { var gl = context.getGL(); // bind the vertices buffer @@ -672,7 +673,7 @@ ol.render.webgl.ImageReplay.prototype.replay = function(context, } else { // draw feature by feature for the hit-detection result = this.drawHitDetectionReplay_(gl, context, featureCallback, - opt_hitExtent); + oneByOne, opt_hitExtent); } // disable the vertex attrib arrays @@ -715,21 +716,76 @@ ol.render.webgl.ImageReplay.prototype.drawReplay_ = * @param {WebGLRenderingContext} gl gl. * @param {ol.webgl.Context} context Context. * @param {function(ol.Feature): T|undefined} featureCallback Feature callback. + * @param {boolean} oneByOne Draw features one-by-one for the hit-detecion. * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting * this extent are checked. * @return {T|undefined} Callback result. * @template T */ ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplay_ = - function(gl, context, featureCallback, opt_hitExtent) { + function(gl, context, featureCallback, oneByOne, opt_hitExtent) { goog.asserts.assert(this.hitDetectionTextures_.length === this.hitDetectionGroupIndices_.length); var elementType = context.hasOESElementIndexUint ? goog.webgl.UNSIGNED_INT : goog.webgl.UNSIGNED_SHORT; var elementSize = context.hasOESElementIndexUint ? 4 : 2; - var i, groupStart, groupEnd, numItems, start, end, feature; + if (!oneByOne) { + // draw all hit-detection features in "once" (by texture group) + return this.drawHitDetectionReplayAll_(gl, context, featureCallback, + elementType, elementSize); + } else { + // draw hit-detection features one by one + return this.drawHitDetectionReplayOneByOne_(gl, context, featureCallback, + elementType, elementSize, opt_hitExtent); + } +}; + + +/** + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {function(ol.Feature): T|undefined} featureCallback Feature callback. + * @param {number} elementType Element type. + * @param {number} elementSize Element size. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplayAll_ = + function(gl, context, featureCallback, elementType, elementSize) { + var i, ii, start; + for (i = 0, ii = this.hitDetectionTextures_.length, start = 0; i < ii; ++i) { + gl.bindTexture(goog.webgl.TEXTURE_2D, this.hitDetectionTextures_[i]); + var end = this.hitDetectionGroupIndices_[i]; + var numItems = end - start; + var offsetInBytes = start * elementSize; + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + gl.drawElements(goog.webgl.TRIANGLES, numItems, elementType, offsetInBytes); + start = end; + } + return featureCallback(null); +}; + + +/** + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {function(ol.Feature): T|undefined} featureCallback Feature callback. + * @param {number} elementType Element type. + * @param {number} elementSize Element size. + * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting + * this extent are checked. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplayOneByOne_ = + function(gl, context, featureCallback, elementType, elementSize, + opt_hitExtent) { + var i, groupStart, groupEnd, numItems, featureInfo, start, end, feature; var featureIndex = this.startIndices_.length - 1; + for (i = this.hitDetectionTextures_.length - 1; i >= 0; --i) { gl.bindTexture(goog.webgl.TEXTURE_2D, this.hitDetectionTextures_[i]); groupStart = (i > 0) ? this.hitDetectionGroupIndices_[i - 1] : 0; @@ -957,7 +1013,7 @@ ol.render.webgl.ReplayGroup.prototype.replay = function(context, replay.replay(context, center, resolution, rotation, size, pixelRatio, opacity, brightness, contrast, hue, saturation, skippedFeaturesHash, - undefined); + undefined, false); } } }; @@ -978,6 +1034,7 @@ ol.render.webgl.ReplayGroup.prototype.replay = function(context, * @param {number} saturation Global saturation. * @param {Object} skippedFeaturesHash Ids of features to skip. * @param {function(ol.Feature): T|undefined} featureCallback Feature callback. + * @param {boolean} oneByOne Draw features one-by-one for the hit-detecion. * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting * this extent are checked. * @return {T|undefined} Callback result. @@ -986,7 +1043,7 @@ ol.render.webgl.ReplayGroup.prototype.replay = function(context, ol.render.webgl.ReplayGroup.prototype.replayHitDetection_ = function(context, center, resolution, rotation, size, pixelRatio, opacity, brightness, contrast, hue, saturation, skippedFeaturesHash, - featureCallback, opt_hitExtent) { + featureCallback, oneByOne, opt_hitExtent) { var i, replay, result; for (i = ol.render.REPLAY_ORDER.length - 1; i >= 0; --i) { replay = this.replays_[ol.render.REPLAY_ORDER[i]]; @@ -994,7 +1051,7 @@ ol.render.webgl.ReplayGroup.prototype.replayHitDetection_ = function(context, result = replay.replay(context, center, resolution, rotation, size, pixelRatio, opacity, brightness, contrast, hue, saturation, - skippedFeaturesHash, featureCallback, opt_hitExtent); + skippedFeaturesHash, featureCallback, oneByOne, opt_hitExtent); if (result) { return result; } @@ -1061,7 +1118,49 @@ ol.render.webgl.ReplayGroup.prototype.forEachFeatureAtPixel = function( return result; } } - }, hitExtent); + }, true, hitExtent); +}; + + +/** + * @param {ol.webgl.Context} context Context. + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {ol.Size} size Size. + * @param {number} pixelRatio Pixel ratio. + * @param {number} opacity Global opacity. + * @param {number} brightness Global brightness. + * @param {number} contrast Global contrast. + * @param {number} hue Global hue. + * @param {number} saturation Global saturation. + * @param {Object} skippedFeaturesHash Ids of features to skip. + * @param {ol.Coordinate} coordinate Coordinate. + * @return {boolean} Is there a feature at the given pixel? + */ +ol.render.webgl.ReplayGroup.prototype.hasFeatureAtPixel = function( + context, center, resolution, rotation, size, pixelRatio, + opacity, brightness, contrast, hue, saturation, skippedFeaturesHash, + coordinate) { + var gl = context.getGL(); + gl.bindFramebuffer( + gl.FRAMEBUFFER, context.getHitDetectionFramebuffer()); + + var hasFeature = this.replayHitDetection_(context, + coordinate, resolution, rotation, ol.render.webgl.HIT_DETECTION_SIZE_, + pixelRatio, opacity, brightness, contrast, hue, saturation, + skippedFeaturesHash, + /** + * @param {ol.Feature} feature Feature. + * @return {boolean} Is there a feature? + */ + function(feature) { + var imageData = new Uint8Array(4); + gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, imageData); + return imageData[3] > 0; + }, false); + + return goog.isDef(hasFeature) ? hasFeature : false; }; diff --git a/src/ol/renderer/webgl/webglmaprenderer.js b/src/ol/renderer/webgl/webglmaprenderer.js index 1d6efe5372..4d3f3c7763 100644 --- a/src/ol/renderer/webgl/webglmaprenderer.js +++ b/src/ol/renderer/webgl/webglmaprenderer.js @@ -601,6 +601,53 @@ ol.renderer.webgl.Map.prototype.forEachFeatureAtPixel = }; +/** + * @inheritDoc + */ +ol.renderer.webgl.Map.prototype.hasFeatureAtPixel = + function(coordinate, frameState, layerFilter, thisArg2) { + var hasFeature = false; + + if (this.getGL().isContextLost()) { + return false; + } + + var context = this.getContext(); + var viewState = frameState.viewState; + + // do the hit-detection for the overlays first + if (!goog.isNull(this.replayGroup)) { + // use default color values + var d = ol.renderer.webgl.Map.DEFAULT_COLOR_VALUES_; + + hasFeature = this.replayGroup.hasFeatureAtPixel(context, + viewState.center, viewState.resolution, viewState.rotation, + frameState.size, frameState.pixelRatio, + d.opacity, d.brightness, d.contrast, d.hue, d.saturation, {}, + coordinate); + if (hasFeature) { + return true; + } + } + var layerStates = this.getMap().getLayerGroup().getLayerStatesArray(); + var numLayers = layerStates.length; + var i; + for (i = numLayers - 1; i >= 0; --i) { + var layerState = layerStates[i]; + var layer = layerState.layer; + if (ol.layer.Layer.visibleAtResolution(layerState, viewState.resolution) && + layerFilter.call(thisArg2, layer)) { + var layerRenderer = this.getLayerRenderer(layer); + hasFeature = layerRenderer.hasFeatureAtPixel(coordinate, frameState); + if (hasFeature) { + return true; + } + } + } + return hasFeature; +}; + + /** * @private */ diff --git a/src/ol/renderer/webgl/webglvectorlayerrenderer.js b/src/ol/renderer/webgl/webglvectorlayerrenderer.js index cafc212ad4..3b149b5518 100644 --- a/src/ol/renderer/webgl/webglvectorlayerrenderer.js +++ b/src/ol/renderer/webgl/webglvectorlayerrenderer.js @@ -140,6 +140,28 @@ ol.renderer.webgl.VectorLayer.prototype.forEachFeatureAtPixel = }; +/** + * @inheritDoc + */ +ol.renderer.webgl.VectorLayer.prototype.hasFeatureAtPixel = + function(coordinate, frameState) { + if (goog.isNull(this.replayGroup_) || goog.isNull(this.layerState_)) { + return false; + } else { + var mapRenderer = this.getWebGLMapRenderer(); + var context = mapRenderer.getContext(); + var viewState = frameState.viewState; + var layerState = this.layerState_; + return this.replayGroup_.hasFeatureAtPixel(context, + viewState.center, viewState.resolution, viewState.rotation, + frameState.size, frameState.pixelRatio, + layerState.opacity, layerState.brightness, layerState.contrast, + layerState.hue, layerState.saturation, frameState.skippedFeatureUids, + coordinate); + } +}; + + /** * Handle changes in image style state. * @param {goog.events.Event} event Image style change event.