From 360e77481ddee323bceb1fde2a93879341c256cd Mon Sep 17 00:00:00 2001 From: GaborFarkas Date: Wed, 5 Oct 2016 10:00:45 +0200 Subject: [PATCH] Restructure webgl replays --- package.json | 4 +- .../flat/{topologyflatgeom.js => topology.js} | 0 src/ol/render/webgl/imagereplay/index.js | 2481 +---------------- src/ol/render/webgl/{webgl.js => index.js} | 0 .../render/webgl/{webgl.jsdoc => index.jsdoc} | 0 .../defaultshader.glsl} | 4 +- .../defaultshader.js} | 79 +- src/ol/render/webgl/linestringreplay/index.js | 654 +++++ .../defaultshader.glsl} | 4 +- .../webgl/polygonreplay/defaultshader.js | 126 + src/ol/render/webgl/polygonreplay/index.js | 1018 +++++++ src/ol/render/webgl/replay.js | 321 +++ src/ol/render/webgl/replaygroup.js | 280 ++ .../render/webgl/webglpolygondefaultshader.js | 123 - .../ol/geom/flat/topologyflatgeom.test.js | 4 +- test/spec/ol/render/webgl/replay.test.js | 132 +- 16 files changed, 2597 insertions(+), 2633 deletions(-) rename src/ol/geom/flat/{topologyflatgeom.js => topology.js} (100%) rename src/ol/render/webgl/{webgl.js => index.js} (100%) rename src/ol/render/webgl/{webgl.jsdoc => index.jsdoc} (100%) rename src/ol/render/webgl/{webgllinestringdefault.glsl => linestringreplay/defaultshader.glsl} (97%) rename src/ol/render/webgl/{webgllinestringdefaultshader.js => linestringreplay/defaultshader.js} (80%) create mode 100644 src/ol/render/webgl/linestringreplay/index.js rename src/ol/render/webgl/{webglpolygondefault.glsl => polygonreplay/defaultshader.glsl} (82%) create mode 100644 src/ol/render/webgl/polygonreplay/defaultshader.js create mode 100644 src/ol/render/webgl/polygonreplay/index.js create mode 100644 src/ol/render/webgl/replay.js create mode 100644 src/ol/render/webgl/replaygroup.js delete mode 100644 src/ol/render/webgl/webglpolygondefaultshader.js diff --git a/package.json b/package.json index fd446ace18..0613b80f49 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,6 @@ "browserify": "13.1.1", "closure-util": "1.15.1", "derequire": "2.0.3", - "earcut": "2.1.1", "fs-extra": "1.0.0", "glob": "7.1.1", "handlebars": "4.0.6", @@ -124,7 +123,6 @@ "module": "vector-tile", "name": "vectortile", "browserify": true - }, - "earcut" + } ] } diff --git a/src/ol/geom/flat/topologyflatgeom.js b/src/ol/geom/flat/topology.js similarity index 100% rename from src/ol/geom/flat/topologyflatgeom.js rename to src/ol/geom/flat/topology.js diff --git a/src/ol/render/webgl/imagereplay/index.js b/src/ol/render/webgl/imagereplay/index.js index f7b5ae6122..402546cf16 100644 --- a/src/ol/render/webgl/imagereplay/index.js +++ b/src/ol/render/webgl/imagereplay/index.js @@ -1,322 +1,21 @@ goog.provide('ol.render.webgl.ImageReplay'); -goog.provide('ol.render.webgl.LineStringReplay'); -goog.provide('ol.render.webgl.PolygonReplay'); -goog.provide('ol.render.webgl.Replay'); -goog.provide('ol.render.webgl.ReplayGroup'); goog.require('ol'); -goog.require('ol.color'); goog.require('ol.extent'); goog.require('ol.obj'); -goog.require('ol.render.ReplayGroup'); -goog.require('ol.geom.flat.orient'); -goog.require('ol.geom.flat.transform'); -goog.require('ol.geom.flat.topology'); -goog.require('ol.render.VectorContext'); -goog.require('ol.render.replay'); goog.require('ol.render.webgl.imagereplay.defaultshader'); -goog.require('ol.transform'); +goog.require('ol.render.webgl.Replay'); goog.require('ol.render.webgl'); -goog.require('ol.render.webgl.linestringreplay.shader.Default'); -goog.require('ol.render.webgl.linestringreplay.shader.Default.Locations'); -goog.require('ol.render.webgl.linestringreplay.shader.DefaultFragment'); -goog.require('ol.render.webgl.linestringreplay.shader.DefaultVertex'); -goog.require('ol.render.webgl.polygonreplay.shader.Default'); -goog.require('ol.render.webgl.polygonreplay.shader.Default.Locations'); -goog.require('ol.render.webgl.polygonreplay.shader.DefaultFragment'); -goog.require('ol.render.webgl.polygonreplay.shader.DefaultVertex'); -goog.require('ol.style.Stroke'); -goog.require('ol.structs.LinkedList'); -goog.require('ol.structs.RBush'); -goog.require('ol.vec.Mat4'); goog.require('ol.webgl'); goog.require('ol.webgl.Buffer'); goog.require('ol.webgl.Context'); -/** - * @constructor - * @extends {ol.render.VectorContext} - * @param {number} tolerance Tolerance. - * @param {ol.Extent} maxExtent Max extent. - * @protected - * @struct - */ -ol.render.webgl.Replay = function(tolerance, maxExtent) { - ol.render.VectorContext.call(this); - - /** - * @protected - * @type {number} - */ - this.tolerance = tolerance; - - /** - * @protected - * @const - * @type {ol.Extent} - */ - this.maxExtent = maxExtent; - - /** - * @private - * @type {ol.Extent} - */ - this.bufferedMaxExtent_ = null; - - /** - * The origin of the coordinate system for the point coordinates sent to - * the GPU. To eliminate jitter caused by precision problems in the GPU - * we use the "Rendering Relative to Eye" technique described in the "3D - * Engine Design for Virtual Globes" book. - * @private - * @type {ol.Coordinate} - */ - this.origin_ = ol.extent.getCenter(maxExtent); - - /** - * @type {ol.Transform} - * @private - */ - this.projectionMatrix_ = ol.transform.create(); - - /** - * @type {ol.Transform} - * @private - */ - this.offsetRotateMatrix_ = ol.transform.create(); - - /** - * @type {ol.Transform} - * @private - */ - this.offsetScaleMatrix_ = ol.transform.create(); - - /** - * @type {Array.} - * @private - */ - this.tmpMat4_ = ol.vec.Mat4.create(); - - /** - * @type {Array.} - * @private - */ - this.indices_ = []; - - /** - * @type {ol.webgl.Buffer} - * @private - */ - this.indicesBuffer_ = null; - - /** - * Start index per feature (the index). - * @type {Array.} - * @private - */ - this.startIndices_ = []; - - /** - * Start index per feature (the feature). - * @type {Array.} - * @private - */ - this.startIndicesFeature_ = []; - - /** - * @type {Array.} - * @private - */ - this.vertices_ = []; - - /** - * @type {ol.webgl.Buffer} - * @private - */ - this.verticesBuffer_ = null; - - /** - * Optional parameter for PolygonReplay instances. - * @type {ol.render.webgl.LineStringReplay|undefined} - * @private - */ - this.lineStringReplay_ = undefined; - -}; -ol.inherits(ol.render.webgl.Replay, ol.render.VectorContext); - - -ol.render.webgl.Replay.prototype.getDeleteResourcesFunction = goog.abstractMethod; - -ol.render.webgl.Replay.prototype.finish = goog.abstractMethod; - -ol.render.webgl.Replay.prototype.setUpProgram_ = goog.abstractMethod; - -ol.render.webgl.Replay.prototype.drawReplay_ = goog.abstractMethod; - - -/** - * @private - * @param {WebGLRenderingContext} gl gl. - * @param {ol.webgl.Context} context Context. - * @param {Object.} skippedFeaturesHash Ids of features - * to skip. - * @param {function((ol.Feature|ol.render.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.Replay.prototype.drawHitDetectionReplay_ = function(gl, context, skippedFeaturesHash, - featureCallback, oneByOne, opt_hitExtent) { - if (!oneByOne) { - // draw all hit-detection features in "once" (by texture group) - return this.drawHitDetectionReplayAll_(gl, context, - skippedFeaturesHash, featureCallback); - } else { - // draw hit-detection features one by one - return this.drawHitDetectionReplayOneByOne_(gl, context, - skippedFeaturesHash, featureCallback, opt_hitExtent); - } -}; - - -/** - * @private - * @param {WebGLRenderingContext} gl gl. - * @param {ol.webgl.Context} context Context. - * @param {Object.} skippedFeaturesHash Ids of features - * to skip. - * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback. - * @return {T|undefined} Callback result. - * @template T - */ -ol.render.webgl.Replay.prototype.drawHitDetectionReplayAll_ = function(gl, context, skippedFeaturesHash, - featureCallback) { - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - this.drawReplay_(gl, context, skippedFeaturesHash, true); - - var result = featureCallback(null); - if (result) { - return result; - } else { - return undefined; - } -}; - - -ol.render.webgl.Replay.prototype.drawHitDetectionReplayOneByOne_ = goog.abstractMethod; - - -/** - * @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 {Object.} skippedFeaturesHash Ids of features - * to skip. - * @param {function((ol.Feature|ol.render.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.Replay.prototype.replay = function(context, - center, resolution, rotation, size, pixelRatio, - opacity, skippedFeaturesHash, - featureCallback, oneByOne, opt_hitExtent) { - var gl = context.getGL(); - - // bind the vertices buffer - goog.asserts.assert(this.verticesBuffer_, - 'verticesBuffer must not be null'); - context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.verticesBuffer_); - - // bind the indices buffer - goog.asserts.assert(this.indicesBuffer_, - 'indicesBuffer must not be null'); - context.bindBuffer(ol.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_); - - var locations = this.setUpProgram_(gl, context, size, pixelRatio); - - // set the "uniform" values - var projectionMatrix = ol.transform.reset(this.projectionMatrix_); - ol.transform.scale(projectionMatrix, 2 / (resolution * size[0]), 2 / (resolution * size[1])); - ol.transform.rotate(projectionMatrix, -rotation); - ol.transform.translate(projectionMatrix, -(center[0] - this.origin_[0]), -(center[1] - this.origin_[1])); - - var offsetScaleMatrix = ol.transform.reset(this.offsetScaleMatrix_); - ol.transform.scale(offsetScaleMatrix, 2 / size[0], 2 / size[1]); - - var offsetRotateMatrix = ol.transform.reset(this.offsetRotateMatrix_); - if (rotation !== 0) { - ol.transform.rotate(offsetRotateMatrix, -rotation); - } - - gl.uniformMatrix4fv(locations.u_projectionMatrix, false, - ol.vec.Mat4.fromTransform(this.tmpMat4_, projectionMatrix)); - gl.uniformMatrix4fv(locations.u_offsetScaleMatrix, false, - ol.vec.Mat4.fromTransform(this.tmpMat4_, offsetScaleMatrix)); - gl.uniformMatrix4fv(locations.u_offsetRotateMatrix, false, - ol.vec.Mat4.fromTransform(this.tmpMat4_, offsetRotateMatrix)); - gl.uniform1f(locations.u_opacity, opacity); - - // draw! - var result; - if (featureCallback === undefined) { - this.drawReplay_(gl, context, skippedFeaturesHash); - } else { - // draw feature by feature for the hit-detection - result = this.drawHitDetectionReplay_(gl, context, skippedFeaturesHash, - featureCallback, oneByOne, opt_hitExtent); - } - - // disable the vertex attrib arrays - for (var i in locations) { - if (typeof locations[i] === 'number') { - gl.disableVertexAttribArray(locations[i]); - } - } - - if (this.lineStringReplay_) { - this.lineStringReplay_.replay(context, - center, resolution, rotation, size, pixelRatio, - opacity, skippedFeaturesHash, - featureCallback, oneByOne, opt_hitExtent); - } - - return result; -}; - -/** - * @private - * @param {WebGLRenderingContext} gl gl. - * @param {ol.webgl.Context} context Context. - * @param {number} start Start index. - * @param {number} end End index. - */ -ol.render.webgl.Replay.prototype.drawElements_ = function( - gl, context, start, end) { - var elementType = context.hasOESElementIndexUint ? - ol.webgl.UNSIGNED_INT : ol.webgl.UNSIGNED_SHORT; - var elementSize = context.hasOESElementIndexUint ? 4 : 2; - - var numItems = end - start; - var offsetInBytes = start * elementSize; - gl.drawElements(ol.webgl.TRIANGLES, numItems, elementType, offsetInBytes); -}; /** * @constructor * @extends {ol.render.webgl.Replay} * @param {number} tolerance Tolerance. * @param {ol.Extent} maxExtent Max extent. - * @protected * @struct */ ol.render.webgl.ImageReplay = function(tolerance, maxExtent) { @@ -440,20 +139,19 @@ ol.inherits(ol.render.webgl.ImageReplay, ol.render.webgl.Replay); /** - * @param {ol.webgl.Context} context WebGL context. - * @return {function()} Delete resources function. + * @inheritDoc */ ol.render.webgl.ImageReplay.prototype.getDeleteResourcesFunction = function(context) { // We only delete our stuff here. The shaders and the program may // be used by other ImageReplay instances (for other layers). And // they will be deleted when disposing of the ol.webgl.Context // object. - ol.DEBUG && console.assert(this.verticesBuffer_, + ol.DEBUG && console.assert(this.verticesBuffer, 'verticesBuffer must not be null'); - ol.DEBUG && console.assert(this.indicesBuffer_, + ol.DEBUG && console.assert(this.indicesBuffer, 'indicesBuffer must not be null'); - var verticesBuffer = this.verticesBuffer_; - var indicesBuffer = this.indicesBuffer_; + var verticesBuffer = this.verticesBuffer; + var indicesBuffer = this.indicesBuffer; var textures = this.textures_; var hitDetectionTextures = this.hitDetectionTextures_; var gl = context.getGL(); @@ -510,12 +208,12 @@ ol.render.webgl.ImageReplay.prototype.drawCoordinates_ = function(flatCoordinate var width = /** @type {number} */ (this.width_); var cos = Math.cos(rotation); var sin = Math.sin(rotation); - var numIndices = this.indices_.length; - var numVertices = this.vertices_.length; + var numIndices = this.indices.length; + var numVertices = this.vertices.length; var i, n, offsetX, offsetY, x, y; for (i = offset; i < end; i += stride) { - x = flatCoordinates[i] - this.origin_[0]; - y = flatCoordinates[i + 1] - this.origin_[1]; + x = flatCoordinates[i] - this.origin[0]; + y = flatCoordinates[i + 1] - this.origin[1]; // There are 4 vertices per [x, y] point, one for each corner of the // rectangle we're going to draw. We'd use 1 vertex per [x, y] point if @@ -532,57 +230,57 @@ ol.render.webgl.ImageReplay.prototype.drawCoordinates_ = function(flatCoordinate // bottom-left corner offsetX = -scale * anchorX; offsetY = -scale * (height - anchorY); - this.vertices_[numVertices++] = x; - this.vertices_[numVertices++] = y; - this.vertices_[numVertices++] = offsetX * cos - offsetY * sin; - this.vertices_[numVertices++] = offsetX * sin + offsetY * cos; - this.vertices_[numVertices++] = originX / imageWidth; - this.vertices_[numVertices++] = (originY + height) / imageHeight; - this.vertices_[numVertices++] = opacity; - this.vertices_[numVertices++] = rotateWithView; + this.vertices[numVertices++] = x; + this.vertices[numVertices++] = y; + this.vertices[numVertices++] = offsetX * cos - offsetY * sin; + this.vertices[numVertices++] = offsetX * sin + offsetY * cos; + this.vertices[numVertices++] = originX / imageWidth; + this.vertices[numVertices++] = (originY + height) / imageHeight; + this.vertices[numVertices++] = opacity; + this.vertices[numVertices++] = rotateWithView; // bottom-right corner offsetX = scale * (width - anchorX); offsetY = -scale * (height - anchorY); - this.vertices_[numVertices++] = x; - this.vertices_[numVertices++] = y; - this.vertices_[numVertices++] = offsetX * cos - offsetY * sin; - this.vertices_[numVertices++] = offsetX * sin + offsetY * cos; - this.vertices_[numVertices++] = (originX + width) / imageWidth; - this.vertices_[numVertices++] = (originY + height) / imageHeight; - this.vertices_[numVertices++] = opacity; - this.vertices_[numVertices++] = rotateWithView; + this.vertices[numVertices++] = x; + this.vertices[numVertices++] = y; + this.vertices[numVertices++] = offsetX * cos - offsetY * sin; + this.vertices[numVertices++] = offsetX * sin + offsetY * cos; + this.vertices[numVertices++] = (originX + width) / imageWidth; + this.vertices[numVertices++] = (originY + height) / imageHeight; + this.vertices[numVertices++] = opacity; + this.vertices[numVertices++] = rotateWithView; // top-right corner offsetX = scale * (width - anchorX); offsetY = scale * anchorY; - this.vertices_[numVertices++] = x; - this.vertices_[numVertices++] = y; - this.vertices_[numVertices++] = offsetX * cos - offsetY * sin; - this.vertices_[numVertices++] = offsetX * sin + offsetY * cos; - this.vertices_[numVertices++] = (originX + width) / imageWidth; - this.vertices_[numVertices++] = originY / imageHeight; - this.vertices_[numVertices++] = opacity; - this.vertices_[numVertices++] = rotateWithView; + this.vertices[numVertices++] = x; + this.vertices[numVertices++] = y; + this.vertices[numVertices++] = offsetX * cos - offsetY * sin; + this.vertices[numVertices++] = offsetX * sin + offsetY * cos; + this.vertices[numVertices++] = (originX + width) / imageWidth; + this.vertices[numVertices++] = originY / imageHeight; + this.vertices[numVertices++] = opacity; + this.vertices[numVertices++] = rotateWithView; // top-left corner offsetX = -scale * anchorX; offsetY = scale * anchorY; - this.vertices_[numVertices++] = x; - this.vertices_[numVertices++] = y; - this.vertices_[numVertices++] = offsetX * cos - offsetY * sin; - this.vertices_[numVertices++] = offsetX * sin + offsetY * cos; - this.vertices_[numVertices++] = originX / imageWidth; - this.vertices_[numVertices++] = originY / imageHeight; - this.vertices_[numVertices++] = opacity; - this.vertices_[numVertices++] = rotateWithView; + this.vertices[numVertices++] = x; + this.vertices[numVertices++] = y; + this.vertices[numVertices++] = offsetX * cos - offsetY * sin; + this.vertices[numVertices++] = offsetX * sin + offsetY * cos; + this.vertices[numVertices++] = originX / imageWidth; + this.vertices[numVertices++] = originY / imageHeight; + this.vertices[numVertices++] = opacity; + this.vertices[numVertices++] = rotateWithView; - this.indices_[numIndices++] = n; - this.indices_[numIndices++] = n + 1; - this.indices_[numIndices++] = n + 2; - this.indices_[numIndices++] = n; - this.indices_[numIndices++] = n + 2; - this.indices_[numIndices++] = n + 3; + this.indices[numIndices++] = n; + this.indices[numIndices++] = n + 1; + this.indices[numIndices++] = n + 2; + this.indices[numIndices++] = n; + this.indices[numIndices++] = n + 2; + this.indices[numIndices++] = n + 3; } return numVertices; @@ -593,8 +291,8 @@ ol.render.webgl.ImageReplay.prototype.drawCoordinates_ = function(flatCoordinate * @inheritDoc */ ol.render.webgl.ImageReplay.prototype.drawMultiPoint = function(multiPointGeometry, feature) { - this.startIndices_.push(this.indices_.length); - this.startIndicesFeature_.push(feature); + this.startIndices.push(this.indices.length); + this.startIndicesFeature.push(feature); var flatCoordinates = multiPointGeometry.getFlatCoordinates(); var stride = multiPointGeometry.getStride(); this.drawCoordinates_( @@ -606,8 +304,8 @@ ol.render.webgl.ImageReplay.prototype.drawMultiPoint = function(multiPointGeomet * @inheritDoc */ ol.render.webgl.ImageReplay.prototype.drawPoint = function(pointGeometry, feature) { - this.startIndices_.push(this.indices_.length); - this.startIndicesFeature_.push(feature); + this.startIndices.push(this.indices.length); + this.startIndicesFeature.push(feature); var flatCoordinates = pointGeometry.getFlatCoordinates(); var stride = pointGeometry.getStride(); this.drawCoordinates_( @@ -616,32 +314,30 @@ ol.render.webgl.ImageReplay.prototype.drawPoint = function(pointGeometry, featur /** - * @param {ol.webgl.Context} context Context. + * @inheritDoc */ ol.render.webgl.ImageReplay.prototype.finish = function(context) { var gl = context.getGL(); - this.groupIndices_.push(this.indices_.length); + this.groupIndices_.push(this.indices.length); ol.DEBUG && console.assert(this.images_.length === this.groupIndices_.length, 'number of images and groupIndices match'); - this.hitDetectionGroupIndices_.push(this.indices_.length); + this.hitDetectionGroupIndices_.push(this.indices.length); ol.DEBUG && console.assert(this.hitDetectionImages_.length === this.hitDetectionGroupIndices_.length, 'number of hitDetectionImages and hitDetectionGroupIndices match'); // create, bind, and populate the vertices buffer - this.verticesBuffer_ = new ol.webgl.Buffer(this.vertices_); - context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.verticesBuffer_); + this.verticesBuffer = new ol.webgl.Buffer(this.vertices); - var indices = this.indices_; + var indices = this.indices; var bits = context.hasOESElementIndexUint ? 32 : 16; ol.DEBUG && console.assert(indices[indices.length - 1] < Math.pow(2, bits), 'Too large element index detected [%s] (OES_element_index_uint "%s")', indices[indices.length - 1], context.hasOESElementIndexUint); // create, bind, and populate the indices buffer - this.indicesBuffer_ = new ol.webgl.Buffer(indices); - context.bindBuffer(ol.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_); + this.indicesBuffer = new ol.webgl.Buffer(indices); // create textures /** @type {Object.} */ @@ -664,14 +360,14 @@ ol.render.webgl.ImageReplay.prototype.finish = function(context) { this.hitDetectionImages_ = null; this.imageHeight_ = undefined; this.imageWidth_ = undefined; - this.indices_ = null; + this.indices = null; this.opacity_ = undefined; this.originX_ = undefined; this.originY_ = undefined; this.rotateWithView_ = undefined; this.rotation_ = undefined; this.scale_ = undefined; - this.vertices_ = null; + this.vertices = null; this.width_ = undefined; }; @@ -707,14 +403,9 @@ ol.render.webgl.ImageReplay.prototype.createTextures_ = function(textures, image /** - * @private - * @param {WebGLRenderingContext} gl gl. - * @param {ol.webgl.Context} context Context. - * @param {ol.Size} size Size. - * @param {number} pixelRatio Pixel ratio. - * @return {ol.render.webgl.imagereplay.shader.Default.Locations} Locations. + * @inheritDoc */ -ol.render.webgl.ImageReplay.prototype.setUpProgram_ = function(gl, context, size, pixelRatio) { +ol.render.webgl.ImageReplay.prototype.setUpProgram = function(gl, context, size, pixelRatio) { // get the program var fragmentShader = ol.render.webgl.imagereplay.defaultshader.fragment; var vertexShader = ol.render.webgl.imagereplay.defaultshader.vertex; @@ -757,15 +448,11 @@ ol.render.webgl.ImageReplay.prototype.setUpProgram_ = function(gl, context, size return locations; }; + /** - * @private - * @param {WebGLRenderingContext} gl gl. - * @param {ol.webgl.Context} context Context. - * @param {Object.} skippedFeaturesHash Ids of features - * to skip. - * @param {boolean} hitDetection Hit detection mode. + * @inheritDoc */ -ol.render.webgl.ImageReplay.prototype.drawReplay_ = function(gl, context, skippedFeaturesHash, hitDetection) { +ol.render.webgl.ImageReplay.prototype.drawReplay = function(gl, context, skippedFeaturesHash, hitDetection) { var textures = hitDetection ? this.hitDetectionTextures_ : this.textures_; var groupIndices = hitDetection ? this.hitDetectionGroupIndices_ : this.groupIndices_; ol.DEBUG && console.assert(textures.length === groupIndices.length, @@ -779,7 +466,7 @@ ol.render.webgl.ImageReplay.prototype.drawReplay_ = function(gl, context, skippe for (i = 0, ii = textures.length, start = 0; i < ii; ++i) { gl.bindTexture(ol.webgl.TEXTURE_2D, textures[i]); var end = groupIndices[i]; - this.drawElements_(gl, context, start, end); + this.drawElements(gl, context, start, end); start = end; } } @@ -824,25 +511,25 @@ ol.render.webgl.ImageReplay.prototype.drawReplaySkipping_ = function(gl, context var start = groupStart; var end = groupStart; - while (featureIndex < this.startIndices_.length && - this.startIndices_[featureIndex] <= groupEnd) { - var feature = this.startIndicesFeature_[featureIndex]; + while (featureIndex < this.startIndices.length && + this.startIndices[featureIndex] <= groupEnd) { + var feature = this.startIndicesFeature[featureIndex]; var featureUid = ol.getUid(feature).toString(); if (skippedFeaturesHash[featureUid] !== undefined) { // feature should be skipped if (start !== end) { // draw the features so far - this.drawElements_(gl, context, start, end); + this.drawElements(gl, context, start, end); } // continue with the next feature - start = (featureIndex === this.startIndices_.length - 1) ? - groupEnd : this.startIndices_[featureIndex + 1]; + start = (featureIndex === this.startIndices.length - 1) ? + groupEnd : this.startIndices[featureIndex + 1]; end = start; } else { // the feature is not skipped, augment the end index - end = (featureIndex === this.startIndices_.length - 1) ? - groupEnd : this.startIndices_[featureIndex + 1]; + end = (featureIndex === this.startIndices.length - 1) ? + groupEnd : this.startIndices[featureIndex + 1]; } featureIndex++; } @@ -850,81 +537,23 @@ ol.render.webgl.ImageReplay.prototype.drawReplaySkipping_ = function(gl, context if (start !== end) { // draw the remaining features (in case there was no skipped feature // in this texture group, all features of a group are drawn together) - this.drawElements_(gl, context, start, end); + this.drawElements(gl, context, start, end); } } }; /** - * @private - * @param {WebGLRenderingContext} gl gl. - * @param {ol.webgl.Context} context Context. - * @param {Object.} skippedFeaturesHash Ids of features - * to skip. - * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback. - * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting - * this extent are checked. - * @return {T|undefined} Callback result. - * @template T + * @inheritDoc */ -ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplay_ = function(gl, context, skippedFeaturesHash, featureCallback, oneByOne, - opt_hitExtent) { - if (!oneByOne) { - // draw all hit-detection features in "once" (by texture group) - return this.drawHitDetectionReplayAll_(gl, context, - skippedFeaturesHash, featureCallback); - } else { - // draw hit-detection features one by one - return this.drawHitDetectionReplayOneByOne_(gl, context, - skippedFeaturesHash, featureCallback, opt_hitExtent); - } -}; - - -/** - * @private - * @param {WebGLRenderingContext} gl gl. - * @param {ol.webgl.Context} context Context. - * @param {Object.} skippedFeaturesHash Ids of features - * to skip. - * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback. - * @return {T|undefined} Callback result. - * @template T - */ -ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplayAll_ = function(gl, context, skippedFeaturesHash, featureCallback) { - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - this.drawReplay_(gl, context, skippedFeaturesHash, true); - - var result = featureCallback(null); - if (result) { - return result; - } else { - return undefined; - } -}; - - -/** - * @private - * @param {WebGLRenderingContext} gl gl. - * @param {ol.webgl.Context} context Context. - * @param {Object.} skippedFeaturesHash Ids of features - * to skip. - * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback. - * @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, skippedFeaturesHash, +ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplayOneByOne = function(gl, context, skippedFeaturesHash, featureCallback, opt_hitExtent) { ol.DEBUG && console.assert(this.hitDetectionTextures_.length === this.hitDetectionGroupIndices_.length, 'number of hitDetectionTextures and hitDetectionGroupIndices match'); var i, groupStart, start, end, feature, featureUid; - var featureIndex = this.startIndices_.length - 1; + var featureIndex = this.startIndices.length - 1; for (i = this.hitDetectionTextures_.length - 1; i >= 0; --i) { gl.bindTexture(ol.webgl.TEXTURE_2D, this.hitDetectionTextures_[i]); groupStart = (i > 0) ? this.hitDetectionGroupIndices_[i - 1] : 0; @@ -932,9 +561,9 @@ ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplayOneByOne_ = function // draw all features for this texture group while (featureIndex >= 0 && - this.startIndices_[featureIndex] >= groupStart) { - start = this.startIndices_[featureIndex]; - feature = this.startIndicesFeature_[featureIndex]; + this.startIndices[featureIndex] >= groupStart) { + start = this.startIndices[featureIndex]; + feature = this.startIndicesFeature[featureIndex]; featureUid = ol.getUid(feature).toString(); if (skippedFeaturesHash[featureUid] === undefined && @@ -943,7 +572,7 @@ ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplayOneByOne_ = function /** @type {Array} */ (opt_hitExtent), feature.getGeometry().getExtent()))) { gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - this.drawElements_(gl, context, start, end); + this.drawElements(gl, context, start, end); var result = featureCallback(feature); if (result) { @@ -959,13 +588,6 @@ ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplayOneByOne_ = function }; -/** - * @inheritDoc - * @abstract - */ -ol.render.webgl.ImageReplay.prototype.setFillStrokeStyle = function() {}; - - /** * @inheritDoc */ @@ -1003,7 +625,7 @@ ol.render.webgl.ImageReplay.prototype.setImageStyle = function(imageStyle) { } else { currentImage = this.images_[this.images_.length - 1]; if (ol.getUid(currentImage) != ol.getUid(image)) { - this.groupIndices_.push(this.indices_.length); + this.groupIndices_.push(this.indices.length); ol.DEBUG && console.assert(this.groupIndices_.length === this.images_.length, 'number of groupIndices and images match'); this.images_.push(image); @@ -1016,7 +638,7 @@ ol.render.webgl.ImageReplay.prototype.setImageStyle = function(imageStyle) { currentImage = this.hitDetectionImages_[this.hitDetectionImages_.length - 1]; if (ol.getUid(currentImage) != ol.getUid(hitDetectionImage)) { - this.hitDetectionGroupIndices_.push(this.indices_.length); + this.hitDetectionGroupIndices_.push(this.indices.length); ol.DEBUG && console.assert(this.hitDetectionGroupIndices_.length === this.hitDetectionImages_.length, 'number of hitDetectionGroupIndices and hitDetectionImages match'); @@ -1037,1938 +659,3 @@ ol.render.webgl.ImageReplay.prototype.setImageStyle = function(imageStyle) { this.scale_ = scale; this.width_ = size[0]; }; - - -/** - * @constructor - * @extends {ol.render.webgl.Replay} - * @param {number} tolerance Tolerance. - * @param {ol.Extent} maxExtent Max extent. - * @protected - * @struct - */ -ol.render.webgl.LineStringReplay = function(tolerance, maxExtent) { - ol.render.webgl.Replay.call(this, tolerance, maxExtent); - - /** - * @private - * @type {ol.render.webgl.linestringreplay.shader.Default.Locations} - */ - this.defaultLocations_ = null; - - /** - * @private - * @type {Array.>} - */ - this.styles_ = []; - - /** - * @private - * @type {Array.} - */ - this.styleIndices_ = []; - - /** - * @private - * @type {{strokeColor: (Array.|null), - * lineCap: (string|undefined), - * lineDash: Array., - * lineJoin: (string|undefined), - * lineWidth: (number|undefined), - * miterLimit: (number|undefined), - * changed: boolean}|null} - */ - this.state_ = { - strokeColor: null, - lineCap: undefined, - lineDash: null, - lineJoin: undefined, - lineWidth: undefined, - miterLimit: undefined, - changed: false - }; - -}; -ol.inherits(ol.render.webgl.LineStringReplay, ol.render.webgl.Replay); - - -/** - * Draw one segment. - * @param {Array.} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @private - */ -ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoordinates, offset, end, stride) { - - var i, ii; - var numVertices = this.vertices_.length; - var numIndices = this.indices_.length; - //To save a vertex, the direction of a point is a product of the sign (1 or -1), a prime from - //ol.render.webgl.lineStringInstruction, and a rounding factor (1 or 2). If the product is even, - //we round it. If it is odd, we don't. - var lineJoin = this.state_.lineJoin === 'bevel' ? 0 : - this.state_.lineJoin === 'miter' ? 1 : 2; - var lineCap = this.state_.lineCap === 'butt' ? 0 : - this.state_.lineCap === 'square' ? 1 : 2; - var closed = ol.geom.flat.topology.lineStringIsClosed(flatCoordinates, offset, end, stride); - var startCoords, sign, n; - var lastIndex = numIndices; - var lastSign = 1; - //We need the adjacent vertices to define normals in joins. p0 = last, p1 = current, p2 = next. - //We rotate those points, thus every point is RTE corrected only once. - var p0, p1, p2; - - for (i = offset, ii = end; i < ii; i += stride) { - - n = numVertices / 7; - - p0 = p1; - p1 = p2 || [flatCoordinates[i], flatCoordinates[i + 1]]; - //First vertex. - if (i === offset) { - p2 = [flatCoordinates[i + stride], flatCoordinates[i + stride + 1]]; - if (end - offset === stride * 2 && ol.array.equals(p1, p2)) { - break; - } - if (closed) { - //A closed line! Complete the circle. - p0 = [flatCoordinates[end - stride * 2], - flatCoordinates[end - stride * 2 + 1]]; - - startCoords = p2; - } else { - //Add the first two/four vertices. - - if (lineCap) { - numVertices = this.addVertices_([0, 0], p1, p2, - lastSign * ol.render.webgl.lineStringInstruction.BEGIN_LINE_CAP * lineCap, numVertices); - - numVertices = this.addVertices_([0, 0], p1, p2, - -lastSign * ol.render.webgl.lineStringInstruction.BEGIN_LINE_CAP * lineCap, numVertices); - - this.indices_[numIndices++] = n + 2; - this.indices_[numIndices++] = n; - this.indices_[numIndices++] = n + 1; - - this.indices_[numIndices++] = n + 1; - this.indices_[numIndices++] = n + 3; - this.indices_[numIndices++] = n + 2; - - } - - numVertices = this.addVertices_([0, 0], p1, p2, - lastSign * ol.render.webgl.lineStringInstruction.BEGIN_LINE * (lineCap || 1), numVertices); - - numVertices = this.addVertices_([0, 0], p1, p2, - -lastSign * ol.render.webgl.lineStringInstruction.BEGIN_LINE * (lineCap || 1), numVertices); - - lastIndex = numVertices / 7 - 1; - - continue; - } - } else if (i === end - stride) { - //Last vertex. - if (closed) { - //Same as the first vertex. - p2 = startCoords; - break; - } else { - //For the compiler not to complain. This will never be [0, 0]. - p0 = p0 || [0, 0]; - - numVertices = this.addVertices_(p0, p1, [0, 0], - lastSign * ol.render.webgl.lineStringInstruction.END_LINE * (lineCap || 1), numVertices); - - numVertices = this.addVertices_(p0, p1, [0, 0], - -lastSign * ol.render.webgl.lineStringInstruction.END_LINE * (lineCap || 1), numVertices); - - this.indices_[numIndices++] = n; - this.indices_[numIndices++] = lastIndex - 1; - this.indices_[numIndices++] = lastIndex; - - this.indices_[numIndices++] = lastIndex; - this.indices_[numIndices++] = n + 1; - this.indices_[numIndices++] = n; - - if (lineCap) { - numVertices = this.addVertices_(p0, p1, [0, 0], - lastSign * ol.render.webgl.lineStringInstruction.END_LINE_CAP * lineCap, numVertices); - - numVertices = this.addVertices_(p0, p1, [0, 0], - -lastSign * ol.render.webgl.lineStringInstruction.END_LINE_CAP * lineCap, numVertices); - - this.indices_[numIndices++] = n + 2; - this.indices_[numIndices++] = n; - this.indices_[numIndices++] = n + 1; - - this.indices_[numIndices++] = n + 1; - this.indices_[numIndices++] = n + 3; - this.indices_[numIndices++] = n + 2; - - } - - break; - } - } else { - p2 = [flatCoordinates[i + stride], flatCoordinates[i + stride + 1]]; - } - - sign = ol.render.webgl.triangleIsCounterClockwise(p0[0], p0[1], p1[0], p1[1], p2[0], p2[1]) - ? -1 : 1; - - numVertices = this.addVertices_(p0, p1, p2, - sign * ol.render.webgl.lineStringInstruction.BEVEL_FIRST * (lineJoin || 1), numVertices); - - numVertices = this.addVertices_(p0, p1, p2, - sign * ol.render.webgl.lineStringInstruction.BEVEL_SECOND * (lineJoin || 1), numVertices); - - numVertices = this.addVertices_(p0, p1, p2, - -sign * ol.render.webgl.lineStringInstruction.MITER_BOTTOM * (lineJoin || 1), numVertices); - - if (i > offset) { - this.indices_[numIndices++] = n; - this.indices_[numIndices++] = lastIndex - 1; - this.indices_[numIndices++] = lastIndex; - - this.indices_[numIndices++] = n + 2; - this.indices_[numIndices++] = n; - this.indices_[numIndices++] = lastSign * sign > 0 ? lastIndex : lastIndex - 1; - } - - this.indices_[numIndices++] = n; - this.indices_[numIndices++] = n + 2; - this.indices_[numIndices++] = n + 1; - - lastIndex = n + 2; - lastSign = sign; - - //Add miter - if (lineJoin) { - numVertices = this.addVertices_(p0, p1, p2, - sign * ol.render.webgl.lineStringInstruction.MITER_TOP * lineJoin, numVertices); - - this.indices_[numIndices++] = n + 1; - this.indices_[numIndices++] = n + 3; - this.indices_[numIndices++] = n; - } - } - - if (closed) { - //Link the last triangle/rhombus to the first one. - //n will never be numVertices / 7 here. However, the compiler complains otherwise. - n = n || numVertices / 7; - sign = ol.geom.flat.orient.linearRingIsClockwise([p0[0], p0[1], p1[0], p1[1], p2[0], p2[1]], 0, 6, 2) - ? 1 : -1; - - numVertices = this.addVertices_(p0, p1, p2, - sign * ol.render.webgl.lineStringInstruction.BEVEL_FIRST * (lineJoin || 1), numVertices); - - numVertices = this.addVertices_(p0, p1, p2, - -sign * ol.render.webgl.lineStringInstruction.MITER_BOTTOM * (lineJoin || 1), numVertices); - - this.indices_[numIndices++] = n; - this.indices_[numIndices++] = lastIndex - 1; - this.indices_[numIndices++] = lastIndex; - - this.indices_[numIndices++] = n + 1; - this.indices_[numIndices++] = n; - this.indices_[numIndices++] = lastSign * sign > 0 ? lastIndex : lastIndex - 1; - } -}; - -/** - * @param {Array.} p0 Last coordinates. - * @param {Array.} p1 Current coordinates. - * @param {Array.} p2 Next coordinates. - * @param {number} product Sign, instruction, and rounding product. - * @param {number} numVertices Vertex counter. - * @return {number} Vertex counter. - * @private - */ -ol.render.webgl.LineStringReplay.prototype.addVertices_ = function(p0, p1, p2, product, numVertices) { - this.vertices_[numVertices++] = p0[0]; - this.vertices_[numVertices++] = p0[1]; - this.vertices_[numVertices++] = p1[0]; - this.vertices_[numVertices++] = p1[1]; - this.vertices_[numVertices++] = p2[0]; - this.vertices_[numVertices++] = p2[1]; - this.vertices_[numVertices++] = product; - - return numVertices; -}; - -/** - * Check if the linestring can be drawn (i. e. valid). - * @param {Array.} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @return {boolean} The linestring can be drawn. - * @private - */ -ol.render.webgl.LineStringReplay.prototype.isValid_ = function(flatCoordinates, offset, end, stride) { - var range = end - offset; - if (range < stride * 2) { - return false; - } else if (range === stride * 2) { - var firstP = [flatCoordinates[offset], flatCoordinates[offset + 1]]; - var lastP = [flatCoordinates[offset + stride], flatCoordinates[offset + stride + 1]]; - return !ol.array.equals(firstP, lastP); - } - - return true; -}; - - -/** - * @inheritDoc - */ -ol.render.webgl.LineStringReplay.prototype.drawLineString = function(lineStringGeometry, feature) { - var flatCoordinates = lineStringGeometry.getFlatCoordinates(); - var stride = lineStringGeometry.getStride(); - if (this.isValid_(flatCoordinates, 0, flatCoordinates.length, stride)) { - flatCoordinates = ol.geom.flat.transform.translate(flatCoordinates, 0, flatCoordinates.length, - stride, -this.origin_[0], -this.origin_[1]); - if (this.state_.changed) { - this.styleIndices_.push(this.indices_.length); - this.state_.changed = false; - } - this.startIndices_.push(this.indices_.length); - this.startIndicesFeature_.push(feature); - this.drawCoordinates_( - flatCoordinates, 0, flatCoordinates.length, stride); - } -}; - - -/** - * @inheritDoc - */ -ol.render.webgl.LineStringReplay.prototype.drawMultiLineString = function(multiLineStringGeometry, feature) { - var indexCount = this.indices_.length; - var lineStringGeometries = multiLineStringGeometry.getLineStrings(); - var i, ii; - for (i = 0, ii = lineStringGeometries.length; i < ii; ++i) { - var flatCoordinates = lineStringGeometries[i].getFlatCoordinates(); - var stride = lineStringGeometries[i].getStride(); - if (this.isValid_(flatCoordinates, 0, flatCoordinates.length, stride)) { - flatCoordinates = ol.geom.flat.transform.translate(flatCoordinates, 0, flatCoordinates.length, - stride, -this.origin_[0], -this.origin_[1]); - this.drawCoordinates_( - flatCoordinates, 0, flatCoordinates.length, stride); - } - } - if (this.indices_.length > indexCount) { - this.startIndices_.push(indexCount); - this.startIndicesFeature_.push(feature); - if (this.state_.changed) { - this.styleIndices_.push(indexCount); - this.state_.changed = false; - } - } -}; - - -/** - * @param {ol.webgl.Context} context Context. - **/ -ol.render.webgl.LineStringReplay.prototype.finish = function(context) { - // create, bind, and populate the vertices buffer - this.verticesBuffer_ = new ol.webgl.Buffer(this.vertices_); - context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.verticesBuffer_); - - // create, bind, and populate the indices buffer - this.indicesBuffer_ = new ol.webgl.Buffer(this.indices_); - context.bindBuffer(ol.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_); - - this.startIndices_.push(this.indices_.length); - - //Clean up, if there is nothing to draw - if (this.styleIndices_.length === 0 && this.styles_.length > 0) { - this.styles_ = []; - } - - this.vertices_ = null; - this.indices_ = null; -}; - - -/** - * @param {ol.webgl.Context} context WebGL context. - * @return {function()} Delete resources function. - */ -ol.render.webgl.LineStringReplay.prototype.getDeleteResourcesFunction = function(context) { - // We only delete our stuff here. The shaders and the program may - // be used by other LineStringReplay instances (for other layers). And - // they will be deleted when disposing of the ol.webgl.Context - // object. - goog.asserts.assert(this.verticesBuffer_, 'verticesBuffer must not be null'); - var verticesBuffer = this.verticesBuffer_; - var indicesBuffer = this.indicesBuffer_; - return function() { - context.deleteBuffer(verticesBuffer); - context.deleteBuffer(indicesBuffer); - }; -}; - - -/** - * @private - * @param {WebGLRenderingContext} gl gl. - * @param {ol.webgl.Context} context Context. - * @param {ol.Size} size Size. - * @param {number} pixelRatio Pixel ratio. - * @return {ol.render.webgl.linestringreplay.shader.Default.Locations} Locations. - */ -ol.render.webgl.LineStringReplay.prototype.setUpProgram_ = function(gl, context, size, pixelRatio) { - // get the program - var fragmentShader, vertexShader; - fragmentShader = - ol.render.webgl.linestringreplay.shader.DefaultFragment.getInstance(); - vertexShader = - ol.render.webgl.linestringreplay.shader.DefaultVertex.getInstance(); - var program = context.getProgram(fragmentShader, vertexShader); - - // get the locations - var locations; - if (!this.defaultLocations_) { - locations = new ol.render.webgl.linestringreplay.shader.Default - .Locations(gl, program); - this.defaultLocations_ = locations; - } else { - locations = this.defaultLocations_; - } - - context.useProgram(program); - - // enable the vertex attrib arrays - gl.enableVertexAttribArray(locations.a_lastPos); - gl.vertexAttribPointer(locations.a_lastPos, 2, ol.webgl.FLOAT, - false, 28, 0); - - gl.enableVertexAttribArray(locations.a_position); - gl.vertexAttribPointer(locations.a_position, 2, ol.webgl.FLOAT, - false, 28, 8); - - gl.enableVertexAttribArray(locations.a_nextPos); - gl.vertexAttribPointer(locations.a_nextPos, 2, ol.webgl.FLOAT, - false, 28, 16); - - gl.enableVertexAttribArray(locations.a_direction); - gl.vertexAttribPointer(locations.a_direction, 1, ol.webgl.FLOAT, - false, 28, 24); - - // Enable renderer specific uniforms. If clauses needed, as otherwise the compiler complains. - gl.uniform2fv(locations.u_size, size); - gl.uniform1f(locations.u_pixelRatio, pixelRatio); - - return locations; -}; - - -/** - * @private - * @param {WebGLRenderingContext} gl gl. - * @param {ol.webgl.Context} context Context. - * @param {Object} skippedFeaturesHash Ids of features to skip. - * @param {boolean} hitDetection Hit detection mode. - */ -ol.render.webgl.LineStringReplay.prototype.drawReplay_ = function(gl, context, skippedFeaturesHash, hitDetection) { - //Save GL parameters. - var tmpDepthFunc = /** @type {number} */ (gl.getParameter(gl.DEPTH_FUNC)); - var tmpDepthMask = /** @type {boolean} */ (gl.getParameter(gl.DEPTH_WRITEMASK)); - - if (!hitDetection) { - gl.enable(gl.DEPTH_TEST); - gl.depthMask(true); - gl.depthFunc(gl.NOTEQUAL); - } - - if (!ol.object.isEmpty(skippedFeaturesHash)) { - this.drawReplaySkipping_(gl, context, skippedFeaturesHash); - } else { - goog.asserts.assert(this.styles_.length === this.styleIndices_.length, - 'number of styles and styleIndices match'); - - //Draw by style groups to minimize drawElements() calls. - var i, start, end, nextStyle; - end = this.startIndices_[this.startIndices_.length - 1]; - for (i = this.styleIndices_.length - 1; i >= 0; --i) { - start = this.styleIndices_[i]; - nextStyle = this.styles_[i]; - this.setStrokeStyle_(gl, nextStyle[0], nextStyle[1], nextStyle[2]); - this.drawElements_(gl, context, start, end); - end = start; - } - } - if (!hitDetection) { - gl.clear(gl.DEPTH_BUFFER_BIT); - gl.disable(gl.DEPTH_TEST); - //Restore GL parameters. - gl.depthMask(tmpDepthMask); - gl.depthFunc(tmpDepthFunc); - } -}; - - -/** - * @private - * @param {WebGLRenderingContext} gl gl. - * @param {ol.webgl.Context} context Context. - * @param {Object} skippedFeaturesHash Ids of features to skip. - */ -ol.render.webgl.LineStringReplay.prototype.drawReplaySkipping_ = function(gl, context, skippedFeaturesHash) { - goog.asserts.assert(this.startIndices_.length - 1 === this.startIndicesFeature_.length, - 'number of startIndices and startIndicesFeature match'); - - var i, start, end, nextStyle, groupStart, feature, featureUid, featureIndex, featureStart; - featureIndex = this.startIndices_.length - 2; - end = start = this.startIndices_[featureIndex + 1]; - for (i = this.styleIndices_.length - 1; i >= 0; --i) { - nextStyle = this.styles_[i]; - this.setStrokeStyle_(gl, nextStyle[0], nextStyle[1], nextStyle[2]); - groupStart = this.styleIndices_[i]; - - while (featureIndex >= 0 && - this.startIndices_[featureIndex] >= groupStart) { - featureStart = this.startIndices_[featureIndex]; - feature = this.startIndicesFeature_[featureIndex]; - featureUid = goog.getUid(feature).toString(); - - if (skippedFeaturesHash[featureUid]) { - if (start !== end) { - this.drawElements_(gl, context, start, end); - } - end = featureStart; - } - featureIndex--; - start = featureStart; - } - if (start !== end) { - this.drawElements_(gl, context, start, end); - } - } -}; - - -/** - * @private - * @param {WebGLRenderingContext} gl gl. - * @param {ol.webgl.Context} context Context. - * @param {Object.} skippedFeaturesHash Ids of features - * to skip. - * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback. - * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting - * this extent are checked. - * @return {T|undefined} Callback result. - * @template T - */ -ol.render.webgl.LineStringReplay.prototype.drawHitDetectionReplayOneByOne_ = function(gl, context, skippedFeaturesHash, - featureCallback, opt_hitExtent) { - goog.asserts.assert(this.styles_.length === this.styleIndices_.length, - 'number of styles and styleIndices match'); - goog.asserts.assert(this.startIndices_.length - 1 === this.startIndicesFeature_.length, - 'number of startIndices and startIndicesFeature match'); - - var i, start, end, nextStyle, groupStart, feature, featureUid, featureIndex; - featureIndex = this.startIndices_.length - 2; - end = this.startIndices_[featureIndex + 1]; - for (i = this.styleIndices_.length - 1; i >= 0; --i) { - nextStyle = this.styles_[i]; - this.setStrokeStyle_(gl, nextStyle[0], nextStyle[1], nextStyle[2]); - groupStart = this.styleIndices_[i]; - - while (featureIndex >= 0 && - this.startIndices_[featureIndex] >= groupStart) { - start = this.startIndices_[featureIndex]; - feature = this.startIndicesFeature_[featureIndex]; - featureUid = goog.getUid(feature).toString(); - - if (skippedFeaturesHash[featureUid] === undefined && - feature.getGeometry() && - (opt_hitExtent === undefined || ol.extent.intersects( - /** @type {Array} */ (opt_hitExtent), - feature.getGeometry().getExtent()))) { - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - this.drawElements_(gl, context, start, end); - - var result = featureCallback(feature); - - if (result) { - return result; - } - - } - featureIndex--; - end = start; - } - } - return undefined; -}; - - -/** - * @private - * @param {WebGLRenderingContext} gl gl. - * @param {Array.} color Color. - * @param {number} lineWidth Line width. - * @param {number} miterLimit Miter limit. - */ -ol.render.webgl.LineStringReplay.prototype.setStrokeStyle_ = function(gl, color, lineWidth, miterLimit) { - gl.uniform4fv(this.defaultLocations_.u_color, color); - gl.uniform1f(this.defaultLocations_.u_lineWidth, lineWidth); - gl.uniform1f(this.defaultLocations_.u_miterLimit, miterLimit); -}; - - -/** - * @inheritDoc - */ -ol.render.webgl.LineStringReplay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) { - goog.asserts.assert(this.state_, 'this.state_ should not be null'); - goog.asserts.assert(!fillStyle, 'fillStyle should be null'); - goog.asserts.assert(strokeStyle, 'strokeStyle should not be null'); - var strokeStyleLineCap = strokeStyle.getLineCap(); - this.state_.lineCap = strokeStyleLineCap !== undefined ? - strokeStyleLineCap : ol.render.webgl.defaultLineCap; - var strokeStyleLineDash = strokeStyle.getLineDash(); - this.state_.lineDash = strokeStyleLineDash ? - strokeStyleLineDash : ol.render.webgl.defaultLineDash; - var strokeStyleLineJoin = strokeStyle.getLineJoin(); - this.state_.lineJoin = strokeStyleLineJoin !== undefined ? - strokeStyleLineJoin : ol.render.webgl.defaultLineJoin; - var strokeStyleColor = ol.color.asArray(strokeStyle.getColor()).map(function(c, i) { - return i != 3 ? c / 255 : c; - }) || ol.render.webgl.defaultStrokeStyle; - var strokeStyleWidth = strokeStyle.getWidth(); - strokeStyleWidth = strokeStyleWidth !== undefined ? - strokeStyleWidth : ol.render.webgl.defaultLineWidth; - var strokeStyleMiterLimit = strokeStyle.getMiterLimit(); - strokeStyleMiterLimit = strokeStyleMiterLimit !== undefined ? - strokeStyleMiterLimit : ol.render.webgl.defaultMiterLimit; - if (!this.state_.strokeColor || !ol.array.equals(this.state_.strokeColor, strokeStyleColor) || - this.state_.lineWidth !== strokeStyleWidth || this.state_.miterLimit !== strokeStyleMiterLimit) { - this.state_.changed = true; - this.state_.strokeColor = strokeStyleColor; - this.state_.lineWidth = strokeStyleWidth; - this.state_.miterLimit = strokeStyleMiterLimit; - this.styles_.push([strokeStyleColor, strokeStyleWidth, strokeStyleMiterLimit]); - } -}; - - -/** - * @constructor - * @extends {ol.render.webgl.Replay} - * @param {number} tolerance Tolerance. - * @param {ol.Extent} maxExtent Max extent. - * @protected - * @struct - */ -ol.render.webgl.PolygonReplay = function(tolerance, maxExtent) { - ol.render.webgl.Replay.call(this, tolerance, maxExtent); - - this.lineStringReplay_ = new ol.render.webgl.LineStringReplay( - tolerance, maxExtent); - - /** - * @private - * @type {ol.render.webgl.polygonreplay.shader.Default.Locations} - */ - this.defaultLocations_ = null; - - /** - * @private - * @type {Array.>} - */ - this.styles_ = []; - - /** - * @private - * @type {Array.} - */ - this.styleIndices_ = []; - - /** - * @private - * @type {{fillColor: (Array.|null), - * changed: boolean}|null} - */ - this.state_ = { - fillColor: null, - changed: false - }; - -}; -ol.inherits(ol.render.webgl.PolygonReplay, ol.render.webgl.Replay); - - -/** - * Draw one polygon. - * @param {Array.} flatCoordinates Flat coordinates. - * @param {Array.>} holeFlatCoordinates Hole flat coordinates. - * @param {number} stride Stride. - * @private - */ -ol.render.webgl.PolygonReplay.prototype.drawCoordinates_ = function( - flatCoordinates, holeFlatCoordinates, stride) { - // Triangulate the polygon - var outerRing = new ol.structs.LinkedList(); - var rtree = new ol.structs.RBush(); - // Initialize the outer ring - var maxX = this.processFlatCoordinates_(flatCoordinates, stride, outerRing, rtree, true); - - // Eliminate holes, if there are any - if (holeFlatCoordinates.length) { - var i, ii; - var holeLists = []; - for (i = 0, ii = holeFlatCoordinates.length; i < ii; ++i) { - var holeList = { - list: new ol.structs.LinkedList(), - maxX: undefined - }; - holeLists.push(holeList); - holeList.maxX = this.processFlatCoordinates_(holeFlatCoordinates[i], - stride, holeList.list, rtree, false); - } - holeLists.sort(function(a, b) { - return b.maxX - a.maxX; - }); - for (i = 0; i < holeLists.length; ++i) { - this.bridgeHole_(holeLists[i].list, holeLists[i].maxX, outerRing, maxX, rtree); - } - } - this.classifyPoints_(outerRing, rtree, false); - this.triangulate_(outerRing, rtree); -}; - - -/** - * Inserts flat coordinates in a linked list and adds them to the vertex buffer. - * @private - * @param {Array.} flatCoordinates Flat coordinates. - * @param {number} stride Stride. - * @param {ol.structs.LinkedList} list Linked list. - * @param {ol.structs.RBush} rtree R-Tree of the polygon. - * @param {boolean} clockwise Coordinate order should be clockwise. - * @return {number} Maximum X value. - */ -ol.render.webgl.PolygonReplay.prototype.processFlatCoordinates_ = function( - flatCoordinates, stride, list, rtree, clockwise) { - var isClockwise = ol.geom.flat.orient.linearRingIsClockwise(flatCoordinates, - 0, flatCoordinates.length, stride); - var i, ii, maxX; - var n = this.vertices_.length / 2; - /** @type {ol.WebglPolygonVertex} */ - var start; - /** @type {ol.WebglPolygonVertex} */ - var p0; - /** @type {ol.WebglPolygonVertex} */ - var p1; - var extents = []; - var segments = []; - if (clockwise === isClockwise) { - start = this.createPoint_(flatCoordinates[0], flatCoordinates[1], n++); - p0 = start; - maxX = flatCoordinates[0]; - for (i = stride, ii = flatCoordinates.length; i < ii; i += stride) { - p1 = this.createPoint_(flatCoordinates[i], flatCoordinates[i + 1], n++); - segments.push(this.insertItem_(p0, p1, list)); - extents.push([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y), Math.max(p0.x, p1.x), - Math.max(p0.y, p1.y)]); - maxX = flatCoordinates[i] > maxX ? flatCoordinates[i] : maxX; - p0 = p1; - } - segments.push(this.insertItem_(p1, start, list)); - extents.push([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y), Math.max(p0.x, p1.x), - Math.max(p0.y, p1.y)]); - } else { - var end = flatCoordinates.length - stride; - start = this.createPoint_(flatCoordinates[end], flatCoordinates[end + 1], n++); - p0 = start; - maxX = flatCoordinates[end]; - for (i = end - stride, ii = 0; i >= ii; i -= stride) { - p1 = this.createPoint_(flatCoordinates[i], flatCoordinates[i + 1], n++); - segments.push(this.insertItem_(p0, p1, list)); - extents.push([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y), Math.max(p0.x, p1.x), - Math.max(p0.y, p1.y)]); - maxX = flatCoordinates[i] > maxX ? flatCoordinates[i] : maxX; - p0 = p1; - } - segments.push(this.insertItem_(p1, start, list)); - extents.push([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y), Math.max(p0.x, p1.x), - Math.max(p0.y, p1.y)]); - } - rtree.load(extents, segments); - - return maxX; -}; - - -/** - * Classifies the points of a polygon list as convex, reflex. Removes collinear vertices. - * @private - * @param {ol.structs.LinkedList} list Polygon ring. - * @param {ol.structs.RBush} rtree R-Tree of the polygon. - * @param {boolean} ccw The orientation of the polygon is counter-clockwise. - * @return {boolean} There were reclassified points. - */ -ol.render.webgl.PolygonReplay.prototype.classifyPoints_ = function(list, rtree, ccw) { - var start = list.firstItem(); - var s0 = start; - var s1 = list.nextItem(); - var pointsReclassified = false; - do { - var reflex = ccw ? ol.render.webgl.triangleIsCounterClockwise(s1.p1.x, - s1.p1.y, s0.p1.x, s0.p1.y, s0.p0.x, s0.p0.y) : - ol.render.webgl.triangleIsCounterClockwise(s0.p0.x, s0.p0.y, s0.p1.x, - s0.p1.y, s1.p1.x, s1.p1.y); - if (reflex === undefined) { - this.removeItem_(s0, s1, list, rtree); - pointsReclassified = true; - if (s1 === start) { - start = list.getNextItem(); - } - s1 = s0; - list.prevItem(); - } else if (s0.p1.reflex !== reflex) { - s0.p1.reflex = reflex; - pointsReclassified = true; - } - s0 = s1; - s1 = list.nextItem(); - } while (s0 !== start); - return pointsReclassified; -}; - - -/** - * @private - * @param {ol.structs.LinkedList} hole Linked list of the hole. - * @param {number} holeMaxX Maximum X value of the hole. - * @param {ol.structs.LinkedList} list Linked list of the polygon. - * @param {number} listMaxX Maximum X value of the polygon. - * @param {ol.structs.RBush} rtree R-Tree of the polygon. - */ -ol.render.webgl.PolygonReplay.prototype.bridgeHole_ = function(hole, holeMaxX, - list, listMaxX, rtree) { - var seg = hole.firstItem(); - while (seg.p1.x !== holeMaxX) { - seg = hole.nextItem(); - } - - var p1 = seg.p1; - /** @type {ol.WebglPolygonVertex} */ - var p2 = {x: listMaxX, y: p1.y, i: -1}; - var minDist = Infinity; - var i, ii, bestPoint; - /** @type {ol.WebglPolygonVertex} */ - var p5; - - var intersectingSegments = this.getIntersections_({p0: p1, p1: p2}, rtree, true); - for (i = 0, ii = intersectingSegments.length; i < ii; ++i) { - var currSeg = intersectingSegments[i]; - if (currSeg.p0 !== p1 && currSeg.p1 !== p1) { - var intersection = this.calculateIntersection_(p1, p2, currSeg.p0, - currSeg.p1, true); - var dist = Math.abs(p1.x - intersection[0]); - if (dist < minDist) { - minDist = dist; - p5 = {x: intersection[0], y: intersection[1], i: -1}; - seg = currSeg; - } - } - } - bestPoint = seg.p1; - - var pointsInTriangle = this.getPointsInTriangle_(p1, p5, seg.p1, rtree); - if (pointsInTriangle.length) { - var theta = Infinity; - for (i = 0, ii = pointsInTriangle.length; i < ii; ++i) { - var currPoint = pointsInTriangle[i]; - var currTheta = Math.atan2(p1.y - currPoint.y, p2.x - currPoint.x); - if (currTheta < theta || (currTheta === theta && currPoint.x < bestPoint.x)) { - theta = currTheta; - bestPoint = currPoint; - } - } - } - - seg = list.firstItem(); - while (seg.p1 !== bestPoint) { - seg = list.nextItem(); - } - - //We clone the bridge points as they can have different convexity. - var p0Bridge = {x: p1.x, y: p1.y, i: p1.i, reflex: undefined}; - var p1Bridge = {x: seg.p1.x, y: seg.p1.y, i: seg.p1.i, reflex: undefined}; - - hole.getNextItem().p0 = p0Bridge; - this.insertItem_(p1, seg.p1, hole, rtree); - this.insertItem_(p1Bridge, p0Bridge, hole, rtree); - seg.p1 = p1Bridge; - hole.setFirstItem(); - list.concat(hole); -}; - - -/** - * @private - * @param {ol.structs.LinkedList} list Linked list of the polygon. - * @param {ol.structs.RBush} rtree R-Tree of the polygon. - */ -ol.render.webgl.PolygonReplay.prototype.triangulate_ = function(list, rtree) { - var ccw = false; - var simple = this.isSimple_(list, rtree); - - // Start clipping ears - while (list.getLength() > 3) { - if (simple) { - if (!this.clipEars_(list, rtree, simple, ccw)) { - if (!this.classifyPoints_(list, rtree, ccw)) { - // Due to the behavior of OL's PIP algorithm, the ear clipping cannot - // introduce touching segments. However, the original data may have some. - if (!this.resolveLocalSelfIntersections_(list, rtree, true)) { - // Something went wrong. - break; - } - } - } - } else { - if (!this.clipEars_(list, rtree, simple, ccw)) { - // We ran out of ears, try to reclassify. - if (!this.classifyPoints_(list, rtree, ccw)) { - // We have a bad polygon, try to resolve local self-intersections. - if (!this.resolveLocalSelfIntersections_(list, rtree)) { - simple = this.isSimple_(list, rtree); - if (!simple) { - // We have a really bad polygon, try more time consuming methods. - this.splitPolygon_(list, rtree); - break; - } else { - ccw = !this.isClockwise_(list); - this.classifyPoints_(list, rtree, ccw); - } - } - } - } - } - } - if (list.getLength() === 3) { - var numIndices = this.indices_.length; - this.indices_[numIndices++] = list.getPrevItem().p0.i; - this.indices_[numIndices++] = list.getCurrItem().p0.i; - this.indices_[numIndices++] = list.getNextItem().p0.i; - } -}; - - -/** - * @private - * @param {ol.structs.LinkedList} list Linked list of the polygon. - * @param {ol.structs.RBush} rtree R-Tree of the polygon. - * @param {boolean} simple The polygon is simple. - * @param {boolean} ccw Orientation of the polygon is counter-clockwise. - * @return {boolean} There were processed ears. - */ -ol.render.webgl.PolygonReplay.prototype.clipEars_ = function(list, rtree, simple, ccw) { - var numIndices = this.indices_.length; - var start = list.firstItem(); - var s0 = list.getPrevItem(); - var s1 = start; - var s2 = list.nextItem(); - var s3 = list.getNextItem(); - var p0, p1, p2; - var processedEars = false; - do { - p0 = s1.p0; - p1 = s1.p1; - p2 = s2.p1; - if (p1.reflex === false) { - // We might have a valid ear - var diagonalIsInside = ccw ? this.diagonalIsInside_(s3.p1, p2, p1, p0, - s0.p0) : this.diagonalIsInside_(s0.p0, p0, p1, p2, s3.p1); - if ((simple || this.getIntersections_({p0: p0, p1: p2}, rtree).length === 0) && - diagonalIsInside && this.getPointsInTriangle_(p0, p1, p2, rtree, true).length === 0) { - //The diagonal is completely inside the polygon - if (simple || p0.reflex === false || p2.reflex === false || - ol.geom.flat.orient.linearRingIsClockwise([s0.p0.x, s0.p0.y, p0.x, - p0.y, p1.x, p1.y, p2.x, p2.y, s3.p1.x, s3.p1.y], 0, 10, 2) === !ccw) { - //The diagonal is persumably valid, we have an ear - this.indices_[numIndices++] = p0.i; - this.indices_[numIndices++] = p1.i; - this.indices_[numIndices++] = p2.i; - this.removeItem_(s1, s2, list, rtree); - if (s2 === start) { - start = s3; - } - processedEars = true; - } - } - } - // Else we have a reflex point. - s0 = list.getPrevItem(); - s1 = list.getCurrItem(); - s2 = list.nextItem(); - s3 = list.getNextItem(); - } while (s1 !== start && list.getLength() > 3); - - return processedEars; -}; - - -/** - * @private - * @param {ol.structs.LinkedList} list Linked list of the polygon. - * @param {ol.structs.RBush} rtree R-Tree of the polygon. - * @param {boolean=} opt_touch Resolve touching segments. - * @return {boolean} There were resolved intersections. -*/ -ol.render.webgl.PolygonReplay.prototype.resolveLocalSelfIntersections_ = function( - list, rtree, opt_touch) { - var start = list.firstItem(); - list.nextItem(); - var s0 = start; - var s1 = list.nextItem(); - var resolvedIntersections = false; - - do { - var intersection = this.calculateIntersection_(s0.p0, s0.p1, s1.p0, s1.p1, - opt_touch); - if (intersection) { - var breakCond = false; - var numVertices = this.vertices_.length; - var numIndices = this.indices_.length; - var n = numVertices / 2; - var seg = list.prevItem(); - list.removeItem(); - rtree.remove(seg); - breakCond = (seg === start); - var p; - if (opt_touch) { - if (intersection[0] === s0.p0.x && intersection[1] === s0.p0.y) { - list.prevItem(); - p = s0.p0; - s1.p0 = p; - rtree.remove(s0); - breakCond = breakCond || (s0 === start); - } else { - p = s1.p1; - s0.p1 = p; - rtree.remove(s1); - breakCond = breakCond || (s1 === start); - } - list.removeItem(); - } else { - p = this.createPoint_(intersection[0], intersection[1], n); - s0.p1 = p; - s1.p0 = p; - rtree.update([Math.min(s0.p0.x, s0.p1.x), Math.min(s0.p0.y, s0.p1.y), - Math.max(s0.p0.x, s0.p1.x), Math.max(s0.p0.y, s0.p1.y)], s0); - rtree.update([Math.min(s1.p0.x, s1.p1.x), Math.min(s1.p0.y, s1.p1.y), - Math.max(s1.p0.x, s1.p1.x), Math.max(s1.p0.y, s1.p1.y)], s1); - } - - this.indices_[numIndices++] = seg.p0.i; - this.indices_[numIndices++] = seg.p1.i; - this.indices_[numIndices++] = p.i; - - resolvedIntersections = true; - if (breakCond) { - break; - } - } - - s0 = list.getPrevItem(); - s1 = list.nextItem(); - } while (s0 !== start); - return resolvedIntersections; -}; - - -/** - * @private - * @param {ol.structs.LinkedList} list Linked list of the polygon. - * @param {ol.structs.RBush} rtree R-Tree of the polygon. - * @return {boolean} The polygon is simple. - */ -ol.render.webgl.PolygonReplay.prototype.isSimple_ = function(list, rtree) { - var start = list.firstItem(); - var seg = start; - do { - if (this.getIntersections_(seg, rtree).length) { - return false; - } - seg = list.nextItem(); - } while (seg !== start); - return true; -}; - - -/** - * @private - * @param {ol.structs.LinkedList} list Linked list of the polygon. - * @return {boolean} Orientation is clockwise. - */ -ol.render.webgl.PolygonReplay.prototype.isClockwise_ = function(list) { - var length = list.getLength() * 2; - var flatCoordinates = new Array(length); - var start = list.firstItem(); - var seg = start; - var i = 0; - do { - flatCoordinates[i++] = seg.p0.x; - flatCoordinates[i++] = seg.p0.y; - seg = list.nextItem(); - } while (seg !== start); - return ol.geom.flat.orient.linearRingIsClockwise(flatCoordinates, 0, length, 2); -}; - - -/** - * @private - * @param {ol.structs.LinkedList} list Linked list of the polygon. - * @param {ol.structs.RBush} rtree R-Tree of the polygon. - */ -ol.render.webgl.PolygonReplay.prototype.splitPolygon_ = function(list, rtree) { - var start = list.firstItem(); - var s0 = start; - do { - var intersections = this.getIntersections_(s0, rtree); - if (intersections.length) { - var s1 = intersections[0]; - var n = this.vertices_.length / 2; - var intersection = this.calculateIntersection_(s0.p0, - s0.p1, s1.p0, s1.p1); - var p = this.createPoint_(intersection[0], intersection[1], n); - var newPolygon = new ol.structs.LinkedList(); - var newRtree = new ol.structs.RBush(); - this.insertItem_(p, s0.p1, newPolygon, newRtree); - s0.p1 = p; - rtree.update([Math.min(s0.p0.x, p.x), Math.min(s0.p0.y, p.y), - Math.max(s0.p0.x, p.x), Math.max(s0.p0.y, p.y)], s0); - var currItem = list.nextItem(); - while (currItem !== s1) { - this.insertItem_(currItem.p0, currItem.p1, newPolygon, newRtree); - rtree.remove(currItem); - list.removeItem(); - currItem = list.getCurrItem(); - } - this.insertItem_(s1.p0, p, newPolygon, newRtree); - s1.p0 = p; - rtree.update([Math.min(s1.p1.x, p.x), Math.min(s1.p1.y, p.y), - Math.max(s1.p1.x, p.x), Math.max(s1.p1.y, p.y)], s1); - this.classifyPoints_(list, rtree, false); - this.triangulate_(list, rtree); - this.classifyPoints_(newPolygon, newRtree, false); - this.triangulate_(newPolygon, newRtree); - break; - } - s0 = list.nextItem(); - } while (s0 !== start); -}; - - -/** - * @private - * @param {number} x X coordinate. - * @param {number} y Y coordinate. - * @param {number} i Index. - * @return {ol.WebglPolygonVertex} List item. - */ -ol.render.webgl.PolygonReplay.prototype.createPoint_ = function(x, y, i) { - var numVertices = this.vertices_.length; - this.vertices_[numVertices++] = x; - this.vertices_[numVertices++] = y; - /** @type {ol.WebglPolygonVertex} */ - var p = { - x: x, - y: y, - i: i, - reflex: undefined - }; - return p; -}; - - -/** - * @private - * @param {ol.WebglPolygonVertex} p0 First point of segment. - * @param {ol.WebglPolygonVertex} p1 Second point of segment. - * @param {ol.structs.LinkedList} list Polygon ring. - * @param {ol.structs.RBush=} opt_rtree Insert the segment into the R-Tree. - * @return {ol.WebglPolygonSegment} segment. - */ -ol.render.webgl.PolygonReplay.prototype.insertItem_ = function(p0, p1, list, opt_rtree) { - var seg = { - p0: p0, - p1: p1 - }; - list.insertItem(seg); - if (opt_rtree) { - opt_rtree.insert([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y), - Math.max(p0.x, p1.x), Math.max(p0.y, p1.y)], seg); - } - return seg; -}; - - - /** - * @private - * @param {ol.WebglPolygonSegment} s0 Segment before the remove candidate. - * @param {ol.WebglPolygonSegment} s1 Remove candidate segment. - * @param {ol.structs.LinkedList} list Polygon ring. - * @param {ol.structs.RBush} rtree R-Tree of the polygon. - */ -ol.render.webgl.PolygonReplay.prototype.removeItem_ = function(s0, s1, list, rtree) { - list.removeItem(); - s0.p1 = s1.p1; - rtree.remove(s1); - rtree.update([Math.min(s0.p0.x, s0.p1.x), Math.min(s0.p0.y, s0.p1.y), - Math.max(s0.p0.x, s0.p1.x), Math.max(s0.p0.y, s0.p1.y)], s0); -}; - - -/** - * @private - * @param {ol.WebglPolygonVertex} p0 First point. - * @param {ol.WebglPolygonVertex} p1 Second point. - * @param {ol.WebglPolygonVertex} p2 Third point. - * @param {ol.structs.RBush} rtree R-Tree of the polygon. - * @param {boolean=} opt_reflex Only include reflex points. - * @return {Array.} Points in the triangle. - */ -ol.render.webgl.PolygonReplay.prototype.getPointsInTriangle_ = function(p0, p1, - p2, rtree, opt_reflex) { - var i, ii, j, p; - var result = []; - var segmentsInExtent = rtree.getInExtent([Math.min(p0.x, p1.x, p2.x), - Math.min(p0.y, p1.y, p2.y), Math.max(p0.x, p1.x, p2.x), Math.max(p0.y, - p1.y, p2.y)]); - for (i = 0, ii = segmentsInExtent.length; i < ii; ++i) { - for (j in segmentsInExtent[i]) { - p = segmentsInExtent[i][j]; - if (p.x && p.y && (!opt_reflex || p.reflex)) { - if ((p.x !== p0.x || p.y !== p0.y) && (p.x !== p1.x || p.y !== p1.y) && - (p.x !== p2.x || p.y !== p2.y) && result.indexOf(p) === -1 && - ol.geom.flat.contains.linearRingContainsXY([p0.x, p0.y, p1.x, p1.y, - p2.x, p2.y], 0, 6, 2, p.x, p.y)) { - result.push(p); - } - } - } - } - return result; -}; - - -/** - * @private - * @param {ol.WebglPolygonSegment} segment Segment. - * @param {ol.structs.RBush} rtree R-Tree of the polygon. - * @param {boolean=} opt_touch Touching segments should be considered an intersection. - * @return {Array.} Intersecting segments. - */ -ol.render.webgl.PolygonReplay.prototype.getIntersections_ = function(segment, rtree, opt_touch) { - var p0 = segment.p0; - var p1 = segment.p1; - var segmentsInExtent = rtree.getInExtent([Math.min(p0.x, p1.x), - Math.min(p0.y, p1.y), Math.max(p0.x, p1.x), Math.max(p0.y, p1.y)]); - var result = []; - var i, ii; - for (i = 0, ii = segmentsInExtent.length; i < ii; ++i) { - var currSeg = segmentsInExtent[i]; - if (segment !== currSeg && (opt_touch || currSeg.p0 !== p1 || currSeg.p1 !== p0) && - this.calculateIntersection_(p0, p1, currSeg.p0, currSeg.p1, opt_touch)) { - result.push(currSeg); - } - } - return result; -}; - - -/** - * Line intersection algorithm by Paul Bourke. - * @see http://paulbourke.net/geometry/pointlineplane/ - * - * @private - * @param {ol.WebglPolygonVertex} p0 First point. - * @param {ol.WebglPolygonVertex} p1 Second point. - * @param {ol.WebglPolygonVertex} p2 Third point. - * @param {ol.WebglPolygonVertex} p3 Fourth point. - * @param {boolean=} opt_touch Touching segments should be considered an intersection. - * @return {Array.|undefined} Intersection coordinates. - */ -ol.render.webgl.PolygonReplay.prototype.calculateIntersection_ = function(p0, - p1, p2, p3, opt_touch) { - var denom = (p3.y - p2.y) * (p1.x - p0.x) - (p3.x - p2.x) * (p1.y - p0.y); - if (denom !== 0) { - var ua = ((p3.x - p2.x) * (p0.y - p2.y) - (p3.y - p2.y) * (p0.x - p2.x)) / denom; - var ub = ((p1.x - p0.x) * (p0.y - p2.y) - (p1.y - p0.y) * (p0.x - p2.x)) / denom; - if ((!opt_touch && ua > ol.render.webgl.EPSILON && ua < 1 - ol.render.webgl.EPSILON && - ub > ol.render.webgl.EPSILON && ub < 1 - ol.render.webgl.EPSILON) || (opt_touch && - ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1)) { - return [p0.x + ua * (p1.x - p0.x), p0.y + ua * (p1.y - p0.y)]; - } - } - return undefined; -}; - - -/** - * @private - * @param {ol.WebglPolygonVertex} p0 Point before the start of the diagonal. - * @param {ol.WebglPolygonVertex} p1 Start point of the diagonal. - * @param {ol.WebglPolygonVertex} p2 Ear candidate. - * @param {ol.WebglPolygonVertex} p3 End point of the diagonal. - * @param {ol.WebglPolygonVertex} p4 Point after the end of the diagonal. - * @return {boolean} Diagonal is inside the polygon. - */ -ol.render.webgl.PolygonReplay.prototype.diagonalIsInside_ = function(p0, p1, p2, p3, p4) { - if (p1.reflex === undefined || p3.reflex === undefined) { - return false; - } - var p1IsLeftOf = (p2.x - p3.x) * (p1.y - p3.y) > (p2.y - p3.y) * (p1.x - p3.x); - var p1IsRightOf = (p4.x - p3.x) * (p1.y - p3.y) < (p4.y - p3.y) * (p1.x - p3.x); - var p3IsLeftOf = (p0.x - p1.x) * (p3.y - p1.y) > (p0.y - p1.y) * (p3.x - p1.x); - var p3IsRightOf = (p2.x - p1.x) * (p3.y - p1.y) < (p2.y - p1.y) * (p3.x - p1.x); - var p1InCone = p3.reflex ? p1IsRightOf || p1IsLeftOf : p1IsRightOf && p1IsLeftOf; - var p3InCone = p1.reflex ? p3IsRightOf || p3IsLeftOf : p3IsRightOf && p3IsLeftOf; - return p1InCone && p3InCone; -}; - - -/** - * @inheritDoc - */ -ol.render.webgl.PolygonReplay.prototype.drawMultiPolygon = function(multiPolygonGeometry, feature) { - var polygons = multiPolygonGeometry.getPolygons(); - var stride = multiPolygonGeometry.getStride(); - var currIndex = this.indices_.length; - var currLineIndex = this.lineStringReplay_.indices_.length; - var i, ii, j, jj; - for (i = 0, ii = polygons.length; i < ii; ++i) { - var linearRings = polygons[i].getLinearRings(); - if (linearRings.length > 0) { - var flatCoordinates = linearRings[0].getFlatCoordinates(); - flatCoordinates = ol.geom.flat.transform.translate(flatCoordinates, 0, flatCoordinates.length, - stride, -this.origin_[0], -this.origin_[1]); - this.lineStringReplay_.drawCoordinates_(flatCoordinates, 0, flatCoordinates.length, stride); - var holes = []; - var holeFlatCoords; - for (j = 1, jj = linearRings.length; j < jj; ++j) { - holeFlatCoords = linearRings[j].getFlatCoordinates(); - holeFlatCoords = ol.geom.flat.transform.translate(holeFlatCoords, 0, holeFlatCoords.length, - stride, -this.origin_[0], -this.origin_[1]); - holes.push(holeFlatCoords); - this.lineStringReplay_.drawCoordinates_(holeFlatCoords, 0, holeFlatCoords.length, stride); - } - this.drawCoordinates_(flatCoordinates, holes, stride); - } - } - if (this.indices_.length > currIndex) { - this.startIndices_.push(currIndex); - this.startIndicesFeature_.push(feature); - if (this.state_.changed) { - this.styleIndices_.push(currIndex); - this.state_.changed = false; - } - } - if (this.lineStringReplay_.indices_.length > currLineIndex) { - this.lineStringReplay_.startIndices_.push(currLineIndex); - this.lineStringReplay_.startIndicesFeature_.push(feature); - if (this.lineStringReplay_.state_.changed) { - this.lineStringReplay_.styleIndices_.push(currLineIndex); - this.lineStringReplay_.state_.changed = false; - } - } -}; - - -/** - * @inheritDoc - */ -ol.render.webgl.PolygonReplay.prototype.drawPolygon = function(polygonGeometry, feature) { - var linearRings = polygonGeometry.getLinearRings(); - var stride = polygonGeometry.getStride(); - if (linearRings.length > 0) { - this.startIndices_.push(this.indices_.length); - this.startIndicesFeature_.push(feature); - if (this.state_.changed) { - this.styleIndices_.push(this.indices_.length); - this.state_.changed = false; - } - - this.lineStringReplay_.startIndices_.push(this.lineStringReplay_.indices_.length); - this.lineStringReplay_.startIndicesFeature_.push(feature); - if (this.lineStringReplay_.state_.changed) { - this.lineStringReplay_.styleIndices_.push(this.lineStringReplay_.indices_.length); - this.lineStringReplay_.state_.changed = false; - } - - var flatCoordinates = linearRings[0].getFlatCoordinates(); - flatCoordinates = ol.geom.flat.transform.translate(flatCoordinates, 0, flatCoordinates.length, - stride, -this.origin_[0], -this.origin_[1]); - this.lineStringReplay_.drawCoordinates_(flatCoordinates, 0, flatCoordinates.length, stride); - var holes = []; - var i, ii, holeFlatCoords; - for (i = 1, ii = linearRings.length; i < ii; ++i) { - holeFlatCoords = linearRings[i].getFlatCoordinates(); - holeFlatCoords = ol.geom.flat.transform.translate(holeFlatCoords, 0, holeFlatCoords.length, - stride, -this.origin_[0], -this.origin_[1]); - holes.push(holeFlatCoords); - this.lineStringReplay_.drawCoordinates_(holeFlatCoords, 0, holeFlatCoords.length, stride); - } - this.drawCoordinates_(flatCoordinates, holes, stride); - } -}; - - -/** - * @param {ol.webgl.Context} context Context. - **/ -ol.render.webgl.PolygonReplay.prototype.finish = function(context) { - // create, bind, and populate the vertices buffer - this.verticesBuffer_ = new ol.webgl.Buffer(this.vertices_); - context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.verticesBuffer_); - - // create, bind, and populate the indices buffer - this.indicesBuffer_ = new ol.webgl.Buffer(this.indices_); - context.bindBuffer(ol.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer_); - - this.startIndices_.push(this.indices_.length); - - this.lineStringReplay_.finish(context); - - //Clean up, if there is nothing to draw - if (this.styleIndices_.length === 0 && this.styles_.length > 0) { - this.styles_ = []; - } - - this.vertices_ = null; - this.indices_ = null; -}; - - -/** - * @param {ol.webgl.Context} context WebGL context. - * @return {function()} Delete resources function. - */ -ol.render.webgl.PolygonReplay.prototype.getDeleteResourcesFunction = function(context) { - // We only delete our stuff here. The shaders and the program may - // be used by other PolygonReplay instances (for other layers). And - // they will be deleted when disposing of the ol.webgl.Context - // object. - goog.asserts.assert(!goog.isNull(this.verticesBuffer_), - 'verticesBuffer must not be null'); - goog.asserts.assert(!goog.isNull(this.indicesBuffer_), - 'indicesBuffer must not be null'); - var verticesBuffer = this.verticesBuffer_; - var indicesBuffer = this.indicesBuffer_; - var lineDeleter = this.lineStringReplay_.getDeleteResourcesFunction(context); - return function() { - context.deleteBuffer(verticesBuffer); - context.deleteBuffer(indicesBuffer); - lineDeleter(); - }; -}; - - -/** - * @private - * @param {WebGLRenderingContext} gl gl. - * @param {ol.webgl.Context} context Context. - * @param {ol.Size} size Size. - * @param {number} pixelRatio Pixel ratio. - * @return {ol.render.webgl.polygonreplay.shader.Default.Locations} Locations. - */ -ol.render.webgl.PolygonReplay.prototype.setUpProgram_ = function(gl, context, size, pixelRatio) { - // get the program - var fragmentShader, vertexShader; - fragmentShader = - ol.render.webgl.polygonreplay.shader.DefaultFragment.getInstance(); - vertexShader = - ol.render.webgl.polygonreplay.shader.DefaultVertex.getInstance(); - var program = context.getProgram(fragmentShader, vertexShader); - - // get the locations - var locations; - if (!this.defaultLocations_) { - locations = new ol.render.webgl.polygonreplay.shader.Default - .Locations(gl, program); - this.defaultLocations_ = locations; - } else { - locations = this.defaultLocations_; - } - - context.useProgram(program); - - // enable the vertex attrib arrays - gl.enableVertexAttribArray(locations.a_position); - gl.vertexAttribPointer(locations.a_position, 2, ol.webgl.FLOAT, - false, 8, 0); - - return locations; -}; - - -/** - * @private - * @param {WebGLRenderingContext} gl gl. - * @param {ol.webgl.Context} context Context. - * @param {Object} skippedFeaturesHash Ids of features to skip. - * @param {boolean} hitDetection Hit detection mode. - */ -ol.render.webgl.PolygonReplay.prototype.drawReplay_ = function(gl, context, skippedFeaturesHash, hitDetection) { - //Save GL parameters. - var tmpDepthFunc = /** @type {number} */ (gl.getParameter(gl.DEPTH_FUNC)); - var tmpDepthMask = /** @type {boolean} */ (gl.getParameter(gl.DEPTH_WRITEMASK)); - - if (!hitDetection) { - gl.enable(gl.DEPTH_TEST); - gl.depthMask(true); - gl.depthFunc(gl.NOTEQUAL); - } - - if (!ol.object.isEmpty(skippedFeaturesHash)) { - this.drawReplaySkipping_(gl, context, skippedFeaturesHash); - } else { - goog.asserts.assert(this.styles_.length === this.styleIndices_.length, - 'number of styles and styleIndices match'); - - //Draw by style groups to minimize drawElements() calls. - var i, start, end, nextStyle; - end = this.startIndices_[this.startIndices_.length - 1]; - for (i = this.styleIndices_.length - 1; i >= 0; --i) { - start = this.styleIndices_[i]; - nextStyle = this.styles_[i]; - this.setFillStyle_(gl, nextStyle); - this.drawElements_(gl, context, start, end); - end = start; - } - } - if (!hitDetection) { - gl.clear(gl.DEPTH_BUFFER_BIT); - gl.disable(gl.DEPTH_TEST); - //Restore GL parameters. - gl.depthMask(tmpDepthMask); - gl.depthFunc(tmpDepthFunc); - } -}; - - -/** - * @private - * @param {WebGLRenderingContext} gl gl. - * @param {ol.webgl.Context} context Context. - * @param {Object.} skippedFeaturesHash Ids of features - * to skip. - * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback. - * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting - * this extent are checked. - * @return {T|undefined} Callback result. - * @template T - */ -ol.render.webgl.PolygonReplay.prototype.drawHitDetectionReplayOneByOne_ = function(gl, context, skippedFeaturesHash, - featureCallback, opt_hitExtent) { - goog.asserts.assert(this.styles_.length === this.styleIndices_.length, - 'number of styles and styleIndices match'); - goog.asserts.assert(this.startIndices_.length - 1 === this.startIndicesFeature_.length, - 'number of startIndices and startIndicesFeature match'); - - var i, start, end, nextStyle, groupStart, feature, featureUid, featureIndex; - featureIndex = this.startIndices_.length - 2; - end = this.startIndices_[featureIndex + 1]; - for (i = this.styleIndices_.length - 1; i >= 0; --i) { - nextStyle = this.styles_[i]; - this.setFillStyle_(gl, nextStyle); - groupStart = this.styleIndices_[i]; - - while (featureIndex >= 0 && - this.startIndices_[featureIndex] >= groupStart) { - start = this.startIndices_[featureIndex]; - feature = this.startIndicesFeature_[featureIndex]; - featureUid = goog.getUid(feature).toString(); - - if (skippedFeaturesHash[featureUid] === undefined && - feature.getGeometry() && - (opt_hitExtent === undefined || ol.extent.intersects( - /** @type {Array} */ (opt_hitExtent), - feature.getGeometry().getExtent()))) { - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - this.drawElements_(gl, context, start, end); - - var result = featureCallback(feature); - - if (result) { - return result; - } - - } - featureIndex--; - end = start; - } - } - return undefined; -}; - - -/** - * @private - * @param {WebGLRenderingContext} gl gl. - * @param {ol.webgl.Context} context Context. - * @param {Object} skippedFeaturesHash Ids of features to skip. - */ -ol.render.webgl.PolygonReplay.prototype.drawReplaySkipping_ = function(gl, context, skippedFeaturesHash) { - goog.asserts.assert(this.startIndices_.length - 1 === this.startIndicesFeature_.length, - 'number of startIndices and startIndicesFeature match'); - - var i, start, end, nextStyle, groupStart, feature, featureUid, featureIndex, featureStart; - featureIndex = this.startIndices_.length - 2; - end = start = this.startIndices_[featureIndex + 1]; - for (i = this.styleIndices_.length - 1; i >= 0; --i) { - nextStyle = this.styles_[i]; - this.setFillStyle_(gl, nextStyle); - groupStart = this.styleIndices_[i]; - - while (featureIndex >= 0 && - this.startIndices_[featureIndex] >= groupStart) { - featureStart = this.startIndices_[featureIndex]; - feature = this.startIndicesFeature_[featureIndex]; - featureUid = goog.getUid(feature).toString(); - - if (skippedFeaturesHash[featureUid]) { - if (start !== end) { - this.drawElements_(gl, context, start, end); - } - end = featureStart; - } - featureIndex--; - start = featureStart; - } - if (start !== end) { - this.drawElements_(gl, context, start, end); - } - } -}; - - -/** - * @private - * @param {WebGLRenderingContext} gl gl. - * @param {Array.} color Color. - */ -ol.render.webgl.PolygonReplay.prototype.setFillStyle_ = function(gl, color) { - gl.uniform4fv(this.defaultLocations_.u_color, color); -}; - - -/** - * @inheritDoc - */ - -ol.render.webgl.PolygonReplay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) { - goog.asserts.assert(this.state_, 'this.state_ should not be null'); - goog.asserts.assert(fillStyle || strokeStyle, 'one of the styles should not be null'); - var fillStyleColor = fillStyle ? fillStyle.getColor() : [0, 0, 0, 0]; - if (!(fillStyleColor instanceof CanvasGradient) && - !(fillStyleColor instanceof CanvasPattern)) { - fillStyleColor = ol.color.asArray(fillStyleColor).map(function(c, i) { - return i != 3 ? c / 255 : c; - }) || ol.render.webgl.defaultFillStyle; - } else { - fillStyleColor = ol.render.webgl.defaultFillStyle; - } - if (!this.state_.fillColor || !ol.array.equals(fillStyleColor, this.state_.fillColor)) { - this.state_.fillColor = fillStyleColor; - this.state_.changed = true; - this.styles_.push(fillStyleColor); - } - //Provide a null stroke style, if no strokeStyle is provided. Required for the draw interaction to work. - if (strokeStyle) { - this.lineStringReplay_.setFillStrokeStyle(null, strokeStyle); - } else { - var nullStrokeStyle = new ol.style.Stroke({ - color: [0, 0, 0, 0], - lineWidth: 0 - }); - this.lineStringReplay_.setFillStrokeStyle(null, nullStrokeStyle); - } -}; - - -/** - * @constructor - * @extends {ol.render.ReplayGroup} - * @param {number} tolerance Tolerance. - * @param {ol.Extent} maxExtent Max extent. - * @param {number=} opt_renderBuffer Render buffer. - * @struct - */ -ol.render.webgl.ReplayGroup = function(tolerance, maxExtent, opt_renderBuffer) { - ol.render.ReplayGroup.call(this); - - /** - * @type {ol.Extent} - * @private - */ - this.maxExtent_ = maxExtent; - - /** - * @type {number} - * @private - */ - this.tolerance_ = tolerance; - - /** - * @type {number|undefined} - * @private - */ - this.renderBuffer_ = opt_renderBuffer; - - /** - * ImageReplay only is supported at this point. - * @type {Object.} - * @private - */ - this.replays_ = {}; - -}; -ol.inherits(ol.render.webgl.ReplayGroup, ol.render.ReplayGroup); - - -/** - * @param {ol.webgl.Context} context WebGL context. - * @return {function()} Delete resources function. - */ -ol.render.webgl.ReplayGroup.prototype.getDeleteResourcesFunction = function(context) { - var functions = []; - var replayKey; - for (replayKey in this.replays_) { - functions.push( - this.replays_[replayKey].getDeleteResourcesFunction(context)); - } - return function() { - var length = functions.length; - var result; - for (var i = 0; i < length; i++) { - result = functions[i].apply(this, arguments); - } - return result; - }; -}; - - -/** - * @param {ol.webgl.Context} context Context. - */ -ol.render.webgl.ReplayGroup.prototype.finish = function(context) { - var replayKey; - for (replayKey in this.replays_) { - this.replays_[replayKey].finish(context); - } -}; - - -/** - * @inheritDoc - */ -ol.render.webgl.ReplayGroup.prototype.getReplay = function(zIndex, replayType) { - var replay = this.replays_[replayType]; - if (replay === undefined) { - var constructor = ol.render.webgl.BATCH_CONSTRUCTORS_[replayType]; - replay = new constructor(this.tolerance_, this.maxExtent_); - this.replays_[replayType] = replay; - } - return replay; -}; - - -/** - * @inheritDoc - */ -ol.render.webgl.ReplayGroup.prototype.isEmpty = function() { - return ol.obj.isEmpty(this.replays_); -}; - - -/** - * @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 {Object.} skippedFeaturesHash Ids of features - * to skip. - */ -ol.render.webgl.ReplayGroup.prototype.replay = function(context, - center, resolution, rotation, size, pixelRatio, - opacity, skippedFeaturesHash) { - var i, ii, replay; - for (i = 0, ii = ol.render.replay.ORDER.length; i < ii; ++i) { - replay = this.replays_[ol.render.replay.ORDER[i]]; - if (replay !== undefined) { - replay.replay(context, - center, resolution, rotation, size, pixelRatio, - opacity, skippedFeaturesHash, - undefined, false); - } - } -}; - - -/** - * @private - * @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 {Object.} skippedFeaturesHash Ids of features - * to skip. - * @param {function((ol.Feature|ol.render.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.ReplayGroup.prototype.replayHitDetection_ = function(context, - center, resolution, rotation, size, pixelRatio, opacity, - skippedFeaturesHash, 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]]; - if (replay !== undefined) { - result = replay.replay(context, - center, resolution, rotation, size, pixelRatio, opacity, - skippedFeaturesHash, featureCallback, oneByOne, opt_hitExtent); - if (result) { - return result; - } - } - } - return undefined; -}; - - -/** - * @param {ol.Coordinate} coordinate Coordinate. - * @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 {Object.} skippedFeaturesHash Ids of features - * to skip. - * @param {function((ol.Feature|ol.render.Feature)): T|undefined} callback Feature callback. - * @return {T|undefined} Callback result. - * @template T - */ -ol.render.webgl.ReplayGroup.prototype.forEachFeatureAtCoordinate = function( - coordinate, context, center, resolution, rotation, size, pixelRatio, - opacity, skippedFeaturesHash, - callback) { - var gl = context.getGL(); - gl.bindFramebuffer( - gl.FRAMEBUFFER, context.getHitDetectionFramebuffer()); - - - /** - * @type {ol.Extent} - */ - var hitExtent; - if (this.renderBuffer_ !== undefined) { - // build an extent around the coordinate, so that only features that - // intersect this extent are checked - hitExtent = ol.extent.buffer( - ol.extent.createOrUpdateFromCoordinate(coordinate), - resolution * this.renderBuffer_); - } - - return this.replayHitDetection_(context, - coordinate, resolution, rotation, ol.render.webgl.HIT_DETECTION_SIZE_, - pixelRatio, opacity, skippedFeaturesHash, - /** - * @param {ol.Feature|ol.render.Feature} feature Feature. - * @return {?} Callback result. - */ - function(feature) { - var imageData = new Uint8Array(4); - gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, imageData); - - if (imageData[3] > 0) { - var result = callback(feature); - if (result) { - return result; - } - } - }, true, hitExtent); -}; - - -/** - * @param {ol.Coordinate} coordinate Coordinate. - * @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 {Object.} skippedFeaturesHash Ids of features - * to skip. - * @return {boolean} Is there a feature at the given coordinate? - */ -ol.render.webgl.ReplayGroup.prototype.hasFeatureAtCoordinate = function( - coordinate, context, center, resolution, rotation, size, pixelRatio, - opacity, skippedFeaturesHash) { - 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, skippedFeaturesHash, - /** - * @param {ol.Feature|ol.render.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 hasFeature !== undefined; -}; - - -/** - * @const - * @private - * @type {Object.} - */ -ol.render.webgl.BATCH_CONSTRUCTORS_ = { - 'Image': ol.render.webgl.ImageReplay, - 'LineString': ol.render.webgl.LineStringReplay, - 'Polygon': ol.render.webgl.PolygonReplay -}; - - -/** - * @const - * @private - * @type {Array.} - */ -ol.render.webgl.HIT_DETECTION_SIZE_ = [1, 1]; diff --git a/src/ol/render/webgl/webgl.js b/src/ol/render/webgl/index.js similarity index 100% rename from src/ol/render/webgl/webgl.js rename to src/ol/render/webgl/index.js diff --git a/src/ol/render/webgl/webgl.jsdoc b/src/ol/render/webgl/index.jsdoc similarity index 100% rename from src/ol/render/webgl/webgl.jsdoc rename to src/ol/render/webgl/index.jsdoc diff --git a/src/ol/render/webgl/webgllinestringdefault.glsl b/src/ol/render/webgl/linestringreplay/defaultshader.glsl similarity index 97% rename from src/ol/render/webgl/webgllinestringdefault.glsl rename to src/ol/render/webgl/linestringreplay/defaultshader.glsl index 72edd05366..e18f2f70eb 100644 --- a/src/ol/render/webgl/webgllinestringdefault.glsl +++ b/src/ol/render/webgl/linestringreplay/defaultshader.glsl @@ -1,5 +1,5 @@ -//! NAMESPACE=ol.render.webgl.linestringreplay.shader.Default -//! CLASS=ol.render.webgl.linestringreplay.shader.Default +//! NAMESPACE=ol.render.webgl.linestringreplay.defaultshader +//! CLASS=ol.render.webgl.linestringreplay.defaultshader //! COMMON diff --git a/src/ol/render/webgl/webgllinestringdefaultshader.js b/src/ol/render/webgl/linestringreplay/defaultshader.js similarity index 80% rename from src/ol/render/webgl/webgllinestringdefaultshader.js rename to src/ol/render/webgl/linestringreplay/defaultshader.js index bec5b5baaf..cf48042084 100644 --- a/src/ol/render/webgl/webgllinestringdefaultshader.js +++ b/src/ol/render/webgl/linestringreplay/defaultshader.js @@ -1,80 +1,83 @@ // This file is automatically generated, do not edit -goog.provide('ol.render.webgl.linestringreplay.shader.Default'); -goog.provide('ol.render.webgl.linestringreplay.shader.Default.Locations'); -goog.provide('ol.render.webgl.linestringreplay.shader.DefaultFragment'); -goog.provide('ol.render.webgl.linestringreplay.shader.DefaultVertex'); +goog.provide('ol.render.webgl.linestringreplay.defaultshader'); -goog.require('ol.webgl.shader'); +goog.require('ol'); +goog.require('ol.webgl.Fragment'); +goog.require('ol.webgl.Vertex'); /** * @constructor - * @extends {ol.webgl.shader.Fragment} + * @extends {ol.webgl.Fragment} * @struct */ -ol.render.webgl.linestringreplay.shader.DefaultFragment = function() { - ol.webgl.shader.Fragment.call(this, ol.render.webgl.linestringreplay.shader.DefaultFragment.SOURCE); +ol.render.webgl.linestringreplay.defaultshader.Fragment = function() { + ol.webgl.Fragment.call(this, ol.render.webgl.linestringreplay.defaultshader.Fragment.SOURCE); }; -ol.inherits(ol.render.webgl.linestringreplay.shader.DefaultFragment, ol.webgl.shader.Fragment); -goog.addSingletonGetter(ol.render.webgl.linestringreplay.shader.DefaultFragment); +ol.inherits(ol.render.webgl.linestringreplay.defaultshader.Fragment, ol.webgl.Fragment); /** * @const * @type {string} */ -ol.render.webgl.linestringreplay.shader.DefaultFragment.DEBUG_SOURCE = 'precision mediump float;\nvarying float v_round;\nvarying vec2 v_roundVertex;\nvarying float v_halfWidth;\n\n\n\nuniform float u_opacity;\nuniform vec4 u_color;\nuniform vec2 u_size;\nuniform float u_pixelRatio;\n\nvoid main(void) {\n if (v_round > 0.0) {\n vec2 windowCoords = vec2((v_roundVertex.x + 1.0) / 2.0 * u_size.x * u_pixelRatio,\n (v_roundVertex.y + 1.0) / 2.0 * u_size.y * u_pixelRatio);\n if (length(windowCoords - gl_FragCoord.xy) > v_halfWidth * u_pixelRatio) {\n discard;\n }\n }\n gl_FragColor = u_color;\n float alpha = u_color.a * u_opacity;\n if (alpha == 0.0) {\n discard;\n }\n gl_FragColor.a = alpha;\n}\n'; +ol.render.webgl.linestringreplay.defaultshader.Fragment.DEBUG_SOURCE = 'precision mediump float;\nvarying float v_round;\nvarying vec2 v_roundVertex;\nvarying float v_halfWidth;\n\n\n\nuniform float u_opacity;\nuniform vec4 u_color;\nuniform vec2 u_size;\nuniform float u_pixelRatio;\n\nvoid main(void) {\n if (v_round > 0.0) {\n vec2 windowCoords = vec2((v_roundVertex.x + 1.0) / 2.0 * u_size.x * u_pixelRatio,\n (v_roundVertex.y + 1.0) / 2.0 * u_size.y * u_pixelRatio);\n if (length(windowCoords - gl_FragCoord.xy) > v_halfWidth * u_pixelRatio) {\n discard;\n }\n }\n gl_FragColor = u_color;\n float alpha = u_color.a * u_opacity;\n if (alpha == 0.0) {\n discard;\n }\n gl_FragColor.a = alpha;\n}\n'; /** * @const * @type {string} */ -ol.render.webgl.linestringreplay.shader.DefaultFragment.OPTIMIZED_SOURCE = 'precision mediump float;varying float a;varying vec2 b;varying float c;uniform float l;uniform vec4 m;uniform vec2 n;uniform float o;void main(void){if(a>0.0){vec2 windowCoords=vec2((b.x+1.0)/2.0*n.x*o,(b.y+1.0)/2.0*n.y*o);if(length(windowCoords-gl_FragCoord.xy)>c*o){discard;}} gl_FragColor=m;float alpha=m.a*l;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}'; +ol.render.webgl.linestringreplay.defaultshader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;varying float a;varying vec2 b;varying float c;uniform float l;uniform vec4 m;uniform vec2 n;uniform float o;void main(void){if(a>0.0){vec2 windowCoords=vec2((b.x+1.0)/2.0*n.x*o,(b.y+1.0)/2.0*n.y*o);if(length(windowCoords-gl_FragCoord.xy)>c*o){discard;}} gl_FragColor=m;float alpha=m.a*l;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}'; /** * @const * @type {string} */ -ol.render.webgl.linestringreplay.shader.DefaultFragment.SOURCE = goog.DEBUG ? - ol.render.webgl.linestringreplay.shader.DefaultFragment.DEBUG_SOURCE : - ol.render.webgl.linestringreplay.shader.DefaultFragment.OPTIMIZED_SOURCE; +ol.render.webgl.linestringreplay.defaultshader.Fragment.SOURCE = ol.DEBUG ? + ol.render.webgl.linestringreplay.defaultshader.Fragment.DEBUG_SOURCE : + ol.render.webgl.linestringreplay.defaultshader.Fragment.OPTIMIZED_SOURCE; + + +ol.render.webgl.linestringreplay.defaultshader.fragment = new ol.render.webgl.linestringreplay.defaultshader.Fragment(); /** * @constructor - * @extends {ol.webgl.shader.Vertex} + * @extends {ol.webgl.Vertex} * @struct */ -ol.render.webgl.linestringreplay.shader.DefaultVertex = function() { - ol.webgl.shader.Vertex.call(this, ol.render.webgl.linestringreplay.shader.DefaultVertex.SOURCE); +ol.render.webgl.linestringreplay.defaultshader.Vertex = function() { + ol.webgl.Vertex.call(this, ol.render.webgl.linestringreplay.defaultshader.Vertex.SOURCE); }; -ol.inherits(ol.render.webgl.linestringreplay.shader.DefaultVertex, ol.webgl.shader.Vertex); -goog.addSingletonGetter(ol.render.webgl.linestringreplay.shader.DefaultVertex); +ol.inherits(ol.render.webgl.linestringreplay.defaultshader.Vertex, ol.webgl.Vertex); /** * @const * @type {string} */ -ol.render.webgl.linestringreplay.shader.DefaultVertex.DEBUG_SOURCE = 'varying float v_round;\nvarying vec2 v_roundVertex;\nvarying float v_halfWidth;\n\n\nattribute vec2 a_lastPos;\nattribute vec2 a_position;\nattribute vec2 a_nextPos;\nattribute float a_direction;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform float u_lineWidth;\nuniform float u_miterLimit;\n\nvoid main(void) {\n bool degenerate = false;\n v_halfWidth = u_lineWidth / 2.0;\n float miterLimit = u_miterLimit + u_lineWidth;\n vec2 offset;\n v_round = 0.0;\n float direction = a_direction / abs(a_direction);\n vec4 projPos = u_projectionMatrix * vec4(a_position, 0., 1.);\n v_roundVertex = projPos.xy;\n if (mod(a_direction, 3.0) == 0.0 || mod(a_direction, 17.0) == 0.0) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 normal = normalize(vec2(-dirVect.y, dirVect.x));\n offset = v_halfWidth * normal * direction;\n } else if (mod(a_direction, 5.0) == 0.0 || mod(a_direction, 13.0) == 0.0) {\n vec2 dirVect = a_lastPos - a_position;\n vec2 normal = normalize(vec2(dirVect.y, -dirVect.x));\n offset = v_halfWidth * normal * direction;\n } else if (mod(a_direction, 19.0) == 0.0 || mod(a_direction, 23.0) == 0.0) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 tmpNormal = normalize(vec2(-dirVect.y, dirVect.x));\n vec2 tangent = normalize(normalize(a_nextPos - a_position) + normalize(a_position - a_lastPos));\n vec2 normal = vec2(-tangent.y, tangent.x);\n float miterLength = abs(v_halfWidth / dot(normal, tmpNormal));\n if (mod(a_direction, 23.0) == 0.0) {\n offset = normal * direction * miterLength;\n if (mod(a_direction, 2.0) == 0.0) {\n v_round = 1.0;\n } else if (miterLength > miterLimit) {\n offset = tmpNormal * direction * v_halfWidth;\n }\n } else {\n dirVect = a_lastPos - a_position;\n vec2 longOffset, shortOffset, longVertex;\n vec4 shortProjVertex;\n if (length(a_nextPos - a_position) > length(a_lastPos - a_position)) {\n longOffset = tmpNormal * direction * v_halfWidth;\n shortOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * v_halfWidth;\n longVertex = a_nextPos;\n shortProjVertex = u_projectionMatrix * vec4(a_lastPos, 0., 1.);\n } else {\n shortOffset = tmpNormal * direction * v_halfWidth;\n longOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * v_halfWidth;\n longVertex = a_lastPos;\n shortProjVertex = u_projectionMatrix * vec4(a_nextPos, 0., 1.);\n }\n //Intersection algorithm based on theory by Paul Bourke (http://paulbourke.net/geometry/pointlineplane/).\n vec4 p1 = u_projectionMatrix * vec4(longVertex, 0., 1.) + u_offsetScaleMatrix * vec4(longOffset, 0., 0.);\n vec4 p2 = projPos + u_offsetScaleMatrix * vec4(longOffset, 0., 0.);\n vec4 p3 = shortProjVertex + u_offsetScaleMatrix * vec4(-shortOffset, 0., 0.);\n vec4 p4 = shortProjVertex + u_offsetScaleMatrix * vec4(shortOffset, 0., 0.);\n float denom = (p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y);\n float epsilon = 0.000000000001;\n float firstU = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x)) / denom;\n float secondU = ((p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x)) / denom;\n if (firstU > epsilon && firstU < 1.0 - epsilon && secondU > epsilon && secondU < 1.0 - epsilon) {\n gl_Position = shortProjVertex;\n gl_Position.x = p1.x + firstU * (p2.x - p1.x);\n gl_Position.y = p1.y + firstU * (p2.y - p1.y);\n degenerate = true;\n } else {\n offset = normal * direction * miterLength;\n }\n }\n } else if (mod(a_direction, 7.0) == 0.0 || mod(a_direction, 11.0) == 0.0) {\n vec2 normal;\n if (mod(a_direction, 7.0) == 0.0) {\n vec2 dirVect = a_position - a_nextPos;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(firstNormal.y * direction, -firstNormal.x * direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n normal = vec2(hypotenuse.y * direction, -hypotenuse.x * direction);\n } else {\n vec2 dirVect = a_position - a_lastPos;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(-firstNormal.y * direction, firstNormal.x * direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n normal = vec2(-hypotenuse.y * direction, hypotenuse.x * direction);\n }\n float length = sqrt(v_halfWidth * v_halfWidth * 2.0);\n offset = normal * length;\n if (mod(a_direction, 2.0) == 0.0) {\n v_round = 1.0;\n }\n }\n if (!degenerate) {\n vec4 offsets = u_offsetScaleMatrix * vec4(offset, 0., 0.);\n gl_Position = projPos + offsets;\n }\n}\n\n\n'; +ol.render.webgl.linestringreplay.defaultshader.Vertex.DEBUG_SOURCE = 'varying float v_round;\nvarying vec2 v_roundVertex;\nvarying float v_halfWidth;\n\n\nattribute vec2 a_lastPos;\nattribute vec2 a_position;\nattribute vec2 a_nextPos;\nattribute float a_direction;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform float u_lineWidth;\nuniform float u_miterLimit;\n\nvoid main(void) {\n bool degenerate = false;\n v_halfWidth = u_lineWidth / 2.0;\n float miterLimit = u_miterLimit + u_lineWidth;\n vec2 offset;\n v_round = 0.0;\n float direction = a_direction / abs(a_direction);\n vec4 projPos = u_projectionMatrix * vec4(a_position, 0., 1.);\n v_roundVertex = projPos.xy;\n if (mod(a_direction, 3.0) == 0.0 || mod(a_direction, 17.0) == 0.0) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 normal = normalize(vec2(-dirVect.y, dirVect.x));\n offset = v_halfWidth * normal * direction;\n } else if (mod(a_direction, 5.0) == 0.0 || mod(a_direction, 13.0) == 0.0) {\n vec2 dirVect = a_lastPos - a_position;\n vec2 normal = normalize(vec2(dirVect.y, -dirVect.x));\n offset = v_halfWidth * normal * direction;\n } else if (mod(a_direction, 19.0) == 0.0 || mod(a_direction, 23.0) == 0.0) {\n vec2 dirVect = a_nextPos - a_position;\n vec2 tmpNormal = normalize(vec2(-dirVect.y, dirVect.x));\n vec2 tangent = normalize(normalize(a_nextPos - a_position) + normalize(a_position - a_lastPos));\n vec2 normal = vec2(-tangent.y, tangent.x);\n float miterLength = abs(v_halfWidth / dot(normal, tmpNormal));\n if (mod(a_direction, 23.0) == 0.0) {\n offset = normal * direction * miterLength;\n if (mod(a_direction, 2.0) == 0.0) {\n v_round = 1.0;\n } else if (miterLength > miterLimit) {\n offset = tmpNormal * direction * v_halfWidth;\n }\n } else {\n dirVect = a_lastPos - a_position;\n vec2 longOffset, shortOffset, longVertex;\n vec4 shortProjVertex;\n if (length(a_nextPos - a_position) > length(a_lastPos - a_position)) {\n longOffset = tmpNormal * direction * v_halfWidth;\n shortOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * v_halfWidth;\n longVertex = a_nextPos;\n shortProjVertex = u_projectionMatrix * vec4(a_lastPos, 0., 1.);\n } else {\n shortOffset = tmpNormal * direction * v_halfWidth;\n longOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * v_halfWidth;\n longVertex = a_lastPos;\n shortProjVertex = u_projectionMatrix * vec4(a_nextPos, 0., 1.);\n }\n //Intersection algorithm based on theory by Paul Bourke (http://paulbourke.net/geometry/pointlineplane/).\n vec4 p1 = u_projectionMatrix * vec4(longVertex, 0., 1.) + u_offsetScaleMatrix * vec4(longOffset, 0., 0.);\n vec4 p2 = projPos + u_offsetScaleMatrix * vec4(longOffset, 0., 0.);\n vec4 p3 = shortProjVertex + u_offsetScaleMatrix * vec4(-shortOffset, 0., 0.);\n vec4 p4 = shortProjVertex + u_offsetScaleMatrix * vec4(shortOffset, 0., 0.);\n float denom = (p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y);\n float epsilon = 0.000000000001;\n float firstU = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x)) / denom;\n float secondU = ((p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x)) / denom;\n if (firstU > epsilon && firstU < 1.0 - epsilon && secondU > epsilon && secondU < 1.0 - epsilon) {\n gl_Position = shortProjVertex;\n gl_Position.x = p1.x + firstU * (p2.x - p1.x);\n gl_Position.y = p1.y + firstU * (p2.y - p1.y);\n degenerate = true;\n } else {\n offset = normal * direction * miterLength;\n }\n }\n } else if (mod(a_direction, 7.0) == 0.0 || mod(a_direction, 11.0) == 0.0) {\n vec2 normal;\n if (mod(a_direction, 7.0) == 0.0) {\n vec2 dirVect = a_position - a_nextPos;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(firstNormal.y * direction, -firstNormal.x * direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n normal = vec2(hypotenuse.y * direction, -hypotenuse.x * direction);\n } else {\n vec2 dirVect = a_position - a_lastPos;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(-firstNormal.y * direction, firstNormal.x * direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n normal = vec2(-hypotenuse.y * direction, hypotenuse.x * direction);\n }\n float length = sqrt(v_halfWidth * v_halfWidth * 2.0);\n offset = normal * length;\n if (mod(a_direction, 2.0) == 0.0) {\n v_round = 1.0;\n }\n }\n if (!degenerate) {\n vec4 offsets = u_offsetScaleMatrix * vec4(offset, 0., 0.);\n gl_Position = projPos + offsets;\n }\n}\n\n\n'; /** * @const * @type {string} */ -ol.render.webgl.linestringreplay.shader.DefaultVertex.OPTIMIZED_SOURCE = 'varying float a;varying vec2 b;varying float c;attribute vec2 d;attribute vec2 e;attribute vec2 f;attribute float g;uniform mat4 h;uniform mat4 i;uniform float j;uniform float k;void main(void){bool degenerate=false;c=j/2.0;float miterLimit=k+j;vec2 offset;a=0.0;float direction=g/abs(g);vec4 projPos=h*vec4(e,0.,1.);b=projPos.xy;if(mod(g,3.0)==0.0||mod(g,17.0)==0.0){vec2 dirVect=f-e;vec2 normal=normalize(vec2(-dirVect.y,dirVect.x));offset=c*normal*direction;}else if(mod(g,5.0)==0.0||mod(g,13.0)==0.0){vec2 dirVect=d-e;vec2 normal=normalize(vec2(dirVect.y,-dirVect.x));offset=c*normal*direction;}else if(mod(g,19.0)==0.0||mod(g,23.0)==0.0){vec2 dirVect=f-e;vec2 tmpNormal=normalize(vec2(-dirVect.y,dirVect.x));vec2 tangent=normalize(normalize(f-e)+normalize(e-d));vec2 normal=vec2(-tangent.y,tangent.x);float miterLength=abs(c/dot(normal,tmpNormal));if(mod(g,23.0)==0.0){offset=normal*direction*miterLength;if(mod(g,2.0)==0.0){a=1.0;}else if(miterLength>miterLimit){offset=tmpNormal*direction*c;}} else{dirVect=d-e;vec2 longOffset,shortOffset,longVertex;vec4 shortProjVertex;if(length(f-e)>length(d-e)){longOffset=tmpNormal*direction*c;shortOffset=normalize(vec2(dirVect.y,-dirVect.x))*direction*c;longVertex=f;shortProjVertex=h*vec4(d,0.,1.);}else{shortOffset=tmpNormal*direction*c;longOffset=normalize(vec2(dirVect.y,-dirVect.x))*direction*c;longVertex=d;shortProjVertex=h*vec4(f,0.,1.);}vec4 p1=h*vec4(longVertex,0.,1.)+i*vec4(longOffset,0.,0.);vec4 p2=projPos+i*vec4(longOffset,0.,0.);vec4 p3=shortProjVertex+i*vec4(-shortOffset,0.,0.);vec4 p4=shortProjVertex+i*vec4(shortOffset,0.,0.);float denom=(p4.y-p3.y)*(p2.x-p1.x)-(p4.x-p3.x)*(p2.y-p1.y);float epsilon=0.000000000001;float firstU=((p4.x-p3.x)*(p1.y-p3.y)-(p4.y-p3.y)*(p1.x-p3.x))/denom;float secondU=((p2.x-p1.x)*(p1.y-p3.y)-(p2.y-p1.y)*(p1.x-p3.x))/denom;if(firstU>epsilon&&firstU<1.0-epsilon&&secondU>epsilon&&secondU<1.0-epsilon){gl_Position=shortProjVertex;gl_Position.x=p1.x+firstU*(p2.x-p1.x);gl_Position.y=p1.y+firstU*(p2.y-p1.y);degenerate=true;}else{offset=normal*direction*miterLength;}}}else if(mod(g,7.0)==0.0||mod(g,11.0)==0.0){vec2 normal;if(mod(g,7.0)==0.0){vec2 dirVect=e-f;vec2 firstNormal=normalize(dirVect);vec2 secondNormal=vec2(firstNormal.y*direction,-firstNormal.x*direction);vec2 hypotenuse=normalize(firstNormal-secondNormal);normal=vec2(hypotenuse.y*direction,-hypotenuse.x*direction);}else{vec2 dirVect=e-d;vec2 firstNormal=normalize(dirVect);vec2 secondNormal=vec2(-firstNormal.y*direction,firstNormal.x*direction);vec2 hypotenuse=normalize(firstNormal-secondNormal);normal=vec2(-hypotenuse.y*direction,hypotenuse.x*direction);}float length=sqrt(c*c*2.0);offset=normal*length;if(mod(g,2.0)==0.0){a=1.0;}} if(!degenerate){vec4 offsets=i*vec4(offset,0.,0.);gl_Position=projPos+offsets;}}'; +ol.render.webgl.linestringreplay.defaultshader.Vertex.OPTIMIZED_SOURCE = 'varying float a;varying vec2 b;varying float c;attribute vec2 d;attribute vec2 e;attribute vec2 f;attribute float g;uniform mat4 h;uniform mat4 i;uniform float j;uniform float k;void main(void){bool degenerate=false;c=j/2.0;float miterLimit=k+j;vec2 offset;a=0.0;float direction=g/abs(g);vec4 projPos=h*vec4(e,0.,1.);b=projPos.xy;if(mod(g,3.0)==0.0||mod(g,17.0)==0.0){vec2 dirVect=f-e;vec2 normal=normalize(vec2(-dirVect.y,dirVect.x));offset=c*normal*direction;}else if(mod(g,5.0)==0.0||mod(g,13.0)==0.0){vec2 dirVect=d-e;vec2 normal=normalize(vec2(dirVect.y,-dirVect.x));offset=c*normal*direction;}else if(mod(g,19.0)==0.0||mod(g,23.0)==0.0){vec2 dirVect=f-e;vec2 tmpNormal=normalize(vec2(-dirVect.y,dirVect.x));vec2 tangent=normalize(normalize(f-e)+normalize(e-d));vec2 normal=vec2(-tangent.y,tangent.x);float miterLength=abs(c/dot(normal,tmpNormal));if(mod(g,23.0)==0.0){offset=normal*direction*miterLength;if(mod(g,2.0)==0.0){a=1.0;}else if(miterLength>miterLimit){offset=tmpNormal*direction*c;}} else{dirVect=d-e;vec2 longOffset,shortOffset,longVertex;vec4 shortProjVertex;if(length(f-e)>length(d-e)){longOffset=tmpNormal*direction*c;shortOffset=normalize(vec2(dirVect.y,-dirVect.x))*direction*c;longVertex=f;shortProjVertex=h*vec4(d,0.,1.);}else{shortOffset=tmpNormal*direction*c;longOffset=normalize(vec2(dirVect.y,-dirVect.x))*direction*c;longVertex=d;shortProjVertex=h*vec4(f,0.,1.);}vec4 p1=h*vec4(longVertex,0.,1.)+i*vec4(longOffset,0.,0.);vec4 p2=projPos+i*vec4(longOffset,0.,0.);vec4 p3=shortProjVertex+i*vec4(-shortOffset,0.,0.);vec4 p4=shortProjVertex+i*vec4(shortOffset,0.,0.);float denom=(p4.y-p3.y)*(p2.x-p1.x)-(p4.x-p3.x)*(p2.y-p1.y);float epsilon=0.000000000001;float firstU=((p4.x-p3.x)*(p1.y-p3.y)-(p4.y-p3.y)*(p1.x-p3.x))/denom;float secondU=((p2.x-p1.x)*(p1.y-p3.y)-(p2.y-p1.y)*(p1.x-p3.x))/denom;if(firstU>epsilon&&firstU<1.0-epsilon&&secondU>epsilon&&secondU<1.0-epsilon){gl_Position=shortProjVertex;gl_Position.x=p1.x+firstU*(p2.x-p1.x);gl_Position.y=p1.y+firstU*(p2.y-p1.y);degenerate=true;}else{offset=normal*direction*miterLength;}}}else if(mod(g,7.0)==0.0||mod(g,11.0)==0.0){vec2 normal;if(mod(g,7.0)==0.0){vec2 dirVect=e-f;vec2 firstNormal=normalize(dirVect);vec2 secondNormal=vec2(firstNormal.y*direction,-firstNormal.x*direction);vec2 hypotenuse=normalize(firstNormal-secondNormal);normal=vec2(hypotenuse.y*direction,-hypotenuse.x*direction);}else{vec2 dirVect=e-d;vec2 firstNormal=normalize(dirVect);vec2 secondNormal=vec2(-firstNormal.y*direction,firstNormal.x*direction);vec2 hypotenuse=normalize(firstNormal-secondNormal);normal=vec2(-hypotenuse.y*direction,hypotenuse.x*direction);}float length=sqrt(c*c*2.0);offset=normal*length;if(mod(g,2.0)==0.0){a=1.0;}} if(!degenerate){vec4 offsets=i*vec4(offset,0.,0.);gl_Position=projPos+offsets;}}'; /** * @const * @type {string} */ -ol.render.webgl.linestringreplay.shader.DefaultVertex.SOURCE = goog.DEBUG ? - ol.render.webgl.linestringreplay.shader.DefaultVertex.DEBUG_SOURCE : - ol.render.webgl.linestringreplay.shader.DefaultVertex.OPTIMIZED_SOURCE; +ol.render.webgl.linestringreplay.defaultshader.Vertex.SOURCE = ol.DEBUG ? + ol.render.webgl.linestringreplay.defaultshader.Vertex.DEBUG_SOURCE : + ol.render.webgl.linestringreplay.defaultshader.Vertex.OPTIMIZED_SOURCE; + + +ol.render.webgl.linestringreplay.defaultshader.vertex = new ol.render.webgl.linestringreplay.defaultshader.Vertex(); /** @@ -83,77 +86,77 @@ ol.render.webgl.linestringreplay.shader.DefaultVertex.SOURCE = goog.DEBUG ? * @param {WebGLProgram} program Program. * @struct */ -ol.render.webgl.linestringreplay.shader.Default.Locations = function(gl, program) { +ol.render.webgl.linestringreplay.defaultshader.Locations = function(gl, program) { /** * @type {WebGLUniformLocation} */ this.u_color = gl.getUniformLocation( - program, goog.DEBUG ? 'u_color' : 'm'); + program, ol.DEBUG ? 'u_color' : 'm'); /** * @type {WebGLUniformLocation} */ this.u_lineWidth = gl.getUniformLocation( - program, goog.DEBUG ? 'u_lineWidth' : 'j'); + program, ol.DEBUG ? 'u_lineWidth' : 'j'); /** * @type {WebGLUniformLocation} */ this.u_miterLimit = gl.getUniformLocation( - program, goog.DEBUG ? 'u_miterLimit' : 'k'); + program, ol.DEBUG ? 'u_miterLimit' : 'k'); /** * @type {WebGLUniformLocation} */ this.u_offsetScaleMatrix = gl.getUniformLocation( - program, goog.DEBUG ? 'u_offsetScaleMatrix' : 'i'); + program, ol.DEBUG ? 'u_offsetScaleMatrix' : 'i'); /** * @type {WebGLUniformLocation} */ this.u_opacity = gl.getUniformLocation( - program, goog.DEBUG ? 'u_opacity' : 'l'); + program, ol.DEBUG ? 'u_opacity' : 'l'); /** * @type {WebGLUniformLocation} */ this.u_pixelRatio = gl.getUniformLocation( - program, goog.DEBUG ? 'u_pixelRatio' : 'o'); + program, ol.DEBUG ? 'u_pixelRatio' : 'o'); /** * @type {WebGLUniformLocation} */ this.u_projectionMatrix = gl.getUniformLocation( - program, goog.DEBUG ? 'u_projectionMatrix' : 'h'); + program, ol.DEBUG ? 'u_projectionMatrix' : 'h'); /** * @type {WebGLUniformLocation} */ this.u_size = gl.getUniformLocation( - program, goog.DEBUG ? 'u_size' : 'n'); + program, ol.DEBUG ? 'u_size' : 'n'); /** * @type {number} */ this.a_direction = gl.getAttribLocation( - program, goog.DEBUG ? 'a_direction' : 'g'); + program, ol.DEBUG ? 'a_direction' : 'g'); /** * @type {number} */ this.a_lastPos = gl.getAttribLocation( - program, goog.DEBUG ? 'a_lastPos' : 'd'); + program, ol.DEBUG ? 'a_lastPos' : 'd'); /** * @type {number} */ this.a_nextPos = gl.getAttribLocation( - program, goog.DEBUG ? 'a_nextPos' : 'f'); + program, ol.DEBUG ? 'a_nextPos' : 'f'); /** * @type {number} */ this.a_position = gl.getAttribLocation( - program, goog.DEBUG ? 'a_position' : 'e'); + program, ol.DEBUG ? 'a_position' : 'e'); }; diff --git a/src/ol/render/webgl/linestringreplay/index.js b/src/ol/render/webgl/linestringreplay/index.js new file mode 100644 index 0000000000..498121129b --- /dev/null +++ b/src/ol/render/webgl/linestringreplay/index.js @@ -0,0 +1,654 @@ +goog.provide('ol.render.webgl.LineStringReplay'); + +goog.require('ol'); +goog.require('ol.color'); +goog.require('ol.extent'); +goog.require('ol.geom.flat.orient'); +goog.require('ol.geom.flat.transform'); +goog.require('ol.geom.flat.topology'); +goog.require('ol.obj'); +goog.require('ol.render.webgl'); +goog.require('ol.render.webgl.Replay'); +goog.require('ol.render.webgl.linestringreplay.defaultshader'); +goog.require('ol.webgl'); +goog.require('ol.webgl.Buffer'); + + +/** + * @constructor + * @extends {ol.render.webgl.Replay} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Max extent. + * @struct + */ +ol.render.webgl.LineStringReplay = function(tolerance, maxExtent) { + ol.render.webgl.Replay.call(this, tolerance, maxExtent); + + /** + * @private + * @type {ol.render.webgl.linestringreplay.defaultshader.Locations} + */ + this.defaultLocations_ = null; + + /** + * @private + * @type {Array.>} + */ + this.styles_ = []; + + /** + * @private + * @type {Array.} + */ + this.styleIndices_ = []; + + /** + * @private + * @type {{strokeColor: (Array.|null), + * lineCap: (string|undefined), + * lineDash: Array., + * lineJoin: (string|undefined), + * lineWidth: (number|undefined), + * miterLimit: (number|undefined), + * changed: boolean}|null} + */ + this.state_ = { + strokeColor: null, + lineCap: undefined, + lineDash: null, + lineJoin: undefined, + lineWidth: undefined, + miterLimit: undefined, + changed: false + }; + +}; +ol.inherits(ol.render.webgl.LineStringReplay, ol.render.webgl.Replay); + + +/** + * Draw one segment. + * @private + * @param {Array.} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + */ +ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoordinates, offset, end, stride) { + + var i, ii; + var numVertices = this.vertices.length; + var numIndices = this.indices.length; + //To save a vertex, the direction of a point is a product of the sign (1 or -1), a prime from + //ol.render.webgl.lineStringInstruction, and a rounding factor (1 or 2). If the product is even, + //we round it. If it is odd, we don't. + var lineJoin = this.state_.lineJoin === 'bevel' ? 0 : + this.state_.lineJoin === 'miter' ? 1 : 2; + var lineCap = this.state_.lineCap === 'butt' ? 0 : + this.state_.lineCap === 'square' ? 1 : 2; + var closed = ol.geom.flat.topology.lineStringIsClosed(flatCoordinates, offset, end, stride); + var startCoords, sign, n; + var lastIndex = numIndices; + var lastSign = 1; + //We need the adjacent vertices to define normals in joins. p0 = last, p1 = current, p2 = next. + var p0, p1, p2; + + for (i = offset, ii = end; i < ii; i += stride) { + + n = numVertices / 7; + + p0 = p1; + p1 = p2 || [flatCoordinates[i], flatCoordinates[i + 1]]; + //First vertex. + if (i === offset) { + p2 = [flatCoordinates[i + stride], flatCoordinates[i + stride + 1]]; + if (end - offset === stride * 2 && ol.array.equals(p1, p2)) { + break; + } + if (closed) { + //A closed line! Complete the circle. + p0 = [flatCoordinates[end - stride * 2], + flatCoordinates[end - stride * 2 + 1]]; + + startCoords = p2; + } else { + //Add the first two/four vertices. + + if (lineCap) { + numVertices = this.addVertices_([0, 0], p1, p2, + lastSign * ol.render.webgl.lineStringInstruction.BEGIN_LINE_CAP * lineCap, numVertices); + + numVertices = this.addVertices_([0, 0], p1, p2, + -lastSign * ol.render.webgl.lineStringInstruction.BEGIN_LINE_CAP * lineCap, numVertices); + + this.indices[numIndices++] = n + 2; + this.indices[numIndices++] = n; + this.indices[numIndices++] = n + 1; + + this.indices[numIndices++] = n + 1; + this.indices[numIndices++] = n + 3; + this.indices[numIndices++] = n + 2; + + } + + numVertices = this.addVertices_([0, 0], p1, p2, + lastSign * ol.render.webgl.lineStringInstruction.BEGIN_LINE * (lineCap || 1), numVertices); + + numVertices = this.addVertices_([0, 0], p1, p2, + -lastSign * ol.render.webgl.lineStringInstruction.BEGIN_LINE * (lineCap || 1), numVertices); + + lastIndex = numVertices / 7 - 1; + + continue; + } + } else if (i === end - stride) { + //Last vertex. + if (closed) { + //Same as the first vertex. + p2 = startCoords; + break; + } else { + //For the compiler not to complain. This will never be [0, 0]. + p0 = p0 || [0, 0]; + + numVertices = this.addVertices_(p0, p1, [0, 0], + lastSign * ol.render.webgl.lineStringInstruction.END_LINE * (lineCap || 1), numVertices); + + numVertices = this.addVertices_(p0, p1, [0, 0], + -lastSign * ol.render.webgl.lineStringInstruction.END_LINE * (lineCap || 1), numVertices); + + this.indices[numIndices++] = n; + this.indices[numIndices++] = lastIndex - 1; + this.indices[numIndices++] = lastIndex; + + this.indices[numIndices++] = lastIndex; + this.indices[numIndices++] = n + 1; + this.indices[numIndices++] = n; + + if (lineCap) { + numVertices = this.addVertices_(p0, p1, [0, 0], + lastSign * ol.render.webgl.lineStringInstruction.END_LINE_CAP * lineCap, numVertices); + + numVertices = this.addVertices_(p0, p1, [0, 0], + -lastSign * ol.render.webgl.lineStringInstruction.END_LINE_CAP * lineCap, numVertices); + + this.indices[numIndices++] = n + 2; + this.indices[numIndices++] = n; + this.indices[numIndices++] = n + 1; + + this.indices[numIndices++] = n + 1; + this.indices[numIndices++] = n + 3; + this.indices[numIndices++] = n + 2; + + } + + break; + } + } else { + p2 = [flatCoordinates[i + stride], flatCoordinates[i + stride + 1]]; + } + + sign = ol.render.webgl.triangleIsCounterClockwise(p0[0], p0[1], p1[0], p1[1], p2[0], p2[1]) + ? -1 : 1; + + numVertices = this.addVertices_(p0, p1, p2, + sign * ol.render.webgl.lineStringInstruction.BEVEL_FIRST * (lineJoin || 1), numVertices); + + numVertices = this.addVertices_(p0, p1, p2, + sign * ol.render.webgl.lineStringInstruction.BEVEL_SECOND * (lineJoin || 1), numVertices); + + numVertices = this.addVertices_(p0, p1, p2, + -sign * ol.render.webgl.lineStringInstruction.MITER_BOTTOM * (lineJoin || 1), numVertices); + + if (i > offset) { + this.indices[numIndices++] = n; + this.indices[numIndices++] = lastIndex - 1; + this.indices[numIndices++] = lastIndex; + + this.indices[numIndices++] = n + 2; + this.indices[numIndices++] = n; + this.indices[numIndices++] = lastSign * sign > 0 ? lastIndex : lastIndex - 1; + } + + this.indices[numIndices++] = n; + this.indices[numIndices++] = n + 2; + this.indices[numIndices++] = n + 1; + + lastIndex = n + 2; + lastSign = sign; + + //Add miter + if (lineJoin) { + numVertices = this.addVertices_(p0, p1, p2, + sign * ol.render.webgl.lineStringInstruction.MITER_TOP * lineJoin, numVertices); + + this.indices[numIndices++] = n + 1; + this.indices[numIndices++] = n + 3; + this.indices[numIndices++] = n; + } + } + + if (closed) { + //Link the last triangle/rhombus to the first one. + //n will never be numVertices / 7 here. However, the compiler complains otherwise. + n = n || numVertices / 7; + sign = ol.geom.flat.orient.linearRingIsClockwise([p0[0], p0[1], p1[0], p1[1], p2[0], p2[1]], 0, 6, 2) + ? 1 : -1; + + numVertices = this.addVertices_(p0, p1, p2, + sign * ol.render.webgl.lineStringInstruction.BEVEL_FIRST * (lineJoin || 1), numVertices); + + numVertices = this.addVertices_(p0, p1, p2, + -sign * ol.render.webgl.lineStringInstruction.MITER_BOTTOM * (lineJoin || 1), numVertices); + + this.indices[numIndices++] = n; + this.indices[numIndices++] = lastIndex - 1; + this.indices[numIndices++] = lastIndex; + + this.indices[numIndices++] = n + 1; + this.indices[numIndices++] = n; + this.indices[numIndices++] = lastSign * sign > 0 ? lastIndex : lastIndex - 1; + } +}; + +/** + * @param {Array.} p0 Last coordinates. + * @param {Array.} p1 Current coordinates. + * @param {Array.} p2 Next coordinates. + * @param {number} product Sign, instruction, and rounding product. + * @param {number} numVertices Vertex counter. + * @return {number} Vertex counter. + * @private + */ +ol.render.webgl.LineStringReplay.prototype.addVertices_ = function(p0, p1, p2, product, numVertices) { + this.vertices[numVertices++] = p0[0]; + this.vertices[numVertices++] = p0[1]; + this.vertices[numVertices++] = p1[0]; + this.vertices[numVertices++] = p1[1]; + this.vertices[numVertices++] = p2[0]; + this.vertices[numVertices++] = p2[1]; + this.vertices[numVertices++] = product; + + return numVertices; +}; + +/** + * Check if the linestring can be drawn (i. e. valid). + * @param {Array.} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @return {boolean} The linestring can be drawn. + * @private + */ +ol.render.webgl.LineStringReplay.prototype.isValid_ = function(flatCoordinates, offset, end, stride) { + var range = end - offset; + if (range < stride * 2) { + return false; + } else if (range === stride * 2) { + var firstP = [flatCoordinates[offset], flatCoordinates[offset + 1]]; + var lastP = [flatCoordinates[offset + stride], flatCoordinates[offset + stride + 1]]; + return !ol.array.equals(firstP, lastP); + } + + return true; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.LineStringReplay.prototype.drawLineString = function(lineStringGeometry, feature) { + var flatCoordinates = lineStringGeometry.getFlatCoordinates(); + var stride = lineStringGeometry.getStride(); + if (this.isValid_(flatCoordinates, 0, flatCoordinates.length, stride)) { + flatCoordinates = ol.geom.flat.transform.translate(flatCoordinates, 0, flatCoordinates.length, + stride, -this.origin[0], -this.origin[1]); + if (this.state_.changed) { + this.styleIndices_.push(this.indices.length); + this.state_.changed = false; + } + this.startIndices.push(this.indices.length); + this.startIndicesFeature.push(feature); + this.drawCoordinates_( + flatCoordinates, 0, flatCoordinates.length, stride); + } +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.LineStringReplay.prototype.drawMultiLineString = function(multiLineStringGeometry, feature) { + var indexCount = this.indices.length; + var lineStringGeometries = multiLineStringGeometry.getLineStrings(); + var i, ii; + for (i = 0, ii = lineStringGeometries.length; i < ii; ++i) { + var flatCoordinates = lineStringGeometries[i].getFlatCoordinates(); + var stride = lineStringGeometries[i].getStride(); + if (this.isValid_(flatCoordinates, 0, flatCoordinates.length, stride)) { + flatCoordinates = ol.geom.flat.transform.translate(flatCoordinates, 0, flatCoordinates.length, + stride, -this.origin[0], -this.origin[1]); + this.drawCoordinates_( + flatCoordinates, 0, flatCoordinates.length, stride); + } + } + if (this.indices.length > indexCount) { + this.startIndices.push(indexCount); + this.startIndicesFeature.push(feature); + if (this.state_.changed) { + this.styleIndices_.push(indexCount); + this.state_.changed = false; + } + } +}; + + +/** + * @param {Array.} flatCoordinates Flat coordinates. + * @param {Array.>} holeFlatCoordinates Hole flat coordinates. + * @param {number} stride Stride. + */ +ol.render.webgl.LineStringReplay.prototype.drawPolygonCoordinates = function( + flatCoordinates, holeFlatCoordinates, stride) { + this.drawCoordinates_(flatCoordinates, 0, flatCoordinates.length, stride); + if (holeFlatCoordinates.length) { + var i, ii; + for (i = 0, ii = holeFlatCoordinates.length; i < ii; ++i) { + this.drawCoordinates_(holeFlatCoordinates[i], 0, + holeFlatCoordinates[i].length, stride); + } + } +}; + + +/** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @param {number=} opt_index Index count. + */ +ol.render.webgl.LineStringReplay.prototype.setPolygonStyle = function(feature, opt_index) { + var index = opt_index === undefined ? this.indices.length : opt_index; + this.startIndices.push(index); + this.startIndicesFeature.push(feature); + if (this.state_.changed) { + this.styleIndices_.push(index); + this.state_.changed = false; + } +}; + + +/** + * @return {number} Current index. + */ +ol.render.webgl.LineStringReplay.prototype.getCurrentIndex = function() { + return this.indices.length; +}; + + +/** + * @inheritDoc + **/ +ol.render.webgl.LineStringReplay.prototype.finish = function(context) { + // create, bind, and populate the vertices buffer + this.verticesBuffer = new ol.webgl.Buffer(this.vertices); + + // create, bind, and populate the indices buffer + this.indicesBuffer = new ol.webgl.Buffer(this.indices); + + this.startIndices.push(this.indices.length); + + //Clean up, if there is nothing to draw + if (this.styleIndices_.length === 0 && this.styles_.length > 0) { + this.styles_ = []; + } + + this.vertices = null; + this.indices = null; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.LineStringReplay.prototype.getDeleteResourcesFunction = function(context) { + // We only delete our stuff here. The shaders and the program may + // be used by other LineStringReplay instances (for other layers). And + // they will be deleted when disposing of the ol.webgl.Context + // object. + ol.DEBUG && console.assert(this.verticesBuffer, 'verticesBuffer must not be null'); + var verticesBuffer = this.verticesBuffer; + var indicesBuffer = this.indicesBuffer; + return function() { + context.deleteBuffer(verticesBuffer); + context.deleteBuffer(indicesBuffer); + }; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.LineStringReplay.prototype.setUpProgram = function(gl, context, size, pixelRatio) { + // get the program + var fragmentShader, vertexShader; + fragmentShader = ol.render.webgl.linestringreplay.defaultshader.fragment; + vertexShader = ol.render.webgl.linestringreplay.defaultshader.vertex; + var program = context.getProgram(fragmentShader, vertexShader); + + // get the locations + var locations; + if (!this.defaultLocations_) { + locations = + new ol.render.webgl.linestringreplay.defaultshader.Locations(gl, program); + this.defaultLocations_ = locations; + } else { + locations = this.defaultLocations_; + } + + context.useProgram(program); + + // enable the vertex attrib arrays + gl.enableVertexAttribArray(locations.a_lastPos); + gl.vertexAttribPointer(locations.a_lastPos, 2, ol.webgl.FLOAT, + false, 28, 0); + + gl.enableVertexAttribArray(locations.a_position); + gl.vertexAttribPointer(locations.a_position, 2, ol.webgl.FLOAT, + false, 28, 8); + + gl.enableVertexAttribArray(locations.a_nextPos); + gl.vertexAttribPointer(locations.a_nextPos, 2, ol.webgl.FLOAT, + false, 28, 16); + + gl.enableVertexAttribArray(locations.a_direction); + gl.vertexAttribPointer(locations.a_direction, 1, ol.webgl.FLOAT, + false, 28, 24); + + // Enable renderer specific uniforms. + gl.uniform2fv(locations.u_size, size); + gl.uniform1f(locations.u_pixelRatio, pixelRatio); + + return locations; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.LineStringReplay.prototype.drawReplay = function(gl, context, skippedFeaturesHash, hitDetection) { + //Save GL parameters. + var tmpDepthFunc = /** @type {number} */ (gl.getParameter(gl.DEPTH_FUNC)); + var tmpDepthMask = /** @type {boolean} */ (gl.getParameter(gl.DEPTH_WRITEMASK)); + + if (!hitDetection) { + gl.enable(gl.DEPTH_TEST); + gl.depthMask(true); + gl.depthFunc(gl.NOTEQUAL); + } + + if (!ol.obj.isEmpty(skippedFeaturesHash)) { + this.drawReplaySkipping_(gl, context, skippedFeaturesHash); + } else { + ol.DEBUG && console.assert(this.styles_.length === this.styleIndices_.length, + 'number of styles and styleIndices match'); + + //Draw by style groups to minimize drawElements() calls. + var i, start, end, nextStyle; + end = this.startIndices[this.startIndices.length - 1]; + for (i = this.styleIndices_.length - 1; i >= 0; --i) { + start = this.styleIndices_[i]; + nextStyle = this.styles_[i]; + this.setStrokeStyle_(gl, nextStyle[0], nextStyle[1], nextStyle[2]); + this.drawElements(gl, context, start, end); + end = start; + } + } + if (!hitDetection) { + gl.clear(gl.DEPTH_BUFFER_BIT); + gl.disable(gl.DEPTH_TEST); + //Restore GL parameters. + gl.depthMask(tmpDepthMask); + gl.depthFunc(tmpDepthFunc); + } +}; + + +/** + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object} skippedFeaturesHash Ids of features to skip. + */ +ol.render.webgl.LineStringReplay.prototype.drawReplaySkipping_ = function(gl, context, skippedFeaturesHash) { + ol.DEBUG && console.assert(this.startIndices.length - 1 === this.startIndicesFeature.length, + 'number of startIndices and startIndicesFeature match'); + + var i, start, end, nextStyle, groupStart, feature, featureUid, featureIndex, featureStart; + featureIndex = this.startIndices.length - 2; + end = start = this.startIndices[featureIndex + 1]; + for (i = this.styleIndices_.length - 1; i >= 0; --i) { + nextStyle = this.styles_[i]; + this.setStrokeStyle_(gl, nextStyle[0], nextStyle[1], nextStyle[2]); + groupStart = this.styleIndices_[i]; + + while (featureIndex >= 0 && + this.startIndices[featureIndex] >= groupStart) { + featureStart = this.startIndices[featureIndex]; + feature = this.startIndicesFeature[featureIndex]; + featureUid = ol.getUid(feature).toString(); + + if (skippedFeaturesHash[featureUid]) { + if (start !== end) { + this.drawElements(gl, context, start, end); + } + end = featureStart; + } + featureIndex--; + start = featureStart; + } + if (start !== end) { + this.drawElements(gl, context, start, end); + } + } +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.LineStringReplay.prototype.drawHitDetectionReplayOneByOne = function(gl, context, skippedFeaturesHash, + featureCallback, opt_hitExtent) { + ol.DEBUG && console.assert(this.styles_.length === this.styleIndices_.length, + 'number of styles and styleIndices match'); + ol.DEBUG && console.assert(this.startIndices.length - 1 === this.startIndicesFeature.length, + 'number of startIndices and startIndicesFeature match'); + + var i, start, end, nextStyle, groupStart, feature, featureUid, featureIndex; + featureIndex = this.startIndices.length - 2; + end = this.startIndices[featureIndex + 1]; + for (i = this.styleIndices_.length - 1; i >= 0; --i) { + nextStyle = this.styles_[i]; + this.setStrokeStyle_(gl, nextStyle[0], nextStyle[1], nextStyle[2]); + groupStart = this.styleIndices_[i]; + + while (featureIndex >= 0 && + this.startIndices[featureIndex] >= groupStart) { + start = this.startIndices[featureIndex]; + feature = this.startIndicesFeature[featureIndex]; + featureUid = ol.getUid(feature).toString(); + + if (skippedFeaturesHash[featureUid] === undefined && + feature.getGeometry() && + (opt_hitExtent === undefined || ol.extent.intersects( + /** @type {Array} */ (opt_hitExtent), + feature.getGeometry().getExtent()))) { + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + this.drawElements(gl, context, start, end); + + var result = featureCallback(feature); + + if (result) { + return result; + } + + } + featureIndex--; + end = start; + } + } + return undefined; +}; + + +/** + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {Array.} color Color. + * @param {number} lineWidth Line width. + * @param {number} miterLimit Miter limit. + */ +ol.render.webgl.LineStringReplay.prototype.setStrokeStyle_ = function(gl, color, lineWidth, miterLimit) { + gl.uniform4fv(this.defaultLocations_.u_color, color); + gl.uniform1f(this.defaultLocations_.u_lineWidth, lineWidth); + gl.uniform1f(this.defaultLocations_.u_miterLimit, miterLimit); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.LineStringReplay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) { + ol.DEBUG && console.assert(this.state_, 'this.state_ should not be null'); + var strokeStyleLineCap = strokeStyle.getLineCap(); + this.state_.lineCap = strokeStyleLineCap !== undefined ? + strokeStyleLineCap : ol.render.webgl.defaultLineCap; + var strokeStyleLineDash = strokeStyle.getLineDash(); + this.state_.lineDash = strokeStyleLineDash ? + strokeStyleLineDash : ol.render.webgl.defaultLineDash; + var strokeStyleLineJoin = strokeStyle.getLineJoin(); + this.state_.lineJoin = strokeStyleLineJoin !== undefined ? + strokeStyleLineJoin : ol.render.webgl.defaultLineJoin; + var strokeStyleColor = strokeStyle.getColor(); + if (!(strokeStyleColor instanceof CanvasGradient) && + !(strokeStyleColor instanceof CanvasPattern)) { + strokeStyleColor = ol.color.asArray(strokeStyleColor).map(function(c, i) { + return i != 3 ? c / 255 : c; + }) || ol.render.webgl.defaultStrokeStyle; + } else { + strokeStyleColor = ol.render.webgl.defaultStrokeStyle; + } + var strokeStyleWidth = strokeStyle.getWidth(); + strokeStyleWidth = strokeStyleWidth !== undefined ? + strokeStyleWidth : ol.render.webgl.defaultLineWidth; + var strokeStyleMiterLimit = strokeStyle.getMiterLimit(); + strokeStyleMiterLimit = strokeStyleMiterLimit !== undefined ? + strokeStyleMiterLimit : ol.render.webgl.defaultMiterLimit; + if (!this.state_.strokeColor || !ol.array.equals(this.state_.strokeColor, strokeStyleColor) || + this.state_.lineWidth !== strokeStyleWidth || this.state_.miterLimit !== strokeStyleMiterLimit) { + this.state_.changed = true; + this.state_.strokeColor = strokeStyleColor; + this.state_.lineWidth = strokeStyleWidth; + this.state_.miterLimit = strokeStyleMiterLimit; + this.styles_.push([strokeStyleColor, strokeStyleWidth, strokeStyleMiterLimit]); + } +}; diff --git a/src/ol/render/webgl/webglpolygondefault.glsl b/src/ol/render/webgl/polygonreplay/defaultshader.glsl similarity index 82% rename from src/ol/render/webgl/webglpolygondefault.glsl rename to src/ol/render/webgl/polygonreplay/defaultshader.glsl index fe31b75ad4..9f5102bcda 100644 --- a/src/ol/render/webgl/webglpolygondefault.glsl +++ b/src/ol/render/webgl/polygonreplay/defaultshader.glsl @@ -1,5 +1,5 @@ -//! NAMESPACE=ol.render.webgl.polygonreplay.shader.Default -//! CLASS=ol.render.webgl.polygonreplay.shader.Default +//! NAMESPACE=ol.render.webgl.polygonreplay.defaultshader +//! CLASS=ol.render.webgl.polygonreplay.defaultshader //! COMMON diff --git a/src/ol/render/webgl/polygonreplay/defaultshader.js b/src/ol/render/webgl/polygonreplay/defaultshader.js new file mode 100644 index 0000000000..7c691999e9 --- /dev/null +++ b/src/ol/render/webgl/polygonreplay/defaultshader.js @@ -0,0 +1,126 @@ +// This file is automatically generated, do not edit +goog.provide('ol.render.webgl.polygonreplay.defaultshader'); + +goog.require('ol'); +goog.require('ol.webgl.Fragment'); +goog.require('ol.webgl.Vertex'); + + +/** + * @constructor + * @extends {ol.webgl.Fragment} + * @struct + */ +ol.render.webgl.polygonreplay.defaultshader.Fragment = function() { + ol.webgl.Fragment.call(this, ol.render.webgl.polygonreplay.defaultshader.Fragment.SOURCE); +}; +ol.inherits(ol.render.webgl.polygonreplay.defaultshader.Fragment, ol.webgl.Fragment); + + +/** + * @const + * @type {string} + */ +ol.render.webgl.polygonreplay.defaultshader.Fragment.DEBUG_SOURCE = 'precision mediump float;\n\n\n\nuniform vec4 u_color;\nuniform float u_opacity;\n\nvoid main(void) {\n gl_FragColor = u_color;\n float alpha = u_color.a * u_opacity;\n if (alpha == 0.0) {\n discard;\n }\n gl_FragColor.a = alpha;\n}\n'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.polygonreplay.defaultshader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;uniform vec4 e;uniform float f;void main(void){gl_FragColor=e;float alpha=e.a*f;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.polygonreplay.defaultshader.Fragment.SOURCE = ol.DEBUG ? + ol.render.webgl.polygonreplay.defaultshader.Fragment.DEBUG_SOURCE : + ol.render.webgl.polygonreplay.defaultshader.Fragment.OPTIMIZED_SOURCE; + + +ol.render.webgl.polygonreplay.defaultshader.fragment = new ol.render.webgl.polygonreplay.defaultshader.Fragment(); + + +/** + * @constructor + * @extends {ol.webgl.Vertex} + * @struct + */ +ol.render.webgl.polygonreplay.defaultshader.Vertex = function() { + ol.webgl.Vertex.call(this, ol.render.webgl.polygonreplay.defaultshader.Vertex.SOURCE); +}; +ol.inherits(ol.render.webgl.polygonreplay.defaultshader.Vertex, ol.webgl.Vertex); + + +/** + * @const + * @type {string} + */ +ol.render.webgl.polygonreplay.defaultshader.Vertex.DEBUG_SOURCE = '\n\nattribute vec2 a_position;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\n\nvoid main(void) {\n vec4 offsets = u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.) + offsets;\n}\n\n\n'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.polygonreplay.defaultshader.Vertex.OPTIMIZED_SOURCE = 'attribute vec2 a;uniform mat4 b;uniform mat4 c;uniform mat4 d;void main(void){vec4 offsets=c*vec4(0.,0.,0.,0.);gl_Position=b*vec4(a,0.,1.)+offsets;}'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.polygonreplay.defaultshader.Vertex.SOURCE = ol.DEBUG ? + ol.render.webgl.polygonreplay.defaultshader.Vertex.DEBUG_SOURCE : + ol.render.webgl.polygonreplay.defaultshader.Vertex.OPTIMIZED_SOURCE; + + +ol.render.webgl.polygonreplay.defaultshader.vertex = new ol.render.webgl.polygonreplay.defaultshader.Vertex(); + + +/** + * @constructor + * @param {WebGLRenderingContext} gl GL. + * @param {WebGLProgram} program Program. + * @struct + */ +ol.render.webgl.polygonreplay.defaultshader.Locations = function(gl, program) { + + /** + * @type {WebGLUniformLocation} + */ + this.u_color = gl.getUniformLocation( + program, ol.DEBUG ? 'u_color' : 'e'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_offsetRotateMatrix = gl.getUniformLocation( + program, ol.DEBUG ? 'u_offsetRotateMatrix' : 'd'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_offsetScaleMatrix = gl.getUniformLocation( + program, ol.DEBUG ? 'u_offsetScaleMatrix' : 'c'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_opacity = gl.getUniformLocation( + program, ol.DEBUG ? 'u_opacity' : 'f'); + + /** + * @type {WebGLUniformLocation} + */ + this.u_projectionMatrix = gl.getUniformLocation( + program, ol.DEBUG ? 'u_projectionMatrix' : 'b'); + + /** + * @type {number} + */ + this.a_position = gl.getAttribLocation( + program, ol.DEBUG ? 'a_position' : 'a'); +}; diff --git a/src/ol/render/webgl/polygonreplay/index.js b/src/ol/render/webgl/polygonreplay/index.js new file mode 100644 index 0000000000..427fd3e597 --- /dev/null +++ b/src/ol/render/webgl/polygonreplay/index.js @@ -0,0 +1,1018 @@ +goog.provide('ol.render.webgl.PolygonReplay'); + +goog.require('ol'); +goog.require('ol.color'); +goog.require('ol.extent'); +goog.require('ol.obj'); +goog.require('ol.geom.flat.orient'); +goog.require('ol.geom.flat.transform'); +goog.require('ol.render.webgl.polygonreplay.defaultshader'); +goog.require('ol.render.webgl.LineStringReplay'); +goog.require('ol.render.webgl.Replay'); +goog.require('ol.render.webgl'); +goog.require('ol.style.Stroke'); +goog.require('ol.structs.LinkedList'); +goog.require('ol.structs.RBush'); +goog.require('ol.webgl'); +goog.require('ol.webgl.Buffer'); + + +/** + * @constructor + * @extends {ol.render.webgl.Replay} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Max extent. + * @struct + */ +ol.render.webgl.PolygonReplay = function(tolerance, maxExtent) { + ol.render.webgl.Replay.call(this, tolerance, maxExtent); + + this.lineStringReplay = new ol.render.webgl.LineStringReplay( + tolerance, maxExtent); + + /** + * @private + * @type {ol.render.webgl.polygonreplay.defaultshader.Locations} + */ + this.defaultLocations_ = null; + + /** + * @private + * @type {Array.>} + */ + this.styles_ = []; + + /** + * @private + * @type {Array.} + */ + this.styleIndices_ = []; + + /** + * @private + * @type {{fillColor: (Array.|null), + * changed: boolean}|null} + */ + this.state_ = { + fillColor: null, + changed: false + }; + +}; +ol.inherits(ol.render.webgl.PolygonReplay, ol.render.webgl.Replay); + + +/** + * Draw one polygon. + * @param {Array.} flatCoordinates Flat coordinates. + * @param {Array.>} holeFlatCoordinates Hole flat coordinates. + * @param {number} stride Stride. + * @private + */ +ol.render.webgl.PolygonReplay.prototype.drawCoordinates_ = function( + flatCoordinates, holeFlatCoordinates, stride) { + // Triangulate the polygon + var outerRing = new ol.structs.LinkedList(); + var rtree = new ol.structs.RBush(); + // Initialize the outer ring + var maxX = this.processFlatCoordinates_(flatCoordinates, stride, outerRing, rtree, true); + + // Eliminate holes, if there are any + if (holeFlatCoordinates.length) { + var i, ii; + var holeLists = []; + for (i = 0, ii = holeFlatCoordinates.length; i < ii; ++i) { + var holeList = { + list: new ol.structs.LinkedList(), + maxX: undefined + }; + holeLists.push(holeList); + holeList.maxX = this.processFlatCoordinates_(holeFlatCoordinates[i], + stride, holeList.list, rtree, false); + } + holeLists.sort(function(a, b) { + return b.maxX - a.maxX; + }); + for (i = 0; i < holeLists.length; ++i) { + this.bridgeHole_(holeLists[i].list, holeLists[i].maxX, outerRing, maxX, rtree); + } + } + this.classifyPoints_(outerRing, rtree, false); + this.triangulate_(outerRing, rtree); +}; + + +/** + * Inserts flat coordinates in a linked list and adds them to the vertex buffer. + * @private + * @param {Array.} flatCoordinates Flat coordinates. + * @param {number} stride Stride. + * @param {ol.structs.LinkedList} list Linked list. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + * @param {boolean} clockwise Coordinate order should be clockwise. + * @return {number} Maximum X value. + */ +ol.render.webgl.PolygonReplay.prototype.processFlatCoordinates_ = function( + flatCoordinates, stride, list, rtree, clockwise) { + var isClockwise = ol.geom.flat.orient.linearRingIsClockwise(flatCoordinates, + 0, flatCoordinates.length, stride); + var i, ii, maxX; + var n = this.vertices.length / 2; + /** @type {ol.WebglPolygonVertex} */ + var start; + /** @type {ol.WebglPolygonVertex} */ + var p0; + /** @type {ol.WebglPolygonVertex} */ + var p1; + var extents = []; + var segments = []; + if (clockwise === isClockwise) { + start = this.createPoint_(flatCoordinates[0], flatCoordinates[1], n++); + p0 = start; + maxX = flatCoordinates[0]; + for (i = stride, ii = flatCoordinates.length; i < ii; i += stride) { + p1 = this.createPoint_(flatCoordinates[i], flatCoordinates[i + 1], n++); + segments.push(this.insertItem_(p0, p1, list)); + extents.push([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y), Math.max(p0.x, p1.x), + Math.max(p0.y, p1.y)]); + maxX = flatCoordinates[i] > maxX ? flatCoordinates[i] : maxX; + p0 = p1; + } + segments.push(this.insertItem_(p1, start, list)); + extents.push([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y), Math.max(p0.x, p1.x), + Math.max(p0.y, p1.y)]); + } else { + var end = flatCoordinates.length - stride; + start = this.createPoint_(flatCoordinates[end], flatCoordinates[end + 1], n++); + p0 = start; + maxX = flatCoordinates[end]; + for (i = end - stride, ii = 0; i >= ii; i -= stride) { + p1 = this.createPoint_(flatCoordinates[i], flatCoordinates[i + 1], n++); + segments.push(this.insertItem_(p0, p1, list)); + extents.push([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y), Math.max(p0.x, p1.x), + Math.max(p0.y, p1.y)]); + maxX = flatCoordinates[i] > maxX ? flatCoordinates[i] : maxX; + p0 = p1; + } + segments.push(this.insertItem_(p1, start, list)); + extents.push([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y), Math.max(p0.x, p1.x), + Math.max(p0.y, p1.y)]); + } + rtree.load(extents, segments); + + return maxX; +}; + + +/** + * Classifies the points of a polygon list as convex, reflex. Removes collinear vertices. + * @private + * @param {ol.structs.LinkedList} list Polygon ring. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + * @param {boolean} ccw The orientation of the polygon is counter-clockwise. + * @return {boolean} There were reclassified points. + */ +ol.render.webgl.PolygonReplay.prototype.classifyPoints_ = function(list, rtree, ccw) { + var start = list.firstItem(); + var s0 = start; + var s1 = list.nextItem(); + var pointsReclassified = false; + do { + var reflex = ccw ? ol.render.webgl.triangleIsCounterClockwise(s1.p1.x, + s1.p1.y, s0.p1.x, s0.p1.y, s0.p0.x, s0.p0.y) : + ol.render.webgl.triangleIsCounterClockwise(s0.p0.x, s0.p0.y, s0.p1.x, + s0.p1.y, s1.p1.x, s1.p1.y); + if (reflex === undefined) { + this.removeItem_(s0, s1, list, rtree); + pointsReclassified = true; + if (s1 === start) { + start = list.getNextItem(); + } + s1 = s0; + list.prevItem(); + } else if (s0.p1.reflex !== reflex) { + s0.p1.reflex = reflex; + pointsReclassified = true; + } + s0 = s1; + s1 = list.nextItem(); + } while (s0 !== start); + return pointsReclassified; +}; + + +/** + * @private + * @param {ol.structs.LinkedList} hole Linked list of the hole. + * @param {number} holeMaxX Maximum X value of the hole. + * @param {ol.structs.LinkedList} list Linked list of the polygon. + * @param {number} listMaxX Maximum X value of the polygon. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + */ +ol.render.webgl.PolygonReplay.prototype.bridgeHole_ = function(hole, holeMaxX, + list, listMaxX, rtree) { + var seg = hole.firstItem(); + while (seg.p1.x !== holeMaxX) { + seg = hole.nextItem(); + } + + var p1 = seg.p1; + /** @type {ol.WebglPolygonVertex} */ + var p2 = {x: listMaxX, y: p1.y, i: -1}; + var minDist = Infinity; + var i, ii, bestPoint; + /** @type {ol.WebglPolygonVertex} */ + var p5; + + var intersectingSegments = this.getIntersections_({p0: p1, p1: p2}, rtree, true); + for (i = 0, ii = intersectingSegments.length; i < ii; ++i) { + var currSeg = intersectingSegments[i]; + if (currSeg.p0 !== p1 && currSeg.p1 !== p1) { + var intersection = this.calculateIntersection_(p1, p2, currSeg.p0, + currSeg.p1, true); + var dist = Math.abs(p1.x - intersection[0]); + if (dist < minDist) { + minDist = dist; + p5 = {x: intersection[0], y: intersection[1], i: -1}; + seg = currSeg; + } + } + } + bestPoint = seg.p1; + + var pointsInTriangle = this.getPointsInTriangle_(p1, p5, seg.p1, rtree); + if (pointsInTriangle.length) { + var theta = Infinity; + for (i = 0, ii = pointsInTriangle.length; i < ii; ++i) { + var currPoint = pointsInTriangle[i]; + var currTheta = Math.atan2(p1.y - currPoint.y, p2.x - currPoint.x); + if (currTheta < theta || (currTheta === theta && currPoint.x < bestPoint.x)) { + theta = currTheta; + bestPoint = currPoint; + } + } + } + + seg = list.firstItem(); + while (seg.p1 !== bestPoint) { + seg = list.nextItem(); + } + + //We clone the bridge points as they can have different convexity. + var p0Bridge = {x: p1.x, y: p1.y, i: p1.i, reflex: undefined}; + var p1Bridge = {x: seg.p1.x, y: seg.p1.y, i: seg.p1.i, reflex: undefined}; + + hole.getNextItem().p0 = p0Bridge; + this.insertItem_(p1, seg.p1, hole, rtree); + this.insertItem_(p1Bridge, p0Bridge, hole, rtree); + seg.p1 = p1Bridge; + hole.setFirstItem(); + list.concat(hole); +}; + + +/** + * @private + * @param {ol.structs.LinkedList} list Linked list of the polygon. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + */ +ol.render.webgl.PolygonReplay.prototype.triangulate_ = function(list, rtree) { + var ccw = false; + var simple = this.isSimple_(list, rtree); + + // Start clipping ears + while (list.getLength() > 3) { + if (simple) { + if (!this.clipEars_(list, rtree, simple, ccw)) { + if (!this.classifyPoints_(list, rtree, ccw)) { + // Due to the behavior of OL's PIP algorithm, the ear clipping cannot + // introduce touching segments. However, the original data may have some. + if (!this.resolveLocalSelfIntersections_(list, rtree, true)) { + // Something went wrong. + break; + } + } + } + } else { + if (!this.clipEars_(list, rtree, simple, ccw)) { + // We ran out of ears, try to reclassify. + if (!this.classifyPoints_(list, rtree, ccw)) { + // We have a bad polygon, try to resolve local self-intersections. + if (!this.resolveLocalSelfIntersections_(list, rtree)) { + simple = this.isSimple_(list, rtree); + if (!simple) { + // We have a really bad polygon, try more time consuming methods. + this.splitPolygon_(list, rtree); + break; + } else { + ccw = !this.isClockwise_(list); + this.classifyPoints_(list, rtree, ccw); + } + } + } + } + } + } + if (list.getLength() === 3) { + var numIndices = this.indices.length; + this.indices[numIndices++] = list.getPrevItem().p0.i; + this.indices[numIndices++] = list.getCurrItem().p0.i; + this.indices[numIndices++] = list.getNextItem().p0.i; + } +}; + + +/** + * @private + * @param {ol.structs.LinkedList} list Linked list of the polygon. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + * @param {boolean} simple The polygon is simple. + * @param {boolean} ccw Orientation of the polygon is counter-clockwise. + * @return {boolean} There were processed ears. + */ +ol.render.webgl.PolygonReplay.prototype.clipEars_ = function(list, rtree, simple, ccw) { + var numIndices = this.indices.length; + var start = list.firstItem(); + var s0 = list.getPrevItem(); + var s1 = start; + var s2 = list.nextItem(); + var s3 = list.getNextItem(); + var p0, p1, p2; + var processedEars = false; + do { + p0 = s1.p0; + p1 = s1.p1; + p2 = s2.p1; + if (p1.reflex === false) { + // We might have a valid ear + var diagonalIsInside = ccw ? this.diagonalIsInside_(s3.p1, p2, p1, p0, + s0.p0) : this.diagonalIsInside_(s0.p0, p0, p1, p2, s3.p1); + if ((simple || this.getIntersections_({p0: p0, p1: p2}, rtree).length === 0) && + diagonalIsInside && this.getPointsInTriangle_(p0, p1, p2, rtree, true).length === 0) { + //The diagonal is completely inside the polygon + if (simple || p0.reflex === false || p2.reflex === false || + ol.geom.flat.orient.linearRingIsClockwise([s0.p0.x, s0.p0.y, p0.x, + p0.y, p1.x, p1.y, p2.x, p2.y, s3.p1.x, s3.p1.y], 0, 10, 2) === !ccw) { + //The diagonal is persumably valid, we have an ear + this.indices[numIndices++] = p0.i; + this.indices[numIndices++] = p1.i; + this.indices[numIndices++] = p2.i; + this.removeItem_(s1, s2, list, rtree); + if (s2 === start) { + start = s3; + } + processedEars = true; + } + } + } + // Else we have a reflex point. + s0 = list.getPrevItem(); + s1 = list.getCurrItem(); + s2 = list.nextItem(); + s3 = list.getNextItem(); + } while (s1 !== start && list.getLength() > 3); + + return processedEars; +}; + + +/** + * @private + * @param {ol.structs.LinkedList} list Linked list of the polygon. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + * @param {boolean=} opt_touch Resolve touching segments. + * @return {boolean} There were resolved intersections. +*/ +ol.render.webgl.PolygonReplay.prototype.resolveLocalSelfIntersections_ = function( + list, rtree, opt_touch) { + var start = list.firstItem(); + list.nextItem(); + var s0 = start; + var s1 = list.nextItem(); + var resolvedIntersections = false; + + do { + var intersection = this.calculateIntersection_(s0.p0, s0.p1, s1.p0, s1.p1, + opt_touch); + if (intersection) { + var breakCond = false; + var numVertices = this.vertices.length; + var numIndices = this.indices.length; + var n = numVertices / 2; + var seg = list.prevItem(); + list.removeItem(); + rtree.remove(seg); + breakCond = (seg === start); + var p; + if (opt_touch) { + if (intersection[0] === s0.p0.x && intersection[1] === s0.p0.y) { + list.prevItem(); + p = s0.p0; + s1.p0 = p; + rtree.remove(s0); + breakCond = breakCond || (s0 === start); + } else { + p = s1.p1; + s0.p1 = p; + rtree.remove(s1); + breakCond = breakCond || (s1 === start); + } + list.removeItem(); + } else { + p = this.createPoint_(intersection[0], intersection[1], n); + s0.p1 = p; + s1.p0 = p; + rtree.update([Math.min(s0.p0.x, s0.p1.x), Math.min(s0.p0.y, s0.p1.y), + Math.max(s0.p0.x, s0.p1.x), Math.max(s0.p0.y, s0.p1.y)], s0); + rtree.update([Math.min(s1.p0.x, s1.p1.x), Math.min(s1.p0.y, s1.p1.y), + Math.max(s1.p0.x, s1.p1.x), Math.max(s1.p0.y, s1.p1.y)], s1); + } + + this.indices[numIndices++] = seg.p0.i; + this.indices[numIndices++] = seg.p1.i; + this.indices[numIndices++] = p.i; + + resolvedIntersections = true; + if (breakCond) { + break; + } + } + + s0 = list.getPrevItem(); + s1 = list.nextItem(); + } while (s0 !== start); + return resolvedIntersections; +}; + + +/** + * @private + * @param {ol.structs.LinkedList} list Linked list of the polygon. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + * @return {boolean} The polygon is simple. + */ +ol.render.webgl.PolygonReplay.prototype.isSimple_ = function(list, rtree) { + var start = list.firstItem(); + var seg = start; + do { + if (this.getIntersections_(seg, rtree).length) { + return false; + } + seg = list.nextItem(); + } while (seg !== start); + return true; +}; + + +/** + * @private + * @param {ol.structs.LinkedList} list Linked list of the polygon. + * @return {boolean} Orientation is clockwise. + */ +ol.render.webgl.PolygonReplay.prototype.isClockwise_ = function(list) { + var length = list.getLength() * 2; + var flatCoordinates = new Array(length); + var start = list.firstItem(); + var seg = start; + var i = 0; + do { + flatCoordinates[i++] = seg.p0.x; + flatCoordinates[i++] = seg.p0.y; + seg = list.nextItem(); + } while (seg !== start); + return ol.geom.flat.orient.linearRingIsClockwise(flatCoordinates, 0, length, 2); +}; + + +/** + * @private + * @param {ol.structs.LinkedList} list Linked list of the polygon. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + */ +ol.render.webgl.PolygonReplay.prototype.splitPolygon_ = function(list, rtree) { + var start = list.firstItem(); + var s0 = start; + do { + var intersections = this.getIntersections_(s0, rtree); + if (intersections.length) { + var s1 = intersections[0]; + var n = this.vertices.length / 2; + var intersection = this.calculateIntersection_(s0.p0, + s0.p1, s1.p0, s1.p1); + var p = this.createPoint_(intersection[0], intersection[1], n); + var newPolygon = new ol.structs.LinkedList(); + var newRtree = new ol.structs.RBush(); + this.insertItem_(p, s0.p1, newPolygon, newRtree); + s0.p1 = p; + rtree.update([Math.min(s0.p0.x, p.x), Math.min(s0.p0.y, p.y), + Math.max(s0.p0.x, p.x), Math.max(s0.p0.y, p.y)], s0); + var currItem = list.nextItem(); + while (currItem !== s1) { + this.insertItem_(currItem.p0, currItem.p1, newPolygon, newRtree); + rtree.remove(currItem); + list.removeItem(); + currItem = list.getCurrItem(); + } + this.insertItem_(s1.p0, p, newPolygon, newRtree); + s1.p0 = p; + rtree.update([Math.min(s1.p1.x, p.x), Math.min(s1.p1.y, p.y), + Math.max(s1.p1.x, p.x), Math.max(s1.p1.y, p.y)], s1); + this.classifyPoints_(list, rtree, false); + this.triangulate_(list, rtree); + this.classifyPoints_(newPolygon, newRtree, false); + this.triangulate_(newPolygon, newRtree); + break; + } + s0 = list.nextItem(); + } while (s0 !== start); +}; + + +/** + * @private + * @param {number} x X coordinate. + * @param {number} y Y coordinate. + * @param {number} i Index. + * @return {ol.WebglPolygonVertex} List item. + */ +ol.render.webgl.PolygonReplay.prototype.createPoint_ = function(x, y, i) { + var numVertices = this.vertices.length; + this.vertices[numVertices++] = x; + this.vertices[numVertices++] = y; + /** @type {ol.WebglPolygonVertex} */ + var p = { + x: x, + y: y, + i: i, + reflex: undefined + }; + return p; +}; + + +/** + * @private + * @param {ol.WebglPolygonVertex} p0 First point of segment. + * @param {ol.WebglPolygonVertex} p1 Second point of segment. + * @param {ol.structs.LinkedList} list Polygon ring. + * @param {ol.structs.RBush=} opt_rtree Insert the segment into the R-Tree. + * @return {ol.WebglPolygonSegment} segment. + */ +ol.render.webgl.PolygonReplay.prototype.insertItem_ = function(p0, p1, list, opt_rtree) { + var seg = { + p0: p0, + p1: p1 + }; + list.insertItem(seg); + if (opt_rtree) { + opt_rtree.insert([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y), + Math.max(p0.x, p1.x), Math.max(p0.y, p1.y)], seg); + } + return seg; +}; + + + /** + * @private + * @param {ol.WebglPolygonSegment} s0 Segment before the remove candidate. + * @param {ol.WebglPolygonSegment} s1 Remove candidate segment. + * @param {ol.structs.LinkedList} list Polygon ring. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + */ +ol.render.webgl.PolygonReplay.prototype.removeItem_ = function(s0, s1, list, rtree) { + list.removeItem(); + s0.p1 = s1.p1; + rtree.remove(s1); + rtree.update([Math.min(s0.p0.x, s0.p1.x), Math.min(s0.p0.y, s0.p1.y), + Math.max(s0.p0.x, s0.p1.x), Math.max(s0.p0.y, s0.p1.y)], s0); +}; + + +/** + * @private + * @param {ol.WebglPolygonVertex} p0 First point. + * @param {ol.WebglPolygonVertex} p1 Second point. + * @param {ol.WebglPolygonVertex} p2 Third point. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + * @param {boolean=} opt_reflex Only include reflex points. + * @return {Array.} Points in the triangle. + */ +ol.render.webgl.PolygonReplay.prototype.getPointsInTriangle_ = function(p0, p1, + p2, rtree, opt_reflex) { + var i, ii, j, p; + var result = []; + var segmentsInExtent = rtree.getInExtent([Math.min(p0.x, p1.x, p2.x), + Math.min(p0.y, p1.y, p2.y), Math.max(p0.x, p1.x, p2.x), Math.max(p0.y, + p1.y, p2.y)]); + for (i = 0, ii = segmentsInExtent.length; i < ii; ++i) { + for (j in segmentsInExtent[i]) { + p = segmentsInExtent[i][j]; + if (p.x && p.y && (!opt_reflex || p.reflex)) { + if ((p.x !== p0.x || p.y !== p0.y) && (p.x !== p1.x || p.y !== p1.y) && + (p.x !== p2.x || p.y !== p2.y) && result.indexOf(p) === -1 && + ol.geom.flat.contains.linearRingContainsXY([p0.x, p0.y, p1.x, p1.y, + p2.x, p2.y], 0, 6, 2, p.x, p.y)) { + result.push(p); + } + } + } + } + return result; +}; + + +/** + * @private + * @param {ol.WebglPolygonSegment} segment Segment. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + * @param {boolean=} opt_touch Touching segments should be considered an intersection. + * @return {Array.} Intersecting segments. + */ +ol.render.webgl.PolygonReplay.prototype.getIntersections_ = function(segment, rtree, opt_touch) { + var p0 = segment.p0; + var p1 = segment.p1; + var segmentsInExtent = rtree.getInExtent([Math.min(p0.x, p1.x), + Math.min(p0.y, p1.y), Math.max(p0.x, p1.x), Math.max(p0.y, p1.y)]); + var result = []; + var i, ii; + for (i = 0, ii = segmentsInExtent.length; i < ii; ++i) { + var currSeg = segmentsInExtent[i]; + if (segment !== currSeg && (opt_touch || currSeg.p0 !== p1 || currSeg.p1 !== p0) && + this.calculateIntersection_(p0, p1, currSeg.p0, currSeg.p1, opt_touch)) { + result.push(currSeg); + } + } + return result; +}; + + +/** + * Line intersection algorithm by Paul Bourke. + * @see http://paulbourke.net/geometry/pointlineplane/ + * + * @private + * @param {ol.WebglPolygonVertex} p0 First point. + * @param {ol.WebglPolygonVertex} p1 Second point. + * @param {ol.WebglPolygonVertex} p2 Third point. + * @param {ol.WebglPolygonVertex} p3 Fourth point. + * @param {boolean=} opt_touch Touching segments should be considered an intersection. + * @return {Array.|undefined} Intersection coordinates. + */ +ol.render.webgl.PolygonReplay.prototype.calculateIntersection_ = function(p0, + p1, p2, p3, opt_touch) { + var denom = (p3.y - p2.y) * (p1.x - p0.x) - (p3.x - p2.x) * (p1.y - p0.y); + if (denom !== 0) { + var ua = ((p3.x - p2.x) * (p0.y - p2.y) - (p3.y - p2.y) * (p0.x - p2.x)) / denom; + var ub = ((p1.x - p0.x) * (p0.y - p2.y) - (p1.y - p0.y) * (p0.x - p2.x)) / denom; + if ((!opt_touch && ua > ol.render.webgl.EPSILON && ua < 1 - ol.render.webgl.EPSILON && + ub > ol.render.webgl.EPSILON && ub < 1 - ol.render.webgl.EPSILON) || (opt_touch && + ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1)) { + return [p0.x + ua * (p1.x - p0.x), p0.y + ua * (p1.y - p0.y)]; + } + } + return undefined; +}; + + +/** + * @private + * @param {ol.WebglPolygonVertex} p0 Point before the start of the diagonal. + * @param {ol.WebglPolygonVertex} p1 Start point of the diagonal. + * @param {ol.WebglPolygonVertex} p2 Ear candidate. + * @param {ol.WebglPolygonVertex} p3 End point of the diagonal. + * @param {ol.WebglPolygonVertex} p4 Point after the end of the diagonal. + * @return {boolean} Diagonal is inside the polygon. + */ +ol.render.webgl.PolygonReplay.prototype.diagonalIsInside_ = function(p0, p1, p2, p3, p4) { + if (p1.reflex === undefined || p3.reflex === undefined) { + return false; + } + var p1IsLeftOf = (p2.x - p3.x) * (p1.y - p3.y) > (p2.y - p3.y) * (p1.x - p3.x); + var p1IsRightOf = (p4.x - p3.x) * (p1.y - p3.y) < (p4.y - p3.y) * (p1.x - p3.x); + var p3IsLeftOf = (p0.x - p1.x) * (p3.y - p1.y) > (p0.y - p1.y) * (p3.x - p1.x); + var p3IsRightOf = (p2.x - p1.x) * (p3.y - p1.y) < (p2.y - p1.y) * (p3.x - p1.x); + var p1InCone = p3.reflex ? p1IsRightOf || p1IsLeftOf : p1IsRightOf && p1IsLeftOf; + var p3InCone = p1.reflex ? p3IsRightOf || p3IsLeftOf : p3IsRightOf && p3IsLeftOf; + return p1InCone && p3InCone; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.PolygonReplay.prototype.drawMultiPolygon = function(multiPolygonGeometry, feature) { + var polygons = multiPolygonGeometry.getPolygons(); + var stride = multiPolygonGeometry.getStride(); + var currIndex = this.indices.length; + var currLineIndex = this.lineStringReplay.getCurrentIndex(); + var i, ii, j, jj; + for (i = 0, ii = polygons.length; i < ii; ++i) { + var linearRings = polygons[i].getLinearRings(); + if (linearRings.length > 0) { + var flatCoordinates = linearRings[0].getFlatCoordinates(); + flatCoordinates = ol.geom.flat.transform.translate(flatCoordinates, 0, flatCoordinates.length, + stride, -this.origin[0], -this.origin[1]); + var holes = []; + var holeFlatCoords; + for (j = 1, jj = linearRings.length; j < jj; ++j) { + holeFlatCoords = linearRings[j].getFlatCoordinates(); + holeFlatCoords = ol.geom.flat.transform.translate(holeFlatCoords, 0, holeFlatCoords.length, + stride, -this.origin[0], -this.origin[1]); + holes.push(holeFlatCoords); + } + this.lineStringReplay.drawPolygonCoordinates(flatCoordinates, holes, stride); + this.drawCoordinates_(flatCoordinates, holes, stride); + } + } + if (this.indices.length > currIndex) { + this.startIndices.push(currIndex); + this.startIndicesFeature.push(feature); + if (this.state_.changed) { + this.styleIndices_.push(currIndex); + this.state_.changed = false; + } + } + if (this.lineStringReplay.getCurrentIndex() > currLineIndex) { + this.lineStringReplay.setPolygonStyle(feature, currLineIndex); + } +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.PolygonReplay.prototype.drawPolygon = function(polygonGeometry, feature) { + var linearRings = polygonGeometry.getLinearRings(); + var stride = polygonGeometry.getStride(); + if (linearRings.length > 0) { + this.startIndices.push(this.indices.length); + this.startIndicesFeature.push(feature); + if (this.state_.changed) { + this.styleIndices_.push(this.indices.length); + this.state_.changed = false; + } + this.lineStringReplay.setPolygonStyle(feature); + + var flatCoordinates = linearRings[0].getFlatCoordinates(); + flatCoordinates = ol.geom.flat.transform.translate(flatCoordinates, 0, flatCoordinates.length, + stride, -this.origin[0], -this.origin[1]); + var holes = []; + var i, ii, holeFlatCoords; + for (i = 1, ii = linearRings.length; i < ii; ++i) { + holeFlatCoords = linearRings[i].getFlatCoordinates(); + holeFlatCoords = ol.geom.flat.transform.translate(holeFlatCoords, 0, holeFlatCoords.length, + stride, -this.origin[0], -this.origin[1]); + holes.push(holeFlatCoords); + } + this.lineStringReplay.drawPolygonCoordinates(flatCoordinates, holes, stride); + this.drawCoordinates_(flatCoordinates, holes, stride); + } +}; + + +/** + * @inheritDoc + **/ +ol.render.webgl.PolygonReplay.prototype.finish = function(context) { + // create, bind, and populate the vertices buffer + this.verticesBuffer = new ol.webgl.Buffer(this.vertices); + + // create, bind, and populate the indices buffer + this.indicesBuffer = new ol.webgl.Buffer(this.indices); + + this.startIndices.push(this.indices.length); + + this.lineStringReplay.finish(context); + + //Clean up, if there is nothing to draw + if (this.styleIndices_.length === 0 && this.styles_.length > 0) { + this.styles_ = []; + } + + this.vertices = null; + this.indices = null; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.PolygonReplay.prototype.getDeleteResourcesFunction = function(context) { + // We only delete our stuff here. The shaders and the program may + // be used by other PolygonReplay instances (for other layers). And + // they will be deleted when disposing of the ol.webgl.Context + // object. + ol.DEBUG && console.assert(this.verticesBuffer, + 'verticesBuffer must not be null'); + ol.DEBUG && console.assert(this.indicesBuffer, + 'indicesBuffer must not be null'); + var verticesBuffer = this.verticesBuffer; + var indicesBuffer = this.indicesBuffer; + var lineDeleter = this.lineStringReplay.getDeleteResourcesFunction(context); + return function() { + context.deleteBuffer(verticesBuffer); + context.deleteBuffer(indicesBuffer); + lineDeleter(); + }; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.PolygonReplay.prototype.setUpProgram = function(gl, context, size, pixelRatio) { + // get the program + var fragmentShader, vertexShader; + fragmentShader = ol.render.webgl.polygonreplay.defaultshader.fragment; + vertexShader = ol.render.webgl.polygonreplay.defaultshader.vertex; + var program = context.getProgram(fragmentShader, vertexShader); + + // get the locations + var locations; + if (!this.defaultLocations_) { + locations = + new ol.render.webgl.polygonreplay.defaultshader.Locations(gl, program); + this.defaultLocations_ = locations; + } else { + locations = this.defaultLocations_; + } + + context.useProgram(program); + + // enable the vertex attrib arrays + gl.enableVertexAttribArray(locations.a_position); + gl.vertexAttribPointer(locations.a_position, 2, ol.webgl.FLOAT, + false, 8, 0); + + return locations; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.PolygonReplay.prototype.drawReplay = function(gl, context, skippedFeaturesHash, hitDetection) { + //Save GL parameters. + var tmpDepthFunc = /** @type {number} */ (gl.getParameter(gl.DEPTH_FUNC)); + var tmpDepthMask = /** @type {boolean} */ (gl.getParameter(gl.DEPTH_WRITEMASK)); + + if (!hitDetection) { + gl.enable(gl.DEPTH_TEST); + gl.depthMask(true); + gl.depthFunc(gl.NOTEQUAL); + } + + if (!ol.obj.isEmpty(skippedFeaturesHash)) { + this.drawReplaySkipping_(gl, context, skippedFeaturesHash); + } else { + ol.DEBUG && console.assert(this.styles_.length === this.styleIndices_.length, + 'number of styles and styleIndices match'); + + //Draw by style groups to minimize drawElements() calls. + var i, start, end, nextStyle; + end = this.startIndices[this.startIndices.length - 1]; + for (i = this.styleIndices_.length - 1; i >= 0; --i) { + start = this.styleIndices_[i]; + nextStyle = this.styles_[i]; + this.setFillStyle_(gl, nextStyle); + this.drawElements(gl, context, start, end); + end = start; + } + } + if (!hitDetection) { + gl.clear(gl.DEPTH_BUFFER_BIT); + gl.disable(gl.DEPTH_TEST); + //Restore GL parameters. + gl.depthMask(tmpDepthMask); + gl.depthFunc(tmpDepthFunc); + } +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.PolygonReplay.prototype.drawHitDetectionReplayOneByOne = function(gl, context, skippedFeaturesHash, + featureCallback, opt_hitExtent) { + ol.DEBUG && console.assert(this.styles_.length === this.styleIndices_.length, + 'number of styles and styleIndices match'); + ol.DEBUG && console.assert(this.startIndices.length - 1 === this.startIndicesFeature.length, + 'number of startIndices and startIndicesFeature match'); + + var i, start, end, nextStyle, groupStart, feature, featureUid, featureIndex; + featureIndex = this.startIndices.length - 2; + end = this.startIndices[featureIndex + 1]; + for (i = this.styleIndices_.length - 1; i >= 0; --i) { + nextStyle = this.styles_[i]; + this.setFillStyle_(gl, nextStyle); + groupStart = this.styleIndices_[i]; + + while (featureIndex >= 0 && + this.startIndices[featureIndex] >= groupStart) { + start = this.startIndices[featureIndex]; + feature = this.startIndicesFeature[featureIndex]; + featureUid = ol.getUid(feature).toString(); + + if (skippedFeaturesHash[featureUid] === undefined && + feature.getGeometry() && + (opt_hitExtent === undefined || ol.extent.intersects( + /** @type {Array} */ (opt_hitExtent), + feature.getGeometry().getExtent()))) { + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + this.drawElements(gl, context, start, end); + + var result = featureCallback(feature); + + if (result) { + return result; + } + + } + featureIndex--; + end = start; + } + } + return undefined; +}; + + +/** + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object} skippedFeaturesHash Ids of features to skip. + */ +ol.render.webgl.PolygonReplay.prototype.drawReplaySkipping_ = function(gl, context, skippedFeaturesHash) { + ol.DEBUG && console.assert(this.startIndices.length - 1 === this.startIndicesFeature.length, + 'number of startIndices and startIndicesFeature match'); + + var i, start, end, nextStyle, groupStart, feature, featureUid, featureIndex, featureStart; + featureIndex = this.startIndices.length - 2; + end = start = this.startIndices[featureIndex + 1]; + for (i = this.styleIndices_.length - 1; i >= 0; --i) { + nextStyle = this.styles_[i]; + this.setFillStyle_(gl, nextStyle); + groupStart = this.styleIndices_[i]; + + while (featureIndex >= 0 && + this.startIndices[featureIndex] >= groupStart) { + featureStart = this.startIndices[featureIndex]; + feature = this.startIndicesFeature[featureIndex]; + featureUid = ol.getUid(feature).toString(); + + if (skippedFeaturesHash[featureUid]) { + if (start !== end) { + this.drawElements(gl, context, start, end); + } + end = featureStart; + } + featureIndex--; + start = featureStart; + } + if (start !== end) { + this.drawElements(gl, context, start, end); + } + } +}; + + +/** + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {Array.} color Color. + */ +ol.render.webgl.PolygonReplay.prototype.setFillStyle_ = function(gl, color) { + gl.uniform4fv(this.defaultLocations_.u_color, color); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.PolygonReplay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) { + ol.DEBUG && console.assert(this.state_, 'this.state_ should not be null'); + var fillStyleColor = fillStyle ? fillStyle.getColor() : [0, 0, 0, 0]; + if (!(fillStyleColor instanceof CanvasGradient) && + !(fillStyleColor instanceof CanvasPattern)) { + fillStyleColor = ol.color.asArray(fillStyleColor).map(function(c, i) { + return i != 3 ? c / 255 : c; + }) || ol.render.webgl.defaultFillStyle; + } else { + fillStyleColor = ol.render.webgl.defaultFillStyle; + } + if (!this.state_.fillColor || !ol.array.equals(fillStyleColor, this.state_.fillColor)) { + this.state_.fillColor = fillStyleColor; + this.state_.changed = true; + this.styles_.push(fillStyleColor); + } + //Provide a null stroke style, if no strokeStyle is provided. Required for the draw interaction to work. + if (strokeStyle) { + this.lineStringReplay.setFillStrokeStyle(null, strokeStyle); + } else { + var nullStrokeStyle = new ol.style.Stroke({ + color: [0, 0, 0, 0], + lineWidth: 0 + }); + this.lineStringReplay.setFillStrokeStyle(null, nullStrokeStyle); + } +}; diff --git a/src/ol/render/webgl/replay.js b/src/ol/render/webgl/replay.js new file mode 100644 index 0000000000..7caed3e343 --- /dev/null +++ b/src/ol/render/webgl/replay.js @@ -0,0 +1,321 @@ +goog.provide('ol.render.webgl.Replay'); + +goog.require('ol'); +goog.require('ol.render.VectorContext'); +goog.require('ol.transform'); + +/** + * @constructor + * @extends {ol.render.VectorContext} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Max extent. + * @struct + */ +ol.render.webgl.Replay = function(tolerance, maxExtent) { + ol.render.VectorContext.call(this); + + /** + * @protected + * @type {number} + */ + this.tolerance = tolerance; + + /** + * @protected + * @const + * @type {ol.Extent} + */ + this.maxExtent = maxExtent; + + /** + * The origin of the coordinate system for the point coordinates sent to + * the GPU. To eliminate jitter caused by precision problems in the GPU + * we use the "Rendering Relative to Eye" technique described in the "3D + * Engine Design for Virtual Globes" book. + * @protected + * @type {ol.Coordinate} + */ + this.origin = ol.extent.getCenter(maxExtent); + + /** + * @private + * @type {ol.Transform} + */ + this.projectionMatrix_ = ol.transform.create(); + + /** + * @private + * @type {ol.Transform} + */ + this.offsetRotateMatrix_ = ol.transform.create(); + + /** + * @private + * @type {ol.Transform} + */ + this.offsetScaleMatrix_ = ol.transform.create(); + + /** + * @private + * @type {Array.} + */ + this.tmpMat4_ = ol.vec.Mat4.create(); + + /** + * @protected + * @type {Array.} + */ + this.indices = []; + + /** + * @protected + * @type {?ol.webgl.Buffer} + */ + this.indicesBuffer = null; + + /** + * Start index per feature (the index). + * @protected + * @type {Array.} + */ + this.startIndices = []; + + /** + * Start index per feature (the feature). + * @protected + * @type {Array.} + */ + this.startIndicesFeature = []; + + /** + * @protected + * @type {Array.} + */ + this.vertices = []; + + /** + * @protected + * @type {?ol.webgl.Buffer} + */ + this.verticesBuffer = null; + + /** + * Optional parameter for PolygonReplay instances. + * @protected + * @type {ol.render.webgl.LineStringReplay|undefined} + */ + this.lineStringReplay = undefined; + +}; +ol.inherits(ol.render.webgl.Replay, ol.render.VectorContext); + + +/** + * @abstract + * @param {ol.webgl.Context} context WebGL context. + * @return {function()} Delete resources function. + */ +ol.render.webgl.Replay.prototype.getDeleteResourcesFunction = function(context) {}; + + +/** + * @abstract + * @param {ol.webgl.Context} context Context. + */ +ol.render.webgl.Replay.prototype.finish = function(context) {}; + + +/** + * @abstract + * @protected + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {ol.Size} size Size. + * @param {number} pixelRatio Pixel ratio. + * @return {ol.render.webgl.imagereplay.defaultshader.Locations| + ol.render.webgl.linestringreplay.defaultshader.Locations| + ol.render.webgl.polygonreplay.defaultshader.Locations} Locations. + */ +ol.render.webgl.Replay.prototype.setUpProgram = function(gl, context, size, pixelRatio) {}; + + +/** + * @abstract + * @protected + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object.} skippedFeaturesHash Ids of features + * to skip. + * @param {boolean} hitDetection Hit detection mode. + */ +ol.render.webgl.Replay.prototype.drawReplay = function(gl, context, skippedFeaturesHash, hitDetection) {}; + + +/** + * @abstract + * @protected + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object.} skippedFeaturesHash Ids of features + * to skip. + * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback. + * @param {ol.Extent=} opt_hitExtent Hit extent: Only features intersecting + * this extent are checked. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.webgl.Replay.prototype.drawHitDetectionReplayOneByOne = function(gl, context, skippedFeaturesHash, featureCallback, opt_hitExtent) {}; + + +/** + * @protected + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object.} skippedFeaturesHash Ids of features + * to skip. + * @param {function((ol.Feature|ol.render.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.Replay.prototype.drawHitDetectionReplay = function(gl, context, skippedFeaturesHash, + featureCallback, oneByOne, opt_hitExtent) { + if (!oneByOne) { + // draw all hit-detection features in "once" (by texture group) + return this.drawHitDetectionReplayAll(gl, context, + skippedFeaturesHash, featureCallback); + } else { + // draw hit-detection features one by one + return this.drawHitDetectionReplayOneByOne(gl, context, + skippedFeaturesHash, featureCallback, opt_hitExtent); + } +}; + + +/** + * @protected + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object.} skippedFeaturesHash Ids of features + * to skip. + * @param {function((ol.Feature|ol.render.Feature)): T|undefined} featureCallback Feature callback. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.webgl.Replay.prototype.drawHitDetectionReplayAll = function(gl, context, skippedFeaturesHash, + featureCallback) { + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + this.drawReplay(gl, context, skippedFeaturesHash, true); + + var result = featureCallback(null); + if (result) { + return result; + } else { + return undefined; + } +}; + + +/** + * @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 {Object.} skippedFeaturesHash Ids of features + * to skip. + * @param {function((ol.Feature|ol.render.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.Replay.prototype.replay = function(context, + center, resolution, rotation, size, pixelRatio, + opacity, skippedFeaturesHash, + featureCallback, oneByOne, opt_hitExtent) { + var gl = context.getGL(); + + // bind the vertices buffer + ol.DEBUG && console.assert(this.verticesBuffer, + 'verticesBuffer must not be null'); + context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.verticesBuffer); + + // bind the indices buffer + ol.DEBUG && console.assert(this.indicesBuffer, + 'indicesBuffer must not be null'); + context.bindBuffer(ol.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer); + + var locations = this.setUpProgram(gl, context, size, pixelRatio); + + // set the "uniform" values + var projectionMatrix = ol.transform.reset(this.projectionMatrix_); + ol.transform.scale(projectionMatrix, 2 / (resolution * size[0]), 2 / (resolution * size[1])); + ol.transform.rotate(projectionMatrix, -rotation); + ol.transform.translate(projectionMatrix, -(center[0] - this.origin[0]), -(center[1] - this.origin[1])); + + var offsetScaleMatrix = ol.transform.reset(this.offsetScaleMatrix_); + ol.transform.scale(offsetScaleMatrix, 2 / size[0], 2 / size[1]); + + var offsetRotateMatrix = ol.transform.reset(this.offsetRotateMatrix_); + if (rotation !== 0) { + ol.transform.rotate(offsetRotateMatrix, -rotation); + } + + gl.uniformMatrix4fv(locations.u_projectionMatrix, false, + ol.vec.Mat4.fromTransform(this.tmpMat4_, projectionMatrix)); + gl.uniformMatrix4fv(locations.u_offsetScaleMatrix, false, + ol.vec.Mat4.fromTransform(this.tmpMat4_, offsetScaleMatrix)); + gl.uniformMatrix4fv(locations.u_offsetRotateMatrix, false, + ol.vec.Mat4.fromTransform(this.tmpMat4_, offsetRotateMatrix)); + gl.uniform1f(locations.u_opacity, opacity); + + // draw! + var result; + if (featureCallback === undefined) { + this.drawReplay(gl, context, skippedFeaturesHash, false); + } else { + // draw feature by feature for the hit-detection + result = this.drawHitDetectionReplay(gl, context, skippedFeaturesHash, + featureCallback, oneByOne, opt_hitExtent); + } + + // disable the vertex attrib arrays + for (var i in locations) { + if (typeof locations[i] === 'number') { + gl.disableVertexAttribArray(locations[i]); + } + } + + if (this.lineStringReplay) { + this.lineStringReplay.replay(context, + center, resolution, rotation, size, pixelRatio, + opacity, skippedFeaturesHash, + featureCallback, oneByOne, opt_hitExtent); + } + + return result; +}; + +/** + * @protected + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {number} start Start index. + * @param {number} end End index. + */ +ol.render.webgl.Replay.prototype.drawElements = function( + gl, context, start, end) { + var elementType = context.hasOESElementIndexUint ? + ol.webgl.UNSIGNED_INT : ol.webgl.UNSIGNED_SHORT; + var elementSize = context.hasOESElementIndexUint ? 4 : 2; + + var numItems = end - start; + var offsetInBytes = start * elementSize; + gl.drawElements(ol.webgl.TRIANGLES, numItems, elementType, offsetInBytes); +}; diff --git a/src/ol/render/webgl/replaygroup.js b/src/ol/render/webgl/replaygroup.js new file mode 100644 index 0000000000..0d82c470f4 --- /dev/null +++ b/src/ol/render/webgl/replaygroup.js @@ -0,0 +1,280 @@ +goog.provide('ol.render.webgl.ReplayGroup'); + +goog.require('ol'); +goog.require('ol.render.ReplayGroup'); +goog.require('ol.render.webgl'); +goog.require('ol.render.webgl.ImageReplay'); +goog.require('ol.render.webgl.LineStringReplay'); +goog.require('ol.render.webgl.PolygonReplay'); + +/** + * @constructor + * @extends {ol.render.ReplayGroup} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Max extent. + * @param {number=} opt_renderBuffer Render buffer. + * @struct + */ +ol.render.webgl.ReplayGroup = function(tolerance, maxExtent, opt_renderBuffer) { + ol.render.ReplayGroup.call(this); + + /** + * @type {ol.Extent} + * @private + */ + this.maxExtent_ = maxExtent; + + /** + * @type {number} + * @private + */ + this.tolerance_ = tolerance; + + /** + * @type {number|undefined} + * @private + */ + this.renderBuffer_ = opt_renderBuffer; + + /** + * ImageReplay only is supported at this point. + * @type {Object.} + * @private + */ + this.replays_ = {}; + +}; +ol.inherits(ol.render.webgl.ReplayGroup, ol.render.ReplayGroup); + + +/** + * @param {ol.webgl.Context} context WebGL context. + * @return {function()} Delete resources function. + */ +ol.render.webgl.ReplayGroup.prototype.getDeleteResourcesFunction = function(context) { + var functions = []; + var replayKey; + for (replayKey in this.replays_) { + functions.push( + this.replays_[replayKey].getDeleteResourcesFunction(context)); + } + return function() { + var length = functions.length; + var result; + for (var i = 0; i < length; i++) { + result = functions[i].apply(this, arguments); + } + return result; + }; +}; + + +/** + * @param {ol.webgl.Context} context Context. + */ +ol.render.webgl.ReplayGroup.prototype.finish = function(context) { + var replayKey; + for (replayKey in this.replays_) { + this.replays_[replayKey].finish(context); + } +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.ReplayGroup.prototype.getReplay = function(zIndex, replayType) { + var replay = this.replays_[replayType]; + if (replay === undefined) { + var constructor = ol.render.webgl.ReplayGroup.BATCH_CONSTRUCTORS_[replayType]; + replay = new constructor(this.tolerance_, this.maxExtent_); + this.replays_[replayType] = replay; + } + return replay; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.ReplayGroup.prototype.isEmpty = function() { + return ol.obj.isEmpty(this.replays_); +}; + + +/** + * @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 {Object.} skippedFeaturesHash Ids of features + * to skip. + */ +ol.render.webgl.ReplayGroup.prototype.replay = function(context, + center, resolution, rotation, size, pixelRatio, + opacity, skippedFeaturesHash) { + var i, ii, replay; + for (i = 0, ii = ol.render.replay.ORDER.length; i < ii; ++i) { + replay = this.replays_[ol.render.replay.ORDER[i]]; + if (replay !== undefined) { + replay.replay(context, + center, resolution, rotation, size, pixelRatio, + opacity, skippedFeaturesHash, + undefined, false); + } + } +}; + + +/** + * @private + * @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 {Object.} skippedFeaturesHash Ids of features + * to skip. + * @param {function((ol.Feature|ol.render.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.ReplayGroup.prototype.replayHitDetection_ = function(context, + center, resolution, rotation, size, pixelRatio, opacity, + skippedFeaturesHash, 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]]; + if (replay !== undefined) { + result = replay.replay(context, + center, resolution, rotation, size, pixelRatio, opacity, + skippedFeaturesHash, featureCallback, oneByOne, opt_hitExtent); + if (result) { + return result; + } + } + } + return undefined; +}; + + +/** + * @param {ol.Coordinate} coordinate Coordinate. + * @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 {Object.} skippedFeaturesHash Ids of features + * to skip. + * @param {function((ol.Feature|ol.render.Feature)): T|undefined} callback Feature callback. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.webgl.ReplayGroup.prototype.forEachFeatureAtCoordinate = function( + coordinate, context, center, resolution, rotation, size, pixelRatio, + opacity, skippedFeaturesHash, + callback) { + var gl = context.getGL(); + gl.bindFramebuffer( + gl.FRAMEBUFFER, context.getHitDetectionFramebuffer()); + + + /** + * @type {ol.Extent} + */ + var hitExtent; + if (this.renderBuffer_ !== undefined) { + // build an extent around the coordinate, so that only features that + // intersect this extent are checked + hitExtent = ol.extent.buffer( + ol.extent.createOrUpdateFromCoordinate(coordinate), + resolution * this.renderBuffer_); + } + + return this.replayHitDetection_(context, + coordinate, resolution, rotation, ol.render.webgl.ReplayGroup.HIT_DETECTION_SIZE_, + pixelRatio, opacity, skippedFeaturesHash, + /** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @return {?} Callback result. + */ + function(feature) { + var imageData = new Uint8Array(4); + gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, imageData); + + if (imageData[3] > 0) { + var result = callback(feature); + if (result) { + return result; + } + } + }, true, hitExtent); +}; + + +/** + * @param {ol.Coordinate} coordinate Coordinate. + * @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 {Object.} skippedFeaturesHash Ids of features + * to skip. + * @return {boolean} Is there a feature at the given coordinate? + */ +ol.render.webgl.ReplayGroup.prototype.hasFeatureAtCoordinate = function( + coordinate, context, center, resolution, rotation, size, pixelRatio, + opacity, skippedFeaturesHash) { + var gl = context.getGL(); + gl.bindFramebuffer( + gl.FRAMEBUFFER, context.getHitDetectionFramebuffer()); + + var hasFeature = this.replayHitDetection_(context, + coordinate, resolution, rotation, ol.render.webgl.ReplayGroup.HIT_DETECTION_SIZE_, + pixelRatio, opacity, skippedFeaturesHash, + /** + * @param {ol.Feature|ol.render.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 hasFeature !== undefined; +}; + +/** + * @const + * @private + * @type {Array.} + */ +ol.render.webgl.ReplayGroup.HIT_DETECTION_SIZE_ = [1, 1]; + +/** + * @const + * @private + * @type {Object.} + */ +ol.render.webgl.ReplayGroup.BATCH_CONSTRUCTORS_ = { + 'Image': ol.render.webgl.ImageReplay, + 'LineString': ol.render.webgl.LineStringReplay, + 'Polygon': ol.render.webgl.PolygonReplay +}; diff --git a/src/ol/render/webgl/webglpolygondefaultshader.js b/src/ol/render/webgl/webglpolygondefaultshader.js deleted file mode 100644 index 1675d265a1..0000000000 --- a/src/ol/render/webgl/webglpolygondefaultshader.js +++ /dev/null @@ -1,123 +0,0 @@ -// This file is automatically generated, do not edit -goog.provide('ol.render.webgl.polygonreplay.shader.Default'); -goog.provide('ol.render.webgl.polygonreplay.shader.Default.Locations'); -goog.provide('ol.render.webgl.polygonreplay.shader.DefaultFragment'); -goog.provide('ol.render.webgl.polygonreplay.shader.DefaultVertex'); - -goog.require('ol.webgl.shader'); - - -/** - * @constructor - * @extends {ol.webgl.shader.Fragment} - * @struct - */ -ol.render.webgl.polygonreplay.shader.DefaultFragment = function() { - ol.webgl.shader.Fragment.call(this, ol.render.webgl.polygonreplay.shader.DefaultFragment.SOURCE); -}; -ol.inherits(ol.render.webgl.polygonreplay.shader.DefaultFragment, ol.webgl.shader.Fragment); -goog.addSingletonGetter(ol.render.webgl.polygonreplay.shader.DefaultFragment); - - -/** - * @const - * @type {string} - */ -ol.render.webgl.polygonreplay.shader.DefaultFragment.DEBUG_SOURCE = 'precision mediump float;\n\n\n\nuniform vec4 u_color;\nuniform float u_opacity;\n\nvoid main(void) {\n gl_FragColor = u_color;\n float alpha = u_color.a * u_opacity;\n if (alpha == 0.0) {\n discard;\n }\n gl_FragColor.a = alpha;\n}\n'; - - -/** - * @const - * @type {string} - */ -ol.render.webgl.polygonreplay.shader.DefaultFragment.OPTIMIZED_SOURCE = 'precision mediump float;uniform vec4 e;uniform float f;void main(void){gl_FragColor=e;float alpha=e.a*f;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}'; - - -/** - * @const - * @type {string} - */ -ol.render.webgl.polygonreplay.shader.DefaultFragment.SOURCE = goog.DEBUG ? - ol.render.webgl.polygonreplay.shader.DefaultFragment.DEBUG_SOURCE : - ol.render.webgl.polygonreplay.shader.DefaultFragment.OPTIMIZED_SOURCE; - - -/** - * @constructor - * @extends {ol.webgl.shader.Vertex} - * @struct - */ -ol.render.webgl.polygonreplay.shader.DefaultVertex = function() { - ol.webgl.shader.Vertex.call(this, ol.render.webgl.polygonreplay.shader.DefaultVertex.SOURCE); -}; -ol.inherits(ol.render.webgl.polygonreplay.shader.DefaultVertex, ol.webgl.shader.Vertex); -goog.addSingletonGetter(ol.render.webgl.polygonreplay.shader.DefaultVertex); - - -/** - * @const - * @type {string} - */ -ol.render.webgl.polygonreplay.shader.DefaultVertex.DEBUG_SOURCE = '\n\nattribute vec2 a_position;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\n\nvoid main(void) {\n vec4 offsets = u_offsetScaleMatrix * vec4(0., 0., 0., 0.);\n gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.) + offsets;\n}\n\n\n'; - - -/** - * @const - * @type {string} - */ -ol.render.webgl.polygonreplay.shader.DefaultVertex.OPTIMIZED_SOURCE = 'attribute vec2 a;uniform mat4 b;uniform mat4 c;uniform mat4 d;void main(void){vec4 offsets=c*vec4(0.,0.,0.,0.);gl_Position=b*vec4(a,0.,1.)+offsets;}'; - - -/** - * @const - * @type {string} - */ -ol.render.webgl.polygonreplay.shader.DefaultVertex.SOURCE = goog.DEBUG ? - ol.render.webgl.polygonreplay.shader.DefaultVertex.DEBUG_SOURCE : - ol.render.webgl.polygonreplay.shader.DefaultVertex.OPTIMIZED_SOURCE; - - -/** - * @constructor - * @param {WebGLRenderingContext} gl GL. - * @param {WebGLProgram} program Program. - * @struct - */ -ol.render.webgl.polygonreplay.shader.Default.Locations = function(gl, program) { - - /** - * @type {WebGLUniformLocation} - */ - this.u_color = gl.getUniformLocation( - program, goog.DEBUG ? 'u_color' : 'e'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_offsetRotateMatrix = gl.getUniformLocation( - program, goog.DEBUG ? 'u_offsetRotateMatrix' : 'd'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_offsetScaleMatrix = gl.getUniformLocation( - program, goog.DEBUG ? 'u_offsetScaleMatrix' : 'c'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_opacity = gl.getUniformLocation( - program, goog.DEBUG ? 'u_opacity' : 'f'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_projectionMatrix = gl.getUniformLocation( - program, goog.DEBUG ? 'u_projectionMatrix' : 'b'); - - /** - * @type {number} - */ - this.a_position = gl.getAttribLocation( - program, goog.DEBUG ? 'a_position' : 'a'); -}; diff --git a/test/spec/ol/geom/flat/topologyflatgeom.test.js b/test/spec/ol/geom/flat/topologyflatgeom.test.js index dd93d83ac3..f6ad907c69 100644 --- a/test/spec/ol/geom/flat/topologyflatgeom.test.js +++ b/test/spec/ol/geom/flat/topologyflatgeom.test.js @@ -1,5 +1,7 @@ goog.provide('ol.test.geom.flat.topology'); +goog.require('ol.geom.flat.topology'); + describe('ol.geom.flat.topology', function() { describe('ol.geom.flat.topology.lineStringIsClosed', function() { @@ -29,5 +31,3 @@ describe('ol.geom.flat.topology', function() { }); }); - -goog.require('ol.geom.flat.topology'); diff --git a/test/spec/ol/render/webgl/replay.test.js b/test/spec/ol/render/webgl/replay.test.js index 7e157b5ade..decb86cc18 100644 --- a/test/spec/ol/render/webgl/replay.test.js +++ b/test/spec/ol/render/webgl/replay.test.js @@ -1,6 +1,5 @@ goog.provide('ol.test.render.webgl.Replay'); -goog.require('ol.extent'); goog.require('ol.geom.LineString'); goog.require('ol.geom.MultiLineString'); goog.require('ol.geom.MultiPoint'); @@ -8,6 +7,7 @@ goog.require('ol.geom.MultiPolygon'); goog.require('ol.geom.Point'); goog.require('ol.geom.Polygon'); goog.require('ol.render.webgl.ImageReplay'); +goog.require('ol.render.webgl.LineStringReplay'); goog.require('ol.render.webgl.PolygonReplay'); goog.require('ol.style.Fill'); goog.require('ol.style.Image'); @@ -107,25 +107,25 @@ describe('ol.render.webgl.ImageReplay', function() { point = new ol.geom.Point([1000, 2000]); replay.drawPoint(point, null); - expect(replay.vertices_).to.have.length(32); - expect(replay.indices_).to.have.length(6); - expect(replay.indices_[0]).to.be(0); - expect(replay.indices_[1]).to.be(1); - expect(replay.indices_[2]).to.be(2); - expect(replay.indices_[3]).to.be(0); - expect(replay.indices_[4]).to.be(2); - expect(replay.indices_[5]).to.be(3); + expect(replay.vertices).to.have.length(32); + expect(replay.indices).to.have.length(6); + expect(replay.indices[0]).to.be(0); + expect(replay.indices[1]).to.be(1); + expect(replay.indices[2]).to.be(2); + expect(replay.indices[3]).to.be(0); + expect(replay.indices[4]).to.be(2); + expect(replay.indices[5]).to.be(3); point = new ol.geom.Point([2000, 3000]); replay.drawPoint(point, null); - expect(replay.vertices_).to.have.length(64); - expect(replay.indices_).to.have.length(12); - expect(replay.indices_[6]).to.be(4); - expect(replay.indices_[7]).to.be(5); - expect(replay.indices_[8]).to.be(6); - expect(replay.indices_[9]).to.be(4); - expect(replay.indices_[10]).to.be(6); - expect(replay.indices_[11]).to.be(7); + expect(replay.vertices).to.have.length(64); + expect(replay.indices).to.have.length(12); + expect(replay.indices[6]).to.be(4); + expect(replay.indices[7]).to.be(5); + expect(replay.indices[8]).to.be(6); + expect(replay.indices[9]).to.be(4); + expect(replay.indices[10]).to.be(6); + expect(replay.indices[11]).to.be(7); }); }); @@ -141,38 +141,38 @@ describe('ol.render.webgl.ImageReplay', function() { multiPoint = new ol.geom.MultiPoint( [[1000, 2000], [2000, 3000]]); replay.drawMultiPoint(multiPoint, null); - expect(replay.vertices_).to.have.length(64); - expect(replay.indices_).to.have.length(12); - expect(replay.indices_[0]).to.be(0); - expect(replay.indices_[1]).to.be(1); - expect(replay.indices_[2]).to.be(2); - expect(replay.indices_[3]).to.be(0); - expect(replay.indices_[4]).to.be(2); - expect(replay.indices_[5]).to.be(3); - expect(replay.indices_[6]).to.be(4); - expect(replay.indices_[7]).to.be(5); - expect(replay.indices_[8]).to.be(6); - expect(replay.indices_[9]).to.be(4); - expect(replay.indices_[10]).to.be(6); - expect(replay.indices_[11]).to.be(7); + expect(replay.vertices).to.have.length(64); + expect(replay.indices).to.have.length(12); + expect(replay.indices[0]).to.be(0); + expect(replay.indices[1]).to.be(1); + expect(replay.indices[2]).to.be(2); + expect(replay.indices[3]).to.be(0); + expect(replay.indices[4]).to.be(2); + expect(replay.indices[5]).to.be(3); + expect(replay.indices[6]).to.be(4); + expect(replay.indices[7]).to.be(5); + expect(replay.indices[8]).to.be(6); + expect(replay.indices[9]).to.be(4); + expect(replay.indices[10]).to.be(6); + expect(replay.indices[11]).to.be(7); multiPoint = new ol.geom.MultiPoint( [[3000, 4000], [4000, 5000]]); replay.drawMultiPoint(multiPoint, null); - expect(replay.vertices_).to.have.length(128); - expect(replay.indices_).to.have.length(24); - expect(replay.indices_[12]).to.be(8); - expect(replay.indices_[13]).to.be(9); - expect(replay.indices_[14]).to.be(10); - expect(replay.indices_[15]).to.be(8); - expect(replay.indices_[16]).to.be(10); - expect(replay.indices_[17]).to.be(11); - expect(replay.indices_[18]).to.be(12); - expect(replay.indices_[19]).to.be(13); - expect(replay.indices_[20]).to.be(14); - expect(replay.indices_[21]).to.be(12); - expect(replay.indices_[22]).to.be(14); - expect(replay.indices_[23]).to.be(15); + expect(replay.vertices).to.have.length(128); + expect(replay.indices).to.have.length(24); + expect(replay.indices[12]).to.be(8); + expect(replay.indices[13]).to.be(9); + expect(replay.indices[14]).to.be(10); + expect(replay.indices[15]).to.be(8); + expect(replay.indices[16]).to.be(10); + expect(replay.indices[17]).to.be(11); + expect(replay.indices[18]).to.be(12); + expect(replay.indices[19]).to.be(13); + expect(replay.indices[20]).to.be(14); + expect(replay.indices[21]).to.be(12); + expect(replay.indices[22]).to.be(14); + expect(replay.indices[23]).to.be(15); }); }); }); @@ -230,20 +230,20 @@ describe('ol.render.webgl.LineStringReplay', function() { [[1000, 2000], [2000, 3000]]); replay.setFillStrokeStyle(null, strokeStyle1); replay.drawLineString(linestring, null); - expect(replay.vertices_).to.have.length(56); - expect(replay.indices_).to.have.length(18); + expect(replay.vertices).to.have.length(56); + expect(replay.indices).to.have.length(18); expect(replay.state_.changed).to.be(false); - expect(replay.startIndices_).to.have.length(1); - expect(replay.startIndicesFeature_).to.have.length(1); + expect(replay.startIndices).to.have.length(1); + expect(replay.startIndicesFeature).to.have.length(1); linestring = new ol.geom.LineString( [[1000, 3000], [2000, 4000], [3000, 3000]]); replay.drawLineString(linestring, null); - expect(replay.vertices_).to.have.length(140); - expect(replay.indices_).to.have.length(48); + expect(replay.vertices).to.have.length(140); + expect(replay.indices).to.have.length(48); expect(replay.state_.changed).to.be(false); - expect(replay.startIndices_).to.have.length(2); - expect(replay.startIndicesFeature_).to.have.length(2); + expect(replay.startIndices).to.have.length(2); + expect(replay.startIndicesFeature).to.have.length(2); }); }); @@ -257,11 +257,11 @@ describe('ol.render.webgl.LineStringReplay', function() { [[1000, 3000], [2000, 4000], [3000, 3000]]]); replay.setFillStrokeStyle(null, strokeStyle1); replay.drawMultiLineString(multilinestring, null); - expect(replay.vertices_).to.have.length(140); - expect(replay.indices_).to.have.length(48); + expect(replay.vertices).to.have.length(140); + expect(replay.indices).to.have.length(48); expect(replay.state_.changed).to.be(false); - expect(replay.startIndices_).to.have.length(1); - expect(replay.startIndicesFeature_).to.have.length(1); + expect(replay.startIndices).to.have.length(1); + expect(replay.startIndicesFeature).to.have.length(1); }); }); @@ -282,7 +282,7 @@ describe('ol.render.webgl.LineStringReplay', function() { replay.setFillStrokeStyle(null, stroke); replay.drawCoordinates_(flatCoordinates, 0, flatCoordinates.length, 2); - expect(replay.indices_).to.eql( + expect(replay.indices).to.eql( [2, 0, 1, 4, 2, 1, 2, 4, 3, 5, 3, 4, 4, 6, 5]); @@ -302,7 +302,7 @@ describe('ol.render.webgl.LineStringReplay', function() { replay.setFillStrokeStyle(null, stroke); replay.drawCoordinates_(flatCoordinates, 0, flatCoordinates.length, 2); - expect(replay.indices_).to.eql( + expect(replay.indices).to.eql( [2, 0, 1, 4, 2, 1, 2, 4, 3, 3, 5, 2, 6, 3, 4, 4, 7, 6]); @@ -321,7 +321,7 @@ describe('ol.render.webgl.LineStringReplay', function() { replay.setFillStrokeStyle(null, stroke); replay.drawCoordinates_(flatCoordinates, 0, flatCoordinates.length, 2); - expect(replay.indices_).to.eql( + expect(replay.indices).to.eql( [2, 0, 1, 1, 3, 2, 4, 2, 3, 6, 4, 3, 4, 6, 5, 5, 7, 4, @@ -344,7 +344,7 @@ describe('ol.render.webgl.LineStringReplay', function() { replay.setFillStrokeStyle(null, stroke); replay.drawCoordinates_(flatCoordinates, 0, flatCoordinates.length, 2); - expect(replay.indices_).to.eql( + expect(replay.indices).to.eql( [2, 0, 1, 4, 2, 0, 2, 4, 3, 5, 3, 4, 4, 6, 5]); @@ -365,17 +365,17 @@ describe('ol.render.webgl.LineStringReplay', function() { replay.setFillStrokeStyle(null, stroke); replay.drawCoordinates_(flatCoordinates, 0, flatCoordinates.length, 2); - expect(replay.indices_).to.eql( + expect(replay.indices).to.eql( [0, 2, 1, 3, 1, 2, 5, 3, 2, 3, 5, 4, 6, 4, 5, 8, 6, 5, 6, 8, 7, 9, 7, 8, 10, 9, 8]); - expect(replay.vertices_.slice(0, 7)).to.eql( - replay.vertices_.slice(-14, -7)); - expect(replay.vertices_.slice(14, 21)).to.eql( - replay.vertices_.slice(-7)); + expect(replay.vertices.slice(0, 7)).to.eql( + replay.vertices.slice(-14, -7)); + expect(replay.vertices.slice(14, 21)).to.eql( + replay.vertices.slice(-7)); }); }); });