diff --git a/src/ol/render/webgl.js b/src/ol/render/webgl.js index 772aebed65..c484743cf9 100644 --- a/src/ol/render/webgl.js +++ b/src/ol/render/webgl.js @@ -1,68 +1,72 @@ goog.provide('ol.render.webgl'); -/** - * @const - * @type {ol.Color} - */ -ol.render.webgl.defaultFillStyle = [0.0, 0.0, 0.0, 1.0]; +if (ol.ENABLE_WEBGL) { -/** - * @const - * @type {string} - */ -ol.render.webgl.defaultLineCap = 'round'; + /** + * @const + * @type {ol.Color} + */ + ol.render.webgl.defaultFillStyle = [0.0, 0.0, 0.0, 1.0]; + + /** + * @const + * @type {string} + */ + ol.render.webgl.defaultLineCap = 'round'; -/** - * @const - * @type {Array.} - */ -ol.render.webgl.defaultLineDash = []; + /** + * @const + * @type {Array.} + */ + ol.render.webgl.defaultLineDash = []; -/** - * @const - * @type {string} - */ -ol.render.webgl.defaultLineJoin = 'round'; + /** + * @const + * @type {string} + */ + ol.render.webgl.defaultLineJoin = 'round'; -/** - * @const - * @type {number} - */ -ol.render.webgl.defaultMiterLimit = 10; + /** + * @const + * @type {number} + */ + ol.render.webgl.defaultMiterLimit = 10; -/** - * @const - * @type {ol.Color} - */ -ol.render.webgl.defaultStrokeStyle = [0.0, 0.0, 0.0, 1.0]; + /** + * @const + * @type {ol.Color} + */ + ol.render.webgl.defaultStrokeStyle = [0.0, 0.0, 0.0, 1.0]; -/** - * @const - * @type {number} - */ -ol.render.webgl.defaultLineWidth = 1; + /** + * @const + * @type {number} + */ + ol.render.webgl.defaultLineWidth = 1; -/** - * Calculates the orientation of a triangle based on the determinant method. - * @param {number} x1 First X coordinate. - * @param {number} y1 First Y coordinate. - * @param {number} x2 Second X coordinate. - * @param {number} y2 Second Y coordinate. - * @param {number} x3 Third X coordinate. - * @param {number} y3 Third Y coordinate. - * @return {boolean|undefined} Triangle is clockwise. - */ -ol.render.webgl.triangleIsCounterClockwise = function(x1, y1, x2, y2, x3, y3) { - var area = (x2 - x1) * (y3 - y1) - (x3 - x1) * (y2 - y1); - return (area <= ol.render.webgl.EPSILON && area >= -ol.render.webgl.EPSILON) ? - undefined : area > 0; -}; + /** + * Calculates the orientation of a triangle based on the determinant method. + * @param {number} x1 First X coordinate. + * @param {number} y1 First Y coordinate. + * @param {number} x2 Second X coordinate. + * @param {number} y2 Second Y coordinate. + * @param {number} x3 Third X coordinate. + * @param {number} y3 Third Y coordinate. + * @return {boolean|undefined} Triangle is clockwise. + */ + ol.render.webgl.triangleIsCounterClockwise = function(x1, y1, x2, y2, x3, y3) { + var area = (x2 - x1) * (y3 - y1) - (x3 - x1) * (y2 - y1); + return (area <= ol.render.webgl.EPSILON && area >= -ol.render.webgl.EPSILON) ? + undefined : area > 0; + }; -/** - * @const - * @type {number} - */ -ol.render.webgl.EPSILON = Number.EPSILON || 2.220446049250313e-16; + /** + * @const + * @type {number} + */ + ol.render.webgl.EPSILON = Number.EPSILON || 2.220446049250313e-16; + +} diff --git a/src/ol/render/webgl/circlereplay.js b/src/ol/render/webgl/circlereplay.js index 69d88b85b5..4967bbd0e8 100644 --- a/src/ol/render/webgl/circlereplay.js +++ b/src/ol/render/webgl/circlereplay.js @@ -13,403 +13,407 @@ 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.CircleReplay = function(tolerance, maxExtent) { - ol.render.webgl.Replay.call(this, tolerance, maxExtent); +if (ol.ENABLE_WEBGL) { + + /** + * @constructor + * @extends {ol.render.webgl.Replay} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Max extent. + * @struct + */ + ol.render.webgl.CircleReplay = function(tolerance, maxExtent) { + ol.render.webgl.Replay.call(this, tolerance, maxExtent); + + /** + * @private + * @type {ol.render.webgl.circlereplay.defaultshader.Locations} + */ + this.defaultLocations_ = null; + + /** + * @private + * @type {Array.|number>>} + */ + this.styles_ = []; + + /** + * @private + * @type {Array.} + */ + this.styleIndices_ = []; + + /** + * @private + * @type {number} + */ + this.radius_ = 0; + + /** + * @private + * @type {{fillColor: (Array.|null), + * strokeColor: (Array.|null), + * lineDash: Array., + * lineWidth: (number|undefined), + * changed: boolean}|null} + */ + this.state_ = { + fillColor: null, + strokeColor: null, + lineDash: null, + lineWidth: undefined, + changed: false + }; + + }; + ol.inherits(ol.render.webgl.CircleReplay, ol.render.webgl.Replay); + /** * @private - * @type {ol.render.webgl.circlereplay.defaultshader.Locations} + * @param {Array.} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. */ - this.defaultLocations_ = null; + ol.render.webgl.CircleReplay.prototype.drawCoordinates_ = function( + flatCoordinates, offset, end, stride) { + var numVertices = this.vertices.length; + var numIndices = this.indices.length; + var n = numVertices / 4; + var i, ii; + for (i = offset, ii = end; i < ii; i += stride) { + this.vertices[numVertices++] = flatCoordinates[i]; + this.vertices[numVertices++] = flatCoordinates[i + 1]; + this.vertices[numVertices++] = 0; + this.vertices[numVertices++] = this.radius_; - /** - * @private - * @type {Array.|number>>} - */ - this.styles_ = []; + this.vertices[numVertices++] = flatCoordinates[i]; + this.vertices[numVertices++] = flatCoordinates[i + 1]; + this.vertices[numVertices++] = 1; + this.vertices[numVertices++] = this.radius_; - /** - * @private - * @type {Array.} - */ - this.styleIndices_ = []; + this.vertices[numVertices++] = flatCoordinates[i]; + this.vertices[numVertices++] = flatCoordinates[i + 1]; + this.vertices[numVertices++] = 2; + this.vertices[numVertices++] = this.radius_; - /** - * @private - * @type {number} - */ - this.radius_ = 0; + this.vertices[numVertices++] = flatCoordinates[i]; + this.vertices[numVertices++] = flatCoordinates[i + 1]; + this.vertices[numVertices++] = 3; + this.vertices[numVertices++] = this.radius_; - /** - * @private - * @type {{fillColor: (Array.|null), - * strokeColor: (Array.|null), - * lineDash: Array., - * lineWidth: (number|undefined), - * changed: boolean}|null} - */ - this.state_ = { - fillColor: null, - strokeColor: null, - lineDash: null, - lineWidth: undefined, - changed: false + this.indices[numIndices++] = n; + this.indices[numIndices++] = n + 1; + this.indices[numIndices++] = n + 2; + + this.indices[numIndices++] = n + 2; + this.indices[numIndices++] = n + 3; + this.indices[numIndices++] = n; + + n += 4; + } }; -}; -ol.inherits(ol.render.webgl.CircleReplay, ol.render.webgl.Replay); - -/** - * @private - * @param {Array.} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - */ -ol.render.webgl.CircleReplay.prototype.drawCoordinates_ = function( - flatCoordinates, offset, end, stride) { - var numVertices = this.vertices.length; - var numIndices = this.indices.length; - var n = numVertices / 4; - var i, ii; - for (i = offset, ii = end; i < ii; i += stride) { - this.vertices[numVertices++] = flatCoordinates[i]; - this.vertices[numVertices++] = flatCoordinates[i + 1]; - this.vertices[numVertices++] = 0; - this.vertices[numVertices++] = this.radius_; - - this.vertices[numVertices++] = flatCoordinates[i]; - this.vertices[numVertices++] = flatCoordinates[i + 1]; - this.vertices[numVertices++] = 1; - this.vertices[numVertices++] = this.radius_; - - this.vertices[numVertices++] = flatCoordinates[i]; - this.vertices[numVertices++] = flatCoordinates[i + 1]; - this.vertices[numVertices++] = 2; - this.vertices[numVertices++] = this.radius_; - - this.vertices[numVertices++] = flatCoordinates[i]; - this.vertices[numVertices++] = flatCoordinates[i + 1]; - this.vertices[numVertices++] = 3; - this.vertices[numVertices++] = this.radius_; - - this.indices[numIndices++] = n; - this.indices[numIndices++] = n + 1; - this.indices[numIndices++] = n + 2; - - this.indices[numIndices++] = n + 2; - this.indices[numIndices++] = n + 3; - this.indices[numIndices++] = n; - - n += 4; - } -}; - - -/** - * @inheritDoc - */ -ol.render.webgl.CircleReplay.prototype.drawCircle = function(circleGeometry, feature) { - var radius = circleGeometry.getRadius(); - var stride = circleGeometry.getStride(); - if (radius) { - 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.radius_ = radius; - var flatCoordinates = circleGeometry.getFlatCoordinates(); - flatCoordinates = ol.geom.flat.transform.translate(flatCoordinates, 0, 2, - stride, -this.origin[0], -this.origin[1]); - this.drawCoordinates_(flatCoordinates, 0, 2, stride); - } else { - if (this.state_.changed) { - this.styles_.pop(); - if (this.styles_.length) { - var lastState = this.styles_[this.styles_.length - 1]; - this.state_.fillColor = /** @type {Array.} */ (lastState[0]); - this.state_.strokeColor = /** @type {Array.} */ (lastState[1]); - this.state_.lineWidth = /** @type {number} */ (lastState[2]); + /** + * @inheritDoc + */ + ol.render.webgl.CircleReplay.prototype.drawCircle = function(circleGeometry, feature) { + var radius = circleGeometry.getRadius(); + var stride = circleGeometry.getStride(); + if (radius) { + 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.radius_ = radius; + var flatCoordinates = circleGeometry.getFlatCoordinates(); + flatCoordinates = ol.geom.flat.transform.translate(flatCoordinates, 0, 2, + stride, -this.origin[0], -this.origin[1]); + this.drawCoordinates_(flatCoordinates, 0, 2, stride); + } else { + if (this.state_.changed) { + this.styles_.pop(); + if (this.styles_.length) { + var lastState = this.styles_[this.styles_.length - 1]; + this.state_.fillColor = /** @type {Array.} */ (lastState[0]); + this.state_.strokeColor = /** @type {Array.} */ (lastState[1]); + this.state_.lineWidth = /** @type {number} */ (lastState[2]); + this.state_.changed = false; + } + } } - } -}; - - -/** - * @inheritDoc - **/ -ol.render.webgl.CircleReplay.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.CircleReplay.prototype.getDeleteResourcesFunction = function(context) { - // We only delete our stuff here. The shaders and the program may - // be used by other CircleReplay instances (for other layers). And - // they will be deleted when disposing of the ol.webgl.Context - // object. - var verticesBuffer = this.verticesBuffer; - var indicesBuffer = this.indicesBuffer; - return function() { - context.deleteBuffer(verticesBuffer); - context.deleteBuffer(indicesBuffer); }; -}; -/** - * @inheritDoc - */ -ol.render.webgl.CircleReplay.prototype.setUpProgram = function(gl, context, size, pixelRatio) { - // get the program - var fragmentShader, vertexShader; - fragmentShader = ol.render.webgl.circlereplay.defaultshader.fragment; - vertexShader = ol.render.webgl.circlereplay.defaultshader.vertex; - var program = context.getProgram(fragmentShader, vertexShader); + /** + * @inheritDoc + **/ + ol.render.webgl.CircleReplay.prototype.finish = function(context) { + // create, bind, and populate the vertices buffer + this.verticesBuffer = new ol.webgl.Buffer(this.vertices); - // get the locations - var locations; - if (!this.defaultLocations_) { - locations = - new ol.render.webgl.circlereplay.defaultshader.Locations(gl, program); - this.defaultLocations_ = locations; - } else { - locations = this.defaultLocations_; - } + // create, bind, and populate the indices buffer + this.indicesBuffer = new ol.webgl.Buffer(this.indices); - context.useProgram(program); + this.startIndices.push(this.indices.length); - // enable the vertex attrib arrays - gl.enableVertexAttribArray(locations.a_position); - gl.vertexAttribPointer(locations.a_position, 2, ol.webgl.FLOAT, - false, 16, 0); + //Clean up, if there is nothing to draw + if (this.styleIndices_.length === 0 && this.styles_.length > 0) { + this.styles_ = []; + } - gl.enableVertexAttribArray(locations.a_instruction); - gl.vertexAttribPointer(locations.a_instruction, 1, ol.webgl.FLOAT, - false, 16, 8); - - gl.enableVertexAttribArray(locations.a_radius); - gl.vertexAttribPointer(locations.a_radius, 1, ol.webgl.FLOAT, - false, 16, 12); - - // Enable renderer specific uniforms. - gl.uniform2fv(locations.u_size, size); - gl.uniform1f(locations.u_pixelRatio, pixelRatio); - - return locations; -}; + this.vertices = null; + this.indices = null; + }; -/** - * @inheritDoc - */ -ol.render.webgl.CircleReplay.prototype.shutDownProgram = function(gl, locations) { - gl.disableVertexAttribArray(locations.a_position); - gl.disableVertexAttribArray(locations.a_instruction); - gl.disableVertexAttribArray(locations.a_radius); -}; + /** + * @inheritDoc + */ + ol.render.webgl.CircleReplay.prototype.getDeleteResourcesFunction = function(context) { + // We only delete our stuff here. The shaders and the program may + // be used by other CircleReplay instances (for other layers). And + // they will be deleted when disposing of the ol.webgl.Context + // object. + var verticesBuffer = this.verticesBuffer; + var indicesBuffer = this.indicesBuffer; + return function() { + context.deleteBuffer(verticesBuffer); + context.deleteBuffer(indicesBuffer); + }; + }; -/** - * @inheritDoc - */ -ol.render.webgl.CircleReplay.prototype.drawReplay = function(gl, context, skippedFeaturesHash, hitDetection) { - if (!ol.obj.isEmpty(skippedFeaturesHash)) { - this.drawReplaySkipping_(gl, context, skippedFeaturesHash); - } else { - //Draw by style groups to minimize drawElements() calls. - var i, start, end, nextStyle; - end = this.startIndices[this.startIndices.length - 1]; + /** + * @inheritDoc + */ + ol.render.webgl.CircleReplay.prototype.setUpProgram = function(gl, context, size, pixelRatio) { + // get the program + var fragmentShader, vertexShader; + fragmentShader = ol.render.webgl.circlereplay.defaultshader.fragment; + vertexShader = ol.render.webgl.circlereplay.defaultshader.vertex; + var program = context.getProgram(fragmentShader, vertexShader); + + // get the locations + var locations; + if (!this.defaultLocations_) { + locations = + new ol.render.webgl.circlereplay.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, 16, 0); + + gl.enableVertexAttribArray(locations.a_instruction); + gl.vertexAttribPointer(locations.a_instruction, 1, ol.webgl.FLOAT, + false, 16, 8); + + gl.enableVertexAttribArray(locations.a_radius); + gl.vertexAttribPointer(locations.a_radius, 1, ol.webgl.FLOAT, + false, 16, 12); + + // Enable renderer specific uniforms. + gl.uniform2fv(locations.u_size, size); + gl.uniform1f(locations.u_pixelRatio, pixelRatio); + + return locations; + }; + + + /** + * @inheritDoc + */ + ol.render.webgl.CircleReplay.prototype.shutDownProgram = function(gl, locations) { + gl.disableVertexAttribArray(locations.a_position); + gl.disableVertexAttribArray(locations.a_instruction); + gl.disableVertexAttribArray(locations.a_radius); + }; + + + /** + * @inheritDoc + */ + ol.render.webgl.CircleReplay.prototype.drawReplay = function(gl, context, skippedFeaturesHash, hitDetection) { + if (!ol.obj.isEmpty(skippedFeaturesHash)) { + this.drawReplaySkipping_(gl, context, skippedFeaturesHash); + } else { + //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, /** @type {Array.} */ (nextStyle[0])); + this.setStrokeStyle_(gl, /** @type {Array.} */ (nextStyle[1]), + /** @type {number} */ (nextStyle[2])); + this.drawElements(gl, context, start, end); + end = start; + } + } + }; + + + /** + * @inheritDoc + */ + ol.render.webgl.CircleReplay.prototype.drawHitDetectionReplayOneByOne = function(gl, context, skippedFeaturesHash, + featureCallback, opt_hitExtent) { + 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) { - start = this.styleIndices_[i]; nextStyle = this.styles_[i]; this.setFillStyle_(gl, /** @type {Array.} */ (nextStyle[0])); this.setStrokeStyle_(gl, /** @type {Array.} */ (nextStyle[1]), /** @type {number} */ (nextStyle[2])); - this.drawElements(gl, context, start, end); - end = start; - } - } -}; + groupStart = this.styleIndices_[i]; + while (featureIndex >= 0 && + this.startIndices[featureIndex] >= groupStart) { + start = this.startIndices[featureIndex]; + feature = this.startIndicesFeature[featureIndex]; + featureUid = ol.getUid(feature).toString(); -/** - * @inheritDoc - */ -ol.render.webgl.CircleReplay.prototype.drawHitDetectionReplayOneByOne = function(gl, context, skippedFeaturesHash, - featureCallback, opt_hitExtent) { - 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, /** @type {Array.} */ (nextStyle[0])); - this.setStrokeStyle_(gl, /** @type {Array.} */ (nextStyle[1]), - /** @type {number} */ (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 {ol.webgl.Context} context Context. - * @param {Object} skippedFeaturesHash Ids of features to skip. - */ -ol.render.webgl.CircleReplay.prototype.drawReplaySkipping_ = function(gl, context, skippedFeaturesHash) { - 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, /** @type {Array.} */ (nextStyle[0])); - this.setStrokeStyle_(gl, /** @type {Array.} */ (nextStyle[1]), - /** @type {number} */ (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) { + 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; + } + } - end = featureStart; + featureIndex--; + end = start; } - featureIndex--; - start = featureStart; } - if (start !== end) { - this.drawElements(gl, context, start, end); + return undefined; + }; + + + /** + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object} skippedFeaturesHash Ids of features to skip. + */ + ol.render.webgl.CircleReplay.prototype.drawReplaySkipping_ = function(gl, context, skippedFeaturesHash) { + 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, /** @type {Array.} */ (nextStyle[0])); + this.setStrokeStyle_(gl, /** @type {Array.} */ (nextStyle[1]), + /** @type {number} */ (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); + } + start = end = groupStart; } - start = end = groupStart; - } -}; + }; -/** - * @private - * @param {WebGLRenderingContext} gl gl. - * @param {Array.} color Color. - */ -ol.render.webgl.CircleReplay.prototype.setFillStyle_ = function(gl, color) { - gl.uniform4fv(this.defaultLocations_.u_fillColor, color); -}; + /** + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {Array.} color Color. + */ + ol.render.webgl.CircleReplay.prototype.setFillStyle_ = function(gl, color) { + gl.uniform4fv(this.defaultLocations_.u_fillColor, color); + }; -/** - * @private - * @param {WebGLRenderingContext} gl gl. - * @param {Array.} color Color. - * @param {number} lineWidth Line width. - */ -ol.render.webgl.CircleReplay.prototype.setStrokeStyle_ = function(gl, color, lineWidth) { - gl.uniform4fv(this.defaultLocations_.u_strokeColor, color); - gl.uniform1f(this.defaultLocations_.u_lineWidth, lineWidth); -}; + /** + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {Array.} color Color. + * @param {number} lineWidth Line width. + */ + ol.render.webgl.CircleReplay.prototype.setStrokeStyle_ = function(gl, color, lineWidth) { + gl.uniform4fv(this.defaultLocations_.u_strokeColor, color); + gl.uniform1f(this.defaultLocations_.u_lineWidth, lineWidth); + }; -/** - * @inheritDoc - */ -ol.render.webgl.CircleReplay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) { - var strokeStyleColor, strokeStyleWidth; - if (strokeStyle) { - var strokeStyleLineDash = strokeStyle.getLineDash(); - this.state_.lineDash = strokeStyleLineDash ? - strokeStyleLineDash : ol.render.webgl.defaultLineDash; - 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; + /** + * @inheritDoc + */ + ol.render.webgl.CircleReplay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) { + var strokeStyleColor, strokeStyleWidth; + if (strokeStyle) { + var strokeStyleLineDash = strokeStyle.getLineDash(); + this.state_.lineDash = strokeStyleLineDash ? + strokeStyleLineDash : ol.render.webgl.defaultLineDash; + 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; + } + strokeStyleWidth = strokeStyle.getWidth(); + strokeStyleWidth = strokeStyleWidth !== undefined ? + strokeStyleWidth : ol.render.webgl.defaultLineWidth; } else { - strokeStyleColor = ol.render.webgl.defaultStrokeStyle; + strokeStyleColor = [0, 0, 0, 0]; + strokeStyleWidth = 0; } - strokeStyleWidth = strokeStyle.getWidth(); - strokeStyleWidth = strokeStyleWidth !== undefined ? - strokeStyleWidth : ol.render.webgl.defaultLineWidth; - } else { - strokeStyleColor = [0, 0, 0, 0]; - strokeStyleWidth = 0; - } - 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_.strokeColor || !ol.array.equals(this.state_.strokeColor, strokeStyleColor) || - !this.state_.fillColor || !ol.array.equals(this.state_.fillColor, fillStyleColor) || - this.state_.lineWidth !== strokeStyleWidth) { - this.state_.changed = true; - this.state_.fillColor = fillStyleColor; - this.state_.strokeColor = strokeStyleColor; - this.state_.lineWidth = strokeStyleWidth; - this.styles_.push([fillStyleColor, strokeStyleColor, strokeStyleWidth]); - } -}; + 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_.strokeColor || !ol.array.equals(this.state_.strokeColor, strokeStyleColor) || + !this.state_.fillColor || !ol.array.equals(this.state_.fillColor, fillStyleColor) || + this.state_.lineWidth !== strokeStyleWidth) { + this.state_.changed = true; + this.state_.fillColor = fillStyleColor; + this.state_.strokeColor = strokeStyleColor; + this.state_.lineWidth = strokeStyleWidth; + this.styles_.push([fillStyleColor, strokeStyleColor, strokeStyleWidth]); + } + }; + +} diff --git a/src/ol/render/webgl/imagereplay.js b/src/ol/render/webgl/imagereplay.js index 6dfbd1fb3c..c8b449575c 100644 --- a/src/ol/render/webgl/imagereplay.js +++ b/src/ol/render/webgl/imagereplay.js @@ -11,598 +11,602 @@ goog.require('ol.webgl.Buffer'); goog.require('ol.webgl.Context'); -/** - * @constructor - * @extends {ol.render.webgl.Replay} - * @param {number} tolerance Tolerance. - * @param {ol.Extent} maxExtent Max extent. - * @struct - */ -ol.render.webgl.ImageReplay = function(tolerance, maxExtent) { - ol.render.webgl.Replay.call(this, tolerance, maxExtent); +if (ol.ENABLE_WEBGL) { /** - * @type {number|undefined} - * @private + * @constructor + * @extends {ol.render.webgl.Replay} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Max extent. + * @struct */ - this.anchorX_ = undefined; + ol.render.webgl.ImageReplay = function(tolerance, maxExtent) { + ol.render.webgl.Replay.call(this, tolerance, maxExtent); - /** - * @type {number|undefined} - * @private - */ - this.anchorY_ = undefined; + /** + * @type {number|undefined} + * @private + */ + this.anchorX_ = undefined; - /** - * @type {Array.} - * @private - */ - this.groupIndices_ = []; + /** + * @type {number|undefined} + * @private + */ + this.anchorY_ = undefined; - /** - * @type {Array.} - * @private - */ - this.hitDetectionGroupIndices_ = []; + /** + * @type {Array.} + * @private + */ + this.groupIndices_ = []; - /** - * @type {number|undefined} - * @private - */ - this.height_ = undefined; + /** + * @type {Array.} + * @private + */ + this.hitDetectionGroupIndices_ = []; - /** - * @type {Array.} - * @private - */ - this.images_ = []; + /** + * @type {number|undefined} + * @private + */ + this.height_ = undefined; - /** - * @type {Array.} - * @private - */ - this.hitDetectionImages_ = []; + /** + * @type {Array.} + * @private + */ + this.images_ = []; - /** - * @type {number|undefined} - * @private - */ - this.imageHeight_ = undefined; + /** + * @type {Array.} + * @private + */ + this.hitDetectionImages_ = []; - /** - * @type {number|undefined} - * @private - */ - this.imageWidth_ = undefined; + /** + * @type {number|undefined} + * @private + */ + this.imageHeight_ = undefined; - /** - * @private - * @type {ol.render.webgl.imagereplay.defaultshader.Locations} - */ - this.defaultLocations_ = null; + /** + * @type {number|undefined} + * @private + */ + this.imageWidth_ = undefined; - /** - * @private - * @type {number|undefined} - */ - this.opacity_ = undefined; + /** + * @private + * @type {ol.render.webgl.imagereplay.defaultshader.Locations} + */ + this.defaultLocations_ = null; - /** - * @type {number|undefined} - * @private - */ - this.originX_ = undefined; + /** + * @private + * @type {number|undefined} + */ + this.opacity_ = undefined; - /** - * @type {number|undefined} - * @private - */ - this.originY_ = undefined; + /** + * @type {number|undefined} + * @private + */ + this.originX_ = undefined; - /** - * @private - * @type {boolean|undefined} - */ - this.rotateWithView_ = undefined; + /** + * @type {number|undefined} + * @private + */ + this.originY_ = undefined; - /** - * @private - * @type {number|undefined} - */ - this.rotation_ = undefined; + /** + * @private + * @type {boolean|undefined} + */ + this.rotateWithView_ = undefined; - /** - * @private - * @type {number|undefined} - */ - this.scale_ = undefined; + /** + * @private + * @type {number|undefined} + */ + this.rotation_ = undefined; - /** - * @type {Array.} - * @private - */ - this.textures_ = []; + /** + * @private + * @type {number|undefined} + */ + this.scale_ = undefined; - /** - * @type {Array.} - * @private - */ - this.hitDetectionTextures_ = []; + /** + * @type {Array.} + * @private + */ + this.textures_ = []; - /** - * @type {number|undefined} - * @private - */ - this.width_ = undefined; -}; -ol.inherits(ol.render.webgl.ImageReplay, ol.render.webgl.Replay); + /** + * @type {Array.} + * @private + */ + this.hitDetectionTextures_ = []; - -/** - * @inheritDoc - */ -ol.render.webgl.ImageReplay.prototype.getDeleteResourcesFunction = function(context) { - var verticesBuffer = this.verticesBuffer; - var indicesBuffer = this.indicesBuffer; - var textures = this.textures_; - var hitDetectionTextures = this.hitDetectionTextures_; - var gl = context.getGL(); - return function() { - if (!gl.isContextLost()) { - var i, ii; - for (i = 0, ii = textures.length; i < ii; ++i) { - gl.deleteTexture(textures[i]); - } - for (i = 0, ii = hitDetectionTextures.length; i < ii; ++i) { - gl.deleteTexture(hitDetectionTextures[i]); - } - } - context.deleteBuffer(verticesBuffer); - context.deleteBuffer(indicesBuffer); + /** + * @type {number|undefined} + * @private + */ + this.width_ = undefined; }; -}; + ol.inherits(ol.render.webgl.ImageReplay, ol.render.webgl.Replay); -/** - * @param {Array.} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @return {number} My end. - * @private - */ -ol.render.webgl.ImageReplay.prototype.drawCoordinates_ = function(flatCoordinates, offset, end, stride) { - var anchorX = /** @type {number} */ (this.anchorX_); - var anchorY = /** @type {number} */ (this.anchorY_); - var height = /** @type {number} */ (this.height_); - var imageHeight = /** @type {number} */ (this.imageHeight_); - var imageWidth = /** @type {number} */ (this.imageWidth_); - var opacity = /** @type {number} */ (this.opacity_); - var originX = /** @type {number} */ (this.originX_); - var originY = /** @type {number} */ (this.originY_); - var rotateWithView = this.rotateWithView_ ? 1.0 : 0.0; - // this.rotation_ is anti-clockwise, but rotation is clockwise - var rotation = /** @type {number} */ (-this.rotation_); - var scale = /** @type {number} */ (this.scale_); - 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 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]; - - // 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 - // WebGL supported Geometry Shaders (which can emit new vertices), but that - // is not currently the case. - // - // And each vertex includes 8 values: the x and y coordinates, the x and - // y offsets used to calculate the position of the corner, the u and - // v texture coordinates for the corner, the opacity, and whether the - // the image should be rotated with the view (rotateWithView). - - n = numVertices / 8; - - // 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; - - // 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; - - // 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; - - // 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.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; -}; - - -/** - * @inheritDoc - */ -ol.render.webgl.ImageReplay.prototype.drawMultiPoint = function(multiPointGeometry, feature) { - this.startIndices.push(this.indices.length); - this.startIndicesFeature.push(feature); - var flatCoordinates = multiPointGeometry.getFlatCoordinates(); - var stride = multiPointGeometry.getStride(); - this.drawCoordinates_( - flatCoordinates, 0, flatCoordinates.length, stride); -}; - - -/** - * @inheritDoc - */ -ol.render.webgl.ImageReplay.prototype.drawPoint = function(pointGeometry, feature) { - this.startIndices.push(this.indices.length); - this.startIndicesFeature.push(feature); - var flatCoordinates = pointGeometry.getFlatCoordinates(); - var stride = pointGeometry.getStride(); - this.drawCoordinates_( - flatCoordinates, 0, flatCoordinates.length, stride); -}; - - -/** - * @inheritDoc - */ -ol.render.webgl.ImageReplay.prototype.finish = function(context) { - var gl = context.getGL(); - - this.groupIndices_.push(this.indices.length); - this.hitDetectionGroupIndices_.push(this.indices.length); - - // create, bind, and populate the vertices buffer - this.verticesBuffer = new ol.webgl.Buffer(this.vertices); - - var indices = this.indices; - - // create, bind, and populate the indices buffer - this.indicesBuffer = new ol.webgl.Buffer(indices); - - // create textures - /** @type {Object.} */ - var texturePerImage = {}; - - this.createTextures_(this.textures_, this.images_, texturePerImage, gl); - - this.createTextures_(this.hitDetectionTextures_, this.hitDetectionImages_, - texturePerImage, gl); - - this.anchorX_ = undefined; - this.anchorY_ = undefined; - this.height_ = undefined; - this.images_ = null; - this.hitDetectionImages_ = null; - this.imageHeight_ = undefined; - this.imageWidth_ = undefined; - 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.width_ = undefined; -}; - - -/** - * @private - * @param {Array.} textures Textures. - * @param {Array.} images - * Images. - * @param {Object.} texturePerImage Texture cache. - * @param {WebGLRenderingContext} gl Gl. - */ -ol.render.webgl.ImageReplay.prototype.createTextures_ = function(textures, images, texturePerImage, gl) { - var texture, image, uid, i; - var ii = images.length; - for (i = 0; i < ii; ++i) { - image = images[i]; - - uid = ol.getUid(image).toString(); - if (uid in texturePerImage) { - texture = texturePerImage[uid]; - } else { - texture = ol.webgl.Context.createTexture( - gl, image, ol.webgl.CLAMP_TO_EDGE, ol.webgl.CLAMP_TO_EDGE); - texturePerImage[uid] = texture; - } - textures[i] = texture; - } -}; - - -/** - * @inheritDoc - */ -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; - var program = context.getProgram(fragmentShader, vertexShader); - - // get the locations - var locations; - if (!this.defaultLocations_) { - locations = - new ol.render.webgl.imagereplay.defaultshader.Locations(gl, program); - this.defaultLocations_ = locations; - } else { - locations = this.defaultLocations_; - } - - // use the program (FIXME: use the return value) - context.useProgram(program); - - // enable the vertex attrib arrays - gl.enableVertexAttribArray(locations.a_position); - gl.vertexAttribPointer(locations.a_position, 2, ol.webgl.FLOAT, - false, 32, 0); - - gl.enableVertexAttribArray(locations.a_offsets); - gl.vertexAttribPointer(locations.a_offsets, 2, ol.webgl.FLOAT, - false, 32, 8); - - gl.enableVertexAttribArray(locations.a_texCoord); - gl.vertexAttribPointer(locations.a_texCoord, 2, ol.webgl.FLOAT, - false, 32, 16); - - gl.enableVertexAttribArray(locations.a_opacity); - gl.vertexAttribPointer(locations.a_opacity, 1, ol.webgl.FLOAT, - false, 32, 24); - - gl.enableVertexAttribArray(locations.a_rotateWithView); - gl.vertexAttribPointer(locations.a_rotateWithView, 1, ol.webgl.FLOAT, - false, 32, 28); - - return locations; -}; - - -/** - * @inheritDoc - */ -ol.render.webgl.ImageReplay.prototype.shutDownProgram = function(gl, locations) { - gl.disableVertexAttribArray(locations.a_position); - gl.disableVertexAttribArray(locations.a_offsets); - gl.disableVertexAttribArray(locations.a_texCoord); - gl.disableVertexAttribArray(locations.a_opacity); - gl.disableVertexAttribArray(locations.a_rotateWithView); -}; - - -/** - * @inheritDoc - */ -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_; - - if (!ol.obj.isEmpty(skippedFeaturesHash)) { - this.drawReplaySkipping_( - gl, context, skippedFeaturesHash, textures, groupIndices); - } else { - var i, ii, start; - 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); - start = end; - } - } -}; - - -/** - * Draw the replay while paying attention to skipped features. - * - * This functions creates groups of features that can be drawn to together, - * so that the number of `drawElements` calls is minimized. - * - * For example given the following texture groups: - * - * Group 1: A B C - * Group 2: D [E] F G - * - * If feature E should be skipped, the following `drawElements` calls will be - * made: - * - * drawElements with feature A, B and C - * drawElements with feature D - * drawElements with feature F and G - * - * @private - * @param {WebGLRenderingContext} gl gl. - * @param {ol.webgl.Context} context Context. - * @param {Object.} skippedFeaturesHash Ids of features - * to skip. - * @param {Array.} textures Textures. - * @param {Array.} groupIndices Texture group indices. - */ -ol.render.webgl.ImageReplay.prototype.drawReplaySkipping_ = function(gl, context, skippedFeaturesHash, textures, - groupIndices) { - var featureIndex = 0; - - var i, ii; - for (i = 0, ii = textures.length; i < ii; ++i) { - gl.bindTexture(ol.webgl.TEXTURE_2D, textures[i]); - var groupStart = (i > 0) ? groupIndices[i - 1] : 0; - var groupEnd = groupIndices[i]; - - var start = groupStart; - var end = groupStart; - 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); + /** + * @inheritDoc + */ + ol.render.webgl.ImageReplay.prototype.getDeleteResourcesFunction = function(context) { + var verticesBuffer = this.verticesBuffer; + var indicesBuffer = this.indicesBuffer; + var textures = this.textures_; + var hitDetectionTextures = this.hitDetectionTextures_; + var gl = context.getGL(); + return function() { + if (!gl.isContextLost()) { + var i, ii; + for (i = 0, ii = textures.length; i < ii; ++i) { + gl.deleteTexture(textures[i]); } - // continue with the next feature - start = (featureIndex === this.startIndices.length - 1) ? - groupEnd : this.startIndices[featureIndex + 1]; - end = start; + for (i = 0, ii = hitDetectionTextures.length; i < ii; ++i) { + gl.deleteTexture(hitDetectionTextures[i]); + } + } + context.deleteBuffer(verticesBuffer); + context.deleteBuffer(indicesBuffer); + }; + }; + + + /** + * @param {Array.} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @return {number} My end. + * @private + */ + ol.render.webgl.ImageReplay.prototype.drawCoordinates_ = function(flatCoordinates, offset, end, stride) { + var anchorX = /** @type {number} */ (this.anchorX_); + var anchorY = /** @type {number} */ (this.anchorY_); + var height = /** @type {number} */ (this.height_); + var imageHeight = /** @type {number} */ (this.imageHeight_); + var imageWidth = /** @type {number} */ (this.imageWidth_); + var opacity = /** @type {number} */ (this.opacity_); + var originX = /** @type {number} */ (this.originX_); + var originY = /** @type {number} */ (this.originY_); + var rotateWithView = this.rotateWithView_ ? 1.0 : 0.0; + // this.rotation_ is anti-clockwise, but rotation is clockwise + var rotation = /** @type {number} */ (-this.rotation_); + var scale = /** @type {number} */ (this.scale_); + 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 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]; + + // 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 + // WebGL supported Geometry Shaders (which can emit new vertices), but that + // is not currently the case. + // + // And each vertex includes 8 values: the x and y coordinates, the x and + // y offsets used to calculate the position of the corner, the u and + // v texture coordinates for the corner, the opacity, and whether the + // the image should be rotated with the view (rotateWithView). + + n = numVertices / 8; + + // 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; + + // 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; + + // 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; + + // 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.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; + }; + + + /** + * @inheritDoc + */ + ol.render.webgl.ImageReplay.prototype.drawMultiPoint = function(multiPointGeometry, feature) { + this.startIndices.push(this.indices.length); + this.startIndicesFeature.push(feature); + var flatCoordinates = multiPointGeometry.getFlatCoordinates(); + var stride = multiPointGeometry.getStride(); + this.drawCoordinates_( + flatCoordinates, 0, flatCoordinates.length, stride); + }; + + + /** + * @inheritDoc + */ + ol.render.webgl.ImageReplay.prototype.drawPoint = function(pointGeometry, feature) { + this.startIndices.push(this.indices.length); + this.startIndicesFeature.push(feature); + var flatCoordinates = pointGeometry.getFlatCoordinates(); + var stride = pointGeometry.getStride(); + this.drawCoordinates_( + flatCoordinates, 0, flatCoordinates.length, stride); + }; + + + /** + * @inheritDoc + */ + ol.render.webgl.ImageReplay.prototype.finish = function(context) { + var gl = context.getGL(); + + this.groupIndices_.push(this.indices.length); + this.hitDetectionGroupIndices_.push(this.indices.length); + + // create, bind, and populate the vertices buffer + this.verticesBuffer = new ol.webgl.Buffer(this.vertices); + + var indices = this.indices; + + // create, bind, and populate the indices buffer + this.indicesBuffer = new ol.webgl.Buffer(indices); + + // create textures + /** @type {Object.} */ + var texturePerImage = {}; + + this.createTextures_(this.textures_, this.images_, texturePerImage, gl); + + this.createTextures_(this.hitDetectionTextures_, this.hitDetectionImages_, + texturePerImage, gl); + + this.anchorX_ = undefined; + this.anchorY_ = undefined; + this.height_ = undefined; + this.images_ = null; + this.hitDetectionImages_ = null; + this.imageHeight_ = undefined; + this.imageWidth_ = undefined; + 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.width_ = undefined; + }; + + + /** + * @private + * @param {Array.} textures Textures. + * @param {Array.} images + * Images. + * @param {Object.} texturePerImage Texture cache. + * @param {WebGLRenderingContext} gl Gl. + */ + ol.render.webgl.ImageReplay.prototype.createTextures_ = function(textures, images, texturePerImage, gl) { + var texture, image, uid, i; + var ii = images.length; + for (i = 0; i < ii; ++i) { + image = images[i]; + + uid = ol.getUid(image).toString(); + if (uid in texturePerImage) { + texture = texturePerImage[uid]; } else { - // the feature is not skipped, augment the end index - end = (featureIndex === this.startIndices.length - 1) ? - groupEnd : this.startIndices[featureIndex + 1]; + texture = ol.webgl.Context.createTexture( + gl, image, ol.webgl.CLAMP_TO_EDGE, ol.webgl.CLAMP_TO_EDGE); + texturePerImage[uid] = texture; } - featureIndex++; + textures[i] = texture; + } + }; + + + /** + * @inheritDoc + */ + 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; + var program = context.getProgram(fragmentShader, vertexShader); + + // get the locations + var locations; + if (!this.defaultLocations_) { + locations = + new ol.render.webgl.imagereplay.defaultshader.Locations(gl, program); + this.defaultLocations_ = locations; + } else { + locations = this.defaultLocations_; } - 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); - } - } -}; + // use the program (FIXME: use the return value) + context.useProgram(program); + + // enable the vertex attrib arrays + gl.enableVertexAttribArray(locations.a_position); + gl.vertexAttribPointer(locations.a_position, 2, ol.webgl.FLOAT, + false, 32, 0); + + gl.enableVertexAttribArray(locations.a_offsets); + gl.vertexAttribPointer(locations.a_offsets, 2, ol.webgl.FLOAT, + false, 32, 8); + + gl.enableVertexAttribArray(locations.a_texCoord); + gl.vertexAttribPointer(locations.a_texCoord, 2, ol.webgl.FLOAT, + false, 32, 16); + + gl.enableVertexAttribArray(locations.a_opacity); + gl.vertexAttribPointer(locations.a_opacity, 1, ol.webgl.FLOAT, + false, 32, 24); + + gl.enableVertexAttribArray(locations.a_rotateWithView); + gl.vertexAttribPointer(locations.a_rotateWithView, 1, ol.webgl.FLOAT, + false, 32, 28); + + return locations; + }; -/** - * @inheritDoc - */ -ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplayOneByOne = function(gl, context, skippedFeaturesHash, - featureCallback, opt_hitExtent) { - var i, groupStart, start, end, feature, featureUid; - 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; - end = this.hitDetectionGroupIndices_[i]; + /** + * @inheritDoc + */ + ol.render.webgl.ImageReplay.prototype.shutDownProgram = function(gl, locations) { + gl.disableVertexAttribArray(locations.a_position); + gl.disableVertexAttribArray(locations.a_offsets); + gl.disableVertexAttribArray(locations.a_texCoord); + gl.disableVertexAttribArray(locations.a_opacity); + gl.disableVertexAttribArray(locations.a_rotateWithView); + }; - // draw all features for this texture group - 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); + /** + * @inheritDoc + */ + 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_; + + if (!ol.obj.isEmpty(skippedFeaturesHash)) { + this.drawReplaySkipping_( + gl, context, skippedFeaturesHash, textures, groupIndices); + } else { + var i, ii, start; + 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); + start = end; + } + } + }; - var result = featureCallback(feature); - if (result) { - return result; + + /** + * Draw the replay while paying attention to skipped features. + * + * This functions creates groups of features that can be drawn to together, + * so that the number of `drawElements` calls is minimized. + * + * For example given the following texture groups: + * + * Group 1: A B C + * Group 2: D [E] F G + * + * If feature E should be skipped, the following `drawElements` calls will be + * made: + * + * drawElements with feature A, B and C + * drawElements with feature D + * drawElements with feature F and G + * + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {ol.webgl.Context} context Context. + * @param {Object.} skippedFeaturesHash Ids of features + * to skip. + * @param {Array.} textures Textures. + * @param {Array.} groupIndices Texture group indices. + */ + ol.render.webgl.ImageReplay.prototype.drawReplaySkipping_ = function(gl, context, skippedFeaturesHash, textures, + groupIndices) { + var featureIndex = 0; + + var i, ii; + for (i = 0, ii = textures.length; i < ii; ++i) { + gl.bindTexture(ol.webgl.TEXTURE_2D, textures[i]); + var groupStart = (i > 0) ? groupIndices[i - 1] : 0; + var groupEnd = groupIndices[i]; + + var start = groupStart; + var end = groupStart; + 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); + } + // continue with the next feature + 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]; } + featureIndex++; } - end = start; - featureIndex--; + 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); + } } - } - return undefined; -}; + }; -/** - * @inheritDoc - */ -ol.render.webgl.ImageReplay.prototype.setImageStyle = function(imageStyle) { - var anchor = imageStyle.getAnchor(); - var image = imageStyle.getImage(1); - var imageSize = imageStyle.getImageSize(); - var hitDetectionImage = imageStyle.getHitDetectionImage(1); - var opacity = imageStyle.getOpacity(); - var origin = imageStyle.getOrigin(); - var rotateWithView = imageStyle.getRotateWithView(); - var rotation = imageStyle.getRotation(); - var size = imageStyle.getSize(); - var scale = imageStyle.getScale(); + /** + * @inheritDoc + */ + ol.render.webgl.ImageReplay.prototype.drawHitDetectionReplayOneByOne = function(gl, context, skippedFeaturesHash, + featureCallback, opt_hitExtent) { + var i, groupStart, start, end, feature, featureUid; + 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; + end = this.hitDetectionGroupIndices_[i]; - var currentImage; - if (this.images_.length === 0) { - this.images_.push(image); - } else { - currentImage = this.images_[this.images_.length - 1]; - if (ol.getUid(currentImage) != ol.getUid(image)) { - this.groupIndices_.push(this.indices.length); + // draw all features for this texture group + 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; + } + } + + end = start; + featureIndex--; + } + } + return undefined; + }; + + + /** + * @inheritDoc + */ + ol.render.webgl.ImageReplay.prototype.setImageStyle = function(imageStyle) { + var anchor = imageStyle.getAnchor(); + var image = imageStyle.getImage(1); + var imageSize = imageStyle.getImageSize(); + var hitDetectionImage = imageStyle.getHitDetectionImage(1); + var opacity = imageStyle.getOpacity(); + var origin = imageStyle.getOrigin(); + var rotateWithView = imageStyle.getRotateWithView(); + var rotation = imageStyle.getRotation(); + var size = imageStyle.getSize(); + var scale = imageStyle.getScale(); + + var currentImage; + if (this.images_.length === 0) { this.images_.push(image); + } else { + currentImage = this.images_[this.images_.length - 1]; + if (ol.getUid(currentImage) != ol.getUid(image)) { + this.groupIndices_.push(this.indices.length); + this.images_.push(image); + } } - } - if (this.hitDetectionImages_.length === 0) { - this.hitDetectionImages_.push(hitDetectionImage); - } else { - currentImage = - this.hitDetectionImages_[this.hitDetectionImages_.length - 1]; - if (ol.getUid(currentImage) != ol.getUid(hitDetectionImage)) { - this.hitDetectionGroupIndices_.push(this.indices.length); + if (this.hitDetectionImages_.length === 0) { this.hitDetectionImages_.push(hitDetectionImage); + } else { + currentImage = + this.hitDetectionImages_[this.hitDetectionImages_.length - 1]; + if (ol.getUid(currentImage) != ol.getUid(hitDetectionImage)) { + this.hitDetectionGroupIndices_.push(this.indices.length); + this.hitDetectionImages_.push(hitDetectionImage); + } } - } - this.anchorX_ = anchor[0]; - this.anchorY_ = anchor[1]; - this.height_ = size[1]; - this.imageHeight_ = imageSize[1]; - this.imageWidth_ = imageSize[0]; - this.opacity_ = opacity; - this.originX_ = origin[0]; - this.originY_ = origin[1]; - this.rotation_ = rotation; - this.rotateWithView_ = rotateWithView; - this.scale_ = scale; - this.width_ = size[0]; -}; + this.anchorX_ = anchor[0]; + this.anchorY_ = anchor[1]; + this.height_ = size[1]; + this.imageHeight_ = imageSize[1]; + this.imageWidth_ = imageSize[0]; + this.opacity_ = opacity; + this.originX_ = origin[0]; + this.originY_ = origin[1]; + this.rotation_ = rotation; + this.rotateWithView_ = rotateWithView; + this.scale_ = scale; + this.width_ = size[0]; + }; + +} diff --git a/src/ol/render/webgl/immediate.js b/src/ol/render/webgl/immediate.js index afe3d94232..74f4420b18 100644 --- a/src/ol/render/webgl/immediate.js +++ b/src/ol/render/webgl/immediate.js @@ -9,325 +9,329 @@ goog.require('ol.render.webgl.ReplayGroup'); goog.require('ol.render.webgl'); -/** - * @constructor - * @extends {ol.render.VectorContext} - * @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 {ol.Extent} extent Extent. - * @param {number} pixelRatio Pixel ratio. - * @struct - */ -ol.render.webgl.Immediate = function(context, center, resolution, rotation, size, extent, pixelRatio) { - ol.render.VectorContext.call(this); +if (ol.ENABLE_WEBGL) { /** - * @private + * @constructor + * @extends {ol.render.VectorContext} + * @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 {ol.Extent} extent Extent. + * @param {number} pixelRatio Pixel ratio. + * @struct */ - this.context_ = context; + ol.render.webgl.Immediate = function(context, center, resolution, rotation, size, extent, pixelRatio) { + ol.render.VectorContext.call(this); + + /** + * @private + */ + this.context_ = context; + + /** + * @private + */ + this.center_ = center; + + /** + * @private + */ + this.extent_ = extent; + + /** + * @private + */ + this.pixelRatio_ = pixelRatio; + + /** + * @private + */ + this.size_ = size; + + /** + * @private + */ + this.rotation_ = rotation; + + /** + * @private + */ + this.resolution_ = resolution; + + /** + * @private + * @type {ol.style.Image} + */ + this.imageStyle_ = null; + + /** + * @private + * @type {ol.style.Fill} + */ + this.fillStyle_ = null; + + /** + * @private + * @type {ol.style.Stroke} + */ + this.strokeStyle_ = null; + + }; + ol.inherits(ol.render.webgl.Immediate, ol.render.VectorContext); + /** - * @private + * Set the rendering style. Note that since this is an immediate rendering API, + * any `zIndex` on the provided style will be ignored. + * + * @param {ol.style.Style} style The rendering style. + * @api */ - this.center_ = center; + ol.render.webgl.Immediate.prototype.setStyle = function(style) { + this.setFillStrokeStyle(style.getFill(), style.getStroke()); + this.setImageStyle(style.getImage()); + }; + /** - * @private + * Render a geometry into the canvas. Call + * {@link ol.render.webgl.Immediate#setStyle} first to set the rendering style. + * + * @param {ol.geom.Geometry|ol.render.Feature} geometry The geometry to render. + * @api */ - this.extent_ = extent; + ol.render.webgl.Immediate.prototype.drawGeometry = function(geometry) { + var type = geometry.getType(); + switch (type) { + case ol.geom.GeometryType.POINT: + this.drawPoint(/** @type {ol.geom.Point} */ (geometry), null); + break; + case ol.geom.GeometryType.LINE_STRING: + this.drawLineString(/** @type {ol.geom.LineString} */ (geometry), null); + break; + case ol.geom.GeometryType.POLYGON: + this.drawPolygon(/** @type {ol.geom.Polygon} */ (geometry), null); + break; + case ol.geom.GeometryType.MULTI_POINT: + this.drawMultiPoint(/** @type {ol.geom.MultiPoint} */ (geometry), null); + break; + case ol.geom.GeometryType.MULTI_LINE_STRING: + this.drawMultiLineString(/** @type {ol.geom.MultiLineString} */ (geometry), null); + break; + case ol.geom.GeometryType.MULTI_POLYGON: + this.drawMultiPolygon(/** @type {ol.geom.MultiPolygon} */ (geometry), null); + break; + case ol.geom.GeometryType.GEOMETRY_COLLECTION: + this.drawGeometryCollection(/** @type {ol.geom.GeometryCollection} */ (geometry), null); + break; + case ol.geom.GeometryType.CIRCLE: + this.drawCircle(/** @type {ol.geom.Circle} */ (geometry), null); + break; + default: + // pass + } + }; + /** - * @private + * @inheritDoc + * @api */ - this.pixelRatio_ = pixelRatio; + ol.render.webgl.Immediate.prototype.drawFeature = function(feature, style) { + var geometry = style.getGeometryFunction()(feature); + if (!geometry || + !ol.extent.intersects(this.extent_, geometry.getExtent())) { + return; + } + this.setStyle(style); + this.drawGeometry(geometry); + }; + /** - * @private + * @inheritDoc */ - this.size_ = size; + ol.render.webgl.Immediate.prototype.drawGeometryCollection = function(geometry, data) { + var geometries = geometry.getGeometriesArray(); + var i, ii; + for (i = 0, ii = geometries.length; i < ii; ++i) { + this.drawGeometry(geometries[i]); + } + }; + /** - * @private + * @inheritDoc */ - this.rotation_ = rotation; + ol.render.webgl.Immediate.prototype.drawPoint = function(geometry, data) { + var context = this.context_; + var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_); + var replay = /** @type {ol.render.webgl.ImageReplay} */ ( + replayGroup.getReplay(0, ol.render.ReplayType.IMAGE)); + replay.setImageStyle(this.imageStyle_); + replay.drawPoint(geometry, data); + replay.finish(context); + // default colors + var opacity = 1; + var skippedFeatures = {}; + var featureCallback; + var oneByOne = false; + replay.replay(this.context_, this.center_, this.resolution_, this.rotation_, + this.size_, this.pixelRatio_, opacity, skippedFeatures, featureCallback, + oneByOne); + replay.getDeleteResourcesFunction(context)(); + }; + /** - * @private + * @inheritDoc */ - this.resolution_ = resolution; + ol.render.webgl.Immediate.prototype.drawMultiPoint = function(geometry, data) { + var context = this.context_; + var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_); + var replay = /** @type {ol.render.webgl.ImageReplay} */ ( + replayGroup.getReplay(0, ol.render.ReplayType.IMAGE)); + replay.setImageStyle(this.imageStyle_); + replay.drawMultiPoint(geometry, data); + replay.finish(context); + var opacity = 1; + var skippedFeatures = {}; + var featureCallback; + var oneByOne = false; + replay.replay(this.context_, this.center_, this.resolution_, this.rotation_, + this.size_, this.pixelRatio_, opacity, skippedFeatures, featureCallback, + oneByOne); + replay.getDeleteResourcesFunction(context)(); + }; + /** - * @private - * @type {ol.style.Image} + * @inheritDoc */ - this.imageStyle_ = null; + ol.render.webgl.Immediate.prototype.drawLineString = function(geometry, data) { + var context = this.context_; + var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_); + var replay = /** @type {ol.render.webgl.LineStringReplay} */ ( + replayGroup.getReplay(0, ol.render.ReplayType.LINE_STRING)); + replay.setFillStrokeStyle(null, this.strokeStyle_); + replay.drawLineString(geometry, data); + replay.finish(context); + var opacity = 1; + var skippedFeatures = {}; + var featureCallback; + var oneByOne = false; + replay.replay(this.context_, this.center_, this.resolution_, this.rotation_, + this.size_, this.pixelRatio_, opacity, skippedFeatures, featureCallback, + oneByOne); + replay.getDeleteResourcesFunction(context)(); + }; + /** - * @private - * @type {ol.style.Fill} + * @inheritDoc */ - this.fillStyle_ = null; + ol.render.webgl.Immediate.prototype.drawMultiLineString = function(geometry, data) { + var context = this.context_; + var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_); + var replay = /** @type {ol.render.webgl.LineStringReplay} */ ( + replayGroup.getReplay(0, ol.render.ReplayType.LINE_STRING)); + replay.setFillStrokeStyle(null, this.strokeStyle_); + replay.drawMultiLineString(geometry, data); + replay.finish(context); + var opacity = 1; + var skippedFeatures = {}; + var featureCallback; + var oneByOne = false; + replay.replay(this.context_, this.center_, this.resolution_, this.rotation_, + this.size_, this.pixelRatio_, opacity, skippedFeatures, featureCallback, + oneByOne); + replay.getDeleteResourcesFunction(context)(); + }; + /** - * @private - * @type {ol.style.Stroke} + * @inheritDoc */ - this.strokeStyle_ = null; - -}; -ol.inherits(ol.render.webgl.Immediate, ol.render.VectorContext); + ol.render.webgl.Immediate.prototype.drawPolygon = function(geometry, data) { + var context = this.context_; + var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_); + var replay = /** @type {ol.render.webgl.PolygonReplay} */ ( + replayGroup.getReplay(0, ol.render.ReplayType.POLYGON)); + replay.setFillStrokeStyle(this.fillStyle_, this.strokeStyle_); + replay.drawPolygon(geometry, data); + replay.finish(context); + var opacity = 1; + var skippedFeatures = {}; + var featureCallback; + var oneByOne = false; + replay.replay(this.context_, this.center_, this.resolution_, this.rotation_, + this.size_, this.pixelRatio_, opacity, skippedFeatures, featureCallback, + oneByOne); + replay.getDeleteResourcesFunction(context)(); + }; -/** - * Set the rendering style. Note that since this is an immediate rendering API, - * any `zIndex` on the provided style will be ignored. - * - * @param {ol.style.Style} style The rendering style. - * @api - */ -ol.render.webgl.Immediate.prototype.setStyle = function(style) { - this.setFillStrokeStyle(style.getFill(), style.getStroke()); - this.setImageStyle(style.getImage()); -}; + /** + * @inheritDoc + */ + ol.render.webgl.Immediate.prototype.drawMultiPolygon = function(geometry, data) { + var context = this.context_; + var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_); + var replay = /** @type {ol.render.webgl.PolygonReplay} */ ( + replayGroup.getReplay(0, ol.render.ReplayType.POLYGON)); + replay.setFillStrokeStyle(this.fillStyle_, this.strokeStyle_); + replay.drawMultiPolygon(geometry, data); + replay.finish(context); + var opacity = 1; + var skippedFeatures = {}; + var featureCallback; + var oneByOne = false; + replay.replay(this.context_, this.center_, this.resolution_, this.rotation_, + this.size_, this.pixelRatio_, opacity, skippedFeatures, featureCallback, + oneByOne); + replay.getDeleteResourcesFunction(context)(); + }; -/** - * Render a geometry into the canvas. Call - * {@link ol.render.webgl.Immediate#setStyle} first to set the rendering style. - * - * @param {ol.geom.Geometry|ol.render.Feature} geometry The geometry to render. - * @api - */ -ol.render.webgl.Immediate.prototype.drawGeometry = function(geometry) { - var type = geometry.getType(); - switch (type) { - case ol.geom.GeometryType.POINT: - this.drawPoint(/** @type {ol.geom.Point} */ (geometry), null); - break; - case ol.geom.GeometryType.LINE_STRING: - this.drawLineString(/** @type {ol.geom.LineString} */ (geometry), null); - break; - case ol.geom.GeometryType.POLYGON: - this.drawPolygon(/** @type {ol.geom.Polygon} */ (geometry), null); - break; - case ol.geom.GeometryType.MULTI_POINT: - this.drawMultiPoint(/** @type {ol.geom.MultiPoint} */ (geometry), null); - break; - case ol.geom.GeometryType.MULTI_LINE_STRING: - this.drawMultiLineString(/** @type {ol.geom.MultiLineString} */ (geometry), null); - break; - case ol.geom.GeometryType.MULTI_POLYGON: - this.drawMultiPolygon(/** @type {ol.geom.MultiPolygon} */ (geometry), null); - break; - case ol.geom.GeometryType.GEOMETRY_COLLECTION: - this.drawGeometryCollection(/** @type {ol.geom.GeometryCollection} */ (geometry), null); - break; - case ol.geom.GeometryType.CIRCLE: - this.drawCircle(/** @type {ol.geom.Circle} */ (geometry), null); - break; - default: - // pass - } -}; + /** + * @inheritDoc + */ + ol.render.webgl.Immediate.prototype.drawCircle = function(geometry, data) { + var context = this.context_; + var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_); + var replay = /** @type {ol.render.webgl.CircleReplay} */ ( + replayGroup.getReplay(0, ol.render.ReplayType.CIRCLE)); + replay.setFillStrokeStyle(this.fillStyle_, this.strokeStyle_); + replay.drawCircle(geometry, data); + replay.finish(context); + var opacity = 1; + var skippedFeatures = {}; + var featureCallback; + var oneByOne = false; + replay.replay(this.context_, this.center_, this.resolution_, this.rotation_, + this.size_, this.pixelRatio_, opacity, skippedFeatures, featureCallback, + oneByOne); + replay.getDeleteResourcesFunction(context)(); + }; -/** - * @inheritDoc - * @api - */ -ol.render.webgl.Immediate.prototype.drawFeature = function(feature, style) { - var geometry = style.getGeometryFunction()(feature); - if (!geometry || - !ol.extent.intersects(this.extent_, geometry.getExtent())) { - return; - } - this.setStyle(style); - this.drawGeometry(geometry); -}; + /** + * @inheritDoc + */ + ol.render.webgl.Immediate.prototype.setImageStyle = function(imageStyle) { + this.imageStyle_ = imageStyle; + }; -/** - * @inheritDoc - */ -ol.render.webgl.Immediate.prototype.drawGeometryCollection = function(geometry, data) { - var geometries = geometry.getGeometriesArray(); - var i, ii; - for (i = 0, ii = geometries.length; i < ii; ++i) { - this.drawGeometry(geometries[i]); - } -}; + /** + * @inheritDoc + */ + ol.render.webgl.Immediate.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) { + this.fillStyle_ = fillStyle; + this.strokeStyle_ = strokeStyle; + }; - -/** - * @inheritDoc - */ -ol.render.webgl.Immediate.prototype.drawPoint = function(geometry, data) { - var context = this.context_; - var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_); - var replay = /** @type {ol.render.webgl.ImageReplay} */ ( - replayGroup.getReplay(0, ol.render.ReplayType.IMAGE)); - replay.setImageStyle(this.imageStyle_); - replay.drawPoint(geometry, data); - replay.finish(context); - // default colors - var opacity = 1; - var skippedFeatures = {}; - var featureCallback; - var oneByOne = false; - replay.replay(this.context_, this.center_, this.resolution_, this.rotation_, - this.size_, this.pixelRatio_, opacity, skippedFeatures, featureCallback, - oneByOne); - replay.getDeleteResourcesFunction(context)(); -}; - - -/** - * @inheritDoc - */ -ol.render.webgl.Immediate.prototype.drawMultiPoint = function(geometry, data) { - var context = this.context_; - var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_); - var replay = /** @type {ol.render.webgl.ImageReplay} */ ( - replayGroup.getReplay(0, ol.render.ReplayType.IMAGE)); - replay.setImageStyle(this.imageStyle_); - replay.drawMultiPoint(geometry, data); - replay.finish(context); - var opacity = 1; - var skippedFeatures = {}; - var featureCallback; - var oneByOne = false; - replay.replay(this.context_, this.center_, this.resolution_, this.rotation_, - this.size_, this.pixelRatio_, opacity, skippedFeatures, featureCallback, - oneByOne); - replay.getDeleteResourcesFunction(context)(); -}; - - -/** - * @inheritDoc - */ -ol.render.webgl.Immediate.prototype.drawLineString = function(geometry, data) { - var context = this.context_; - var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_); - var replay = /** @type {ol.render.webgl.LineStringReplay} */ ( - replayGroup.getReplay(0, ol.render.ReplayType.LINE_STRING)); - replay.setFillStrokeStyle(null, this.strokeStyle_); - replay.drawLineString(geometry, data); - replay.finish(context); - var opacity = 1; - var skippedFeatures = {}; - var featureCallback; - var oneByOne = false; - replay.replay(this.context_, this.center_, this.resolution_, this.rotation_, - this.size_, this.pixelRatio_, opacity, skippedFeatures, featureCallback, - oneByOne); - replay.getDeleteResourcesFunction(context)(); -}; - - -/** - * @inheritDoc - */ -ol.render.webgl.Immediate.prototype.drawMultiLineString = function(geometry, data) { - var context = this.context_; - var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_); - var replay = /** @type {ol.render.webgl.LineStringReplay} */ ( - replayGroup.getReplay(0, ol.render.ReplayType.LINE_STRING)); - replay.setFillStrokeStyle(null, this.strokeStyle_); - replay.drawMultiLineString(geometry, data); - replay.finish(context); - var opacity = 1; - var skippedFeatures = {}; - var featureCallback; - var oneByOne = false; - replay.replay(this.context_, this.center_, this.resolution_, this.rotation_, - this.size_, this.pixelRatio_, opacity, skippedFeatures, featureCallback, - oneByOne); - replay.getDeleteResourcesFunction(context)(); -}; - - -/** - * @inheritDoc - */ -ol.render.webgl.Immediate.prototype.drawPolygon = function(geometry, data) { - var context = this.context_; - var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_); - var replay = /** @type {ol.render.webgl.PolygonReplay} */ ( - replayGroup.getReplay(0, ol.render.ReplayType.POLYGON)); - replay.setFillStrokeStyle(this.fillStyle_, this.strokeStyle_); - replay.drawPolygon(geometry, data); - replay.finish(context); - var opacity = 1; - var skippedFeatures = {}; - var featureCallback; - var oneByOne = false; - replay.replay(this.context_, this.center_, this.resolution_, this.rotation_, - this.size_, this.pixelRatio_, opacity, skippedFeatures, featureCallback, - oneByOne); - replay.getDeleteResourcesFunction(context)(); -}; - - -/** - * @inheritDoc - */ -ol.render.webgl.Immediate.prototype.drawMultiPolygon = function(geometry, data) { - var context = this.context_; - var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_); - var replay = /** @type {ol.render.webgl.PolygonReplay} */ ( - replayGroup.getReplay(0, ol.render.ReplayType.POLYGON)); - replay.setFillStrokeStyle(this.fillStyle_, this.strokeStyle_); - replay.drawMultiPolygon(geometry, data); - replay.finish(context); - var opacity = 1; - var skippedFeatures = {}; - var featureCallback; - var oneByOne = false; - replay.replay(this.context_, this.center_, this.resolution_, this.rotation_, - this.size_, this.pixelRatio_, opacity, skippedFeatures, featureCallback, - oneByOne); - replay.getDeleteResourcesFunction(context)(); -}; - - -/** - * @inheritDoc - */ -ol.render.webgl.Immediate.prototype.drawCircle = function(geometry, data) { - var context = this.context_; - var replayGroup = new ol.render.webgl.ReplayGroup(1, this.extent_); - var replay = /** @type {ol.render.webgl.CircleReplay} */ ( - replayGroup.getReplay(0, ol.render.ReplayType.CIRCLE)); - replay.setFillStrokeStyle(this.fillStyle_, this.strokeStyle_); - replay.drawCircle(geometry, data); - replay.finish(context); - var opacity = 1; - var skippedFeatures = {}; - var featureCallback; - var oneByOne = false; - replay.replay(this.context_, this.center_, this.resolution_, this.rotation_, - this.size_, this.pixelRatio_, opacity, skippedFeatures, featureCallback, - oneByOne); - replay.getDeleteResourcesFunction(context)(); -}; - - -/** - * @inheritDoc - */ -ol.render.webgl.Immediate.prototype.setImageStyle = function(imageStyle) { - this.imageStyle_ = imageStyle; -}; - - -/** - * @inheritDoc - */ -ol.render.webgl.Immediate.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) { - this.fillStyle_ = fillStyle; - this.strokeStyle_ = strokeStyle; -}; +} diff --git a/src/ol/render/webgl/linestringreplay.js b/src/ol/render/webgl/linestringreplay.js index 6ff0ff364a..5d1089b072 100644 --- a/src/ol/render/webgl/linestringreplay.js +++ b/src/ol/render/webgl/linestringreplay.js @@ -15,663 +15,667 @@ 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); +if (ol.ENABLE_WEBGL) { /** - * @private - * @type {ol.render.webgl.linestringreplay.defaultshader.Locations} + * @constructor + * @extends {ol.render.webgl.Replay} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Max extent. + * @struct */ - this.defaultLocations_ = null; + ol.render.webgl.LineStringReplay = function(tolerance, maxExtent) { + ol.render.webgl.Replay.call(this, tolerance, maxExtent); - /** - * @private - * @type {Array.>} - */ - this.styles_ = []; + /** + * @private + * @type {ol.render.webgl.linestringreplay.defaultshader.Locations} + */ + this.defaultLocations_ = null; - /** - * @private - * @type {Array.} - */ - this.styleIndices_ = []; + /** + * @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 + }; - /** - * @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); + 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) { + /** + * 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.LineStringReplay.Instruction_, 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; + 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.LineStringReplay.Instruction_, 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) { + 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.LineStringReplay.Instruction_.BEGIN_LINE_CAP * lineCap, numVertices); - - numVertices = this.addVertices_([0, 0], p1, p2, - -lastSign * ol.render.webgl.LineStringReplay.Instruction_.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; + 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]]; - numVertices = this.addVertices_([0, 0], p1, p2, - lastSign * ol.render.webgl.LineStringReplay.Instruction_.BEGIN_LINE * (lineCap || 1), numVertices); + startCoords = p2; + } else { + //Add the first two/four vertices. - numVertices = this.addVertices_([0, 0], p1, p2, - -lastSign * ol.render.webgl.LineStringReplay.Instruction_.BEGIN_LINE * (lineCap || 1), numVertices); + if (lineCap) { + numVertices = this.addVertices_([0, 0], p1, p2, + lastSign * ol.render.webgl.LineStringReplay.Instruction_.BEGIN_LINE_CAP * lineCap, numVertices); - lastIndex = numVertices / 7 - 1; + numVertices = this.addVertices_([0, 0], p1, p2, + -lastSign * ol.render.webgl.LineStringReplay.Instruction_.BEGIN_LINE_CAP * lineCap, numVertices); - continue; - } - } else if (i === end - stride) { - //Last vertex. - if (closed) { - //Same as the first vertex. - p2 = startCoords; - break; + 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.LineStringReplay.Instruction_.BEGIN_LINE * (lineCap || 1), numVertices); + + numVertices = this.addVertices_([0, 0], p1, p2, + -lastSign * ol.render.webgl.LineStringReplay.Instruction_.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 { + p0 = p0 || [0, 0]; + + numVertices = this.addVertices_(p0, p1, [0, 0], + lastSign * ol.render.webgl.LineStringReplay.Instruction_.END_LINE * (lineCap || 1), numVertices); + + numVertices = this.addVertices_(p0, p1, [0, 0], + -lastSign * ol.render.webgl.LineStringReplay.Instruction_.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.LineStringReplay.Instruction_.END_LINE_CAP * lineCap, numVertices); + + numVertices = this.addVertices_(p0, p1, [0, 0], + -lastSign * ol.render.webgl.LineStringReplay.Instruction_.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 { - p0 = p0 || [0, 0]; + p2 = [flatCoordinates[i + stride], flatCoordinates[i + stride + 1]]; + } - numVertices = this.addVertices_(p0, p1, [0, 0], - lastSign * ol.render.webgl.LineStringReplay.Instruction_.END_LINE * (lineCap || 1), numVertices); + // We group CW and straight lines, thus the not so inituitive CCW checking function. + sign = ol.render.webgl.triangleIsCounterClockwise(p0[0], p0[1], p1[0], p1[1], p2[0], p2[1]) + ? -1 : 1; - numVertices = this.addVertices_(p0, p1, [0, 0], - -lastSign * ol.render.webgl.LineStringReplay.Instruction_.END_LINE * (lineCap || 1), numVertices); + numVertices = this.addVertices_(p0, p1, p2, + sign * ol.render.webgl.LineStringReplay.Instruction_.BEVEL_FIRST * (lineJoin || 1), numVertices); + numVertices = this.addVertices_(p0, p1, p2, + sign * ol.render.webgl.LineStringReplay.Instruction_.BEVEL_SECOND * (lineJoin || 1), numVertices); + + numVertices = this.addVertices_(p0, p1, p2, + -sign * ol.render.webgl.LineStringReplay.Instruction_.MITER_BOTTOM * (lineJoin || 1), numVertices); + + if (i > offset) { 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 + 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.LineStringReplay.Instruction_.MITER_TOP * lineJoin, numVertices); + + this.indices[numIndices++] = n + 1; + this.indices[numIndices++] = n + 3; this.indices[numIndices++] = n; - - if (lineCap) { - numVertices = this.addVertices_(p0, p1, [0, 0], - lastSign * ol.render.webgl.LineStringReplay.Instruction_.END_LINE_CAP * lineCap, numVertices); - - numVertices = this.addVertices_(p0, p1, [0, 0], - -lastSign * ol.render.webgl.LineStringReplay.Instruction_.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]]; } - // We group CW and straight lines, thus the not so inituitive CCW checking function. - sign = ol.render.webgl.triangleIsCounterClockwise(p0[0], p0[1], p1[0], p1[1], p2[0], p2[1]) - ? -1 : 1; + if (closed) { + 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.LineStringReplay.Instruction_.BEVEL_FIRST * (lineJoin || 1), numVertices); + numVertices = this.addVertices_(p0, p1, p2, + sign * ol.render.webgl.LineStringReplay.Instruction_.BEVEL_FIRST * (lineJoin || 1), numVertices); - numVertices = this.addVertices_(p0, p1, p2, - sign * ol.render.webgl.LineStringReplay.Instruction_.BEVEL_SECOND * (lineJoin || 1), numVertices); + numVertices = this.addVertices_(p0, p1, p2, + -sign * ol.render.webgl.LineStringReplay.Instruction_.MITER_BOTTOM * (lineJoin || 1), numVertices); - numVertices = this.addVertices_(p0, p1, p2, - -sign * ol.render.webgl.LineStringReplay.Instruction_.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 + 1; 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; + /** + * @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; - lastIndex = n + 2; - lastSign = sign; + return numVertices; + }; - //Add miter - if (lineJoin) { - numVertices = this.addVertices_(p0, p1, p2, - sign * ol.render.webgl.LineStringReplay.Instruction_.MITER_TOP * lineJoin, numVertices); - - this.indices[numIndices++] = n + 1; - this.indices[numIndices++] = n + 3; - this.indices[numIndices++] = n; + /** + * 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); } - } - if (closed) { - 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.LineStringReplay.Instruction_.BEVEL_FIRST * (lineJoin || 1), numVertices); - - numVertices = this.addVertices_(p0, p1, p2, - -sign * ol.render.webgl.LineStringReplay.Instruction_.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; -}; + 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(); + /** + * @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); } - } - if (this.indices.length > indexCount) { - this.startIndices.push(indexCount); + }; + + + /** + * @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) { + if (!ol.geom.flat.topology.lineStringIsClosed(flatCoordinates, 0, + flatCoordinates.length, stride)) { + flatCoordinates.push(flatCoordinates[0]); + flatCoordinates.push(flatCoordinates[1]); + } + this.drawCoordinates_(flatCoordinates, 0, flatCoordinates.length, stride); + if (holeFlatCoordinates.length) { + var i, ii; + for (i = 0, ii = holeFlatCoordinates.length; i < ii; ++i) { + if (!ol.geom.flat.topology.lineStringIsClosed(holeFlatCoordinates[i], 0, + holeFlatCoordinates[i].length, stride)) { + holeFlatCoordinates[i].push(holeFlatCoordinates[i][0]); + holeFlatCoordinates[i].push(holeFlatCoordinates[i][1]); + } + 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(indexCount); + this.styleIndices_.push(index); 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) { - if (!ol.geom.flat.topology.lineStringIsClosed(flatCoordinates, 0, - flatCoordinates.length, stride)) { - flatCoordinates.push(flatCoordinates[0]); - flatCoordinates.push(flatCoordinates[1]); - } - this.drawCoordinates_(flatCoordinates, 0, flatCoordinates.length, stride); - if (holeFlatCoordinates.length) { - var i, ii; - for (i = 0, ii = holeFlatCoordinates.length; i < ii; ++i) { - if (!ol.geom.flat.topology.lineStringIsClosed(holeFlatCoordinates[i], 0, - holeFlatCoordinates[i].length, stride)) { - holeFlatCoordinates[i].push(holeFlatCoordinates[i][0]); - holeFlatCoordinates[i].push(holeFlatCoordinates[i][1]); - } - 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) { - 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; -}; + /** + * @return {number} Current index. + */ + ol.render.webgl.LineStringReplay.prototype.getCurrentIndex = function() { + return this.indices.length; + }; -/** - * @inheritDoc - */ -ol.render.webgl.LineStringReplay.prototype.shutDownProgram = function(gl, locations) { - gl.disableVertexAttribArray(locations.a_lastPos); - gl.disableVertexAttribArray(locations.a_position); - gl.disableVertexAttribArray(locations.a_nextPos); - gl.disableVertexAttribArray(locations.a_direction); -}; + /** + * @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.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)); + /** + * @inheritDoc + */ + ol.render.webgl.LineStringReplay.prototype.getDeleteResourcesFunction = function(context) { + var verticesBuffer = this.verticesBuffer; + var indicesBuffer = this.indicesBuffer; + return function() { + context.deleteBuffer(verticesBuffer); + context.deleteBuffer(indicesBuffer); + }; + }; - 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 { - //Draw by style groups to minimize drawElements() calls. - var i, start, end, nextStyle; - end = this.startIndices[this.startIndices.length - 1]; + /** + * @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.shutDownProgram = function(gl, locations) { + gl.disableVertexAttribArray(locations.a_lastPos); + gl.disableVertexAttribArray(locations.a_position); + gl.disableVertexAttribArray(locations.a_nextPos); + gl.disableVertexAttribArray(locations.a_direction); + }; + + + /** + * @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 { + //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); + gl.clear(gl.DEPTH_BUFFER_BIT); + end = start; + } + } + if (!hitDetection) { + gl.disable(gl.DEPTH_TEST); + gl.clear(gl.DEPTH_BUFFER_BIT); + //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) { + 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) { - start = this.styleIndices_[i]; nextStyle = this.styles_[i]; this.setStrokeStyle_(gl, nextStyle[0], nextStyle[1], nextStyle[2]); - this.drawElements(gl, context, start, end); - gl.clear(gl.DEPTH_BUFFER_BIT); - end = start; - } - } - if (!hitDetection) { - gl.disable(gl.DEPTH_TEST); - gl.clear(gl.DEPTH_BUFFER_BIT); - //Restore GL parameters. - gl.depthMask(tmpDepthMask); - gl.depthFunc(tmpDepthFunc); - } -}; + groupStart = this.styleIndices_[i]; + while (featureIndex >= 0 && + this.startIndices[featureIndex] >= groupStart) { + featureStart = this.startIndices[featureIndex]; + feature = this.startIndicesFeature[featureIndex]; + featureUid = ol.getUid(feature).toString(); -/** - * @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) { - 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); - gl.clear(gl.DEPTH_BUFFER_BIT); + if (skippedFeaturesHash[featureUid]) { + if (start !== end) { + this.drawElements(gl, context, start, end); + gl.clear(gl.DEPTH_BUFFER_BIT); + } + end = featureStart; } - end = featureStart; + featureIndex--; + start = featureStart; } - featureIndex--; - start = featureStart; - } - if (start !== end) { - this.drawElements(gl, context, start, end); - gl.clear(gl.DEPTH_BUFFER_BIT); - } - start = end = groupStart; - } -}; - - -/** - * @inheritDoc - */ -ol.render.webgl.LineStringReplay.prototype.drawHitDetectionReplayOneByOne = function(gl, context, skippedFeaturesHash, - featureCallback, opt_hitExtent) { - 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); + if (start !== end) { this.drawElements(gl, context, start, end); - - var result = featureCallback(feature); - - if (result) { - return result; - } - + gl.clear(gl.DEPTH_BUFFER_BIT); } - featureIndex--; - end = start; + start = end = groupStart; } - } - 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.drawHitDetectionReplayOneByOne = function(gl, context, skippedFeaturesHash, + featureCallback, opt_hitExtent) { + 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; + }; -/** - * @inheritDoc - */ -ol.render.webgl.LineStringReplay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) { - 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]); - } -}; + /** + * @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); + }; -/** - * @enum {number} - * @private - */ -ol.render.webgl.LineStringReplay.Instruction_ = { - ROUND: 2, - BEGIN_LINE: 3, - END_LINE: 5, - BEGIN_LINE_CAP: 7, - END_LINE_CAP: 11, - BEVEL_FIRST: 13, - BEVEL_SECOND: 17, - MITER_BOTTOM: 19, - MITER_TOP: 23 -}; + + /** + * @inheritDoc + */ + ol.render.webgl.LineStringReplay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) { + 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]); + } + }; + + /** + * @enum {number} + * @private + */ + ol.render.webgl.LineStringReplay.Instruction_ = { + ROUND: 2, + BEGIN_LINE: 3, + END_LINE: 5, + BEGIN_LINE_CAP: 7, + END_LINE_CAP: 11, + BEVEL_FIRST: 13, + BEVEL_SECOND: 17, + MITER_BOTTOM: 19, + MITER_TOP: 23 + }; + +} diff --git a/src/ol/render/webgl/polygonreplay.js b/src/ol/render/webgl/polygonreplay.js index b30e6b87d4..85f5f8d92f 100644 --- a/src/ol/render/webgl/polygonreplay.js +++ b/src/ol/render/webgl/polygonreplay.js @@ -19,712 +19,758 @@ 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); +if (ol.ENABLE_WEBGL) { /** - * @private - * @type {ol.render.webgl.polygonreplay.defaultshader.Locations} + * @constructor + * @extends {ol.render.webgl.Replay} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Max extent. + * @struct */ - this.defaultLocations_ = null; + 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 - * @type {Array.>} */ - this.styles_ = []; + 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); - /** - * @private - * @type {Array.} - */ - this.styleIndices_ = []; - - /** - * @private - * @type {{fillColor: (Array.|null), - * changed: boolean}|null} - */ - this.state_ = { - fillColor: null, - changed: false + // 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); }; -}; -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)); + /** + * 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)]); - 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)); + } 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)]); - 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); + rtree.load(extents, segments); - return maxX; -}; + 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(); + /** + * 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; } - 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) { + this.classifyPoints_(hole, rtree, true); + var seg = hole.firstItem(); + while (seg.p1.x !== holeMaxX) { + seg = hole.nextItem(); } - s0 = s1; - s1 = list.nextItem(); - } while (s0 !== start); - return pointsReclassified; -}; + 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; -/** - * @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) { - this.classifyPoints_(hole, rtree, true); - 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.reflex === undefined) { - 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; - } - } - } - if (minDist === Infinity) { - return; - } - bestPoint = seg.p1; - - if (minDist > 0) { - 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; + 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.reflex === undefined) { + 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; } } } - } + if (minDist === Infinity) { + return; + } + bestPoint = seg.p1; - 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)) { - break; + if (minDist > 0) { + 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; } } } - } 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); + } + + 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)) { break; - } else { - ccw = !this.isClockwise_(list); - this.classifyPoints_(list, rtree, ccw); + } + } + } + } 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; + if (list.getLength() === 3) { 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); + 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(); - } 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); + 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; + } } - this.indices[numIndices++] = seg.p0.i; - this.indices[numIndices++] = seg.p1.i; - this.indices[numIndices++] = p.i; + s0 = list.getPrevItem(); + s1 = list.nextItem(); + } while (s0 !== start); + return resolvedIntersections; + }; - resolvedIntersections = true; - if (breakCond) { + + /** + * @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.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 + s0 = list.nextItem(); + } while (s0 !== start); }; - 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 + /** + * @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; }; - 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) { - if (list.getCurrItem() === s1) { - 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 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.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 (typeof p === 'object' && (!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); + /** + * @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) { + if (list.getCurrItem() === s1) { + 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 (typeof p === 'object' && (!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; -}; + 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); + /** + * @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; -}; + 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)]; + /** + * 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; -}; + 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; -}; + /** + * @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(); + /** + * @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 holeFlatCoords; - for (j = 1, jj = linearRings.length; j < jj; ++j) { - holeFlatCoords = linearRings[j].getFlatCoordinates(); + 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); @@ -732,287 +778,245 @@ ol.render.webgl.PolygonReplay.prototype.drawMultiPolygon = function(multiPolygon 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) { - 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); + /** + * @inheritDoc + **/ + ol.render.webgl.PolygonReplay.prototype.finish = function(context) { + // create, bind, and populate the vertices buffer + this.verticesBuffer = new ol.webgl.Buffer(this.vertices); - // 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_; - } + // create, bind, and populate the indices buffer + this.indicesBuffer = new ol.webgl.Buffer(this.indices); - context.useProgram(program); + this.startIndices.push(this.indices.length); - // enable the vertex attrib arrays - gl.enableVertexAttribArray(locations.a_position); - gl.vertexAttribPointer(locations.a_position, 2, ol.webgl.FLOAT, - false, 8, 0); + this.lineStringReplay.finish(context); - return locations; -}; + //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.shutDownProgram = function(gl, locations) { - gl.disableVertexAttribArray(locations.a_position); -}; + /** + * @inheritDoc + */ + ol.render.webgl.PolygonReplay.prototype.getDeleteResourcesFunction = function(context) { + 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.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)); + /** + * @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); - if (!hitDetection) { - gl.enable(gl.DEPTH_TEST); - gl.depthMask(true); - gl.depthFunc(gl.NOTEQUAL); - } + // 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_; + } - if (!ol.obj.isEmpty(skippedFeaturesHash)) { - this.drawReplaySkipping_(gl, context, skippedFeaturesHash); - } else { - //Draw by style groups to minimize drawElements() calls. - var i, start, end, nextStyle; - end = this.startIndices[this.startIndices.length - 1]; + 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.shutDownProgram = function(gl, locations) { + gl.disableVertexAttribArray(locations.a_position); + }; + + + /** + * @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 { + //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.disable(gl.DEPTH_TEST); + gl.clear(gl.DEPTH_BUFFER_BIT); + //Restore GL parameters. + gl.depthMask(tmpDepthMask); + gl.depthFunc(tmpDepthFunc); + } + }; + + + /** + * @inheritDoc + */ + ol.render.webgl.PolygonReplay.prototype.drawHitDetectionReplayOneByOne = function(gl, context, skippedFeaturesHash, + featureCallback, opt_hitExtent) { + 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) { - start = this.styleIndices_[i]; nextStyle = this.styles_[i]; this.setFillStyle_(gl, nextStyle); - this.drawElements(gl, context, start, end); - end = start; - } - } - if (!hitDetection) { - gl.disable(gl.DEPTH_TEST); - gl.clear(gl.DEPTH_BUFFER_BIT); - //Restore GL parameters. - gl.depthMask(tmpDepthMask); - gl.depthFunc(tmpDepthFunc); - } -}; + groupStart = this.styleIndices_[i]; + while (featureIndex >= 0 && + this.startIndices[featureIndex] >= groupStart) { + start = this.startIndices[featureIndex]; + feature = this.startIndicesFeature[featureIndex]; + featureUid = ol.getUid(feature).toString(); -/** - * @inheritDoc - */ -ol.render.webgl.PolygonReplay.prototype.drawHitDetectionReplayOneByOne = function(gl, context, skippedFeaturesHash, - featureCallback, opt_hitExtent) { - 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) { - 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) { + 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); - gl.clear(gl.DEPTH_BUFFER_BIT); + + var result = featureCallback(feature); + + if (result) { + return result; + } + } - end = featureStart; + featureIndex--; + end = start; } - featureIndex--; - start = featureStart; } - if (start !== end) { - this.drawElements(gl, context, start, end); - gl.clear(gl.DEPTH_BUFFER_BIT); + 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) { + 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); + gl.clear(gl.DEPTH_BUFFER_BIT); + } + end = featureStart; + } + featureIndex--; + start = featureStart; + } + if (start !== end) { + this.drawElements(gl, context, start, end); + gl.clear(gl.DEPTH_BUFFER_BIT); + } + start = end = groupStart; } - start = end = groupStart; - } -}; + }; -/** - * @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); -}; + /** + * @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) { - 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); - } -}; + /** + * @inheritDoc + */ + ol.render.webgl.PolygonReplay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) { + 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 index a7e4c46db6..5de048d50e 100644 --- a/src/ol/render/webgl/replay.js +++ b/src/ol/render/webgl/replay.js @@ -7,353 +7,358 @@ goog.require('ol.transform'); goog.require('ol.vec.Mat4'); goog.require('ol.webgl'); -/** - * @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); + +if (ol.ENABLE_WEBGL) { + + /** + * @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.circlereplay.defaultshader.Locations| + 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.render.webgl.circlereplay.defaultshader.Locations| + ol.render.webgl.imagereplay.defaultshader.Locations| + ol.render.webgl.linestringreplay.defaultshader.Locations| + ol.render.webgl.polygonreplay.defaultshader.Locations} locations Locations. + */ + ol.render.webgl.Replay.prototype.shutDownProgram = function(gl, locations) {}; + + + /** + * @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 - * @type {number} + * @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 */ - 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.circlereplay.defaultshader.Locations| - 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.render.webgl.circlereplay.defaultshader.Locations| - ol.render.webgl.imagereplay.defaultshader.Locations| - ol.render.webgl.linestringreplay.defaultshader.Locations| - ol.render.webgl.polygonreplay.defaultshader.Locations} locations Locations. - */ -ol.render.webgl.Replay.prototype.shutDownProgram = function(gl, locations) {}; - - -/** - * @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(); - var tmpStencil, tmpStencilFunc, tmpStencilMaskVal, tmpStencilRef, tmpStencilMask, - tmpStencilOpFail, tmpStencilOpPass, tmpStencilOpZFail; - - if (this.lineStringReplay) { - tmpStencil = gl.isEnabled(gl.STENCIL_TEST); - tmpStencilFunc = gl.getParameter(gl.STENCIL_FUNC); - tmpStencilMaskVal = gl.getParameter(gl.STENCIL_VALUE_MASK); - tmpStencilRef = gl.getParameter(gl.STENCIL_REF); - tmpStencilMask = gl.getParameter(gl.STENCIL_WRITEMASK); - tmpStencilOpFail = gl.getParameter(gl.STENCIL_FAIL); - tmpStencilOpPass = gl.getParameter(gl.STENCIL_PASS_DEPTH_PASS); - tmpStencilOpZFail = gl.getParameter(gl.STENCIL_PASS_DEPTH_FAIL); - - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilMask(255); - gl.stencilFunc(gl.ALWAYS, 1, 255); - gl.stencilOp(gl.KEEP, gl.KEEP, gl.REPLACE); - - this.lineStringReplay.replay(context, - center, resolution, rotation, size, pixelRatio, - opacity, skippedFeaturesHash, - featureCallback, oneByOne, opt_hitExtent); - - gl.stencilMask(0); - gl.stencilFunc(gl.NOTEQUAL, 1, 255); - } - - context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.verticesBuffer); - - 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 - this.shutDownProgram(gl, locations); - - if (this.lineStringReplay) { - if (!tmpStencil) { - gl.disable(gl.STENCIL_TEST); + 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); } - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(/** @type {number} */ (tmpStencilFunc), - /** @type {number} */ (tmpStencilRef), /** @type {number} */ (tmpStencilMaskVal)); - gl.stencilMask(/** @type {number} */ (tmpStencilMask)); - gl.stencilOp(/** @type {number} */ (tmpStencilOpFail), - /** @type {number} */ (tmpStencilOpZFail), /** @type {number} */ (tmpStencilOpPass)); - } + }; - 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; + /** + * @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 numItems = end - start; - var offsetInBytes = start * elementSize; - gl.drawElements(ol.webgl.TRIANGLES, numItems, elementType, offsetInBytes); -}; + 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(); + var tmpStencil, tmpStencilFunc, tmpStencilMaskVal, tmpStencilRef, tmpStencilMask, + tmpStencilOpFail, tmpStencilOpPass, tmpStencilOpZFail; + + if (this.lineStringReplay) { + tmpStencil = gl.isEnabled(gl.STENCIL_TEST); + tmpStencilFunc = gl.getParameter(gl.STENCIL_FUNC); + tmpStencilMaskVal = gl.getParameter(gl.STENCIL_VALUE_MASK); + tmpStencilRef = gl.getParameter(gl.STENCIL_REF); + tmpStencilMask = gl.getParameter(gl.STENCIL_WRITEMASK); + tmpStencilOpFail = gl.getParameter(gl.STENCIL_FAIL); + tmpStencilOpPass = gl.getParameter(gl.STENCIL_PASS_DEPTH_PASS); + tmpStencilOpZFail = gl.getParameter(gl.STENCIL_PASS_DEPTH_FAIL); + + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilMask(255); + gl.stencilFunc(gl.ALWAYS, 1, 255); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.REPLACE); + + this.lineStringReplay.replay(context, + center, resolution, rotation, size, pixelRatio, + opacity, skippedFeaturesHash, + featureCallback, oneByOne, opt_hitExtent); + + gl.stencilMask(0); + gl.stencilFunc(gl.NOTEQUAL, 1, 255); + } + + context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.verticesBuffer); + + 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 + this.shutDownProgram(gl, locations); + + if (this.lineStringReplay) { + if (!tmpStencil) { + gl.disable(gl.STENCIL_TEST); + } + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(/** @type {number} */ (tmpStencilFunc), + /** @type {number} */ (tmpStencilRef), /** @type {number} */ (tmpStencilMaskVal)); + gl.stencilMask(/** @type {number} */ (tmpStencilMask)); + gl.stencilOp(/** @type {number} */ (tmpStencilOpFail), + /** @type {number} */ (tmpStencilOpZFail), /** @type {number} */ (tmpStencilOpPass)); + } + + 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 index cacaf252c1..ad5e2db400 100644 --- a/src/ol/render/webgl/replaygroup.js +++ b/src/ol/render/webgl/replaygroup.js @@ -13,306 +13,311 @@ goog.require('ol.render.webgl.LineStringReplay'); goog.require('ol.render.webgl.PolygonReplay'); goog.require('ol.render.webgl.TextReplay'); -/** - * @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); + +if (ol.ENABLE_WEBGL) { /** - * @type {ol.Extent} - * @private + * @constructor + * @extends {ol.render.ReplayGroup} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Max extent. + * @param {number=} opt_renderBuffer Render buffer. + * @struct */ - this.maxExtent_ = maxExtent; + ol.render.webgl.ReplayGroup = function(tolerance, maxExtent, opt_renderBuffer) { + ol.render.ReplayGroup.call(this); - /** - * @type {number} - * @private - */ - this.tolerance_ = tolerance; + /** + * @type {ol.Extent} + * @private + */ + this.maxExtent_ = maxExtent; - /** - * @type {number|undefined} - * @private - */ - this.renderBuffer_ = opt_renderBuffer; + /** + * @type {number} + * @private + */ + this.tolerance_ = tolerance; - /** - * @private - * @type {!Object.>} - */ - this.replaysByZIndex_ = {}; + /** + * @type {number|undefined} + * @private + */ + this.renderBuffer_ = opt_renderBuffer; -}; -ol.inherits(ol.render.webgl.ReplayGroup, ol.render.ReplayGroup); + /** + * @private + * @type {!Object.>} + */ + this.replaysByZIndex_ = {}; - -/** - * @param {ol.webgl.Context} context WebGL context. - * @return {function()} Delete resources function. - */ -ol.render.webgl.ReplayGroup.prototype.getDeleteResourcesFunction = function(context) { - var functions = []; - var zKey; - for (zKey in this.replaysByZIndex_) { - var replays = this.replaysByZIndex_[zKey]; - var replayKey; - for (replayKey in replays) { - functions.push( - 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; }; -}; + ol.inherits(ol.render.webgl.ReplayGroup, ol.render.ReplayGroup); -/** - * @param {ol.webgl.Context} context Context. - */ -ol.render.webgl.ReplayGroup.prototype.finish = function(context) { - var zKey; - for (zKey in this.replaysByZIndex_) { - var replays = this.replaysByZIndex_[zKey]; - var replayKey; - for (replayKey in replays) { - replays[replayKey].finish(context); - } - } -}; - - -/** - * @inheritDoc - */ -ol.render.webgl.ReplayGroup.prototype.getReplay = function(zIndex, replayType) { - var zIndexKey = zIndex !== undefined ? zIndex.toString() : '0'; - var replays = this.replaysByZIndex_[zIndexKey]; - if (replays === undefined) { - replays = {}; - this.replaysByZIndex_[zIndexKey] = replays; - } - var replay = replays[replayType]; - if (replay === undefined) { - var Constructor = ol.render.webgl.ReplayGroup.BATCH_CONSTRUCTORS_[replayType]; - replay = new Constructor(this.tolerance_, this.maxExtent_); - replays[replayType] = replay; - } - return replay; -}; - - -/** - * @inheritDoc - */ -ol.render.webgl.ReplayGroup.prototype.isEmpty = function() { - return ol.obj.isEmpty(this.replaysByZIndex_); -}; - - -/** - * @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) { - /** @type {Array.} */ - var zs = Object.keys(this.replaysByZIndex_).map(Number); - zs.sort(ol.array.numberSafeCompareFunction); - - var i, ii, j, jj, replays, replay; - for (i = 0, ii = zs.length; i < ii; ++i) { - replays = this.replaysByZIndex_[zs[i].toString()]; - for (j = 0, jj = ol.render.replay.ORDER.length; j < jj; ++j) { - replay = replays[ol.render.replay.ORDER[j]]; - if (replay !== undefined) { - replay.replay(context, - center, resolution, rotation, size, pixelRatio, - opacity, skippedFeaturesHash, - undefined, false); + /** + * @param {ol.webgl.Context} context WebGL context. + * @return {function()} Delete resources function. + */ + ol.render.webgl.ReplayGroup.prototype.getDeleteResourcesFunction = function(context) { + var functions = []; + var zKey; + for (zKey in this.replaysByZIndex_) { + var replays = this.replaysByZIndex_[zKey]; + var replayKey; + for (replayKey in replays) { + functions.push( + 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; + }; + }; -/** - * @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) { - /** @type {Array.} */ - var zs = Object.keys(this.replaysByZIndex_).map(Number); - zs.sort(function(a, b) { - return b - a; - }); + /** + * @param {ol.webgl.Context} context Context. + */ + ol.render.webgl.ReplayGroup.prototype.finish = function(context) { + var zKey; + for (zKey in this.replaysByZIndex_) { + var replays = this.replaysByZIndex_[zKey]; + var replayKey; + for (replayKey in replays) { + replays[replayKey].finish(context); + } + } + }; - var i, ii, j, replays, replay, result; - for (i = 0, ii = zs.length; i < ii; ++i) { - replays = this.replaysByZIndex_[zs[i].toString()]; - for (j = ol.render.replay.ORDER.length - 1; j >= 0; --j) { - replay = replays[ol.render.replay.ORDER[j]]; - if (replay !== undefined) { - result = replay.replay(context, - center, resolution, rotation, size, pixelRatio, opacity, - skippedFeaturesHash, featureCallback, oneByOne, opt_hitExtent); - if (result) { - return result; + + /** + * @inheritDoc + */ + ol.render.webgl.ReplayGroup.prototype.getReplay = function(zIndex, replayType) { + var zIndexKey = zIndex !== undefined ? zIndex.toString() : '0'; + var replays = this.replaysByZIndex_[zIndexKey]; + if (replays === undefined) { + replays = {}; + this.replaysByZIndex_[zIndexKey] = replays; + } + var replay = replays[replayType]; + if (replay === undefined) { + var Constructor = ol.render.webgl.ReplayGroup.BATCH_CONSTRUCTORS_[replayType]; + replay = new Constructor(this.tolerance_, this.maxExtent_); + replays[replayType] = replay; + } + return replay; + }; + + + /** + * @inheritDoc + */ + ol.render.webgl.ReplayGroup.prototype.isEmpty = function() { + return ol.obj.isEmpty(this.replaysByZIndex_); + }; + + + /** + * @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) { + /** @type {Array.} */ + var zs = Object.keys(this.replaysByZIndex_).map(Number); + zs.sort(ol.array.numberSafeCompareFunction); + + var i, ii, j, jj, replays, replay; + for (i = 0, ii = zs.length; i < ii; ++i) { + replays = this.replaysByZIndex_[zs[i].toString()]; + for (j = 0, jj = ol.render.replay.ORDER.length; j < jj; ++j) { + replay = replays[ol.render.replay.ORDER[j]]; + if (replay !== undefined) { + replay.replay(context, + center, resolution, rotation, size, pixelRatio, + opacity, skippedFeaturesHash, + undefined, false); } } } - } - 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} + * @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 */ - 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_); - } + ol.render.webgl.ReplayGroup.prototype.replayHitDetection_ = function(context, + center, resolution, rotation, size, pixelRatio, opacity, + skippedFeaturesHash, featureCallback, oneByOne, opt_hitExtent) { + /** @type {Array.} */ + var zs = Object.keys(this.replaysByZIndex_).map(Number); + zs.sort(function(a, b) { + return b - a; + }); - 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); + var i, ii, j, replays, replay, result; + for (i = 0, ii = zs.length; i < ii; ++i) { + replays = this.replaysByZIndex_[zs[i].toString()]; + for (j = ol.render.replay.ORDER.length - 1; j >= 0; --j) { + replay = replays[ol.render.replay.ORDER[j]]; + if (replay !== undefined) { + result = replay.replay(context, + center, resolution, rotation, size, pixelRatio, opacity, + skippedFeaturesHash, featureCallback, oneByOne, opt_hitExtent); if (result) { return result; } } - }, true, hitExtent); -}; + } + } + 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. - * @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()); + /** + * @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()); - 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; -}; + /** + * @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_); + } -/** - * @const - * @private - * @type {Array.} - */ -ol.render.webgl.ReplayGroup.HIT_DETECTION_SIZE_ = [1, 1]; + 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); -/** - * @const - * @private - * @type {Object.} - */ -ol.render.webgl.ReplayGroup.BATCH_CONSTRUCTORS_ = { - 'Circle': ol.render.webgl.CircleReplay, - 'Image': ol.render.webgl.ImageReplay, - 'LineString': ol.render.webgl.LineStringReplay, - 'Polygon': ol.render.webgl.PolygonReplay, - 'Text': ol.render.webgl.TextReplay -}; + 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_ = { + 'Circle': ol.render.webgl.CircleReplay, + 'Image': ol.render.webgl.ImageReplay, + 'LineString': ol.render.webgl.LineStringReplay, + 'Polygon': ol.render.webgl.PolygonReplay, + 'Text': ol.render.webgl.TextReplay + }; + +} diff --git a/src/ol/render/webgl/textreplay.js b/src/ol/render/webgl/textreplay.js index c9b088c1d3..733e620567 100644 --- a/src/ol/render/webgl/textreplay.js +++ b/src/ol/render/webgl/textreplay.js @@ -2,64 +2,69 @@ goog.provide('ol.render.webgl.TextReplay'); goog.require('ol'); -/** - * @constructor - * @param {number} tolerance Tolerance. - * @param {ol.Extent} maxExtent Max extent. - * @struct - */ -ol.render.webgl.TextReplay = function(tolerance, maxExtent) {}; -/** - * @param {ol.style.Text} textStyle Text style. - */ -ol.render.webgl.TextReplay.prototype.setTextStyle = function(textStyle) {}; +if (ol.ENABLE_WEBGL) { -/** - * @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.TextReplay.prototype.replay = function(context, - center, resolution, rotation, size, pixelRatio, - opacity, skippedFeaturesHash, - featureCallback, oneByOne, opt_hitExtent) { - return undefined; -}; + /** + * @constructor + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Max extent. + * @struct + */ + ol.render.webgl.TextReplay = function(tolerance, maxExtent) {}; -/** - * @param {Array.} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry. - * @param {ol.Feature|ol.render.Feature} feature Feature. - */ -ol.render.webgl.TextReplay.prototype.drawText = function(flatCoordinates, offset, - end, stride, geometry, feature) {}; + /** + * @param {ol.style.Text} textStyle Text style. + */ + ol.render.webgl.TextReplay.prototype.setTextStyle = function(textStyle) {}; -/** - * @abstract - * @param {ol.webgl.Context} context Context. - */ -ol.render.webgl.TextReplay.prototype.finish = function(context) {}; + /** + * @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.TextReplay.prototype.replay = function(context, + center, resolution, rotation, size, pixelRatio, + opacity, skippedFeaturesHash, + featureCallback, oneByOne, opt_hitExtent) { + return undefined; + }; -/** - * @param {ol.webgl.Context} context WebGL context. - * @return {function()} Delete resources function. - */ -ol.render.webgl.TextReplay.prototype.getDeleteResourcesFunction = function(context) { - return ol.nullFunction; -}; + /** + * @param {Array.} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry. + * @param {ol.Feature|ol.render.Feature} feature Feature. + */ + ol.render.webgl.TextReplay.prototype.drawText = function(flatCoordinates, offset, + end, stride, geometry, feature) {}; + + /** + * @abstract + * @param {ol.webgl.Context} context Context. + */ + ol.render.webgl.TextReplay.prototype.finish = function(context) {}; + + /** + * @param {ol.webgl.Context} context WebGL context. + * @return {function()} Delete resources function. + */ + ol.render.webgl.TextReplay.prototype.getDeleteResourcesFunction = function(context) { + return ol.nullFunction; + }; + +}