From 5161a99925662b74de555b88fd195bef40077887 Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Thu, 17 Aug 2017 13:21:53 -0400 Subject: [PATCH 1/2] Remove ol.ENABLE_WEBGL condition from webgl files --- src/ol/render/webgl.js | 154 +- src/ol/render/webgl/circlereplay.js | 730 +++--- .../webgl/circlereplay/defaultshader.js | 252 ++- src/ol/render/webgl/imagereplay.js | 276 ++- src/ol/render/webgl/immediate.js | 722 +++--- src/ol/render/webgl/linestringreplay.js | 1204 +++++----- .../webgl/linestringreplay/defaultshader.js | 262 ++- src/ol/render/webgl/polygonreplay.js | 1970 ++++++++--------- .../webgl/polygonreplay/defaultshader.js | 204 +- src/ol/render/webgl/replay.js | 646 +++--- src/ol/render/webgl/replaygroup.js | 568 +++-- src/ol/render/webgl/textreplay.js | 822 ++++--- src/ol/render/webgl/texturereplay.js | 883 ++++---- .../webgl/texturereplay/defaultshader.js | 232 +- src/ol/renderer/webgl/defaultmapshader.js | 204 +- src/ol/renderer/webgl/imagelayer.js | 578 +++-- src/ol/renderer/webgl/layer.js | 460 ++-- src/ol/renderer/webgl/map.js | 1050 +++++---- src/ol/renderer/webgl/tilelayer.js | 704 +++--- src/ol/renderer/webgl/tilelayershader.js | 192 +- src/ol/renderer/webgl/vectorlayer.js | 600 +++-- src/ol/webgl.js | 418 ++-- src/ol/webgl/buffer.js | 92 +- src/ol/webgl/context.js | 662 +++--- src/ol/webgl/fragment.js | 36 +- src/ol/webgl/shader.js | 56 +- src/ol/webgl/shader.mustache | 188 +- src/ol/webgl/vertex.js | 36 +- 28 files changed, 7045 insertions(+), 7156 deletions(-) diff --git a/src/ol/render/webgl.js b/src/ol/render/webgl.js index 26c10c7401..9595d6203c 100644 --- a/src/ol/render/webgl.js +++ b/src/ol/render/webgl.js @@ -3,103 +3,99 @@ goog.provide('ol.render.webgl'); goog.require('ol'); -if (ol.ENABLE_WEBGL) { - - /** - * @const - * @type {string} - */ - ol.render.webgl.defaultFont = '10px sans-serif'; +/** + * @const + * @type {string} + */ +ol.render.webgl.defaultFont = '10px sans-serif'; - /** - * @const - * @type {ol.Color} - */ - ol.render.webgl.defaultFillStyle = [0.0, 0.0, 0.0, 1.0]; +/** + * @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 {string} + */ +ol.render.webgl.defaultLineCap = 'round'; - /** - * @const - * @type {Array.} - */ - ol.render.webgl.defaultLineDash = []; +/** + * @const + * @type {Array.} + */ +ol.render.webgl.defaultLineDash = []; - /** - * @const - * @type {number} - */ - ol.render.webgl.defaultLineDashOffset = 0; +/** + * @const + * @type {number} + */ +ol.render.webgl.defaultLineDashOffset = 0; - /** - * @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.defaultTextAlign = 0.5; +/** + * @const + * @type {number} + */ +ol.render.webgl.defaultTextAlign = 0.5; - /** - * @const - * @type {number} - */ - ol.render.webgl.defaultTextBaseline = 0.5; +/** + * @const + * @type {number} + */ +ol.render.webgl.defaultTextBaseline = 0.5; - /** - * @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 9abeee1d9c..3b4a7e4e30 100644 --- a/src/ol/render/webgl/circlereplay.js +++ b/src/ol/render/webgl/circlereplay.js @@ -13,412 +13,408 @@ goog.require('ol.webgl'); goog.require('ol.webgl.Buffer'); -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., - * lineDashOffset: (number|undefined), - * lineWidth: (number|undefined), - * changed: boolean}|null} - */ - this.state_ = { - fillColor: null, - strokeColor: null, - lineDash: null, - lineDashOffset: undefined, - lineWidth: undefined, - changed: false - }; - - }; - ol.inherits(ol.render.webgl.CircleReplay, ol.render.webgl.Replay); - +/** + * @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 - * @param {Array.} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. + * @type {ol.render.webgl.circlereplay.defaultshader.Locations} */ - 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; - } - }; - + this.defaultLocations_ = null; /** - * @inheritDoc + * @private + * @type {Array.|number>>} */ - 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.styles_ = []; + + /** + * @private + * @type {Array.} + */ + this.styleIndices_ = []; + + /** + * @private + * @type {number} + */ + this.radius_ = 0; + + /** + * @private + * @type {{fillColor: (Array.|null), + * strokeColor: (Array.|null), + * lineDash: Array., + * lineDashOffset: (number|undefined), + * lineWidth: (number|undefined), + * changed: boolean}|null} + */ + this.state_ = { + fillColor: null, + strokeColor: null, + lineDash: null, + lineDashOffset: undefined, + lineWidth: undefined, + changed: false + }; + +}; +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]); 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.finish = function(context) { - // create, bind, and populate the vertices buffer - this.verticesBuffer = new ol.webgl.Buffer(this.vertices); +/** + * @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); - // create, bind, and populate the indices buffer - this.indicesBuffer = new ol.webgl.Buffer(this.indices); + // get the locations + var locations; + if (!this.defaultLocations_) { + // eslint-disable-next-line openlayers-internal/no-missing-requires + locations = new ol.render.webgl.circlereplay.defaultshader.Locations(gl, program); + this.defaultLocations_ = locations; + } else { + locations = this.defaultLocations_; + } - this.startIndices.push(this.indices.length); + context.useProgram(program); - //Clean up, if there is nothing to draw - if (this.styleIndices_.length === 0 && this.styles_.length > 0) { - this.styles_ = []; - } + // enable the vertex attrib arrays + gl.enableVertexAttribArray(locations.a_position); + gl.vertexAttribPointer(locations.a_position, 2, ol.webgl.FLOAT, + false, 16, 0); - this.vertices = null; - this.indices = null; - }; + 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.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.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.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_) { - // eslint-disable-next-line openlayers-internal/no-missing-requires - 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]; +/** + * @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])); - groupStart = this.styleIndices_[i]; + this.drawElements(gl, context, start, end); + end = start; + } + } +}; - 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.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) { this.drawElements(gl, context, start, end); - - var result = featureCallback(feature); - - if (result) { - return result; - } - } - featureIndex--; - end = start; + end = featureStart; } + featureIndex--; + start = featureStart; } - 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; + if (start !== end) { + this.drawElements(gl, context, start, end); } - }; + 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; - var strokeStyleLineDashOffset = strokeStyle.getLineDashOffset(); - this.state_.lineDashOffset = strokeStyleLineDashOffset ? - strokeStyleLineDashOffset : ol.render.webgl.defaultLineDashOffset; - 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 = [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) { +/** + * @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; + var strokeStyleLineDashOffset = strokeStyle.getLineDashOffset(); + this.state_.lineDashOffset = strokeStyleLineDashOffset ? + strokeStyleLineDashOffset : ol.render.webgl.defaultLineDashOffset; + 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.defaultFillStyle; + }) || ol.render.webgl.defaultStrokeStyle; } else { - fillStyleColor = ol.render.webgl.defaultFillStyle; + strokeStyleColor = ol.render.webgl.defaultStrokeStyle; } - 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]); - } - }; - -} + 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]); + } +}; diff --git a/src/ol/render/webgl/circlereplay/defaultshader.js b/src/ol/render/webgl/circlereplay/defaultshader.js index 6da5615e37..e0f2bafe52 100644 --- a/src/ol/render/webgl/circlereplay/defaultshader.js +++ b/src/ol/render/webgl/circlereplay/defaultshader.js @@ -6,161 +6,157 @@ goog.require('ol'); goog.require('ol.webgl.Fragment'); goog.require('ol.webgl.Vertex'); -if (ol.ENABLE_WEBGL) { +/** + * @constructor + * @extends {ol.webgl.Fragment} + * @struct + */ +ol.render.webgl.circlereplay.defaultshader.Fragment = function() { + ol.webgl.Fragment.call(this, ol.render.webgl.circlereplay.defaultshader.Fragment.SOURCE); +}; +ol.inherits(ol.render.webgl.circlereplay.defaultshader.Fragment, ol.webgl.Fragment); + + +/** + * @const + * @type {string} + */ +ol.render.webgl.circlereplay.defaultshader.Fragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_center;\nvarying vec2 v_offset;\nvarying float v_halfWidth;\nvarying float v_pixelRatio;\n\n\n\nuniform float u_opacity;\nuniform vec4 u_fillColor;\nuniform vec4 u_strokeColor;\nuniform vec2 u_size;\n\nvoid main(void) {\n vec2 windowCenter = vec2((v_center.x + 1.0) / 2.0 * u_size.x * v_pixelRatio,\n (v_center.y + 1.0) / 2.0 * u_size.y * v_pixelRatio);\n vec2 windowOffset = vec2((v_offset.x + 1.0) / 2.0 * u_size.x * v_pixelRatio,\n (v_offset.y + 1.0) / 2.0 * u_size.y * v_pixelRatio);\n float radius = length(windowCenter - windowOffset);\n float dist = length(windowCenter - gl_FragCoord.xy);\n if (dist > radius + v_halfWidth) {\n if (u_strokeColor.a == 0.0) {\n gl_FragColor = u_fillColor;\n } else {\n gl_FragColor = u_strokeColor;\n }\n gl_FragColor.a = gl_FragColor.a - (dist - (radius + v_halfWidth));\n } else if (u_fillColor.a == 0.0) {\n // Hooray, no fill, just stroke. We can use real antialiasing.\n gl_FragColor = u_strokeColor;\n if (dist < radius - v_halfWidth) {\n gl_FragColor.a = gl_FragColor.a - (radius - v_halfWidth - dist);\n }\n } else {\n gl_FragColor = u_fillColor;\n float strokeDist = radius - v_halfWidth;\n float antialias = 2.0 * v_pixelRatio;\n if (dist > strokeDist) {\n gl_FragColor = u_strokeColor;\n } else if (dist >= strokeDist - antialias) {\n float step = smoothstep(strokeDist - antialias, strokeDist, dist);\n gl_FragColor = mix(u_fillColor, u_strokeColor, step);\n }\n }\n gl_FragColor.a = gl_FragColor.a * u_opacity;\n if (gl_FragColor.a <= 0.0) {\n discard;\n }\n}\n'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.circlereplay.defaultshader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;varying vec2 b;varying float c;varying float d;uniform float m;uniform vec4 n;uniform vec4 o;uniform vec2 p;void main(void){vec2 windowCenter=vec2((a.x+1.0)/2.0*p.x*d,(a.y+1.0)/2.0*p.y*d);vec2 windowOffset=vec2((b.x+1.0)/2.0*p.x*d,(b.y+1.0)/2.0*p.y*d);float radius=length(windowCenter-windowOffset);float dist=length(windowCenter-gl_FragCoord.xy);if(dist>radius+c){if(o.a==0.0){gl_FragColor=n;}else{gl_FragColor=o;}gl_FragColor.a=gl_FragColor.a-(dist-(radius+c));}else if(n.a==0.0){gl_FragColor=o;if(diststrokeDist){gl_FragColor=o;}else if(dist>=strokeDist-antialias){float step=smoothstep(strokeDist-antialias,strokeDist,dist);gl_FragColor=mix(n,o,step);}} gl_FragColor.a=gl_FragColor.a*m;if(gl_FragColor.a<=0.0){discard;}}'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.circlereplay.defaultshader.Fragment.SOURCE = ol.DEBUG_WEBGL ? + ol.render.webgl.circlereplay.defaultshader.Fragment.DEBUG_SOURCE : + ol.render.webgl.circlereplay.defaultshader.Fragment.OPTIMIZED_SOURCE; + + +ol.render.webgl.circlereplay.defaultshader.fragment = new ol.render.webgl.circlereplay.defaultshader.Fragment(); + + +/** + * @constructor + * @extends {ol.webgl.Vertex} + * @struct + */ +ol.render.webgl.circlereplay.defaultshader.Vertex = function() { + ol.webgl.Vertex.call(this, ol.render.webgl.circlereplay.defaultshader.Vertex.SOURCE); +}; +ol.inherits(ol.render.webgl.circlereplay.defaultshader.Vertex, ol.webgl.Vertex); + + +/** + * @const + * @type {string} + */ +ol.render.webgl.circlereplay.defaultshader.Vertex.DEBUG_SOURCE = 'varying vec2 v_center;\nvarying vec2 v_offset;\nvarying float v_halfWidth;\nvarying float v_pixelRatio;\n\n\nattribute vec2 a_position;\nattribute float a_instruction;\nattribute float a_radius;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\nuniform float u_lineWidth;\nuniform float u_pixelRatio;\n\nvoid main(void) {\n mat4 offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;\n v_center = vec4(u_projectionMatrix * vec4(a_position, 0.0, 1.0)).xy;\n v_pixelRatio = u_pixelRatio;\n float lineWidth = u_lineWidth * u_pixelRatio;\n v_halfWidth = lineWidth / 2.0;\n if (lineWidth == 0.0) {\n lineWidth = 2.0 * u_pixelRatio;\n }\n vec2 offset;\n // Radius with anitaliasing (roughly).\n float radius = a_radius + 3.0 * u_pixelRatio;\n // Until we get gl_VertexID in WebGL, we store an instruction.\n if (a_instruction == 0.0) {\n // Offsetting the edges of the triangle by lineWidth / 2 is necessary, however\n // we should also leave some space for the antialiasing, thus we offset by lineWidth.\n offset = vec2(-1.0, 1.0);\n } else if (a_instruction == 1.0) {\n offset = vec2(-1.0, -1.0);\n } else if (a_instruction == 2.0) {\n offset = vec2(1.0, -1.0);\n } else {\n offset = vec2(1.0, 1.0);\n }\n\n gl_Position = u_projectionMatrix * vec4(a_position + offset * radius, 0.0, 1.0) +\n offsetMatrix * vec4(offset * lineWidth, 0.0, 0.0);\n v_offset = vec4(u_projectionMatrix * vec4(a_position.x + a_radius, a_position.y,\n 0.0, 1.0)).xy;\n\n if (distance(v_center, v_offset) > 20000.0) {\n gl_Position = vec4(v_center, 0.0, 1.0);\n }\n}\n\n\n'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.circlereplay.defaultshader.Vertex.OPTIMIZED_SOURCE = 'varying vec2 a;varying vec2 b;varying float c;varying float d;attribute vec2 e;attribute float f;attribute float g;uniform mat4 h;uniform mat4 i;uniform mat4 j;uniform float k;uniform float l;void main(void){mat4 offsetMatrix=i*j;a=vec4(h*vec4(e,0.0,1.0)).xy;d=l;float lineWidth=k*l;c=lineWidth/2.0;if(lineWidth==0.0){lineWidth=2.0*l;}vec2 offset;float radius=g+3.0*l;if(f==0.0){offset=vec2(-1.0,1.0);}else if(f==1.0){offset=vec2(-1.0,-1.0);}else if(f==2.0){offset=vec2(1.0,-1.0);}else{offset=vec2(1.0,1.0);}gl_Position=h*vec4(e+offset*radius,0.0,1.0)+offsetMatrix*vec4(offset*lineWidth,0.0,0.0);b=vec4(h*vec4(e.x+g,e.y,0.0,1.0)).xy;if(distance(a,b)>20000.0){gl_Position=vec4(a,0.0,1.0);}}'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.circlereplay.defaultshader.Vertex.SOURCE = ol.DEBUG_WEBGL ? + ol.render.webgl.circlereplay.defaultshader.Vertex.DEBUG_SOURCE : + ol.render.webgl.circlereplay.defaultshader.Vertex.OPTIMIZED_SOURCE; + + +ol.render.webgl.circlereplay.defaultshader.vertex = new ol.render.webgl.circlereplay.defaultshader.Vertex(); + + +/** + * @constructor + * @param {WebGLRenderingContext} gl GL. + * @param {WebGLProgram} program Program. + * @struct + */ +ol.render.webgl.circlereplay.defaultshader.Locations = function(gl, program) { /** - * @constructor - * @extends {ol.webgl.Fragment} - * @struct + * @type {WebGLUniformLocation} */ - ol.render.webgl.circlereplay.defaultshader.Fragment = function() { - ol.webgl.Fragment.call(this, ol.render.webgl.circlereplay.defaultshader.Fragment.SOURCE); - }; - ol.inherits(ol.render.webgl.circlereplay.defaultshader.Fragment, ol.webgl.Fragment); - + this.u_fillColor = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_fillColor' : 'n'); /** - * @const - * @type {string} + * @type {WebGLUniformLocation} */ - ol.render.webgl.circlereplay.defaultshader.Fragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_center;\nvarying vec2 v_offset;\nvarying float v_halfWidth;\nvarying float v_pixelRatio;\n\n\n\nuniform float u_opacity;\nuniform vec4 u_fillColor;\nuniform vec4 u_strokeColor;\nuniform vec2 u_size;\n\nvoid main(void) {\n vec2 windowCenter = vec2((v_center.x + 1.0) / 2.0 * u_size.x * v_pixelRatio,\n (v_center.y + 1.0) / 2.0 * u_size.y * v_pixelRatio);\n vec2 windowOffset = vec2((v_offset.x + 1.0) / 2.0 * u_size.x * v_pixelRatio,\n (v_offset.y + 1.0) / 2.0 * u_size.y * v_pixelRatio);\n float radius = length(windowCenter - windowOffset);\n float dist = length(windowCenter - gl_FragCoord.xy);\n if (dist > radius + v_halfWidth) {\n if (u_strokeColor.a == 0.0) {\n gl_FragColor = u_fillColor;\n } else {\n gl_FragColor = u_strokeColor;\n }\n gl_FragColor.a = gl_FragColor.a - (dist - (radius + v_halfWidth));\n } else if (u_fillColor.a == 0.0) {\n // Hooray, no fill, just stroke. We can use real antialiasing.\n gl_FragColor = u_strokeColor;\n if (dist < radius - v_halfWidth) {\n gl_FragColor.a = gl_FragColor.a - (radius - v_halfWidth - dist);\n }\n } else {\n gl_FragColor = u_fillColor;\n float strokeDist = radius - v_halfWidth;\n float antialias = 2.0 * v_pixelRatio;\n if (dist > strokeDist) {\n gl_FragColor = u_strokeColor;\n } else if (dist >= strokeDist - antialias) {\n float step = smoothstep(strokeDist - antialias, strokeDist, dist);\n gl_FragColor = mix(u_fillColor, u_strokeColor, step);\n }\n }\n gl_FragColor.a = gl_FragColor.a * u_opacity;\n if (gl_FragColor.a <= 0.0) {\n discard;\n }\n}\n'; - + this.u_lineWidth = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_lineWidth' : 'k'); /** - * @const - * @type {string} + * @type {WebGLUniformLocation} */ - ol.render.webgl.circlereplay.defaultshader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;varying vec2 b;varying float c;varying float d;uniform float m;uniform vec4 n;uniform vec4 o;uniform vec2 p;void main(void){vec2 windowCenter=vec2((a.x+1.0)/2.0*p.x*d,(a.y+1.0)/2.0*p.y*d);vec2 windowOffset=vec2((b.x+1.0)/2.0*p.x*d,(b.y+1.0)/2.0*p.y*d);float radius=length(windowCenter-windowOffset);float dist=length(windowCenter-gl_FragCoord.xy);if(dist>radius+c){if(o.a==0.0){gl_FragColor=n;}else{gl_FragColor=o;}gl_FragColor.a=gl_FragColor.a-(dist-(radius+c));}else if(n.a==0.0){gl_FragColor=o;if(diststrokeDist){gl_FragColor=o;}else if(dist>=strokeDist-antialias){float step=smoothstep(strokeDist-antialias,strokeDist,dist);gl_FragColor=mix(n,o,step);}} gl_FragColor.a=gl_FragColor.a*m;if(gl_FragColor.a<=0.0){discard;}}'; - + this.u_offsetRotateMatrix = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_offsetRotateMatrix' : 'j'); /** - * @const - * @type {string} + * @type {WebGLUniformLocation} */ - ol.render.webgl.circlereplay.defaultshader.Fragment.SOURCE = ol.DEBUG_WEBGL ? - ol.render.webgl.circlereplay.defaultshader.Fragment.DEBUG_SOURCE : - ol.render.webgl.circlereplay.defaultshader.Fragment.OPTIMIZED_SOURCE; - - - ol.render.webgl.circlereplay.defaultshader.fragment = new ol.render.webgl.circlereplay.defaultshader.Fragment(); - + this.u_offsetScaleMatrix = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_offsetScaleMatrix' : 'i'); /** - * @constructor - * @extends {ol.webgl.Vertex} - * @struct + * @type {WebGLUniformLocation} */ - ol.render.webgl.circlereplay.defaultshader.Vertex = function() { - ol.webgl.Vertex.call(this, ol.render.webgl.circlereplay.defaultshader.Vertex.SOURCE); - }; - ol.inherits(ol.render.webgl.circlereplay.defaultshader.Vertex, ol.webgl.Vertex); - + this.u_opacity = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_opacity' : 'm'); /** - * @const - * @type {string} + * @type {WebGLUniformLocation} */ - ol.render.webgl.circlereplay.defaultshader.Vertex.DEBUG_SOURCE = 'varying vec2 v_center;\nvarying vec2 v_offset;\nvarying float v_halfWidth;\nvarying float v_pixelRatio;\n\n\nattribute vec2 a_position;\nattribute float a_instruction;\nattribute float a_radius;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\nuniform float u_lineWidth;\nuniform float u_pixelRatio;\n\nvoid main(void) {\n mat4 offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;\n v_center = vec4(u_projectionMatrix * vec4(a_position, 0.0, 1.0)).xy;\n v_pixelRatio = u_pixelRatio;\n float lineWidth = u_lineWidth * u_pixelRatio;\n v_halfWidth = lineWidth / 2.0;\n if (lineWidth == 0.0) {\n lineWidth = 2.0 * u_pixelRatio;\n }\n vec2 offset;\n // Radius with anitaliasing (roughly).\n float radius = a_radius + 3.0 * u_pixelRatio;\n // Until we get gl_VertexID in WebGL, we store an instruction.\n if (a_instruction == 0.0) {\n // Offsetting the edges of the triangle by lineWidth / 2 is necessary, however\n // we should also leave some space for the antialiasing, thus we offset by lineWidth.\n offset = vec2(-1.0, 1.0);\n } else if (a_instruction == 1.0) {\n offset = vec2(-1.0, -1.0);\n } else if (a_instruction == 2.0) {\n offset = vec2(1.0, -1.0);\n } else {\n offset = vec2(1.0, 1.0);\n }\n\n gl_Position = u_projectionMatrix * vec4(a_position + offset * radius, 0.0, 1.0) +\n offsetMatrix * vec4(offset * lineWidth, 0.0, 0.0);\n v_offset = vec4(u_projectionMatrix * vec4(a_position.x + a_radius, a_position.y,\n 0.0, 1.0)).xy;\n\n if (distance(v_center, v_offset) > 20000.0) {\n gl_Position = vec4(v_center, 0.0, 1.0);\n }\n}\n\n\n'; - + this.u_pixelRatio = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_pixelRatio' : 'l'); /** - * @const - * @type {string} + * @type {WebGLUniformLocation} */ - ol.render.webgl.circlereplay.defaultshader.Vertex.OPTIMIZED_SOURCE = 'varying vec2 a;varying vec2 b;varying float c;varying float d;attribute vec2 e;attribute float f;attribute float g;uniform mat4 h;uniform mat4 i;uniform mat4 j;uniform float k;uniform float l;void main(void){mat4 offsetMatrix=i*j;a=vec4(h*vec4(e,0.0,1.0)).xy;d=l;float lineWidth=k*l;c=lineWidth/2.0;if(lineWidth==0.0){lineWidth=2.0*l;}vec2 offset;float radius=g+3.0*l;if(f==0.0){offset=vec2(-1.0,1.0);}else if(f==1.0){offset=vec2(-1.0,-1.0);}else if(f==2.0){offset=vec2(1.0,-1.0);}else{offset=vec2(1.0,1.0);}gl_Position=h*vec4(e+offset*radius,0.0,1.0)+offsetMatrix*vec4(offset*lineWidth,0.0,0.0);b=vec4(h*vec4(e.x+g,e.y,0.0,1.0)).xy;if(distance(a,b)>20000.0){gl_Position=vec4(a,0.0,1.0);}}'; - + this.u_projectionMatrix = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_projectionMatrix' : 'h'); /** - * @const - * @type {string} + * @type {WebGLUniformLocation} */ - ol.render.webgl.circlereplay.defaultshader.Vertex.SOURCE = ol.DEBUG_WEBGL ? - ol.render.webgl.circlereplay.defaultshader.Vertex.DEBUG_SOURCE : - ol.render.webgl.circlereplay.defaultshader.Vertex.OPTIMIZED_SOURCE; - - - ol.render.webgl.circlereplay.defaultshader.vertex = new ol.render.webgl.circlereplay.defaultshader.Vertex(); - + this.u_size = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_size' : 'p'); /** - * @constructor - * @param {WebGLRenderingContext} gl GL. - * @param {WebGLProgram} program Program. - * @struct + * @type {WebGLUniformLocation} */ - ol.render.webgl.circlereplay.defaultshader.Locations = function(gl, program) { + this.u_strokeColor = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_strokeColor' : 'o'); - /** - * @type {WebGLUniformLocation} - */ - this.u_fillColor = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_fillColor' : 'n'); + /** + * @type {number} + */ + this.a_instruction = gl.getAttribLocation( + program, ol.DEBUG_WEBGL ? 'a_instruction' : 'f'); - /** - * @type {WebGLUniformLocation} - */ - this.u_lineWidth = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_lineWidth' : 'k'); + /** + * @type {number} + */ + this.a_position = gl.getAttribLocation( + program, ol.DEBUG_WEBGL ? 'a_position' : 'e'); - /** - * @type {WebGLUniformLocation} - */ - this.u_offsetRotateMatrix = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_offsetRotateMatrix' : 'j'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_offsetScaleMatrix = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_offsetScaleMatrix' : 'i'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_opacity = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_opacity' : 'm'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_pixelRatio = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_pixelRatio' : 'l'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_projectionMatrix = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_projectionMatrix' : 'h'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_size = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_size' : 'p'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_strokeColor = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_strokeColor' : 'o'); - - /** - * @type {number} - */ - this.a_instruction = gl.getAttribLocation( - program, ol.DEBUG_WEBGL ? 'a_instruction' : 'f'); - - /** - * @type {number} - */ - this.a_position = gl.getAttribLocation( - program, ol.DEBUG_WEBGL ? 'a_position' : 'e'); - - /** - * @type {number} - */ - this.a_radius = gl.getAttribLocation( - program, ol.DEBUG_WEBGL ? 'a_radius' : 'g'); - }; - -} + /** + * @type {number} + */ + this.a_radius = gl.getAttribLocation( + program, ol.DEBUG_WEBGL ? 'a_radius' : 'g'); +}; diff --git a/src/ol/render/webgl/imagereplay.js b/src/ol/render/webgl/imagereplay.js index 47f22045ef..8d8341cd09 100644 --- a/src/ol/render/webgl/imagereplay.js +++ b/src/ol/render/webgl/imagereplay.js @@ -5,169 +5,165 @@ goog.require('ol.render.webgl.TextureReplay'); goog.require('ol.webgl.Buffer'); -if (ol.ENABLE_WEBGL) { +/** + * @constructor + * @extends {ol.render.webgl.TextureReplay} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Max extent. + * @struct + */ +ol.render.webgl.ImageReplay = function(tolerance, maxExtent) { + ol.render.webgl.TextureReplay.call(this, tolerance, maxExtent); /** - * @constructor - * @extends {ol.render.webgl.TextureReplay} - * @param {number} tolerance Tolerance. - * @param {ol.Extent} maxExtent Max extent. - * @struct + * @type {Array.} + * @protected */ - ol.render.webgl.ImageReplay = function(tolerance, maxExtent) { - ol.render.webgl.TextureReplay.call(this, tolerance, maxExtent); - - /** - * @type {Array.} - * @protected - */ - this.images_ = []; - - /** - * @type {Array.} - * @protected - */ - this.hitDetectionImages_ = []; - - /** - * @type {Array.} - * @private - */ - this.textures_ = []; - - /** - * @type {Array.} - * @private - */ - this.hitDetectionTextures_ = []; - - }; - ol.inherits(ol.render.webgl.ImageReplay, ol.render.webgl.TextureReplay); - + this.images_ = []; /** - * @inheritDoc + * @type {Array.} + * @protected */ - 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); - }; - + this.hitDetectionImages_ = []; /** - * @inheritDoc + * @type {Array.} + * @private */ - 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); - }; - + this.textures_ = []; /** - * @inheritDoc + * @type {Array.} + * @private */ - ol.render.webgl.ImageReplay.prototype.finish = function(context) { - var gl = context.getGL(); + this.hitDetectionTextures_ = []; - 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.images_ = null; - this.hitDetectionImages_ = null; - ol.render.webgl.TextureReplay.prototype.finish.call(this, context); - }; +}; +ol.inherits(ol.render.webgl.ImageReplay, ol.render.webgl.TextureReplay); - /** - * @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.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); +}; - var currentImage; - if (this.images_.length === 0) { + +/** + * @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.images_ = null; + this.hitDetectionImages_ = null; + ol.render.webgl.TextureReplay.prototype.finish.call(this, context); +}; + + +/** + * @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); - } 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) { + 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); - } 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]; +}; - /** - * @inheritDoc - */ - ol.render.webgl.ImageReplay.prototype.getTextures = function(opt_all) { - return opt_all ? this.textures_.concat(this.hitDetectionTextures_) : this.textures_; - }; +/** + * @inheritDoc + */ +ol.render.webgl.ImageReplay.prototype.getTextures = function(opt_all) { + return opt_all ? this.textures_.concat(this.hitDetectionTextures_) : this.textures_; +}; - /** - * @inheritDoc - */ - ol.render.webgl.ImageReplay.prototype.getHitDetectionTextures = function() { - return this.hitDetectionTextures_; - }; - -} +/** + * @inheritDoc + */ +ol.render.webgl.ImageReplay.prototype.getHitDetectionTextures = function() { + return this.hitDetectionTextures_; +}; diff --git a/src/ol/render/webgl/immediate.js b/src/ol/render/webgl/immediate.js index e040123840..1d512b284b 100644 --- a/src/ol/render/webgl/immediate.js +++ b/src/ol/render/webgl/immediate.js @@ -8,410 +8,406 @@ goog.require('ol.render.VectorContext'); goog.require('ol.render.webgl.ReplayGroup'); -if (ol.ENABLE_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); /** - * @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); - - /** - * @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; - - /** - * @private - * @type {ol.style.Text} - */ - this.textStyle_ = null; - - }; - ol.inherits(ol.render.webgl.Immediate, ol.render.VectorContext); - - - /** - * @param {ol.render.webgl.ReplayGroup} replayGroup Replay group. - * @param {Array.} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. * @private */ - ol.render.webgl.Immediate.prototype.drawText_ = function(replayGroup, - flatCoordinates, offset, end, stride) { - var context = this.context_; - var replay = /** @type {ol.render.webgl.TextReplay} */ ( - replayGroup.getReplay(0, ol.render.ReplayType.TEXT)); - replay.setTextStyle(this.textStyle_); - replay.drawText(flatCoordinates, offset, end, stride, null, null); - 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)(); - }; - + this.context_ = 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. - * @override - * @api + * @private */ - ol.render.webgl.Immediate.prototype.setStyle = function(style) { - this.setFillStrokeStyle(style.getFill(), style.getStroke()); - this.setImageStyle(style.getImage()); - this.setTextStyle(style.getText()); - }; - + this.center_ = center; /** - * 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. - * @override - * @api + * @private */ - 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 - } - }; - + this.extent_ = extent; /** - * @inheritDoc - * @api + * @private */ - 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); - }; - + this.pixelRatio_ = pixelRatio; /** - * @inheritDoc + * @private */ - 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]); - } - }; - + this.size_ = size; /** - * @inheritDoc + * @private */ - 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)(); - - if (this.textStyle_) { - var flatCoordinates = geometry.getFlatCoordinates(); - var stride = geometry.getStride(); - this.drawText_(replayGroup, flatCoordinates, 0, flatCoordinates.length, stride); - } - }; - + this.rotation_ = rotation; /** - * @inheritDoc + * @private */ - 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)(); - - if (this.textStyle_) { - var flatCoordinates = geometry.getFlatCoordinates(); - var stride = geometry.getStride(); - this.drawText_(replayGroup, flatCoordinates, 0, flatCoordinates.length, stride); - } - }; - + this.resolution_ = resolution; /** - * @inheritDoc + * @private + * @type {ol.style.Image} */ - 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)(); - - if (this.textStyle_) { - var flatMidpoint = geometry.getFlatMidpoint(); - this.drawText_(replayGroup, flatMidpoint, 0, 2, 2); - } - }; - + this.imageStyle_ = null; /** - * @inheritDoc + * @private + * @type {ol.style.Fill} */ - 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)(); - - if (this.textStyle_) { - var flatMidpoints = geometry.getFlatMidpoints(); - this.drawText_(replayGroup, flatMidpoints, 0, flatMidpoints.length, 2); - } - }; - + this.fillStyle_ = null; /** - * @inheritDoc + * @private + * @type {ol.style.Stroke} */ - 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)(); - - if (this.textStyle_) { - var flatInteriorPoint = geometry.getFlatInteriorPoint(); - this.drawText_(replayGroup, flatInteriorPoint, 0, 2, 2); - } - }; - + this.strokeStyle_ = null; /** - * @inheritDoc + * @private + * @type {ol.style.Text} */ - 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)(); + this.textStyle_ = null; - if (this.textStyle_) { - var flatInteriorPoints = geometry.getFlatInteriorPoints(); - this.drawText_(replayGroup, flatInteriorPoints, 0, flatInteriorPoints.length, 2); - } - }; +}; +ol.inherits(ol.render.webgl.Immediate, ol.render.VectorContext); - /** - * @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)(); - - if (this.textStyle_) { - this.drawText_(replayGroup, geometry.getCenter(), 0, 2, 2); - } - }; +/** + * @param {ol.render.webgl.ReplayGroup} replayGroup Replay group. + * @param {Array.} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + * @private + */ +ol.render.webgl.Immediate.prototype.drawText_ = function(replayGroup, + flatCoordinates, offset, end, stride) { + var context = this.context_; + var replay = /** @type {ol.render.webgl.TextReplay} */ ( + replayGroup.getReplay(0, ol.render.ReplayType.TEXT)); + replay.setTextStyle(this.textStyle_); + replay.drawText(flatCoordinates, offset, end, stride, null, null); + 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.setImageStyle = function(imageStyle) { - this.imageStyle_ = imageStyle; - }; +/** + * 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. + * @override + * @api + */ +ol.render.webgl.Immediate.prototype.setStyle = function(style) { + this.setFillStrokeStyle(style.getFill(), style.getStroke()); + this.setImageStyle(style.getImage()); + this.setTextStyle(style.getText()); +}; - /** - * @inheritDoc - */ - ol.render.webgl.Immediate.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) { - this.fillStyle_ = fillStyle; - this.strokeStyle_ = strokeStyle; - }; +/** + * 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. + * @override + * @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.setTextStyle = function(textStyle) { - this.textStyle_ = textStyle; - }; +/** + * @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.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.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)(); + + if (this.textStyle_) { + var flatCoordinates = geometry.getFlatCoordinates(); + var stride = geometry.getStride(); + this.drawText_(replayGroup, flatCoordinates, 0, flatCoordinates.length, stride); + } +}; + + +/** + * @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)(); + + if (this.textStyle_) { + var flatCoordinates = geometry.getFlatCoordinates(); + var stride = geometry.getStride(); + this.drawText_(replayGroup, flatCoordinates, 0, flatCoordinates.length, stride); + } +}; + + +/** + * @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)(); + + if (this.textStyle_) { + var flatMidpoint = geometry.getFlatMidpoint(); + this.drawText_(replayGroup, flatMidpoint, 0, 2, 2); + } +}; + + +/** + * @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)(); + + if (this.textStyle_) { + var flatMidpoints = geometry.getFlatMidpoints(); + this.drawText_(replayGroup, flatMidpoints, 0, flatMidpoints.length, 2); + } +}; + + +/** + * @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)(); + + if (this.textStyle_) { + var flatInteriorPoint = geometry.getFlatInteriorPoint(); + this.drawText_(replayGroup, flatInteriorPoint, 0, 2, 2); + } +}; + + +/** + * @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)(); + + if (this.textStyle_) { + var flatInteriorPoints = geometry.getFlatInteriorPoints(); + this.drawText_(replayGroup, flatInteriorPoints, 0, flatInteriorPoints.length, 2); + } +}; + + +/** + * @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)(); + + if (this.textStyle_) { + this.drawText_(replayGroup, geometry.getCenter(), 0, 2, 2); + } +}; + + +/** + * @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; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.Immediate.prototype.setTextStyle = function(textStyle) { + this.textStyle_ = textStyle; +}; diff --git a/src/ol/render/webgl/linestringreplay.js b/src/ol/render/webgl/linestringreplay.js index 2bce1502a8..6781a1a721 100644 --- a/src/ol/render/webgl/linestringreplay.js +++ b/src/ol/render/webgl/linestringreplay.js @@ -15,675 +15,671 @@ goog.require('ol.webgl'); goog.require('ol.webgl.Buffer'); -if (ol.ENABLE_WEBGL) { +/** + * @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); /** - * @constructor - * @extends {ol.render.webgl.Replay} - * @param {number} tolerance Tolerance. - * @param {ol.Extent} maxExtent Max extent. - * @struct - */ - ol.render.webgl.LineStringReplay = function(tolerance, maxExtent) { - ol.render.webgl.Replay.call(this, tolerance, maxExtent); - - /** - * @private - * @type {ol.render.webgl.linestringreplay.defaultshader.Locations} - */ - this.defaultLocations_ = null; - - /** - * @private - * @type {Array.>} - */ - this.styles_ = []; - - /** - * @private - * @type {Array.} - */ - this.styleIndices_ = []; - - /** - * @private - * @type {{strokeColor: (Array.|null), - * lineCap: (string|undefined), - * lineDash: Array., - * lineDashOffset: (number|undefined), - * lineJoin: (string|undefined), - * lineWidth: (number|undefined), - * miterLimit: (number|undefined), - * changed: boolean}|null} - */ - this.state_ = { - strokeColor: null, - lineCap: undefined, - lineDash: null, - lineDashOffset: undefined, - lineJoin: undefined, - lineWidth: undefined, - miterLimit: undefined, - changed: false - }; - - }; - ol.inherits(ol.render.webgl.LineStringReplay, ol.render.webgl.Replay); - - - /** - * Draw one segment. * @private - * @param {Array.} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. + * @type {ol.render.webgl.linestringreplay.defaultshader.Locations} */ - ol.render.webgl.LineStringReplay.prototype.drawCoordinates_ = function(flatCoordinates, offset, end, stride) { + this.defaultLocations_ = null; - 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; + /** + * @private + * @type {Array.>} + */ + this.styles_ = []; - for (i = offset, ii = end; i < ii; i += stride) { + /** + * @private + * @type {Array.} + */ + this.styleIndices_ = []; - n = numVertices / 7; + /** + * @private + * @type {{strokeColor: (Array.|null), + * lineCap: (string|undefined), + * lineDash: Array., + * lineDashOffset: (number|undefined), + * lineJoin: (string|undefined), + * lineWidth: (number|undefined), + * miterLimit: (number|undefined), + * changed: boolean}|null} + */ + this.state_ = { + strokeColor: null, + lineCap: undefined, + lineDash: null, + lineDashOffset: undefined, + lineJoin: undefined, + lineWidth: undefined, + miterLimit: undefined, + changed: false + }; - 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]]; +}; +ol.inherits(ol.render.webgl.LineStringReplay, ol.render.webgl.Replay); - 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); +/** + * 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) { - numVertices = this.addVertices_([0, 0], p1, p2, - -lastSign * ol.render.webgl.LineStringReplay.Instruction_.BEGIN_LINE_CAP * lineCap, numVertices); + 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; - this.indices[numIndices++] = n + 2; - this.indices[numIndices++] = n; - this.indices[numIndices++] = n + 1; + for (i = offset, ii = end; i < ii; i += stride) { - this.indices[numIndices++] = n + 1; - this.indices[numIndices++] = n + 3; - this.indices[numIndices++] = n + 2; + n = numVertices / 7; - } - - 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 { - p2 = [flatCoordinates[i + stride], flatCoordinates[i + stride + 1]]; + 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]]; - // 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; + startCoords = p2; + } else { + //Add the first two/four vertices. - numVertices = this.addVertices_(p0, p1, p2, - sign * ol.render.webgl.LineStringReplay.Instruction_.BEVEL_FIRST * (lineJoin || 1), numVertices); + if (lineCap) { + numVertices = this.addVertices_([0, 0], p1, p2, + lastSign * ol.render.webgl.LineStringReplay.Instruction_.BEGIN_LINE_CAP * lineCap, numVertices); - numVertices = this.addVertices_(p0, p1, p2, - sign * ol.render.webgl.LineStringReplay.Instruction_.BEVEL_SECOND * (lineJoin || 1), numVertices); + numVertices = this.addVertices_([0, 0], p1, p2, + -lastSign * ol.render.webgl.LineStringReplay.Instruction_.BEGIN_LINE_CAP * lineCap, numVertices); - numVertices = this.addVertices_(p0, p1, p2, - -sign * ol.render.webgl.LineStringReplay.Instruction_.MITER_BOTTOM * (lineJoin || 1), numVertices); + this.indices[numIndices++] = n + 2; + this.indices[numIndices++] = n; + this.indices[numIndices++] = n + 1; + + this.indices[numIndices++] = n + 1; + this.indices[numIndices++] = n + 3; + this.indices[numIndices++] = n + 2; + + } + + numVertices = this.addVertices_([0, 0], p1, p2, + lastSign * ol.render.webgl.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); - if (i > offset) { this.indices[numIndices++] = n; this.indices[numIndices++] = lastIndex - 1; this.indices[numIndices++] = lastIndex; - this.indices[numIndices++] = n + 2; - this.indices[numIndices++] = n; - this.indices[numIndices++] = lastSign * sign > 0 ? lastIndex : lastIndex - 1; - } - - this.indices[numIndices++] = n; - this.indices[numIndices++] = n + 2; - this.indices[numIndices++] = n + 1; - - lastIndex = n + 2; - lastSign = sign; - - //Add miter - if (lineJoin) { - numVertices = this.addVertices_(p0, p1, p2, - sign * ol.render.webgl.LineStringReplay.Instruction_.MITER_TOP * lineJoin, numVertices); - + this.indices[numIndices++] = lastIndex; 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]]; } - 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; + // 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, 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_.MITER_BOTTOM * (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++] = n + 1; + this.indices[numIndices++] = n + 2; 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; + this.indices[numIndices++] = n; + this.indices[numIndices++] = n + 2; + this.indices[numIndices++] = n + 1; - return numVertices; - }; + lastIndex = n + 2; + lastSign = sign; - /** - * 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); + //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; } + } - return true; - }; + 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; +}; - /** - * @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 ends = multiLineStringGeometry.getEnds(); - ends.unshift(0); - var flatCoordinates = multiLineStringGeometry.getFlatCoordinates(); - var stride = multiLineStringGeometry.getStride(); - var i, ii; - if (ends.length > 1) { - for (i = 1, ii = ends.length; i < ii; ++i) { - if (this.isValid_(flatCoordinates, ends[i - 1], ends[i], stride)) { - var lineString = ol.geom.flat.transform.translate(flatCoordinates, ends[i - 1], ends[i], - stride, -this.origin[0], -this.origin[1]); - this.drawCoordinates_( - lineString, 0, lineString.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); +/** + * @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(index); + this.styleIndices_.push(this.indices.length); 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; - }; + this.startIndicesFeature.push(feature); + this.drawCoordinates_( + flatCoordinates, 0, flatCoordinates.length, stride); + } +}; - /** - * @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_) { - // eslint-disable-next-line openlayers-internal/no-missing-requires - 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; +/** + * @inheritDoc + */ +ol.render.webgl.LineStringReplay.prototype.drawMultiLineString = function(multiLineStringGeometry, feature) { + var indexCount = this.indices.length; + var ends = multiLineStringGeometry.getEnds(); + ends.unshift(0); + var flatCoordinates = multiLineStringGeometry.getFlatCoordinates(); + var stride = multiLineStringGeometry.getStride(); + var i, ii; + if (ends.length > 1) { + for (i = 1, ii = ends.length; i < ii; ++i) { + if (this.isValid_(flatCoordinates, ends[i - 1], ends[i], stride)) { + var lineString = ol.geom.flat.transform.translate(flatCoordinates, ends[i - 1], ends[i], + stride, -this.origin[0], -this.origin[1]); + this.drawCoordinates_( + lineString, 0, lineString.length, stride); } } - if (!hitDetection) { - gl.disable(gl.DEPTH_TEST); + } + 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(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_) { + // eslint-disable-next-line openlayers-internal/no-missing-requires + 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); - //Restore GL parameters. - gl.depthMask(tmpDepthMask); - gl.depthFunc(tmpDepthFunc); + 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) { - nextStyle = this.styles_[i]; - this.setStrokeStyle_(gl, nextStyle[0], nextStyle[1], nextStyle[2]); - groupStart = this.styleIndices_[i]; +/** + * @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(); + 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; - } - }; - - - /** - * @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 (skippedFeaturesHash[featureUid]) { + 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; + end = featureStart; } + featureIndex--; + start = featureStart; } - return undefined; - }; - - - /** - * @private - * @param {WebGLRenderingContext} gl gl. - * @param {Array.} color Color. - * @param {number} lineWidth Line width. - * @param {number} miterLimit Miter limit. - */ - ol.render.webgl.LineStringReplay.prototype.setStrokeStyle_ = function(gl, color, lineWidth, miterLimit) { - gl.uniform4fv(this.defaultLocations_.u_color, color); - gl.uniform1f(this.defaultLocations_.u_lineWidth, lineWidth); - gl.uniform1f(this.defaultLocations_.u_miterLimit, miterLimit); - }; - - - /** - * @inheritDoc - */ - ol.render.webgl.LineStringReplay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) { - 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 strokeStyleLineDashOffset = strokeStyle.getLineDashOffset(); - this.state_.lineDashOffset = strokeStyleLineDashOffset ? - strokeStyleLineDashOffset : ol.render.webgl.defaultLineDashOffset; - 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; + if (start !== end) { + this.drawElements(gl, context, start, end); + gl.clear(gl.DEPTH_BUFFER_BIT); } - 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]); + 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); + this.drawElements(gl, context, start, end); + + var result = featureCallback(feature); + + if (result) { + return result; + } + + } + featureIndex--; + end = start; } - }; + } + return undefined; +}; - /** - * @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 - }; -} +/** + * @private + * @param {WebGLRenderingContext} gl gl. + * @param {Array.} color Color. + * @param {number} lineWidth Line width. + * @param {number} miterLimit Miter limit. + */ +ol.render.webgl.LineStringReplay.prototype.setStrokeStyle_ = function(gl, color, lineWidth, miterLimit) { + gl.uniform4fv(this.defaultLocations_.u_color, color); + gl.uniform1f(this.defaultLocations_.u_lineWidth, lineWidth); + gl.uniform1f(this.defaultLocations_.u_miterLimit, miterLimit); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.LineStringReplay.prototype.setFillStrokeStyle = function(fillStyle, strokeStyle) { + 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 strokeStyleLineDashOffset = strokeStyle.getLineDashOffset(); + this.state_.lineDashOffset = strokeStyleLineDashOffset ? + strokeStyleLineDashOffset : ol.render.webgl.defaultLineDashOffset; + 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/linestringreplay/defaultshader.js b/src/ol/render/webgl/linestringreplay/defaultshader.js index d355e0194c..55ed64504d 100644 --- a/src/ol/render/webgl/linestringreplay/defaultshader.js +++ b/src/ol/render/webgl/linestringreplay/defaultshader.js @@ -6,167 +6,163 @@ goog.require('ol'); goog.require('ol.webgl.Fragment'); goog.require('ol.webgl.Vertex'); -if (ol.ENABLE_WEBGL) { +/** + * @constructor + * @extends {ol.webgl.Fragment} + * @struct + */ +ol.render.webgl.linestringreplay.defaultshader.Fragment = function() { + ol.webgl.Fragment.call(this, ol.render.webgl.linestringreplay.defaultshader.Fragment.SOURCE); +}; +ol.inherits(ol.render.webgl.linestringreplay.defaultshader.Fragment, ol.webgl.Fragment); + + +/** + * @const + * @type {string} + */ +ol.render.webgl.linestringreplay.defaultshader.Fragment.DEBUG_SOURCE = 'precision mediump float;\nvarying float v_round;\nvarying vec2 v_roundVertex;\nvarying float v_halfWidth;\n\n\n\nuniform float u_opacity;\nuniform vec4 u_color;\nuniform vec2 u_size;\nuniform float u_pixelRatio;\n\nvoid main(void) {\n if (v_round > 0.0) {\n vec2 windowCoords = vec2((v_roundVertex.x + 1.0) / 2.0 * u_size.x * u_pixelRatio,\n (v_roundVertex.y + 1.0) / 2.0 * u_size.y * u_pixelRatio);\n if (length(windowCoords - gl_FragCoord.xy) > v_halfWidth * u_pixelRatio) {\n discard;\n }\n }\n gl_FragColor = u_color;\n float alpha = u_color.a * u_opacity;\n if (alpha == 0.0) {\n discard;\n }\n gl_FragColor.a = alpha;\n}\n'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.linestringreplay.defaultshader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;varying float a;varying vec2 b;varying float c;uniform float m;uniform vec4 n;uniform vec2 o;uniform float p;void main(void){if(a>0.0){vec2 windowCoords=vec2((b.x+1.0)/2.0*o.x*p,(b.y+1.0)/2.0*o.y*p);if(length(windowCoords-gl_FragCoord.xy)>c*p){discard;}} gl_FragColor=n;float alpha=n.a*m;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.linestringreplay.defaultshader.Fragment.SOURCE = ol.DEBUG_WEBGL ? + ol.render.webgl.linestringreplay.defaultshader.Fragment.DEBUG_SOURCE : + ol.render.webgl.linestringreplay.defaultshader.Fragment.OPTIMIZED_SOURCE; + + +ol.render.webgl.linestringreplay.defaultshader.fragment = new ol.render.webgl.linestringreplay.defaultshader.Fragment(); + + +/** + * @constructor + * @extends {ol.webgl.Vertex} + * @struct + */ +ol.render.webgl.linestringreplay.defaultshader.Vertex = function() { + ol.webgl.Vertex.call(this, ol.render.webgl.linestringreplay.defaultshader.Vertex.SOURCE); +}; +ol.inherits(ol.render.webgl.linestringreplay.defaultshader.Vertex, ol.webgl.Vertex); + + +/** + * @const + * @type {string} + */ +ol.render.webgl.linestringreplay.defaultshader.Vertex.DEBUG_SOURCE = 'varying float v_round;\nvarying vec2 v_roundVertex;\nvarying float v_halfWidth;\n\n\nattribute vec2 a_lastPos;\nattribute vec2 a_position;\nattribute vec2 a_nextPos;\nattribute float a_direction;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\nuniform float u_lineWidth;\nuniform float u_miterLimit;\n\nbool nearlyEquals(in float value, in float ref) {\n float epsilon = 0.000000000001;\n return value >= ref - epsilon && value <= ref + epsilon;\n}\n\nvoid alongNormal(out vec2 offset, in vec2 nextP, in float turnDir, in float direction) {\n vec2 dirVect = nextP - a_position;\n vec2 normal = normalize(vec2(-turnDir * dirVect.y, turnDir * dirVect.x));\n offset = u_lineWidth / 2.0 * normal * direction;\n}\n\nvoid miterUp(out vec2 offset, out float round, in bool isRound, in float direction) {\n float halfWidth = u_lineWidth / 2.0;\n vec2 tangent = normalize(normalize(a_nextPos - a_position) + normalize(a_position - a_lastPos));\n vec2 normal = vec2(-tangent.y, tangent.x);\n vec2 dirVect = a_nextPos - a_position;\n vec2 tmpNormal = normalize(vec2(-dirVect.y, dirVect.x));\n float miterLength = abs(halfWidth / dot(normal, tmpNormal));\n offset = normal * direction * miterLength;\n round = 0.0;\n if (isRound) {\n round = 1.0;\n } else if (miterLength > u_miterLimit + u_lineWidth) {\n offset = halfWidth * tmpNormal * direction;\n }\n}\n\nbool miterDown(out vec2 offset, in vec4 projPos, in mat4 offsetMatrix, in float direction) {\n bool degenerate = false;\n vec2 tangent = normalize(normalize(a_nextPos - a_position) + normalize(a_position - a_lastPos));\n vec2 normal = vec2(-tangent.y, tangent.x);\n vec2 dirVect = a_lastPos - a_position;\n vec2 tmpNormal = normalize(vec2(-dirVect.y, dirVect.x));\n vec2 longOffset, shortOffset, longVertex;\n vec4 shortProjVertex;\n float halfWidth = u_lineWidth / 2.0;\n if (length(a_nextPos - a_position) > length(a_lastPos - a_position)) {\n longOffset = tmpNormal * direction * halfWidth;\n shortOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * halfWidth;\n longVertex = a_nextPos;\n shortProjVertex = u_projectionMatrix * vec4(a_lastPos, 0.0, 1.0);\n } else {\n shortOffset = tmpNormal * direction * halfWidth;\n longOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * halfWidth;\n longVertex = a_lastPos;\n shortProjVertex = u_projectionMatrix * vec4(a_nextPos, 0.0, 1.0);\n }\n //Intersection algorithm based on theory by Paul Bourke (http://paulbourke.net/geometry/pointlineplane/).\n vec4 p1 = u_projectionMatrix * vec4(longVertex, 0.0, 1.0) + offsetMatrix * vec4(longOffset, 0.0, 0.0);\n vec4 p2 = projPos + offsetMatrix * vec4(longOffset, 0.0, 0.0);\n vec4 p3 = shortProjVertex + offsetMatrix * vec4(-shortOffset, 0.0, 0.0);\n vec4 p4 = shortProjVertex + offsetMatrix * vec4(shortOffset, 0.0, 0.0);\n float denom = (p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y);\n float firstU = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x)) / denom;\n float secondU = ((p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x)) / denom;\n float epsilon = 0.000000000001;\n if (firstU > epsilon && firstU < 1.0 - epsilon && secondU > epsilon && secondU < 1.0 - epsilon) {\n shortProjVertex.x = p1.x + firstU * (p2.x - p1.x);\n shortProjVertex.y = p1.y + firstU * (p2.y - p1.y);\n offset = shortProjVertex.xy;\n degenerate = true;\n } else {\n float miterLength = abs(halfWidth / dot(normal, tmpNormal));\n offset = normal * direction * miterLength;\n }\n return degenerate;\n}\n\nvoid squareCap(out vec2 offset, out float round, in bool isRound, in vec2 nextP,\n in float turnDir, in float direction) {\n round = 0.0;\n vec2 dirVect = a_position - nextP;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(turnDir * firstNormal.y * direction, -turnDir * firstNormal.x * direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n vec2 normal = vec2(turnDir * hypotenuse.y * direction, -turnDir * hypotenuse.x * direction);\n float length = sqrt(v_halfWidth * v_halfWidth * 2.0);\n offset = normal * length;\n if (isRound) {\n round = 1.0;\n }\n}\n\nvoid main(void) {\n bool degenerate = false;\n float direction = float(sign(a_direction));\n mat4 offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;\n vec2 offset;\n vec4 projPos = u_projectionMatrix * vec4(a_position, 0.0, 1.0);\n bool round = nearlyEquals(mod(a_direction, 2.0), 0.0);\n\n v_round = 0.0;\n v_halfWidth = u_lineWidth / 2.0;\n v_roundVertex = projPos.xy;\n\n if (nearlyEquals(mod(a_direction, 3.0), 0.0) || nearlyEquals(mod(a_direction, 17.0), 0.0)) {\n alongNormal(offset, a_nextPos, 1.0, direction);\n } else if (nearlyEquals(mod(a_direction, 5.0), 0.0) || nearlyEquals(mod(a_direction, 13.0), 0.0)) {\n alongNormal(offset, a_lastPos, -1.0, direction);\n } else if (nearlyEquals(mod(a_direction, 23.0), 0.0)) {\n miterUp(offset, v_round, round, direction);\n } else if (nearlyEquals(mod(a_direction, 19.0), 0.0)) {\n degenerate = miterDown(offset, projPos, offsetMatrix, direction);\n } else if (nearlyEquals(mod(a_direction, 7.0), 0.0)) {\n squareCap(offset, v_round, round, a_nextPos, 1.0, direction);\n } else if (nearlyEquals(mod(a_direction, 11.0), 0.0)) {\n squareCap(offset, v_round, round, a_lastPos, -1.0, direction);\n }\n if (!degenerate) {\n vec4 offsets = offsetMatrix * vec4(offset, 0.0, 0.0);\n gl_Position = projPos + offsets;\n } else {\n gl_Position = vec4(offset, 0.0, 1.0);\n }\n}\n\n\n'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.linestringreplay.defaultshader.Vertex.OPTIMIZED_SOURCE = 'varying float a;varying vec2 b;varying float c;attribute vec2 d;attribute vec2 e;attribute vec2 f;attribute float g;uniform mat4 h;uniform mat4 i;uniform mat4 j;uniform float k;uniform float l;bool nearlyEquals(in float value,in float ref){float epsilon=0.000000000001;return value>=ref-epsilon&&value<=ref+epsilon;}void alongNormal(out vec2 offset,in vec2 nextP,in float turnDir,in float direction){vec2 dirVect=nextP-e;vec2 normal=normalize(vec2(-turnDir*dirVect.y,turnDir*dirVect.x));offset=k/2.0*normal*direction;}void miterUp(out vec2 offset,out float round,in bool isRound,in float direction){float halfWidth=k/2.0;vec2 tangent=normalize(normalize(f-e)+normalize(e-d));vec2 normal=vec2(-tangent.y,tangent.x);vec2 dirVect=f-e;vec2 tmpNormal=normalize(vec2(-dirVect.y,dirVect.x));float miterLength=abs(halfWidth/dot(normal,tmpNormal));offset=normal*direction*miterLength;round=0.0;if(isRound){round=1.0;}else if(miterLength>l+k){offset=halfWidth*tmpNormal*direction;}} bool miterDown(out vec2 offset,in vec4 projPos,in mat4 offsetMatrix,in float direction){bool degenerate=false;vec2 tangent=normalize(normalize(f-e)+normalize(e-d));vec2 normal=vec2(-tangent.y,tangent.x);vec2 dirVect=d-e;vec2 tmpNormal=normalize(vec2(-dirVect.y,dirVect.x));vec2 longOffset,shortOffset,longVertex;vec4 shortProjVertex;float halfWidth=k/2.0;if(length(f-e)>length(d-e)){longOffset=tmpNormal*direction*halfWidth;shortOffset=normalize(vec2(dirVect.y,-dirVect.x))*direction*halfWidth;longVertex=f;shortProjVertex=h*vec4(d,0.0,1.0);}else{shortOffset=tmpNormal*direction*halfWidth;longOffset=normalize(vec2(dirVect.y,-dirVect.x))*direction*halfWidth;longVertex=d;shortProjVertex=h*vec4(f,0.0,1.0);}vec4 p1=h*vec4(longVertex,0.0,1.0)+offsetMatrix*vec4(longOffset,0.0,0.0);vec4 p2=projPos+offsetMatrix*vec4(longOffset,0.0,0.0);vec4 p3=shortProjVertex+offsetMatrix*vec4(-shortOffset,0.0,0.0);vec4 p4=shortProjVertex+offsetMatrix*vec4(shortOffset,0.0,0.0);float denom=(p4.y-p3.y)*(p2.x-p1.x)-(p4.x-p3.x)*(p2.y-p1.y);float firstU=((p4.x-p3.x)*(p1.y-p3.y)-(p4.y-p3.y)*(p1.x-p3.x))/denom;float secondU=((p2.x-p1.x)*(p1.y-p3.y)-(p2.y-p1.y)*(p1.x-p3.x))/denom;float epsilon=0.000000000001;if(firstU>epsilon&&firstU<1.0-epsilon&&secondU>epsilon&&secondU<1.0-epsilon){shortProjVertex.x=p1.x+firstU*(p2.x-p1.x);shortProjVertex.y=p1.y+firstU*(p2.y-p1.y);offset=shortProjVertex.xy;degenerate=true;}else{float miterLength=abs(halfWidth/dot(normal,tmpNormal));offset=normal*direction*miterLength;}return degenerate;}void squareCap(out vec2 offset,out float round,in bool isRound,in vec2 nextP,in float turnDir,in float direction){round=0.0;vec2 dirVect=e-nextP;vec2 firstNormal=normalize(dirVect);vec2 secondNormal=vec2(turnDir*firstNormal.y*direction,-turnDir*firstNormal.x*direction);vec2 hypotenuse=normalize(firstNormal-secondNormal);vec2 normal=vec2(turnDir*hypotenuse.y*direction,-turnDir*hypotenuse.x*direction);float length=sqrt(c*c*2.0);offset=normal*length;if(isRound){round=1.0;}} void main(void){bool degenerate=false;float direction=float(sign(g));mat4 offsetMatrix=i*j;vec2 offset;vec4 projPos=h*vec4(e,0.0,1.0);bool round=nearlyEquals(mod(g,2.0),0.0);a=0.0;c=k/2.0;b=projPos.xy;if(nearlyEquals(mod(g,3.0),0.0)||nearlyEquals(mod(g,17.0),0.0)){alongNormal(offset,f,1.0,direction);}else if(nearlyEquals(mod(g,5.0),0.0)||nearlyEquals(mod(g,13.0),0.0)){alongNormal(offset,d,-1.0,direction);}else if(nearlyEquals(mod(g,23.0),0.0)){miterUp(offset,a,round,direction);}else if(nearlyEquals(mod(g,19.0),0.0)){degenerate=miterDown(offset,projPos,offsetMatrix,direction);}else if(nearlyEquals(mod(g,7.0),0.0)){squareCap(offset,a,round,f,1.0,direction);}else if(nearlyEquals(mod(g,11.0),0.0)){squareCap(offset,a,round,d,-1.0,direction);}if(!degenerate){vec4 offsets=offsetMatrix*vec4(offset,0.0,0.0);gl_Position=projPos+offsets;}else{gl_Position=vec4(offset,0.0,1.0);}}'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.linestringreplay.defaultshader.Vertex.SOURCE = ol.DEBUG_WEBGL ? + ol.render.webgl.linestringreplay.defaultshader.Vertex.DEBUG_SOURCE : + ol.render.webgl.linestringreplay.defaultshader.Vertex.OPTIMIZED_SOURCE; + + +ol.render.webgl.linestringreplay.defaultshader.vertex = new ol.render.webgl.linestringreplay.defaultshader.Vertex(); + + +/** + * @constructor + * @param {WebGLRenderingContext} gl GL. + * @param {WebGLProgram} program Program. + * @struct + */ +ol.render.webgl.linestringreplay.defaultshader.Locations = function(gl, program) { /** - * @constructor - * @extends {ol.webgl.Fragment} - * @struct + * @type {WebGLUniformLocation} */ - ol.render.webgl.linestringreplay.defaultshader.Fragment = function() { - ol.webgl.Fragment.call(this, ol.render.webgl.linestringreplay.defaultshader.Fragment.SOURCE); - }; - ol.inherits(ol.render.webgl.linestringreplay.defaultshader.Fragment, ol.webgl.Fragment); - + this.u_color = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_color' : 'n'); /** - * @const - * @type {string} + * @type {WebGLUniformLocation} */ - ol.render.webgl.linestringreplay.defaultshader.Fragment.DEBUG_SOURCE = 'precision mediump float;\nvarying float v_round;\nvarying vec2 v_roundVertex;\nvarying float v_halfWidth;\n\n\n\nuniform float u_opacity;\nuniform vec4 u_color;\nuniform vec2 u_size;\nuniform float u_pixelRatio;\n\nvoid main(void) {\n if (v_round > 0.0) {\n vec2 windowCoords = vec2((v_roundVertex.x + 1.0) / 2.0 * u_size.x * u_pixelRatio,\n (v_roundVertex.y + 1.0) / 2.0 * u_size.y * u_pixelRatio);\n if (length(windowCoords - gl_FragCoord.xy) > v_halfWidth * u_pixelRatio) {\n discard;\n }\n }\n gl_FragColor = u_color;\n float alpha = u_color.a * u_opacity;\n if (alpha == 0.0) {\n discard;\n }\n gl_FragColor.a = alpha;\n}\n'; - + this.u_lineWidth = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_lineWidth' : 'k'); /** - * @const - * @type {string} + * @type {WebGLUniformLocation} */ - ol.render.webgl.linestringreplay.defaultshader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;varying float a;varying vec2 b;varying float c;uniform float m;uniform vec4 n;uniform vec2 o;uniform float p;void main(void){if(a>0.0){vec2 windowCoords=vec2((b.x+1.0)/2.0*o.x*p,(b.y+1.0)/2.0*o.y*p);if(length(windowCoords-gl_FragCoord.xy)>c*p){discard;}} gl_FragColor=n;float alpha=n.a*m;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}'; - + this.u_miterLimit = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_miterLimit' : 'l'); /** - * @const - * @type {string} + * @type {WebGLUniformLocation} */ - ol.render.webgl.linestringreplay.defaultshader.Fragment.SOURCE = ol.DEBUG_WEBGL ? - ol.render.webgl.linestringreplay.defaultshader.Fragment.DEBUG_SOURCE : - ol.render.webgl.linestringreplay.defaultshader.Fragment.OPTIMIZED_SOURCE; - - - ol.render.webgl.linestringreplay.defaultshader.fragment = new ol.render.webgl.linestringreplay.defaultshader.Fragment(); - + this.u_offsetRotateMatrix = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_offsetRotateMatrix' : 'j'); /** - * @constructor - * @extends {ol.webgl.Vertex} - * @struct + * @type {WebGLUniformLocation} */ - ol.render.webgl.linestringreplay.defaultshader.Vertex = function() { - ol.webgl.Vertex.call(this, ol.render.webgl.linestringreplay.defaultshader.Vertex.SOURCE); - }; - ol.inherits(ol.render.webgl.linestringreplay.defaultshader.Vertex, ol.webgl.Vertex); - + this.u_offsetScaleMatrix = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_offsetScaleMatrix' : 'i'); /** - * @const - * @type {string} + * @type {WebGLUniformLocation} */ - ol.render.webgl.linestringreplay.defaultshader.Vertex.DEBUG_SOURCE = 'varying float v_round;\nvarying vec2 v_roundVertex;\nvarying float v_halfWidth;\n\n\nattribute vec2 a_lastPos;\nattribute vec2 a_position;\nattribute vec2 a_nextPos;\nattribute float a_direction;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\nuniform float u_lineWidth;\nuniform float u_miterLimit;\n\nbool nearlyEquals(in float value, in float ref) {\n float epsilon = 0.000000000001;\n return value >= ref - epsilon && value <= ref + epsilon;\n}\n\nvoid alongNormal(out vec2 offset, in vec2 nextP, in float turnDir, in float direction) {\n vec2 dirVect = nextP - a_position;\n vec2 normal = normalize(vec2(-turnDir * dirVect.y, turnDir * dirVect.x));\n offset = u_lineWidth / 2.0 * normal * direction;\n}\n\nvoid miterUp(out vec2 offset, out float round, in bool isRound, in float direction) {\n float halfWidth = u_lineWidth / 2.0;\n vec2 tangent = normalize(normalize(a_nextPos - a_position) + normalize(a_position - a_lastPos));\n vec2 normal = vec2(-tangent.y, tangent.x);\n vec2 dirVect = a_nextPos - a_position;\n vec2 tmpNormal = normalize(vec2(-dirVect.y, dirVect.x));\n float miterLength = abs(halfWidth / dot(normal, tmpNormal));\n offset = normal * direction * miterLength;\n round = 0.0;\n if (isRound) {\n round = 1.0;\n } else if (miterLength > u_miterLimit + u_lineWidth) {\n offset = halfWidth * tmpNormal * direction;\n }\n}\n\nbool miterDown(out vec2 offset, in vec4 projPos, in mat4 offsetMatrix, in float direction) {\n bool degenerate = false;\n vec2 tangent = normalize(normalize(a_nextPos - a_position) + normalize(a_position - a_lastPos));\n vec2 normal = vec2(-tangent.y, tangent.x);\n vec2 dirVect = a_lastPos - a_position;\n vec2 tmpNormal = normalize(vec2(-dirVect.y, dirVect.x));\n vec2 longOffset, shortOffset, longVertex;\n vec4 shortProjVertex;\n float halfWidth = u_lineWidth / 2.0;\n if (length(a_nextPos - a_position) > length(a_lastPos - a_position)) {\n longOffset = tmpNormal * direction * halfWidth;\n shortOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * halfWidth;\n longVertex = a_nextPos;\n shortProjVertex = u_projectionMatrix * vec4(a_lastPos, 0.0, 1.0);\n } else {\n shortOffset = tmpNormal * direction * halfWidth;\n longOffset = normalize(vec2(dirVect.y, -dirVect.x)) * direction * halfWidth;\n longVertex = a_lastPos;\n shortProjVertex = u_projectionMatrix * vec4(a_nextPos, 0.0, 1.0);\n }\n //Intersection algorithm based on theory by Paul Bourke (http://paulbourke.net/geometry/pointlineplane/).\n vec4 p1 = u_projectionMatrix * vec4(longVertex, 0.0, 1.0) + offsetMatrix * vec4(longOffset, 0.0, 0.0);\n vec4 p2 = projPos + offsetMatrix * vec4(longOffset, 0.0, 0.0);\n vec4 p3 = shortProjVertex + offsetMatrix * vec4(-shortOffset, 0.0, 0.0);\n vec4 p4 = shortProjVertex + offsetMatrix * vec4(shortOffset, 0.0, 0.0);\n float denom = (p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y);\n float firstU = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x)) / denom;\n float secondU = ((p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x)) / denom;\n float epsilon = 0.000000000001;\n if (firstU > epsilon && firstU < 1.0 - epsilon && secondU > epsilon && secondU < 1.0 - epsilon) {\n shortProjVertex.x = p1.x + firstU * (p2.x - p1.x);\n shortProjVertex.y = p1.y + firstU * (p2.y - p1.y);\n offset = shortProjVertex.xy;\n degenerate = true;\n } else {\n float miterLength = abs(halfWidth / dot(normal, tmpNormal));\n offset = normal * direction * miterLength;\n }\n return degenerate;\n}\n\nvoid squareCap(out vec2 offset, out float round, in bool isRound, in vec2 nextP,\n in float turnDir, in float direction) {\n round = 0.0;\n vec2 dirVect = a_position - nextP;\n vec2 firstNormal = normalize(dirVect);\n vec2 secondNormal = vec2(turnDir * firstNormal.y * direction, -turnDir * firstNormal.x * direction);\n vec2 hypotenuse = normalize(firstNormal - secondNormal);\n vec2 normal = vec2(turnDir * hypotenuse.y * direction, -turnDir * hypotenuse.x * direction);\n float length = sqrt(v_halfWidth * v_halfWidth * 2.0);\n offset = normal * length;\n if (isRound) {\n round = 1.0;\n }\n}\n\nvoid main(void) {\n bool degenerate = false;\n float direction = float(sign(a_direction));\n mat4 offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;\n vec2 offset;\n vec4 projPos = u_projectionMatrix * vec4(a_position, 0.0, 1.0);\n bool round = nearlyEquals(mod(a_direction, 2.0), 0.0);\n\n v_round = 0.0;\n v_halfWidth = u_lineWidth / 2.0;\n v_roundVertex = projPos.xy;\n\n if (nearlyEquals(mod(a_direction, 3.0), 0.0) || nearlyEquals(mod(a_direction, 17.0), 0.0)) {\n alongNormal(offset, a_nextPos, 1.0, direction);\n } else if (nearlyEquals(mod(a_direction, 5.0), 0.0) || nearlyEquals(mod(a_direction, 13.0), 0.0)) {\n alongNormal(offset, a_lastPos, -1.0, direction);\n } else if (nearlyEquals(mod(a_direction, 23.0), 0.0)) {\n miterUp(offset, v_round, round, direction);\n } else if (nearlyEquals(mod(a_direction, 19.0), 0.0)) {\n degenerate = miterDown(offset, projPos, offsetMatrix, direction);\n } else if (nearlyEquals(mod(a_direction, 7.0), 0.0)) {\n squareCap(offset, v_round, round, a_nextPos, 1.0, direction);\n } else if (nearlyEquals(mod(a_direction, 11.0), 0.0)) {\n squareCap(offset, v_round, round, a_lastPos, -1.0, direction);\n }\n if (!degenerate) {\n vec4 offsets = offsetMatrix * vec4(offset, 0.0, 0.0);\n gl_Position = projPos + offsets;\n } else {\n gl_Position = vec4(offset, 0.0, 1.0);\n }\n}\n\n\n'; - + this.u_opacity = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_opacity' : 'm'); /** - * @const - * @type {string} + * @type {WebGLUniformLocation} */ - ol.render.webgl.linestringreplay.defaultshader.Vertex.OPTIMIZED_SOURCE = 'varying float a;varying vec2 b;varying float c;attribute vec2 d;attribute vec2 e;attribute vec2 f;attribute float g;uniform mat4 h;uniform mat4 i;uniform mat4 j;uniform float k;uniform float l;bool nearlyEquals(in float value,in float ref){float epsilon=0.000000000001;return value>=ref-epsilon&&value<=ref+epsilon;}void alongNormal(out vec2 offset,in vec2 nextP,in float turnDir,in float direction){vec2 dirVect=nextP-e;vec2 normal=normalize(vec2(-turnDir*dirVect.y,turnDir*dirVect.x));offset=k/2.0*normal*direction;}void miterUp(out vec2 offset,out float round,in bool isRound,in float direction){float halfWidth=k/2.0;vec2 tangent=normalize(normalize(f-e)+normalize(e-d));vec2 normal=vec2(-tangent.y,tangent.x);vec2 dirVect=f-e;vec2 tmpNormal=normalize(vec2(-dirVect.y,dirVect.x));float miterLength=abs(halfWidth/dot(normal,tmpNormal));offset=normal*direction*miterLength;round=0.0;if(isRound){round=1.0;}else if(miterLength>l+k){offset=halfWidth*tmpNormal*direction;}} bool miterDown(out vec2 offset,in vec4 projPos,in mat4 offsetMatrix,in float direction){bool degenerate=false;vec2 tangent=normalize(normalize(f-e)+normalize(e-d));vec2 normal=vec2(-tangent.y,tangent.x);vec2 dirVect=d-e;vec2 tmpNormal=normalize(vec2(-dirVect.y,dirVect.x));vec2 longOffset,shortOffset,longVertex;vec4 shortProjVertex;float halfWidth=k/2.0;if(length(f-e)>length(d-e)){longOffset=tmpNormal*direction*halfWidth;shortOffset=normalize(vec2(dirVect.y,-dirVect.x))*direction*halfWidth;longVertex=f;shortProjVertex=h*vec4(d,0.0,1.0);}else{shortOffset=tmpNormal*direction*halfWidth;longOffset=normalize(vec2(dirVect.y,-dirVect.x))*direction*halfWidth;longVertex=d;shortProjVertex=h*vec4(f,0.0,1.0);}vec4 p1=h*vec4(longVertex,0.0,1.0)+offsetMatrix*vec4(longOffset,0.0,0.0);vec4 p2=projPos+offsetMatrix*vec4(longOffset,0.0,0.0);vec4 p3=shortProjVertex+offsetMatrix*vec4(-shortOffset,0.0,0.0);vec4 p4=shortProjVertex+offsetMatrix*vec4(shortOffset,0.0,0.0);float denom=(p4.y-p3.y)*(p2.x-p1.x)-(p4.x-p3.x)*(p2.y-p1.y);float firstU=((p4.x-p3.x)*(p1.y-p3.y)-(p4.y-p3.y)*(p1.x-p3.x))/denom;float secondU=((p2.x-p1.x)*(p1.y-p3.y)-(p2.y-p1.y)*(p1.x-p3.x))/denom;float epsilon=0.000000000001;if(firstU>epsilon&&firstU<1.0-epsilon&&secondU>epsilon&&secondU<1.0-epsilon){shortProjVertex.x=p1.x+firstU*(p2.x-p1.x);shortProjVertex.y=p1.y+firstU*(p2.y-p1.y);offset=shortProjVertex.xy;degenerate=true;}else{float miterLength=abs(halfWidth/dot(normal,tmpNormal));offset=normal*direction*miterLength;}return degenerate;}void squareCap(out vec2 offset,out float round,in bool isRound,in vec2 nextP,in float turnDir,in float direction){round=0.0;vec2 dirVect=e-nextP;vec2 firstNormal=normalize(dirVect);vec2 secondNormal=vec2(turnDir*firstNormal.y*direction,-turnDir*firstNormal.x*direction);vec2 hypotenuse=normalize(firstNormal-secondNormal);vec2 normal=vec2(turnDir*hypotenuse.y*direction,-turnDir*hypotenuse.x*direction);float length=sqrt(c*c*2.0);offset=normal*length;if(isRound){round=1.0;}} void main(void){bool degenerate=false;float direction=float(sign(g));mat4 offsetMatrix=i*j;vec2 offset;vec4 projPos=h*vec4(e,0.0,1.0);bool round=nearlyEquals(mod(g,2.0),0.0);a=0.0;c=k/2.0;b=projPos.xy;if(nearlyEquals(mod(g,3.0),0.0)||nearlyEquals(mod(g,17.0),0.0)){alongNormal(offset,f,1.0,direction);}else if(nearlyEquals(mod(g,5.0),0.0)||nearlyEquals(mod(g,13.0),0.0)){alongNormal(offset,d,-1.0,direction);}else if(nearlyEquals(mod(g,23.0),0.0)){miterUp(offset,a,round,direction);}else if(nearlyEquals(mod(g,19.0),0.0)){degenerate=miterDown(offset,projPos,offsetMatrix,direction);}else if(nearlyEquals(mod(g,7.0),0.0)){squareCap(offset,a,round,f,1.0,direction);}else if(nearlyEquals(mod(g,11.0),0.0)){squareCap(offset,a,round,d,-1.0,direction);}if(!degenerate){vec4 offsets=offsetMatrix*vec4(offset,0.0,0.0);gl_Position=projPos+offsets;}else{gl_Position=vec4(offset,0.0,1.0);}}'; - + this.u_pixelRatio = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_pixelRatio' : 'p'); /** - * @const - * @type {string} + * @type {WebGLUniformLocation} */ - ol.render.webgl.linestringreplay.defaultshader.Vertex.SOURCE = ol.DEBUG_WEBGL ? - ol.render.webgl.linestringreplay.defaultshader.Vertex.DEBUG_SOURCE : - ol.render.webgl.linestringreplay.defaultshader.Vertex.OPTIMIZED_SOURCE; - - - ol.render.webgl.linestringreplay.defaultshader.vertex = new ol.render.webgl.linestringreplay.defaultshader.Vertex(); - + this.u_projectionMatrix = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_projectionMatrix' : 'h'); /** - * @constructor - * @param {WebGLRenderingContext} gl GL. - * @param {WebGLProgram} program Program. - * @struct + * @type {WebGLUniformLocation} */ - ol.render.webgl.linestringreplay.defaultshader.Locations = function(gl, program) { + this.u_size = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_size' : 'o'); - /** - * @type {WebGLUniformLocation} - */ - this.u_color = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_color' : 'n'); + /** + * @type {number} + */ + this.a_direction = gl.getAttribLocation( + program, ol.DEBUG_WEBGL ? 'a_direction' : 'g'); - /** - * @type {WebGLUniformLocation} - */ - this.u_lineWidth = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_lineWidth' : 'k'); + /** + * @type {number} + */ + this.a_lastPos = gl.getAttribLocation( + program, ol.DEBUG_WEBGL ? 'a_lastPos' : 'd'); - /** - * @type {WebGLUniformLocation} - */ - this.u_miterLimit = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_miterLimit' : 'l'); + /** + * @type {number} + */ + this.a_nextPos = gl.getAttribLocation( + program, ol.DEBUG_WEBGL ? 'a_nextPos' : 'f'); - /** - * @type {WebGLUniformLocation} - */ - this.u_offsetRotateMatrix = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_offsetRotateMatrix' : 'j'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_offsetScaleMatrix = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_offsetScaleMatrix' : 'i'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_opacity = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_opacity' : 'm'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_pixelRatio = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_pixelRatio' : 'p'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_projectionMatrix = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_projectionMatrix' : 'h'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_size = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_size' : 'o'); - - /** - * @type {number} - */ - this.a_direction = gl.getAttribLocation( - program, ol.DEBUG_WEBGL ? 'a_direction' : 'g'); - - /** - * @type {number} - */ - this.a_lastPos = gl.getAttribLocation( - program, ol.DEBUG_WEBGL ? 'a_lastPos' : 'd'); - - /** - * @type {number} - */ - this.a_nextPos = gl.getAttribLocation( - program, ol.DEBUG_WEBGL ? 'a_nextPos' : 'f'); - - /** - * @type {number} - */ - this.a_position = gl.getAttribLocation( - program, ol.DEBUG_WEBGL ? 'a_position' : 'e'); - }; - -} + /** + * @type {number} + */ + this.a_position = gl.getAttribLocation( + program, ol.DEBUG_WEBGL ? 'a_position' : 'e'); +}; diff --git a/src/ol/render/webgl/polygonreplay.js b/src/ol/render/webgl/polygonreplay.js index 0b78dd22b2..39c151a529 100644 --- a/src/ol/render/webgl/polygonreplay.js +++ b/src/ol/render/webgl/polygonreplay.js @@ -19,1057 +19,1053 @@ goog.require('ol.webgl'); goog.require('ol.webgl.Buffer'); -if (ol.ENABLE_WEBGL) { +/** + * @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); /** - * @constructor - * @extends {ol.render.webgl.Replay} - * @param {number} tolerance Tolerance. - * @param {ol.Extent} maxExtent Max extent. - * @struct - */ - ol.render.webgl.PolygonReplay = function(tolerance, maxExtent) { - ol.render.webgl.Replay.call(this, tolerance, maxExtent); - - this.lineStringReplay = new ol.render.webgl.LineStringReplay( - tolerance, maxExtent); - - /** - * @private - * @type {ol.render.webgl.polygonreplay.defaultshader.Locations} - */ - this.defaultLocations_ = null; - - /** - * @private - * @type {Array.>} - */ - this.styles_ = []; - - /** - * @private - * @type {Array.} - */ - this.styleIndices_ = []; - - /** - * @private - * @type {{fillColor: (Array.|null), - * changed: boolean}|null} - */ - this.state_ = { - fillColor: null, - changed: false - }; - - }; - ol.inherits(ol.render.webgl.PolygonReplay, ol.render.webgl.Replay); - - - /** - * Draw one polygon. - * @param {Array.} flatCoordinates Flat coordinates. - * @param {Array.>} holeFlatCoordinates Hole flat coordinates. - * @param {number} stride Stride. * @private + * @type {ol.render.webgl.polygonreplay.defaultshader.Locations} */ - 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 - this.processFlatCoordinates_(flatCoordinates, stride, outerRing, rtree, true); - var maxCoords = this.getMaxCoords_(outerRing); + this.defaultLocations_ = null; - // 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(), - maxCoords: undefined, - rtree: new ol.structs.RBush() - }; - holeLists.push(holeList); - this.processFlatCoordinates_(holeFlatCoordinates[i], - stride, holeList.list, holeList.rtree, false); - this.classifyPoints_(holeList.list, holeList.rtree, true); - holeList.maxCoords = this.getMaxCoords_(holeList.list); - } - holeLists.sort(function(a, b) { - return b.maxCoords[0] === a.maxCoords[0] ? - a.maxCoords[1] - b.maxCoords[1] : b.maxCoords[0] - a.maxCoords[0]; - }); - for (i = 0; i < holeLists.length; ++i) { - var currList = holeLists[i].list; - var start = currList.firstItem(); - var currItem = start; - var intersection; - do { - //TODO: Triangulate holes when they intersect the outer ring. - if (this.getIntersections_(currItem, rtree).length) { - intersection = true; - break; - } - currItem = currList.nextItem(); - } while (start !== currItem); - if (!intersection) { - if (this.bridgeHole_(currList, holeLists[i].maxCoords[0], outerRing, maxCoords[0], rtree)) { - rtree.concat(holeLists[i].rtree); - this.classifyPoints_(outerRing, rtree, false); - } - } - } - } else { - this.classifyPoints_(outerRing, rtree, false); - } - this.triangulate_(outerRing, rtree); + /** + * @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); - /** - * 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. - */ - ol.render.webgl.PolygonReplay.prototype.processFlatCoordinates_ = function( - flatCoordinates, stride, list, rtree, clockwise) { - var isClockwise = ol.geom.flat.orient.linearRingIsClockwise(flatCoordinates, - 0, flatCoordinates.length, stride); + +/** + * 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 + this.processFlatCoordinates_(flatCoordinates, stride, outerRing, rtree, true); + var maxCoords = this.getMaxCoords_(outerRing); + + // Eliminate holes, if there are any + if (holeFlatCoordinates.length) { var i, ii; - 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; - 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)]); - 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; - 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)]); - 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)]); + var holeLists = []; + for (i = 0, ii = holeFlatCoordinates.length; i < ii; ++i) { + var holeList = { + list: new ol.structs.LinkedList(), + maxCoords: undefined, + rtree: new ol.structs.RBush() + }; + holeLists.push(holeList); + this.processFlatCoordinates_(holeFlatCoordinates[i], + stride, holeList.list, holeList.rtree, false); + this.classifyPoints_(holeList.list, holeList.rtree, true); + holeList.maxCoords = this.getMaxCoords_(holeList.list); } - rtree.load(extents, segments); - }; - - - /** - * Returns the rightmost coordinates of a polygon on the X axis. - * @private - * @param {ol.structs.LinkedList} list Polygons ring. - * @return {Array.} Max X coordinates. - */ - ol.render.webgl.PolygonReplay.prototype.getMaxCoords_ = function(list) { - var start = list.firstItem(); - var seg = start; - var maxCoords = [seg.p0.x, seg.p0.y]; - - do { - seg = list.nextItem(); - if (seg.p0.x > maxCoords[0]) { - maxCoords = [seg.p0.x, seg.p0.y]; - } - } while (seg !== start); - - return maxCoords; - }; - - - /** - * Classifies the points of a polygon list as convex, reflex. Removes collinear vertices. - * @private - * @param {ol.structs.LinkedList} list Polygon ring. - * @param {ol.structs.RBush} rtree R-Tree of the polygon. - * @param {boolean} ccw The orientation of the polygon is counter-clockwise. - * @return {boolean} There were reclassified points. - */ - ol.render.webgl.PolygonReplay.prototype.classifyPoints_ = function(list, rtree, ccw) { - var start = list.firstItem(); - var s0 = start; - var s1 = list.nextItem(); - var pointsReclassified = false; - do { - var reflex = ccw ? ol.render.webgl.triangleIsCounterClockwise(s1.p1.x, - s1.p1.y, s0.p1.x, s0.p1.y, s0.p0.x, s0.p0.y) : - ol.render.webgl.triangleIsCounterClockwise(s0.p0.x, s0.p0.y, s0.p1.x, - s0.p1.y, s1.p1.x, s1.p1.y); - if (reflex === undefined) { - this.removeItem_(s0, s1, list, rtree); - pointsReclassified = true; - if (s1 === start) { - start = list.getNextItem(); - } - s1 = s0; - list.prevItem(); - } else if (s0.p1.reflex !== reflex) { - s0.p1.reflex = reflex; - pointsReclassified = true; - } - s0 = s1; - s1 = list.nextItem(); - } while (s0 !== start); - return pointsReclassified; - }; - - - /** - * @private - * @param {ol.structs.LinkedList} hole Linked list of the hole. - * @param {number} holeMaxX Maximum X value of the hole. - * @param {ol.structs.LinkedList} list Linked list of the polygon. - * @param {number} listMaxX Maximum X value of the polygon. - * @param {ol.structs.RBush} rtree R-Tree of the polygon. - * @return {boolean} Bridging was successful. - */ - ol.render.webgl.PolygonReplay.prototype.bridgeHole_ = function(hole, holeMaxX, - list, listMaxX, rtree) { - var seg = hole.firstItem(); - while (seg.p1.x !== holeMaxX) { - seg = hole.nextItem(); - } - - var p1 = seg.p1; - /** @type {ol.WebglPolygonVertex} */ - var p2 = {x: listMaxX, y: p1.y, i: -1}; - var minDist = Infinity; - var i, ii, bestPoint; - /** @type {ol.WebglPolygonVertex} */ - var p5; - - var intersectingSegments = this.getIntersections_({p0: p1, p1: p2}, rtree, true); - for (i = 0, ii = intersectingSegments.length; i < ii; ++i) { - var currSeg = intersectingSegments[i]; - var intersection = this.calculateIntersection_(p1, p2, currSeg.p0, - currSeg.p1, true); - var dist = Math.abs(p1.x - intersection[0]); - if (dist < minDist && ol.render.webgl.triangleIsCounterClockwise(p1.x, p1.y, - currSeg.p0.x, currSeg.p0.y, currSeg.p1.x, currSeg.p1.y) !== undefined) { - minDist = dist; - p5 = {x: intersection[0], y: intersection[1], i: -1}; - seg = currSeg; - } - } - if (minDist === Infinity) { - return false; - } - 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; - } - } - } - } - - seg = list.firstItem(); - while (seg.p1.x !== bestPoint.x || seg.p1.y !== bestPoint.y) { - 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); - - return true; - }; - - - /** - * @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.resolveSelfIntersections_(list, rtree, true)) { - break; - } - } - } - } else { - if (!this.clipEars_(list, rtree, simple, ccw)) { - // We ran out of ears, try to reclassify. - if (!this.classifyPoints_(list, rtree, ccw)) { - // We have a bad polygon, try to resolve local self-intersections. - if (!this.resolveSelfIntersections_(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 variableCriterion; - if (simple) { - variableCriterion = this.getPointsInTriangle_(p0, p1, p2, rtree, true).length === 0; - } else { - variableCriterion = 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) && - variableCriterion) { - //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.resolveSelfIntersections_ = function( - list, rtree, opt_touch) { - var start = list.firstItem(); - list.nextItem(); - var s0 = start; - var s1 = list.nextItem(); - var resolvedIntersections = false; - - do { - var intersection = this.calculateIntersection_(s0.p0, s0.p1, s1.p0, s1.p1, - opt_touch); - if (intersection) { - var breakCond = false; - var numVertices = this.vertices.length; - var numIndices = this.indices.length; - var n = numVertices / 2; - var seg = list.prevItem(); - list.removeItem(); - rtree.remove(seg); - breakCond = (seg === start); - var p; - if (opt_touch) { - if (intersection[0] === s0.p0.x && intersection[1] === s0.p0.y) { - list.prevItem(); - p = s0.p0; - s1.p0 = p; - rtree.remove(s0); - breakCond = breakCond || (s0 === start); - } else { - p = s1.p1; - s0.p1 = p; - rtree.remove(s1); - breakCond = breakCond || (s1 === start); - } - list.removeItem(); - } else { - p = this.createPoint_(intersection[0], intersection[1], n); - s0.p1 = p; - s1.p0 = p; - rtree.update([Math.min(s0.p0.x, s0.p1.x), Math.min(s0.p0.y, s0.p1.y), - Math.max(s0.p0.x, s0.p1.x), Math.max(s0.p0.y, s0.p1.y)], s0); - rtree.update([Math.min(s1.p0.x, s1.p1.x), Math.min(s1.p0.y, s1.p1.y), - Math.max(s1.p0.x, s1.p1.x), Math.max(s1.p0.y, s1.p1.y)], s1); - } - - this.indices[numIndices++] = seg.p0.i; - this.indices[numIndices++] = seg.p1.i; - this.indices[numIndices++] = p.i; - - resolvedIntersections = true; - if (breakCond) { + holeLists.sort(function(a, b) { + return b.maxCoords[0] === a.maxCoords[0] ? + a.maxCoords[1] - b.maxCoords[1] : b.maxCoords[0] - a.maxCoords[0]; + }); + for (i = 0; i < holeLists.length; ++i) { + var currList = holeLists[i].list; + var start = currList.firstItem(); + var currItem = start; + var intersection; + do { + //TODO: Triangulate holes when they intersect the outer ring. + if (this.getIntersections_(currItem, rtree).length) { + intersection = true; 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(); + currItem = currList.nextItem(); + } while (start !== currItem); + if (!intersection) { + if (this.bridgeHole_(currList, holeLists[i].maxCoords[0], outerRing, maxCoords[0], rtree)) { + rtree.concat(holeLists[i].rtree); + this.classifyPoints_(outerRing, rtree, false); } - this.insertItem_(s1.p0, p, newPolygon, newRtree); - s1.p0 = p; - rtree.update([Math.min(s1.p1.x, p.x), Math.min(s1.p1.y, p.y), - Math.max(s1.p1.x, p.x), Math.max(s1.p1.y, p.y)], s1); - this.classifyPoints_(list, rtree, false); - this.triangulate_(list, rtree); - this.classifyPoints_(newPolygon, newRtree, false); - this.triangulate_(newPolygon, newRtree); - break; } - s0 = list.nextItem(); - } while (s0 !== start); - }; - - - /** - * @private - * @param {number} x X coordinate. - * @param {number} y Y coordinate. - * @param {number} i Index. - * @return {ol.WebglPolygonVertex} List item. - */ - ol.render.webgl.PolygonReplay.prototype.createPoint_ = function(x, y, i) { - var numVertices = this.vertices.length; - this.vertices[numVertices++] = x; - this.vertices[numVertices++] = y; - /** @type {ol.WebglPolygonVertex} */ - var p = { - x: x, - y: y, - i: i, - reflex: undefined - }; - return p; - }; - - - /** - * @private - * @param {ol.WebglPolygonVertex} p0 First point of segment. - * @param {ol.WebglPolygonVertex} p1 Second point of segment. - * @param {ol.structs.LinkedList} list Polygon ring. - * @param {ol.structs.RBush=} opt_rtree Insert the segment into the R-Tree. - * @return {ol.WebglPolygonSegment} segment. - */ - ol.render.webgl.PolygonReplay.prototype.insertItem_ = function(p0, p1, list, opt_rtree) { - var seg = { - p0: p0, - p1: p1 - }; - list.insertItem(seg); - if (opt_rtree) { - opt_rtree.insert([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y), - Math.max(p0.x, p1.x), Math.max(p0.y, p1.y)], seg); } - return seg; - }; + } else { + this.classifyPoints_(outerRing, rtree, false); + } + this.triangulate_(outerRing, rtree); +}; - /** - * @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); +/** + * 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. + */ +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; + 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; + 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)]); + 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; + 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)]); + 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); +}; - /** - * @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); +/** + * Returns the rightmost coordinates of a polygon on the X axis. + * @private + * @param {ol.structs.LinkedList} list Polygons ring. + * @return {Array.} Max X coordinates. + */ +ol.render.webgl.PolygonReplay.prototype.getMaxCoords_ = function(list) { + var start = list.firstItem(); + var seg = start; + var maxCoords = [seg.p0.x, seg.p0.y]; + + do { + seg = list.nextItem(); + if (seg.p0.x > maxCoords[0]) { + maxCoords = [seg.p0.x, seg.p0.y]; + } + } while (seg !== start); + + return maxCoords; +}; + + +/** + * Classifies the points of a polygon list as convex, reflex. Removes collinear vertices. + * @private + * @param {ol.structs.LinkedList} list Polygon ring. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + * @param {boolean} ccw The orientation of the polygon is counter-clockwise. + * @return {boolean} There were reclassified points. + */ +ol.render.webgl.PolygonReplay.prototype.classifyPoints_ = function(list, rtree, ccw) { + var start = list.firstItem(); + var s0 = start; + var s1 = list.nextItem(); + var pointsReclassified = false; + do { + var reflex = ccw ? ol.render.webgl.triangleIsCounterClockwise(s1.p1.x, + s1.p1.y, s0.p1.x, s0.p1.y, s0.p0.x, s0.p0.y) : + ol.render.webgl.triangleIsCounterClockwise(s0.p0.x, s0.p0.y, s0.p1.x, + s0.p1.y, s1.p1.x, s1.p1.y); + if (reflex === undefined) { + this.removeItem_(s0, s1, list, rtree); + pointsReclassified = true; + if (s1 === start) { + start = list.getNextItem(); + } + s1 = s0; + list.prevItem(); + } else if (s0.p1.reflex !== reflex) { + s0.p1.reflex = reflex; + pointsReclassified = true; + } + s0 = s1; + s1 = list.nextItem(); + } while (s0 !== start); + return pointsReclassified; +}; + + +/** + * @private + * @param {ol.structs.LinkedList} hole Linked list of the hole. + * @param {number} holeMaxX Maximum X value of the hole. + * @param {ol.structs.LinkedList} list Linked list of the polygon. + * @param {number} listMaxX Maximum X value of the polygon. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + * @return {boolean} Bridging was successful. + */ +ol.render.webgl.PolygonReplay.prototype.bridgeHole_ = function(hole, holeMaxX, + list, listMaxX, rtree) { + var seg = hole.firstItem(); + while (seg.p1.x !== holeMaxX) { + seg = hole.nextItem(); + } + + var p1 = seg.p1; + /** @type {ol.WebglPolygonVertex} */ + var p2 = {x: listMaxX, y: p1.y, i: -1}; + var minDist = Infinity; + var i, ii, bestPoint; + /** @type {ol.WebglPolygonVertex} */ + var p5; + + var intersectingSegments = this.getIntersections_({p0: p1, p1: p2}, rtree, true); + for (i = 0, ii = intersectingSegments.length; i < ii; ++i) { + var currSeg = intersectingSegments[i]; + var intersection = this.calculateIntersection_(p1, p2, currSeg.p0, + currSeg.p1, true); + var dist = Math.abs(p1.x - intersection[0]); + if (dist < minDist && ol.render.webgl.triangleIsCounterClockwise(p1.x, p1.y, + currSeg.p0.x, currSeg.p0.y, currSeg.p1.x, currSeg.p1.y) !== undefined) { + minDist = dist; + p5 = {x: intersection[0], y: intersection[1], i: -1}; + seg = currSeg; + } + } + if (minDist === Infinity) { + return false; + } + 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; + } + } + } + } + + seg = list.firstItem(); + while (seg.p1.x !== bestPoint.x || seg.p1.y !== bestPoint.y) { + 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); + + return true; +}; + + +/** + * @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.resolveSelfIntersections_(list, rtree, true)) { + break; } } } - } - return result; - }; - - - /** - * @private - * @param {ol.WebglPolygonSegment} segment Segment. - * @param {ol.structs.RBush} rtree R-Tree of the polygon. - * @param {boolean=} opt_touch Touching segments should be considered an intersection. - * @return {Array.} Intersecting segments. - */ - ol.render.webgl.PolygonReplay.prototype.getIntersections_ = function(segment, rtree, opt_touch) { - var p0 = segment.p0; - var p1 = segment.p1; - var segmentsInExtent = rtree.getInExtent([Math.min(p0.x, p1.x), - Math.min(p0.y, p1.y), Math.max(p0.x, p1.x), Math.max(p0.y, p1.y)]); - var result = []; - var i, ii; - for (i = 0, ii = segmentsInExtent.length; i < ii; ++i) { - var currSeg = segmentsInExtent[i]; - if (segment !== currSeg && (opt_touch || currSeg.p0 !== p1 || currSeg.p1 !== p0) && - this.calculateIntersection_(p0, p1, currSeg.p0, currSeg.p1, opt_touch)) { - result.push(currSeg); - } - } - return result; - }; - - - /** - * Line intersection algorithm by Paul Bourke. - * @see http://paulbourke.net/geometry/pointlineplane/ - * - * @private - * @param {ol.WebglPolygonVertex} p0 First point. - * @param {ol.WebglPolygonVertex} p1 Second point. - * @param {ol.WebglPolygonVertex} p2 Third point. - * @param {ol.WebglPolygonVertex} p3 Fourth point. - * @param {boolean=} opt_touch Touching segments should be considered an intersection. - * @return {Array.|undefined} Intersection coordinates. - */ - ol.render.webgl.PolygonReplay.prototype.calculateIntersection_ = function(p0, - p1, p2, p3, opt_touch) { - var denom = (p3.y - p2.y) * (p1.x - p0.x) - (p3.x - p2.x) * (p1.y - p0.y); - if (denom !== 0) { - var ua = ((p3.x - p2.x) * (p0.y - p2.y) - (p3.y - p2.y) * (p0.x - p2.x)) / denom; - var ub = ((p1.x - p0.x) * (p0.y - p2.y) - (p1.y - p0.y) * (p0.x - p2.x)) / denom; - if ((!opt_touch && ua > ol.render.webgl.EPSILON && ua < 1 - ol.render.webgl.EPSILON && - ub > ol.render.webgl.EPSILON && ub < 1 - ol.render.webgl.EPSILON) || (opt_touch && - ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1)) { - return [p0.x + ua * (p1.x - p0.x), p0.y + ua * (p1.y - p0.y)]; - } - } - return undefined; - }; - - - /** - * @private - * @param {ol.WebglPolygonVertex} p0 Point before the start of the diagonal. - * @param {ol.WebglPolygonVertex} p1 Start point of the diagonal. - * @param {ol.WebglPolygonVertex} p2 Ear candidate. - * @param {ol.WebglPolygonVertex} p3 End point of the diagonal. - * @param {ol.WebglPolygonVertex} p4 Point after the end of the diagonal. - * @return {boolean} Diagonal is inside the polygon. - */ - ol.render.webgl.PolygonReplay.prototype.diagonalIsInside_ = function(p0, p1, p2, p3, p4) { - if (p1.reflex === undefined || p3.reflex === undefined) { - return false; - } - var p1IsLeftOf = (p2.x - p3.x) * (p1.y - p3.y) > (p2.y - p3.y) * (p1.x - p3.x); - var p1IsRightOf = (p4.x - p3.x) * (p1.y - p3.y) < (p4.y - p3.y) * (p1.x - p3.x); - var p3IsLeftOf = (p0.x - p1.x) * (p3.y - p1.y) > (p0.y - p1.y) * (p3.x - p1.x); - var p3IsRightOf = (p2.x - p1.x) * (p3.y - p1.y) < (p2.y - p1.y) * (p3.x - p1.x); - var p1InCone = p3.reflex ? p1IsRightOf || p1IsLeftOf : p1IsRightOf && p1IsLeftOf; - var p3InCone = p1.reflex ? p3IsRightOf || p3IsLeftOf : p3IsRightOf && p3IsLeftOf; - return p1InCone && p3InCone; - }; - - - /** - * @inheritDoc - */ - ol.render.webgl.PolygonReplay.prototype.drawMultiPolygon = function(multiPolygonGeometry, feature) { - var endss = multiPolygonGeometry.getEndss(); - var stride = multiPolygonGeometry.getStride(); - var currIndex = this.indices.length; - var currLineIndex = this.lineStringReplay.getCurrentIndex(); - var flatCoordinates = multiPolygonGeometry.getFlatCoordinates(); - var i, ii, j, jj; - var start = 0; - for (i = 0, ii = endss.length; i < ii; ++i) { - var ends = endss[i]; - if (ends.length > 0) { - var outerRing = ol.geom.flat.transform.translate(flatCoordinates, start, ends[0], - stride, -this.origin[0], -this.origin[1]); - if (outerRing.length) { - var holes = []; - var holeFlatCoords; - for (j = 1, jj = ends.length; j < jj; ++j) { - if (ends[j] !== ends[j - 1]) { - holeFlatCoords = ol.geom.flat.transform.translate(flatCoordinates, ends[j - 1], - ends[j], stride, -this.origin[0], -this.origin[1]); - holes.push(holeFlatCoords); + } 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.resolveSelfIntersections_(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); } } - this.lineStringReplay.drawPolygonCoordinates(outerRing, holes, stride); - this.drawCoordinates_(outerRing, holes, stride); } } - start = ends[ends.length - 1]; } - 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 (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 variableCriterion; + if (simple) { + variableCriterion = this.getPointsInTriangle_(p0, p1, p2, rtree, true).length === 0; + } else { + variableCriterion = 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) && + variableCriterion) { + //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; + } } } - if (this.lineStringReplay.getCurrentIndex() > currLineIndex) { - this.lineStringReplay.setPolygonStyle(feature, currLineIndex); + // 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.resolveSelfIntersections_ = function( + list, rtree, opt_touch) { + var start = list.firstItem(); + list.nextItem(); + var s0 = start; + var s1 = list.nextItem(); + var resolvedIntersections = false; + + do { + var intersection = this.calculateIntersection_(s0.p0, s0.p1, s1.p0, s1.p1, + opt_touch); + if (intersection) { + var breakCond = false; + var numVertices = this.vertices.length; + var numIndices = this.indices.length; + var n = numVertices / 2; + var seg = list.prevItem(); + list.removeItem(); + rtree.remove(seg); + breakCond = (seg === start); + var p; + if (opt_touch) { + if (intersection[0] === s0.p0.x && intersection[1] === s0.p0.y) { + list.prevItem(); + p = s0.p0; + s1.p0 = p; + rtree.remove(s0); + breakCond = breakCond || (s0 === start); + } else { + p = s1.p1; + s0.p1 = p; + rtree.remove(s1); + breakCond = breakCond || (s1 === start); + } + list.removeItem(); + } else { + p = this.createPoint_(intersection[0], intersection[1], n); + s0.p1 = p; + s1.p0 = p; + rtree.update([Math.min(s0.p0.x, s0.p1.x), Math.min(s0.p0.y, s0.p1.y), + Math.max(s0.p0.x, s0.p1.x), Math.max(s0.p0.y, s0.p1.y)], s0); + rtree.update([Math.min(s1.p0.x, s1.p1.x), Math.min(s1.p0.y, s1.p1.y), + Math.max(s1.p0.x, s1.p1.x), Math.max(s1.p0.y, s1.p1.y)], s1); + } + + this.indices[numIndices++] = seg.p0.i; + this.indices[numIndices++] = seg.p1.i; + this.indices[numIndices++] = p.i; + + resolvedIntersections = true; + if (breakCond) { + break; + } } + + s0 = list.getPrevItem(); + s1 = list.nextItem(); + } while (s0 !== start); + return resolvedIntersections; +}; + + +/** + * @private + * @param {ol.structs.LinkedList} list Linked list of the polygon. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + * @return {boolean} The polygon is simple. + */ +ol.render.webgl.PolygonReplay.prototype.isSimple_ = function(list, rtree) { + var start = list.firstItem(); + var seg = start; + do { + if (this.getIntersections_(seg, rtree).length) { + return false; + } + seg = list.nextItem(); + } while (seg !== start); + return true; +}; + + +/** + * @private + * @param {ol.structs.LinkedList} list Linked list of the polygon. + * @return {boolean} Orientation is clockwise. + */ +ol.render.webgl.PolygonReplay.prototype.isClockwise_ = function(list) { + var length = list.getLength() * 2; + var flatCoordinates = new Array(length); + var start = list.firstItem(); + var seg = start; + var i = 0; + do { + flatCoordinates[i++] = seg.p0.x; + flatCoordinates[i++] = seg.p0.y; + seg = list.nextItem(); + } while (seg !== start); + return ol.geom.flat.orient.linearRingIsClockwise(flatCoordinates, 0, length, 2); +}; + + +/** + * @private + * @param {ol.structs.LinkedList} list Linked list of the polygon. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + */ +ol.render.webgl.PolygonReplay.prototype.splitPolygon_ = function(list, rtree) { + var start = list.firstItem(); + var s0 = start; + do { + var intersections = this.getIntersections_(s0, rtree); + if (intersections.length) { + var s1 = intersections[0]; + var n = this.vertices.length / 2; + var intersection = this.calculateIntersection_(s0.p0, + s0.p1, s1.p0, s1.p1); + var p = this.createPoint_(intersection[0], intersection[1], n); + var newPolygon = new ol.structs.LinkedList(); + var newRtree = new ol.structs.RBush(); + this.insertItem_(p, s0.p1, newPolygon, newRtree); + s0.p1 = p; + rtree.update([Math.min(s0.p0.x, p.x), Math.min(s0.p0.y, p.y), + Math.max(s0.p0.x, p.x), Math.max(s0.p0.y, p.y)], s0); + var currItem = list.nextItem(); + while (currItem !== s1) { + this.insertItem_(currItem.p0, currItem.p1, newPolygon, newRtree); + rtree.remove(currItem); + list.removeItem(); + currItem = list.getCurrItem(); + } + this.insertItem_(s1.p0, p, newPolygon, newRtree); + s1.p0 = p; + rtree.update([Math.min(s1.p1.x, p.x), Math.min(s1.p1.y, p.y), + Math.max(s1.p1.x, p.x), Math.max(s1.p1.y, p.y)], s1); + this.classifyPoints_(list, rtree, false); + this.triangulate_(list, rtree); + this.classifyPoints_(newPolygon, newRtree, false); + this.triangulate_(newPolygon, newRtree); + break; + } + s0 = list.nextItem(); + } while (s0 !== start); +}; + + +/** + * @private + * @param {number} x X coordinate. + * @param {number} y Y coordinate. + * @param {number} i Index. + * @return {ol.WebglPolygonVertex} List item. + */ +ol.render.webgl.PolygonReplay.prototype.createPoint_ = function(x, y, i) { + var numVertices = this.vertices.length; + this.vertices[numVertices++] = x; + this.vertices[numVertices++] = y; + /** @type {ol.WebglPolygonVertex} */ + var p = { + x: x, + y: y, + i: i, + reflex: undefined }; + return p; +}; - /** - * @inheritDoc - */ - ol.render.webgl.PolygonReplay.prototype.drawPolygon = function(polygonGeometry, feature) { - var ends = polygonGeometry.getEnds(); - var stride = polygonGeometry.getStride(); +/** + * @private + * @param {ol.WebglPolygonVertex} p0 First point of segment. + * @param {ol.WebglPolygonVertex} p1 Second point of segment. + * @param {ol.structs.LinkedList} list Polygon ring. + * @param {ol.structs.RBush=} opt_rtree Insert the segment into the R-Tree. + * @return {ol.WebglPolygonSegment} segment. + */ +ol.render.webgl.PolygonReplay.prototype.insertItem_ = function(p0, p1, list, opt_rtree) { + var seg = { + p0: p0, + p1: p1 + }; + list.insertItem(seg); + if (opt_rtree) { + opt_rtree.insert([Math.min(p0.x, p1.x), Math.min(p0.y, p1.y), + Math.max(p0.x, p1.x), Math.max(p0.y, p1.y)], seg); + } + return seg; +}; + + +/** + * @private + * @param {ol.WebglPolygonSegment} s0 Segment before the remove candidate. + * @param {ol.WebglPolygonSegment} s1 Remove candidate segment. + * @param {ol.structs.LinkedList} list Polygon ring. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + */ +ol.render.webgl.PolygonReplay.prototype.removeItem_ = function(s0, s1, list, rtree) { + 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; +}; + + +/** + * @private + * @param {ol.WebglPolygonSegment} segment Segment. + * @param {ol.structs.RBush} rtree R-Tree of the polygon. + * @param {boolean=} opt_touch Touching segments should be considered an intersection. + * @return {Array.} Intersecting segments. + */ +ol.render.webgl.PolygonReplay.prototype.getIntersections_ = function(segment, rtree, opt_touch) { + var p0 = segment.p0; + var p1 = segment.p1; + var segmentsInExtent = rtree.getInExtent([Math.min(p0.x, p1.x), + Math.min(p0.y, p1.y), Math.max(p0.x, p1.x), Math.max(p0.y, p1.y)]); + var result = []; + var i, ii; + for (i = 0, ii = segmentsInExtent.length; i < ii; ++i) { + var currSeg = segmentsInExtent[i]; + if (segment !== currSeg && (opt_touch || currSeg.p0 !== p1 || currSeg.p1 !== p0) && + this.calculateIntersection_(p0, p1, currSeg.p0, currSeg.p1, opt_touch)) { + result.push(currSeg); + } + } + return result; +}; + + +/** + * Line intersection algorithm by Paul Bourke. + * @see http://paulbourke.net/geometry/pointlineplane/ + * + * @private + * @param {ol.WebglPolygonVertex} p0 First point. + * @param {ol.WebglPolygonVertex} p1 Second point. + * @param {ol.WebglPolygonVertex} p2 Third point. + * @param {ol.WebglPolygonVertex} p3 Fourth point. + * @param {boolean=} opt_touch Touching segments should be considered an intersection. + * @return {Array.|undefined} Intersection coordinates. + */ +ol.render.webgl.PolygonReplay.prototype.calculateIntersection_ = function(p0, + p1, p2, p3, opt_touch) { + var denom = (p3.y - p2.y) * (p1.x - p0.x) - (p3.x - p2.x) * (p1.y - p0.y); + if (denom !== 0) { + var ua = ((p3.x - p2.x) * (p0.y - p2.y) - (p3.y - p2.y) * (p0.x - p2.x)) / denom; + var ub = ((p1.x - p0.x) * (p0.y - p2.y) - (p1.y - p0.y) * (p0.x - p2.x)) / denom; + if ((!opt_touch && ua > ol.render.webgl.EPSILON && ua < 1 - ol.render.webgl.EPSILON && + ub > ol.render.webgl.EPSILON && ub < 1 - ol.render.webgl.EPSILON) || (opt_touch && + ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1)) { + return [p0.x + ua * (p1.x - p0.x), p0.y + ua * (p1.y - p0.y)]; + } + } + return undefined; +}; + + +/** + * @private + * @param {ol.WebglPolygonVertex} p0 Point before the start of the diagonal. + * @param {ol.WebglPolygonVertex} p1 Start point of the diagonal. + * @param {ol.WebglPolygonVertex} p2 Ear candidate. + * @param {ol.WebglPolygonVertex} p3 End point of the diagonal. + * @param {ol.WebglPolygonVertex} p4 Point after the end of the diagonal. + * @return {boolean} Diagonal is inside the polygon. + */ +ol.render.webgl.PolygonReplay.prototype.diagonalIsInside_ = function(p0, p1, p2, p3, p4) { + if (p1.reflex === undefined || p3.reflex === undefined) { + return false; + } + var p1IsLeftOf = (p2.x - p3.x) * (p1.y - p3.y) > (p2.y - p3.y) * (p1.x - p3.x); + var p1IsRightOf = (p4.x - p3.x) * (p1.y - p3.y) < (p4.y - p3.y) * (p1.x - p3.x); + var p3IsLeftOf = (p0.x - p1.x) * (p3.y - p1.y) > (p0.y - p1.y) * (p3.x - p1.x); + var p3IsRightOf = (p2.x - p1.x) * (p3.y - p1.y) < (p2.y - p1.y) * (p3.x - p1.x); + var p1InCone = p3.reflex ? p1IsRightOf || p1IsLeftOf : p1IsRightOf && p1IsLeftOf; + var p3InCone = p1.reflex ? p3IsRightOf || p3IsLeftOf : p3IsRightOf && p3IsLeftOf; + return p1InCone && p3InCone; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.PolygonReplay.prototype.drawMultiPolygon = function(multiPolygonGeometry, feature) { + var endss = multiPolygonGeometry.getEndss(); + var stride = multiPolygonGeometry.getStride(); + var currIndex = this.indices.length; + var currLineIndex = this.lineStringReplay.getCurrentIndex(); + var flatCoordinates = multiPolygonGeometry.getFlatCoordinates(); + var i, ii, j, jj; + var start = 0; + for (i = 0, ii = endss.length; i < ii; ++i) { + var ends = endss[i]; if (ends.length > 0) { - var flatCoordinates = polygonGeometry.getFlatCoordinates().map(Number); - var outerRing = ol.geom.flat.transform.translate(flatCoordinates, 0, ends[0], + var outerRing = ol.geom.flat.transform.translate(flatCoordinates, start, ends[0], stride, -this.origin[0], -this.origin[1]); if (outerRing.length) { var holes = []; - var i, ii, holeFlatCoords; - for (i = 1, ii = ends.length; i < ii; ++i) { - if (ends[i] !== ends[i - 1]) { - holeFlatCoords = ol.geom.flat.transform.translate(flatCoordinates, ends[i - 1], - ends[i], stride, -this.origin[0], -this.origin[1]); + var holeFlatCoords; + for (j = 1, jj = ends.length; j < jj; ++j) { + if (ends[j] !== ends[j - 1]) { + holeFlatCoords = ol.geom.flat.transform.translate(flatCoordinates, ends[j - 1], + ends[j], stride, -this.origin[0], -this.origin[1]); holes.push(holeFlatCoords); } } - - 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); - this.lineStringReplay.drawPolygonCoordinates(outerRing, holes, stride); this.drawCoordinates_(outerRing, 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_ = []; + start = ends[ends.length - 1]; + } + 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; } - - this.vertices = null; - this.indices = null; - }; + } + if (this.lineStringReplay.getCurrentIndex() > currLineIndex) { + this.lineStringReplay.setPolygonStyle(feature, currLineIndex); + } +}; - /** - * @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); - - // get the locations - var locations; - if (!this.defaultLocations_) { - // eslint-disable-next-line openlayers-internal/no-missing-requires - locations = new ol.render.webgl.polygonreplay.defaultshader.Locations(gl, program); - this.defaultLocations_ = locations; - } else { - locations = this.defaultLocations_; - } - - context.useProgram(program); - - // enable the vertex attrib arrays - gl.enableVertexAttribArray(locations.a_position); - gl.vertexAttribPointer(locations.a_position, 2, ol.webgl.FLOAT, - false, 8, 0); - - return locations; - }; - - - /** - * @inheritDoc - */ - ol.render.webgl.PolygonReplay.prototype.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; +/** + * @inheritDoc + */ +ol.render.webgl.PolygonReplay.prototype.drawPolygon = function(polygonGeometry, feature) { + var ends = polygonGeometry.getEnds(); + var stride = polygonGeometry.getStride(); + if (ends.length > 0) { + var flatCoordinates = polygonGeometry.getFlatCoordinates().map(Number); + var outerRing = ol.geom.flat.transform.translate(flatCoordinates, 0, ends[0], + stride, -this.origin[0], -this.origin[1]); + if (outerRing.length) { + var holes = []; + var i, ii, holeFlatCoords; + for (i = 1, ii = ends.length; i < ii; ++i) { + if (ends[i] !== ends[i - 1]) { + holeFlatCoords = ol.geom.flat.transform.translate(flatCoordinates, ends[i - 1], + ends[i], stride, -this.origin[0], -this.origin[1]); + holes.push(holeFlatCoords); + } } + + 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); + + this.lineStringReplay.drawPolygonCoordinates(outerRing, holes, stride); + this.drawCoordinates_(outerRing, holes, stride); } - 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.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.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]; +/** + * @inheritDoc + */ +ol.render.webgl.PolygonReplay.prototype.setUpProgram = function(gl, context, size, pixelRatio) { + // get the program + var fragmentShader, vertexShader; + fragmentShader = ol.render.webgl.polygonreplay.defaultshader.fragment; + vertexShader = ol.render.webgl.polygonreplay.defaultshader.vertex; + var program = context.getProgram(fragmentShader, vertexShader); + + // get the locations + var locations; + if (!this.defaultLocations_) { + // eslint-disable-next-line openlayers-internal/no-missing-requires + locations = new ol.render.webgl.polygonreplay.defaultshader.Locations(gl, program); + this.defaultLocations_ = locations; + } else { + locations = this.defaultLocations_; + } + + context.useProgram(program); + + // enable the vertex attrib arrays + gl.enableVertexAttribArray(locations.a_position); + gl.vertexAttribPointer(locations.a_position, 2, ol.webgl.FLOAT, + false, 8, 0); + + return locations; +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.PolygonReplay.prototype.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); - groupStart = this.styleIndices_[i]; + 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); + } +}; - 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.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) { this.drawElements(gl, context, start, end); - - var result = featureCallback(feature); - - if (result) { - return result; - } - + gl.clear(gl.DEPTH_BUFFER_BIT); } - featureIndex--; - end = start; + end = featureStart; } + featureIndex--; + start = featureStart; } - 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; + if (start !== end) { + this.drawElements(gl, context, start, end); + gl.clear(gl.DEPTH_BUFFER_BIT); } - }; + 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/polygonreplay/defaultshader.js b/src/ol/render/webgl/polygonreplay/defaultshader.js index 46db42c7cb..0de2d957d0 100644 --- a/src/ol/render/webgl/polygonreplay/defaultshader.js +++ b/src/ol/render/webgl/polygonreplay/defaultshader.js @@ -6,125 +6,121 @@ goog.require('ol'); goog.require('ol.webgl.Fragment'); goog.require('ol.webgl.Vertex'); -if (ol.ENABLE_WEBGL) { +/** + * @constructor + * @extends {ol.webgl.Fragment} + * @struct + */ +ol.render.webgl.polygonreplay.defaultshader.Fragment = function() { + ol.webgl.Fragment.call(this, ol.render.webgl.polygonreplay.defaultshader.Fragment.SOURCE); +}; +ol.inherits(ol.render.webgl.polygonreplay.defaultshader.Fragment, ol.webgl.Fragment); + + +/** + * @const + * @type {string} + */ +ol.render.webgl.polygonreplay.defaultshader.Fragment.DEBUG_SOURCE = 'precision mediump float;\n\n\n\nuniform vec4 u_color;\nuniform float u_opacity;\n\nvoid main(void) {\n gl_FragColor = u_color;\n float alpha = u_color.a * u_opacity;\n if (alpha == 0.0) {\n discard;\n }\n gl_FragColor.a = alpha;\n}\n'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.polygonreplay.defaultshader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;uniform vec4 e;uniform float f;void main(void){gl_FragColor=e;float alpha=e.a*f;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.polygonreplay.defaultshader.Fragment.SOURCE = ol.DEBUG_WEBGL ? + ol.render.webgl.polygonreplay.defaultshader.Fragment.DEBUG_SOURCE : + ol.render.webgl.polygonreplay.defaultshader.Fragment.OPTIMIZED_SOURCE; + + +ol.render.webgl.polygonreplay.defaultshader.fragment = new ol.render.webgl.polygonreplay.defaultshader.Fragment(); + + +/** + * @constructor + * @extends {ol.webgl.Vertex} + * @struct + */ +ol.render.webgl.polygonreplay.defaultshader.Vertex = function() { + ol.webgl.Vertex.call(this, ol.render.webgl.polygonreplay.defaultshader.Vertex.SOURCE); +}; +ol.inherits(ol.render.webgl.polygonreplay.defaultshader.Vertex, ol.webgl.Vertex); + + +/** + * @const + * @type {string} + */ +ol.render.webgl.polygonreplay.defaultshader.Vertex.DEBUG_SOURCE = '\n\nattribute vec2 a_position;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\n\nvoid main(void) {\n gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0);\n}\n\n\n'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.polygonreplay.defaultshader.Vertex.OPTIMIZED_SOURCE = 'attribute vec2 a;uniform mat4 b;uniform mat4 c;uniform mat4 d;void main(void){gl_Position=b*vec4(a,0.0,1.0);}'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.polygonreplay.defaultshader.Vertex.SOURCE = ol.DEBUG_WEBGL ? + ol.render.webgl.polygonreplay.defaultshader.Vertex.DEBUG_SOURCE : + ol.render.webgl.polygonreplay.defaultshader.Vertex.OPTIMIZED_SOURCE; + + +ol.render.webgl.polygonreplay.defaultshader.vertex = new ol.render.webgl.polygonreplay.defaultshader.Vertex(); + + +/** + * @constructor + * @param {WebGLRenderingContext} gl GL. + * @param {WebGLProgram} program Program. + * @struct + */ +ol.render.webgl.polygonreplay.defaultshader.Locations = function(gl, program) { /** - * @constructor - * @extends {ol.webgl.Fragment} - * @struct + * @type {WebGLUniformLocation} */ - ol.render.webgl.polygonreplay.defaultshader.Fragment = function() { - ol.webgl.Fragment.call(this, ol.render.webgl.polygonreplay.defaultshader.Fragment.SOURCE); - }; - ol.inherits(ol.render.webgl.polygonreplay.defaultshader.Fragment, ol.webgl.Fragment); - + this.u_color = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_color' : 'e'); /** - * @const - * @type {string} + * @type {WebGLUniformLocation} */ - ol.render.webgl.polygonreplay.defaultshader.Fragment.DEBUG_SOURCE = 'precision mediump float;\n\n\n\nuniform vec4 u_color;\nuniform float u_opacity;\n\nvoid main(void) {\n gl_FragColor = u_color;\n float alpha = u_color.a * u_opacity;\n if (alpha == 0.0) {\n discard;\n }\n gl_FragColor.a = alpha;\n}\n'; - + this.u_offsetRotateMatrix = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_offsetRotateMatrix' : 'd'); /** - * @const - * @type {string} + * @type {WebGLUniformLocation} */ - ol.render.webgl.polygonreplay.defaultshader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;uniform vec4 e;uniform float f;void main(void){gl_FragColor=e;float alpha=e.a*f;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}'; - + this.u_offsetScaleMatrix = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_offsetScaleMatrix' : 'c'); /** - * @const - * @type {string} + * @type {WebGLUniformLocation} */ - ol.render.webgl.polygonreplay.defaultshader.Fragment.SOURCE = ol.DEBUG_WEBGL ? - ol.render.webgl.polygonreplay.defaultshader.Fragment.DEBUG_SOURCE : - ol.render.webgl.polygonreplay.defaultshader.Fragment.OPTIMIZED_SOURCE; - - - ol.render.webgl.polygonreplay.defaultshader.fragment = new ol.render.webgl.polygonreplay.defaultshader.Fragment(); - + this.u_opacity = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_opacity' : 'f'); /** - * @constructor - * @extends {ol.webgl.Vertex} - * @struct + * @type {WebGLUniformLocation} */ - ol.render.webgl.polygonreplay.defaultshader.Vertex = function() { - ol.webgl.Vertex.call(this, ol.render.webgl.polygonreplay.defaultshader.Vertex.SOURCE); - }; - ol.inherits(ol.render.webgl.polygonreplay.defaultshader.Vertex, ol.webgl.Vertex); - + this.u_projectionMatrix = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_projectionMatrix' : 'b'); /** - * @const - * @type {string} + * @type {number} */ - ol.render.webgl.polygonreplay.defaultshader.Vertex.DEBUG_SOURCE = '\n\nattribute vec2 a_position;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\n\nvoid main(void) {\n gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0);\n}\n\n\n'; - - - /** - * @const - * @type {string} - */ - ol.render.webgl.polygonreplay.defaultshader.Vertex.OPTIMIZED_SOURCE = 'attribute vec2 a;uniform mat4 b;uniform mat4 c;uniform mat4 d;void main(void){gl_Position=b*vec4(a,0.0,1.0);}'; - - - /** - * @const - * @type {string} - */ - ol.render.webgl.polygonreplay.defaultshader.Vertex.SOURCE = ol.DEBUG_WEBGL ? - ol.render.webgl.polygonreplay.defaultshader.Vertex.DEBUG_SOURCE : - ol.render.webgl.polygonreplay.defaultshader.Vertex.OPTIMIZED_SOURCE; - - - ol.render.webgl.polygonreplay.defaultshader.vertex = new ol.render.webgl.polygonreplay.defaultshader.Vertex(); - - - /** - * @constructor - * @param {WebGLRenderingContext} gl GL. - * @param {WebGLProgram} program Program. - * @struct - */ - ol.render.webgl.polygonreplay.defaultshader.Locations = function(gl, program) { - - /** - * @type {WebGLUniformLocation} - */ - this.u_color = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_color' : 'e'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_offsetRotateMatrix = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_offsetRotateMatrix' : 'd'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_offsetScaleMatrix = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_offsetScaleMatrix' : 'c'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_opacity = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_opacity' : 'f'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_projectionMatrix = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_projectionMatrix' : 'b'); - - /** - * @type {number} - */ - this.a_position = gl.getAttribLocation( - program, ol.DEBUG_WEBGL ? 'a_position' : 'a'); - }; - -} + this.a_position = gl.getAttribLocation( + program, ol.DEBUG_WEBGL ? 'a_position' : 'a'); +}; diff --git a/src/ol/render/webgl/replay.js b/src/ol/render/webgl/replay.js index a6d7ef24db..21b728388e 100644 --- a/src/ol/render/webgl/replay.js +++ b/src/ol/render/webgl/replay.js @@ -8,358 +8,354 @@ goog.require('ol.vec.Mat4'); goog.require('ol.webgl'); -if (ol.ENABLE_WEBGL) { - - /** - * @constructor - * @abstract - * @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.linestringreplay.defaultshader.Locations| - ol.render.webgl.polygonreplay.defaultshader.Locations| - ol.render.webgl.texturereplay.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.linestringreplay.defaultshader.Locations| - ol.render.webgl.polygonreplay.defaultshader.Locations| - ol.render.webgl.texturereplay.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) {}; - +/** + * @constructor + * @abstract + * @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 - * @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 + * @type {number} */ - 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); - } - }; - + this.tolerance = tolerance; /** * @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 + * @const + * @type {ol.Extent} */ - 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; - } - }; - + this.maxExtent = maxExtent; /** - * @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 + * 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} */ - 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; + this.origin = ol.extent.getCenter(maxExtent); - 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); + /** + * @private + * @type {ol.Transform} + */ + this.projectionMatrix_ = ol.transform.create(); - 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); + /** + * @private + * @type {ol.Transform} + */ + this.offsetRotateMatrix_ = ol.transform.create(); - this.lineStringReplay.replay(context, - center, resolution, rotation, size, pixelRatio, - opacity, skippedFeaturesHash, - featureCallback, oneByOne, opt_hitExtent); + /** + * @private + * @type {ol.Transform} + */ + this.offsetScaleMatrix_ = ol.transform.create(); - gl.stencilMask(0); - gl.stencilFunc(gl.NOTEQUAL, 1, 255); - } + /** + * @private + * @type {Array.} + */ + this.tmpMat4_ = ol.vec.Mat4.create(); - context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.verticesBuffer); + /** + * @protected + * @type {Array.} + */ + this.indices = []; - context.bindBuffer(ol.webgl.ELEMENT_ARRAY_BUFFER, this.indicesBuffer); + /** + * @protected + * @type {?ol.webgl.Buffer} + */ + this.indicesBuffer = null; - var locations = this.setUpProgram(gl, context, size, pixelRatio); + /** + * Start index per feature (the index). + * @protected + * @type {Array.} + */ + this.startIndices = []; - // 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])); + /** + * Start index per feature (the feature). + * @protected + * @type {Array.} + */ + this.startIndicesFeature = []; - var offsetScaleMatrix = ol.transform.reset(this.offsetScaleMatrix_); - ol.transform.scale(offsetScaleMatrix, 2 / size[0], 2 / size[1]); + /** + * @protected + * @type {Array.} + */ + this.vertices = []; - var offsetRotateMatrix = ol.transform.reset(this.offsetRotateMatrix_); - if (rotation !== 0) { - ol.transform.rotate(offsetRotateMatrix, -rotation); - } + /** + * @protected + * @type {?ol.webgl.Buffer} + */ + this.verticesBuffer = null; - 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); + /** + * Optional parameter for PolygonReplay instances. + * @protected + * @type {ol.render.webgl.LineStringReplay|undefined} + */ + this.lineStringReplay = undefined; - // 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); - } +}; +ol.inherits(ol.render.webgl.Replay, ol.render.VectorContext); - // 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)); - } +/** + * @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.linestringreplay.defaultshader.Locations| + ol.render.webgl.polygonreplay.defaultshader.Locations| + ol.render.webgl.texturereplay.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.linestringreplay.defaultshader.Locations| + ol.render.webgl.polygonreplay.defaultshader.Locations| + ol.render.webgl.texturereplay.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; + } +}; - /** - * @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); - }; +/** + * @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 28b18c8fd8..98c4f74191 100644 --- a/src/ol/render/webgl/replaygroup.js +++ b/src/ol/render/webgl/replaygroup.js @@ -13,313 +13,309 @@ goog.require('ol.render.webgl.PolygonReplay'); goog.require('ol.render.webgl.TextReplay'); -if (ol.ENABLE_WEBGL) { +/** + * @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); /** - * @constructor - * @extends {ol.render.ReplayGroup} - * @param {number} tolerance Tolerance. - * @param {ol.Extent} maxExtent Max extent. - * @param {number=} opt_renderBuffer Render buffer. - * @struct + * @type {ol.Extent} + * @private */ - ol.render.webgl.ReplayGroup = function(tolerance, maxExtent, opt_renderBuffer) { - ol.render.ReplayGroup.call(this); - - /** - * @type {ol.Extent} - * @private - */ - this.maxExtent_ = maxExtent; - - /** - * @type {number} - * @private - */ - this.tolerance_ = tolerance; - - /** - * @type {number|undefined} - * @private - */ - this.renderBuffer_ = opt_renderBuffer; - - /** - * @private - * @type {!Object.>} - */ - this.replaysByZIndex_ = {}; - - }; - ol.inherits(ol.render.webgl.ReplayGroup, ol.render.ReplayGroup); - + this.maxExtent_ = maxExtent; /** - * @param {ol.webgl.Context} context WebGL context. - * @return {function()} Delete resources function. + * @type {number} + * @private */ - 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; - }; - }; - + this.tolerance_ = tolerance; /** - * @param {ol.webgl.Context} context Context. + * @type {number|undefined} + * @private */ - 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) { - /** - * @type {Function} - */ - 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); - } - } - } - }; - + this.renderBuffer_ = opt_renderBuffer; /** * @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 + * @type {!Object.>} */ - 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; - }); + this.replaysByZIndex_ = {}; - 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); +}; +ol.inherits(ol.render.webgl.ReplayGroup, ol.render.ReplayGroup); + + +/** + * @param {ol.webgl.Context} context WebGL context. + * @return {function()} Delete resources function. + */ +ol.render.webgl.ReplayGroup.prototype.getDeleteResourcesFunction = function(context) { + var functions = []; + var 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; + }; +}; + + +/** + * @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) { + /** + * @type {Function} + */ + 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); + } + } + } +}; + + +/** + * @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; + }); + + 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; + } + } + } + } + return undefined; +}; + + +/** + * @param {ol.Coordinate} coordinate Coordinate. + * @param {ol.webgl.Context} context Context. + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {ol.Size} size Size. + * @param {number} pixelRatio Pixel ratio. + * @param {number} opacity Global opacity. + * @param {Object.} skippedFeaturesHash Ids of features + * to skip. + * @param {function((ol.Feature|ol.render.Feature)): T|undefined} callback Feature callback. + * @return {T|undefined} Callback result. + * @template T + */ +ol.render.webgl.ReplayGroup.prototype.forEachFeatureAtCoordinate = function( + coordinate, context, center, resolution, rotation, size, pixelRatio, + opacity, skippedFeaturesHash, + callback) { + var gl = context.getGL(); + gl.bindFramebuffer( + gl.FRAMEBUFFER, context.getHitDetectionFramebuffer()); + + + /** + * @type {ol.Extent} + */ + var hitExtent; + if (this.renderBuffer_ !== undefined) { + // build an extent around the coordinate, so that only features that + // intersect this extent are checked + hitExtent = ol.extent.buffer( + ol.extent.createOrUpdateFromCoordinate(coordinate), + resolution * this.renderBuffer_); + } + + return this.replayHitDetection_(context, + coordinate, resolution, rotation, ol.render.webgl.ReplayGroup.HIT_DETECTION_SIZE_, + pixelRatio, opacity, skippedFeaturesHash, + /** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @return {?} Callback result. + */ + function(feature) { + var imageData = new Uint8Array(4); + gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, imageData); + + if (imageData[3] > 0) { + var result = callback(feature); if (result) { return result; } } - } - } - return undefined; - }; + }, 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. - * @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()); +/** + * @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); - /** - * @type {ol.Extent} - */ - var hitExtent; - if (this.renderBuffer_ !== undefined) { - // build an extent around the coordinate, so that only features that - // intersect this extent are checked - hitExtent = ol.extent.buffer( - ol.extent.createOrUpdateFromCoordinate(coordinate), - resolution * this.renderBuffer_); - } + return hasFeature !== undefined; +}; - 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 {Array.} + */ +ol.render.webgl.ReplayGroup.HIT_DETECTION_SIZE_ = [1, 1]; - 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 - }; - -} +/** + * @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 b18899585f..f4fbca41ba 100644 --- a/src/ol/render/webgl/textreplay.js +++ b/src/ol/render/webgl/textreplay.js @@ -10,446 +10,442 @@ goog.require('ol.style.AtlasManager'); goog.require('ol.webgl.Buffer'); -if (ol.ENABLE_WEBGL) { - - /** - * @constructor - * @extends {ol.render.webgl.TextureReplay} - * @param {number} tolerance Tolerance. - * @param {ol.Extent} maxExtent Max extent. - * @struct - */ - ol.render.webgl.TextReplay = function(tolerance, maxExtent) { - ol.render.webgl.TextureReplay.call(this, tolerance, maxExtent); - - /** - * @private - * @type {Array.} - */ - this.images_ = []; - - /** - * @private - * @type {Array.} - */ - this.textures_ = []; - - /** - * @private - * @type {HTMLCanvasElement} - */ - this.measureCanvas_ = ol.dom.createCanvasContext2D(0, 0).canvas; - - /** - * @private - * @type {{strokeColor: (ol.ColorLike|null), - * lineCap: (string|undefined), - * lineDash: Array., - * lineDashOffset: (number|undefined), - * lineJoin: (string|undefined), - * lineWidth: number, - * miterLimit: (number|undefined), - * fillColor: (ol.ColorLike|null), - * font: (string|undefined), - * scale: (number|undefined)}} - */ - this.state_ = { - strokeColor: null, - lineCap: undefined, - lineDash: null, - lineDashOffset: undefined, - lineJoin: undefined, - lineWidth: 0, - miterLimit: undefined, - fillColor: null, - font: undefined, - scale: undefined - }; - - /** - * @private - * @type {string} - */ - this.text_ = ''; - - /** - * @private - * @type {number|undefined} - */ - this.textAlign_ = undefined; - - /** - * @private - * @type {number|undefined} - */ - this.textBaseline_ = undefined; - - /** - * @private - * @type {number|undefined} - */ - this.offsetX_ = undefined; - - /** - * @private - * @type {number|undefined} - */ - this.offsetY_ = undefined; - - /** - * @private - * @type {Object.} - */ - this.atlases_ = {}; - - /** - * @private - * @type {ol.WebglGlyphAtlas|undefined} - */ - this.currAtlas_ = undefined; - - this.scale = 1; - - this.opacity = 1; - - }; - ol.inherits(ol.render.webgl.TextReplay, ol.render.webgl.TextureReplay); - - - /** - * @inheritDoc - */ - ol.render.webgl.TextReplay.prototype.drawText = function(flatCoordinates, offset, - end, stride, geometry, feature) { - if (this.text_) { - this.startIndices.push(this.indices.length); - this.startIndicesFeature.push(feature); - - var glyphAtlas = this.currAtlas_; - var lines = this.text_.split('\n'); - var textSize = this.getTextSize_(lines); - var i, ii, j, jj, currX, currY, charArr, charInfo; - var anchorX = Math.round(textSize[0] * this.textAlign_ - this.offsetX_); - var anchorY = Math.round(textSize[1] * this.textBaseline_ - this.offsetY_); - var lineWidth = (this.state_.lineWidth / 2) * this.state_.scale; - - for (i = 0, ii = lines.length; i < ii; ++i) { - currX = 0; - currY = glyphAtlas.height * i; - charArr = lines[i].split(''); - - for (j = 0, jj = charArr.length; j < jj; ++j) { - charInfo = glyphAtlas.atlas.getInfo(charArr[j]); - - if (charInfo) { - var image = charInfo.image; - - this.anchorX = anchorX - currX; - this.anchorY = anchorY - currY; - this.originX = j === 0 ? charInfo.offsetX - lineWidth : charInfo.offsetX; - this.originY = charInfo.offsetY; - this.height = glyphAtlas.height; - this.width = j === 0 || j === charArr.length - 1 ? - glyphAtlas.width[charArr[j]] + lineWidth : glyphAtlas.width[charArr[j]]; - this.imageHeight = image.height; - this.imageWidth = image.width; - - 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); - } - } - - this.drawText_(flatCoordinates, offset, end, stride); - } - currX += this.width; - } - } - } - }; - +/** + * @constructor + * @extends {ol.render.webgl.TextureReplay} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Max extent. + * @struct + */ +ol.render.webgl.TextReplay = function(tolerance, maxExtent) { + ol.render.webgl.TextureReplay.call(this, tolerance, maxExtent); /** * @private - * @param {Array.} lines Label to draw split to lines. - * @return {Array.} Size of the label in pixels. + * @type {Array.} */ - ol.render.webgl.TextReplay.prototype.getTextSize_ = function(lines) { - var self = this; + this.images_ = []; + + /** + * @private + * @type {Array.} + */ + this.textures_ = []; + + /** + * @private + * @type {HTMLCanvasElement} + */ + this.measureCanvas_ = ol.dom.createCanvasContext2D(0, 0).canvas; + + /** + * @private + * @type {{strokeColor: (ol.ColorLike|null), + * lineCap: (string|undefined), + * lineDash: Array., + * lineDashOffset: (number|undefined), + * lineJoin: (string|undefined), + * lineWidth: number, + * miterLimit: (number|undefined), + * fillColor: (ol.ColorLike|null), + * font: (string|undefined), + * scale: (number|undefined)}} + */ + this.state_ = { + strokeColor: null, + lineCap: undefined, + lineDash: null, + lineDashOffset: undefined, + lineJoin: undefined, + lineWidth: 0, + miterLimit: undefined, + fillColor: null, + font: undefined, + scale: undefined + }; + + /** + * @private + * @type {string} + */ + this.text_ = ''; + + /** + * @private + * @type {number|undefined} + */ + this.textAlign_ = undefined; + + /** + * @private + * @type {number|undefined} + */ + this.textBaseline_ = undefined; + + /** + * @private + * @type {number|undefined} + */ + this.offsetX_ = undefined; + + /** + * @private + * @type {number|undefined} + */ + this.offsetY_ = undefined; + + /** + * @private + * @type {Object.} + */ + this.atlases_ = {}; + + /** + * @private + * @type {ol.WebglGlyphAtlas|undefined} + */ + this.currAtlas_ = undefined; + + this.scale = 1; + + this.opacity = 1; + +}; +ol.inherits(ol.render.webgl.TextReplay, ol.render.webgl.TextureReplay); + + +/** + * @inheritDoc + */ +ol.render.webgl.TextReplay.prototype.drawText = function(flatCoordinates, offset, + end, stride, geometry, feature) { + if (this.text_) { + this.startIndices.push(this.indices.length); + this.startIndicesFeature.push(feature); + var glyphAtlas = this.currAtlas_; - var textHeight = lines.length * glyphAtlas.height; - //Split every line to an array of chars, sum up their width, and select the longest. - var textWidth = lines.map(function(str) { - var sum = 0; - var i, ii; - for (i = 0, ii = str.length; i < ii; ++i) { - var curr = str[i]; - if (!glyphAtlas.width[curr]) { - self.addCharToAtlas_(curr); + var lines = this.text_.split('\n'); + var textSize = this.getTextSize_(lines); + var i, ii, j, jj, currX, currY, charArr, charInfo; + var anchorX = Math.round(textSize[0] * this.textAlign_ - this.offsetX_); + var anchorY = Math.round(textSize[1] * this.textBaseline_ - this.offsetY_); + var lineWidth = (this.state_.lineWidth / 2) * this.state_.scale; + + for (i = 0, ii = lines.length; i < ii; ++i) { + currX = 0; + currY = glyphAtlas.height * i; + charArr = lines[i].split(''); + + for (j = 0, jj = charArr.length; j < jj; ++j) { + charInfo = glyphAtlas.atlas.getInfo(charArr[j]); + + if (charInfo) { + var image = charInfo.image; + + this.anchorX = anchorX - currX; + this.anchorY = anchorY - currY; + this.originX = j === 0 ? charInfo.offsetX - lineWidth : charInfo.offsetX; + this.originY = charInfo.offsetY; + this.height = glyphAtlas.height; + this.width = j === 0 || j === charArr.length - 1 ? + glyphAtlas.width[charArr[j]] + lineWidth : glyphAtlas.width[charArr[j]]; + this.imageHeight = image.height; + this.imageWidth = image.width; + + 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); + } + } + + this.drawText_(flatCoordinates, offset, end, stride); } - sum += glyphAtlas.width[curr] ? glyphAtlas.width[curr] : 0; + currX += this.width; } - return sum; - }).reduce(function(max, curr) { - return Math.max(max, curr); - }); - - return [textWidth, textHeight]; - }; + } + } +}; - /** - * @private - * @param {Array.} flatCoordinates Flat coordinates. - * @param {number} offset Offset. - * @param {number} end End. - * @param {number} stride Stride. - */ - ol.render.webgl.TextReplay.prototype.drawText_ = function(flatCoordinates, offset, - end, stride) { +/** + * @private + * @param {Array.} lines Label to draw split to lines. + * @return {Array.} Size of the label in pixels. + */ +ol.render.webgl.TextReplay.prototype.getTextSize_ = function(lines) { + var self = this; + var glyphAtlas = this.currAtlas_; + var textHeight = lines.length * glyphAtlas.height; + //Split every line to an array of chars, sum up their width, and select the longest. + var textWidth = lines.map(function(str) { + var sum = 0; var i, ii; - for (i = offset, ii = end; i < ii; i += stride) { - this.drawCoordinates(flatCoordinates, offset, end, stride); - } - }; - - - /** - * @private - * @param {string} char Character. - */ - ol.render.webgl.TextReplay.prototype.addCharToAtlas_ = function(char) { - if (char.length === 1) { - var glyphAtlas = this.currAtlas_; - var state = this.state_; - var mCtx = this.measureCanvas_.getContext('2d'); - mCtx.font = state.font; - var width = Math.ceil(mCtx.measureText(char).width * state.scale); - - var info = glyphAtlas.atlas.add(char, width, glyphAtlas.height, - function(ctx, x, y) { - //Parameterize the canvas - ctx.font = /** @type {string} */ (state.font); - ctx.fillStyle = state.fillColor; - ctx.strokeStyle = state.strokeColor; - ctx.lineWidth = state.lineWidth; - ctx.lineCap = /*** @type {string} */ (state.lineCap); - ctx.lineJoin = /** @type {string} */ (state.lineJoin); - ctx.miterLimit = /** @type {number} */ (state.miterLimit); - ctx.textAlign = 'left'; - ctx.textBaseline = 'top'; - if (ol.has.CANVAS_LINE_DASH && state.lineDash) { - //FIXME: use pixelRatio - ctx.setLineDash(state.lineDash); - ctx.lineDashOffset = /** @type {number} */ (state.lineDashOffset); - } - if (state.scale !== 1) { - //FIXME: use pixelRatio - ctx.setTransform(/** @type {number} */ (state.scale), 0, 0, - /** @type {number} */ (state.scale), 0, 0); - } - - //Draw the character on the canvas - if (state.strokeColor) { - ctx.strokeText(char, x, y); - } - if (state.fillColor) { - ctx.fillText(char, x, y); - } - }); - - if (info) { - glyphAtlas.width[char] = width; + for (i = 0, ii = str.length; i < ii; ++i) { + var curr = str[i]; + if (!glyphAtlas.width[curr]) { + self.addCharToAtlas_(curr); } + sum += glyphAtlas.width[curr] ? glyphAtlas.width[curr] : 0; } - }; + return sum; + }).reduce(function(max, curr) { + return Math.max(max, curr); + }); + + return [textWidth, textHeight]; +}; - /** - * @inheritDoc - */ - ol.render.webgl.TextReplay.prototype.finish = function(context) { - var gl = context.getGL(); - - this.groupIndices.push(this.indices.length); - this.hitDetectionGroupIndices = this.groupIndices; - - // 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); - - // create textures - /** @type {Object.} */ - var texturePerImage = {}; - - this.createTextures(this.textures_, this.images_, texturePerImage, gl); - - this.state_ = { - strokeColor: null, - lineCap: undefined, - lineDash: null, - lineDashOffset: undefined, - lineJoin: undefined, - lineWidth: 0, - miterLimit: undefined, - fillColor: null, - font: undefined, - scale: undefined - }; - this.text_ = ''; - this.textAlign_ = undefined; - this.textBaseline_ = undefined; - this.offsetX_ = undefined; - this.offsetY_ = undefined; - this.images_ = null; - this.atlases_ = {}; - this.currAtlas_ = undefined; - ol.render.webgl.TextureReplay.prototype.finish.call(this, context); - }; +/** + * @private + * @param {Array.} flatCoordinates Flat coordinates. + * @param {number} offset Offset. + * @param {number} end End. + * @param {number} stride Stride. + */ +ol.render.webgl.TextReplay.prototype.drawText_ = function(flatCoordinates, offset, + end, stride) { + var i, ii; + for (i = offset, ii = end; i < ii; i += stride) { + this.drawCoordinates(flatCoordinates, offset, end, stride); + } +}; - /** - * @inheritDoc - */ - ol.render.webgl.TextReplay.prototype.setTextStyle = function(textStyle) { +/** + * @private + * @param {string} char Character. + */ +ol.render.webgl.TextReplay.prototype.addCharToAtlas_ = function(char) { + if (char.length === 1) { + var glyphAtlas = this.currAtlas_; var state = this.state_; - var textFillStyle = textStyle.getFill(); - var textStrokeStyle = textStyle.getStroke(); - if (!textStyle || !textStyle.getText() || (!textFillStyle && !textStrokeStyle)) { - this.text_ = ''; + var mCtx = this.measureCanvas_.getContext('2d'); + mCtx.font = state.font; + var width = Math.ceil(mCtx.measureText(char).width * state.scale); + + var info = glyphAtlas.atlas.add(char, width, glyphAtlas.height, + function(ctx, x, y) { + //Parameterize the canvas + ctx.font = /** @type {string} */ (state.font); + ctx.fillStyle = state.fillColor; + ctx.strokeStyle = state.strokeColor; + ctx.lineWidth = state.lineWidth; + ctx.lineCap = /*** @type {string} */ (state.lineCap); + ctx.lineJoin = /** @type {string} */ (state.lineJoin); + ctx.miterLimit = /** @type {number} */ (state.miterLimit); + ctx.textAlign = 'left'; + ctx.textBaseline = 'top'; + if (ol.has.CANVAS_LINE_DASH && state.lineDash) { + //FIXME: use pixelRatio + ctx.setLineDash(state.lineDash); + ctx.lineDashOffset = /** @type {number} */ (state.lineDashOffset); + } + if (state.scale !== 1) { + //FIXME: use pixelRatio + ctx.setTransform(/** @type {number} */ (state.scale), 0, 0, + /** @type {number} */ (state.scale), 0, 0); + } + + //Draw the character on the canvas + if (state.strokeColor) { + ctx.strokeText(char, x, y); + } + if (state.fillColor) { + ctx.fillText(char, x, y); + } + }); + + if (info) { + glyphAtlas.width[char] = width; + } + } +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.TextReplay.prototype.finish = function(context) { + var gl = context.getGL(); + + this.groupIndices.push(this.indices.length); + this.hitDetectionGroupIndices = this.groupIndices; + + // 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); + + // create textures + /** @type {Object.} */ + var texturePerImage = {}; + + this.createTextures(this.textures_, this.images_, texturePerImage, gl); + + this.state_ = { + strokeColor: null, + lineCap: undefined, + lineDash: null, + lineDashOffset: undefined, + lineJoin: undefined, + lineWidth: 0, + miterLimit: undefined, + fillColor: null, + font: undefined, + scale: undefined + }; + this.text_ = ''; + this.textAlign_ = undefined; + this.textBaseline_ = undefined; + this.offsetX_ = undefined; + this.offsetY_ = undefined; + this.images_ = null; + this.atlases_ = {}; + this.currAtlas_ = undefined; + ol.render.webgl.TextureReplay.prototype.finish.call(this, context); +}; + + +/** + * @inheritDoc + */ +ol.render.webgl.TextReplay.prototype.setTextStyle = function(textStyle) { + var state = this.state_; + var textFillStyle = textStyle.getFill(); + var textStrokeStyle = textStyle.getStroke(); + if (!textStyle || !textStyle.getText() || (!textFillStyle && !textStrokeStyle)) { + this.text_ = ''; + } else { + if (!textFillStyle) { + state.fillColor = null; } else { - if (!textFillStyle) { - state.fillColor = null; + var textFillStyleColor = textFillStyle.getColor(); + state.fillColor = ol.colorlike.asColorLike(textFillStyleColor ? + textFillStyleColor : ol.render.webgl.defaultFillStyle); + } + if (!textStrokeStyle) { + state.strokeColor = null; + state.lineWidth = 0; + } else { + var textStrokeStyleColor = textStrokeStyle.getColor(); + state.strokeColor = ol.colorlike.asColorLike(textStrokeStyleColor ? + textStrokeStyleColor : ol.render.webgl.defaultStrokeStyle); + state.lineWidth = textStrokeStyle.getWidth() || ol.render.webgl.defaultLineWidth; + state.lineCap = textStrokeStyle.getLineCap() || ol.render.webgl.defaultLineCap; + state.lineDashOffset = textStrokeStyle.getLineDashOffset() || ol.render.webgl.defaultLineDashOffset; + state.lineJoin = textStrokeStyle.getLineJoin() || ol.render.webgl.defaultLineJoin; + state.miterLimit = textStrokeStyle.getMiterLimit() || ol.render.webgl.defaultMiterLimit; + var lineDash = textStrokeStyle.getLineDash(); + state.lineDash = lineDash ? lineDash.slice() : ol.render.webgl.defaultLineDash; + } + state.font = textStyle.getFont() || ol.render.webgl.defaultFont; + state.scale = textStyle.getScale() || 1; + this.text_ = /** @type {string} */ (textStyle.getText()); + var textAlign = ol.render.webgl.TextReplay.Align_[textStyle.getTextAlign()]; + var textBaseline = ol.render.webgl.TextReplay.Align_[textStyle.getTextBaseline()]; + this.textAlign_ = textAlign === undefined ? + ol.render.webgl.defaultTextAlign : textAlign; + this.textBaseline_ = textBaseline === undefined ? + ol.render.webgl.defaultTextBaseline : textBaseline; + this.offsetX_ = textStyle.getOffsetX() || 0; + this.offsetY_ = textStyle.getOffsetY() || 0; + this.rotateWithView = !!textStyle.getRotateWithView(); + this.rotation = textStyle.getRotation() || 0; + + this.currAtlas_ = this.getAtlas_(state); + } +}; + + +/** + * @private + * @param {Object} state Font attributes. + * @return {ol.WebglGlyphAtlas} Glyph atlas. + */ +ol.render.webgl.TextReplay.prototype.getAtlas_ = function(state) { + var params = []; + var i; + for (i in state) { + if (state[i] || state[i] === 0) { + if (Array.isArray(state[i])) { + params = params.concat(state[i]); } else { - var textFillStyleColor = textFillStyle.getColor(); - state.fillColor = ol.colorlike.asColorLike(textFillStyleColor ? - textFillStyleColor : ol.render.webgl.defaultFillStyle); - } - if (!textStrokeStyle) { - state.strokeColor = null; - state.lineWidth = 0; - } else { - var textStrokeStyleColor = textStrokeStyle.getColor(); - state.strokeColor = ol.colorlike.asColorLike(textStrokeStyleColor ? - textStrokeStyleColor : ol.render.webgl.defaultStrokeStyle); - state.lineWidth = textStrokeStyle.getWidth() || ol.render.webgl.defaultLineWidth; - state.lineCap = textStrokeStyle.getLineCap() || ol.render.webgl.defaultLineCap; - state.lineDashOffset = textStrokeStyle.getLineDashOffset() || ol.render.webgl.defaultLineDashOffset; - state.lineJoin = textStrokeStyle.getLineJoin() || ol.render.webgl.defaultLineJoin; - state.miterLimit = textStrokeStyle.getMiterLimit() || ol.render.webgl.defaultMiterLimit; - var lineDash = textStrokeStyle.getLineDash(); - state.lineDash = lineDash ? lineDash.slice() : ol.render.webgl.defaultLineDash; - } - state.font = textStyle.getFont() || ol.render.webgl.defaultFont; - state.scale = textStyle.getScale() || 1; - this.text_ = /** @type {string} */ (textStyle.getText()); - var textAlign = ol.render.webgl.TextReplay.Align_[textStyle.getTextAlign()]; - var textBaseline = ol.render.webgl.TextReplay.Align_[textStyle.getTextBaseline()]; - this.textAlign_ = textAlign === undefined ? - ol.render.webgl.defaultTextAlign : textAlign; - this.textBaseline_ = textBaseline === undefined ? - ol.render.webgl.defaultTextBaseline : textBaseline; - this.offsetX_ = textStyle.getOffsetX() || 0; - this.offsetY_ = textStyle.getOffsetY() || 0; - this.rotateWithView = !!textStyle.getRotateWithView(); - this.rotation = textStyle.getRotation() || 0; - - this.currAtlas_ = this.getAtlas_(state); - } - }; - - - /** - * @private - * @param {Object} state Font attributes. - * @return {ol.WebglGlyphAtlas} Glyph atlas. - */ - ol.render.webgl.TextReplay.prototype.getAtlas_ = function(state) { - var params = []; - var i; - for (i in state) { - if (state[i] || state[i] === 0) { - if (Array.isArray(state[i])) { - params = params.concat(state[i]); - } else { - params.push(state[i]); - } + params.push(state[i]); } } - var hash = this.calculateHash_(params); - if (!this.atlases_[hash]) { - var mCtx = this.measureCanvas_.getContext('2d'); - mCtx.font = state.font; - var height = Math.ceil((mCtx.measureText('M').width * 1.5 + - state.lineWidth / 2) * state.scale); + } + var hash = this.calculateHash_(params); + if (!this.atlases_[hash]) { + var mCtx = this.measureCanvas_.getContext('2d'); + mCtx.font = state.font; + var height = Math.ceil((mCtx.measureText('M').width * 1.5 + + state.lineWidth / 2) * state.scale); - this.atlases_[hash] = { - atlas: new ol.style.AtlasManager({ - space: state.lineWidth + 1 - }), - width: {}, - height: height - }; - } - return this.atlases_[hash]; - }; + this.atlases_[hash] = { + atlas: new ol.style.AtlasManager({ + space: state.lineWidth + 1 + }), + width: {}, + height: height + }; + } + return this.atlases_[hash]; +}; - /** - * @private - * @param {Array.} params Array of parameters. - * @return {string} Hash string. - */ - ol.render.webgl.TextReplay.prototype.calculateHash_ = function(params) { - //TODO: Create a more performant, reliable, general hash function. - var i, ii; - var hash = ''; - for (i = 0, ii = params.length; i < ii; ++i) { - hash += params[i]; - } - return hash; - }; +/** + * @private + * @param {Array.} params Array of parameters. + * @return {string} Hash string. + */ +ol.render.webgl.TextReplay.prototype.calculateHash_ = function(params) { + //TODO: Create a more performant, reliable, general hash function. + var i, ii; + var hash = ''; + for (i = 0, ii = params.length; i < ii; ++i) { + hash += params[i]; + } + return hash; +}; - /** - * @inheritDoc - */ - ol.render.webgl.TextReplay.prototype.getTextures = function(opt_all) { - return this.textures_; - }; +/** + * @inheritDoc + */ +ol.render.webgl.TextReplay.prototype.getTextures = function(opt_all) { + return this.textures_; +}; - /** - * @inheritDoc - */ - ol.render.webgl.TextReplay.prototype.getHitDetectionTextures = function() { - return this.textures_; - }; +/** + * @inheritDoc + */ +ol.render.webgl.TextReplay.prototype.getHitDetectionTextures = function() { + return this.textures_; +}; - /** - * @enum {number} - * @private - */ - ol.render.webgl.TextReplay.Align_ = { - left: 0, - end: 0, - center: 0.5, - right: 1, - start: 1, - top: 0, - middle: 0.5, - hanging: 0.2, - alphabetic: 0.8, - ideographic: 0.8, - bottom: 1 - }; - -} +/** + * @enum {number} + * @private + */ +ol.render.webgl.TextReplay.Align_ = { + left: 0, + end: 0, + center: 0.5, + right: 1, + start: 1, + top: 0, + middle: 0.5, + hanging: 0.2, + alphabetic: 0.8, + ideographic: 0.8, + bottom: 1 +}; diff --git a/src/ol/render/webgl/texturereplay.js b/src/ol/render/webgl/texturereplay.js index 3b2fb7b13b..12c8e66f36 100644 --- a/src/ol/render/webgl/texturereplay.js +++ b/src/ol/render/webgl/texturereplay.js @@ -8,490 +8,487 @@ goog.require('ol.render.webgl.Replay'); goog.require('ol.webgl'); goog.require('ol.webgl.Context'); -if (ol.ENABLE_WEBGL) { +/** + * @constructor + * @abstract + * @extends {ol.render.webgl.Replay} + * @param {number} tolerance Tolerance. + * @param {ol.Extent} maxExtent Max extent. + * @struct + */ +ol.render.webgl.TextureReplay = function(tolerance, maxExtent) { + ol.render.webgl.Replay.call(this, tolerance, maxExtent); /** - * @constructor - * @abstract - * @extends {ol.render.webgl.Replay} - * @param {number} tolerance Tolerance. - * @param {ol.Extent} maxExtent Max extent. - * @struct - */ - ol.render.webgl.TextureReplay = function(tolerance, maxExtent) { - ol.render.webgl.Replay.call(this, tolerance, maxExtent); - - /** - * @type {number|undefined} - * @protected - */ - this.anchorX = undefined; - - /** - * @type {number|undefined} - * @protected - */ - this.anchorY = undefined; - - /** - * @type {Array.} - * @protected - */ - this.groupIndices = []; - - /** - * @type {Array.} - * @protected - */ - this.hitDetectionGroupIndices = []; - - /** - * @type {number|undefined} - * @protected - */ - this.height = undefined; - - /** - * @type {number|undefined} - * @protected - */ - this.imageHeight = undefined; - - /** - * @type {number|undefined} - * @protected - */ - this.imageWidth = undefined; - - /** - * @protected - * @type {ol.render.webgl.texturereplay.defaultshader.Locations} - */ - this.defaultLocations = null; - - /** - * @protected - * @type {number|undefined} - */ - this.opacity = undefined; - - /** - * @type {number|undefined} - * @protected - */ - this.originX = undefined; - - /** - * @type {number|undefined} - * @protected - */ - this.originY = undefined; - - /** - * @protected - * @type {boolean|undefined} - */ - this.rotateWithView = undefined; - - /** - * @protected - * @type {number|undefined} - */ - this.rotation = undefined; - - /** - * @protected - * @type {number|undefined} - */ - this.scale = undefined; - - /** - * @type {number|undefined} - * @protected - */ - this.width = undefined; - }; - ol.inherits(ol.render.webgl.TextureReplay, ol.render.webgl.Replay); - - - /** - * @inheritDoc - */ - ol.render.webgl.TextureReplay.prototype.getDeleteResourcesFunction = function(context) { - var verticesBuffer = this.verticesBuffer; - var indicesBuffer = this.indicesBuffer; - var textures = this.getTextures(true); - 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]); - } - } - 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. + * @type {number|undefined} * @protected */ - ol.render.webgl.TextureReplay.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]; + this.anchorX = undefined; - // 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). + /** + * @type {number|undefined} + * @protected + */ + this.anchorY = undefined; - n = numVertices / 8; + /** + * @type {Array.} + * @protected + */ + this.groupIndices = []; - // 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; + /** + * @type {Array.} + * @protected + */ + this.hitDetectionGroupIndices = []; - // 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; + /** + * @type {number|undefined} + * @protected + */ + this.height = undefined; - // 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; - }; + /** + * @type {number|undefined} + * @protected + */ + this.imageHeight = undefined; + /** + * @type {number|undefined} + * @protected + */ + this.imageWidth = undefined; /** * @protected - * @param {Array.} textures Textures. - * @param {Array.} images - * Images. - * @param {Object.} texturePerImage Texture cache. - * @param {WebGLRenderingContext} gl Gl. + * @type {ol.render.webgl.texturereplay.defaultshader.Locations} */ - ol.render.webgl.TextureReplay.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; - } - }; - + this.defaultLocations = null; /** - * @inheritDoc + * @protected + * @type {number|undefined} */ - ol.render.webgl.TextureReplay.prototype.setUpProgram = function(gl, context, size, pixelRatio) { - // get the program - var fragmentShader = ol.render.webgl.texturereplay.defaultshader.fragment; - var vertexShader = ol.render.webgl.texturereplay.defaultshader.vertex; - var program = context.getProgram(fragmentShader, vertexShader); + this.opacity = undefined; - // get the locations - var locations; - if (!this.defaultLocations) { - // eslint-disable-next-line openlayers-internal/no-missing-requires - locations = new ol.render.webgl.texturereplay.defaultshader.Locations(gl, program); - this.defaultLocations = locations; + /** + * @type {number|undefined} + * @protected + */ + this.originX = undefined; + + /** + * @type {number|undefined} + * @protected + */ + this.originY = undefined; + + /** + * @protected + * @type {boolean|undefined} + */ + this.rotateWithView = undefined; + + /** + * @protected + * @type {number|undefined} + */ + this.rotation = undefined; + + /** + * @protected + * @type {number|undefined} + */ + this.scale = undefined; + + /** + * @type {number|undefined} + * @protected + */ + this.width = undefined; +}; +ol.inherits(ol.render.webgl.TextureReplay, ol.render.webgl.Replay); + + +/** + * @inheritDoc + */ +ol.render.webgl.TextureReplay.prototype.getDeleteResourcesFunction = function(context) { + var verticesBuffer = this.verticesBuffer; + var indicesBuffer = this.indicesBuffer; + var textures = this.getTextures(true); + 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]); + } + } + 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. + * @protected + */ +ol.render.webgl.TextureReplay.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; +}; + + +/** + * @protected + * @param {Array.} textures Textures. + * @param {Array.} images + * Images. + * @param {Object.} texturePerImage Texture cache. + * @param {WebGLRenderingContext} gl Gl. + */ +ol.render.webgl.TextureReplay.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 { - locations = this.defaultLocations; + texture = ol.webgl.Context.createTexture( + gl, image, ol.webgl.CLAMP_TO_EDGE, ol.webgl.CLAMP_TO_EDGE); + texturePerImage[uid] = texture; } - - // 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; - }; + textures[i] = texture; + } +}; - /** - * @inheritDoc - */ - ol.render.webgl.TextureReplay.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.TextureReplay.prototype.setUpProgram = function(gl, context, size, pixelRatio) { + // get the program + var fragmentShader = ol.render.webgl.texturereplay.defaultshader.fragment; + var vertexShader = ol.render.webgl.texturereplay.defaultshader.vertex; + var program = context.getProgram(fragmentShader, vertexShader); + + // get the locations + var locations; + if (!this.defaultLocations) { + // eslint-disable-next-line openlayers-internal/no-missing-requires + locations = new ol.render.webgl.texturereplay.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.TextureReplay.prototype.drawReplay = function(gl, context, skippedFeaturesHash, hitDetection) { - var textures = hitDetection ? this.getHitDetectionTextures() : this.getTextures(); - 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; - } - } - }; +/** + * @inheritDoc + */ +ol.render.webgl.TextureReplay.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 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 - * - * @protected - * @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.TextureReplay.prototype.drawReplaySkipping = function(gl, context, skippedFeaturesHash, textures, - groupIndices) { - var featureIndex = 0; +/** + * @inheritDoc + */ +ol.render.webgl.TextureReplay.prototype.drawReplay = function(gl, context, skippedFeaturesHash, hitDetection) { + var textures = hitDetection ? this.getHitDetectionTextures() : this.getTextures(); + var groupIndices = hitDetection ? this.hitDetectionGroupIndices : this.groupIndices; - var i, ii; - for (i = 0, ii = textures.length; i < ii; ++i) { + 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 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++; - } - - 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); - } + var end = groupIndices[i]; + this.drawElements(gl, context, start, end); + start = end; } - }; + } +}; - /** - * @inheritDoc - */ - ol.render.webgl.TextureReplay.prototype.drawHitDetectionReplayOneByOne = function(gl, context, skippedFeaturesHash, - featureCallback, opt_hitExtent) { - var i, groupStart, start, end, feature, featureUid; - var featureIndex = this.startIndices.length - 1; - var hitDetectionTextures = this.getHitDetectionTextures(); - for (i = hitDetectionTextures.length - 1; i >= 0; --i) { - gl.bindTexture(ol.webgl.TEXTURE_2D, hitDetectionTextures[i]); - groupStart = (i > 0) ? this.hitDetectionGroupIndices[i - 1] : 0; - end = this.hitDetectionGroupIndices[i]; +/** + * 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 + * + * @protected + * @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.TextureReplay.prototype.drawReplaySkipping = function(gl, context, skippedFeaturesHash, textures, + groupIndices) { + var featureIndex = 0; - // 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(); + 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]; - 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); + 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); - - var result = featureCallback(feature); - if (result) { - return result; - } } - + // continue with the next feature + start = (featureIndex === this.startIndices.length - 1) ? + groupEnd : this.startIndices[featureIndex + 1]; end = start; - featureIndex--; + } else { + // the feature is not skipped, augment the end index + end = (featureIndex === this.startIndices.length - 1) ? + groupEnd : this.startIndices[featureIndex + 1]; } + featureIndex++; } - return undefined; - }; + + 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); + } + } +}; - /** - * @inheritDoc - */ - ol.render.webgl.TextureReplay.prototype.finish = function(context) { - this.anchorX = undefined; - this.anchorY = undefined; - this.height = undefined; - 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; - }; +/** + * @inheritDoc + */ +ol.render.webgl.TextureReplay.prototype.drawHitDetectionReplayOneByOne = function(gl, context, skippedFeaturesHash, + featureCallback, opt_hitExtent) { + var i, groupStart, start, end, feature, featureUid; + var featureIndex = this.startIndices.length - 1; + var hitDetectionTextures = this.getHitDetectionTextures(); + for (i = hitDetectionTextures.length - 1; i >= 0; --i) { + gl.bindTexture(ol.webgl.TEXTURE_2D, hitDetectionTextures[i]); + groupStart = (i > 0) ? this.hitDetectionGroupIndices[i - 1] : 0; + end = this.hitDetectionGroupIndices[i]; + + // 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; +}; - /** - * @abstract - * @protected - * @param {boolean=} opt_all Return hit detection textures with regular ones. - * @returns {Array.} Textures. - */ - ol.render.webgl.TextureReplay.prototype.getTextures = function(opt_all) {}; +/** + * @inheritDoc + */ +ol.render.webgl.TextureReplay.prototype.finish = function(context) { + this.anchorX = undefined; + this.anchorY = undefined; + this.height = undefined; + 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; +}; - /** - * @abstract - * @protected - * @returns {Array.} Textures. - */ - ol.render.webgl.TextureReplay.prototype.getHitDetectionTextures = function() {}; -} +/** + * @abstract + * @protected + * @param {boolean=} opt_all Return hit detection textures with regular ones. + * @returns {Array.} Textures. + */ +ol.render.webgl.TextureReplay.prototype.getTextures = function(opt_all) {}; + + +/** + * @abstract + * @protected + * @returns {Array.} Textures. + */ +ol.render.webgl.TextureReplay.prototype.getHitDetectionTextures = function() {}; diff --git a/src/ol/render/webgl/texturereplay/defaultshader.js b/src/ol/render/webgl/texturereplay/defaultshader.js index a2459f0339..955210e673 100644 --- a/src/ol/render/webgl/texturereplay/defaultshader.js +++ b/src/ol/render/webgl/texturereplay/defaultshader.js @@ -6,149 +6,145 @@ goog.require('ol'); goog.require('ol.webgl.Fragment'); goog.require('ol.webgl.Vertex'); -if (ol.ENABLE_WEBGL) { +/** + * @constructor + * @extends {ol.webgl.Fragment} + * @struct + */ +ol.render.webgl.texturereplay.defaultshader.Fragment = function() { + ol.webgl.Fragment.call(this, ol.render.webgl.texturereplay.defaultshader.Fragment.SOURCE); +}; +ol.inherits(ol.render.webgl.texturereplay.defaultshader.Fragment, ol.webgl.Fragment); + + +/** + * @const + * @type {string} + */ +ol.render.webgl.texturereplay.defaultshader.Fragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\nvarying float v_opacity;\n\nuniform float u_opacity;\nuniform sampler2D u_image;\n\nvoid main(void) {\n vec4 texColor = texture2D(u_image, v_texCoord);\n gl_FragColor.rgb = texColor.rgb;\n float alpha = texColor.a * v_opacity * u_opacity;\n if (alpha == 0.0) {\n discard;\n }\n gl_FragColor.a = alpha;\n}\n'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.texturereplay.defaultshader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;varying float b;uniform float k;uniform sampler2D l;void main(void){vec4 texColor=texture2D(l,a);gl_FragColor.rgb=texColor.rgb;float alpha=texColor.a*b*k;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.texturereplay.defaultshader.Fragment.SOURCE = ol.DEBUG_WEBGL ? + ol.render.webgl.texturereplay.defaultshader.Fragment.DEBUG_SOURCE : + ol.render.webgl.texturereplay.defaultshader.Fragment.OPTIMIZED_SOURCE; + + +ol.render.webgl.texturereplay.defaultshader.fragment = new ol.render.webgl.texturereplay.defaultshader.Fragment(); + + +/** + * @constructor + * @extends {ol.webgl.Vertex} + * @struct + */ +ol.render.webgl.texturereplay.defaultshader.Vertex = function() { + ol.webgl.Vertex.call(this, ol.render.webgl.texturereplay.defaultshader.Vertex.SOURCE); +}; +ol.inherits(ol.render.webgl.texturereplay.defaultshader.Vertex, ol.webgl.Vertex); + + +/** + * @const + * @type {string} + */ +ol.render.webgl.texturereplay.defaultshader.Vertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\nvarying float v_opacity;\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\nattribute vec2 a_offsets;\nattribute float a_opacity;\nattribute float a_rotateWithView;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\n\nvoid main(void) {\n mat4 offsetMatrix = u_offsetScaleMatrix;\n if (a_rotateWithView == 1.0) {\n offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;\n }\n vec4 offsets = offsetMatrix * vec4(a_offsets, 0.0, 0.0);\n gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets;\n v_texCoord = a_texCoord;\n v_opacity = a_opacity;\n}\n\n\n'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.texturereplay.defaultshader.Vertex.OPTIMIZED_SOURCE = 'varying vec2 a;varying float b;attribute vec2 c;attribute vec2 d;attribute vec2 e;attribute float f;attribute float g;uniform mat4 h;uniform mat4 i;uniform mat4 j;void main(void){mat4 offsetMatrix=i;if(g==1.0){offsetMatrix=i*j;}vec4 offsets=offsetMatrix*vec4(e,0.0,0.0);gl_Position=h*vec4(c,0.0,1.0)+offsets;a=d;b=f;}'; + + +/** + * @const + * @type {string} + */ +ol.render.webgl.texturereplay.defaultshader.Vertex.SOURCE = ol.DEBUG_WEBGL ? + ol.render.webgl.texturereplay.defaultshader.Vertex.DEBUG_SOURCE : + ol.render.webgl.texturereplay.defaultshader.Vertex.OPTIMIZED_SOURCE; + + +ol.render.webgl.texturereplay.defaultshader.vertex = new ol.render.webgl.texturereplay.defaultshader.Vertex(); + + +/** + * @constructor + * @param {WebGLRenderingContext} gl GL. + * @param {WebGLProgram} program Program. + * @struct + */ +ol.render.webgl.texturereplay.defaultshader.Locations = function(gl, program) { /** - * @constructor - * @extends {ol.webgl.Fragment} - * @struct + * @type {WebGLUniformLocation} */ - ol.render.webgl.texturereplay.defaultshader.Fragment = function() { - ol.webgl.Fragment.call(this, ol.render.webgl.texturereplay.defaultshader.Fragment.SOURCE); - }; - ol.inherits(ol.render.webgl.texturereplay.defaultshader.Fragment, ol.webgl.Fragment); - + this.u_image = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_image' : 'l'); /** - * @const - * @type {string} + * @type {WebGLUniformLocation} */ - ol.render.webgl.texturereplay.defaultshader.Fragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\nvarying float v_opacity;\n\nuniform float u_opacity;\nuniform sampler2D u_image;\n\nvoid main(void) {\n vec4 texColor = texture2D(u_image, v_texCoord);\n gl_FragColor.rgb = texColor.rgb;\n float alpha = texColor.a * v_opacity * u_opacity;\n if (alpha == 0.0) {\n discard;\n }\n gl_FragColor.a = alpha;\n}\n'; - + this.u_offsetRotateMatrix = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_offsetRotateMatrix' : 'j'); /** - * @const - * @type {string} + * @type {WebGLUniformLocation} */ - ol.render.webgl.texturereplay.defaultshader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;varying float b;uniform float k;uniform sampler2D l;void main(void){vec4 texColor=texture2D(l,a);gl_FragColor.rgb=texColor.rgb;float alpha=texColor.a*b*k;if(alpha==0.0){discard;}gl_FragColor.a=alpha;}'; - + this.u_offsetScaleMatrix = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_offsetScaleMatrix' : 'i'); /** - * @const - * @type {string} + * @type {WebGLUniformLocation} */ - ol.render.webgl.texturereplay.defaultshader.Fragment.SOURCE = ol.DEBUG_WEBGL ? - ol.render.webgl.texturereplay.defaultshader.Fragment.DEBUG_SOURCE : - ol.render.webgl.texturereplay.defaultshader.Fragment.OPTIMIZED_SOURCE; - - - ol.render.webgl.texturereplay.defaultshader.fragment = new ol.render.webgl.texturereplay.defaultshader.Fragment(); - + this.u_opacity = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_opacity' : 'k'); /** - * @constructor - * @extends {ol.webgl.Vertex} - * @struct + * @type {WebGLUniformLocation} */ - ol.render.webgl.texturereplay.defaultshader.Vertex = function() { - ol.webgl.Vertex.call(this, ol.render.webgl.texturereplay.defaultshader.Vertex.SOURCE); - }; - ol.inherits(ol.render.webgl.texturereplay.defaultshader.Vertex, ol.webgl.Vertex); - + this.u_projectionMatrix = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_projectionMatrix' : 'h'); /** - * @const - * @type {string} + * @type {number} */ - ol.render.webgl.texturereplay.defaultshader.Vertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\nvarying float v_opacity;\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\nattribute vec2 a_offsets;\nattribute float a_opacity;\nattribute float a_rotateWithView;\n\nuniform mat4 u_projectionMatrix;\nuniform mat4 u_offsetScaleMatrix;\nuniform mat4 u_offsetRotateMatrix;\n\nvoid main(void) {\n mat4 offsetMatrix = u_offsetScaleMatrix;\n if (a_rotateWithView == 1.0) {\n offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;\n }\n vec4 offsets = offsetMatrix * vec4(a_offsets, 0.0, 0.0);\n gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets;\n v_texCoord = a_texCoord;\n v_opacity = a_opacity;\n}\n\n\n'; - + this.a_offsets = gl.getAttribLocation( + program, ol.DEBUG_WEBGL ? 'a_offsets' : 'e'); /** - * @const - * @type {string} + * @type {number} */ - ol.render.webgl.texturereplay.defaultshader.Vertex.OPTIMIZED_SOURCE = 'varying vec2 a;varying float b;attribute vec2 c;attribute vec2 d;attribute vec2 e;attribute float f;attribute float g;uniform mat4 h;uniform mat4 i;uniform mat4 j;void main(void){mat4 offsetMatrix=i;if(g==1.0){offsetMatrix=i*j;}vec4 offsets=offsetMatrix*vec4(e,0.0,0.0);gl_Position=h*vec4(c,0.0,1.0)+offsets;a=d;b=f;}'; - + this.a_opacity = gl.getAttribLocation( + program, ol.DEBUG_WEBGL ? 'a_opacity' : 'f'); /** - * @const - * @type {string} + * @type {number} */ - ol.render.webgl.texturereplay.defaultshader.Vertex.SOURCE = ol.DEBUG_WEBGL ? - ol.render.webgl.texturereplay.defaultshader.Vertex.DEBUG_SOURCE : - ol.render.webgl.texturereplay.defaultshader.Vertex.OPTIMIZED_SOURCE; - - - ol.render.webgl.texturereplay.defaultshader.vertex = new ol.render.webgl.texturereplay.defaultshader.Vertex(); - + this.a_position = gl.getAttribLocation( + program, ol.DEBUG_WEBGL ? 'a_position' : 'c'); /** - * @constructor - * @param {WebGLRenderingContext} gl GL. - * @param {WebGLProgram} program Program. - * @struct + * @type {number} */ - ol.render.webgl.texturereplay.defaultshader.Locations = function(gl, program) { + this.a_rotateWithView = gl.getAttribLocation( + program, ol.DEBUG_WEBGL ? 'a_rotateWithView' : 'g'); - /** - * @type {WebGLUniformLocation} - */ - this.u_image = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_image' : 'l'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_offsetRotateMatrix = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_offsetRotateMatrix' : 'j'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_offsetScaleMatrix = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_offsetScaleMatrix' : 'i'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_opacity = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_opacity' : 'k'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_projectionMatrix = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_projectionMatrix' : 'h'); - - /** - * @type {number} - */ - this.a_offsets = gl.getAttribLocation( - program, ol.DEBUG_WEBGL ? 'a_offsets' : 'e'); - - /** - * @type {number} - */ - this.a_opacity = gl.getAttribLocation( - program, ol.DEBUG_WEBGL ? 'a_opacity' : 'f'); - - /** - * @type {number} - */ - this.a_position = gl.getAttribLocation( - program, ol.DEBUG_WEBGL ? 'a_position' : 'c'); - - /** - * @type {number} - */ - this.a_rotateWithView = gl.getAttribLocation( - program, ol.DEBUG_WEBGL ? 'a_rotateWithView' : 'g'); - - /** - * @type {number} - */ - this.a_texCoord = gl.getAttribLocation( - program, ol.DEBUG_WEBGL ? 'a_texCoord' : 'd'); - }; - -} + /** + * @type {number} + */ + this.a_texCoord = gl.getAttribLocation( + program, ol.DEBUG_WEBGL ? 'a_texCoord' : 'd'); +}; diff --git a/src/ol/renderer/webgl/defaultmapshader.js b/src/ol/renderer/webgl/defaultmapshader.js index d315b01b1e..c881570943 100644 --- a/src/ol/renderer/webgl/defaultmapshader.js +++ b/src/ol/renderer/webgl/defaultmapshader.js @@ -6,125 +6,121 @@ goog.require('ol'); goog.require('ol.webgl.Fragment'); goog.require('ol.webgl.Vertex'); -if (ol.ENABLE_WEBGL) { +/** + * @constructor + * @extends {ol.webgl.Fragment} + * @struct + */ +ol.renderer.webgl.defaultmapshader.Fragment = function() { + ol.webgl.Fragment.call(this, ol.renderer.webgl.defaultmapshader.Fragment.SOURCE); +}; +ol.inherits(ol.renderer.webgl.defaultmapshader.Fragment, ol.webgl.Fragment); + + +/** + * @const + * @type {string} + */ +ol.renderer.webgl.defaultmapshader.Fragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\n\n\nuniform float u_opacity;\nuniform sampler2D u_texture;\n\nvoid main(void) {\n vec4 texColor = texture2D(u_texture, v_texCoord);\n gl_FragColor.rgb = texColor.rgb;\n gl_FragColor.a = texColor.a * u_opacity;\n}\n'; + + +/** + * @const + * @type {string} + */ +ol.renderer.webgl.defaultmapshader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;uniform float f;uniform sampler2D g;void main(void){vec4 texColor=texture2D(g,a);gl_FragColor.rgb=texColor.rgb;gl_FragColor.a=texColor.a*f;}'; + + +/** + * @const + * @type {string} + */ +ol.renderer.webgl.defaultmapshader.Fragment.SOURCE = ol.DEBUG_WEBGL ? + ol.renderer.webgl.defaultmapshader.Fragment.DEBUG_SOURCE : + ol.renderer.webgl.defaultmapshader.Fragment.OPTIMIZED_SOURCE; + + +ol.renderer.webgl.defaultmapshader.fragment = new ol.renderer.webgl.defaultmapshader.Fragment(); + + +/** + * @constructor + * @extends {ol.webgl.Vertex} + * @struct + */ +ol.renderer.webgl.defaultmapshader.Vertex = function() { + ol.webgl.Vertex.call(this, ol.renderer.webgl.defaultmapshader.Vertex.SOURCE); +}; +ol.inherits(ol.renderer.webgl.defaultmapshader.Vertex, ol.webgl.Vertex); + + +/** + * @const + * @type {string} + */ +ol.renderer.webgl.defaultmapshader.Vertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\n\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\n\nuniform mat4 u_texCoordMatrix;\nuniform mat4 u_projectionMatrix;\n\nvoid main(void) {\n gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.);\n v_texCoord = (u_texCoordMatrix * vec4(a_texCoord, 0., 1.)).st;\n}\n\n\n'; + + +/** + * @const + * @type {string} + */ +ol.renderer.webgl.defaultmapshader.Vertex.OPTIMIZED_SOURCE = 'varying vec2 a;attribute vec2 b;attribute vec2 c;uniform mat4 d;uniform mat4 e;void main(void){gl_Position=e*vec4(b,0.,1.);a=(d*vec4(c,0.,1.)).st;}'; + + +/** + * @const + * @type {string} + */ +ol.renderer.webgl.defaultmapshader.Vertex.SOURCE = ol.DEBUG_WEBGL ? + ol.renderer.webgl.defaultmapshader.Vertex.DEBUG_SOURCE : + ol.renderer.webgl.defaultmapshader.Vertex.OPTIMIZED_SOURCE; + + +ol.renderer.webgl.defaultmapshader.vertex = new ol.renderer.webgl.defaultmapshader.Vertex(); + + +/** + * @constructor + * @param {WebGLRenderingContext} gl GL. + * @param {WebGLProgram} program Program. + * @struct + */ +ol.renderer.webgl.defaultmapshader.Locations = function(gl, program) { /** - * @constructor - * @extends {ol.webgl.Fragment} - * @struct + * @type {WebGLUniformLocation} */ - ol.renderer.webgl.defaultmapshader.Fragment = function() { - ol.webgl.Fragment.call(this, ol.renderer.webgl.defaultmapshader.Fragment.SOURCE); - }; - ol.inherits(ol.renderer.webgl.defaultmapshader.Fragment, ol.webgl.Fragment); - + this.u_opacity = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_opacity' : 'f'); /** - * @const - * @type {string} + * @type {WebGLUniformLocation} */ - ol.renderer.webgl.defaultmapshader.Fragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\n\n\nuniform float u_opacity;\nuniform sampler2D u_texture;\n\nvoid main(void) {\n vec4 texColor = texture2D(u_texture, v_texCoord);\n gl_FragColor.rgb = texColor.rgb;\n gl_FragColor.a = texColor.a * u_opacity;\n}\n'; - + this.u_projectionMatrix = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_projectionMatrix' : 'e'); /** - * @const - * @type {string} + * @type {WebGLUniformLocation} */ - ol.renderer.webgl.defaultmapshader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;uniform float f;uniform sampler2D g;void main(void){vec4 texColor=texture2D(g,a);gl_FragColor.rgb=texColor.rgb;gl_FragColor.a=texColor.a*f;}'; - + this.u_texCoordMatrix = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_texCoordMatrix' : 'd'); /** - * @const - * @type {string} + * @type {WebGLUniformLocation} */ - ol.renderer.webgl.defaultmapshader.Fragment.SOURCE = ol.DEBUG_WEBGL ? - ol.renderer.webgl.defaultmapshader.Fragment.DEBUG_SOURCE : - ol.renderer.webgl.defaultmapshader.Fragment.OPTIMIZED_SOURCE; - - - ol.renderer.webgl.defaultmapshader.fragment = new ol.renderer.webgl.defaultmapshader.Fragment(); - + this.u_texture = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_texture' : 'g'); /** - * @constructor - * @extends {ol.webgl.Vertex} - * @struct + * @type {number} */ - ol.renderer.webgl.defaultmapshader.Vertex = function() { - ol.webgl.Vertex.call(this, ol.renderer.webgl.defaultmapshader.Vertex.SOURCE); - }; - ol.inherits(ol.renderer.webgl.defaultmapshader.Vertex, ol.webgl.Vertex); - + this.a_position = gl.getAttribLocation( + program, ol.DEBUG_WEBGL ? 'a_position' : 'b'); /** - * @const - * @type {string} + * @type {number} */ - ol.renderer.webgl.defaultmapshader.Vertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\n\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\n\nuniform mat4 u_texCoordMatrix;\nuniform mat4 u_projectionMatrix;\n\nvoid main(void) {\n gl_Position = u_projectionMatrix * vec4(a_position, 0., 1.);\n v_texCoord = (u_texCoordMatrix * vec4(a_texCoord, 0., 1.)).st;\n}\n\n\n'; - - - /** - * @const - * @type {string} - */ - ol.renderer.webgl.defaultmapshader.Vertex.OPTIMIZED_SOURCE = 'varying vec2 a;attribute vec2 b;attribute vec2 c;uniform mat4 d;uniform mat4 e;void main(void){gl_Position=e*vec4(b,0.,1.);a=(d*vec4(c,0.,1.)).st;}'; - - - /** - * @const - * @type {string} - */ - ol.renderer.webgl.defaultmapshader.Vertex.SOURCE = ol.DEBUG_WEBGL ? - ol.renderer.webgl.defaultmapshader.Vertex.DEBUG_SOURCE : - ol.renderer.webgl.defaultmapshader.Vertex.OPTIMIZED_SOURCE; - - - ol.renderer.webgl.defaultmapshader.vertex = new ol.renderer.webgl.defaultmapshader.Vertex(); - - - /** - * @constructor - * @param {WebGLRenderingContext} gl GL. - * @param {WebGLProgram} program Program. - * @struct - */ - ol.renderer.webgl.defaultmapshader.Locations = function(gl, program) { - - /** - * @type {WebGLUniformLocation} - */ - this.u_opacity = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_opacity' : 'f'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_projectionMatrix = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_projectionMatrix' : 'e'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_texCoordMatrix = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_texCoordMatrix' : 'd'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_texture = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_texture' : 'g'); - - /** - * @type {number} - */ - this.a_position = gl.getAttribLocation( - program, ol.DEBUG_WEBGL ? 'a_position' : 'b'); - - /** - * @type {number} - */ - this.a_texCoord = gl.getAttribLocation( - program, ol.DEBUG_WEBGL ? 'a_texCoord' : 'c'); - }; - -} + this.a_texCoord = gl.getAttribLocation( + program, ol.DEBUG_WEBGL ? 'a_texCoord' : 'c'); +}; diff --git a/src/ol/renderer/webgl/imagelayer.js b/src/ol/renderer/webgl/imagelayer.js index 0145865658..ba81204703 100644 --- a/src/ol/renderer/webgl/imagelayer.js +++ b/src/ol/renderer/webgl/imagelayer.js @@ -14,324 +14,320 @@ goog.require('ol.webgl'); goog.require('ol.webgl.Context'); -if (ol.ENABLE_WEBGL) { +/** + * @constructor + * @extends {ol.renderer.webgl.Layer} + * @param {ol.renderer.webgl.Map} mapRenderer Map renderer. + * @param {ol.layer.Image} imageLayer Tile layer. + * @api + */ +ol.renderer.webgl.ImageLayer = function(mapRenderer, imageLayer) { + + ol.renderer.webgl.Layer.call(this, mapRenderer, imageLayer); /** - * @constructor - * @extends {ol.renderer.webgl.Layer} - * @param {ol.renderer.webgl.Map} mapRenderer Map renderer. - * @param {ol.layer.Image} imageLayer Tile layer. - * @api + * The last rendered image. + * @private + * @type {?ol.ImageBase} */ - ol.renderer.webgl.ImageLayer = function(mapRenderer, imageLayer) { + this.image_ = null; - ol.renderer.webgl.Layer.call(this, mapRenderer, imageLayer); + /** + * @private + * @type {CanvasRenderingContext2D} + */ + this.hitCanvasContext_ = null; - /** - * The last rendered image. - * @private - * @type {?ol.ImageBase} - */ - this.image_ = null; + /** + * @private + * @type {?ol.Transform} + */ + this.hitTransformationMatrix_ = null; - /** - * @private - * @type {CanvasRenderingContext2D} - */ - this.hitCanvasContext_ = null; +}; +ol.inherits(ol.renderer.webgl.ImageLayer, ol.renderer.webgl.Layer); - /** - * @private - * @type {?ol.Transform} - */ + +/** + * Determine if this renderer handles the provided layer. + * @param {ol.renderer.Type} type The renderer type. + * @param {ol.layer.Layer} layer The candidate layer. + * @return {boolean} The renderer can render the layer. + */ +ol.renderer.webgl.ImageLayer['handles'] = function(type, layer) { + return type === ol.renderer.Type.WEBGL && layer.getType() === ol.LayerType.IMAGE; +}; + + +/** + * Create a layer renderer. + * @param {ol.renderer.Map} mapRenderer The map renderer. + * @param {ol.layer.Layer} layer The layer to be rendererd. + * @return {ol.renderer.webgl.ImageLayer} The layer renderer. + */ +ol.renderer.webgl.ImageLayer['create'] = function(mapRenderer, layer) { + return new ol.renderer.webgl.ImageLayer( + /** @type {ol.renderer.webgl.Map} */ (mapRenderer), + /** @type {ol.layer.Image} */ (layer) + ); +}; + + +/** + * @param {ol.ImageBase} image Image. + * @private + * @return {WebGLTexture} Texture. + */ +ol.renderer.webgl.ImageLayer.prototype.createTexture_ = function(image) { + + // We meet the conditions to work with non-power of two textures. + // http://www.khronos.org/webgl/wiki/WebGL_and_OpenGL_Differences#Non-Power_of_Two_Texture_Support + // http://learningwebgl.com/blog/?p=2101 + + var imageElement = image.getImage(); + var gl = this.mapRenderer.getGL(); + + return ol.webgl.Context.createTexture( + gl, imageElement, ol.webgl.CLAMP_TO_EDGE, ol.webgl.CLAMP_TO_EDGE); +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.ImageLayer.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, callback, thisArg) { + var layer = this.getLayer(); + var source = layer.getSource(); + var resolution = frameState.viewState.resolution; + var rotation = frameState.viewState.rotation; + var skippedFeatureUids = frameState.skippedFeatureUids; + return source.forEachFeatureAtCoordinate( + coordinate, resolution, rotation, hitTolerance, skippedFeatureUids, + + /** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @return {?} Callback result. + */ + function(feature) { + return callback.call(thisArg, feature, layer); + }); +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.ImageLayer.prototype.prepareFrame = function(frameState, layerState, context) { + + var gl = this.mapRenderer.getGL(); + + var pixelRatio = frameState.pixelRatio; + var viewState = frameState.viewState; + var viewCenter = viewState.center; + var viewResolution = viewState.resolution; + var viewRotation = viewState.rotation; + + var image = this.image_; + var texture = this.texture; + var imageLayer = /** @type {ol.layer.Image} */ (this.getLayer()); + var imageSource = imageLayer.getSource(); + + var hints = frameState.viewHints; + + var renderedExtent = frameState.extent; + if (layerState.extent !== undefined) { + renderedExtent = ol.extent.getIntersection( + renderedExtent, layerState.extent); + } + if (!hints[ol.ViewHint.ANIMATING] && !hints[ol.ViewHint.INTERACTING] && + !ol.extent.isEmpty(renderedExtent)) { + var projection = viewState.projection; + if (!ol.ENABLE_RASTER_REPROJECTION) { + var sourceProjection = imageSource.getProjection(); + if (sourceProjection) { + projection = sourceProjection; + } + } + var image_ = imageSource.getImage(renderedExtent, viewResolution, + pixelRatio, projection); + if (image_) { + var loaded = this.loadImage(image_); + if (loaded) { + image = image_; + texture = this.createTexture_(image_); + if (this.texture) { + /** + * @param {WebGLRenderingContext} gl GL. + * @param {WebGLTexture} texture Texture. + */ + var postRenderFunction = function(gl, texture) { + if (!gl.isContextLost()) { + gl.deleteTexture(texture); + } + }.bind(null, gl, this.texture); + frameState.postRenderFunctions.push( + /** @type {ol.PostRenderFunction} */ (postRenderFunction) + ); + } + } + } + } + + if (image) { + var canvas = this.mapRenderer.getContext().getCanvas(); + + this.updateProjectionMatrix_(canvas.width, canvas.height, + pixelRatio, viewCenter, viewResolution, viewRotation, + image.getExtent()); this.hitTransformationMatrix_ = null; - }; - ol.inherits(ol.renderer.webgl.ImageLayer, ol.renderer.webgl.Layer); + // Translate and scale to flip the Y coord. + var texCoordMatrix = this.texCoordMatrix; + ol.transform.reset(texCoordMatrix); + ol.transform.scale(texCoordMatrix, 1, -1); + ol.transform.translate(texCoordMatrix, 0, -1); + + this.image_ = image; + this.texture = texture; + + this.updateAttributions(frameState.attributions, image.getAttributions()); + this.updateLogos(frameState, imageSource); + } + + return !!image; +}; - /** - * Determine if this renderer handles the provided layer. - * @param {ol.renderer.Type} type The renderer type. - * @param {ol.layer.Layer} layer The candidate layer. - * @return {boolean} The renderer can render the layer. - */ - ol.renderer.webgl.ImageLayer['handles'] = function(type, layer) { - return type === ol.renderer.Type.WEBGL && layer.getType() === ol.LayerType.IMAGE; - }; +/** + * @param {number} canvasWidth Canvas width. + * @param {number} canvasHeight Canvas height. + * @param {number} pixelRatio Pixel ratio. + * @param {ol.Coordinate} viewCenter View center. + * @param {number} viewResolution View resolution. + * @param {number} viewRotation View rotation. + * @param {ol.Extent} imageExtent Image extent. + * @private + */ +ol.renderer.webgl.ImageLayer.prototype.updateProjectionMatrix_ = function(canvasWidth, canvasHeight, pixelRatio, + viewCenter, viewResolution, viewRotation, imageExtent) { + + var canvasExtentWidth = canvasWidth * viewResolution; + var canvasExtentHeight = canvasHeight * viewResolution; + + var projectionMatrix = this.projectionMatrix; + ol.transform.reset(projectionMatrix); + ol.transform.scale(projectionMatrix, + pixelRatio * 2 / canvasExtentWidth, + pixelRatio * 2 / canvasExtentHeight); + ol.transform.rotate(projectionMatrix, -viewRotation); + ol.transform.translate(projectionMatrix, + imageExtent[0] - viewCenter[0], + imageExtent[1] - viewCenter[1]); + ol.transform.scale(projectionMatrix, + (imageExtent[2] - imageExtent[0]) / 2, + (imageExtent[3] - imageExtent[1]) / 2); + ol.transform.translate(projectionMatrix, 1, 1); + +}; - /** - * Create a layer renderer. - * @param {ol.renderer.Map} mapRenderer The map renderer. - * @param {ol.layer.Layer} layer The layer to be rendererd. - * @return {ol.renderer.webgl.ImageLayer} The layer renderer. - */ - ol.renderer.webgl.ImageLayer['create'] = function(mapRenderer, layer) { - return new ol.renderer.webgl.ImageLayer( - /** @type {ol.renderer.webgl.Map} */ (mapRenderer), - /** @type {ol.layer.Image} */ (layer) - ); - }; +/** + * @inheritDoc + */ +ol.renderer.webgl.ImageLayer.prototype.hasFeatureAtCoordinate = function(coordinate, frameState) { + var hasFeature = this.forEachFeatureAtCoordinate( + coordinate, frameState, 0, ol.functions.TRUE, this); + return hasFeature !== undefined; +}; - /** - * @param {ol.ImageBase} image Image. - * @private - * @return {WebGLTexture} Texture. - */ - ol.renderer.webgl.ImageLayer.prototype.createTexture_ = function(image) { +/** + * @inheritDoc + */ +ol.renderer.webgl.ImageLayer.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg) { + if (!this.image_ || !this.image_.getImage()) { + return undefined; + } - // We meet the conditions to work with non-power of two textures. - // http://www.khronos.org/webgl/wiki/WebGL_and_OpenGL_Differences#Non-Power_of_Two_Texture_Support - // http://learningwebgl.com/blog/?p=2101 - - var imageElement = image.getImage(); - var gl = this.mapRenderer.getGL(); - - return ol.webgl.Context.createTexture( - gl, imageElement, ol.webgl.CLAMP_TO_EDGE, ol.webgl.CLAMP_TO_EDGE); - }; - - - /** - * @inheritDoc - */ - ol.renderer.webgl.ImageLayer.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, callback, thisArg) { - var layer = this.getLayer(); - var source = layer.getSource(); - var resolution = frameState.viewState.resolution; - var rotation = frameState.viewState.rotation; - var skippedFeatureUids = frameState.skippedFeatureUids; - return source.forEachFeatureAtCoordinate( - coordinate, resolution, rotation, hitTolerance, skippedFeatureUids, - - /** - * @param {ol.Feature|ol.render.Feature} feature Feature. - * @return {?} Callback result. - */ - function(feature) { - return callback.call(thisArg, feature, layer); - }); - }; - - - /** - * @inheritDoc - */ - ol.renderer.webgl.ImageLayer.prototype.prepareFrame = function(frameState, layerState, context) { - - var gl = this.mapRenderer.getGL(); - - var pixelRatio = frameState.pixelRatio; - var viewState = frameState.viewState; - var viewCenter = viewState.center; - var viewResolution = viewState.resolution; - var viewRotation = viewState.rotation; - - var image = this.image_; - var texture = this.texture; - var imageLayer = /** @type {ol.layer.Image} */ (this.getLayer()); - var imageSource = imageLayer.getSource(); - - var hints = frameState.viewHints; - - var renderedExtent = frameState.extent; - if (layerState.extent !== undefined) { - renderedExtent = ol.extent.getIntersection( - renderedExtent, layerState.extent); - } - if (!hints[ol.ViewHint.ANIMATING] && !hints[ol.ViewHint.INTERACTING] && - !ol.extent.isEmpty(renderedExtent)) { - var projection = viewState.projection; - if (!ol.ENABLE_RASTER_REPROJECTION) { - var sourceProjection = imageSource.getProjection(); - if (sourceProjection) { - projection = sourceProjection; - } - } - var image_ = imageSource.getImage(renderedExtent, viewResolution, - pixelRatio, projection); - if (image_) { - var loaded = this.loadImage(image_); - if (loaded) { - image = image_; - texture = this.createTexture_(image_); - if (this.texture) { - /** - * @param {WebGLRenderingContext} gl GL. - * @param {WebGLTexture} texture Texture. - */ - var postRenderFunction = function(gl, texture) { - if (!gl.isContextLost()) { - gl.deleteTexture(texture); - } - }.bind(null, gl, this.texture); - frameState.postRenderFunctions.push( - /** @type {ol.PostRenderFunction} */ (postRenderFunction) - ); - } - } - } - } - - if (image) { - var canvas = this.mapRenderer.getContext().getCanvas(); - - this.updateProjectionMatrix_(canvas.width, canvas.height, - pixelRatio, viewCenter, viewResolution, viewRotation, - image.getExtent()); - this.hitTransformationMatrix_ = null; - - // Translate and scale to flip the Y coord. - var texCoordMatrix = this.texCoordMatrix; - ol.transform.reset(texCoordMatrix); - ol.transform.scale(texCoordMatrix, 1, -1); - ol.transform.translate(texCoordMatrix, 0, -1); - - this.image_ = image; - this.texture = texture; - - this.updateAttributions(frameState.attributions, image.getAttributions()); - this.updateLogos(frameState, imageSource); - } - - return !!image; - }; - - - /** - * @param {number} canvasWidth Canvas width. - * @param {number} canvasHeight Canvas height. - * @param {number} pixelRatio Pixel ratio. - * @param {ol.Coordinate} viewCenter View center. - * @param {number} viewResolution View resolution. - * @param {number} viewRotation View rotation. - * @param {ol.Extent} imageExtent Image extent. - * @private - */ - ol.renderer.webgl.ImageLayer.prototype.updateProjectionMatrix_ = function(canvasWidth, canvasHeight, pixelRatio, - viewCenter, viewResolution, viewRotation, imageExtent) { - - var canvasExtentWidth = canvasWidth * viewResolution; - var canvasExtentHeight = canvasHeight * viewResolution; - - var projectionMatrix = this.projectionMatrix; - ol.transform.reset(projectionMatrix); - ol.transform.scale(projectionMatrix, - pixelRatio * 2 / canvasExtentWidth, - pixelRatio * 2 / canvasExtentHeight); - ol.transform.rotate(projectionMatrix, -viewRotation); - ol.transform.translate(projectionMatrix, - imageExtent[0] - viewCenter[0], - imageExtent[1] - viewCenter[1]); - ol.transform.scale(projectionMatrix, - (imageExtent[2] - imageExtent[0]) / 2, - (imageExtent[3] - imageExtent[1]) / 2); - ol.transform.translate(projectionMatrix, 1, 1); - - }; - - - /** - * @inheritDoc - */ - ol.renderer.webgl.ImageLayer.prototype.hasFeatureAtCoordinate = function(coordinate, frameState) { + if (this.getLayer().getSource() instanceof ol.source.ImageVector) { + // for ImageVector sources use the original hit-detection logic, + // so that for example also transparent polygons are detected + var coordinate = ol.transform.apply( + frameState.pixelToCoordinateTransform, pixel.slice()); var hasFeature = this.forEachFeatureAtCoordinate( coordinate, frameState, 0, ol.functions.TRUE, this); - return hasFeature !== undefined; - }; + if (hasFeature) { + return callback.call(thisArg, this.getLayer(), null); + } else { + return undefined; + } + } else { + var imageSize = + [this.image_.getImage().width, this.image_.getImage().height]; - /** - * @inheritDoc - */ - ol.renderer.webgl.ImageLayer.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg) { - if (!this.image_ || !this.image_.getImage()) { + if (!this.hitTransformationMatrix_) { + this.hitTransformationMatrix_ = this.getHitTransformationMatrix_( + frameState.size, imageSize); + } + + var pixelOnFrameBuffer = ol.transform.apply( + this.hitTransformationMatrix_, pixel.slice()); + + if (pixelOnFrameBuffer[0] < 0 || pixelOnFrameBuffer[0] > imageSize[0] || + pixelOnFrameBuffer[1] < 0 || pixelOnFrameBuffer[1] > imageSize[1]) { + // outside the image, no need to check return undefined; } - if (this.getLayer().getSource() instanceof ol.source.ImageVector) { - // for ImageVector sources use the original hit-detection logic, - // so that for example also transparent polygons are detected - var coordinate = ol.transform.apply( - frameState.pixelToCoordinateTransform, pixel.slice()); - var hasFeature = this.forEachFeatureAtCoordinate( - coordinate, frameState, 0, ol.functions.TRUE, this); - - if (hasFeature) { - return callback.call(thisArg, this.getLayer(), null); - } else { - return undefined; - } - } else { - var imageSize = - [this.image_.getImage().width, this.image_.getImage().height]; - - if (!this.hitTransformationMatrix_) { - this.hitTransformationMatrix_ = this.getHitTransformationMatrix_( - frameState.size, imageSize); - } - - var pixelOnFrameBuffer = ol.transform.apply( - this.hitTransformationMatrix_, pixel.slice()); - - if (pixelOnFrameBuffer[0] < 0 || pixelOnFrameBuffer[0] > imageSize[0] || - pixelOnFrameBuffer[1] < 0 || pixelOnFrameBuffer[1] > imageSize[1]) { - // outside the image, no need to check - return undefined; - } - - if (!this.hitCanvasContext_) { - this.hitCanvasContext_ = ol.dom.createCanvasContext2D(1, 1); - } - - this.hitCanvasContext_.clearRect(0, 0, 1, 1); - this.hitCanvasContext_.drawImage(this.image_.getImage(), - pixelOnFrameBuffer[0], pixelOnFrameBuffer[1], 1, 1, 0, 0, 1, 1); - - var imageData = this.hitCanvasContext_.getImageData(0, 0, 1, 1).data; - if (imageData[3] > 0) { - return callback.call(thisArg, this.getLayer(), imageData); - } else { - return undefined; - } + if (!this.hitCanvasContext_) { + this.hitCanvasContext_ = ol.dom.createCanvasContext2D(1, 1); } - }; + + this.hitCanvasContext_.clearRect(0, 0, 1, 1); + this.hitCanvasContext_.drawImage(this.image_.getImage(), + pixelOnFrameBuffer[0], pixelOnFrameBuffer[1], 1, 1, 0, 0, 1, 1); + + var imageData = this.hitCanvasContext_.getImageData(0, 0, 1, 1).data; + if (imageData[3] > 0) { + return callback.call(thisArg, this.getLayer(), imageData); + } else { + return undefined; + } + } +}; - /** - * The transformation matrix to get the pixel on the image for a - * pixel on the map. - * @param {ol.Size} mapSize The map size. - * @param {ol.Size} imageSize The image size. - * @return {ol.Transform} The transformation matrix. - * @private - */ - ol.renderer.webgl.ImageLayer.prototype.getHitTransformationMatrix_ = function(mapSize, imageSize) { - // the first matrix takes a map pixel, flips the y-axis and scales to - // a range between -1 ... 1 - var mapCoordTransform = ol.transform.create(); - ol.transform.translate(mapCoordTransform, -1, -1); - ol.transform.scale(mapCoordTransform, 2 / mapSize[0], 2 / mapSize[1]); - ol.transform.translate(mapCoordTransform, 0, mapSize[1]); - ol.transform.scale(mapCoordTransform, 1, -1); +/** + * The transformation matrix to get the pixel on the image for a + * pixel on the map. + * @param {ol.Size} mapSize The map size. + * @param {ol.Size} imageSize The image size. + * @return {ol.Transform} The transformation matrix. + * @private + */ +ol.renderer.webgl.ImageLayer.prototype.getHitTransformationMatrix_ = function(mapSize, imageSize) { + // the first matrix takes a map pixel, flips the y-axis and scales to + // a range between -1 ... 1 + var mapCoordTransform = ol.transform.create(); + ol.transform.translate(mapCoordTransform, -1, -1); + ol.transform.scale(mapCoordTransform, 2 / mapSize[0], 2 / mapSize[1]); + ol.transform.translate(mapCoordTransform, 0, mapSize[1]); + ol.transform.scale(mapCoordTransform, 1, -1); - // the second matrix is the inverse of the projection matrix used in the - // shader for drawing - var projectionMatrixInv = ol.transform.invert(this.projectionMatrix.slice()); + // the second matrix is the inverse of the projection matrix used in the + // shader for drawing + var projectionMatrixInv = ol.transform.invert(this.projectionMatrix.slice()); - // the third matrix scales to the image dimensions and flips the y-axis again - var transform = ol.transform.create(); - ol.transform.translate(transform, 0, imageSize[1]); - ol.transform.scale(transform, 1, -1); - ol.transform.scale(transform, imageSize[0] / 2, imageSize[1] / 2); - ol.transform.translate(transform, 1, 1); + // the third matrix scales to the image dimensions and flips the y-axis again + var transform = ol.transform.create(); + ol.transform.translate(transform, 0, imageSize[1]); + ol.transform.scale(transform, 1, -1); + ol.transform.scale(transform, imageSize[0] / 2, imageSize[1] / 2); + ol.transform.translate(transform, 1, 1); - ol.transform.multiply(transform, projectionMatrixInv); - ol.transform.multiply(transform, mapCoordTransform); + ol.transform.multiply(transform, projectionMatrixInv); + ol.transform.multiply(transform, mapCoordTransform); - return transform; - }; - -} + return transform; +}; diff --git a/src/ol/renderer/webgl/layer.js b/src/ol/renderer/webgl/layer.js index 9f557a71c7..46b1f82f24 100644 --- a/src/ol/renderer/webgl/layer.js +++ b/src/ol/renderer/webgl/layer.js @@ -13,260 +13,256 @@ goog.require('ol.webgl.Buffer'); goog.require('ol.webgl.Context'); -if (ol.ENABLE_WEBGL) { +/** + * @constructor + * @abstract + * @extends {ol.renderer.Layer} + * @param {ol.renderer.webgl.Map} mapRenderer Map renderer. + * @param {ol.layer.Layer} layer Layer. + */ +ol.renderer.webgl.Layer = function(mapRenderer, layer) { + + ol.renderer.Layer.call(this, layer); /** - * @constructor - * @abstract - * @extends {ol.renderer.Layer} - * @param {ol.renderer.webgl.Map} mapRenderer Map renderer. - * @param {ol.layer.Layer} layer Layer. - */ - ol.renderer.webgl.Layer = function(mapRenderer, layer) { - - ol.renderer.Layer.call(this, layer); - - /** - * @protected - * @type {ol.renderer.webgl.Map} - */ - this.mapRenderer = mapRenderer; - - /** - * @private - * @type {ol.webgl.Buffer} - */ - this.arrayBuffer_ = new ol.webgl.Buffer([ - -1, -1, 0, 0, - 1, -1, 1, 0, - -1, 1, 0, 1, - 1, 1, 1, 1 - ]); - - /** - * @protected - * @type {WebGLTexture} - */ - this.texture = null; - - /** - * @protected - * @type {WebGLFramebuffer} - */ - this.framebuffer = null; - - /** - * @protected - * @type {number|undefined} - */ - this.framebufferDimension = undefined; - - /** - * @protected - * @type {ol.Transform} - */ - this.texCoordMatrix = ol.transform.create(); - - /** - * @protected - * @type {ol.Transform} - */ - this.projectionMatrix = ol.transform.create(); - - /** - * @type {Array.} - * @private - */ - this.tmpMat4_ = ol.vec.Mat4.create(); - - /** - * @private - * @type {ol.renderer.webgl.defaultmapshader.Locations} - */ - this.defaultLocations_ = null; - - }; - ol.inherits(ol.renderer.webgl.Layer, ol.renderer.Layer); - - - /** - * @param {olx.FrameState} frameState Frame state. - * @param {number} framebufferDimension Framebuffer dimension. * @protected + * @type {ol.renderer.webgl.Map} */ - ol.renderer.webgl.Layer.prototype.bindFramebuffer = function(frameState, framebufferDimension) { - - var gl = this.mapRenderer.getGL(); - - if (this.framebufferDimension === undefined || - this.framebufferDimension != framebufferDimension) { - /** - * @param {WebGLRenderingContext} gl GL. - * @param {WebGLFramebuffer} framebuffer Framebuffer. - * @param {WebGLTexture} texture Texture. - */ - var postRenderFunction = function(gl, framebuffer, texture) { - if (!gl.isContextLost()) { - gl.deleteFramebuffer(framebuffer); - gl.deleteTexture(texture); - } - }.bind(null, gl, this.framebuffer, this.texture); - - frameState.postRenderFunctions.push( - /** @type {ol.PostRenderFunction} */ (postRenderFunction) - ); - - var texture = ol.webgl.Context.createEmptyTexture( - gl, framebufferDimension, framebufferDimension); - - var framebuffer = gl.createFramebuffer(); - gl.bindFramebuffer(ol.webgl.FRAMEBUFFER, framebuffer); - gl.framebufferTexture2D(ol.webgl.FRAMEBUFFER, - ol.webgl.COLOR_ATTACHMENT0, ol.webgl.TEXTURE_2D, texture, 0); - - this.texture = texture; - this.framebuffer = framebuffer; - this.framebufferDimension = framebufferDimension; - - } else { - gl.bindFramebuffer(ol.webgl.FRAMEBUFFER, this.framebuffer); - } - - }; - + this.mapRenderer = mapRenderer; /** - * @param {olx.FrameState} frameState Frame state. - * @param {ol.LayerState} layerState Layer state. - * @param {ol.webgl.Context} context Context. + * @private + * @type {ol.webgl.Buffer} */ - ol.renderer.webgl.Layer.prototype.composeFrame = function(frameState, layerState, context) { - - this.dispatchComposeEvent_( - ol.render.EventType.PRECOMPOSE, context, frameState); - - context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.arrayBuffer_); - - var gl = context.getGL(); - - var fragmentShader = ol.renderer.webgl.defaultmapshader.fragment; - var vertexShader = ol.renderer.webgl.defaultmapshader.vertex; - - var program = context.getProgram(fragmentShader, vertexShader); - - var locations; - if (!this.defaultLocations_) { - // eslint-disable-next-line openlayers-internal/no-missing-requires - locations = new ol.renderer.webgl.defaultmapshader.Locations(gl, program); - this.defaultLocations_ = locations; - } else { - locations = this.defaultLocations_; - } - - if (context.useProgram(program)) { - gl.enableVertexAttribArray(locations.a_position); - gl.vertexAttribPointer( - locations.a_position, 2, ol.webgl.FLOAT, false, 16, 0); - gl.enableVertexAttribArray(locations.a_texCoord); - gl.vertexAttribPointer( - locations.a_texCoord, 2, ol.webgl.FLOAT, false, 16, 8); - gl.uniform1i(locations.u_texture, 0); - } - - gl.uniformMatrix4fv(locations.u_texCoordMatrix, false, - ol.vec.Mat4.fromTransform(this.tmpMat4_, this.getTexCoordMatrix())); - gl.uniformMatrix4fv(locations.u_projectionMatrix, false, - ol.vec.Mat4.fromTransform(this.tmpMat4_, this.getProjectionMatrix())); - gl.uniform1f(locations.u_opacity, layerState.opacity); - gl.bindTexture(ol.webgl.TEXTURE_2D, this.getTexture()); - gl.drawArrays(ol.webgl.TRIANGLE_STRIP, 0, 4); - - this.dispatchComposeEvent_( - ol.render.EventType.POSTCOMPOSE, context, frameState); - - }; - + this.arrayBuffer_ = new ol.webgl.Buffer([ + -1, -1, 0, 0, + 1, -1, 1, 0, + -1, 1, 0, 1, + 1, 1, 1, 1 + ]); /** - * @param {ol.render.EventType} type Event type. - * @param {ol.webgl.Context} context WebGL context. - * @param {olx.FrameState} frameState Frame state. + * @protected + * @type {WebGLTexture} + */ + this.texture = null; + + /** + * @protected + * @type {WebGLFramebuffer} + */ + this.framebuffer = null; + + /** + * @protected + * @type {number|undefined} + */ + this.framebufferDimension = undefined; + + /** + * @protected + * @type {ol.Transform} + */ + this.texCoordMatrix = ol.transform.create(); + + /** + * @protected + * @type {ol.Transform} + */ + this.projectionMatrix = ol.transform.create(); + + /** + * @type {Array.} * @private */ - ol.renderer.webgl.Layer.prototype.dispatchComposeEvent_ = function(type, context, frameState) { - var layer = this.getLayer(); - if (layer.hasListener(type)) { - var viewState = frameState.viewState; - var resolution = viewState.resolution; - var pixelRatio = frameState.pixelRatio; - var extent = frameState.extent; - var center = viewState.center; - var rotation = viewState.rotation; - var size = frameState.size; - - var render = new ol.render.webgl.Immediate( - context, center, resolution, rotation, size, extent, pixelRatio); - var composeEvent = new ol.render.Event( - type, render, frameState, null, context); - layer.dispatchEvent(composeEvent); - } - }; - + this.tmpMat4_ = ol.vec.Mat4.create(); /** - * @return {!ol.Transform} Matrix. + * @private + * @type {ol.renderer.webgl.defaultmapshader.Locations} */ - ol.renderer.webgl.Layer.prototype.getTexCoordMatrix = function() { - return this.texCoordMatrix; - }; + this.defaultLocations_ = null; + +}; +ol.inherits(ol.renderer.webgl.Layer, ol.renderer.Layer); - /** - * @return {WebGLTexture} Texture. - */ - ol.renderer.webgl.Layer.prototype.getTexture = function() { - return this.texture; - }; +/** + * @param {olx.FrameState} frameState Frame state. + * @param {number} framebufferDimension Framebuffer dimension. + * @protected + */ +ol.renderer.webgl.Layer.prototype.bindFramebuffer = function(frameState, framebufferDimension) { + + var gl = this.mapRenderer.getGL(); + + if (this.framebufferDimension === undefined || + this.framebufferDimension != framebufferDimension) { + /** + * @param {WebGLRenderingContext} gl GL. + * @param {WebGLFramebuffer} framebuffer Framebuffer. + * @param {WebGLTexture} texture Texture. + */ + var postRenderFunction = function(gl, framebuffer, texture) { + if (!gl.isContextLost()) { + gl.deleteFramebuffer(framebuffer); + gl.deleteTexture(texture); + } + }.bind(null, gl, this.framebuffer, this.texture); + + frameState.postRenderFunctions.push( + /** @type {ol.PostRenderFunction} */ (postRenderFunction) + ); + + var texture = ol.webgl.Context.createEmptyTexture( + gl, framebufferDimension, framebufferDimension); + + var framebuffer = gl.createFramebuffer(); + gl.bindFramebuffer(ol.webgl.FRAMEBUFFER, framebuffer); + gl.framebufferTexture2D(ol.webgl.FRAMEBUFFER, + ol.webgl.COLOR_ATTACHMENT0, ol.webgl.TEXTURE_2D, texture, 0); + + this.texture = texture; + this.framebuffer = framebuffer; + this.framebufferDimension = framebufferDimension; + + } else { + gl.bindFramebuffer(ol.webgl.FRAMEBUFFER, this.framebuffer); + } + +}; - /** - * @return {!ol.Transform} Matrix. - */ - ol.renderer.webgl.Layer.prototype.getProjectionMatrix = function() { - return this.projectionMatrix; - }; +/** + * @param {olx.FrameState} frameState Frame state. + * @param {ol.LayerState} layerState Layer state. + * @param {ol.webgl.Context} context Context. + */ +ol.renderer.webgl.Layer.prototype.composeFrame = function(frameState, layerState, context) { + + this.dispatchComposeEvent_( + ol.render.EventType.PRECOMPOSE, context, frameState); + + context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.arrayBuffer_); + + var gl = context.getGL(); + + var fragmentShader = ol.renderer.webgl.defaultmapshader.fragment; + var vertexShader = ol.renderer.webgl.defaultmapshader.vertex; + + var program = context.getProgram(fragmentShader, vertexShader); + + var locations; + if (!this.defaultLocations_) { + // eslint-disable-next-line openlayers-internal/no-missing-requires + locations = new ol.renderer.webgl.defaultmapshader.Locations(gl, program); + this.defaultLocations_ = locations; + } else { + locations = this.defaultLocations_; + } + + if (context.useProgram(program)) { + gl.enableVertexAttribArray(locations.a_position); + gl.vertexAttribPointer( + locations.a_position, 2, ol.webgl.FLOAT, false, 16, 0); + gl.enableVertexAttribArray(locations.a_texCoord); + gl.vertexAttribPointer( + locations.a_texCoord, 2, ol.webgl.FLOAT, false, 16, 8); + gl.uniform1i(locations.u_texture, 0); + } + + gl.uniformMatrix4fv(locations.u_texCoordMatrix, false, + ol.vec.Mat4.fromTransform(this.tmpMat4_, this.getTexCoordMatrix())); + gl.uniformMatrix4fv(locations.u_projectionMatrix, false, + ol.vec.Mat4.fromTransform(this.tmpMat4_, this.getProjectionMatrix())); + gl.uniform1f(locations.u_opacity, layerState.opacity); + gl.bindTexture(ol.webgl.TEXTURE_2D, this.getTexture()); + gl.drawArrays(ol.webgl.TRIANGLE_STRIP, 0, 4); + + this.dispatchComposeEvent_( + ol.render.EventType.POSTCOMPOSE, context, frameState); + +}; - /** - * Handle webglcontextlost. - */ - ol.renderer.webgl.Layer.prototype.handleWebGLContextLost = function() { - this.texture = null; - this.framebuffer = null; - this.framebufferDimension = undefined; - }; +/** + * @param {ol.render.EventType} type Event type. + * @param {ol.webgl.Context} context WebGL context. + * @param {olx.FrameState} frameState Frame state. + * @private + */ +ol.renderer.webgl.Layer.prototype.dispatchComposeEvent_ = function(type, context, frameState) { + var layer = this.getLayer(); + if (layer.hasListener(type)) { + var viewState = frameState.viewState; + var resolution = viewState.resolution; + var pixelRatio = frameState.pixelRatio; + var extent = frameState.extent; + var center = viewState.center; + var rotation = viewState.rotation; + var size = frameState.size; + + var render = new ol.render.webgl.Immediate( + context, center, resolution, rotation, size, extent, pixelRatio); + var composeEvent = new ol.render.Event( + type, render, frameState, null, context); + layer.dispatchEvent(composeEvent); + } +}; - /** - * @abstract - * @param {olx.FrameState} frameState Frame state. - * @param {ol.LayerState} layerState Layer state. - * @param {ol.webgl.Context} context Context. - * @return {boolean} whether composeFrame should be called. - */ - ol.renderer.webgl.Layer.prototype.prepareFrame = function(frameState, layerState, context) {}; +/** + * @return {!ol.Transform} Matrix. + */ +ol.renderer.webgl.Layer.prototype.getTexCoordMatrix = function() { + return this.texCoordMatrix; +}; - /** - * @abstract - * @param {ol.Pixel} pixel Pixel. - * @param {olx.FrameState} frameState FrameState. - * @param {function(this: S, ol.layer.Layer, (Uint8ClampedArray|Uint8Array)): T} callback Layer - * callback. - * @param {S} thisArg Value to use as `this` when executing `callback`. - * @return {T|undefined} Callback result. - * @template S,T,U - */ - ol.renderer.webgl.Layer.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg) {}; +/** + * @return {WebGLTexture} Texture. + */ +ol.renderer.webgl.Layer.prototype.getTexture = function() { + return this.texture; +}; -} + +/** + * @return {!ol.Transform} Matrix. + */ +ol.renderer.webgl.Layer.prototype.getProjectionMatrix = function() { + return this.projectionMatrix; +}; + + +/** + * Handle webglcontextlost. + */ +ol.renderer.webgl.Layer.prototype.handleWebGLContextLost = function() { + this.texture = null; + this.framebuffer = null; + this.framebufferDimension = undefined; +}; + + +/** + * @abstract + * @param {olx.FrameState} frameState Frame state. + * @param {ol.LayerState} layerState Layer state. + * @param {ol.webgl.Context} context Context. + * @return {boolean} whether composeFrame should be called. + */ +ol.renderer.webgl.Layer.prototype.prepareFrame = function(frameState, layerState, context) {}; + + +/** + * @abstract + * @param {ol.Pixel} pixel Pixel. + * @param {olx.FrameState} frameState FrameState. + * @param {function(this: S, ol.layer.Layer, (Uint8ClampedArray|Uint8Array)): T} callback Layer + * callback. + * @param {S} thisArg Value to use as `this` when executing `callback`. + * @return {T|undefined} Callback result. + * @template S,T,U + */ +ol.renderer.webgl.Layer.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg) {}; diff --git a/src/ol/renderer/webgl/map.js b/src/ol/renderer/webgl/map.js index 517f53f340..f3107c07b7 100644 --- a/src/ol/renderer/webgl/map.js +++ b/src/ol/renderer/webgl/map.js @@ -22,578 +22,574 @@ goog.require('ol.webgl.Context'); goog.require('ol.webgl.ContextEventType'); -if (ol.ENABLE_WEBGL) { +/** + * @constructor + * @extends {ol.renderer.Map} + * @param {Element} container Container. + * @param {ol.PluggableMap} map Map. + * @api + */ +ol.renderer.webgl.Map = function(container, map) { + ol.renderer.Map.call(this, container, map); + + /** + * @private + * @type {HTMLCanvasElement} + */ + this.canvas_ = /** @type {HTMLCanvasElement} */ + (document.createElement('CANVAS')); + this.canvas_.style.width = '100%'; + this.canvas_.style.height = '100%'; + this.canvas_.style.display = 'block'; + this.canvas_.className = ol.css.CLASS_UNSELECTABLE; + container.insertBefore(this.canvas_, container.childNodes[0] || null); + + /** + * @private + * @type {number} + */ + this.clipTileCanvasWidth_ = 0; + + /** + * @private + * @type {number} + */ + this.clipTileCanvasHeight_ = 0; + + /** + * @private + * @type {CanvasRenderingContext2D} + */ + this.clipTileContext_ = ol.dom.createCanvasContext2D(); + + /** + * @private + * @type {boolean} + */ + this.renderedVisible_ = true; + + /** + * @private + * @type {WebGLRenderingContext} + */ + this.gl_ = ol.webgl.getContext(this.canvas_, { + antialias: true, + depth: true, + failIfMajorPerformanceCaveat: true, + preserveDrawingBuffer: false, + stencil: true + }); + + /** + * @private + * @type {ol.webgl.Context} + */ + this.context_ = new ol.webgl.Context(this.canvas_, this.gl_); + + ol.events.listen(this.canvas_, ol.webgl.ContextEventType.LOST, + this.handleWebGLContextLost, this); + ol.events.listen(this.canvas_, ol.webgl.ContextEventType.RESTORED, + this.handleWebGLContextRestored, this); + + /** + * @private + * @type {ol.structs.LRUCache.} + */ + this.textureCache_ = new ol.structs.LRUCache(); + + /** + * @private + * @type {ol.Coordinate} + */ + this.focus_ = null; + + /** + * @private + * @type {ol.structs.PriorityQueue.} + */ + this.tileTextureQueue_ = new ol.structs.PriorityQueue( + /** + * @param {Array.<*>} element Element. + * @return {number} Priority. + * @this {ol.renderer.webgl.Map} + */ + (function(element) { + var tileCenter = /** @type {ol.Coordinate} */ (element[1]); + var tileResolution = /** @type {number} */ (element[2]); + var deltaX = tileCenter[0] - this.focus_[0]; + var deltaY = tileCenter[1] - this.focus_[1]; + return 65536 * Math.log(tileResolution) + + Math.sqrt(deltaX * deltaX + deltaY * deltaY) / tileResolution; + }).bind(this), + /** + * @param {Array.<*>} element Element. + * @return {string} Key. + */ + function(element) { + return /** @type {ol.Tile} */ (element[0]).getKey(); + }); + /** - * @constructor - * @extends {ol.renderer.Map} - * @param {Element} container Container. * @param {ol.PluggableMap} map Map. - * @api + * @param {?olx.FrameState} frameState Frame state. + * @return {boolean} false. + * @this {ol.renderer.webgl.Map} */ - ol.renderer.webgl.Map = function(container, map) { - ol.renderer.Map.call(this, container, map); - - /** - * @private - * @type {HTMLCanvasElement} - */ - this.canvas_ = /** @type {HTMLCanvasElement} */ - (document.createElement('CANVAS')); - this.canvas_.style.width = '100%'; - this.canvas_.style.height = '100%'; - this.canvas_.style.display = 'block'; - this.canvas_.className = ol.css.CLASS_UNSELECTABLE; - container.insertBefore(this.canvas_, container.childNodes[0] || null); - - /** - * @private - * @type {number} - */ - this.clipTileCanvasWidth_ = 0; - - /** - * @private - * @type {number} - */ - this.clipTileCanvasHeight_ = 0; - - /** - * @private - * @type {CanvasRenderingContext2D} - */ - this.clipTileContext_ = ol.dom.createCanvasContext2D(); - - /** - * @private - * @type {boolean} - */ - this.renderedVisible_ = true; - - /** - * @private - * @type {WebGLRenderingContext} - */ - this.gl_ = ol.webgl.getContext(this.canvas_, { - antialias: true, - depth: true, - failIfMajorPerformanceCaveat: true, - preserveDrawingBuffer: false, - stencil: true - }); - - /** - * @private - * @type {ol.webgl.Context} - */ - this.context_ = new ol.webgl.Context(this.canvas_, this.gl_); - - ol.events.listen(this.canvas_, ol.webgl.ContextEventType.LOST, - this.handleWebGLContextLost, this); - ol.events.listen(this.canvas_, ol.webgl.ContextEventType.RESTORED, - this.handleWebGLContextRestored, this); - - /** - * @private - * @type {ol.structs.LRUCache.} - */ - this.textureCache_ = new ol.structs.LRUCache(); - - /** - * @private - * @type {ol.Coordinate} - */ - this.focus_ = null; - - /** - * @private - * @type {ol.structs.PriorityQueue.} - */ - this.tileTextureQueue_ = new ol.structs.PriorityQueue( - /** - * @param {Array.<*>} element Element. - * @return {number} Priority. - * @this {ol.renderer.webgl.Map} - */ - (function(element) { - var tileCenter = /** @type {ol.Coordinate} */ (element[1]); - var tileResolution = /** @type {number} */ (element[2]); - var deltaX = tileCenter[0] - this.focus_[0]; - var deltaY = tileCenter[1] - this.focus_[1]; - return 65536 * Math.log(tileResolution) + - Math.sqrt(deltaX * deltaX + deltaY * deltaY) / tileResolution; - }).bind(this), - /** - * @param {Array.<*>} element Element. - * @return {string} Key. - */ - function(element) { - return /** @type {ol.Tile} */ (element[0]).getKey(); - }); - - - /** - * @param {ol.PluggableMap} map Map. - * @param {?olx.FrameState} frameState Frame state. - * @return {boolean} false. - * @this {ol.renderer.webgl.Map} - */ - this.loadNextTileTexture_ = - function(map, frameState) { - if (!this.tileTextureQueue_.isEmpty()) { - this.tileTextureQueue_.reprioritize(); - var element = this.tileTextureQueue_.dequeue(); - var tile = /** @type {ol.Tile} */ (element[0]); - var tileSize = /** @type {ol.Size} */ (element[3]); - var tileGutter = /** @type {number} */ (element[4]); - this.bindTileTexture( - tile, tileSize, tileGutter, ol.webgl.LINEAR, ol.webgl.LINEAR); - } - return false; - }.bind(this); - - - /** - * @private - * @type {number} - */ - this.textureCacheFrameMarkerCount_ = 0; - - this.initializeGL_(); - }; - ol.inherits(ol.renderer.webgl.Map, ol.renderer.Map); - - - /** - * Determine if this renderer handles the provided layer. - * @param {ol.renderer.Type} type The renderer type. - * @return {boolean} The renderer can render the layer. - */ - ol.renderer.webgl.Map['handles'] = function(type) { - return ol.has.WEBGL && type === ol.renderer.Type.WEBGL; - }; - - - /** - * Create the map renderer. - * @param {Element} container Container. - * @param {ol.PluggableMap} map Map. - * @return {ol.renderer.webgl.Map} The map renderer. - */ - ol.renderer.webgl.Map['create'] = function(container, map) { - return new ol.renderer.webgl.Map(container, map); - }; - - - /** - * @param {ol.Tile} tile Tile. - * @param {ol.Size} tileSize Tile size. - * @param {number} tileGutter Tile gutter. - * @param {number} magFilter Mag filter. - * @param {number} minFilter Min filter. - */ - ol.renderer.webgl.Map.prototype.bindTileTexture = function(tile, tileSize, tileGutter, magFilter, minFilter) { - var gl = this.getGL(); - var tileKey = tile.getKey(); - if (this.textureCache_.containsKey(tileKey)) { - var textureCacheEntry = this.textureCache_.get(tileKey); - gl.bindTexture(ol.webgl.TEXTURE_2D, textureCacheEntry.texture); - if (textureCacheEntry.magFilter != magFilter) { - gl.texParameteri( - ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_MAG_FILTER, magFilter); - textureCacheEntry.magFilter = magFilter; - } - if (textureCacheEntry.minFilter != minFilter) { - gl.texParameteri( - ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_MIN_FILTER, minFilter); - textureCacheEntry.minFilter = minFilter; - } - } else { - var texture = gl.createTexture(); - gl.bindTexture(ol.webgl.TEXTURE_2D, texture); - if (tileGutter > 0) { - var clipTileCanvas = this.clipTileContext_.canvas; - var clipTileContext = this.clipTileContext_; - if (this.clipTileCanvasWidth_ !== tileSize[0] || - this.clipTileCanvasHeight_ !== tileSize[1]) { - clipTileCanvas.width = tileSize[0]; - clipTileCanvas.height = tileSize[1]; - this.clipTileCanvasWidth_ = tileSize[0]; - this.clipTileCanvasHeight_ = tileSize[1]; - } else { - clipTileContext.clearRect(0, 0, tileSize[0], tileSize[1]); + this.loadNextTileTexture_ = + function(map, frameState) { + if (!this.tileTextureQueue_.isEmpty()) { + this.tileTextureQueue_.reprioritize(); + var element = this.tileTextureQueue_.dequeue(); + var tile = /** @type {ol.Tile} */ (element[0]); + var tileSize = /** @type {ol.Size} */ (element[3]); + var tileGutter = /** @type {number} */ (element[4]); + this.bindTileTexture( + tile, tileSize, tileGutter, ol.webgl.LINEAR, ol.webgl.LINEAR); } - clipTileContext.drawImage(tile.getImage(), tileGutter, tileGutter, - tileSize[0], tileSize[1], 0, 0, tileSize[0], tileSize[1]); - gl.texImage2D(ol.webgl.TEXTURE_2D, 0, - ol.webgl.RGBA, ol.webgl.RGBA, - ol.webgl.UNSIGNED_BYTE, clipTileCanvas); - } else { - gl.texImage2D(ol.webgl.TEXTURE_2D, 0, - ol.webgl.RGBA, ol.webgl.RGBA, - ol.webgl.UNSIGNED_BYTE, tile.getImage()); - } + return false; + }.bind(this); + + + /** + * @private + * @type {number} + */ + this.textureCacheFrameMarkerCount_ = 0; + + this.initializeGL_(); +}; +ol.inherits(ol.renderer.webgl.Map, ol.renderer.Map); + + +/** + * Determine if this renderer handles the provided layer. + * @param {ol.renderer.Type} type The renderer type. + * @return {boolean} The renderer can render the layer. + */ +ol.renderer.webgl.Map['handles'] = function(type) { + return ol.has.WEBGL && type === ol.renderer.Type.WEBGL; +}; + + +/** + * Create the map renderer. + * @param {Element} container Container. + * @param {ol.PluggableMap} map Map. + * @return {ol.renderer.webgl.Map} The map renderer. + */ +ol.renderer.webgl.Map['create'] = function(container, map) { + return new ol.renderer.webgl.Map(container, map); +}; + + +/** + * @param {ol.Tile} tile Tile. + * @param {ol.Size} tileSize Tile size. + * @param {number} tileGutter Tile gutter. + * @param {number} magFilter Mag filter. + * @param {number} minFilter Min filter. + */ +ol.renderer.webgl.Map.prototype.bindTileTexture = function(tile, tileSize, tileGutter, magFilter, minFilter) { + var gl = this.getGL(); + var tileKey = tile.getKey(); + if (this.textureCache_.containsKey(tileKey)) { + var textureCacheEntry = this.textureCache_.get(tileKey); + gl.bindTexture(ol.webgl.TEXTURE_2D, textureCacheEntry.texture); + if (textureCacheEntry.magFilter != magFilter) { gl.texParameteri( ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_MAG_FILTER, magFilter); + textureCacheEntry.magFilter = magFilter; + } + if (textureCacheEntry.minFilter != minFilter) { gl.texParameteri( ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_MIN_FILTER, minFilter); - gl.texParameteri(ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_WRAP_S, - ol.webgl.CLAMP_TO_EDGE); - gl.texParameteri(ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_WRAP_T, - ol.webgl.CLAMP_TO_EDGE); - this.textureCache_.set(tileKey, { - texture: texture, - magFilter: magFilter, - minFilter: minFilter - }); + textureCacheEntry.minFilter = minFilter; } - }; - - - /** - * @param {ol.render.EventType} type Event type. - * @param {olx.FrameState} frameState Frame state. - * @private - */ - ol.renderer.webgl.Map.prototype.dispatchComposeEvent_ = function(type, frameState) { - var map = this.getMap(); - if (map.hasListener(type)) { - var context = this.context_; - - var extent = frameState.extent; - var size = frameState.size; - var viewState = frameState.viewState; - var pixelRatio = frameState.pixelRatio; - - var resolution = viewState.resolution; - var center = viewState.center; - var rotation = viewState.rotation; - - var vectorContext = new ol.render.webgl.Immediate(context, - center, resolution, rotation, size, extent, pixelRatio); - var composeEvent = new ol.render.Event(type, vectorContext, - frameState, null, context); - map.dispatchEvent(composeEvent); - } - }; - - - /** - * @inheritDoc - */ - ol.renderer.webgl.Map.prototype.disposeInternal = function() { - var gl = this.getGL(); - if (!gl.isContextLost()) { - this.textureCache_.forEach( - /** - * @param {?ol.WebglTextureCacheEntry} textureCacheEntry - * Texture cache entry. - */ - function(textureCacheEntry) { - if (textureCacheEntry) { - gl.deleteTexture(textureCacheEntry.texture); - } - }); - } - this.context_.dispose(); - ol.renderer.Map.prototype.disposeInternal.call(this); - }; - - - /** - * @param {ol.PluggableMap} map Map. - * @param {olx.FrameState} frameState Frame state. - * @private - */ - ol.renderer.webgl.Map.prototype.expireCache_ = function(map, frameState) { - var gl = this.getGL(); - var textureCacheEntry; - while (this.textureCache_.getCount() - this.textureCacheFrameMarkerCount_ > - ol.WEBGL_TEXTURE_CACHE_HIGH_WATER_MARK) { - textureCacheEntry = this.textureCache_.peekLast(); - if (!textureCacheEntry) { - if (+this.textureCache_.peekLastKey() == frameState.index) { - break; - } else { - --this.textureCacheFrameMarkerCount_; - } + } else { + var texture = gl.createTexture(); + gl.bindTexture(ol.webgl.TEXTURE_2D, texture); + if (tileGutter > 0) { + var clipTileCanvas = this.clipTileContext_.canvas; + var clipTileContext = this.clipTileContext_; + if (this.clipTileCanvasWidth_ !== tileSize[0] || + this.clipTileCanvasHeight_ !== tileSize[1]) { + clipTileCanvas.width = tileSize[0]; + clipTileCanvas.height = tileSize[1]; + this.clipTileCanvasWidth_ = tileSize[0]; + this.clipTileCanvasHeight_ = tileSize[1]; } else { - gl.deleteTexture(textureCacheEntry.texture); + clipTileContext.clearRect(0, 0, tileSize[0], tileSize[1]); } - this.textureCache_.pop(); + clipTileContext.drawImage(tile.getImage(), tileGutter, tileGutter, + tileSize[0], tileSize[1], 0, 0, tileSize[0], tileSize[1]); + gl.texImage2D(ol.webgl.TEXTURE_2D, 0, + ol.webgl.RGBA, ol.webgl.RGBA, + ol.webgl.UNSIGNED_BYTE, clipTileCanvas); + } else { + gl.texImage2D(ol.webgl.TEXTURE_2D, 0, + ol.webgl.RGBA, ol.webgl.RGBA, + ol.webgl.UNSIGNED_BYTE, tile.getImage()); } - }; + gl.texParameteri( + ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_MAG_FILTER, magFilter); + gl.texParameteri( + ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_MIN_FILTER, minFilter); + gl.texParameteri(ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_WRAP_S, + ol.webgl.CLAMP_TO_EDGE); + gl.texParameteri(ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_WRAP_T, + ol.webgl.CLAMP_TO_EDGE); + this.textureCache_.set(tileKey, { + texture: texture, + magFilter: magFilter, + minFilter: minFilter + }); + } +}; - /** - * @return {ol.webgl.Context} The context. - */ - ol.renderer.webgl.Map.prototype.getContext = function() { - return this.context_; - }; +/** + * @param {ol.render.EventType} type Event type. + * @param {olx.FrameState} frameState Frame state. + * @private + */ +ol.renderer.webgl.Map.prototype.dispatchComposeEvent_ = function(type, frameState) { + var map = this.getMap(); + if (map.hasListener(type)) { + var context = this.context_; + + var extent = frameState.extent; + var size = frameState.size; + var viewState = frameState.viewState; + var pixelRatio = frameState.pixelRatio; + + var resolution = viewState.resolution; + var center = viewState.center; + var rotation = viewState.rotation; + + var vectorContext = new ol.render.webgl.Immediate(context, + center, resolution, rotation, size, extent, pixelRatio); + var composeEvent = new ol.render.Event(type, vectorContext, + frameState, null, context); + map.dispatchEvent(composeEvent); + } +}; - /** - * @return {WebGLRenderingContext} GL. - */ - ol.renderer.webgl.Map.prototype.getGL = function() { - return this.gl_; - }; +/** + * @inheritDoc + */ +ol.renderer.webgl.Map.prototype.disposeInternal = function() { + var gl = this.getGL(); + if (!gl.isContextLost()) { + this.textureCache_.forEach( + /** + * @param {?ol.WebglTextureCacheEntry} textureCacheEntry + * Texture cache entry. + */ + function(textureCacheEntry) { + if (textureCacheEntry) { + gl.deleteTexture(textureCacheEntry.texture); + } + }); + } + this.context_.dispose(); + ol.renderer.Map.prototype.disposeInternal.call(this); +}; - /** - * @return {ol.structs.PriorityQueue.} Tile texture queue. - */ - ol.renderer.webgl.Map.prototype.getTileTextureQueue = function() { - return this.tileTextureQueue_; - }; - - - /** - * @inheritDoc - */ - ol.renderer.webgl.Map.prototype.getType = function() { - return ol.renderer.Type.WEBGL; - }; - - - /** - * @param {ol.events.Event} event Event. - * @protected - */ - ol.renderer.webgl.Map.prototype.handleWebGLContextLost = function(event) { - event.preventDefault(); - this.textureCache_.clear(); - this.textureCacheFrameMarkerCount_ = 0; - - var renderers = this.getLayerRenderers(); - for (var id in renderers) { - var renderer = /** @type {ol.renderer.webgl.Layer} */ (renderers[id]); - renderer.handleWebGLContextLost(); - } - }; - - - /** - * @protected - */ - ol.renderer.webgl.Map.prototype.handleWebGLContextRestored = function() { - this.initializeGL_(); - this.getMap().render(); - }; - - - /** - * @private - */ - ol.renderer.webgl.Map.prototype.initializeGL_ = function() { - var gl = this.gl_; - gl.activeTexture(ol.webgl.TEXTURE0); - gl.blendFuncSeparate( - ol.webgl.SRC_ALPHA, ol.webgl.ONE_MINUS_SRC_ALPHA, - ol.webgl.ONE, ol.webgl.ONE_MINUS_SRC_ALPHA); - gl.disable(ol.webgl.CULL_FACE); - gl.disable(ol.webgl.DEPTH_TEST); - gl.disable(ol.webgl.SCISSOR_TEST); - gl.disable(ol.webgl.STENCIL_TEST); - }; - - - /** - * @param {ol.Tile} tile Tile. - * @return {boolean} Is tile texture loaded. - */ - ol.renderer.webgl.Map.prototype.isTileTextureLoaded = function(tile) { - return this.textureCache_.containsKey(tile.getKey()); - }; - - - /** - * @inheritDoc - */ - ol.renderer.webgl.Map.prototype.renderFrame = function(frameState) { - - var context = this.getContext(); - var gl = this.getGL(); - - if (gl.isContextLost()) { - return false; - } - - if (!frameState) { - if (this.renderedVisible_) { - this.canvas_.style.display = 'none'; - this.renderedVisible_ = false; +/** + * @param {ol.PluggableMap} map Map. + * @param {olx.FrameState} frameState Frame state. + * @private + */ +ol.renderer.webgl.Map.prototype.expireCache_ = function(map, frameState) { + var gl = this.getGL(); + var textureCacheEntry; + while (this.textureCache_.getCount() - this.textureCacheFrameMarkerCount_ > + ol.WEBGL_TEXTURE_CACHE_HIGH_WATER_MARK) { + textureCacheEntry = this.textureCache_.peekLast(); + if (!textureCacheEntry) { + if (+this.textureCache_.peekLastKey() == frameState.index) { + break; + } else { + --this.textureCacheFrameMarkerCount_; } - return false; + } else { + gl.deleteTexture(textureCacheEntry.texture); } + this.textureCache_.pop(); + } +}; - this.focus_ = frameState.focus; - this.textureCache_.set((-frameState.index).toString(), null); - ++this.textureCacheFrameMarkerCount_; +/** + * @return {ol.webgl.Context} The context. + */ +ol.renderer.webgl.Map.prototype.getContext = function() { + return this.context_; +}; - this.dispatchComposeEvent_(ol.render.EventType.PRECOMPOSE, frameState); - /** @type {Array.} */ - var layerStatesToDraw = []; - var layerStatesArray = frameState.layerStatesArray; - ol.array.stableSort(layerStatesArray, ol.renderer.Map.sortByZIndex); +/** + * @return {WebGLRenderingContext} GL. + */ +ol.renderer.webgl.Map.prototype.getGL = function() { + return this.gl_; +}; - var viewResolution = frameState.viewState.resolution; - var i, ii, layerRenderer, layerState; - for (i = 0, ii = layerStatesArray.length; i < ii; ++i) { - layerState = layerStatesArray[i]; - if (ol.layer.Layer.visibleAtResolution(layerState, viewResolution) && - layerState.sourceState == ol.source.State.READY) { - layerRenderer = /** @type {ol.renderer.webgl.Layer} */ (this.getLayerRenderer(layerState.layer)); - if (layerRenderer.prepareFrame(frameState, layerState, context)) { - layerStatesToDraw.push(layerState); - } - } + +/** + * @return {ol.structs.PriorityQueue.} Tile texture queue. + */ +ol.renderer.webgl.Map.prototype.getTileTextureQueue = function() { + return this.tileTextureQueue_; +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.Map.prototype.getType = function() { + return ol.renderer.Type.WEBGL; +}; + + +/** + * @param {ol.events.Event} event Event. + * @protected + */ +ol.renderer.webgl.Map.prototype.handleWebGLContextLost = function(event) { + event.preventDefault(); + this.textureCache_.clear(); + this.textureCacheFrameMarkerCount_ = 0; + + var renderers = this.getLayerRenderers(); + for (var id in renderers) { + var renderer = /** @type {ol.renderer.webgl.Layer} */ (renderers[id]); + renderer.handleWebGLContextLost(); + } +}; + + +/** + * @protected + */ +ol.renderer.webgl.Map.prototype.handleWebGLContextRestored = function() { + this.initializeGL_(); + this.getMap().render(); +}; + + +/** + * @private + */ +ol.renderer.webgl.Map.prototype.initializeGL_ = function() { + var gl = this.gl_; + gl.activeTexture(ol.webgl.TEXTURE0); + gl.blendFuncSeparate( + ol.webgl.SRC_ALPHA, ol.webgl.ONE_MINUS_SRC_ALPHA, + ol.webgl.ONE, ol.webgl.ONE_MINUS_SRC_ALPHA); + gl.disable(ol.webgl.CULL_FACE); + gl.disable(ol.webgl.DEPTH_TEST); + gl.disable(ol.webgl.SCISSOR_TEST); + gl.disable(ol.webgl.STENCIL_TEST); +}; + + +/** + * @param {ol.Tile} tile Tile. + * @return {boolean} Is tile texture loaded. + */ +ol.renderer.webgl.Map.prototype.isTileTextureLoaded = function(tile) { + return this.textureCache_.containsKey(tile.getKey()); +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.Map.prototype.renderFrame = function(frameState) { + + var context = this.getContext(); + var gl = this.getGL(); + + if (gl.isContextLost()) { + return false; + } + + if (!frameState) { + if (this.renderedVisible_) { + this.canvas_.style.display = 'none'; + this.renderedVisible_ = false; } + return false; + } - var width = frameState.size[0] * frameState.pixelRatio; - var height = frameState.size[1] * frameState.pixelRatio; - if (this.canvas_.width != width || this.canvas_.height != height) { - this.canvas_.width = width; - this.canvas_.height = height; - } + this.focus_ = frameState.focus; - gl.bindFramebuffer(ol.webgl.FRAMEBUFFER, null); + this.textureCache_.set((-frameState.index).toString(), null); + ++this.textureCacheFrameMarkerCount_; - gl.clearColor(0, 0, 0, 0); - gl.clear(ol.webgl.COLOR_BUFFER_BIT); - gl.enable(ol.webgl.BLEND); - gl.viewport(0, 0, this.canvas_.width, this.canvas_.height); + this.dispatchComposeEvent_(ol.render.EventType.PRECOMPOSE, frameState); - for (i = 0, ii = layerStatesToDraw.length; i < ii; ++i) { - layerState = layerStatesToDraw[i]; + /** @type {Array.} */ + var layerStatesToDraw = []; + var layerStatesArray = frameState.layerStatesArray; + ol.array.stableSort(layerStatesArray, ol.renderer.Map.sortByZIndex); + + var viewResolution = frameState.viewState.resolution; + var i, ii, layerRenderer, layerState; + for (i = 0, ii = layerStatesArray.length; i < ii; ++i) { + layerState = layerStatesArray[i]; + if (ol.layer.Layer.visibleAtResolution(layerState, viewResolution) && + layerState.sourceState == ol.source.State.READY) { layerRenderer = /** @type {ol.renderer.webgl.Layer} */ (this.getLayerRenderer(layerState.layer)); - layerRenderer.composeFrame(frameState, layerState, context); - } - - if (!this.renderedVisible_) { - this.canvas_.style.display = ''; - this.renderedVisible_ = true; - } - - this.calculateMatrices2D(frameState); - - if (this.textureCache_.getCount() - this.textureCacheFrameMarkerCount_ > - ol.WEBGL_TEXTURE_CACHE_HIGH_WATER_MARK) { - frameState.postRenderFunctions.push( - /** @type {ol.PostRenderFunction} */ (this.expireCache_.bind(this)) - ); - } - - if (!this.tileTextureQueue_.isEmpty()) { - frameState.postRenderFunctions.push(this.loadNextTileTexture_); - frameState.animate = true; - } - - this.dispatchComposeEvent_(ol.render.EventType.POSTCOMPOSE, frameState); - - this.scheduleRemoveUnusedLayerRenderers(frameState); - this.scheduleExpireIconCache(frameState); - - }; - - - /** - * @inheritDoc - */ - ol.renderer.webgl.Map.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, callback, thisArg, - layerFilter, thisArg2) { - var result; - - if (this.getGL().isContextLost()) { - return false; - } - - var viewState = frameState.viewState; - - var layerStates = frameState.layerStatesArray; - var numLayers = layerStates.length; - var i; - for (i = numLayers - 1; i >= 0; --i) { - var layerState = layerStates[i]; - var layer = layerState.layer; - if (ol.layer.Layer.visibleAtResolution(layerState, viewState.resolution) && - layerFilter.call(thisArg2, layer)) { - var layerRenderer = this.getLayerRenderer(layer); - result = layerRenderer.forEachFeatureAtCoordinate( - coordinate, frameState, hitTolerance, callback, thisArg); - if (result) { - return result; - } + if (layerRenderer.prepareFrame(frameState, layerState, context)) { + layerStatesToDraw.push(layerState); } } - return undefined; - }; + } + + var width = frameState.size[0] * frameState.pixelRatio; + var height = frameState.size[1] * frameState.pixelRatio; + if (this.canvas_.width != width || this.canvas_.height != height) { + this.canvas_.width = width; + this.canvas_.height = height; + } + + gl.bindFramebuffer(ol.webgl.FRAMEBUFFER, null); + + gl.clearColor(0, 0, 0, 0); + gl.clear(ol.webgl.COLOR_BUFFER_BIT); + gl.enable(ol.webgl.BLEND); + gl.viewport(0, 0, this.canvas_.width, this.canvas_.height); + + for (i = 0, ii = layerStatesToDraw.length; i < ii; ++i) { + layerState = layerStatesToDraw[i]; + layerRenderer = /** @type {ol.renderer.webgl.Layer} */ (this.getLayerRenderer(layerState.layer)); + layerRenderer.composeFrame(frameState, layerState, context); + } + + if (!this.renderedVisible_) { + this.canvas_.style.display = ''; + this.renderedVisible_ = true; + } + + this.calculateMatrices2D(frameState); + + if (this.textureCache_.getCount() - this.textureCacheFrameMarkerCount_ > + ol.WEBGL_TEXTURE_CACHE_HIGH_WATER_MARK) { + frameState.postRenderFunctions.push( + /** @type {ol.PostRenderFunction} */ (this.expireCache_.bind(this)) + ); + } + + if (!this.tileTextureQueue_.isEmpty()) { + frameState.postRenderFunctions.push(this.loadNextTileTexture_); + frameState.animate = true; + } + + this.dispatchComposeEvent_(ol.render.EventType.POSTCOMPOSE, frameState); + + this.scheduleRemoveUnusedLayerRenderers(frameState); + this.scheduleExpireIconCache(frameState); + +}; - /** - * @inheritDoc - */ - ol.renderer.webgl.Map.prototype.hasFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, layerFilter, thisArg) { - var hasFeature = false; +/** + * @inheritDoc + */ +ol.renderer.webgl.Map.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, callback, thisArg, + layerFilter, thisArg2) { + var result; - if (this.getGL().isContextLost()) { - return false; - } + if (this.getGL().isContextLost()) { + return false; + } - var viewState = frameState.viewState; + var viewState = frameState.viewState; - var layerStates = frameState.layerStatesArray; - var numLayers = layerStates.length; - var i; - for (i = numLayers - 1; i >= 0; --i) { - var layerState = layerStates[i]; - var layer = layerState.layer; - if (ol.layer.Layer.visibleAtResolution(layerState, viewState.resolution) && - layerFilter.call(thisArg, layer)) { - var layerRenderer = this.getLayerRenderer(layer); - hasFeature = - layerRenderer.hasFeatureAtCoordinate(coordinate, frameState); - if (hasFeature) { - return true; - } + var layerStates = frameState.layerStatesArray; + var numLayers = layerStates.length; + var i; + for (i = numLayers - 1; i >= 0; --i) { + var layerState = layerStates[i]; + var layer = layerState.layer; + if (ol.layer.Layer.visibleAtResolution(layerState, viewState.resolution) && + layerFilter.call(thisArg2, layer)) { + var layerRenderer = this.getLayerRenderer(layer); + result = layerRenderer.forEachFeatureAtCoordinate( + coordinate, frameState, hitTolerance, callback, thisArg); + if (result) { + return result; } } - return hasFeature; - }; + } + return undefined; +}; - /** - * @inheritDoc - */ - ol.renderer.webgl.Map.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg, - layerFilter, thisArg2) { - if (this.getGL().isContextLost()) { - return false; - } +/** + * @inheritDoc + */ +ol.renderer.webgl.Map.prototype.hasFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, layerFilter, thisArg) { + var hasFeature = false; - var viewState = frameState.viewState; - var result; + if (this.getGL().isContextLost()) { + return false; + } - var layerStates = frameState.layerStatesArray; - var numLayers = layerStates.length; - var i; - for (i = numLayers - 1; i >= 0; --i) { - var layerState = layerStates[i]; - var layer = layerState.layer; - if (ol.layer.Layer.visibleAtResolution(layerState, viewState.resolution) && - layerFilter.call(thisArg, layer)) { - var layerRenderer = /** @type {ol.renderer.webgl.Layer} */ (this.getLayerRenderer(layer)); - result = layerRenderer.forEachLayerAtPixel( - pixel, frameState, callback, thisArg); - if (result) { - return result; - } + var viewState = frameState.viewState; + + var layerStates = frameState.layerStatesArray; + var numLayers = layerStates.length; + var i; + for (i = numLayers - 1; i >= 0; --i) { + var layerState = layerStates[i]; + var layer = layerState.layer; + if (ol.layer.Layer.visibleAtResolution(layerState, viewState.resolution) && + layerFilter.call(thisArg, layer)) { + var layerRenderer = this.getLayerRenderer(layer); + hasFeature = + layerRenderer.hasFeatureAtCoordinate(coordinate, frameState); + if (hasFeature) { + return true; } } - return undefined; - }; + } + return hasFeature; +}; -} + +/** + * @inheritDoc + */ +ol.renderer.webgl.Map.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg, + layerFilter, thisArg2) { + if (this.getGL().isContextLost()) { + return false; + } + + var viewState = frameState.viewState; + var result; + + var layerStates = frameState.layerStatesArray; + var numLayers = layerStates.length; + var i; + for (i = numLayers - 1; i >= 0; --i) { + var layerState = layerStates[i]; + var layer = layerState.layer; + if (ol.layer.Layer.visibleAtResolution(layerState, viewState.resolution) && + layerFilter.call(thisArg, layer)) { + var layerRenderer = /** @type {ol.renderer.webgl.Layer} */ (this.getLayerRenderer(layer)); + result = layerRenderer.forEachLayerAtPixel( + pixel, frameState, callback, thisArg); + if (result) { + return result; + } + } + } + return undefined; +}; diff --git a/src/ol/renderer/webgl/tilelayer.js b/src/ol/renderer/webgl/tilelayer.js index ce4620be0d..23229fb73b 100644 --- a/src/ol/renderer/webgl/tilelayer.js +++ b/src/ol/renderer/webgl/tilelayer.js @@ -19,395 +19,391 @@ goog.require('ol.webgl'); goog.require('ol.webgl.Buffer'); -if (ol.ENABLE_WEBGL) { +/** + * @constructor + * @extends {ol.renderer.webgl.Layer} + * @param {ol.renderer.webgl.Map} mapRenderer Map renderer. + * @param {ol.layer.Tile} tileLayer Tile layer. + * @api + */ +ol.renderer.webgl.TileLayer = function(mapRenderer, tileLayer) { + + ol.renderer.webgl.Layer.call(this, mapRenderer, tileLayer); /** - * @constructor - * @extends {ol.renderer.webgl.Layer} - * @param {ol.renderer.webgl.Map} mapRenderer Map renderer. - * @param {ol.layer.Tile} tileLayer Tile layer. - * @api + * @private + * @type {ol.webgl.Fragment} */ - ol.renderer.webgl.TileLayer = function(mapRenderer, tileLayer) { - - ol.renderer.webgl.Layer.call(this, mapRenderer, tileLayer); - - /** - * @private - * @type {ol.webgl.Fragment} - */ - this.fragmentShader_ = ol.renderer.webgl.tilelayershader.fragment; - - /** - * @private - * @type {ol.webgl.Vertex} - */ - this.vertexShader_ = ol.renderer.webgl.tilelayershader.vertex; - - /** - * @private - * @type {ol.renderer.webgl.tilelayershader.Locations} - */ - this.locations_ = null; - - /** - * @private - * @type {ol.webgl.Buffer} - */ - this.renderArrayBuffer_ = new ol.webgl.Buffer([ - 0, 0, 0, 1, - 1, 0, 1, 1, - 0, 1, 0, 0, - 1, 1, 1, 0 - ]); - - /** - * @private - * @type {ol.TileRange} - */ - this.renderedTileRange_ = null; - - /** - * @private - * @type {ol.Extent} - */ - this.renderedFramebufferExtent_ = null; - - /** - * @private - * @type {number} - */ - this.renderedRevision_ = -1; - - /** - * @private - * @type {ol.Size} - */ - this.tmpSize_ = [0, 0]; - - }; - ol.inherits(ol.renderer.webgl.TileLayer, ol.renderer.webgl.Layer); - + this.fragmentShader_ = ol.renderer.webgl.tilelayershader.fragment; /** - * Determine if this renderer handles the provided layer. - * @param {ol.renderer.Type} type The renderer type. - * @param {ol.layer.Layer} layer The candidate layer. - * @return {boolean} The renderer can render the layer. + * @private + * @type {ol.webgl.Vertex} */ - ol.renderer.webgl.TileLayer['handles'] = function(type, layer) { - return type === ol.renderer.Type.WEBGL && layer.getType() === ol.LayerType.TILE; - }; - + this.vertexShader_ = ol.renderer.webgl.tilelayershader.vertex; /** - * Create a layer renderer. - * @param {ol.renderer.Map} mapRenderer The map renderer. - * @param {ol.layer.Layer} layer The layer to be rendererd. - * @return {ol.renderer.webgl.TileLayer} The layer renderer. + * @private + * @type {ol.renderer.webgl.tilelayershader.Locations} */ - ol.renderer.webgl.TileLayer['create'] = function(mapRenderer, layer) { - return new ol.renderer.webgl.TileLayer( - /** @type {ol.renderer.webgl.Map} */ (mapRenderer), - /** @type {ol.layer.Tile} */ (layer) - ); - }; - + this.locations_ = null; /** - * @inheritDoc + * @private + * @type {ol.webgl.Buffer} */ - ol.renderer.webgl.TileLayer.prototype.disposeInternal = function() { - var context = this.mapRenderer.getContext(); - context.deleteBuffer(this.renderArrayBuffer_); - ol.renderer.webgl.Layer.prototype.disposeInternal.call(this); - }; - + this.renderArrayBuffer_ = new ol.webgl.Buffer([ + 0, 0, 0, 1, + 1, 0, 1, 1, + 0, 1, 0, 0, + 1, 1, 1, 0 + ]); /** - * @inheritDoc + * @private + * @type {ol.TileRange} */ - ol.renderer.webgl.TileLayer.prototype.createLoadedTileFinder = function(source, projection, tiles) { - var mapRenderer = this.mapRenderer; + this.renderedTileRange_ = null; - return ( - /** - * @param {number} zoom Zoom level. - * @param {ol.TileRange} tileRange Tile range. - * @return {boolean} The tile range is fully loaded. - */ - function(zoom, tileRange) { - function callback(tile) { - var loaded = mapRenderer.isTileTextureLoaded(tile); - if (loaded) { - if (!tiles[zoom]) { - tiles[zoom] = {}; - } - tiles[zoom][tile.tileCoord.toString()] = tile; + /** + * @private + * @type {ol.Extent} + */ + this.renderedFramebufferExtent_ = null; + + /** + * @private + * @type {number} + */ + this.renderedRevision_ = -1; + + /** + * @private + * @type {ol.Size} + */ + this.tmpSize_ = [0, 0]; + +}; +ol.inherits(ol.renderer.webgl.TileLayer, ol.renderer.webgl.Layer); + + +/** + * Determine if this renderer handles the provided layer. + * @param {ol.renderer.Type} type The renderer type. + * @param {ol.layer.Layer} layer The candidate layer. + * @return {boolean} The renderer can render the layer. + */ +ol.renderer.webgl.TileLayer['handles'] = function(type, layer) { + return type === ol.renderer.Type.WEBGL && layer.getType() === ol.LayerType.TILE; +}; + + +/** + * Create a layer renderer. + * @param {ol.renderer.Map} mapRenderer The map renderer. + * @param {ol.layer.Layer} layer The layer to be rendererd. + * @return {ol.renderer.webgl.TileLayer} The layer renderer. + */ +ol.renderer.webgl.TileLayer['create'] = function(mapRenderer, layer) { + return new ol.renderer.webgl.TileLayer( + /** @type {ol.renderer.webgl.Map} */ (mapRenderer), + /** @type {ol.layer.Tile} */ (layer) + ); +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.TileLayer.prototype.disposeInternal = function() { + var context = this.mapRenderer.getContext(); + context.deleteBuffer(this.renderArrayBuffer_); + ol.renderer.webgl.Layer.prototype.disposeInternal.call(this); +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.TileLayer.prototype.createLoadedTileFinder = function(source, projection, tiles) { + var mapRenderer = this.mapRenderer; + + return ( + /** + * @param {number} zoom Zoom level. + * @param {ol.TileRange} tileRange Tile range. + * @return {boolean} The tile range is fully loaded. + */ + function(zoom, tileRange) { + function callback(tile) { + var loaded = mapRenderer.isTileTextureLoaded(tile); + if (loaded) { + if (!tiles[zoom]) { + tiles[zoom] = {}; } - return loaded; + tiles[zoom][tile.tileCoord.toString()] = tile; } - return source.forEachLoadedTile(projection, zoom, tileRange, callback); - }); - }; - - - /** - * @inheritDoc - */ - ol.renderer.webgl.TileLayer.prototype.handleWebGLContextLost = function() { - ol.renderer.webgl.Layer.prototype.handleWebGLContextLost.call(this); - this.locations_ = null; - }; - - - /** - * @inheritDoc - */ - ol.renderer.webgl.TileLayer.prototype.prepareFrame = function(frameState, layerState, context) { - - var mapRenderer = this.mapRenderer; - var gl = context.getGL(); - - var viewState = frameState.viewState; - var projection = viewState.projection; - - var tileLayer = /** @type {ol.layer.Tile} */ (this.getLayer()); - var tileSource = tileLayer.getSource(); - var tileGrid = tileSource.getTileGridForProjection(projection); - var z = tileGrid.getZForResolution(viewState.resolution); - var tileResolution = tileGrid.getResolution(z); - - var tilePixelSize = - tileSource.getTilePixelSize(z, frameState.pixelRatio, projection); - var pixelRatio = tilePixelSize[0] / - ol.size.toSize(tileGrid.getTileSize(z), this.tmpSize_)[0]; - var tilePixelResolution = tileResolution / pixelRatio; - var tileGutter = tileSource.getTilePixelRatio(pixelRatio) * tileSource.getGutter(projection); - - var center = viewState.center; - var extent = frameState.extent; - var tileRange = tileGrid.getTileRangeForExtentAndResolution( - extent, tileResolution); - - var framebufferExtent; - if (this.renderedTileRange_ && - this.renderedTileRange_.equals(tileRange) && - this.renderedRevision_ == tileSource.getRevision()) { - framebufferExtent = this.renderedFramebufferExtent_; - } else { - - var tileRangeSize = tileRange.getSize(); - - var maxDimension = Math.max( - tileRangeSize[0] * tilePixelSize[0], - tileRangeSize[1] * tilePixelSize[1]); - var framebufferDimension = ol.math.roundUpToPowerOfTwo(maxDimension); - var framebufferExtentDimension = tilePixelResolution * framebufferDimension; - var origin = tileGrid.getOrigin(z); - var minX = origin[0] + - tileRange.minX * tilePixelSize[0] * tilePixelResolution; - var minY = origin[1] + - tileRange.minY * tilePixelSize[1] * tilePixelResolution; - framebufferExtent = [ - minX, minY, - minX + framebufferExtentDimension, minY + framebufferExtentDimension - ]; - - this.bindFramebuffer(frameState, framebufferDimension); - gl.viewport(0, 0, framebufferDimension, framebufferDimension); - - gl.clearColor(0, 0, 0, 0); - gl.clear(ol.webgl.COLOR_BUFFER_BIT); - gl.disable(ol.webgl.BLEND); - - var program = context.getProgram(this.fragmentShader_, this.vertexShader_); - context.useProgram(program); - if (!this.locations_) { - // eslint-disable-next-line openlayers-internal/no-missing-requires - this.locations_ = new ol.renderer.webgl.tilelayershader.Locations(gl, program); + return loaded; } + return source.forEachLoadedTile(projection, zoom, tileRange, callback); + }); +}; - context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.renderArrayBuffer_); - gl.enableVertexAttribArray(this.locations_.a_position); - gl.vertexAttribPointer( - this.locations_.a_position, 2, ol.webgl.FLOAT, false, 16, 0); - gl.enableVertexAttribArray(this.locations_.a_texCoord); - gl.vertexAttribPointer( - this.locations_.a_texCoord, 2, ol.webgl.FLOAT, false, 16, 8); - gl.uniform1i(this.locations_.u_texture, 0); - /** - * @type {Object.>} - */ - var tilesToDrawByZ = {}; - tilesToDrawByZ[z] = {}; +/** + * @inheritDoc + */ +ol.renderer.webgl.TileLayer.prototype.handleWebGLContextLost = function() { + ol.renderer.webgl.Layer.prototype.handleWebGLContextLost.call(this); + this.locations_ = null; +}; - var findLoadedTiles = this.createLoadedTileFinder( - tileSource, projection, tilesToDrawByZ); - var useInterimTilesOnError = tileLayer.getUseInterimTilesOnError(); - var allTilesLoaded = true; - var tmpExtent = ol.extent.createEmpty(); - var tmpTileRange = new ol.TileRange(0, 0, 0, 0); - var childTileRange, drawable, fullyLoaded, tile, tileState; - var x, y, tileExtent; - for (x = tileRange.minX; x <= tileRange.maxX; ++x) { - for (y = tileRange.minY; y <= tileRange.maxY; ++y) { +/** + * @inheritDoc + */ +ol.renderer.webgl.TileLayer.prototype.prepareFrame = function(frameState, layerState, context) { - tile = tileSource.getTile(z, x, y, pixelRatio, projection); - if (layerState.extent !== undefined) { - // ignore tiles outside layer extent - tileExtent = tileGrid.getTileCoordExtent(tile.tileCoord, tmpExtent); - if (!ol.extent.intersects(tileExtent, layerState.extent)) { - continue; - } - } - tileState = tile.getState(); - drawable = tileState == ol.TileState.LOADED || - tileState == ol.TileState.EMPTY || - tileState == ol.TileState.ERROR && !useInterimTilesOnError; - if (!drawable) { - tile = tile.getInterimTile(); - } - tileState = tile.getState(); - if (tileState == ol.TileState.LOADED) { - if (mapRenderer.isTileTextureLoaded(tile)) { - tilesToDrawByZ[z][tile.tileCoord.toString()] = tile; - continue; - } - } else if (tileState == ol.TileState.EMPTY || - (tileState == ol.TileState.ERROR && - !useInterimTilesOnError)) { + var mapRenderer = this.mapRenderer; + var gl = context.getGL(); + + var viewState = frameState.viewState; + var projection = viewState.projection; + + var tileLayer = /** @type {ol.layer.Tile} */ (this.getLayer()); + var tileSource = tileLayer.getSource(); + var tileGrid = tileSource.getTileGridForProjection(projection); + var z = tileGrid.getZForResolution(viewState.resolution); + var tileResolution = tileGrid.getResolution(z); + + var tilePixelSize = + tileSource.getTilePixelSize(z, frameState.pixelRatio, projection); + var pixelRatio = tilePixelSize[0] / + ol.size.toSize(tileGrid.getTileSize(z), this.tmpSize_)[0]; + var tilePixelResolution = tileResolution / pixelRatio; + var tileGutter = tileSource.getTilePixelRatio(pixelRatio) * tileSource.getGutter(projection); + + var center = viewState.center; + var extent = frameState.extent; + var tileRange = tileGrid.getTileRangeForExtentAndResolution( + extent, tileResolution); + + var framebufferExtent; + if (this.renderedTileRange_ && + this.renderedTileRange_.equals(tileRange) && + this.renderedRevision_ == tileSource.getRevision()) { + framebufferExtent = this.renderedFramebufferExtent_; + } else { + + var tileRangeSize = tileRange.getSize(); + + var maxDimension = Math.max( + tileRangeSize[0] * tilePixelSize[0], + tileRangeSize[1] * tilePixelSize[1]); + var framebufferDimension = ol.math.roundUpToPowerOfTwo(maxDimension); + var framebufferExtentDimension = tilePixelResolution * framebufferDimension; + var origin = tileGrid.getOrigin(z); + var minX = origin[0] + + tileRange.minX * tilePixelSize[0] * tilePixelResolution; + var minY = origin[1] + + tileRange.minY * tilePixelSize[1] * tilePixelResolution; + framebufferExtent = [ + minX, minY, + minX + framebufferExtentDimension, minY + framebufferExtentDimension + ]; + + this.bindFramebuffer(frameState, framebufferDimension); + gl.viewport(0, 0, framebufferDimension, framebufferDimension); + + gl.clearColor(0, 0, 0, 0); + gl.clear(ol.webgl.COLOR_BUFFER_BIT); + gl.disable(ol.webgl.BLEND); + + var program = context.getProgram(this.fragmentShader_, this.vertexShader_); + context.useProgram(program); + if (!this.locations_) { + // eslint-disable-next-line openlayers-internal/no-missing-requires + this.locations_ = new ol.renderer.webgl.tilelayershader.Locations(gl, program); + } + + context.bindBuffer(ol.webgl.ARRAY_BUFFER, this.renderArrayBuffer_); + gl.enableVertexAttribArray(this.locations_.a_position); + gl.vertexAttribPointer( + this.locations_.a_position, 2, ol.webgl.FLOAT, false, 16, 0); + gl.enableVertexAttribArray(this.locations_.a_texCoord); + gl.vertexAttribPointer( + this.locations_.a_texCoord, 2, ol.webgl.FLOAT, false, 16, 8); + gl.uniform1i(this.locations_.u_texture, 0); + + /** + * @type {Object.>} + */ + var tilesToDrawByZ = {}; + tilesToDrawByZ[z] = {}; + + var findLoadedTiles = this.createLoadedTileFinder( + tileSource, projection, tilesToDrawByZ); + + var useInterimTilesOnError = tileLayer.getUseInterimTilesOnError(); + var allTilesLoaded = true; + var tmpExtent = ol.extent.createEmpty(); + var tmpTileRange = new ol.TileRange(0, 0, 0, 0); + var childTileRange, drawable, fullyLoaded, tile, tileState; + var x, y, tileExtent; + for (x = tileRange.minX; x <= tileRange.maxX; ++x) { + for (y = tileRange.minY; y <= tileRange.maxY; ++y) { + + tile = tileSource.getTile(z, x, y, pixelRatio, projection); + if (layerState.extent !== undefined) { + // ignore tiles outside layer extent + tileExtent = tileGrid.getTileCoordExtent(tile.tileCoord, tmpExtent); + if (!ol.extent.intersects(tileExtent, layerState.extent)) { continue; } - - allTilesLoaded = false; - fullyLoaded = tileGrid.forEachTileCoordParentTileRange( - tile.tileCoord, findLoadedTiles, null, tmpTileRange, tmpExtent); - if (!fullyLoaded) { - childTileRange = tileGrid.getTileCoordChildTileRange( - tile.tileCoord, tmpTileRange, tmpExtent); - if (childTileRange) { - findLoadedTiles(z + 1, childTileRange); - } + } + tileState = tile.getState(); + drawable = tileState == ol.TileState.LOADED || + tileState == ol.TileState.EMPTY || + tileState == ol.TileState.ERROR && !useInterimTilesOnError; + if (!drawable) { + tile = tile.getInterimTile(); + } + tileState = tile.getState(); + if (tileState == ol.TileState.LOADED) { + if (mapRenderer.isTileTextureLoaded(tile)) { + tilesToDrawByZ[z][tile.tileCoord.toString()] = tile; + continue; } + } else if (tileState == ol.TileState.EMPTY || + (tileState == ol.TileState.ERROR && + !useInterimTilesOnError)) { + continue; + } + allTilesLoaded = false; + fullyLoaded = tileGrid.forEachTileCoordParentTileRange( + tile.tileCoord, findLoadedTiles, null, tmpTileRange, tmpExtent); + if (!fullyLoaded) { + childTileRange = tileGrid.getTileCoordChildTileRange( + tile.tileCoord, tmpTileRange, tmpExtent); + if (childTileRange) { + findLoadedTiles(z + 1, childTileRange); + } } } - /** @type {Array.} */ - var zs = Object.keys(tilesToDrawByZ).map(Number); - zs.sort(ol.array.numberSafeCompareFunction); - var u_tileOffset = new Float32Array(4); - var i, ii, tileKey, tilesToDraw; - for (i = 0, ii = zs.length; i < ii; ++i) { - tilesToDraw = tilesToDrawByZ[zs[i]]; - for (tileKey in tilesToDraw) { - tile = tilesToDraw[tileKey]; - tileExtent = tileGrid.getTileCoordExtent(tile.tileCoord, tmpExtent); - u_tileOffset[0] = 2 * (tileExtent[2] - tileExtent[0]) / - framebufferExtentDimension; - u_tileOffset[1] = 2 * (tileExtent[3] - tileExtent[1]) / - framebufferExtentDimension; - u_tileOffset[2] = 2 * (tileExtent[0] - framebufferExtent[0]) / - framebufferExtentDimension - 1; - u_tileOffset[3] = 2 * (tileExtent[1] - framebufferExtent[1]) / - framebufferExtentDimension - 1; - gl.uniform4fv(this.locations_.u_tileOffset, u_tileOffset); - mapRenderer.bindTileTexture(tile, tilePixelSize, - tileGutter * pixelRatio, ol.webgl.LINEAR, ol.webgl.LINEAR); - gl.drawArrays(ol.webgl.TRIANGLE_STRIP, 0, 4); - } + } + + /** @type {Array.} */ + var zs = Object.keys(tilesToDrawByZ).map(Number); + zs.sort(ol.array.numberSafeCompareFunction); + var u_tileOffset = new Float32Array(4); + var i, ii, tileKey, tilesToDraw; + for (i = 0, ii = zs.length; i < ii; ++i) { + tilesToDraw = tilesToDrawByZ[zs[i]]; + for (tileKey in tilesToDraw) { + tile = tilesToDraw[tileKey]; + tileExtent = tileGrid.getTileCoordExtent(tile.tileCoord, tmpExtent); + u_tileOffset[0] = 2 * (tileExtent[2] - tileExtent[0]) / + framebufferExtentDimension; + u_tileOffset[1] = 2 * (tileExtent[3] - tileExtent[1]) / + framebufferExtentDimension; + u_tileOffset[2] = 2 * (tileExtent[0] - framebufferExtent[0]) / + framebufferExtentDimension - 1; + u_tileOffset[3] = 2 * (tileExtent[1] - framebufferExtent[1]) / + framebufferExtentDimension - 1; + gl.uniform4fv(this.locations_.u_tileOffset, u_tileOffset); + mapRenderer.bindTileTexture(tile, tilePixelSize, + tileGutter * pixelRatio, ol.webgl.LINEAR, ol.webgl.LINEAR); + gl.drawArrays(ol.webgl.TRIANGLE_STRIP, 0, 4); } - - if (allTilesLoaded) { - this.renderedTileRange_ = tileRange; - this.renderedFramebufferExtent_ = framebufferExtent; - this.renderedRevision_ = tileSource.getRevision(); - } else { - this.renderedTileRange_ = null; - this.renderedFramebufferExtent_ = null; - this.renderedRevision_ = -1; - frameState.animate = true; - } - } - this.updateUsedTiles(frameState.usedTiles, tileSource, z, tileRange); - var tileTextureQueue = mapRenderer.getTileTextureQueue(); - this.manageTilePyramid( - frameState, tileSource, tileGrid, pixelRatio, projection, extent, z, - tileLayer.getPreload(), - /** - * @param {ol.Tile} tile Tile. - */ - function(tile) { - if (tile.getState() == ol.TileState.LOADED && - !mapRenderer.isTileTextureLoaded(tile) && - !tileTextureQueue.isKeyQueued(tile.getKey())) { - tileTextureQueue.enqueue([ - tile, - tileGrid.getTileCoordCenter(tile.tileCoord), - tileGrid.getResolution(tile.tileCoord[0]), - tilePixelSize, tileGutter * pixelRatio - ]); - } - }, this); - this.scheduleExpireCache(frameState, tileSource); - this.updateLogos(frameState, tileSource); - - var texCoordMatrix = this.texCoordMatrix; - ol.transform.reset(texCoordMatrix); - ol.transform.translate(texCoordMatrix, - (Math.round(center[0] / tileResolution) * tileResolution - framebufferExtent[0]) / - (framebufferExtent[2] - framebufferExtent[0]), - (Math.round(center[1] / tileResolution) * tileResolution - framebufferExtent[1]) / - (framebufferExtent[3] - framebufferExtent[1])); - if (viewState.rotation !== 0) { - ol.transform.rotate(texCoordMatrix, viewState.rotation); - } - ol.transform.scale(texCoordMatrix, - frameState.size[0] * viewState.resolution / - (framebufferExtent[2] - framebufferExtent[0]), - frameState.size[1] * viewState.resolution / - (framebufferExtent[3] - framebufferExtent[1])); - ol.transform.translate(texCoordMatrix, -0.5, -0.5); - - return true; - }; - - - /** - * @inheritDoc - */ - ol.renderer.webgl.TileLayer.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg) { - if (!this.framebuffer) { - return undefined; - } - - var pixelOnMapScaled = [ - pixel[0] / frameState.size[0], - (frameState.size[1] - pixel[1]) / frameState.size[1]]; - - var pixelOnFrameBufferScaled = ol.transform.apply( - this.texCoordMatrix, pixelOnMapScaled.slice()); - var pixelOnFrameBuffer = [ - pixelOnFrameBufferScaled[0] * this.framebufferDimension, - pixelOnFrameBufferScaled[1] * this.framebufferDimension]; - - var gl = this.mapRenderer.getContext().getGL(); - gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer); - var imageData = new Uint8Array(4); - gl.readPixels(pixelOnFrameBuffer[0], pixelOnFrameBuffer[1], 1, 1, - gl.RGBA, gl.UNSIGNED_BYTE, imageData); - - if (imageData[3] > 0) { - return callback.call(thisArg, this.getLayer(), imageData); + if (allTilesLoaded) { + this.renderedTileRange_ = tileRange; + this.renderedFramebufferExtent_ = framebufferExtent; + this.renderedRevision_ = tileSource.getRevision(); } else { - return undefined; + this.renderedTileRange_ = null; + this.renderedFramebufferExtent_ = null; + this.renderedRevision_ = -1; + frameState.animate = true; } - }; -} + } + + this.updateUsedTiles(frameState.usedTiles, tileSource, z, tileRange); + var tileTextureQueue = mapRenderer.getTileTextureQueue(); + this.manageTilePyramid( + frameState, tileSource, tileGrid, pixelRatio, projection, extent, z, + tileLayer.getPreload(), + /** + * @param {ol.Tile} tile Tile. + */ + function(tile) { + if (tile.getState() == ol.TileState.LOADED && + !mapRenderer.isTileTextureLoaded(tile) && + !tileTextureQueue.isKeyQueued(tile.getKey())) { + tileTextureQueue.enqueue([ + tile, + tileGrid.getTileCoordCenter(tile.tileCoord), + tileGrid.getResolution(tile.tileCoord[0]), + tilePixelSize, tileGutter * pixelRatio + ]); + } + }, this); + this.scheduleExpireCache(frameState, tileSource); + this.updateLogos(frameState, tileSource); + + var texCoordMatrix = this.texCoordMatrix; + ol.transform.reset(texCoordMatrix); + ol.transform.translate(texCoordMatrix, + (Math.round(center[0] / tileResolution) * tileResolution - framebufferExtent[0]) / + (framebufferExtent[2] - framebufferExtent[0]), + (Math.round(center[1] / tileResolution) * tileResolution - framebufferExtent[1]) / + (framebufferExtent[3] - framebufferExtent[1])); + if (viewState.rotation !== 0) { + ol.transform.rotate(texCoordMatrix, viewState.rotation); + } + ol.transform.scale(texCoordMatrix, + frameState.size[0] * viewState.resolution / + (framebufferExtent[2] - framebufferExtent[0]), + frameState.size[1] * viewState.resolution / + (framebufferExtent[3] - framebufferExtent[1])); + ol.transform.translate(texCoordMatrix, -0.5, -0.5); + + return true; +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.TileLayer.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg) { + if (!this.framebuffer) { + return undefined; + } + + var pixelOnMapScaled = [ + pixel[0] / frameState.size[0], + (frameState.size[1] - pixel[1]) / frameState.size[1]]; + + var pixelOnFrameBufferScaled = ol.transform.apply( + this.texCoordMatrix, pixelOnMapScaled.slice()); + var pixelOnFrameBuffer = [ + pixelOnFrameBufferScaled[0] * this.framebufferDimension, + pixelOnFrameBufferScaled[1] * this.framebufferDimension]; + + var gl = this.mapRenderer.getContext().getGL(); + gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer); + var imageData = new Uint8Array(4); + gl.readPixels(pixelOnFrameBuffer[0], pixelOnFrameBuffer[1], 1, 1, + gl.RGBA, gl.UNSIGNED_BYTE, imageData); + + if (imageData[3] > 0) { + return callback.call(thisArg, this.getLayer(), imageData); + } else { + return undefined; + } +}; diff --git a/src/ol/renderer/webgl/tilelayershader.js b/src/ol/renderer/webgl/tilelayershader.js index c97eeb56d0..98a51845be 100644 --- a/src/ol/renderer/webgl/tilelayershader.js +++ b/src/ol/renderer/webgl/tilelayershader.js @@ -6,113 +6,109 @@ goog.require('ol'); goog.require('ol.webgl.Fragment'); goog.require('ol.webgl.Vertex'); -if (ol.ENABLE_WEBGL) { +/** + * @constructor + * @extends {ol.webgl.Fragment} + * @struct + */ +ol.renderer.webgl.tilelayershader.Fragment = function() { + ol.webgl.Fragment.call(this, ol.renderer.webgl.tilelayershader.Fragment.SOURCE); +}; +ol.inherits(ol.renderer.webgl.tilelayershader.Fragment, ol.webgl.Fragment); + + +/** + * @const + * @type {string} + */ +ol.renderer.webgl.tilelayershader.Fragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\n\n\nuniform sampler2D u_texture;\n\nvoid main(void) {\n gl_FragColor = texture2D(u_texture, v_texCoord);\n}\n'; + + +/** + * @const + * @type {string} + */ +ol.renderer.webgl.tilelayershader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;uniform sampler2D e;void main(void){gl_FragColor=texture2D(e,a);}'; + + +/** + * @const + * @type {string} + */ +ol.renderer.webgl.tilelayershader.Fragment.SOURCE = ol.DEBUG_WEBGL ? + ol.renderer.webgl.tilelayershader.Fragment.DEBUG_SOURCE : + ol.renderer.webgl.tilelayershader.Fragment.OPTIMIZED_SOURCE; + + +ol.renderer.webgl.tilelayershader.fragment = new ol.renderer.webgl.tilelayershader.Fragment(); + + +/** + * @constructor + * @extends {ol.webgl.Vertex} + * @struct + */ +ol.renderer.webgl.tilelayershader.Vertex = function() { + ol.webgl.Vertex.call(this, ol.renderer.webgl.tilelayershader.Vertex.SOURCE); +}; +ol.inherits(ol.renderer.webgl.tilelayershader.Vertex, ol.webgl.Vertex); + + +/** + * @const + * @type {string} + */ +ol.renderer.webgl.tilelayershader.Vertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\n\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\nuniform vec4 u_tileOffset;\n\nvoid main(void) {\n gl_Position = vec4(a_position * u_tileOffset.xy + u_tileOffset.zw, 0., 1.);\n v_texCoord = a_texCoord;\n}\n\n\n'; + + +/** + * @const + * @type {string} + */ +ol.renderer.webgl.tilelayershader.Vertex.OPTIMIZED_SOURCE = 'varying vec2 a;attribute vec2 b;attribute vec2 c;uniform vec4 d;void main(void){gl_Position=vec4(b*d.xy+d.zw,0.,1.);a=c;}'; + + +/** + * @const + * @type {string} + */ +ol.renderer.webgl.tilelayershader.Vertex.SOURCE = ol.DEBUG_WEBGL ? + ol.renderer.webgl.tilelayershader.Vertex.DEBUG_SOURCE : + ol.renderer.webgl.tilelayershader.Vertex.OPTIMIZED_SOURCE; + + +ol.renderer.webgl.tilelayershader.vertex = new ol.renderer.webgl.tilelayershader.Vertex(); + + +/** + * @constructor + * @param {WebGLRenderingContext} gl GL. + * @param {WebGLProgram} program Program. + * @struct + */ +ol.renderer.webgl.tilelayershader.Locations = function(gl, program) { /** - * @constructor - * @extends {ol.webgl.Fragment} - * @struct + * @type {WebGLUniformLocation} */ - ol.renderer.webgl.tilelayershader.Fragment = function() { - ol.webgl.Fragment.call(this, ol.renderer.webgl.tilelayershader.Fragment.SOURCE); - }; - ol.inherits(ol.renderer.webgl.tilelayershader.Fragment, ol.webgl.Fragment); - + this.u_texture = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_texture' : 'e'); /** - * @const - * @type {string} + * @type {WebGLUniformLocation} */ - ol.renderer.webgl.tilelayershader.Fragment.DEBUG_SOURCE = 'precision mediump float;\nvarying vec2 v_texCoord;\n\n\nuniform sampler2D u_texture;\n\nvoid main(void) {\n gl_FragColor = texture2D(u_texture, v_texCoord);\n}\n'; - + this.u_tileOffset = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? 'u_tileOffset' : 'd'); /** - * @const - * @type {string} + * @type {number} */ - ol.renderer.webgl.tilelayershader.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;varying vec2 a;uniform sampler2D e;void main(void){gl_FragColor=texture2D(e,a);}'; - + this.a_position = gl.getAttribLocation( + program, ol.DEBUG_WEBGL ? 'a_position' : 'b'); /** - * @const - * @type {string} + * @type {number} */ - ol.renderer.webgl.tilelayershader.Fragment.SOURCE = ol.DEBUG_WEBGL ? - ol.renderer.webgl.tilelayershader.Fragment.DEBUG_SOURCE : - ol.renderer.webgl.tilelayershader.Fragment.OPTIMIZED_SOURCE; - - - ol.renderer.webgl.tilelayershader.fragment = new ol.renderer.webgl.tilelayershader.Fragment(); - - - /** - * @constructor - * @extends {ol.webgl.Vertex} - * @struct - */ - ol.renderer.webgl.tilelayershader.Vertex = function() { - ol.webgl.Vertex.call(this, ol.renderer.webgl.tilelayershader.Vertex.SOURCE); - }; - ol.inherits(ol.renderer.webgl.tilelayershader.Vertex, ol.webgl.Vertex); - - - /** - * @const - * @type {string} - */ - ol.renderer.webgl.tilelayershader.Vertex.DEBUG_SOURCE = 'varying vec2 v_texCoord;\n\n\nattribute vec2 a_position;\nattribute vec2 a_texCoord;\nuniform vec4 u_tileOffset;\n\nvoid main(void) {\n gl_Position = vec4(a_position * u_tileOffset.xy + u_tileOffset.zw, 0., 1.);\n v_texCoord = a_texCoord;\n}\n\n\n'; - - - /** - * @const - * @type {string} - */ - ol.renderer.webgl.tilelayershader.Vertex.OPTIMIZED_SOURCE = 'varying vec2 a;attribute vec2 b;attribute vec2 c;uniform vec4 d;void main(void){gl_Position=vec4(b*d.xy+d.zw,0.,1.);a=c;}'; - - - /** - * @const - * @type {string} - */ - ol.renderer.webgl.tilelayershader.Vertex.SOURCE = ol.DEBUG_WEBGL ? - ol.renderer.webgl.tilelayershader.Vertex.DEBUG_SOURCE : - ol.renderer.webgl.tilelayershader.Vertex.OPTIMIZED_SOURCE; - - - ol.renderer.webgl.tilelayershader.vertex = new ol.renderer.webgl.tilelayershader.Vertex(); - - - /** - * @constructor - * @param {WebGLRenderingContext} gl GL. - * @param {WebGLProgram} program Program. - * @struct - */ - ol.renderer.webgl.tilelayershader.Locations = function(gl, program) { - - /** - * @type {WebGLUniformLocation} - */ - this.u_texture = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_texture' : 'e'); - - /** - * @type {WebGLUniformLocation} - */ - this.u_tileOffset = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? 'u_tileOffset' : 'd'); - - /** - * @type {number} - */ - this.a_position = gl.getAttribLocation( - program, ol.DEBUG_WEBGL ? 'a_position' : 'b'); - - /** - * @type {number} - */ - this.a_texCoord = gl.getAttribLocation( - program, ol.DEBUG_WEBGL ? 'a_texCoord' : 'c'); - }; - -} + this.a_texCoord = gl.getAttribLocation( + program, ol.DEBUG_WEBGL ? 'a_texCoord' : 'c'); +}; diff --git a/src/ol/renderer/webgl/vectorlayer.js b/src/ol/renderer/webgl/vectorlayer.js index ec8ae1cd47..b34425e950 100644 --- a/src/ol/renderer/webgl/vectorlayer.js +++ b/src/ol/renderer/webgl/vectorlayer.js @@ -11,337 +11,333 @@ goog.require('ol.renderer.webgl.Layer'); goog.require('ol.transform'); -if (ol.ENABLE_WEBGL) { +/** + * @constructor + * @extends {ol.renderer.webgl.Layer} + * @param {ol.renderer.webgl.Map} mapRenderer Map renderer. + * @param {ol.layer.Vector} vectorLayer Vector layer. + * @api + */ +ol.renderer.webgl.VectorLayer = function(mapRenderer, vectorLayer) { + + ol.renderer.webgl.Layer.call(this, mapRenderer, vectorLayer); /** - * @constructor - * @extends {ol.renderer.webgl.Layer} - * @param {ol.renderer.webgl.Map} mapRenderer Map renderer. - * @param {ol.layer.Vector} vectorLayer Vector layer. - * @api - */ - ol.renderer.webgl.VectorLayer = function(mapRenderer, vectorLayer) { - - ol.renderer.webgl.Layer.call(this, mapRenderer, vectorLayer); - - /** - * @private - * @type {boolean} - */ - this.dirty_ = false; - - /** - * @private - * @type {number} - */ - this.renderedRevision_ = -1; - - /** - * @private - * @type {number} - */ - this.renderedResolution_ = NaN; - - /** - * @private - * @type {ol.Extent} - */ - this.renderedExtent_ = ol.extent.createEmpty(); - - /** - * @private - * @type {function(ol.Feature, ol.Feature): number|null} - */ - this.renderedRenderOrder_ = null; - - /** - * @private - * @type {ol.render.webgl.ReplayGroup} - */ - this.replayGroup_ = null; - - /** - * The last layer state. - * @private - * @type {?ol.LayerState} - */ - this.layerState_ = null; - - }; - ol.inherits(ol.renderer.webgl.VectorLayer, ol.renderer.webgl.Layer); - - - /** - * Determine if this renderer handles the provided layer. - * @param {ol.renderer.Type} type The renderer type. - * @param {ol.layer.Layer} layer The candidate layer. - * @return {boolean} The renderer can render the layer. - */ - ol.renderer.webgl.VectorLayer['handles'] = function(type, layer) { - return type === ol.renderer.Type.WEBGL && layer.getType() === ol.LayerType.VECTOR; - }; - - - /** - * Create a layer renderer. - * @param {ol.renderer.Map} mapRenderer The map renderer. - * @param {ol.layer.Layer} layer The layer to be rendererd. - * @return {ol.renderer.webgl.VectorLayer} The layer renderer. - */ - ol.renderer.webgl.VectorLayer['create'] = function(mapRenderer, layer) { - return new ol.renderer.webgl.VectorLayer( - /** @type {ol.renderer.webgl.Map} */ (mapRenderer), - /** @type {ol.layer.Vector} */ (layer) - ); - }; - - - /** - * @inheritDoc - */ - ol.renderer.webgl.VectorLayer.prototype.composeFrame = function(frameState, layerState, context) { - this.layerState_ = layerState; - var viewState = frameState.viewState; - var replayGroup = this.replayGroup_; - var size = frameState.size; - var pixelRatio = frameState.pixelRatio; - var gl = this.mapRenderer.getGL(); - if (replayGroup && !replayGroup.isEmpty()) { - gl.enable(gl.SCISSOR_TEST); - gl.scissor(0, 0, size[0] * pixelRatio, size[1] * pixelRatio); - replayGroup.replay(context, - viewState.center, viewState.resolution, viewState.rotation, - size, pixelRatio, layerState.opacity, - layerState.managed ? frameState.skippedFeatureUids : {}); - gl.disable(gl.SCISSOR_TEST); - } - - }; - - - /** - * @inheritDoc - */ - ol.renderer.webgl.VectorLayer.prototype.disposeInternal = function() { - var replayGroup = this.replayGroup_; - if (replayGroup) { - var context = this.mapRenderer.getContext(); - replayGroup.getDeleteResourcesFunction(context)(); - this.replayGroup_ = null; - } - ol.renderer.webgl.Layer.prototype.disposeInternal.call(this); - }; - - - /** - * @inheritDoc - */ - ol.renderer.webgl.VectorLayer.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, callback, thisArg) { - if (!this.replayGroup_ || !this.layerState_) { - return undefined; - } else { - var context = this.mapRenderer.getContext(); - var viewState = frameState.viewState; - var layer = this.getLayer(); - var layerState = this.layerState_; - /** @type {Object.} */ - var features = {}; - return this.replayGroup_.forEachFeatureAtCoordinate(coordinate, - context, viewState.center, viewState.resolution, viewState.rotation, - frameState.size, frameState.pixelRatio, layerState.opacity, - {}, - /** - * @param {ol.Feature|ol.render.Feature} feature Feature. - * @return {?} Callback result. - */ - function(feature) { - var key = ol.getUid(feature).toString(); - if (!(key in features)) { - features[key] = true; - return callback.call(thisArg, feature, layer); - } - }); - } - }; - - - /** - * @inheritDoc - */ - ol.renderer.webgl.VectorLayer.prototype.hasFeatureAtCoordinate = function(coordinate, frameState) { - if (!this.replayGroup_ || !this.layerState_) { - return false; - } else { - var context = this.mapRenderer.getContext(); - var viewState = frameState.viewState; - var layerState = this.layerState_; - return this.replayGroup_.hasFeatureAtCoordinate(coordinate, - context, viewState.center, viewState.resolution, viewState.rotation, - frameState.size, frameState.pixelRatio, layerState.opacity, - frameState.skippedFeatureUids); - } - }; - - - /** - * @inheritDoc - */ - ol.renderer.webgl.VectorLayer.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg) { - var coordinate = ol.transform.apply( - frameState.pixelToCoordinateTransform, pixel.slice()); - var hasFeature = this.hasFeatureAtCoordinate(coordinate, frameState); - - if (hasFeature) { - return callback.call(thisArg, this.getLayer(), null); - } else { - return undefined; - } - }; - - - /** - * Handle changes in image style state. - * @param {ol.events.Event} event Image style change event. * @private + * @type {boolean} */ - ol.renderer.webgl.VectorLayer.prototype.handleStyleImageChange_ = function(event) { - this.renderIfReadyAndVisible(); - }; - + this.dirty_ = false; /** - * @inheritDoc + * @private + * @type {number} */ - ol.renderer.webgl.VectorLayer.prototype.prepareFrame = function(frameState, layerState, context) { + this.renderedRevision_ = -1; - var vectorLayer = /** @type {ol.layer.Vector} */ (this.getLayer()); - var vectorSource = vectorLayer.getSource(); + /** + * @private + * @type {number} + */ + this.renderedResolution_ = NaN; - this.updateAttributions( - frameState.attributions, vectorSource.getAttributions()); - this.updateLogos(frameState, vectorSource); + /** + * @private + * @type {ol.Extent} + */ + this.renderedExtent_ = ol.extent.createEmpty(); - var animating = frameState.viewHints[ol.ViewHint.ANIMATING]; - var interacting = frameState.viewHints[ol.ViewHint.INTERACTING]; - var updateWhileAnimating = vectorLayer.getUpdateWhileAnimating(); - var updateWhileInteracting = vectorLayer.getUpdateWhileInteracting(); + /** + * @private + * @type {function(ol.Feature, ol.Feature): number|null} + */ + this.renderedRenderOrder_ = null; - if (!this.dirty_ && (!updateWhileAnimating && animating) || - (!updateWhileInteracting && interacting)) { - return true; - } + /** + * @private + * @type {ol.render.webgl.ReplayGroup} + */ + this.replayGroup_ = null; - var frameStateExtent = frameState.extent; + /** + * The last layer state. + * @private + * @type {?ol.LayerState} + */ + this.layerState_ = null; + +}; +ol.inherits(ol.renderer.webgl.VectorLayer, ol.renderer.webgl.Layer); + + +/** + * Determine if this renderer handles the provided layer. + * @param {ol.renderer.Type} type The renderer type. + * @param {ol.layer.Layer} layer The candidate layer. + * @return {boolean} The renderer can render the layer. + */ +ol.renderer.webgl.VectorLayer['handles'] = function(type, layer) { + return type === ol.renderer.Type.WEBGL && layer.getType() === ol.LayerType.VECTOR; +}; + + +/** + * Create a layer renderer. + * @param {ol.renderer.Map} mapRenderer The map renderer. + * @param {ol.layer.Layer} layer The layer to be rendererd. + * @return {ol.renderer.webgl.VectorLayer} The layer renderer. + */ +ol.renderer.webgl.VectorLayer['create'] = function(mapRenderer, layer) { + return new ol.renderer.webgl.VectorLayer( + /** @type {ol.renderer.webgl.Map} */ (mapRenderer), + /** @type {ol.layer.Vector} */ (layer) + ); +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.VectorLayer.prototype.composeFrame = function(frameState, layerState, context) { + this.layerState_ = layerState; + var viewState = frameState.viewState; + var replayGroup = this.replayGroup_; + var size = frameState.size; + var pixelRatio = frameState.pixelRatio; + var gl = this.mapRenderer.getGL(); + if (replayGroup && !replayGroup.isEmpty()) { + gl.enable(gl.SCISSOR_TEST); + gl.scissor(0, 0, size[0] * pixelRatio, size[1] * pixelRatio); + replayGroup.replay(context, + viewState.center, viewState.resolution, viewState.rotation, + size, pixelRatio, layerState.opacity, + layerState.managed ? frameState.skippedFeatureUids : {}); + gl.disable(gl.SCISSOR_TEST); + } + +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.VectorLayer.prototype.disposeInternal = function() { + var replayGroup = this.replayGroup_; + if (replayGroup) { + var context = this.mapRenderer.getContext(); + replayGroup.getDeleteResourcesFunction(context)(); + this.replayGroup_ = null; + } + ol.renderer.webgl.Layer.prototype.disposeInternal.call(this); +}; + + +/** + * @inheritDoc + */ +ol.renderer.webgl.VectorLayer.prototype.forEachFeatureAtCoordinate = function(coordinate, frameState, hitTolerance, callback, thisArg) { + if (!this.replayGroup_ || !this.layerState_) { + return undefined; + } else { + var context = this.mapRenderer.getContext(); var viewState = frameState.viewState; - var projection = viewState.projection; - var resolution = viewState.resolution; - var pixelRatio = frameState.pixelRatio; - var vectorLayerRevision = vectorLayer.getRevision(); - var vectorLayerRenderBuffer = vectorLayer.getRenderBuffer(); - var vectorLayerRenderOrder = vectorLayer.getRenderOrder(); + var layer = this.getLayer(); + var layerState = this.layerState_; + /** @type {Object.} */ + var features = {}; + return this.replayGroup_.forEachFeatureAtCoordinate(coordinate, + context, viewState.center, viewState.resolution, viewState.rotation, + frameState.size, frameState.pixelRatio, layerState.opacity, + {}, + /** + * @param {ol.Feature|ol.render.Feature} feature Feature. + * @return {?} Callback result. + */ + function(feature) { + var key = ol.getUid(feature).toString(); + if (!(key in features)) { + features[key] = true; + return callback.call(thisArg, feature, layer); + } + }); + } +}; - if (vectorLayerRenderOrder === undefined) { - vectorLayerRenderOrder = ol.renderer.vector.defaultOrder; - } - var extent = ol.extent.buffer(frameStateExtent, - vectorLayerRenderBuffer * resolution); +/** + * @inheritDoc + */ +ol.renderer.webgl.VectorLayer.prototype.hasFeatureAtCoordinate = function(coordinate, frameState) { + if (!this.replayGroup_ || !this.layerState_) { + return false; + } else { + var context = this.mapRenderer.getContext(); + var viewState = frameState.viewState; + var layerState = this.layerState_; + return this.replayGroup_.hasFeatureAtCoordinate(coordinate, + context, viewState.center, viewState.resolution, viewState.rotation, + frameState.size, frameState.pixelRatio, layerState.opacity, + frameState.skippedFeatureUids); + } +}; - if (!this.dirty_ && - this.renderedResolution_ == resolution && - this.renderedRevision_ == vectorLayerRevision && - this.renderedRenderOrder_ == vectorLayerRenderOrder && - ol.extent.containsExtent(this.renderedExtent_, extent)) { - return true; - } - if (this.replayGroup_) { - frameState.postRenderFunctions.push( - this.replayGroup_.getDeleteResourcesFunction(context)); - } +/** + * @inheritDoc + */ +ol.renderer.webgl.VectorLayer.prototype.forEachLayerAtPixel = function(pixel, frameState, callback, thisArg) { + var coordinate = ol.transform.apply( + frameState.pixelToCoordinateTransform, pixel.slice()); + var hasFeature = this.hasFeatureAtCoordinate(coordinate, frameState); - this.dirty_ = false; + if (hasFeature) { + return callback.call(thisArg, this.getLayer(), null); + } else { + return undefined; + } +}; - var replayGroup = new ol.render.webgl.ReplayGroup( - ol.renderer.vector.getTolerance(resolution, pixelRatio), - extent, vectorLayer.getRenderBuffer()); - vectorSource.loadFeatures(extent, resolution, projection); - /** - * @param {ol.Feature} feature Feature. - * @this {ol.renderer.webgl.VectorLayer} - */ - var renderFeature = function(feature) { - var styles; - var styleFunction = feature.getStyleFunction(); - if (styleFunction) { - styles = styleFunction.call(feature, resolution); - } else { - styleFunction = vectorLayer.getStyleFunction(); - if (styleFunction) { - styles = styleFunction(feature, resolution); - } - } - if (styles) { - var dirty = this.renderFeature( - feature, resolution, pixelRatio, styles, replayGroup); - this.dirty_ = this.dirty_ || dirty; - } - }; - if (vectorLayerRenderOrder) { - /** @type {Array.} */ - var features = []; - vectorSource.forEachFeatureInExtent(extent, - /** - * @param {ol.Feature} feature Feature. - */ - function(feature) { - features.push(feature); - }, this); - features.sort(vectorLayerRenderOrder); - features.forEach(renderFeature, this); - } else { - vectorSource.forEachFeatureInExtent(extent, renderFeature, this); - } - replayGroup.finish(context); - this.renderedResolution_ = resolution; - this.renderedRevision_ = vectorLayerRevision; - this.renderedRenderOrder_ = vectorLayerRenderOrder; - this.renderedExtent_ = extent; - this.replayGroup_ = replayGroup; +/** + * Handle changes in image style state. + * @param {ol.events.Event} event Image style change event. + * @private + */ +ol.renderer.webgl.VectorLayer.prototype.handleStyleImageChange_ = function(event) { + this.renderIfReadyAndVisible(); +}; + +/** + * @inheritDoc + */ +ol.renderer.webgl.VectorLayer.prototype.prepareFrame = function(frameState, layerState, context) { + + var vectorLayer = /** @type {ol.layer.Vector} */ (this.getLayer()); + var vectorSource = vectorLayer.getSource(); + + this.updateAttributions( + frameState.attributions, vectorSource.getAttributions()); + this.updateLogos(frameState, vectorSource); + + var animating = frameState.viewHints[ol.ViewHint.ANIMATING]; + var interacting = frameState.viewHints[ol.ViewHint.INTERACTING]; + var updateWhileAnimating = vectorLayer.getUpdateWhileAnimating(); + var updateWhileInteracting = vectorLayer.getUpdateWhileInteracting(); + + if (!this.dirty_ && (!updateWhileAnimating && animating) || + (!updateWhileInteracting && interacting)) { return true; - }; + } + var frameStateExtent = frameState.extent; + var viewState = frameState.viewState; + var projection = viewState.projection; + var resolution = viewState.resolution; + var pixelRatio = frameState.pixelRatio; + var vectorLayerRevision = vectorLayer.getRevision(); + var vectorLayerRenderBuffer = vectorLayer.getRenderBuffer(); + var vectorLayerRenderOrder = vectorLayer.getRenderOrder(); + if (vectorLayerRenderOrder === undefined) { + vectorLayerRenderOrder = ol.renderer.vector.defaultOrder; + } + + var extent = ol.extent.buffer(frameStateExtent, + vectorLayerRenderBuffer * resolution); + + if (!this.dirty_ && + this.renderedResolution_ == resolution && + this.renderedRevision_ == vectorLayerRevision && + this.renderedRenderOrder_ == vectorLayerRenderOrder && + ol.extent.containsExtent(this.renderedExtent_, extent)) { + return true; + } + + if (this.replayGroup_) { + frameState.postRenderFunctions.push( + this.replayGroup_.getDeleteResourcesFunction(context)); + } + + this.dirty_ = false; + + var replayGroup = new ol.render.webgl.ReplayGroup( + ol.renderer.vector.getTolerance(resolution, pixelRatio), + extent, vectorLayer.getRenderBuffer()); + vectorSource.loadFeatures(extent, resolution, projection); /** * @param {ol.Feature} feature Feature. - * @param {number} resolution Resolution. - * @param {number} pixelRatio Pixel ratio. - * @param {(ol.style.Style|Array.)} styles The style or array of - * styles. - * @param {ol.render.webgl.ReplayGroup} replayGroup Replay group. - * @return {boolean} `true` if an image is loading. + * @this {ol.renderer.webgl.VectorLayer} */ - ol.renderer.webgl.VectorLayer.prototype.renderFeature = function(feature, resolution, pixelRatio, styles, replayGroup) { - if (!styles) { - return false; - } - var loading = false; - if (Array.isArray(styles)) { - for (var i = styles.length - 1, ii = 0; i >= ii; --i) { - loading = ol.renderer.vector.renderFeature( - replayGroup, feature, styles[i], - ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio), - this.handleStyleImageChange_, this) || loading; - } + var renderFeature = function(feature) { + var styles; + var styleFunction = feature.getStyleFunction(); + if (styleFunction) { + styles = styleFunction.call(feature, resolution); } else { + styleFunction = vectorLayer.getStyleFunction(); + if (styleFunction) { + styles = styleFunction(feature, resolution); + } + } + if (styles) { + var dirty = this.renderFeature( + feature, resolution, pixelRatio, styles, replayGroup); + this.dirty_ = this.dirty_ || dirty; + } + }; + if (vectorLayerRenderOrder) { + /** @type {Array.} */ + var features = []; + vectorSource.forEachFeatureInExtent(extent, + /** + * @param {ol.Feature} feature Feature. + */ + function(feature) { + features.push(feature); + }, this); + features.sort(vectorLayerRenderOrder); + features.forEach(renderFeature, this); + } else { + vectorSource.forEachFeatureInExtent(extent, renderFeature, this); + } + replayGroup.finish(context); + + this.renderedResolution_ = resolution; + this.renderedRevision_ = vectorLayerRevision; + this.renderedRenderOrder_ = vectorLayerRenderOrder; + this.renderedExtent_ = extent; + this.replayGroup_ = replayGroup; + + return true; +}; + + +/** + * @param {ol.Feature} feature Feature. + * @param {number} resolution Resolution. + * @param {number} pixelRatio Pixel ratio. + * @param {(ol.style.Style|Array.)} styles The style or array of + * styles. + * @param {ol.render.webgl.ReplayGroup} replayGroup Replay group. + * @return {boolean} `true` if an image is loading. + */ +ol.renderer.webgl.VectorLayer.prototype.renderFeature = function(feature, resolution, pixelRatio, styles, replayGroup) { + if (!styles) { + return false; + } + var loading = false; + if (Array.isArray(styles)) { + for (var i = styles.length - 1, ii = 0; i >= ii; --i) { loading = ol.renderer.vector.renderFeature( - replayGroup, feature, styles, + replayGroup, feature, styles[i], ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio), this.handleStyleImageChange_, this) || loading; } - return loading; - }; - -} + } else { + loading = ol.renderer.vector.renderFeature( + replayGroup, feature, styles, + ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio), + this.handleStyleImageChange_, this) || loading; + } + return loading; +}; diff --git a/src/ol/webgl.js b/src/ol/webgl.js index a8d09b33a9..64967f8e37 100644 --- a/src/ol/webgl.js +++ b/src/ol/webgl.js @@ -3,292 +3,288 @@ goog.provide('ol.webgl'); goog.require('ol'); -if (ol.ENABLE_WEBGL) { +/** Constants taken from goog.webgl + */ - /** Constants taken from goog.webgl - */ +/** + * @const + * @type {number} + */ +ol.webgl.ONE = 1; - /** - * @const - * @type {number} - */ - ol.webgl.ONE = 1; +/** + * @const + * @type {number} + */ +ol.webgl.SRC_ALPHA = 0x0302; - /** - * @const - * @type {number} - */ - ol.webgl.SRC_ALPHA = 0x0302; +/** + * @const + * @type {number} + */ +ol.webgl.COLOR_ATTACHMENT0 = 0x8CE0; - /** - * @const - * @type {number} - */ - ol.webgl.COLOR_ATTACHMENT0 = 0x8CE0; +/** + * @const + * @type {number} + */ +ol.webgl.COLOR_BUFFER_BIT = 0x00004000; - /** - * @const - * @type {number} - */ - ol.webgl.COLOR_BUFFER_BIT = 0x00004000; +/** + * @const + * @type {number} + */ +ol.webgl.TRIANGLES = 0x0004; - /** - * @const - * @type {number} - */ - ol.webgl.TRIANGLES = 0x0004; +/** + * @const + * @type {number} + */ +ol.webgl.TRIANGLE_STRIP = 0x0005; - /** - * @const - * @type {number} - */ - ol.webgl.TRIANGLE_STRIP = 0x0005; +/** + * @const + * @type {number} + */ +ol.webgl.ONE_MINUS_SRC_ALPHA = 0x0303; - /** - * @const - * @type {number} - */ - ol.webgl.ONE_MINUS_SRC_ALPHA = 0x0303; +/** + * @const + * @type {number} + */ +ol.webgl.ARRAY_BUFFER = 0x8892; - /** - * @const - * @type {number} - */ - ol.webgl.ARRAY_BUFFER = 0x8892; +/** + * @const + * @type {number} + */ +ol.webgl.ELEMENT_ARRAY_BUFFER = 0x8893; - /** - * @const - * @type {number} - */ - ol.webgl.ELEMENT_ARRAY_BUFFER = 0x8893; +/** + * @const + * @type {number} + */ +ol.webgl.STREAM_DRAW = 0x88E0; - /** - * @const - * @type {number} - */ - ol.webgl.STREAM_DRAW = 0x88E0; +/** + * @const + * @type {number} + */ +ol.webgl.STATIC_DRAW = 0x88E4; - /** - * @const - * @type {number} - */ - ol.webgl.STATIC_DRAW = 0x88E4; +/** + * @const + * @type {number} + */ +ol.webgl.DYNAMIC_DRAW = 0x88E8; - /** - * @const - * @type {number} - */ - ol.webgl.DYNAMIC_DRAW = 0x88E8; +/** + * @const + * @type {number} + */ +ol.webgl.CULL_FACE = 0x0B44; - /** - * @const - * @type {number} - */ - ol.webgl.CULL_FACE = 0x0B44; +/** + * @const + * @type {number} + */ +ol.webgl.BLEND = 0x0BE2; - /** - * @const - * @type {number} - */ - ol.webgl.BLEND = 0x0BE2; +/** + * @const + * @type {number} + */ +ol.webgl.STENCIL_TEST = 0x0B90; - /** - * @const - * @type {number} - */ - ol.webgl.STENCIL_TEST = 0x0B90; +/** + * @const + * @type {number} + */ +ol.webgl.DEPTH_TEST = 0x0B71; - /** - * @const - * @type {number} - */ - ol.webgl.DEPTH_TEST = 0x0B71; +/** + * @const + * @type {number} + */ +ol.webgl.SCISSOR_TEST = 0x0C11; - /** - * @const - * @type {number} - */ - ol.webgl.SCISSOR_TEST = 0x0C11; +/** + * @const + * @type {number} + */ +ol.webgl.UNSIGNED_BYTE = 0x1401; - /** - * @const - * @type {number} - */ - ol.webgl.UNSIGNED_BYTE = 0x1401; +/** + * @const + * @type {number} + */ +ol.webgl.UNSIGNED_SHORT = 0x1403; - /** - * @const - * @type {number} - */ - ol.webgl.UNSIGNED_SHORT = 0x1403; +/** + * @const + * @type {number} + */ +ol.webgl.UNSIGNED_INT = 0x1405; - /** - * @const - * @type {number} - */ - ol.webgl.UNSIGNED_INT = 0x1405; +/** + * @const + * @type {number} + */ +ol.webgl.FLOAT = 0x1406; - /** - * @const - * @type {number} - */ - ol.webgl.FLOAT = 0x1406; +/** + * @const + * @type {number} + */ +ol.webgl.RGBA = 0x1908; - /** - * @const - * @type {number} - */ - ol.webgl.RGBA = 0x1908; +/** + * @const + * @type {number} + */ +ol.webgl.FRAGMENT_SHADER = 0x8B30; - /** - * @const - * @type {number} - */ - ol.webgl.FRAGMENT_SHADER = 0x8B30; +/** + * @const + * @type {number} + */ +ol.webgl.VERTEX_SHADER = 0x8B31; - /** - * @const - * @type {number} - */ - ol.webgl.VERTEX_SHADER = 0x8B31; +/** + * @const + * @type {number} + */ +ol.webgl.LINK_STATUS = 0x8B82; - /** - * @const - * @type {number} - */ - ol.webgl.LINK_STATUS = 0x8B82; +/** + * @const + * @type {number} + */ +ol.webgl.LINEAR = 0x2601; - /** - * @const - * @type {number} - */ - ol.webgl.LINEAR = 0x2601; +/** + * @const + * @type {number} + */ +ol.webgl.TEXTURE_MAG_FILTER = 0x2800; - /** - * @const - * @type {number} - */ - ol.webgl.TEXTURE_MAG_FILTER = 0x2800; +/** + * @const + * @type {number} + */ +ol.webgl.TEXTURE_MIN_FILTER = 0x2801; - /** - * @const - * @type {number} - */ - ol.webgl.TEXTURE_MIN_FILTER = 0x2801; +/** + * @const + * @type {number} + */ +ol.webgl.TEXTURE_WRAP_S = 0x2802; - /** - * @const - * @type {number} - */ - ol.webgl.TEXTURE_WRAP_S = 0x2802; +/** + * @const + * @type {number} + */ +ol.webgl.TEXTURE_WRAP_T = 0x2803; - /** - * @const - * @type {number} - */ - ol.webgl.TEXTURE_WRAP_T = 0x2803; +/** + * @const + * @type {number} + */ +ol.webgl.TEXTURE_2D = 0x0DE1; - /** - * @const - * @type {number} - */ - ol.webgl.TEXTURE_2D = 0x0DE1; +/** + * @const + * @type {number} + */ +ol.webgl.TEXTURE0 = 0x84C0; - /** - * @const - * @type {number} - */ - ol.webgl.TEXTURE0 = 0x84C0; +/** + * @const + * @type {number} + */ +ol.webgl.CLAMP_TO_EDGE = 0x812F; - /** - * @const - * @type {number} - */ - ol.webgl.CLAMP_TO_EDGE = 0x812F; +/** + * @const + * @type {number} + */ +ol.webgl.COMPILE_STATUS = 0x8B81; - /** - * @const - * @type {number} - */ - ol.webgl.COMPILE_STATUS = 0x8B81; +/** + * @const + * @type {number} + */ +ol.webgl.FRAMEBUFFER = 0x8D40; - /** - * @const - * @type {number} - */ - ol.webgl.FRAMEBUFFER = 0x8D40; +/** end of goog.webgl constants + */ - /** end of goog.webgl constants - */ +/** + * @const + * @private + * @type {Array.} + */ +ol.webgl.CONTEXT_IDS_ = [ + 'experimental-webgl', + 'webgl', + 'webkit-3d', + 'moz-webgl' +]; - /** - * @const - * @private - * @type {Array.} - */ - ol.webgl.CONTEXT_IDS_ = [ - 'experimental-webgl', - 'webgl', - 'webkit-3d', - 'moz-webgl' - ]; - - /** - * @param {HTMLCanvasElement} canvas Canvas. - * @param {Object=} opt_attributes Attributes. - * @return {WebGLRenderingContext} WebGL rendering context. - */ - ol.webgl.getContext = function(canvas, opt_attributes) { - var context, i, ii = ol.webgl.CONTEXT_IDS_.length; - for (i = 0; i < ii; ++i) { - try { - context = canvas.getContext(ol.webgl.CONTEXT_IDS_[i], opt_attributes); - if (context) { - return /** @type {!WebGLRenderingContext} */ (context); - } - } catch (e) { - // pass +/** + * @param {HTMLCanvasElement} canvas Canvas. + * @param {Object=} opt_attributes Attributes. + * @return {WebGLRenderingContext} WebGL rendering context. + */ +ol.webgl.getContext = function(canvas, opt_attributes) { + var context, i, ii = ol.webgl.CONTEXT_IDS_.length; + for (i = 0; i < ii; ++i) { + try { + context = canvas.getContext(ol.webgl.CONTEXT_IDS_[i], opt_attributes); + if (context) { + return /** @type {!WebGLRenderingContext} */ (context); } + } catch (e) { + // pass } - return null; - }; - -} + } + return null; +}; diff --git a/src/ol/webgl/buffer.js b/src/ol/webgl/buffer.js index 19cc90bcdc..139bac88ae 100644 --- a/src/ol/webgl/buffer.js +++ b/src/ol/webgl/buffer.js @@ -4,56 +4,52 @@ goog.require('ol'); goog.require('ol.webgl'); -if (ol.ENABLE_WEBGL) { +/** + * @constructor + * @param {Array.=} opt_arr Array. + * @param {number=} opt_usage Usage. + * @struct + */ +ol.webgl.Buffer = function(opt_arr, opt_usage) { /** - * @constructor - * @param {Array.=} opt_arr Array. - * @param {number=} opt_usage Usage. - * @struct - */ - ol.webgl.Buffer = function(opt_arr, opt_usage) { - - /** - * @private - * @type {Array.} - */ - this.arr_ = opt_arr !== undefined ? opt_arr : []; - - /** - * @private - * @type {number} - */ - this.usage_ = opt_usage !== undefined ? - opt_usage : ol.webgl.Buffer.Usage_.STATIC_DRAW; - - }; - - - /** - * @return {Array.} Array. - */ - ol.webgl.Buffer.prototype.getArray = function() { - return this.arr_; - }; - - - /** - * @return {number} Usage. - */ - ol.webgl.Buffer.prototype.getUsage = function() { - return this.usage_; - }; - - - /** - * @enum {number} * @private + * @type {Array.} */ - ol.webgl.Buffer.Usage_ = { - STATIC_DRAW: ol.webgl.STATIC_DRAW, - STREAM_DRAW: ol.webgl.STREAM_DRAW, - DYNAMIC_DRAW: ol.webgl.DYNAMIC_DRAW - }; + this.arr_ = opt_arr !== undefined ? opt_arr : []; -} + /** + * @private + * @type {number} + */ + this.usage_ = opt_usage !== undefined ? + opt_usage : ol.webgl.Buffer.Usage_.STATIC_DRAW; + +}; + + +/** + * @return {Array.} Array. + */ +ol.webgl.Buffer.prototype.getArray = function() { + return this.arr_; +}; + + +/** + * @return {number} Usage. + */ +ol.webgl.Buffer.prototype.getUsage = function() { + return this.usage_; +}; + + +/** + * @enum {number} + * @private + */ +ol.webgl.Buffer.Usage_ = { + STATIC_DRAW: ol.webgl.STATIC_DRAW, + STREAM_DRAW: ol.webgl.STREAM_DRAW, + DYNAMIC_DRAW: ol.webgl.DYNAMIC_DRAW +}; diff --git a/src/ol/webgl/context.js b/src/ol/webgl/context.js index f3695633f1..f851c14e9b 100644 --- a/src/ol/webgl/context.js +++ b/src/ol/webgl/context.js @@ -9,367 +9,363 @@ goog.require('ol.webgl'); goog.require('ol.webgl.ContextEventType'); -if (ol.ENABLE_WEBGL) { +/** + * @classdesc + * A WebGL context for accessing low-level WebGL capabilities. + * + * @constructor + * @extends {ol.Disposable} + * @param {HTMLCanvasElement} canvas Canvas. + * @param {WebGLRenderingContext} gl GL. + */ +ol.webgl.Context = function(canvas, gl) { /** - * @classdesc - * A WebGL context for accessing low-level WebGL capabilities. - * - * @constructor - * @extends {ol.Disposable} - * @param {HTMLCanvasElement} canvas Canvas. - * @param {WebGLRenderingContext} gl GL. + * @private + * @type {HTMLCanvasElement} */ - ol.webgl.Context = function(canvas, gl) { - - /** - * @private - * @type {HTMLCanvasElement} - */ - this.canvas_ = canvas; - - /** - * @private - * @type {WebGLRenderingContext} - */ - this.gl_ = gl; - - /** - * @private - * @type {Object.} - */ - this.bufferCache_ = {}; - - /** - * @private - * @type {Object.} - */ - this.shaderCache_ = {}; - - /** - * @private - * @type {Object.} - */ - this.programCache_ = {}; - - /** - * @private - * @type {WebGLProgram} - */ - this.currentProgram_ = null; - - /** - * @private - * @type {WebGLFramebuffer} - */ - this.hitDetectionFramebuffer_ = null; - - /** - * @private - * @type {WebGLTexture} - */ - this.hitDetectionTexture_ = null; - - /** - * @private - * @type {WebGLRenderbuffer} - */ - this.hitDetectionRenderbuffer_ = null; - - /** - * @type {boolean} - */ - this.hasOESElementIndexUint = ol.array.includes( - ol.WEBGL_EXTENSIONS, 'OES_element_index_uint'); - - // use the OES_element_index_uint extension if available - if (this.hasOESElementIndexUint) { - gl.getExtension('OES_element_index_uint'); - } - - ol.events.listen(this.canvas_, ol.webgl.ContextEventType.LOST, - this.handleWebGLContextLost, this); - ol.events.listen(this.canvas_, ol.webgl.ContextEventType.RESTORED, - this.handleWebGLContextRestored, this); - - }; - ol.inherits(ol.webgl.Context, ol.Disposable); - + this.canvas_ = canvas; /** - * Just bind the buffer if it's in the cache. Otherwise create - * the WebGL buffer, bind it, populate it, and add an entry to - * the cache. - * @param {number} target Target. - * @param {ol.webgl.Buffer} buf Buffer. + * @private + * @type {WebGLRenderingContext} */ - ol.webgl.Context.prototype.bindBuffer = function(target, buf) { - var gl = this.getGL(); - var arr = buf.getArray(); - var bufferKey = String(ol.getUid(buf)); - if (bufferKey in this.bufferCache_) { - var bufferCacheEntry = this.bufferCache_[bufferKey]; - gl.bindBuffer(target, bufferCacheEntry.buffer); - } else { - var buffer = gl.createBuffer(); - gl.bindBuffer(target, buffer); - var /** @type {ArrayBufferView} */ arrayBuffer; - if (target == ol.webgl.ARRAY_BUFFER) { - arrayBuffer = new Float32Array(arr); - } else if (target == ol.webgl.ELEMENT_ARRAY_BUFFER) { - arrayBuffer = this.hasOESElementIndexUint ? - new Uint32Array(arr) : new Uint16Array(arr); - } - gl.bufferData(target, arrayBuffer, buf.getUsage()); - this.bufferCache_[bufferKey] = { - buf: buf, - buffer: buffer - }; - } - }; - + this.gl_ = gl; /** - * @param {ol.webgl.Buffer} buf Buffer. + * @private + * @type {Object.} */ - ol.webgl.Context.prototype.deleteBuffer = function(buf) { - var gl = this.getGL(); - var bufferKey = String(ol.getUid(buf)); + this.bufferCache_ = {}; + + /** + * @private + * @type {Object.} + */ + this.shaderCache_ = {}; + + /** + * @private + * @type {Object.} + */ + this.programCache_ = {}; + + /** + * @private + * @type {WebGLProgram} + */ + this.currentProgram_ = null; + + /** + * @private + * @type {WebGLFramebuffer} + */ + this.hitDetectionFramebuffer_ = null; + + /** + * @private + * @type {WebGLTexture} + */ + this.hitDetectionTexture_ = null; + + /** + * @private + * @type {WebGLRenderbuffer} + */ + this.hitDetectionRenderbuffer_ = null; + + /** + * @type {boolean} + */ + this.hasOESElementIndexUint = ol.array.includes( + ol.WEBGL_EXTENSIONS, 'OES_element_index_uint'); + + // use the OES_element_index_uint extension if available + if (this.hasOESElementIndexUint) { + gl.getExtension('OES_element_index_uint'); + } + + ol.events.listen(this.canvas_, ol.webgl.ContextEventType.LOST, + this.handleWebGLContextLost, this); + ol.events.listen(this.canvas_, ol.webgl.ContextEventType.RESTORED, + this.handleWebGLContextRestored, this); + +}; +ol.inherits(ol.webgl.Context, ol.Disposable); + + +/** + * Just bind the buffer if it's in the cache. Otherwise create + * the WebGL buffer, bind it, populate it, and add an entry to + * the cache. + * @param {number} target Target. + * @param {ol.webgl.Buffer} buf Buffer. + */ +ol.webgl.Context.prototype.bindBuffer = function(target, buf) { + var gl = this.getGL(); + var arr = buf.getArray(); + var bufferKey = String(ol.getUid(buf)); + if (bufferKey in this.bufferCache_) { var bufferCacheEntry = this.bufferCache_[bufferKey]; - if (!gl.isContextLost()) { - gl.deleteBuffer(bufferCacheEntry.buffer); + gl.bindBuffer(target, bufferCacheEntry.buffer); + } else { + var buffer = gl.createBuffer(); + gl.bindBuffer(target, buffer); + var /** @type {ArrayBufferView} */ arrayBuffer; + if (target == ol.webgl.ARRAY_BUFFER) { + arrayBuffer = new Float32Array(arr); + } else if (target == ol.webgl.ELEMENT_ARRAY_BUFFER) { + arrayBuffer = this.hasOESElementIndexUint ? + new Uint32Array(arr) : new Uint16Array(arr); } - delete this.bufferCache_[bufferKey]; - }; + gl.bufferData(target, arrayBuffer, buf.getUsage()); + this.bufferCache_[bufferKey] = { + buf: buf, + buffer: buffer + }; + } +}; - /** - * @inheritDoc - */ - ol.webgl.Context.prototype.disposeInternal = function() { - ol.events.unlistenAll(this.canvas_); +/** + * @param {ol.webgl.Buffer} buf Buffer. + */ +ol.webgl.Context.prototype.deleteBuffer = function(buf) { + var gl = this.getGL(); + var bufferKey = String(ol.getUid(buf)); + var bufferCacheEntry = this.bufferCache_[bufferKey]; + if (!gl.isContextLost()) { + gl.deleteBuffer(bufferCacheEntry.buffer); + } + delete this.bufferCache_[bufferKey]; +}; + + +/** + * @inheritDoc + */ +ol.webgl.Context.prototype.disposeInternal = function() { + ol.events.unlistenAll(this.canvas_); + var gl = this.getGL(); + if (!gl.isContextLost()) { + var key; + for (key in this.bufferCache_) { + gl.deleteBuffer(this.bufferCache_[key].buffer); + } + for (key in this.programCache_) { + gl.deleteProgram(this.programCache_[key]); + } + for (key in this.shaderCache_) { + gl.deleteShader(this.shaderCache_[key]); + } + // delete objects for hit-detection + gl.deleteFramebuffer(this.hitDetectionFramebuffer_); + gl.deleteRenderbuffer(this.hitDetectionRenderbuffer_); + gl.deleteTexture(this.hitDetectionTexture_); + } +}; + + +/** + * @return {HTMLCanvasElement} Canvas. + */ +ol.webgl.Context.prototype.getCanvas = function() { + return this.canvas_; +}; + + +/** + * Get the WebGL rendering context + * @return {WebGLRenderingContext} The rendering context. + * @api + */ +ol.webgl.Context.prototype.getGL = function() { + return this.gl_; +}; + + +/** + * Get the frame buffer for hit detection. + * @return {WebGLFramebuffer} The hit detection frame buffer. + */ +ol.webgl.Context.prototype.getHitDetectionFramebuffer = function() { + if (!this.hitDetectionFramebuffer_) { + this.initHitDetectionFramebuffer_(); + } + return this.hitDetectionFramebuffer_; +}; + + +/** + * Get shader from the cache if it's in the cache. Otherwise, create + * the WebGL shader, compile it, and add entry to cache. + * @param {ol.webgl.Shader} shaderObject Shader object. + * @return {WebGLShader} Shader. + */ +ol.webgl.Context.prototype.getShader = function(shaderObject) { + var shaderKey = String(ol.getUid(shaderObject)); + if (shaderKey in this.shaderCache_) { + return this.shaderCache_[shaderKey]; + } else { var gl = this.getGL(); - if (!gl.isContextLost()) { - var key; - for (key in this.bufferCache_) { - gl.deleteBuffer(this.bufferCache_[key].buffer); - } - for (key in this.programCache_) { - gl.deleteProgram(this.programCache_[key]); - } - for (key in this.shaderCache_) { - gl.deleteShader(this.shaderCache_[key]); - } - // delete objects for hit-detection - gl.deleteFramebuffer(this.hitDetectionFramebuffer_); - gl.deleteRenderbuffer(this.hitDetectionRenderbuffer_); - gl.deleteTexture(this.hitDetectionTexture_); - } - }; + var shader = gl.createShader(shaderObject.getType()); + gl.shaderSource(shader, shaderObject.getSource()); + gl.compileShader(shader); + this.shaderCache_[shaderKey] = shader; + return shader; + } +}; - /** - * @return {HTMLCanvasElement} Canvas. - */ - ol.webgl.Context.prototype.getCanvas = function() { - return this.canvas_; - }; +/** + * Get the program from the cache if it's in the cache. Otherwise create + * the WebGL program, attach the shaders to it, and add an entry to the + * cache. + * @param {ol.webgl.Fragment} fragmentShaderObject Fragment shader. + * @param {ol.webgl.Vertex} vertexShaderObject Vertex shader. + * @return {WebGLProgram} Program. + */ +ol.webgl.Context.prototype.getProgram = function( + fragmentShaderObject, vertexShaderObject) { + var programKey = + ol.getUid(fragmentShaderObject) + '/' + ol.getUid(vertexShaderObject); + if (programKey in this.programCache_) { + return this.programCache_[programKey]; + } else { + var gl = this.getGL(); + var program = gl.createProgram(); + gl.attachShader(program, this.getShader(fragmentShaderObject)); + gl.attachShader(program, this.getShader(vertexShaderObject)); + gl.linkProgram(program); + this.programCache_[programKey] = program; + return program; + } +}; - /** - * Get the WebGL rendering context - * @return {WebGLRenderingContext} The rendering context. - * @api - */ - ol.webgl.Context.prototype.getGL = function() { - return this.gl_; - }; +/** + * FIXME empy description for jsdoc + */ +ol.webgl.Context.prototype.handleWebGLContextLost = function() { + ol.obj.clear(this.bufferCache_); + ol.obj.clear(this.shaderCache_); + ol.obj.clear(this.programCache_); + this.currentProgram_ = null; + this.hitDetectionFramebuffer_ = null; + this.hitDetectionTexture_ = null; + this.hitDetectionRenderbuffer_ = null; +}; - /** - * Get the frame buffer for hit detection. - * @return {WebGLFramebuffer} The hit detection frame buffer. - */ - ol.webgl.Context.prototype.getHitDetectionFramebuffer = function() { - if (!this.hitDetectionFramebuffer_) { - this.initHitDetectionFramebuffer_(); - } - return this.hitDetectionFramebuffer_; - }; +/** + * FIXME empy description for jsdoc + */ +ol.webgl.Context.prototype.handleWebGLContextRestored = function() { +}; - /** - * Get shader from the cache if it's in the cache. Otherwise, create - * the WebGL shader, compile it, and add entry to cache. - * @param {ol.webgl.Shader} shaderObject Shader object. - * @return {WebGLShader} Shader. - */ - ol.webgl.Context.prototype.getShader = function(shaderObject) { - var shaderKey = String(ol.getUid(shaderObject)); - if (shaderKey in this.shaderCache_) { - return this.shaderCache_[shaderKey]; - } else { - var gl = this.getGL(); - var shader = gl.createShader(shaderObject.getType()); - gl.shaderSource(shader, shaderObject.getSource()); - gl.compileShader(shader); - this.shaderCache_[shaderKey] = shader; - return shader; - } - }; +/** + * Creates a 1x1 pixel framebuffer for the hit-detection. + * @private + */ +ol.webgl.Context.prototype.initHitDetectionFramebuffer_ = function() { + var gl = this.gl_; + var framebuffer = gl.createFramebuffer(); + gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); + + var texture = ol.webgl.Context.createEmptyTexture(gl, 1, 1); + var renderbuffer = gl.createRenderbuffer(); + gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, 1, 1); + gl.framebufferTexture2D( + gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, + gl.RENDERBUFFER, renderbuffer); + + gl.bindTexture(gl.TEXTURE_2D, null); + gl.bindRenderbuffer(gl.RENDERBUFFER, null); + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + + this.hitDetectionFramebuffer_ = framebuffer; + this.hitDetectionTexture_ = texture; + this.hitDetectionRenderbuffer_ = renderbuffer; +}; - /** - * Get the program from the cache if it's in the cache. Otherwise create - * the WebGL program, attach the shaders to it, and add an entry to the - * cache. - * @param {ol.webgl.Fragment} fragmentShaderObject Fragment shader. - * @param {ol.webgl.Vertex} vertexShaderObject Vertex shader. - * @return {WebGLProgram} Program. - */ - ol.webgl.Context.prototype.getProgram = function( - fragmentShaderObject, vertexShaderObject) { - var programKey = - ol.getUid(fragmentShaderObject) + '/' + ol.getUid(vertexShaderObject); - if (programKey in this.programCache_) { - return this.programCache_[programKey]; - } else { - var gl = this.getGL(); - var program = gl.createProgram(); - gl.attachShader(program, this.getShader(fragmentShaderObject)); - gl.attachShader(program, this.getShader(vertexShaderObject)); - gl.linkProgram(program); - this.programCache_[programKey] = program; - return program; - } - }; +/** + * Use a program. If the program is already in use, this will return `false`. + * @param {WebGLProgram} program Program. + * @return {boolean} Changed. + * @api + */ +ol.webgl.Context.prototype.useProgram = function(program) { + if (program == this.currentProgram_) { + return false; + } else { + var gl = this.getGL(); + gl.useProgram(program); + this.currentProgram_ = program; + return true; + } +}; - /** - * FIXME empy description for jsdoc - */ - ol.webgl.Context.prototype.handleWebGLContextLost = function() { - ol.obj.clear(this.bufferCache_); - ol.obj.clear(this.shaderCache_); - ol.obj.clear(this.programCache_); - this.currentProgram_ = null; - this.hitDetectionFramebuffer_ = null; - this.hitDetectionTexture_ = null; - this.hitDetectionRenderbuffer_ = null; - }; +/** + * @param {WebGLRenderingContext} gl WebGL rendering context. + * @param {number=} opt_wrapS wrapS. + * @param {number=} opt_wrapT wrapT. + * @return {WebGLTexture} The texture. + * @private + */ +ol.webgl.Context.createTexture_ = function(gl, opt_wrapS, opt_wrapT) { + var texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + + if (opt_wrapS !== undefined) { + gl.texParameteri( + ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_WRAP_S, opt_wrapS); + } + if (opt_wrapT !== undefined) { + gl.texParameteri( + ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_WRAP_T, opt_wrapT); + } + + return texture; +}; - /** - * FIXME empy description for jsdoc - */ - ol.webgl.Context.prototype.handleWebGLContextRestored = function() { - }; +/** + * @param {WebGLRenderingContext} gl WebGL rendering context. + * @param {number} width Width. + * @param {number} height Height. + * @param {number=} opt_wrapS wrapS. + * @param {number=} opt_wrapT wrapT. + * @return {WebGLTexture} The texture. + */ +ol.webgl.Context.createEmptyTexture = function( + gl, width, height, opt_wrapS, opt_wrapT) { + var texture = ol.webgl.Context.createTexture_(gl, opt_wrapS, opt_wrapT); + gl.texImage2D( + gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, + null); + + return texture; +}; - /** - * Creates a 1x1 pixel framebuffer for the hit-detection. - * @private - */ - ol.webgl.Context.prototype.initHitDetectionFramebuffer_ = function() { - var gl = this.gl_; - var framebuffer = gl.createFramebuffer(); - gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); +/** + * @param {WebGLRenderingContext} gl WebGL rendering context. + * @param {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} image Image. + * @param {number=} opt_wrapS wrapS. + * @param {number=} opt_wrapT wrapT. + * @return {WebGLTexture} The texture. + */ +ol.webgl.Context.createTexture = function(gl, image, opt_wrapS, opt_wrapT) { + var texture = ol.webgl.Context.createTexture_(gl, opt_wrapS, opt_wrapT); + gl.texImage2D( + gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); - var texture = ol.webgl.Context.createEmptyTexture(gl, 1, 1); - var renderbuffer = gl.createRenderbuffer(); - gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer); - gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, 1, 1); - gl.framebufferTexture2D( - gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); - gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, - gl.RENDERBUFFER, renderbuffer); - - gl.bindTexture(gl.TEXTURE_2D, null); - gl.bindRenderbuffer(gl.RENDERBUFFER, null); - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - - this.hitDetectionFramebuffer_ = framebuffer; - this.hitDetectionTexture_ = texture; - this.hitDetectionRenderbuffer_ = renderbuffer; - }; - - - /** - * Use a program. If the program is already in use, this will return `false`. - * @param {WebGLProgram} program Program. - * @return {boolean} Changed. - * @api - */ - ol.webgl.Context.prototype.useProgram = function(program) { - if (program == this.currentProgram_) { - return false; - } else { - var gl = this.getGL(); - gl.useProgram(program); - this.currentProgram_ = program; - return true; - } - }; - - - /** - * @param {WebGLRenderingContext} gl WebGL rendering context. - * @param {number=} opt_wrapS wrapS. - * @param {number=} opt_wrapT wrapT. - * @return {WebGLTexture} The texture. - * @private - */ - ol.webgl.Context.createTexture_ = function(gl, opt_wrapS, opt_wrapT) { - var texture = gl.createTexture(); - gl.bindTexture(gl.TEXTURE_2D, texture); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - - if (opt_wrapS !== undefined) { - gl.texParameteri( - ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_WRAP_S, opt_wrapS); - } - if (opt_wrapT !== undefined) { - gl.texParameteri( - ol.webgl.TEXTURE_2D, ol.webgl.TEXTURE_WRAP_T, opt_wrapT); - } - - return texture; - }; - - - /** - * @param {WebGLRenderingContext} gl WebGL rendering context. - * @param {number} width Width. - * @param {number} height Height. - * @param {number=} opt_wrapS wrapS. - * @param {number=} opt_wrapT wrapT. - * @return {WebGLTexture} The texture. - */ - ol.webgl.Context.createEmptyTexture = function( - gl, width, height, opt_wrapS, opt_wrapT) { - var texture = ol.webgl.Context.createTexture_(gl, opt_wrapS, opt_wrapT); - gl.texImage2D( - gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, - null); - - return texture; - }; - - - /** - * @param {WebGLRenderingContext} gl WebGL rendering context. - * @param {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} image Image. - * @param {number=} opt_wrapS wrapS. - * @param {number=} opt_wrapT wrapT. - * @return {WebGLTexture} The texture. - */ - ol.webgl.Context.createTexture = function(gl, image, opt_wrapS, opt_wrapT) { - var texture = ol.webgl.Context.createTexture_(gl, opt_wrapS, opt_wrapT); - gl.texImage2D( - gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); - - return texture; - }; - -} + return texture; +}; diff --git a/src/ol/webgl/fragment.js b/src/ol/webgl/fragment.js index 1d1bbc728f..49bb0f0445 100644 --- a/src/ol/webgl/fragment.js +++ b/src/ol/webgl/fragment.js @@ -5,25 +5,21 @@ goog.require('ol.webgl'); goog.require('ol.webgl.Shader'); -if (ol.ENABLE_WEBGL) { - - /** - * @constructor - * @extends {ol.webgl.Shader} - * @param {string} source Source. - * @struct - */ - ol.webgl.Fragment = function(source) { - ol.webgl.Shader.call(this, source); - }; - ol.inherits(ol.webgl.Fragment, ol.webgl.Shader); +/** + * @constructor + * @extends {ol.webgl.Shader} + * @param {string} source Source. + * @struct + */ +ol.webgl.Fragment = function(source) { + ol.webgl.Shader.call(this, source); +}; +ol.inherits(ol.webgl.Fragment, ol.webgl.Shader); - /** - * @inheritDoc - */ - ol.webgl.Fragment.prototype.getType = function() { - return ol.webgl.FRAGMENT_SHADER; - }; - -} +/** + * @inheritDoc + */ +ol.webgl.Fragment.prototype.getType = function() { + return ol.webgl.FRAGMENT_SHADER; +}; diff --git a/src/ol/webgl/shader.js b/src/ol/webgl/shader.js index 4a4f507a06..688260e8d3 100644 --- a/src/ol/webgl/shader.js +++ b/src/ol/webgl/shader.js @@ -4,43 +4,39 @@ goog.require('ol'); goog.require('ol.functions'); -if (ol.ENABLE_WEBGL) { +/** + * @constructor + * @abstract + * @param {string} source Source. + * @struct + */ +ol.webgl.Shader = function(source) { /** - * @constructor - * @abstract - * @param {string} source Source. - * @struct + * @private + * @type {string} */ - ol.webgl.Shader = function(source) { + this.source_ = source; - /** - * @private - * @type {string} - */ - this.source_ = source; - - }; +}; - /** - * @abstract - * @return {number} Type. - */ - ol.webgl.Shader.prototype.getType = function() {}; +/** + * @abstract + * @return {number} Type. + */ +ol.webgl.Shader.prototype.getType = function() {}; - /** - * @return {string} Source. - */ - ol.webgl.Shader.prototype.getSource = function() { - return this.source_; - }; +/** + * @return {string} Source. + */ +ol.webgl.Shader.prototype.getSource = function() { + return this.source_; +}; - /** - * @return {boolean} Is animated? - */ - ol.webgl.Shader.prototype.isAnimated = ol.functions.FALSE; - -} +/** + * @return {boolean} Is animated? + */ +ol.webgl.Shader.prototype.isAnimated = ol.functions.FALSE; diff --git a/src/ol/webgl/shader.mustache b/src/ol/webgl/shader.mustache index 46a003fd2a..0d9bc3acaf 100644 --- a/src/ol/webgl/shader.mustache +++ b/src/ol/webgl/shader.mustache @@ -6,105 +6,101 @@ goog.require('ol'); goog.require('ol.webgl.Fragment'); goog.require('ol.webgl.Vertex'); -if (ol.ENABLE_WEBGL) { +/** + * @constructor + * @extends {ol.webgl.Fragment} + * @struct + */ +{{className}}.Fragment = function() { + ol.webgl.Fragment.call(this, {{className}}.Fragment.SOURCE); +}; +ol.inherits({{className}}.Fragment, ol.webgl.Fragment); + + +/** + * @const + * @type {string} + */ +{{className}}.Fragment.DEBUG_SOURCE = 'precision mediump float;\n{{{getOriginalFragmentSource}}}'; + + +/** + * @const + * @type {string} + */ +{{className}}.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;{{{getFragmentSource}}}'; + + +/** + * @const + * @type {string} + */ +{{className}}.Fragment.SOURCE = ol.DEBUG_WEBGL ? + {{className}}.Fragment.DEBUG_SOURCE : + {{className}}.Fragment.OPTIMIZED_SOURCE; + + +{{className}}.fragment = new {{className}}.Fragment(); + + +/** + * @constructor + * @extends {ol.webgl.Vertex} + * @struct + */ +{{className}}.Vertex = function() { + ol.webgl.Vertex.call(this, {{className}}.Vertex.SOURCE); +}; +ol.inherits({{className}}.Vertex, ol.webgl.Vertex); + + +/** + * @const + * @type {string} + */ +{{className}}.Vertex.DEBUG_SOURCE = '{{{getOriginalVertexSource}}}'; + + +/** + * @const + * @type {string} + */ +{{className}}.Vertex.OPTIMIZED_SOURCE = '{{{getVertexSource}}}'; + + +/** + * @const + * @type {string} + */ +{{className}}.Vertex.SOURCE = ol.DEBUG_WEBGL ? + {{className}}.Vertex.DEBUG_SOURCE : + {{className}}.Vertex.OPTIMIZED_SOURCE; + + +{{className}}.vertex = new {{className}}.Vertex(); + + +/** + * @constructor + * @param {WebGLRenderingContext} gl GL. + * @param {WebGLProgram} program Program. + * @struct + */ +{{namespace}}.Locations = function(gl, program) { +{{#getUniforms}} /** - * @constructor - * @extends {ol.webgl.Fragment} - * @struct + * @type {WebGLUniformLocation} */ - {{className}}.Fragment = function() { - ol.webgl.Fragment.call(this, {{className}}.Fragment.SOURCE); - }; - ol.inherits({{className}}.Fragment, ol.webgl.Fragment); - + this.{{originalName}} = gl.getUniformLocation( + program, ol.DEBUG_WEBGL ? '{{originalName}}' : '{{shortName}}'); +{{/getUniforms}} +{{#getAttributes}} /** - * @const - * @type {string} + * @type {number} */ - {{className}}.Fragment.DEBUG_SOURCE = 'precision mediump float;\n{{{getOriginalFragmentSource}}}'; - - - /** - * @const - * @type {string} - */ - {{className}}.Fragment.OPTIMIZED_SOURCE = 'precision mediump float;{{{getFragmentSource}}}'; - - - /** - * @const - * @type {string} - */ - {{className}}.Fragment.SOURCE = ol.DEBUG_WEBGL ? - {{className}}.Fragment.DEBUG_SOURCE : - {{className}}.Fragment.OPTIMIZED_SOURCE; - - - {{className}}.fragment = new {{className}}.Fragment(); - - - /** - * @constructor - * @extends {ol.webgl.Vertex} - * @struct - */ - {{className}}.Vertex = function() { - ol.webgl.Vertex.call(this, {{className}}.Vertex.SOURCE); - }; - ol.inherits({{className}}.Vertex, ol.webgl.Vertex); - - - /** - * @const - * @type {string} - */ - {{className}}.Vertex.DEBUG_SOURCE = '{{{getOriginalVertexSource}}}'; - - - /** - * @const - * @type {string} - */ - {{className}}.Vertex.OPTIMIZED_SOURCE = '{{{getVertexSource}}}'; - - - /** - * @const - * @type {string} - */ - {{className}}.Vertex.SOURCE = ol.DEBUG_WEBGL ? - {{className}}.Vertex.DEBUG_SOURCE : - {{className}}.Vertex.OPTIMIZED_SOURCE; - - - {{className}}.vertex = new {{className}}.Vertex(); - - - /** - * @constructor - * @param {WebGLRenderingContext} gl GL. - * @param {WebGLProgram} program Program. - * @struct - */ - {{namespace}}.Locations = function(gl, program) { - {{#getUniforms}} - - /** - * @type {WebGLUniformLocation} - */ - this.{{originalName}} = gl.getUniformLocation( - program, ol.DEBUG_WEBGL ? '{{originalName}}' : '{{shortName}}'); - {{/getUniforms}} - {{#getAttributes}} - - /** - * @type {number} - */ - this.{{originalName}} = gl.getAttribLocation( - program, ol.DEBUG_WEBGL ? '{{originalName}}' : '{{shortName}}'); - {{/getAttributes}} - }; - -} + this.{{originalName}} = gl.getAttribLocation( + program, ol.DEBUG_WEBGL ? '{{originalName}}' : '{{shortName}}'); +{{/getAttributes}} +}; diff --git a/src/ol/webgl/vertex.js b/src/ol/webgl/vertex.js index c68a9a2fe4..024c7dc012 100644 --- a/src/ol/webgl/vertex.js +++ b/src/ol/webgl/vertex.js @@ -5,25 +5,21 @@ goog.require('ol.webgl'); goog.require('ol.webgl.Shader'); -if (ol.ENABLE_WEBGL) { - - /** - * @constructor - * @extends {ol.webgl.Shader} - * @param {string} source Source. - * @struct - */ - ol.webgl.Vertex = function(source) { - ol.webgl.Shader.call(this, source); - }; - ol.inherits(ol.webgl.Vertex, ol.webgl.Shader); +/** + * @constructor + * @extends {ol.webgl.Shader} + * @param {string} source Source. + * @struct + */ +ol.webgl.Vertex = function(source) { + ol.webgl.Shader.call(this, source); +}; +ol.inherits(ol.webgl.Vertex, ol.webgl.Shader); - /** - * @inheritDoc - */ - ol.webgl.Vertex.prototype.getType = function() { - return ol.webgl.VERTEX_SHADER; - }; - -} +/** + * @inheritDoc + */ +ol.webgl.Vertex.prototype.getType = function() { + return ol.webgl.VERTEX_SHADER; +}; From f4420c535222ef4a5307f7bb401357a79aa3524a Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Thu, 17 Aug 2017 13:22:35 -0400 Subject: [PATCH 2/2] Do not exclude WebGL code in transformed files --- transforms/module.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/transforms/module.js b/transforms/module.js index fab7483e34..a0403e8548 100644 --- a/transforms/module.js +++ b/transforms/module.js @@ -2,7 +2,8 @@ const parentPackage = require('../package.json'); const thisPackage = require('../package/package.json'); const defines = { - 'ol.ENABLE_WEBGL': false + // Compiler defines go here, e.g. + // 'ol.ENABLE_WEBGL': false }; function rename(name) {